All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.hireproof.structure.Schema.scala Maven / Gradle / Ivy

The newest version!
package io.hireproof.structure

import cats.Eval
import cats.data.{Chain, Validated}
import cats.syntax.all._
import io.circe.syntax._
import io.circe.{Decoder, Encoder, Json, JsonObject}
import io.hireproof.screening.validations.parsing
import io.hireproof.screening.{Constraint, Validation, Violation}

sealed abstract class Schema[A] extends Structure[A] {
  override type Self[a] <: Schema[a]

  def default: Option[A]
  def description: Option[String]
  def example: Option[A]
  def name: Option[String]
  def validations: Chain[Validation[_, _]]

  final def setDefault(default: Option[A]): Self[A] =
    vimapCopy[A](default, description, example, name, validations)(_.valid, identity)
  final def withDefault(default: A): Self[A] = setDefault(default.some)
  final def withoutDefault: Self[A] = setDefault(none)
  final def setDescription(description: Option[String]): Self[A] =
    vimapCopy[A](default, description, example, name, validations)(_.valid, identity)
  final def withDescription(description: String): Self[A] = setDescription(description.some)
  final def withoutDescription: Self[A] = setDescription(none)
  final def setName(description: Option[String]): Self[A] =
    vimapCopy[A](default, description, example, name, validations)(_.valid, identity)
  final def withName(description: String): Self[A] = setName(description.some)
  final def withoutName: Self[A] = setName(none)
  final def setExample(example: Option[A]): Self[A] =
    vimapCopy[A](default, description, example, name, validations)(_.valid, identity)
  final def withExample(example: A): Self[A] = setExample(example.some)
  final def withoutExample: Self[A] = setExample(none)

  final def jsonExample: Option[Json] = example.flatMap(toJson)
  final def stringExample: Option[String] = example.flatMap(toString)

  final def jsonDefault: Option[Json] = default.flatMap(toJson)
  final def stringDefault: Option[String] = default.flatMap(toString)

  final override private[structure] def vimap[T](
      f: A => Validated[Errors, T],
      g: T => A,
      validation: Option[Validation[_, _]]
  ): Self[T] = vimapCopy[T](default, description, example, name, validations ++ Chain.fromOption(validation))(f, g)

  protected def vimapCopy[T](
      default: Option[A],
      description: Option[String],
      example: Option[A],
      name: Option[String],
      validations: Chain[Validation[_, _]]
  )(f: A => Validated[Errors, T], g: T => A): Self[T]

  final protected def copy(
      default: Option[A],
      description: Option[String],
      example: Option[A],
      name: Option[String],
      validations: Chain[Validation[_, _]]
  ): Self[A] = vimapCopy[A](default, description, example, name, validations)(_.valid, identity)

  final def fromJson(json: Option[Json]): Validated[Errors, A] = fromJson(json, default)
  private[structure] def fromJson(json: Option[Json], default: Option[A]): Validated[Errors, A]
  final def fromString(value: Option[String]): Validated[Errors, A] = fromString(value, default)
  private[structure] def fromString(value: Option[String], default: Option[A]): Validated[Errors, A]
  def toJson(a: A): Option[Json]
  def toString(a: A): Option[String]
}

object Schema {
  private val Required: Errors = Errors.rootNel(Violation(Constraint.required, Json.Null))

  sealed abstract class Value[A](
      override val default: Option[A],
      override val description: Option[String],
      override val example: Option[A],
      val name: Option[String],
      override val validations: Chain[Validation[_, _]]
  ) extends Schema[A] {
    override type Self[a] <: Schema.Value[a]

    final def fromJsonValue(json: Json): Validated[Errors, A] = fromJsonValue(json, default)
    private[structure] def fromJsonValue(json: Json, default: Option[A]): Validated[Errors, A]
    final def fromStringValue(value: String): Validated[Errors, A] = fromStringValue(value, default)
    private[structure] def fromStringValue(value: String, default: Option[A]): Validated[Errors, A]
    def toJsonValue(a: A): Json
    def toStringValue(a: A): String
  }

