Skip to content

Instantly share code, notes, and snippets.

@counter2015
Last active August 2, 2022 08:04
Show Gist options
  • Save counter2015/e2a6ac2f645504e7e092548b9575664a to your computer and use it in GitHub Desktop.
Save counter2015/e2a6ac2f645504e7e092548b9575664a to your computer and use it in GitHub Desktop.
A json-schema draft
import cats.syntax.functor._
import io.circe.generic.extras.semiauto.{deriveConfiguredDecoder, deriveConfiguredEncoder}
import io.circe.generic.extras.{Configuration, ConfiguredJsonCodec}
import io.circe.syntax._
import io.circe.{Decoder, Encoder, Json}
// https://json-schema.org/draft/2020-12/json-schema-validation.html#rfc.section.6
sealed trait JsonSchemaValue
object JsonSchemaValue {
implicit val config: Configuration = Configuration.default.withStrictDecoding
implicit val encoder: Encoder[JsonSchemaValue] = Encoder.instance {
case v: Reference => v.asJson
case v: JsonSchema => v.asJson
}
implicit val decoder: Decoder[JsonSchemaValue] = List[Decoder[JsonSchemaValue]](
Decoder[Reference].widen,
Decoder[JsonSchema].widen
).reduceLeft(_ or _)
@ConfiguredJsonCodec
case class Reference($ref: String) extends JsonSchemaValue
/** `asJson` will return a Json object with the following properties, remove any null values on object
*/
case class JsonSchema(
$id: Option[String] = None,
$schema: Option[String] = None, // Some("https://json-schema.org/draft/2020-12/schema")
title: Option[String] = None,
description: Option[String] = None,
`type`: Option[JType] = None,
properties: Option[Map[String, JsonSchemaValue]] = None,
allOf: Option[List[JsonSchemaValue]] = None,
oneOf: Option[List[JsonSchemaValue]] = None,
anyOf: Option[List[JsonSchemaValue]] = None,
items: Option[JsonSchemaValue] = None,
`enum`: Option[List[Json]] = None,
examples: Option[List[String]] = None,
format: Option[String] = None,
required: Option[List[String]] = None,
minimum: Option[Int] = None,
exclusiveMinimum: Option[Int] = None,
maximum: Option[Int] = None,
exclusiveMaximum: Option[Int] = None,
minLength: Option[Int] = None,
maxLength: Option[Int] = None,
pattern: Option[String] = None,
const: Option[Json] = None,
default: Option[Json] = None,
multipleOf: Option[Int] = None // the value must be positive
) extends JsonSchemaValue
object JsonSchema {
implicit val encoder: Encoder[JsonSchema] = deriveConfiguredEncoder[JsonSchema].mapJson(_.dropNullValues)
implicit val decoder: Decoder[JsonSchema] = deriveConfiguredDecoder[JsonSchema]
}
def textOut(schema: JsonSchemaValue): String = schema.asJson.spaces4
}
import io.circe.{Decoder, Encoder}
import io.circe.syntax._
sealed trait JType
/*
etype = "number" | "object" | "integer" | "string" | "null" | "array"
jtype = etype | array[etype]
see: https://json-schema.org/draft/2020-12/json-schema-validation.html#rfc.section.6.1.1
*/
object JType {
case class StringType(value: SchemaTypeEnum) extends JType
case class StringListType(value: List[SchemaTypeEnum]) extends JType
implicit val encoder: Encoder[JType] = Encoder.instance {
case v: StringType => v.value.asJson
case v: StringListType => v.value.asJson
}
implicit val decoder: Decoder[JType] = List[Decoder[JType]](
SchemaTypeEnum.decoder.map(StringType),
Decoder.decodeList[SchemaTypeEnum].map(StringListType)
).reduceLeft(_ or _)
val default: StringType = StringType(SchemaTypeEnum.`object`)
}
import io.circe._
sealed trait SchemaTypeEnum
object SchemaTypeEnum {
case object `null` extends SchemaTypeEnum
case object `boolean` extends SchemaTypeEnum
case object `object` extends SchemaTypeEnum
case object `array` extends SchemaTypeEnum
sealed trait `number` extends SchemaTypeEnum
case object `number` extends `number`
case object `string` extends SchemaTypeEnum
case object `integer` extends `number`
implicit val encoder: Encoder[SchemaTypeEnum] = Encoder.instance {
case `null` => Json.fromString("null")
case `boolean` => Json.fromString("boolean")
case `object` => Json.fromString("object")
case `array` => Json.fromString("array")
case `number` => Json.fromString("number")
case `string` => Json.fromString("string")
case `integer` => Json.fromString("integer")
}
implicit val decoder: Decoder[SchemaTypeEnum] = (c: HCursor) => {
c.as[String] match {
case Right("null") => Right(`null`)
case Right("boolean") => Right(`boolean`)
case Right("object") => Right(`object`)
case Right("array") => Right(`array`)
case Right("number") => Right(`number`)
case Right("string") => Right(`string`)
case Right("integer") => Right(`integer`)
case Right(value) => Left(DecodingFailure(s"\"$value\" is not a valid type of json schema", c.history))
case Left(value) => Left(DecodingFailure(s"\"$value\" is not a valid type of json schema", c.history))
}
}
def fromString(str: String): SchemaTypeEnum = str match {
case "null" => `null`
case "boolean" => `boolean`
case "object" => `object`
case "array" => `array`
case "number" => `number`
case "string" => `string`
case "integer" => `integer`
case _ => `object`
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment