39 changed files with 39 additions and 289 deletions
@ -1,22 +0,0 @@ |
|||||
package com.xsn.explorer.errors |
|
||||
|
|
||||
import com.alexitc.playsonify.models.{FieldValidationError, InputValidationError, PublicError} |
|
||||
import play.api.i18n.{Lang, MessagesApi} |
|
||||
|
|
||||
sealed trait PaginatedQueryError |
|
||||
|
|
||||
case object PaginatedQueryOffsetError extends PaginatedQueryError with InputValidationError { |
|
||||
override def toPublicErrorList(messagesApi: MessagesApi)(implicit lang: Lang): List[PublicError] = { |
|
||||
val message = messagesApi("error.paginatedQuery.offset.invalid") |
|
||||
val error = FieldValidationError("offset", message) |
|
||||
List(error) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
case class PaginatedQueryLimitError(maxValue: Int) extends PaginatedQueryError with InputValidationError { |
|
||||
override def toPublicErrorList(messagesApi: MessagesApi)(implicit lang: Lang): List[PublicError] = { |
|
||||
val message = messagesApi("error.paginatedQuery.limit.invalid", maxValue) |
|
||||
val error = FieldValidationError("limit", message) |
|
||||
List(error) |
|
||||
} |
|
||||
} |
|
@ -1,10 +0,0 @@ |
|||||
package com.xsn.explorer.models.base |
|
||||
|
|
||||
import play.api.libs.json.{JsNumber, Writes} |
|
||||
|
|
||||
case class Count(int: Int) extends AnyVal |
|
||||
|
|
||||
object Count { |
|
||||
|
|
||||
implicit val writes: Writes[Count] = Writes[Count] { count => JsNumber(count.int) } |
|
||||
} |
|
@ -1,3 +0,0 @@ |
|||||
package com.xsn.explorer.models.base |
|
||||
|
|
||||
case class FieldOrdering[+A](field: A, orderingCondition: OrderingCondition) |
|
@ -1,10 +0,0 @@ |
|||||
package com.xsn.explorer.models.base |
|
||||
|
|
||||
import play.api.libs.json.{JsNumber, Writes} |
|
||||
|
|
||||
case class Limit(int: Int) extends AnyVal |
|
||||
|
|
||||
object Limit { |
|
||||
|
|
||||
implicit val writes: Writes[Limit] = Writes[Limit] { limit => JsNumber(limit.int) } |
|
||||
} |
|
@ -1,10 +0,0 @@ |
|||||
package com.xsn.explorer.models.base |
|
||||
|
|
||||
import play.api.libs.json.{JsNumber, Writes} |
|
||||
|
|
||||
case class Offset(int: Int) extends AnyVal |
|
||||
|
|
||||
object Offset { |
|
||||
|
|
||||
implicit val writes: Writes[Offset] = Writes[Offset] { offset => JsNumber(offset.int) } |
|
||||
} |
|
@ -1,9 +0,0 @@ |
|||||
package com.xsn.explorer.models.base |
|
||||
|
|
||||
sealed trait OrderingCondition |
|
||||
|
|
||||
object OrderingCondition { |
|
||||
|
|
||||
case object AscendingOrder extends OrderingCondition |
|
||||
case object DescendingOrder extends OrderingCondition |
|
||||
} |
|
@ -1,3 +0,0 @@ |
|||||
package com.xsn.explorer.models.base |
|
||||
|
|
||||
case class OrderingQuery(string: String) extends AnyVal |
|
@ -1,3 +0,0 @@ |
|||||
package com.xsn.explorer.models.base |
|
||||
|
|
||||
case class PaginatedQuery(offset: Offset, limit: Limit) |
|
@ -1,15 +0,0 @@ |
|||||
package com.xsn.explorer.models.base |
|
||||
|
|
||||
import play.api.libs.json.{Json, OWrites, Writes} |
|
||||
|
|
||||
case class PaginatedResult[T](offset: Offset, limit: Limit, total: Count, data: List[T]) |
|
||||
object PaginatedResult { |
|
||||
implicit def writes[T](implicit writesT: Writes[T]): Writes[PaginatedResult[T]] = OWrites[PaginatedResult[T]] { result => |
|
||||
Json.obj( |
|
||||
"offset" -> result.offset, |
|
||||
"limit" -> result.limit, |
|
||||
"total" -> result.total, |
|
||||
"data" -> result.data |
|
||||
) |
|
||||
} |
|
||||
} |
|
@ -1,14 +0,0 @@ |
|||||
package com.xsn.explorer.models.base |
|
||||
|
|
||||
import play.api.libs.json.{JsNumber, Writes} |
|
||||
|
|
||||
trait WrappedInt extends Any { |
|
||||
def int: Int |
|
||||
} |
|
||||
|
|
||||
object WrappedInt { |
|
||||
|
|
||||
implicit val writes: Writes[WrappedInt] = { |
|
||||
Writes[WrappedInt] { wrapped => JsNumber(wrapped.int) } |
|
||||
} |
|
||||
} |
|
@ -1,16 +0,0 @@ |
|||||
package com.xsn.explorer.models.base |
|
||||
|
|
||||
import play.api.libs.json.{JsString, Writes} |
|
||||
|
|
||||
trait WrappedString extends Any { |
|
||||
def string: String |
|
||||
|
|
||||
override def toString: String = string |
|
||||
} |
|
||||
|
|
||||
object WrappedString { |
|
||||
|
|
||||
implicit val writes: Writes[WrappedString] = { |
|
||||
Writes[WrappedString] { wrapped => JsString(wrapped.string) } |
|
||||
} |
|
||||
} |
|
@ -1,64 +0,0 @@ |
|||||
package com.xsn.explorer.parsers |
|
||||
|
|
||||
import com.alexitc.playsonify.core.{ApplicationErrors, ApplicationResult} |
|
||||
import com.xsn.explorer.errors.{InvalidOrderingConditionError, InvalidOrderingError, UnknownOrderingFieldError} |
|
||||
import com.xsn.explorer.models.base.{FieldOrdering, OrderingCondition, OrderingQuery} |
|
||||
import org.scalactic._ |
|
||||
|
|
||||
trait FieldOrderingParser[+A] { |
|
||||
|
|
||||
protected def parseField(unsafeField: String): Option[A] |
|
||||
|
|
||||
protected def defaultField: A |
|
||||
|
|
||||
protected def defaultOrderingCondition: OrderingCondition = OrderingCondition.AscendingOrder |
|
||||
|
|
||||
/** |
|
||||
* Accepts values in the format field[:condition], being condition |
|
||||
* an optional argument allowing the these values: |
|
||||
* - asc: for ascending order. |
|
||||
* - desc: for descending order. |
|
||||
* |
|
||||
* The empty string is also accepted returning a default ordering. |
|
||||
*/ |
|
||||
def from(orderByQuery: OrderingQuery): ApplicationResult[FieldOrdering[A]] = { |
|
||||
Option(orderByQuery.string) |
|
||||
.filter(_.nonEmpty) |
|
||||
.map { string => from(string.split(":")) } |
|
||||
.getOrElse { |
|
||||
val ordering = FieldOrdering(defaultField, defaultOrderingCondition) |
|
||||
Good(ordering) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private def from(parts: Seq[String]): FieldOrdering[A] Or ApplicationErrors = parts match { |
|
||||
case Seq(unsafeField) => |
|
||||
for { |
|
||||
field <- getFieldResult(unsafeField) |
|
||||
} yield FieldOrdering(field, defaultOrderingCondition) |
|
||||
|
|
||||
case Seq(unsafeField, unsafeOrderingCondition) => |
|
||||
Accumulation.withGood( |
|
||||
getFieldResult(unsafeField), |
|
||||
getOrderingConditionResult(unsafeOrderingCondition)) { FieldOrdering.apply } |
|
||||
|
|
||||
case _ => |
|
||||
Bad(InvalidOrderingError).accumulating |
|
||||
} |
|
||||
|
|
||||
private def getFieldResult(unsafeField: String) = { |
|
||||
val maybe = parseField(unsafeField) |
|
||||
Or.from(maybe, One(UnknownOrderingFieldError)) |
|
||||
} |
|
||||
|
|
||||
private def getOrderingConditionResult(unsafeOrderingCondition: String) = { |
|
||||
val maybe = parseOrderingCondition(unsafeOrderingCondition) |
|
||||
Or.from(maybe, One(InvalidOrderingConditionError)) |
|
||||
} |
|
||||
|
|
||||
protected def parseOrderingCondition(unsafeOrderingCondition: String): Option[OrderingCondition] = unsafeOrderingCondition match { |
|
||||
case "asc" => Some(OrderingCondition.AscendingOrder) |
|
||||
case "desc" => Some(OrderingCondition.DescendingOrder) |
|
||||
case _ => None |
|
||||
} |
|
||||
} |
|
@ -1,37 +0,0 @@ |
|||||
package com.xsn.explorer.services.validators |
|
||||
|
|
||||
import com.alexitc.playsonify.core.ApplicationResult |
|
||||
import com.xsn.explorer.errors.{PaginatedQueryLimitError, PaginatedQueryOffsetError} |
|
||||
import com.xsn.explorer.models.base.{Limit, Offset, PaginatedQuery} |
|
||||
import org.scalactic.{Accumulation, Bad, Good} |
|
||||
|
|
||||
class PaginatedQueryValidator { |
|
||||
|
|
||||
private val MinOffset = 0 |
|
||||
private val LimitRange = 1 to 100 |
|
||||
|
|
||||
def validate(query: PaginatedQuery): ApplicationResult[PaginatedQuery] = { |
|
||||
Accumulation.withGood( |
|
||||
validateOffset(query.offset), |
|
||||
validateLimit(query.limit)) { |
|
||||
|
|
||||
PaginatedQuery.apply |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private def validateOffset(offset: Offset): ApplicationResult[Offset] = { |
|
||||
if (offset.int >= MinOffset) { |
|
||||
Good(offset) |
|
||||
} else { |
|
||||
Bad(PaginatedQueryOffsetError).accumulating |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private def validateLimit(limit: Limit): ApplicationResult[Limit] = { |
|
||||
if (LimitRange contains limit.int) { |
|
||||
Good(limit) |
|
||||
} else { |
|
||||
Bad(PaginatedQueryLimitError(LimitRange.last)).accumulating |
|
||||
} |
|
||||
} |
|
||||
} |
|
Loading…
Reference in new issue