  sealed abstract class Primitive[A](
      default: Option[A],
      description: Option[String],
      example: Option[A],
      val format: Option[String],
      name: Option[String],
      val tpe: Type,
      validations: Chain[Validation[_, _]]
  ) extends Schema.Value[A](default, description, example, name, validations) { self =>
    final override type Self[a] = Schema.Primitive[a]

    final def setFormat(format: Option[String]): Schema.Primitive[A] = copy(format = format)
    final def withFormat(format: String): Schema.Primitive[A] = setFormat(format.some)
    final def withoutFormat: Schema.Primitive[A] = setFormat(none)

    final def copy(
        default: Option[A] = default,
        description: Option[String] = description,
        example: Option[A] = example,
        format: Option[String] = format,
        name: Option[String] = name
    ): Schema.Primitive[A] =
      vimapCopy[A](default, description, example, format, name, validations)(_.valid, identity)

    override protected def vimapCopy[T](
        default: Option[A],
        description: Option[String],
        example: Option[A],
        name: Option[String],
        validations: Chain[Validation[_, _]]
    )(f: A => Validated[Errors, T], g: T => A): Schema.Primitive[T] =
      vimapCopy(default, description, example, format, name, validations)(f, g)

    private def vimapCopy[T](
        default: Option[A],
        description: Option[String],
        example: Option[A],
        format: Option[String],
        name: Option[String],
        validations: Chain[Validation[_, _]]
    )(f: A => Validated[Errors, T], g: T => A): Schema.Primitive[T] = new Primitive[T](
      default.flatMap(f(_).toOption),
      description,
      example.flatMap(f(_).toOption),
      format,
      name,
      tpe,
      validations
    ) {
      override def fromJsonValue(json: Json, default: Option[T]): Validated[Errors, T] =
        self.fromJsonValue(json, default.map(g)).andThen(f)
      override def fromStringValue(value: String, default: Option[T]): Validated[Errors, T] =
        self.fromStringValue(value, default.map(g)).andThen(f)
      override def toJsonValue(gt: T): Json = self.toJsonValue(g(gt))
      override def toStringValue(gt: T): String = self.toStringValue(g(gt))
    }

    final override private[structure] def fromJson(json: Option[Json], default: Option[A]): Validated[Errors, A] =
      json
        .traverse(fromJsonValue)
        .map(_.orElse(default))
        .andThen(Validated.fromOption(_, Required))
    final override private[structure] def fromString(
        value: Option[String],
        default: Option[A]
    ): Validated[Errors, A] =
      value.traverse(fromStringValue).map(_.orElse(default)).andThen(Validated.fromOption(_, Required))
    final override def toJson(fa: A): Option[Json] = toJsonValue(fa).some
    final override def toString(fa: A): Option[String] = toStringValue(fa).some
  }

  object Primitive {
    private def apply[A: Decoder: Encoder](
        format: Option[String],
        parse: Validation[String, A],
        print: A => String,
        tpe: Type
    ): Schema.Primitive[A] = new Primitive[A](
      default = none,
      description = none,
      example = none,
      format,
      name = none,
      tpe,
      validations = Chain.empty
    ) {
      override def fromJsonValue(json: Json, default: Option[A]): Validated[Errors, A] =
        if (json.isNull) default.toValid(Required)
        else json.as[A].toValidated.leftMap(_ => Errors.rootNel(Violation(Constraint.json(this.tpe.toString), json)))
      override def fromStringValue(value: String, default: Option[A]): Validated[Errors, A] =
        parse.run(value).leftMap(Errors.root)
      override def toJsonValue(a: A): Json = a.asJson
      override def toStringValue(a: A): String = print(a)
    }

    val bigDecimal: Schema.Primitive[BigDecimal] = Primitive(none, parsing.bigDecimal, String.valueOf, Type.Number)
    val bigInt: Schema.Primitive[BigInt] = Primitive(none, parsing.bigInt, String.valueOf, Type.Number)
    val boolean: Schema.Primitive[Boolean] = Primitive(none, parsing.boolean, String.valueOf, Type.Boolean)
    val double: Schema.Primitive[Double] = Primitive("double".some, parsing.double, String.valueOf, Type.Number)
    val float: Schema.Primitive[Float] = Primitive("float".some, parsing.float, String.valueOf, Type.Number)
    val int: Schema.Primitive[Int] = Primitive("int32".some, parsing.int, String.valueOf, Type.Integer)
    val long: Schema.Primitive[Long] = Primitive("int64".some, parsing.long, String.valueOf, Type.Integer)
    val string: Schema.Primitive[String] = Primitive(none, Validation.ask, identity, Type.String)
  }

