Skip to content

Commit

Permalink
feat: added missing entities validations
Browse files Browse the repository at this point in the history
  • Loading branch information
rodobarcaaa committed Oct 24, 2023
1 parent 8d2ab56 commit 08b54ea
Show file tree
Hide file tree
Showing 28 changed files with 167 additions and 88 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@ package com.example.books.application

import cats.effect.IO
import com.example.books.domain.author.{Author, AuthorRepository}
import com.example.books.domain.common.Id
import com.example.shared.application.CommonService
import com.example.shared.domain.common.Id
import com.example.shared.domain.page.{PageRequest, PageResponse}

final class AuthorService(repo: AuthorRepository) extends CommonService {

private def upsert(author: Author) = for {
_ <- validateRequest(author)
_ <- repo.upsert(author)
} yield ()
private def upsert(author: Author) = validateRequest(author) *> repo.upsert(author)

def create(author: Author): IO[Unit] = upsert(author)

Expand Down
11 changes: 7 additions & 4 deletions src/main/scala/com/example/books/application/BookService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@ package com.example.books.application

import cats.effect.IO
import com.example.books.domain.book.{Book, BookFilters, BookRepository}
import com.example.books.domain.common.Id
import com.example.shared.application.CommonService
import com.example.shared.domain.common.Id
import com.example.shared.domain.page.{PageRequest, PageResponse}

final class BookService(repo: BookRepository) {
final class BookService(repo: BookRepository) extends CommonService {

def create(book: Book): IO[Unit] = repo.upsert(book)
private def upsert(book: Book) = validateRequest(book) *> repo.upsert(book)

def update(id: Id, book: Book): IO[Unit] = repo.upsert(book.copy(id = id))
def create(book: Book): IO[Unit] = upsert(book)

def update(id: Id, book: Book): IO[Unit] = upsert(book.copy(id = id))

def find(id: Id): IO[Option[Book]] = repo.find(id)

Expand Down
19 changes: 0 additions & 19 deletions src/main/scala/com/example/books/application/CommonService.scala

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@ package com.example.books.application

import cats.effect.IO
import com.example.books.domain.publisher.{Publisher, PublisherRepository}
import com.example.books.domain.common.Id
import com.example.shared.application.CommonService
import com.example.shared.domain.common.Id
import com.example.shared.domain.page.{PageRequest, PageResponse}

final class PublisherService(repo: PublisherRepository) {
final class PublisherService(repo: PublisherRepository) extends CommonService {

def create(publisher: Publisher): IO[Unit] = repo.upsert(publisher)
private def upsert(publisher: Publisher) = validateRequest(publisher) *> repo.upsert(publisher)

def update(id: Id, publisher: Publisher): IO[Unit] = repo.upsert(publisher.copy(id = id))
def create(publisher: Publisher): IO[Unit] = upsert(publisher)

def update(id: Id, publisher: Publisher): IO[Unit] = upsert(publisher.copy(id = id))

def find(id: Id): IO[Option[Publisher]] = repo.find(id)

Expand Down
17 changes: 6 additions & 11 deletions src/main/scala/com/example/books/domain/author/Author.scala
Original file line number Diff line number Diff line change
@@ -1,31 +1,26 @@
package com.example.books.domain.author

import cats.data.ValidatedNel
import cats.implicits.catsSyntaxTuple2Semigroupal
import com.example.books.domain.common.{HasValidated, Id, Name}
import cats.implicits._
import com.example.shared.domain.common.{HasValidated, Id, Name}

import java.util.UUID

final case class Author(id: Id, firstName: Name, lastName: Name) extends HasValidated {
def completeName: String = s"$firstName $lastName"


override def validated: ValidatedNel[String, Unit] = (
firstName.validate("firstName", Author.maxLengthFirstName),
lastName.validate("lastName", Author.maxLengthLastName)
firstName.validate("firstName"),
lastName.validate("lastName")
).mapN((_, _) => ())
}

object Author {

private lazy val maxLengthFirstName = 255
private lazy val maxLengthLastName = 255

// apply, unapply and tupled methods to use by slick table mapping

def apply: (UUID, String, String) => Author = {
case (id, firstName, lastName) =>
Author(Id(id), Name(firstName), Name(lastName))
def apply: (UUID, String, String) => Author = { case (id, firstName, lastName) =>
Author(Id(id), Name(firstName), Name(lastName))
}

def unapply: Author => Option[(UUID, String, String)] = { author =>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.example.books.domain.author

import cats.effect.IO
import com.example.books.domain.common.Id
import com.example.shared.domain.common.Id
import com.example.shared.domain.page.{PageRequest, PageResponse}

trait AuthorRepository {
Expand Down
64 changes: 58 additions & 6 deletions src/main/scala/com/example/books/domain/book/Book.scala
Original file line number Diff line number Diff line change
@@ -1,13 +1,52 @@
package com.example.books.domain.book

import com.example.books.domain.common.Id
import cats.data._
import cats.implicits._
import com.example.books.domain.book.Book._
import com.example.shared.domain.common.{HasValidated, HasValidations, Id}

import java.time.LocalDate
import java.util.UUID

final case class BookTitle(value: String) extends AnyVal
final case class BookIsbn(value: String) extends AnyVal
final case class BookYear(value: Int) extends AnyVal
final case class BookDescription(value: String) extends AnyVal
final case class BookTitle(value: String) extends AnyVal {

def validate(tag: String = "title", maxLength: Int = maxLengthMediumText): ValidatedNel[String, Unit] = (
HasValidations.validateEmpty(value, tag),
Validated.condNel(value.length <= maxLength, (), s"$tag max length should be $maxLength characters")
).mapN((_, _) => ())

}

final case class BookIsbn(value: String) extends AnyVal {

def validate(tag: String = "isbn", maxLength: Int = maxLengthSmallText): ValidatedNel[String, Unit] = (
HasValidations.validateEmpty(value, tag),
Validated.condNel(value.length <= maxLength, (), s"$tag max length should be $maxLength characters")
).mapN((_, _) => ())

}

final case class BookDescription(value: String) extends AnyVal {

def validate(tag: String = "description", maxLength: Int = maxLengthLongText): ValidatedNel[String, Unit] = (
HasValidations.validateEmpty(value, tag),
Validated.condNel(value.length <= maxLength, (), s"$tag max length should be $maxLength characters")
).mapN((_, _) => ())

}

final case class BookYear(value: Int) extends AnyVal {

def validate(tag: String = "year"): ValidatedNel[String, Unit] = {
val currentYear = LocalDate.now.getYear
Validated.condNel(
value.toString.length == 4 && value >= 1900 && value <= currentYear,
(),
s"$tag should be a valid year"
)
}

}

final case class Book(
id: Id,
Expand All @@ -17,10 +56,23 @@ final case class Book(
year: BookYear,
publisherId: Id,
authorId: Id
)
) extends HasValidated {

override def validated: ValidatedNel[String, Unit] = (
isbn.validate(),
title.validate(),
description.validate(),
year.validate()
).mapN((_, _, _, _) => ())

}

object Book {

lazy val maxLengthSmallText: Int = 35
lazy val maxLengthMediumText: Int = 255
lazy val maxLengthLongText: Int = 1255

// apply, unapply and tupled methods to use by slick table mapping

def apply: (UUID, String, String, String, Int, UUID, UUID) => Book = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.example.books.domain.book

import cats.effect.IO
import com.example.books.domain.common.Id
import com.example.shared.domain.common.Id
import com.example.shared.domain.page.{PageRequest, PageResponse}

trait BookRepository {
Expand Down

This file was deleted.

14 changes: 0 additions & 14 deletions src/main/scala/com/example/books/domain/common/Name.scala

This file was deleted.

3 changes: 0 additions & 3 deletions src/main/scala/com/example/books/domain/common/URL.scala

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package com.example.books.domain.publisher

import com.example.books.domain.common.{Id, Name, URL}
import cats.data.ValidatedNel
import cats.implicits._
import com.example.shared.domain.common.{HasValidated, Id, Name, URL}

import java.util.UUID

final case class Publisher(id: Id, name: Name, url: URL)
final case class Publisher(id: Id, name: Name, url: URL) extends HasValidated {

override def validated: ValidatedNel[String, Unit] = (name.validate(), url.validate()).mapN((_, _) => ())
}

object Publisher {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.example.books.domain.publisher

import cats.effect.IO
import com.example.books.domain.common.Id
import com.example.shared.domain.common.Id
import com.example.shared.domain.page.{PageRequest, PageResponse}

trait PublisherRepository {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.example.books.infrastructure.codecs

import com.example.books.domain.author.Author
import com.example.shared.infrastructure.circe.CommonCodecs

trait AuthorCodecs extends CommonCodecs {
import io.circe._
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.example.books.infrastructure.codecs

import com.example.books.domain.book._
import com.example.shared.infrastructure.circe.CommonCodecs

trait BookCodecs extends CommonCodecs {
import io.circe._
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.example.books.infrastructure.codecs

import com.example.books.domain.publisher.Publisher
import com.example.shared.infrastructure.circe.CommonCodecs

trait PublisherCodecs extends CommonCodecs {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package com.example.books.infrastructure.http

import com.example.books.application.AuthorService
import com.example.books.domain.author.Author
import com.example.books.domain.common.Id
import com.example.books.infrastructure.codecs.AuthorCodecs
import com.example.shared.domain.common.Id
import com.example.shared.domain.page.{PageRequest, PageResponse}
import com.example.shared.infrastructure.http._

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package com.example.books.infrastructure.http

import com.example.books.application.BookService
import com.example.books.domain.book.{Book, BookFilters}
import com.example.books.domain.common.Id
import com.example.books.infrastructure.codecs.BookCodecs
import com.example.shared.domain.common.Id
import com.example.shared.domain.page.{PageRequest, PageResponse}
import com.example.shared.infrastructure.http._

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package com.example.books.infrastructure.http

import com.example.books.application.PublisherService
import com.example.books.domain.common.Id
import com.example.books.domain.publisher.Publisher
import com.example.books.infrastructure.codecs.PublisherCodecs
import com.example.shared.domain.common.Id
import com.example.shared.domain.page.{PageRequest, PageResponse}
import com.example.shared.infrastructure.http._

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package com.example.books.infrastructure.repository

import cats.effect.IO
import com.example.books.domain.author.{Author, AuthorRepository}
import com.example.books.domain.common.Id
import com.example.books.infrastructure.slick.AuthorMapping
import com.example.shared.domain.common.Id
import com.example.shared.domain.page.{PageRequest, PageResponse}
import com.example.shared.infrastructure.slick.HasSlickPgProvider

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package com.example.books.infrastructure.repository

import cats.effect.IO
import com.example.books.domain.book.{Book, BookFilters, BookRepository}
import com.example.books.domain.common.Id
import com.example.books.infrastructure.slick.BookMapping
import com.example.shared.domain.common.Id
import com.example.shared.domain.page.{PageRequest, PageResponse}
import com.example.shared.infrastructure.slick.HasSlickPgProvider

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package com.example.books.infrastructure.repository

import cats.effect.IO
import com.example.books.domain.common.Id
import com.example.books.domain.publisher.{Publisher, PublisherRepository}
import com.example.books.infrastructure.slick.PublisherMapping
import com.example.shared.domain.common.Id
import com.example.shared.domain.page.{PageRequest, PageResponse}
import com.example.shared.infrastructure.slick.HasSlickPgProvider

Expand Down
14 changes: 14 additions & 0 deletions src/main/scala/com/example/shared/application/CommonService.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.example.shared.application

import cats.effect.IO
import com.example.shared.domain.common.HasValidated
import com.example.shared.infrastructure.http.Fail

class CommonService {

def validateRequest(req: HasValidated): IO[Unit] = {
val errors = req.validated.fold(_.toList, _ => Nil)
IO.raiseWhen(errors.nonEmpty)(Fail.UnprocessableEntity(errors.mkString(", ")))
}

}
15 changes: 15 additions & 0 deletions src/main/scala/com/example/shared/domain/common/HasValidated.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.example.shared.domain.common

import cats.data.{Validated, ValidatedNel}

trait HasValidated {
def validated: ValidatedNel[String, Unit]
}

object HasValidations {

def validateEmpty(value: String, tag: String) = {
Validated.condNel(value.nonEmpty, (), s"$tag should not be empty")
}

}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.example.books.domain.common
package com.example.shared.domain.common

import java.util.UUID

Expand Down
Loading

0 comments on commit 08b54ea

Please sign in to comment.