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