  sealed abstract class Optional[A](
      override val default: Option[A],
      val schema: Schema[_],
      override val example: Option[A]
  ) extends Schema[A] { self =>
    final override type Self[a] = Schema.Optional[a]

    final override def description: Option[String] = schema.description
    final override def name: Option[String] = schema.name
    final override def validations: Chain[Validation[_, _]] = schema.validations

    final override protected def vimapCopy[T](
        default: Option[A],
        description: Option[String],
        example: Option[A],
        name: Option[String],
        validations: Chain[Validation[_, _]]
    )(f: A => Validated[Errors, T], g: T => A): Schema.Optional[T] =
      new Optional[T](
        default.flatMap(f(_).toOption),
        schema.copy(default = none, description, example = none, name, validations),
        example.flatMap(f(_).toOption)
      ) {
        override def fromJson(json: Option[Json], default: Option[T]): Validated[Errors, T] =
          self.fromJson(json, default.map(g)).andThen(f)
        override def fromString(value: Option[String], default: Option[T]): Validated[Errors, T] =
          self.fromString(value, default.map(g)).andThen(f)
        override def toJson(fa: T): Option[Json] = self.toJson(g(fa))
        override def toString(fa: T): Option[String] = self.toString(g(fa))
      }
  }

  object Optional {
    def apply[A](schema: Schema[A]): Schema.Optional[Option[A]] = {
      val s = schema
      new Schema.Optional[Option[A]](default = none, s, example = none) {
        override def fromJson(json: Option[Json], default: Option[Option[A]]): Validated[Errors, Option[A]] =
          json.flatMap(_.as[Option[Json]].toOption.flatten).traverse(json => s.fromJson(json.some))
        override def fromString(value: Option[String], default: Option[Option[A]]): Validated[Errors, Option[A]] =
          value.traverse(value => s.fromString(value.some))
        override def toJson(fa: Option[A]): Option[Json] = fa.flatMap(s.toJson)
        override def toString(fa: Option[A]): Option[String] = fa.flatMap(s.toString)
      }
    }
  }

  sealed abstract class Collection[A](
      default: Option[A],
      val delimiter: Delimiter,
      description: Option[String],
      example: Option[A],
      name: Option[String],
      val schema: Eval[Schema[_]],
      validations: Chain[Validation[_, _]]
  ) extends Schema.Value[A](default, description, example, name, validations) { self =>
    final override type Self[a] = Schema.Collection[a]

    final def withDelimiter(delimiter: Delimiter): Schema.Collection[A] = copy(delimiter = delimiter)

    final def copy(
        default: Option[A] = default,
        delimiter: Delimiter = delimiter,
        description: Option[String] = description,
        example: Option[A] = example,
        name: Option[String] = name
    ): Schema.Collection[A] =
      vimapCopy(default, delimiter, description, example, name, validations)(_.valid, identity)

    final override protected def vimapCopy[T](
        default: Option[A],
        description: Option[String],
        example: Option[A],
        name: Option[String],
        validations: Chain[Validation[_, _]]
    )(f: A => Validated[Errors, T], g: T => A): Collection[T] =
      vimapCopy(default, delimiter, description, example, name, validations)(f, g)

    final private def vimapCopy[T](
        default: Option[A],
        delimiter: Delimiter,
        description: Option[String],
        example: Option[A],
        name: Option[String],
        validations: Chain[Validation[_, _]]
    )(f: A => Validated[Errors, T], g: T => A): Schema.Collection[T] = new Collection[T](
      default.flatMap(f(_).toOption),
      delimiter,
      description,
      example.flatMap(f(_).toOption),
      name,
      schema,
      validations
    ) {
      override def fromJson(json: Option[Json], default: Option[T]): Validated[Errors, T] =
        self.fromJson(json, default.map(g)).andThen(f)
      override def fromString(value: Option[String], default: Option[T]): Validated[Errors, T] =
        self.fromString(value, default.map(g)).andThen(f)
      override def fromJsonValue(json: Json, default: Option[T]): Validated[Errors, T] =
        self.fromJsonValue(json, default.map(g)).andThen(f)
      override def fromStringValue(value: String, default: Option[T]): Validated[Errors, T] =
        self.fromStringValue(value, default.map(g)).andThen(f)
      override def toJsonValue(a: T): Json = self.toJsonValue(g(a))
      override def toStringValue(a: T): String = self.toStringValue(g(a))
    }

    final override def toJson(a: A): Option[Json] = toJsonValue(a).some
    final override def toString(a: A): Option[String] = toStringValue(a).some
  }

  object Collection {
    def seq[A](schema: => Schema[A]): Schema.Collection[Seq[A]] = {
      val s = Eval.later(schema)
      new Collection[Seq[A]](
        default = none,
        delimiter = Delimiter.Default,
        description = none,
        example = none,
        name = none,
        s,
        validations = Chain.empty
      ) {
        override def fromJson(json: Option[Json], default: Option[Seq[A]]): Validated[Errors, Seq[A]] =
          json.traverse(fromJsonValue).map(_.orElse(default)).andThen(Validated.fromOption(_, Required))
        override def fromString(value: Option[String], default: Option[Seq[A]]): Validated[Errors, Seq[A]] =
          value.traverse(fromStringValue).map(_.orElse(default)).andThen(Validated.fromOption(_, Required))
        override def fromJsonValue(json: Json, default: Option[Seq[A]]): Validated[Errors, Seq[A]] =
          if (json.isNull) default.toValid(Required)
          else
            json
              .as[Seq[Json]]
              .toValidated
              .leftMap(_ => Errors.rootNel(Violation(Constraint.json("[]"), json)))
              .andThen(_.zipWithIndex.traverse { case (json, index) =>
                s.value.fromJson(json.some).leftMap(_.modifyHistory(index /: _))
              })
        override def fromStringValue(value: String, default: Option[Seq[A]]): Validated[Errors, Seq[A]] =
          delimiter.decode(value).toList.traverse(value => s.value.fromString(value.some))
        override def toJsonValue(a: Seq[A]): Json = s.value match {
          case s: Schema.Value[_]    => a.map(s.toJsonValue).asJson
          case s: Schema.Optional[_] => a.map(s.toJson).asJson
        }
        override def toStringValue(a: Seq[A]): String = delimiter.encode(Chain.fromSeq(a).mapFilter(s.value.toString))
      }
    }
  }

  sealed abstract class Dictionary[A](
      default: Option[A],
      description: Option[String],
      example: Option[A],
      name: Option[String],
      val schema: Eval[Schema[_]],
      validations: Chain[Validation[_, _]]
  ) extends Schema.Value[A](default, description, example, name, validations) { self =>
    final override type Self[a] = Schema.Dictionary[a]

    override protected def vimapCopy[T](
        default: Option[A],
        description: Option[String],
        example: Option[A],
        name: Option[String],
        validations: Chain[Validation[_, _]]
    )(f: A => Validated[Errors, T], g: T => A): Schema.Dictionary[T] = new Dictionary[T](
      default.flatMap(f(_).toOption),
      description,
      example.flatMap(f(_).toOption),
      name,
      schema,
      validations
    ) {
      override def fromJsonObject(json: JsonObject, default: Option[T]): Validated[Errors, T] =
        self.fromJsonObject(json, default.map(g)).andThen(f)
      override def toJsonObject(fa: T): JsonObject = self.toJsonObject(g(fa))
    }

    final override private[structure] def fromJson(json: Option[Json], default: Option[A]): Validated[Errors, A] =
      json.traverse(fromJsonValue).map(_.orElse(default)).andThen {
        case Some(a) => a.valid
        case None    => fromJsonObject(JsonObject.empty, default)
      }
    final override private[structure] def fromString(
        value: Option[String],
        default: Option[A]
    ): Validated[Errors, A] =
      value.traverse(fromStringValue).map(_.orElse(default)).andThen {
        case Some(a) => a.valid
        case None    => fromJsonObject(JsonObject.empty, default)
      }
    final override private[structure] def fromJsonValue(json: Json, default: Option[A]): Validated[Errors, A] =
      if (json.isNull) default match {
        case Some(a) => a.valid
        case None    => fromJsonObject(JsonObject.empty, default)
      }
      else
        json.asObject
          .toValid(Errors.rootNel(Violation(Constraint.json("{}"), json)))
          .andThen(fromJsonObject(_, default))
    final override private[structure] def fromStringValue(
        value: String,
        default: Option[A]
    ): Validated[Errors, A] =
      parsing.json.run(value).leftMap(Errors.root).andThen(fromJsonValue)
    final override def toJson(fa: A): Option[Json] = toJsonValue(fa).some
    final override def toString(fa: A): Option[String] = toStringValue(fa).some
    final override def toJsonValue(a: A): Json = Json.fromJsonObject(toJsonObject(a))
    final override def toStringValue(a: A): String = toJsonValue(a).noSpaces
    private[structure] def fromJsonObject(json: JsonObject, default: Option[A]): Validated[Errors, A]
    private[structure] def toJsonObject(fa: A): JsonObject
  }

  object Dictionary {
    def map[A](schema: => Schema[A]): Schema.Dictionary[Map[String, A]] = {
      val s = schema
      new Dictionary[Map[String, A]](
        default = none,
        description = none,
        example = none,
        name = none,
        schema = Eval.later(s),
        validations = Chain.empty
      ) {
        override def fromJsonObject(
            json: JsonObject,
            default: Option[Map[String, A]]
        ): Validated[Errors, Map[String, A]] = json.toList
          .traverse { case (key, json) =>
            s.fromJson(json.some).tupleLeft(key).leftMap(_.modifyHistory(key /: _))
          }
          .map(_.toMap)
        override def toJsonObject(fa: Map[String, A]): JsonObject = fa.foldLeft(JsonObject.empty) {
          case (json, (key, value)) =>
            s.toJson(value).fold(json)(value => (key, value) +: json)
        }
      }
    }
  }

  sealed abstract class Product[A](
      default: Option[A],
      description: Option[String],
      example: Option[A],
      name: Option[String],
      validations: Chain[Validation[_, _]]
  ) extends Schema.Value[A](default, description, example, name, validations)
      with Structure.Product[A] { self =>
    final override type Self[a] = Schema.Product[a]
    final override type Element[a] = Field[a]
    final override type Group[a] = Schema.Product[a]

    def toChain: Chain[Field[_]]

    final override def zipAll[T](schema: Schema.Product[T]): Schema.Product[A |*| T] =
      new Product[A |*| T](default = none, description, example = none, name, validations) {
        override def toChain: Chain[Field[_]] = self.toChain ++ schema.toChain
        override def fromJsonObject(json: JsonObject): Validated[Errors, (JsonObject, A |*| T)] =
          self.fromJsonObject(json) match {
            case Validated.Valid((json, fa)) => schema.fromJsonObject(json).map(_.map((fa, _)))
            case Validated.Invalid(left) =>
              schema.fromJsonObject(json) match {
                case Validated.Valid(_)       => left.invalid
                case Validated.Invalid(right) => (left |+| right).invalid
              }
          }
        override def toJsonObject(fa: A |*| T): JsonObject =
          self.toJsonObject(fa._1) deepMerge schema.toJsonObject(fa._2)
      }

    final override def zip[T](field: Field[T]): Schema.Product[A |*| T] = zipAll(Product.fromField(field))

    final def copy(
        default: Option[A] = default,
        description: Option[String] = description,
        example: Option[A] = example,
        name: Option[String] = name
    ): Schema.Product[A] = vimapCopy(default, description, example, name, validations)(_.valid, identity)

    final override protected def vimapCopy[T](
        default: Option[A],
        description: Option[String],
        example: Option[A],
        name: Option[String],
        validations: Chain[Validation[_, _]]
    )(f: A => Validated[Errors, T], g: T => A): Schema.Product[T] = new Product[T](
      default.flatMap(f(_).toOption),
      description,
      example.flatMap(f(_).toOption),
      name,
      validations
    ) {
      override def toChain: Chain[Field[_]] = self.toChain
      override def fromJsonObject(json: JsonObject): Validated[Errors, (JsonObject, T)] =
        self.fromJsonObject(json).andThen(_.traverse(f))
      override def toJsonObject(fa: T): JsonObject = self.toJsonObject(g(fa))
    }

    final override private[structure] def fromJson(json: Option[Json], default: Option[A]): Validated[Errors, A] =
      json.traverse(fromJsonValue).map(_.orElse(default)).andThen {
        case Some(a) => a.valid
        case None    => fromJsonObject(JsonObject.empty).map(_._2)
      }
    final override private[structure] def fromString(
        value: Option[String],
        default: Option[A]
    ): Validated[Errors, A] =
      value.traverse(fromStringValue).map(_.orElse(default)).andThen {
        case Some(a) => a.valid
        case None    => fromJsonObject(JsonObject.empty).map(_._2)
      }
    final override private[structure] def fromJsonValue(json: Json, default: Option[A]): Validated[Errors, A] =
      if (json.isNull) default match {
        case Some(a) => a.valid
        case None    => fromJsonObject(JsonObject.empty).map(_._2)
      }
      else
        json.asObject
          .toValid(Errors.rootNel(Violation(Constraint.json("{}"), json)))
          .andThen(fromJsonObject)
          .map(_._2)
    final override private[structure] def fromStringValue(
        value: String,
        default: Option[A]
    ): Validated[Errors, A] =
      parsing.json.run(value).leftMap(Errors.root).andThen(fromJsonValue)
    final override def toJson(fa: A): Option[Json] = toJsonValue(fa).some
    final override def toString(fa: A): Option[String] = toStringValue(fa).some
    final override def toJsonValue(fa: A): Json = Json.fromJsonObject(toJsonObject(fa))
    final override def toStringValue(fa: A): String = toJsonValue(fa).noSpaces

    private[structure] def fromJsonObject(json: JsonObject): Validated[Errors, (JsonObject, A)]
    private[structure] def toJsonObject(fa: A): JsonObject
  }

  object Product {
    val Empty: Schema.Product[Unit] = new Product[Unit](
      default = none,
      description = none,
      example = none,
      name = none,
      validations = Chain.empty
    ) {
      override def toChain: Chain[Field[_]] = Chain.empty
      override def fromJsonObject(json: JsonObject): Validated[Errors, (JsonObject, Unit)] = (json, ()).valid
      override def toJsonObject(fa: Unit): JsonObject = JsonObject.empty
    }

    def fromField[A](field: Field[A]): Schema.Product[A] =
      new Product[A](default = none, description = none, example = none, name = none, validations = Chain.empty) {
        override def toChain: Chain[Field[_]] = Chain.one(field)
        override def fromJsonObject(json: JsonObject): Validated[Errors, (JsonObject, A)] =
          field.fromJsonObject(json)
        override def toJsonObject(a: A): JsonObject = field.toJsonObject(a)
      }
  }

  sealed abstract class Sum[A](
      default: Option[A],
      description: Option[String],
      val discriminator: Option[Discriminator],
      example: Option[A],
      name: Option[String],
      validations: Chain[Validation[_, _]]
  ) extends Schema.Value[A](default, description, example, name, validations)
      with Structure.Sum[A] { self =>
    final override type Self[a] = Sum[a]
    final override type Element[a] = Branch[a]
    final override type Group[a] = Sum[a]

    def toChain: Chain[Branch[_]]

    final def setDiscriminator(discriminator: Option[Discriminator]): Schema.Sum[A] =
      copy(discriminator = discriminator)
    final def withDiscriminator(discriminator: Discriminator): Schema.Sum[A] = setDiscriminator(discriminator.some)
    final def withoutDiscriminator: Schema.Sum[A] = setDiscriminator(none)

    final def copy(
        default: Option[A] = default,
        description: Option[String] = description,
        discriminator: Option[Discriminator] = discriminator,
        example: Option[A] = example,
        name: Option[String] = name
    ): Schema.Sum[A] = vimapCopy(default, description, example, name, validations)(_.valid, identity)

    final override def orElseAll[T](schema: Schema.Sum[T]): Schema.Sum[A |+| T] = new Sum[A |+| T](
      default = none,
      description,
      discriminator,
      example = none,
      name,
      validations
    ) {
      override def toChain: Chain[Branch[_]] = self.toChain ++ schema.toChain
      override def fromJson(
          discriminator: Option[Discriminator],
          json: Option[Json],
          default: Option[A |+| T]
      ): Validated[Errors, Option[A |+| T]] =
        self.fromJson(discriminator, json, default.flatMap(_.left.toOption)).andThen {
          case Some(fa) => fa.asLeft.some.valid
          case None     => schema.fromJson(discriminator, json, default.flatMap(_.toOption)).map(_.map(_.asRight))
        }
      override def toJson(discriminator: Option[Discriminator], fa: A |+| T): Option[Json] = fa match {
        case Left(fa)  => self.toJson(discriminator, fa)
        case Right(gt) => schema.toJson(discriminator, gt)
      }
    }

    final override def orElse[T](branch: Branch[T]): Schema.Sum[A |+| T] = orElseAll(Sum.fromBranch(branch))

    final override protected def vimapCopy[T](
        default: Option[A],
        description: Option[String],
        example: Option[A],
        name: Option[String],
        validations: Chain[Validation[_, _]]
    )(f: A => Validated[Errors, T], g: T => A): Schema.Sum[T] = new Sum[T](
      default.flatMap(f(_).toOption),
      description,
      discriminator,
      example.flatMap(f(_).toOption),
      name,
      validations
    ) {
      override def toChain: Chain[Branch[_]] = self.toChain
      override def fromJson(
          discriminator: Option[Discriminator],
          json: Option[Json],
          default: Option[T]
      ): Validated[Errors, Option[T]] = self.fromJson(discriminator, json, default.map(g)).andThen(_.traverse(f))
      override def toJson(discriminator: Option[Discriminator], gt: T): Option[Json] =
        self.toJson(discriminator, g(gt))
    }

    final override private[structure] def fromJson(json: Option[Json], default: Option[A]): Validated[Errors, A] =
      json match {
        case Some(json) =>
          fromJson(discriminator, json.some, default)
            .andThen(_.toValid(Errors.rootNel(Violation.invalid(json))))
        case None => default.fold(fromJson(discriminator, json, default).andThen(_.toValid(Required)))(_.valid)
      }
    final override private[structure] def fromJsonValue(json: Json, default: Option[A]): Validated[Errors, A] =
      fromJson(json.some)
    final override private[structure] def fromString(
        value: Option[String],
        default: Option[A]
    ): Validated[Errors, A] =
      value.traverse(parsing.json.run).leftMap(Errors.root).andThen(fromJson)
    final override private[structure] def fromStringValue(
        value: String,
        default: Option[A]
    ): Validated[Errors, A] = fromString(value.some)

    final override def toJson(fa: A): Option[Json] = toJsonValue(fa).some
    final override def toString(fa: A): Option[String] = toStringValue(fa).some
    final override def toJsonValue(fa: A): Json = toJson(discriminator, fa).getOrElse(Json.Null)
    final override def toStringValue(fa: A): String = toJsonValue(fa).noSpaces

    protected def fromJson(
        discriminator: Option[Discriminator],
        json: Option[Json],
        default: Option[A]
    ): Validated[Errors, Option[A]]
    protected def toJson(discriminator: Option[Discriminator], fa: A): Option[Json]
  }

  object Sum {
    def fromBranch[A](branch: Branch[A]): Schema.Sum[A] = new Sum[A](
      default = none,
      description = none,
      Discriminator.Default.some,
      example = none,
      name = none,
      validations = Chain.empty
    ) {
      override def toChain: Chain[Branch[_]] = Chain.one(branch)
      override def fromJson(
          discriminator: Option[Discriminator],
          json: Option[Json],
          default: Option[A]
      ): Validated[Errors, Option[A]] =
        branch.fromJson(discriminator, json)
      override def toJson(discriminator: Option[Discriminator], a: A): Option[Json] = branch.toJson(discriminator, a)
    }
  }

  sealed abstract class Enumeration[A](
      default: Option[A],
      description: Option[String],
      example: Option[A],
      name: Option[String],
      validations: Chain[Validation[_, _]],
      val tpe: Type
  ) extends Schema.Value[A](default, description, example, name, validations) { self =>
    final override type Self[a] = Schema.Enumeration[a]

    def stringValues: Set[String]
    def jsonValues: Set[Json]

    final def copy(
        default: Option[A] = default,
        description: Option[String] = description,
        example: Option[A] = example,
        name: Option[String] = name
    ): Schema.Enumeration[A] = vimapCopy(default, description, example, name, validations)(_.valid, identity)

    final override protected def vimapCopy[T](
        default: Option[A],
        description: Option[String],
        example: Option[A],
        name: Option[String],
        validations: Chain[Validation[_, _]]
    )(f: A => Validated[Errors, T], g: T => A): Schema.Enumeration[T] = new Enumeration[T](
      default.flatMap(f(_).toOption),
      description,
      example.flatMap(f(_).toOption),
      name,
      validations,
      tpe
    ) {
      override def stringValues: Set[String] = self.stringValues
      override def jsonValues: Set[Json] = self.jsonValues
      override def fromJsonValue(json: Json, default: Option[T]): Validated[Errors, T] =
        self.fromJsonValue(json, default.map(g)).andThen(f)
      override def fromStringValue(value: String, default: Option[T]): Validated[Errors, T] =
        self.fromStringValue(value, default.map(g)).andThen(f)
      override def toJsonValue(gt: T): Json = self.toJsonValue(g(gt))
      override def toStringValue(gt: T): String = self.toStringValue(g(gt))
    }

    final override private[structure] def fromJson(json: Option[Json], default: Option[A]): Validated[Errors, A] =
      json.traverse(fromJsonValue).map(_.orElse(default)).andThen(Validated.fromOption(_, Required))
    final override private[structure] def fromString(
        value: Option[String],
        default: Option[A]
    ): Validated[Errors, A] =
      value.traverse(fromStringValue).map(_.orElse(default)).andThen(Validated.fromOption(_, Required))
    final override def toJson(fa: A): Option[Json] = toJsonValue(fa).some
    final override def toString(fa: A): Option[String] = toStringValue(fa).some
  }

  object Enumeration {
    def apply[A, B](schema: Schema.Primitive[A], values: Set[B], mapping: B => A): Schema.Enumeration[B] =
      new Enumeration[B](
        default = none,
        description = none,
        example = none,
        name = none,
        validations = Chain.empty,
        tpe = schema.tpe
      ) {
        val inverse: Map[A, B] = values.toList.map(b => (mapping(b), b)).toMap
        override def stringValues: Set[String] = values.map(mapping).map(schema.toStringValue)
        override def jsonValues: Set[Json] = values.map(mapping).map(schema.toJsonValue)
        override def fromJsonValue(json: Json, default: Option[B]): Validated[Errors, B] =
          if (json.isNull) default.toValid(Required)
          else schema.fromJsonValue(json, default.map(mapping.apply)).map(inverse)
        override def fromStringValue(value: String, default: Option[B]): Validated[Errors, B] =
          schema.fromStringValue(value, default.map(mapping.apply)).map(inverse)
        override def toJsonValue(b: B): Json = schema.toJsonValue(mapping(b))
        override def toStringValue(b: B): String = schema.toStringValue(mapping(b))
      }
  }

  sealed abstract class Any[A](
      default: Option[A],
      description: Option[String],
      example: Option[A],
      name: Option[String],
      validations: Chain[Validation[_, _]]
  ) extends Schema.Value[A](default, description, example, name, validations) { self =>
    override type Self[a] = Schema.Any[a]

    final def copy(
        default: Option[A] = default,
        description: Option[String] = description,
        example: Option[A] = example,
        name: Option[String] = name
    ): Schema.Any[A] = vimapCopy(default, description, example, name, validations)(_.valid, identity)

    final override protected def vimapCopy[T](
        default: Option[A],
        description: Option[String],
        example: Option[A],
        name: Option[String],
        validations: Chain[Validation[_, _]]
    )(f: A => Validated[Errors, T], g: T => A): Schema.Any[T] =
      new Any[T](default.flatMap(f(_).toOption), description, example.flatMap(f(_).toOption), name, validations) {
        override def fromJsonValue(json: Json, default: Option[T]): Validated[Errors, T] =
          self.fromJsonValue(json, default.map(g)).andThen(f)
        override def fromStringValue(value: String, default: Option[T]): Validated[Errors, T] =
          self.fromStringValue(value, default.map(g)).andThen(f)
        override def toJsonValue(a: T): Json = self.toJsonValue(g(a))
        override def toStringValue(a: T): String = self.toStringValue(g(a))
      }

    final override private[structure] def fromJson(json: Option[Json], default: Option[A]): Validated[Errors, A] =
      json.traverse(fromJsonValue).map(_.orElse(default)).andThen(Validated.fromOption(_, Required))
    final override private[structure] def fromString(
        value: Option[String],
        default: Option[A]
    ): Validated[Errors, A] =
      value.traverse(fromStringValue).map(_.orElse(default)).andThen(Validated.fromOption(_, Required))
    final override def toJson(fa: A): Option[Json] = toJsonValue(fa).some
    final override def toString(fa: A): Option[String] = toStringValue(fa).some
  }

  object Any {
    def apply[A](
        decodeJson: Json => Validated[Errors, A],
        decodeString: String => Validated[Errors, A]
    )(encodeJson: A => Json, encodeString: A => String): Schema.Any[A] =
      new Any[A](default = none, description = none, example = none, name = none, validations = Chain.empty) {
        override def fromJsonValue(json: Json, default: Option[A]): Validated[Errors, A] =
          if (json.isNull) default.fold(decodeJson(json))(_.valid) else decodeJson(json)
        override def fromStringValue(value: String, default: Option[A]): Validated[Errors, A] = decodeString(value)
        override def toJsonValue(a: A): Json = encodeJson(a)
        override def toStringValue(a: A): String = encodeString(a)
      }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy