From c9a1fc9b70e1c72c2a66730c08765983d04b8e2a Mon Sep 17 00:00:00 2001 From: aonyshchuk Date: Fri, 11 Oct 2019 22:29:42 -0700 Subject: [PATCH 01/11] add support for primitive types and arrays to be exposed as default values --- .../andyglow/jsonschema/SchemaMacroSpec.scala | 25 +++++++-- .../andyglow/jsonschema/AsCirceSpec.scala | 2 +- .../json/LowPriorityArrayImplicits.scala | 18 +++++++ .../json/LowPriorityArrayImplicits.scala | 18 +++++++ .../json/LowPriorityArrayImplicits.scala | 17 ++++++ .../com/github/andyglow/json/ToValue.scala | 36 +++++++++++++ .../com/github/andyglow/json/Value.scala | 2 +- .../andyglow/jsonschema/AsDraftSupport.scala | 4 +- core/src/main/scala/json/Schema.scala | 53 +++++++++++++++++-- .../andyglow/jsonschema/AsJson4sSpec.scala | 2 +- .../andyglow/jsonschema/SchemaMacro.scala | 42 ++++++++++----- .../andyglow/jsonschema/AsPlaySpec.scala | 2 +- .../andyglow/jsonschema/AsSpraySpec.scala | 2 +- .../github/andyglow/jsonschema/AsUSpec.scala | 6 +-- 14 files changed, 199 insertions(+), 30 deletions(-) create mode 100644 core/src/main/scala-2.11/com/github/andyglow/json/LowPriorityArrayImplicits.scala create mode 100644 core/src/main/scala-2.12/com/github/andyglow/json/LowPriorityArrayImplicits.scala create mode 100644 core/src/main/scala-2.13/com/github/andyglow/json/LowPriorityArrayImplicits.scala create mode 100644 core/src/main/scala/com/github/andyglow/json/ToValue.scala diff --git a/api/src/test/scala/com/github/andyglow/jsonschema/SchemaMacroSpec.scala b/api/src/test/scala/com/github/andyglow/jsonschema/SchemaMacroSpec.scala index 068f1955..095025f4 100644 --- a/api/src/test/scala/com/github/andyglow/jsonschema/SchemaMacroSpec.scala +++ b/api/src/test/scala/com/github/andyglow/jsonschema/SchemaMacroSpec.scala @@ -32,9 +32,19 @@ class SchemaMacroSpec extends WordSpec { import `object`.Field Json.schema[Foo3] shouldEqual `object`( - Field("name" , `string`[String](None, None), required = false), - Field("bar" , `integer`, required = false), - Field("active" , `boolean`, required = false)) + Field("name" , `string`[String](None, None), required = false, default = "xxx"), + Field("bar" , `integer`, required = false, default = 5), + Field("active" , `boolean`, required = false, default = true)) + } + + "generate schema for case class of array fields with default values" in { + import `object`.Field + + Json.schema[Bar9] shouldEqual `object`( + Field("seq" , `array`(`string`[String](None, None)), required = false, default = Seq.empty[String]), + Field("set" , `set`(`integer`), required = false, default = Set(1, 5, 9)), + Field("list" , `array`(`boolean`), required = false, default = List(true, false)), + Field("vector" , `array`(`number`[Long]), required = false, default = Vector(9, 7))) } "generate references for implicitly defined dependencies" in { @@ -172,6 +182,15 @@ object SchemaMacroSpec { case class Bar8(foo: String) extends AnyVal case class Foo9(name: String) + + case class Bar9( + seq: Seq[String] = Seq.empty, + set: Set[Int] = Set(1, 5, 9), + list: List[Boolean] = List(true, false), + vector: Vector[Long] = Vector(9L, 7L)) + + + } sealed trait FooBar diff --git a/circe-json/src/test/scala/com/github/andyglow/jsonschema/AsCirceSpec.scala b/circe-json/src/test/scala/com/github/andyglow/jsonschema/AsCirceSpec.scala index 89abd2f7..acb98c09 100644 --- a/circe-json/src/test/scala/com/github/andyglow/jsonschema/AsCirceSpec.scala +++ b/circe-json/src/test/scala/com/github/andyglow/jsonschema/AsCirceSpec.scala @@ -39,7 +39,7 @@ class AsCirceSpec extends PropSpec { "middleName" -> Json.obj("type" -> Json.fromString("string")), "lastName" -> Json.obj("type" -> Json.fromString("string")), "age" -> Json.obj("type" -> Json.fromString("integer")), - "active" -> Json.obj("type" -> Json.fromString("boolean"))), + "active" -> Json.obj("type" -> Json.fromString("boolean"), "default" -> Json.True)), "required" -> Json.arr(Json.fromString("age"), Json.fromString("lastName"), Json.fromString("firstName"))) } } diff --git a/core/src/main/scala-2.11/com/github/andyglow/json/LowPriorityArrayImplicits.scala b/core/src/main/scala-2.11/com/github/andyglow/json/LowPriorityArrayImplicits.scala new file mode 100644 index 00000000..6c635d6e --- /dev/null +++ b/core/src/main/scala-2.11/com/github/andyglow/json/LowPriorityArrayImplicits.scala @@ -0,0 +1,18 @@ +package com.github.andyglow.json + +import scala.language.higherKinds + + +trait LowPriorityArrayImplicits { this: LowPriorityPrimitiveImplicits => + import Value._ + import ToValue._ + + implicit def ArrV[T, C[_] <: Traversable[_]](implicit + to: ToValue[T]): ToValue[C[T]] = { + + mk { items => + val v = items map { v: Any => to(v.asInstanceOf[T]) } + arr(v.toSeq) + } + } +} diff --git a/core/src/main/scala-2.12/com/github/andyglow/json/LowPriorityArrayImplicits.scala b/core/src/main/scala-2.12/com/github/andyglow/json/LowPriorityArrayImplicits.scala new file mode 100644 index 00000000..1794e8fb --- /dev/null +++ b/core/src/main/scala-2.12/com/github/andyglow/json/LowPriorityArrayImplicits.scala @@ -0,0 +1,18 @@ +package com.github.andyglow.json + +import scala.language.higherKinds + + +trait LowPriorityArrayImplicits { this: LowPriorityPrimitiveImplicits => + import Value._ + import ToValue._ + + implicit def ArrV[T, C[_] <: Traversable[_]](implicit + to: ToValue[T]): ToValue[C[T]] = { + + mk { items => + val v = items map { v: Any => to(v.asInstanceOf[T]) } + arr(v.toSeq) + } + } +} \ No newline at end of file diff --git a/core/src/main/scala-2.13/com/github/andyglow/json/LowPriorityArrayImplicits.scala b/core/src/main/scala-2.13/com/github/andyglow/json/LowPriorityArrayImplicits.scala new file mode 100644 index 00000000..cae62c10 --- /dev/null +++ b/core/src/main/scala-2.13/com/github/andyglow/json/LowPriorityArrayImplicits.scala @@ -0,0 +1,17 @@ +package com.github.andyglow.json + +import scala.language.higherKinds + +trait LowPriorityArrayImplicits { this: LowPriorityPrimitiveImplicits => + import Value._ + import ToValue._ + + implicit def ArrV[T, C[_] <: Iterable[_]](implicit + to: ToValue[T]): ToValue[C[T]] = { + + mk { items => + val v = items map { v: Any => to(v.asInstanceOf[T]) } + arr(v.toSeq) + } + } +} \ No newline at end of file diff --git a/core/src/main/scala/com/github/andyglow/json/ToValue.scala b/core/src/main/scala/com/github/andyglow/json/ToValue.scala new file mode 100644 index 00000000..19760f44 --- /dev/null +++ b/core/src/main/scala/com/github/andyglow/json/ToValue.scala @@ -0,0 +1,36 @@ +package com.github.andyglow.json + + +trait ToValue[T] { + + def apply(x: T): Value +} + +trait LowPriorityPrimitiveImplicits { + import Value._ + import ToValue._ + + implicit val BoolV: ToValue[Boolean] = mk(bool.apply) + implicit val IntV: ToValue[Int] = mk(num.apply) + implicit val LongV: ToValue[Long] = mk(num.apply) + implicit val ShortV: ToValue[Short] = mk(num.apply) + implicit val FloatV: ToValue[Float] = mk(num.apply) + implicit val DoubleV: ToValue[Double] = mk(num.apply) + implicit val BigDecimalV: ToValue[BigDecimal] = mk(num.apply) + implicit val BigIntV: ToValue[BigInt] = mk(num.apply) + implicit val NumberV: ToValue[Number] = mk(num.apply) + implicit val StringV: ToValue[String] = mk(str.apply) + implicit val NullV: ToValue[Null] = mk(_ => `null`) + + // NOTICE: there are no way to work around objects (case classes) as long as different libraries + // may generate different json representations out of the single case class instance +} + +trait LowPriorityImplicits extends LowPriorityPrimitiveImplicits with LowPriorityArrayImplicits + +object ToValue extends LowPriorityImplicits { + + def mk[T](f: T => Value): ToValue[T] = new ToValue[T] { def apply(x: T): Value = f(x) } + + def apply[T](x: T)(implicit to: ToValue[T]): Value = to(x) +} \ No newline at end of file diff --git a/core/src/main/scala/com/github/andyglow/json/Value.scala b/core/src/main/scala/com/github/andyglow/json/Value.scala index 8091f581..4be06a6e 100644 --- a/core/src/main/scala/com/github/andyglow/json/Value.scala +++ b/core/src/main/scala/com/github/andyglow/json/Value.scala @@ -4,7 +4,7 @@ import scala.collection._ sealed trait Value { - override def toString: String + def toString: String } object Value { diff --git a/core/src/main/scala/com/github/andyglow/jsonschema/AsDraftSupport.scala b/core/src/main/scala/com/github/andyglow/jsonschema/AsDraftSupport.scala index fe2f463a..9c77807c 100644 --- a/core/src/main/scala/com/github/andyglow/jsonschema/AsDraftSupport.scala +++ b/core/src/main/scala/com/github/andyglow/jsonschema/AsDraftSupport.scala @@ -25,7 +25,9 @@ trait AsDraftSupport { def mkObj(pp: Option[ValidationDef[_, _]], x: `object`[_]): obj = { val props = x.fields.map { field => - field.name -> apply(field.tpe) + val d = field.default map { d => obj("default" -> d) } getOrElse obj() + + field.name -> ( d ++ apply(field.tpe)) }.toMap val required = x.fields collect { diff --git a/core/src/main/scala/json/Schema.scala b/core/src/main/scala/json/Schema.scala index a5c6eae3..3b4c717b 100644 --- a/core/src/main/scala/json/Schema.scala +++ b/core/src/main/scala/json/Schema.scala @@ -1,5 +1,7 @@ package json +import com.github.andyglow.json.{ToValue, Value} + import scala.annotation.implicitNotFound import scala.language.higherKinds @@ -119,9 +121,13 @@ object Schema { } } - object `object` { + final object `object` { - final case class Field[T](name: String, tpe: Schema[T], required: Boolean = true) { + final class Field[T]( + val name: String, + val tpe: Schema[T], + val required: Boolean, + val default: Option[Value]) { def canEqual(that: Any): Boolean = that.isInstanceOf[Field[T]] @@ -129,11 +135,50 @@ object Schema { val other = that.asInstanceOf[Field[T]] this.name == other.name && - this.required == other.required && - this.tpe == other.tpe + this.required == other.required && + this.tpe == other.tpe && + this.default == other.default } override def hashCode: Int = name.hashCode + + override def toString: String = { + val extra = (required, default) match { + case (true, None) => " /R" + case (false, None) => "" + case (true, Some(v)) => s" /R /$v" + case (false, Some(v)) => s" /$v" + } + + s"$name: ${tpe}$extra" + } + } + + final object Field { + + def apply[T]( + name: String, + tpe: Schema[T]): Field[T] = { + + new Field(name, tpe, required = true, default = None) + } + + def apply[T]( + name: String, + tpe: Schema[T], + required: Boolean): Field[T] = { + + new Field(name, tpe, required, default = None) + } + + def apply[T: ToValue]( + name: String, + tpe: Schema[T], + required: Boolean, + default: T): Field[T] = { + + new Field(name, tpe, required, Some(ToValue(default))) + } } def apply[T](field: Field[_], xs: Field[_]*): `object`[T] = new `object`((field +: xs.toSeq).toSet) diff --git a/json4s-json/src/test/scala/com/github/andyglow/jsonschema/AsJson4sSpec.scala b/json4s-json/src/test/scala/com/github/andyglow/jsonschema/AsJson4sSpec.scala index 3e924e2e..05c32352 100644 --- a/json4s-json/src/test/scala/com/github/andyglow/jsonschema/AsJson4sSpec.scala +++ b/json4s-json/src/test/scala/com/github/andyglow/jsonschema/AsJson4sSpec.scala @@ -40,7 +40,7 @@ class AsJson4sSpec extends PropSpec{ "middleName" -> JObject("type" -> JString("string")), "lastName" -> JObject("type" -> JString("string")), "age" -> JObject("type" -> JString("integer")), - "active" -> JObject("type" -> JString("boolean"))), + "active" -> JObject("type" -> JString("boolean"), "default" -> JBool.True)), "required" -> JArray(List(JString("age"), JString("lastName"), JString("firstName")))) } } diff --git a/macros/src/main/scala/com/github/andyglow/jsonschema/SchemaMacro.scala b/macros/src/main/scala/com/github/andyglow/jsonschema/SchemaMacro.scala index 7fd10375..57bd7dee 100644 --- a/macros/src/main/scala/com/github/andyglow/jsonschema/SchemaMacro.scala +++ b/macros/src/main/scala/com/github/andyglow/jsonschema/SchemaMacro.scala @@ -148,9 +148,7 @@ object SchemaMacro { final def lookupCompanionOf(clazz: Symbol): Symbol = clazz.companion - def possibleApplyMethodsOf(tpe: Type): List[MethodSymbol] = { - val subjectCompanionSym = tpe.typeSymbol - val subjectCompanion = lookupCompanionOf(subjectCompanionSym) + def possibleApplyMethodsOf(subjectCompanion: Symbol): List[MethodSymbol] = { val subjectCompanionTpe = subjectCompanion.typeSignature subjectCompanionTpe.decl(TermName("apply")) match { @@ -174,15 +172,19 @@ object SchemaMacro { } } - def applyMethod(tpe: Type): Option[MethodSymbol] = possibleApplyMethodsOf(tpe).headOption + def applyMethod(subjectCompanion: Symbol): Option[MethodSymbol] = + possibleApplyMethodsOf(subjectCompanion).headOption case class Field( name: TermName, tpe: Type, effectiveTpe: Type, annotations: List[Annotation], - hasDefault: Boolean, - isOption: Boolean) + default: Option[Tree], + isOption: Boolean) { + + def hasDefault: Boolean = default.isDefined + } def fieldMap(tpe: Type): Seq[Field] = { @@ -196,25 +198,33 @@ object SchemaMacro { s.name.toString.trim -> s.accessed.annotations }.toMap - def toField(fieldSym: TermSymbol): Field = { + val subjectCompanionSym = tpe.typeSymbol + val subjectCompanion = lookupCompanionOf(subjectCompanionSym) + + + def toField(fieldSym: TermSymbol, i: Int): Field = { val name = fieldSym.name.toString.trim val fieldTpe = fieldSym.typeSignature val isOption = fieldTpe <:< optionTpe + val hasDefault = fieldSym.isParamWithDefault + val default = if (hasDefault) { + val getter = TermName("apply$default$" + (i + 1)) + Some(q"$subjectCompanion.$getter") + } else + None Field( name = TermName(name), tpe = fieldTpe, effectiveTpe = if (isOption) fieldTpe.typeArgs.head else fieldTpe, annotations = annotationMap.getOrElse(name, List.empty), - isOption = isOption, - hasDefault = fieldSym.isParamWithDefault) + default = default, + isOption = isOption) } - val fields = applyMethod(tpe) flatMap { method => + val fields = applyMethod(subjectCompanion) flatMap { method => method.paramLists.headOption map { params => - val fields = params map { _.asTerm } map toField - - fields.toSeq + params.map { _.asTerm }.zipWithIndex map { case (f, i) => toField(f, i) } } } @@ -240,7 +250,11 @@ object SchemaMacro { val name = f.name.decodedName.toString val jsonType = resolve(f.effectiveTpe, if (f.isOption) stack else tpe +: stack) - q"$obj.Field[${f.effectiveTpe}](name = $name, tpe = $jsonType, required = ${ !f.isOption && !f.hasDefault })" + f.default map { d => + q"$obj.Field[${f.effectiveTpe}](name = $name, tpe = $jsonType, required = ${ !f.isOption && !f.hasDefault }, default = $d)" + } getOrElse { + q"$obj.Field[${f.effectiveTpe}](name = $name, tpe = $jsonType, required = ${ !f.isOption && !f.hasDefault })" + } } q"$obj[$tpe](..$fields)" diff --git a/play-json/src/test/scala/com/github/andyglow/jsonschema/AsPlaySpec.scala b/play-json/src/test/scala/com/github/andyglow/jsonschema/AsPlaySpec.scala index 71876518..ad919a6a 100644 --- a/play-json/src/test/scala/com/github/andyglow/jsonschema/AsPlaySpec.scala +++ b/play-json/src/test/scala/com/github/andyglow/jsonschema/AsPlaySpec.scala @@ -40,7 +40,7 @@ class AsPlaySpec extends PropSpec{ "middleName" -> Json.obj("type" -> "string"), "lastName" -> Json.obj("type" -> "string"), "age" -> Json.obj("type" -> "integer"), - "active" -> Json.obj("type" -> "boolean")), + "active" -> Json.obj("type" -> "boolean", "default" -> true)), "required" -> Json.arr("age", "lastName", "firstName")) } } diff --git a/spray-json/src/test/scala/com/github/andyglow/jsonschema/AsSpraySpec.scala b/spray-json/src/test/scala/com/github/andyglow/jsonschema/AsSpraySpec.scala index d8536ecf..d10dce6c 100644 --- a/spray-json/src/test/scala/com/github/andyglow/jsonschema/AsSpraySpec.scala +++ b/spray-json/src/test/scala/com/github/andyglow/jsonschema/AsSpraySpec.scala @@ -40,7 +40,7 @@ class AsSpraySpec extends PropSpec { "middleName" -> JsObject("type" -> JsString("string")), "lastName" -> JsObject("type" -> JsString("string")), "age" -> JsObject("type" -> JsString("integer")), - "active" -> JsObject("type" -> JsString("boolean"))), + "active" -> JsObject("type" -> JsString("boolean"), "default" -> JsTrue)), "required" -> JsArray(JsString("age"), JsString("lastName"), JsString("firstName"))) } } diff --git a/u-json/src/test/scala/com/github/andyglow/jsonschema/AsUSpec.scala b/u-json/src/test/scala/com/github/andyglow/jsonschema/AsUSpec.scala index 2b6989d0..1caba3a3 100644 --- a/u-json/src/test/scala/com/github/andyglow/jsonschema/AsUSpec.scala +++ b/u-json/src/test/scala/com/github/andyglow/jsonschema/AsUSpec.scala @@ -41,7 +41,7 @@ class AsUSpec extends PropSpec { "properties" -> ujson.Obj( "rating" -> ujson.Obj(f"$$ref" -> ujson.Str("#/definitions/com.github.andyglow.jsonschema.AsUSpec.Rating")), "age" -> ujson.Obj("type" -> ujson.Str("integer")), - "active" -> ujson.Obj("type" -> ujson.Str("boolean")), + "active" -> ujson.Obj("type" -> ujson.Str("boolean"), "default" -> ujson.True), "name" -> ujson.Obj( "type" -> ujson.Str("object"), "additionalProperties" -> ujson.False, @@ -75,7 +75,7 @@ class AsUSpec extends PropSpec { "properties" -> ujson.Obj( "rating" -> ujson.Obj(f"$$ref" -> ujson.Str("#com.github.andyglow.jsonschema.AsUSpec.Rating")), "age" -> ujson.Obj("type" -> ujson.Str("integer")), - "active" -> ujson.Obj("type" -> ujson.Str("boolean")), + "active" -> ujson.Obj("type" -> ujson.Str("boolean"), "default" -> ujson.True), "name" -> ujson.Obj( "type" -> ujson.Str("object"), "additionalProperties" -> ujson.False, @@ -110,7 +110,7 @@ class AsUSpec extends PropSpec { "properties" -> ujson.Obj( "rating" -> ujson.Obj(f"$$ref" -> ujson.Str("#com.github.andyglow.jsonschema.AsUSpec.Rating")), "age" -> ujson.Obj("type" -> ujson.Str("integer")), - "active" -> ujson.Obj("type" -> ujson.Str("boolean")), + "active" -> ujson.Obj("type" -> ujson.Str("boolean"), "default" -> ujson.True), "name" -> ujson.Obj( "type" -> ujson.Str("object"), "additionalProperties" -> ujson.False, From 5db2542fc77bf5932da58deb7a15291403eae290 Mon Sep 17 00:00:00 2001 From: aonyshchuk Date: Fri, 11 Oct 2019 22:41:34 -0700 Subject: [PATCH 02/11] add support for maps to be exposed as default values --- .../andyglow/jsonschema/SchemaMacroSpec.scala | 11 ++++---- .../com/github/andyglow/json/ToValue.scala | 28 ++++++++++++++++++- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/api/src/test/scala/com/github/andyglow/jsonschema/SchemaMacroSpec.scala b/api/src/test/scala/com/github/andyglow/jsonschema/SchemaMacroSpec.scala index 095025f4..c9f06d01 100644 --- a/api/src/test/scala/com/github/andyglow/jsonschema/SchemaMacroSpec.scala +++ b/api/src/test/scala/com/github/andyglow/jsonschema/SchemaMacroSpec.scala @@ -44,7 +44,9 @@ class SchemaMacroSpec extends WordSpec { Field("seq" , `array`(`string`[String](None, None)), required = false, default = Seq.empty[String]), Field("set" , `set`(`integer`), required = false, default = Set(1, 5, 9)), Field("list" , `array`(`boolean`), required = false, default = List(true, false)), - Field("vector" , `array`(`number`[Long]), required = false, default = Vector(9, 7))) + Field("vector" , `array`(`number`[Long]), required = false, default = Vector(9, 7)), + Field("strMap" , `string-map`(`number`[Double]), required = false, default = Map("foo" -> .12)), + Field("intMap" , `int-map`(`string`[String](None, None)), required = false, default = Map(1 -> "1", 2 -> "2"))) } "generate references for implicitly defined dependencies" in { @@ -187,10 +189,9 @@ object SchemaMacroSpec { seq: Seq[String] = Seq.empty, set: Set[Int] = Set(1, 5, 9), list: List[Boolean] = List(true, false), - vector: Vector[Long] = Vector(9L, 7L)) - - - + vector: Vector[Long] = Vector(9L, 7L), + strMap: Map[String, Double] = Map("foo" -> .12), + intMap: Map[Int, String] = Map(1 -> "1", 2 -> "2")) } sealed trait FooBar diff --git a/core/src/main/scala/com/github/andyglow/json/ToValue.scala b/core/src/main/scala/com/github/andyglow/json/ToValue.scala index 19760f44..44cf0efd 100644 --- a/core/src/main/scala/com/github/andyglow/json/ToValue.scala +++ b/core/src/main/scala/com/github/andyglow/json/ToValue.scala @@ -26,7 +26,33 @@ trait LowPriorityPrimitiveImplicits { // may generate different json representations out of the single case class instance } -trait LowPriorityImplicits extends LowPriorityPrimitiveImplicits with LowPriorityArrayImplicits +trait LowPriorityMapImplicits { this: LowPriorityPrimitiveImplicits => + import Value._ + import ToValue._ + + implicit def StrMapV[T](implicit + to: ToValue[T]): ToValue[Map[String, T]] = { + + mk { items => + val v = items map { case (k, v) => (k, to(v)) } + obj(v.toMap) + } + } + + implicit def IntMapV[T](implicit + to: ToValue[T]): ToValue[Map[Int, T]] = { + + mk { items => + val v = items map { case (k, v) => (k.toString, to(v)) } + obj(v.toMap) + } + } +} + +trait LowPriorityImplicits + extends LowPriorityPrimitiveImplicits + with LowPriorityArrayImplicits + with LowPriorityMapImplicits object ToValue extends LowPriorityImplicits { From 209b3465317d5e37f5afb0d0708442cd9c6e0813 Mon Sep 17 00:00:00 2001 From: aonyshchuk Date: Fri, 11 Oct 2019 23:42:35 -0700 Subject: [PATCH 03/11] add play level support for case classes to be able to be exposed as default values --- .../github/andyglow/jsonschema/AsPlay.scala | 48 ++++++++++++++----- .../andyglow/jsonschema/AsPlaySpec.scala | 19 +++++++- 2 files changed, 54 insertions(+), 13 deletions(-) diff --git a/play-json/src/main/scala/com/github/andyglow/jsonschema/AsPlay.scala b/play-json/src/main/scala/com/github/andyglow/jsonschema/AsPlay.scala index 2b2ffa78..1ff2ee10 100644 --- a/play-json/src/main/scala/com/github/andyglow/jsonschema/AsPlay.scala +++ b/play-json/src/main/scala/com/github/andyglow/jsonschema/AsPlay.scala @@ -20,11 +20,14 @@ object AsPlay { type P def adapt(x: T): P + + // TODO: adding this we turn entire Adapter idea into isomorphism, so consider renaming it into ISO + def unadapt(x: P): T } trait LowPriorityAdapter { - implicit val anyAdapter: Adapter.Aux[Value, JsValue] = Adapter make { + implicit val anyAdapter: Adapter.Aux[Value, JsValue] = Adapter.make({ case `null` => JsNull case `true` => JsTrue case `false` => JsFalse @@ -32,24 +35,47 @@ object AsPlay { case x: str => Adapter.strAdapter.adapt(x) case x: arr => Adapter.arrAdapter.adapt(x) case x: obj => Adapter.objAdapter.adapt(x) - } + }, { + case JsNull => `null` + case JsTrue => `true` + case JsFalse => `false` + case x: JsNumber => Adapter.numAdapter.unadapt(x) + case x: JsString => Adapter.strAdapter.unadapt(x) + case x: JsArray => Adapter.arrAdapter.unadapt(x) + case x: JsObject => Adapter.objAdapter.unadapt(x) + }) } object Adapter extends LowPriorityAdapter { type Aux[T, PP] = Adapter[T] { type P = PP } - def make[T, PP](f: T => PP): Aux[T, PP] = new Adapter[T] { + def adapt[T, PP](value: T)(implicit a: Aux[T, PP]): PP = a.adapt(value) + def unadapt[T, PP](value: PP)(implicit a: Aux[T, PP]): T = a.unadapt(value) + + def make[T, PP](t: T => PP, f: PP => T): Aux[T, PP] = new Adapter[T] { type P = PP - def adapt(x: T): PP = f(x) + def adapt(x: T): PP = t(x) + def unadapt(x: PP): T = f(x) } - implicit val nullAdapter: Aux[`null`.type, JsNull.type] = make(_ => JsNull) - implicit val trueAdapter: Aux[`true`.type, JsBoolean] = make(_ => JsTrue) - implicit val falseAdapter: Aux[`false`.type, JsBoolean] = make(_ => JsFalse) - implicit val numAdapter: Aux[num, JsNumber] = make(x => JsNumber(x.value)) - implicit val strAdapter: Aux[str, JsString] = make(x => JsString(x.value)) - implicit val arrAdapter: Aux[arr, JsArray] = make(x => JsArray { x.value.toSeq map { AsPlay(_) } }) - implicit val objAdapter: Aux[obj, JsObject] = make(x => JsObject { x.value.toMap mapV { AsPlay(_) } }) + implicit val nullAdapter: Aux[`null`.type, JsNull.type] = make(_ => JsNull, _ => `null`) + implicit val trueAdapter: Aux[`true`.type, JsBoolean] = make(_ => JsTrue, _ => `true`) + implicit val falseAdapter: Aux[`false`.type, JsBoolean] = make(_ => JsFalse, _ => `false`) + implicit val numAdapter: Aux[num, JsNumber] = make(x => JsNumber(x.value), x => num(x.value)) + implicit val strAdapter: Aux[str, JsString] = make(x => JsString(x.value), x => str(x.value)) + implicit val arrAdapter: Aux[arr, JsArray] = make( + x => JsArray { x.value.toSeq map { adapt(_) } }, + x => arr { x.value map { unadapt(_) }}) + implicit val objAdapter: Aux[obj, JsObject] = make( + x => JsObject { x.value.toMap mapV { adapt(_) } }, + x => obj { x.value.toMap mapV { unadapt(_) } }) + } + + implicit def toValue[T](implicit w: Writes[T]): ToValue[T] = new ToValue[T] { + override def apply(x: T): Value = { + val js = w.writes(x) + Adapter.unadapt(js) + } } } diff --git a/play-json/src/test/scala/com/github/andyglow/jsonschema/AsPlaySpec.scala b/play-json/src/test/scala/com/github/andyglow/jsonschema/AsPlaySpec.scala index ad919a6a..06c35f1f 100644 --- a/play-json/src/test/scala/com/github/andyglow/jsonschema/AsPlaySpec.scala +++ b/play-json/src/test/scala/com/github/andyglow/jsonschema/AsPlaySpec.scala @@ -1,5 +1,7 @@ package com.github.andyglow.jsonschema +import java.time.LocalDate + import com.github.andyglow.json.Value import org.scalatest._ import org.scalatest.Matchers._ @@ -40,19 +42,32 @@ class AsPlaySpec extends PropSpec{ "middleName" -> Json.obj("type" -> "string"), "lastName" -> Json.obj("type" -> "string"), "age" -> Json.obj("type" -> "integer"), - "active" -> Json.obj("type" -> "boolean", "default" -> true)), + "active" -> Json.obj("type" -> "boolean", "default" -> true), + "credentials" -> Json.obj("type" -> "object", + "additionalProperties" -> false, + "required" -> Json.arr("login", "password"), + "properties" -> Json.obj( + "login" -> Json.obj("type" -> "string"), + "password" -> Json.obj("type" -> "string")), + "default" -> Json.obj("login" -> "anonymous", "password" -> "-"))), "required" -> Json.arr("age", "lastName", "firstName")) } } object AsPlaySpec { + case class Credentials(login: String, password: String) + object Credentials { + implicit val writes: Writes[Credentials] = Json.writes[Credentials] + } + case class UserProfile( firstName: String, middleName: Option[String], lastName: String, age: Int, - active: Boolean = true) + active: Boolean = true, + credentials: Credentials = Credentials("anonymous", "-")) implicit val jsValEq: Equality[JsValue] = new Equality[JsValue] { override def areEqual(a: JsValue, b: Any): Boolean = a match { From edcddcab4f3a19b99ab562208e1c61f657cb63c2 Mon Sep 17 00:00:00 2001 From: aonyshchuk Date: Mon, 14 Oct 2019 02:14:35 -0700 Subject: [PATCH 04/11] add play level support for ADT to be able to be exposed as default values --- build.sbt | 4 +- .../com/github/andyglow/json/ToValue.scala | 6 +++ .../andyglow/jsonschema/AsDraftSupport.scala | 2 +- core/src/main/scala/json/Schema.scala | 11 ++++- .../andyglow/jsonschema/SchemaMacro.scala | 47 ++++++++++++++----- .../andyglow/jsonschema/ParseJsonSchema.scala | 2 +- .../github/andyglow/jsonschema/Active.scala | 20 ++++++++ .../andyglow/jsonschema/BetaFeature.scala | 20 ++++++++ .../andyglow/jsonschema/Credentials.scala | 9 ++++ .../com/github/andyglow/jsonschema/Role.scala | 24 ++++++++++ .../andyglow/jsonschema/UserProfile.scala | 11 +++++ .../andyglow/jsonschema/AsPlaySpec.scala | 18 ++----- project/build.properties | 2 +- 13 files changed, 146 insertions(+), 30 deletions(-) create mode 100644 play-json/src/test/scala-2.11/com/github/andyglow/jsonschema/Active.scala create mode 100644 play-json/src/test/scala-2.11/com/github/andyglow/jsonschema/BetaFeature.scala create mode 100644 play-json/src/test/scala-2.11/com/github/andyglow/jsonschema/Credentials.scala create mode 100644 play-json/src/test/scala-2.11/com/github/andyglow/jsonschema/Role.scala create mode 100644 play-json/src/test/scala-2.11/com/github/andyglow/jsonschema/UserProfile.scala diff --git a/build.sbt b/build.sbt index 3b2ae648..38394aa1 100644 --- a/build.sbt +++ b/build.sbt @@ -17,7 +17,7 @@ lazy val commonSettings = Seq( scalaVersion := "2.11.12", - crossScalaVersions := Seq("2.11.12", "2.12.8", "2.13.0"), + crossScalaVersions := Seq("2.11.12", "2.12.10", "2.13.1"), scalacOptions ++= { val options = Seq( @@ -109,7 +109,7 @@ lazy val macros = project in file("macros") dependsOn core settings ( name := "scala-jsonschema-macros", libraryDependencies ++= Seq( - (scalaVersion apply ("org.scala-lang" % "scala-reflect" % _ % Compile)).value) + (scalaVersion apply ("org.scala-lang" % "scala-reflect" % _ % Compile)).value.withSources.withJavadoc) ) lazy val api = { project in file("api") }.dependsOn(core, macros).settings( diff --git a/core/src/main/scala/com/github/andyglow/json/ToValue.scala b/core/src/main/scala/com/github/andyglow/json/ToValue.scala index 44cf0efd..bd2503dd 100644 --- a/core/src/main/scala/com/github/andyglow/json/ToValue.scala +++ b/core/src/main/scala/com/github/andyglow/json/ToValue.scala @@ -49,6 +49,12 @@ trait LowPriorityMapImplicits { this: LowPriorityPrimitiveImplicits => } } +trait LowPriorityProductImplicits { + import Value._ + import ToValue._ + + implicit def ProductV[T <: Product]: ToValue[T] = mk(p => str(p.productPrefix)) +} trait LowPriorityImplicits extends LowPriorityPrimitiveImplicits with LowPriorityArrayImplicits diff --git a/core/src/main/scala/com/github/andyglow/jsonschema/AsDraftSupport.scala b/core/src/main/scala/com/github/andyglow/jsonschema/AsDraftSupport.scala index 9c77807c..ee8f0331 100644 --- a/core/src/main/scala/com/github/andyglow/jsonschema/AsDraftSupport.scala +++ b/core/src/main/scala/com/github/andyglow/jsonschema/AsDraftSupport.scala @@ -64,7 +64,7 @@ trait AsDraftSupport { def mkEnum(pp: Option[ValidationDef[_, _]], x: `enum`[_]): obj = { obj( "type" -> "string", - "enum" -> x.values.toArr) + "enum" -> arr(x.values.toSeq)) } def mkOneOf(pp: Option[ValidationDef[_, _]], x: `oneof`[_]): obj = { diff --git a/core/src/main/scala/json/Schema.scala b/core/src/main/scala/json/Schema.scala index 3b4c717b..178435aa 100644 --- a/core/src/main/scala/json/Schema.scala +++ b/core/src/main/scala/json/Schema.scala @@ -68,7 +68,7 @@ object Schema { final case class `object`[T](fields: Set[`object`.Field[_]]) extends Schema[T] - final case class `enum`[T](values: Set[String]) extends Schema[T] + final case class `enum`[T](values: Set[Value]) extends Schema[T] final case class `oneof`[T](subTypes: Set[Schema[_]]) extends Schema[T] @@ -179,6 +179,15 @@ object Schema { new Field(name, tpe, required, Some(ToValue(default))) } + + def fromJson[T]( + name: String, + tpe: Schema[T], + required: Boolean, + default: Option[Value]): Field[T] = { + + new Field(name, tpe, required, default) + } } def apply[T](field: Field[_], xs: Field[_]*): `object`[T] = new `object`((field +: xs.toSeq).toSet) diff --git a/macros/src/main/scala/com/github/andyglow/jsonschema/SchemaMacro.scala b/macros/src/main/scala/com/github/andyglow/jsonschema/SchemaMacro.scala index 57bd7dee..67d37a89 100644 --- a/macros/src/main/scala/com/github/andyglow/jsonschema/SchemaMacro.scala +++ b/macros/src/main/scala/com/github/andyglow/jsonschema/SchemaMacro.scala @@ -3,6 +3,8 @@ package com.github.andyglow.jsonschema import java.net.{URI, URL} import java.util.UUID +import com.github.andyglow.json.ToValue + import scala.reflect.macros.blackbox object SchemaMacro { @@ -10,12 +12,14 @@ object SchemaMacro { def impl[T : c.WeakTypeTag](c: blackbox.Context): c.Expr[json.Schema[T]] = { import c.universe._ - val jsonPkg = q"_root_.json" - val scalaPkg = q"_root_.scala" - val schemaObj = q"$jsonPkg.Schema" + val jsonPkg = q"_root_.json" + val intJsonPkg = q"_root_.com.github.andyglow.json" + val scalaPkg = q"_root_.scala" + val schemaObj = q"$jsonPkg.Schema" val subject = weakTypeOf[T] val optionTpe = weakTypeOf[Option[_]] + val toValueTpe = weakTypeOf[ToValue[_]] val setTpe = weakTypeOf[Set[_]] val jsonTypeConstructor = weakTypeOf[json.Schema[_]].typeConstructor val jsonSubject = appliedType(jsonTypeConstructor, subject) @@ -103,19 +107,37 @@ object SchemaMacro { object SE { - def unapply(tpe: Type): Option[Set[String]] = { + def unapply(tpe: Type): Option[Set[Tree]] = { + if (tpe.typeSymbol.isClass && tpe.typeSymbol.asClass.isSealed) { val instances = tpe.typeSymbol.asClass.knownDirectSubclasses - - if (instances forall { i => val c = i.asClass; c.isModuleClass && c.isCaseClass}) { - Some(instances map { _.name.decodedName.toString }) + val toValueTree = c.inferImplicitValue( + appliedType(toValueTpe, tpe), + silent = true, + withMacrosDisabled = true) + + if (instances forall { i => val c = i.asClass; c.isModuleClass}) { + if (toValueTree.nonEmpty) { + Some(instances collect { + case i: ClassSymbol => + val caseObj = i.owner.asClass.toType.decls.find { d => + d.name == i.name.toTermName + } getOrElse NoSymbol + + q"$toValueTree($caseObj)" + }) + } else { + Some(instances map { i => q"$intJsonPkg.Value.str(${i.name.decodedName.toString})" }) + } } else None } else None } - def gen(tpe: Type, names: Set[String]): Tree = q"$schemaObj.`enum`[$tpe]($names)" + def gen(tpe: Type, symbols: Set[Tree]): Tree = { + q"$schemaObj.`enum`[$tpe]($symbols)" + } } object SC { @@ -201,15 +223,18 @@ object SchemaMacro { val subjectCompanionSym = tpe.typeSymbol val subjectCompanion = lookupCompanionOf(subjectCompanionSym) - def toField(fieldSym: TermSymbol, i: Int): Field = { val name = fieldSym.name.toString.trim val fieldTpe = fieldSym.typeSignature val isOption = fieldTpe <:< optionTpe val hasDefault = fieldSym.isParamWithDefault + val toV = c.inferImplicitValue(appliedType(toValueTpe, fieldTpe)) val default = if (hasDefault) { val getter = TermName("apply$default$" + (i + 1)) - Some(q"$subjectCompanion.$getter") + if (toV.nonEmpty) Some(q"Some($toV($subjectCompanion.$getter))") else { + c.error(c.enclosingPosition, s"Can't infer a json value for $name") + None + } } else None @@ -251,7 +276,7 @@ object SchemaMacro { val jsonType = resolve(f.effectiveTpe, if (f.isOption) stack else tpe +: stack) f.default map { d => - q"$obj.Field[${f.effectiveTpe}](name = $name, tpe = $jsonType, required = ${ !f.isOption && !f.hasDefault }, default = $d)" + q"$obj.Field.fromJson[${f.effectiveTpe}](name = $name, tpe = $jsonType, required = ${ !f.isOption && !f.hasDefault }, default = $d)" } getOrElse { q"$obj.Field[${f.effectiveTpe}](name = $name, tpe = $jsonType, required = ${ !f.isOption && !f.hasDefault })" } diff --git a/parser/src/main/scala/com/github/andyglow/jsonschema/ParseJsonSchema.scala b/parser/src/main/scala/com/github/andyglow/jsonschema/ParseJsonSchema.scala index c60f8b98..55959427 100644 --- a/parser/src/main/scala/com/github/andyglow/jsonschema/ParseJsonSchema.scala +++ b/parser/src/main/scala/com/github/andyglow/jsonschema/ParseJsonSchema.scala @@ -51,7 +51,7 @@ object ParseJsonSchema { def makeStrOrEnum = x.value.arr("enum") match { case None => makeStr - case Some(arr) => Success { `enum`((arr collect { case str(x) => x }).toSet) } + case Some(arr) => Success { `enum`(arr.toSet) } } def makeStr = Success { diff --git a/play-json/src/test/scala-2.11/com/github/andyglow/jsonschema/Active.scala b/play-json/src/test/scala-2.11/com/github/andyglow/jsonschema/Active.scala new file mode 100644 index 00000000..ff5b5081 --- /dev/null +++ b/play-json/src/test/scala-2.11/com/github/andyglow/jsonschema/Active.scala @@ -0,0 +1,20 @@ +package com.github.andyglow.jsonschema + +import com.github.andyglow.json.{ToValue, Value} + +// members goes without companion + +sealed trait Active + +case object On extends Active +case object Off extends Active +case object Suspended extends Active + +object Active { + + implicit val ActiveV: ToValue[Active] = ToValue mk { + case On => Value.str("On") + case Off => Value.str("Off") + case Suspended => Value.str("Suspended") + } +} \ No newline at end of file diff --git a/play-json/src/test/scala-2.11/com/github/andyglow/jsonschema/BetaFeature.scala b/play-json/src/test/scala-2.11/com/github/andyglow/jsonschema/BetaFeature.scala new file mode 100644 index 00000000..43e1974b --- /dev/null +++ b/play-json/src/test/scala-2.11/com/github/andyglow/jsonschema/BetaFeature.scala @@ -0,0 +1,20 @@ +package com.github.andyglow.jsonschema + +import play.api.libs.json._ + +sealed trait BetaFeature + +case object F0 extends BetaFeature +case object F1 extends BetaFeature +case object F2 extends BetaFeature + +object BetaFeature { + + implicit val BetaFeatureW: Writes[BetaFeature] = new Writes[BetaFeature] { + override def writes(o: BetaFeature): JsValue = o match { + case F0 => JsString("feature-0-name") + case F1 => JsString("feature-1-name") + case F2 => JsString("feature-2-name") + } + } +} diff --git a/play-json/src/test/scala-2.11/com/github/andyglow/jsonschema/Credentials.scala b/play-json/src/test/scala-2.11/com/github/andyglow/jsonschema/Credentials.scala new file mode 100644 index 00000000..2fd4cc18 --- /dev/null +++ b/play-json/src/test/scala-2.11/com/github/andyglow/jsonschema/Credentials.scala @@ -0,0 +1,9 @@ +package com.github.andyglow.jsonschema + +import play.api.libs.json._ + +case class Credentials(login: String, password: String) +object Credentials { + implicit val writes: Writes[Credentials] = Json.writes[Credentials] +} + diff --git a/play-json/src/test/scala-2.11/com/github/andyglow/jsonschema/Role.scala b/play-json/src/test/scala-2.11/com/github/andyglow/jsonschema/Role.scala new file mode 100644 index 00000000..44412d0d --- /dev/null +++ b/play-json/src/test/scala-2.11/com/github/andyglow/jsonschema/Role.scala @@ -0,0 +1,24 @@ +package com.github.andyglow.jsonschema + +import play.api.libs.json._ + +// members goes inside companion + +sealed trait Role + +object Role { + + case object User extends Role + + case object Admin extends Role + + case object Manager extends Role + + implicit val RoleW: Writes[Role] = new Writes[Role] { + override def writes(o: Role): JsValue = o match { + case User => JsString("e-user") + case Manager => JsString("e-manager") + case Admin => JsString("e-admin") + } + } +} \ No newline at end of file diff --git a/play-json/src/test/scala-2.11/com/github/andyglow/jsonschema/UserProfile.scala b/play-json/src/test/scala-2.11/com/github/andyglow/jsonschema/UserProfile.scala new file mode 100644 index 00000000..a1b77650 --- /dev/null +++ b/play-json/src/test/scala-2.11/com/github/andyglow/jsonschema/UserProfile.scala @@ -0,0 +1,11 @@ +package com.github.andyglow.jsonschema + +case class UserProfile( + firstName: String, + middleName: Option[String], + lastName: String, + age: Int, + enabledFeatures: Set[BetaFeature] = Set(F0, F1), + active: Active = On, + credentials: Credentials = Credentials("anonymous", "-"), + role: Role = Role.User) \ No newline at end of file diff --git a/play-json/src/test/scala/com/github/andyglow/jsonschema/AsPlaySpec.scala b/play-json/src/test/scala/com/github/andyglow/jsonschema/AsPlaySpec.scala index 06c35f1f..c60e4a9f 100644 --- a/play-json/src/test/scala/com/github/andyglow/jsonschema/AsPlaySpec.scala +++ b/play-json/src/test/scala/com/github/andyglow/jsonschema/AsPlaySpec.scala @@ -42,7 +42,9 @@ class AsPlaySpec extends PropSpec{ "middleName" -> Json.obj("type" -> "string"), "lastName" -> Json.obj("type" -> "string"), "age" -> Json.obj("type" -> "integer"), - "active" -> Json.obj("type" -> "boolean", "default" -> true), + "role" -> Json.obj("type" -> "string", "default" -> "e-user", "enum" -> Json.arr("e-admin","e-manager","e-user")), + "active" -> Json.obj("type" -> "string", "default" -> "On", "enum" -> Json.arr("On", "Off", "Suspended")), + "enabledFeatures" -> Json.obj("type" -> "array", "uniqueItems" -> true, "default" -> Json.arr("feature-0-name", "feature-1-name"), "items" -> Json.obj("type" -> "string", "enum" -> Json.arr("feature-0-name", "feature-1-name", "feature-2-name"))), "credentials" -> Json.obj("type" -> "object", "additionalProperties" -> false, "required" -> Json.arr("login", "password"), @@ -54,20 +56,10 @@ class AsPlaySpec extends PropSpec{ } } -object AsPlaySpec { - case class Credentials(login: String, password: String) - object Credentials { - implicit val writes: Writes[Credentials] = Json.writes[Credentials] - } - case class UserProfile( - firstName: String, - middleName: Option[String], - lastName: String, - age: Int, - active: Boolean = true, - credentials: Credentials = Credentials("anonymous", "-")) +object AsPlaySpec { + import Role._ implicit val jsValEq: Equality[JsValue] = new Equality[JsValue] { override def areEqual(a: JsValue, b: Any): Boolean = a match { diff --git a/project/build.properties b/project/build.properties index c03cdb80..a7237716 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version = 1.2.7 \ No newline at end of file +sbt.version = 1.3.0 \ No newline at end of file From b012e955a348e7cb80a64ce0133f01bba3810f47 Mon Sep 17 00:00:00 2001 From: aonyshchuk Date: Mon, 14 Oct 2019 02:25:46 -0700 Subject: [PATCH 05/11] cleanup a data models for tests --- .../andyglow/jsonschema/model}/Active.scala | 3 ++- .../jsonschema/model/BetaFeature.scala | 7 +++++ .../jsonschema/model/Credentials.scala | 5 ++++ .../andyglow/jsonschema/model/Role.scala | 15 +++++++++++ .../jsonschema/model}/UserProfile.scala | 2 +- build.sbt | 10 +++---- .../andyglow/jsonschema/BetaFeature.scala | 20 -------------- .../andyglow/jsonschema/Credentials.scala | 9 ------- .../com/github/andyglow/jsonschema/Role.scala | 24 ----------------- .../andyglow/jsonschema/AsPlaySpec.scala | 3 ++- .../andyglow/jsonschema/UserProfileJson.scala | 27 +++++++++++++++++++ 11 files changed, 64 insertions(+), 61 deletions(-) rename {play-json/src/test/scala-2.11/com/github/andyglow/jsonschema => api/src/test/scala/com/github/andyglow/jsonschema/model}/Active.scala (83%) create mode 100644 api/src/test/scala/com/github/andyglow/jsonschema/model/BetaFeature.scala create mode 100644 api/src/test/scala/com/github/andyglow/jsonschema/model/Credentials.scala create mode 100644 api/src/test/scala/com/github/andyglow/jsonschema/model/Role.scala rename {play-json/src/test/scala-2.11/com/github/andyglow/jsonschema => api/src/test/scala/com/github/andyglow/jsonschema/model}/UserProfile.scala (85%) delete mode 100644 play-json/src/test/scala-2.11/com/github/andyglow/jsonschema/BetaFeature.scala delete mode 100644 play-json/src/test/scala-2.11/com/github/andyglow/jsonschema/Credentials.scala delete mode 100644 play-json/src/test/scala-2.11/com/github/andyglow/jsonschema/Role.scala create mode 100644 play-json/src/test/scala/com/github/andyglow/jsonschema/UserProfileJson.scala diff --git a/play-json/src/test/scala-2.11/com/github/andyglow/jsonschema/Active.scala b/api/src/test/scala/com/github/andyglow/jsonschema/model/Active.scala similarity index 83% rename from play-json/src/test/scala-2.11/com/github/andyglow/jsonschema/Active.scala rename to api/src/test/scala/com/github/andyglow/jsonschema/model/Active.scala index ff5b5081..6052ddfd 100644 --- a/play-json/src/test/scala-2.11/com/github/andyglow/jsonschema/Active.scala +++ b/api/src/test/scala/com/github/andyglow/jsonschema/model/Active.scala @@ -1,4 +1,4 @@ -package com.github.andyglow.jsonschema +package com.github.andyglow.jsonschema.model import com.github.andyglow.json.{ToValue, Value} @@ -12,6 +12,7 @@ case object Suspended extends Active object Active { + // ToValue is explicitly specified implicit val ActiveV: ToValue[Active] = ToValue mk { case On => Value.str("On") case Off => Value.str("Off") diff --git a/api/src/test/scala/com/github/andyglow/jsonschema/model/BetaFeature.scala b/api/src/test/scala/com/github/andyglow/jsonschema/model/BetaFeature.scala new file mode 100644 index 00000000..5f28bb03 --- /dev/null +++ b/api/src/test/scala/com/github/andyglow/jsonschema/model/BetaFeature.scala @@ -0,0 +1,7 @@ +package com.github.andyglow.jsonschema.model + +sealed trait BetaFeature + +case object F0 extends BetaFeature +case object F1 extends BetaFeature +case object F2 extends BetaFeature diff --git a/api/src/test/scala/com/github/andyglow/jsonschema/model/Credentials.scala b/api/src/test/scala/com/github/andyglow/jsonschema/model/Credentials.scala new file mode 100644 index 00000000..fa042660 --- /dev/null +++ b/api/src/test/scala/com/github/andyglow/jsonschema/model/Credentials.scala @@ -0,0 +1,5 @@ +package com.github.andyglow.jsonschema.model + + +case class Credentials(login: String, password: String) + diff --git a/api/src/test/scala/com/github/andyglow/jsonschema/model/Role.scala b/api/src/test/scala/com/github/andyglow/jsonschema/model/Role.scala new file mode 100644 index 00000000..f2c0a325 --- /dev/null +++ b/api/src/test/scala/com/github/andyglow/jsonschema/model/Role.scala @@ -0,0 +1,15 @@ +package com.github.andyglow.jsonschema.model + + +// members goes inside companion + +sealed trait Role + +object Role { + + case object User extends Role + + case object Admin extends Role + + case object Manager extends Role +} \ No newline at end of file diff --git a/play-json/src/test/scala-2.11/com/github/andyglow/jsonschema/UserProfile.scala b/api/src/test/scala/com/github/andyglow/jsonschema/model/UserProfile.scala similarity index 85% rename from play-json/src/test/scala-2.11/com/github/andyglow/jsonschema/UserProfile.scala rename to api/src/test/scala/com/github/andyglow/jsonschema/model/UserProfile.scala index a1b77650..7a13c3d9 100644 --- a/play-json/src/test/scala-2.11/com/github/andyglow/jsonschema/UserProfile.scala +++ b/api/src/test/scala/com/github/andyglow/jsonschema/model/UserProfile.scala @@ -1,4 +1,4 @@ -package com.github.andyglow.jsonschema +package com.github.andyglow.jsonschema.model case class UserProfile( firstName: String, diff --git a/build.sbt b/build.sbt index 38394aa1..73ba5b34 100644 --- a/build.sbt +++ b/build.sbt @@ -118,7 +118,7 @@ lazy val api = { project in file("api") }.dependsOn(core, macros).settings( name := "scala-jsonschema-api" ) -lazy val `play-json` = { project in file("play-json") }.dependsOn(core, api).settings( +lazy val `play-json` = { project in file("play-json") }.dependsOn(core % "compile->compile;test->test", api).settings( commonSettings, name := "scala-jsonschema-play-json", @@ -126,7 +126,7 @@ lazy val `play-json` = { project in file("play-json") }.dependsOn(core, api).set libraryDependencies += "com.typesafe.play" %% "play-json" % "2.7.4" ) -lazy val `spray-json` = { project in file("spray-json") }.dependsOn(core, api).settings( +lazy val `spray-json` = { project in file("spray-json") }.dependsOn(core % "compile->compile;test->test", api).settings( commonSettings, name := "scala-jsonschema-spray-json", @@ -134,7 +134,7 @@ lazy val `spray-json` = { project in file("spray-json") }.dependsOn(core, api).s libraryDependencies += "io.spray" %% "spray-json" % "1.3.5" ) -lazy val `circe-json` = { project in file("circe-json") }.dependsOn(core, api).settings( +lazy val `circe-json` = { project in file("circe-json") }.dependsOn(core % "compile->compile;test->test", api).settings( commonSettings, name := "scala-jsonschema-circe-json", @@ -150,7 +150,7 @@ lazy val `circe-json` = { project in file("circe-json") }.dependsOn(core, api).s } ) -lazy val `json4s-json` = { project in file("json4s-json") }.dependsOn(core, api).settings( +lazy val `json4s-json` = { project in file("json4s-json") }.dependsOn(core % "compile->compile;test->test", api).settings( commonSettings, name := "scala-jsonschema-json4s-json", @@ -158,7 +158,7 @@ lazy val `json4s-json` = { project in file("json4s-json") }.dependsOn(core, api) libraryDependencies += "org.json4s" %% "json4s-ast" % "3.6.7" ) -lazy val `u-json` = { project in file("u-json") }.dependsOn(core, api).settings( +lazy val `u-json` = { project in file("u-json") }.dependsOn(core % "compile->compile;test->test", api).settings( commonSettings, name := "scala-jsonschema-ujson", diff --git a/play-json/src/test/scala-2.11/com/github/andyglow/jsonschema/BetaFeature.scala b/play-json/src/test/scala-2.11/com/github/andyglow/jsonschema/BetaFeature.scala deleted file mode 100644 index 43e1974b..00000000 --- a/play-json/src/test/scala-2.11/com/github/andyglow/jsonschema/BetaFeature.scala +++ /dev/null @@ -1,20 +0,0 @@ -package com.github.andyglow.jsonschema - -import play.api.libs.json._ - -sealed trait BetaFeature - -case object F0 extends BetaFeature -case object F1 extends BetaFeature -case object F2 extends BetaFeature - -object BetaFeature { - - implicit val BetaFeatureW: Writes[BetaFeature] = new Writes[BetaFeature] { - override def writes(o: BetaFeature): JsValue = o match { - case F0 => JsString("feature-0-name") - case F1 => JsString("feature-1-name") - case F2 => JsString("feature-2-name") - } - } -} diff --git a/play-json/src/test/scala-2.11/com/github/andyglow/jsonschema/Credentials.scala b/play-json/src/test/scala-2.11/com/github/andyglow/jsonschema/Credentials.scala deleted file mode 100644 index 2fd4cc18..00000000 --- a/play-json/src/test/scala-2.11/com/github/andyglow/jsonschema/Credentials.scala +++ /dev/null @@ -1,9 +0,0 @@ -package com.github.andyglow.jsonschema - -import play.api.libs.json._ - -case class Credentials(login: String, password: String) -object Credentials { - implicit val writes: Writes[Credentials] = Json.writes[Credentials] -} - diff --git a/play-json/src/test/scala-2.11/com/github/andyglow/jsonschema/Role.scala b/play-json/src/test/scala-2.11/com/github/andyglow/jsonschema/Role.scala deleted file mode 100644 index 44412d0d..00000000 --- a/play-json/src/test/scala-2.11/com/github/andyglow/jsonschema/Role.scala +++ /dev/null @@ -1,24 +0,0 @@ -package com.github.andyglow.jsonschema - -import play.api.libs.json._ - -// members goes inside companion - -sealed trait Role - -object Role { - - case object User extends Role - - case object Admin extends Role - - case object Manager extends Role - - implicit val RoleW: Writes[Role] = new Writes[Role] { - override def writes(o: Role): JsValue = o match { - case User => JsString("e-user") - case Manager => JsString("e-manager") - case Admin => JsString("e-admin") - } - } -} \ No newline at end of file diff --git a/play-json/src/test/scala/com/github/andyglow/jsonschema/AsPlaySpec.scala b/play-json/src/test/scala/com/github/andyglow/jsonschema/AsPlaySpec.scala index c60e4a9f..e7020476 100644 --- a/play-json/src/test/scala/com/github/andyglow/jsonschema/AsPlaySpec.scala +++ b/play-json/src/test/scala/com/github/andyglow/jsonschema/AsPlaySpec.scala @@ -7,12 +7,14 @@ import org.scalatest._ import org.scalatest.Matchers._ import org.scalatest.prop.TableDrivenPropertyChecks._ import com.github.andyglow.json.Value._ +import com.github.andyglow.jsonschema.model.UserProfile import json.schema.Version.Draft04 import org.scalactic.Equality import play.api.libs.json._ class AsPlaySpec extends PropSpec{ import AsPlaySpec._ + import UserProfileJson._ private val examples = Table[Value, JsValue]( ("json" , "PlayJson"), @@ -59,7 +61,6 @@ class AsPlaySpec extends PropSpec{ object AsPlaySpec { - import Role._ implicit val jsValEq: Equality[JsValue] = new Equality[JsValue] { override def areEqual(a: JsValue, b: Any): Boolean = a match { diff --git a/play-json/src/test/scala/com/github/andyglow/jsonschema/UserProfileJson.scala b/play-json/src/test/scala/com/github/andyglow/jsonschema/UserProfileJson.scala new file mode 100644 index 00000000..4c342639 --- /dev/null +++ b/play-json/src/test/scala/com/github/andyglow/jsonschema/UserProfileJson.scala @@ -0,0 +1,27 @@ +package com.github.andyglow.jsonschema + +import com.github.andyglow.jsonschema.model._ +import play.api.libs.json._ + +object UserProfileJson { + + implicit val CredentialsW: Writes[Credentials] = Json.writes[Credentials] + + implicit val BetaFeatureW: Writes[BetaFeature] = new Writes[BetaFeature] { + override def writes(o: BetaFeature): JsValue = o match { + case F0 => JsString("feature-0-name") + case F1 => JsString("feature-1-name") + case F2 => JsString("feature-2-name") + } + } + + implicit val RoleW: Writes[Role] = new Writes[Role] { + import Role._ + + override def writes(o: Role): JsValue = o match { + case User => JsString("e-user") + case Manager => JsString("e-manager") + case Admin => JsString("e-admin") + } + } +} From 80b6460e768b5c788fdf916ac315fd7bb6af9d3e Mon Sep 17 00:00:00 2001 From: aonyshchuk Date: Mon, 14 Oct 2019 02:44:03 -0700 Subject: [PATCH 06/11] add spray support for default value --- .../github/andyglow/jsonschema/AsPlay.scala | 1 + .../andyglow/jsonschema/AsPlaySpec.scala | 2 - .../github/andyglow/jsonschema/AsSpray.scala | 48 ++++++++++++++----- .../andyglow/jsonschema/AsSpraySpec.scala | 20 ++++---- .../andyglow/jsonschema/UserProfileJson.scala | 27 +++++++++++ 5 files changed, 77 insertions(+), 21 deletions(-) create mode 100644 spray-json/src/test/scala/com/github/andyglow/jsonschema/UserProfileJson.scala diff --git a/play-json/src/main/scala/com/github/andyglow/jsonschema/AsPlay.scala b/play-json/src/main/scala/com/github/andyglow/jsonschema/AsPlay.scala index 1ff2ee10..f5cf9194 100644 --- a/play-json/src/main/scala/com/github/andyglow/jsonschema/AsPlay.scala +++ b/play-json/src/main/scala/com/github/andyglow/jsonschema/AsPlay.scala @@ -50,6 +50,7 @@ object AsPlay { type Aux[T, PP] = Adapter[T] { type P = PP } def adapt[T, PP](value: T)(implicit a: Aux[T, PP]): PP = a.adapt(value) + def unadapt[T, PP](value: PP)(implicit a: Aux[T, PP]): T = a.unadapt(value) def make[T, PP](t: T => PP, f: PP => T): Aux[T, PP] = new Adapter[T] { diff --git a/play-json/src/test/scala/com/github/andyglow/jsonschema/AsPlaySpec.scala b/play-json/src/test/scala/com/github/andyglow/jsonschema/AsPlaySpec.scala index e7020476..57da1647 100644 --- a/play-json/src/test/scala/com/github/andyglow/jsonschema/AsPlaySpec.scala +++ b/play-json/src/test/scala/com/github/andyglow/jsonschema/AsPlaySpec.scala @@ -1,7 +1,5 @@ package com.github.andyglow.jsonschema -import java.time.LocalDate - import com.github.andyglow.json.Value import org.scalatest._ import org.scalatest.Matchers._ diff --git a/spray-json/src/main/scala/com/github/andyglow/jsonschema/AsSpray.scala b/spray-json/src/main/scala/com/github/andyglow/jsonschema/AsSpray.scala index 92858b12..ec87e155 100644 --- a/spray-json/src/main/scala/com/github/andyglow/jsonschema/AsSpray.scala +++ b/spray-json/src/main/scala/com/github/andyglow/jsonschema/AsSpray.scala @@ -21,11 +21,12 @@ object AsSpray { type P def adapt(x: T): P + def unadapt(x: P): T } trait LowPriorityAdapter { - implicit val anyAdapter: Adapter.Aux[Value, JsValue] = Adapter make { + implicit val anyAdapter: Adapter.Aux[Value, JsValue] = Adapter.make({ case `null` => JsNull case `true` => JsTrue case `false` => JsFalse @@ -33,24 +34,49 @@ object AsSpray { case x: str => Adapter.strAdapter.adapt(x) case x: arr => Adapter.arrAdapter.adapt(x) case x: obj => Adapter.objAdapter.adapt(x) - } + }, { + case JsNull => `null` + case JsTrue => `true` + case JsFalse => `false` + case x: JsNumber => Adapter.numAdapter.unadapt(x) + case x: JsString => Adapter.strAdapter.unadapt(x) + case x: JsArray => Adapter.arrAdapter.unadapt(x) + case x: JsObject => Adapter.objAdapter.unadapt(x) + }) } object Adapter extends LowPriorityAdapter { type Aux[T, PP] = Adapter[T] { type P = PP } - def make[T, PP](f: T => PP): Aux[T, PP] = new Adapter[T] { + def adapt[T, PP](value: T)(implicit a: Aux[T, PP]): PP = a.adapt(value) + + def unadapt[T, PP](value: PP)(implicit a: Aux[T, PP]): T = a.unadapt(value) + + def make[T, PP](to: T => PP, from: PP => T): Aux[T, PP] = new Adapter[T] { type P = PP - def adapt(x: T): PP = f(x) + def adapt(x: T): PP = to(x) + def unadapt(x: PP): T = from(x) } - implicit val nullAdapter: Aux[`null`.type, JsNull.type] = make(_ => JsNull) - implicit val trueAdapter: Aux[`true`.type, JsBoolean] = make(_ => JsTrue) - implicit val falseAdapter: Aux[`false`.type, JsBoolean] = make(_ => JsFalse) - implicit val numAdapter: Aux[num, JsNumber] = make(x => JsNumber(x.value)) - implicit val strAdapter: Aux[str, JsString] = make(x => JsString(x.value)) - implicit val arrAdapter: Aux[arr, JsArray] = make(x => JsArray { x.value.toVector map { AsSpray(_) } }) - implicit val objAdapter: Aux[obj, JsObject] = make(x => JsObject { x.value.toMap mapV { AsSpray(_) } }) + implicit val nullAdapter: Aux[`null`.type, JsNull.type] = make(_ => JsNull, _ => `null`) + implicit val trueAdapter: Aux[`true`.type, JsBoolean] = make(_ => JsTrue, _ => `true`) + implicit val falseAdapter: Aux[`false`.type, JsBoolean] = make(_ => JsFalse, _ => `false`) + implicit val numAdapter: Aux[num, JsNumber] = make(x => JsNumber(x.value), x => num(x.value)) + implicit val strAdapter: Aux[str, JsString] = make(x => JsString(x.value), x => str(x.value)) + implicit val arrAdapter: Aux[arr, JsArray] = make( + x => JsArray { x.value.toVector map { adapt(_) } }, + x => arr { x.elements map { unadapt(_) }}) + implicit val objAdapter: Aux[obj, JsObject] = make( + x => JsObject { x.value.toMap mapV { adapt(_) } }, + x => obj { x.fields.toMap mapV { unadapt(_) } }) + } + + + implicit def toValue[T](implicit w: JsonWriter[T]): ToValue[T] = new ToValue[T] { + override def apply(x: T): Value = { + val js = w.write(x) + Adapter.unadapt(js) + } } } diff --git a/spray-json/src/test/scala/com/github/andyglow/jsonschema/AsSpraySpec.scala b/spray-json/src/test/scala/com/github/andyglow/jsonschema/AsSpraySpec.scala index d10dce6c..1431ef9e 100644 --- a/spray-json/src/test/scala/com/github/andyglow/jsonschema/AsSpraySpec.scala +++ b/spray-json/src/test/scala/com/github/andyglow/jsonschema/AsSpraySpec.scala @@ -5,12 +5,14 @@ import org.scalatest._ import org.scalatest.Matchers._ import org.scalatest.prop.TableDrivenPropertyChecks._ import com.github.andyglow.json.Value._ +import com.github.andyglow.jsonschema.model.UserProfile import json.schema.Version.Draft04 import org.scalactic.Equality import spray.json._ class AsSpraySpec extends PropSpec { import AsSpraySpec._ + import UserProfileJson._ private val examples = Table[Value, JsValue]( ("json" , "SprayJson"), @@ -40,20 +42,22 @@ class AsSpraySpec extends PropSpec { "middleName" -> JsObject("type" -> JsString("string")), "lastName" -> JsObject("type" -> JsString("string")), "age" -> JsObject("type" -> JsString("integer")), - "active" -> JsObject("type" -> JsString("boolean"), "default" -> JsTrue)), + "role" -> JsObject("type" -> JsString("string"), "default" -> JsString("e-user"), "enum" -> JsArray(JsString("e-admin"), JsString("e-manager"), JsString("e-user"))), + "active" -> JsObject("type" -> JsString("string"), "default" -> JsString("On"), "enum" -> JsArray(JsString("On"), JsString("Off"), JsString("Suspended"))), + "enabledFeatures" -> JsObject("type" -> JsString("array"), "uniqueItems" -> JsTrue, "default" -> JsArray(JsString("feature-0-name"), JsString("feature-1-name")), "items" -> JsObject("type" -> JsString("string"), "enum" -> JsArray(JsString("feature-0-name"), JsString("feature-1-name"), JsString("feature-2-name")))), + "credentials" -> JsObject("type" -> JsString("object"), + "additionalProperties" -> JsFalse, + "required" -> JsArray(JsString("login"), JsString("password")), + "properties" -> JsObject( + "login" -> JsObject("type" -> JsString("string")), + "password" -> JsObject("type" -> JsString("string"))), + "default" -> JsObject("login" -> JsString("anonymous"), "password" -> JsString("-")))), "required" -> JsArray(JsString("age"), JsString("lastName"), JsString("firstName"))) } } object AsSpraySpec { - case class UserProfile( - firstName: String, - middleName: Option[String], - lastName: String, - age: Int, - active: Boolean = true) - implicit val jsValEq: Equality[JsValue] = new Equality[JsValue] { override def areEqual(a: JsValue, b: Any): Boolean = a match { case JsNull => b == JsNull diff --git a/spray-json/src/test/scala/com/github/andyglow/jsonschema/UserProfileJson.scala b/spray-json/src/test/scala/com/github/andyglow/jsonschema/UserProfileJson.scala new file mode 100644 index 00000000..b9d0ca91 --- /dev/null +++ b/spray-json/src/test/scala/com/github/andyglow/jsonschema/UserProfileJson.scala @@ -0,0 +1,27 @@ +package com.github.andyglow.jsonschema + +import com.github.andyglow.jsonschema.model._ +import spray.json._ + +object UserProfileJson extends DefaultJsonProtocol { + + implicit val CredentialsW: JsonWriter[Credentials] = jsonFormat2(Credentials) + + implicit val BetaFeatureW: JsonWriter[BetaFeature] = new JsonWriter[BetaFeature] { + override def write(o: BetaFeature): JsValue = o match { + case F0 => JsString("feature-0-name") + case F1 => JsString("feature-1-name") + case F2 => JsString("feature-2-name") + } + } + + implicit val RoleW: JsonWriter[Role] = new JsonWriter[Role] { + import Role._ + + override def write(o: Role): JsValue = o match { + case User => JsString("e-user") + case Manager => JsString("e-manager") + case Admin => JsString("e-admin") + } + } +} From 06192efa0ba105f9b3b0ad2dcfbd85d6be80dda9 Mon Sep 17 00:00:00 2001 From: aonyshchuk Date: Mon, 14 Oct 2019 03:03:32 -0700 Subject: [PATCH 07/11] add circe support for default value --- .../github/andyglow/jsonschema/AsCirce.scala | 17 +++++++++++ .../andyglow/jsonschema/AsCirceSpec.scala | 21 ++++++++------ .../andyglow/jsonschema/UserProfileJson.scala | 29 +++++++++++++++++++ 3 files changed, 58 insertions(+), 9 deletions(-) create mode 100644 circe-json/src/test/scala/com/github/andyglow/jsonschema/UserProfileJson.scala diff --git a/circe-json/src/main/scala/com/github/andyglow/jsonschema/AsCirce.scala b/circe-json/src/main/scala/com/github/andyglow/jsonschema/AsCirce.scala index 5326f15f..582b82af 100644 --- a/circe-json/src/main/scala/com/github/andyglow/jsonschema/AsCirce.scala +++ b/circe-json/src/main/scala/com/github/andyglow/jsonschema/AsCirce.scala @@ -24,4 +24,21 @@ object AsCirce { def asCirce[V <: Version](v: V)(implicit asValue: AsValueBuilder[V]): Json = AsCirce(AsValue.schema(x, v)) } + + implicit def toValue[T](implicit w: Encoder[T]): ToValue[T] = new ToValue[T] { + override def apply(x: T): Value = { + val js = w.apply(x) + def translate(js: Json): Value = js match { + case Json.Null => `null` + case Json.True => `true` + case Json.False=> `false` + case x if x.isNumber => num(x.asNumber.get.toDouble) + case x if x.isString => str(x.asString.get) + case x if x.isArray => val a = x.asArray.get map translate; arr(a) + case x if x.isObject => val map = x.asObject.get.toMap mapV translate; obj(map) + } + + translate(js) + } + } } diff --git a/circe-json/src/test/scala/com/github/andyglow/jsonschema/AsCirceSpec.scala b/circe-json/src/test/scala/com/github/andyglow/jsonschema/AsCirceSpec.scala index acb98c09..c6034363 100644 --- a/circe-json/src/test/scala/com/github/andyglow/jsonschema/AsCirceSpec.scala +++ b/circe-json/src/test/scala/com/github/andyglow/jsonschema/AsCirceSpec.scala @@ -4,12 +4,14 @@ import org.scalatest._ import org.scalatest.Matchers._ import org.scalatest.prop.TableDrivenPropertyChecks._ import com.github.andyglow.json.Value._ +import com.github.andyglow.jsonschema.model.UserProfile import io.circe._ import json.schema.Version.Draft04 import org.scalactic.Equality class AsCirceSpec extends PropSpec { import AsCirceSpec._ + import UserProfileJson._ private val examples = Table( ("json" , "Circe"), @@ -39,21 +41,22 @@ class AsCirceSpec extends PropSpec { "middleName" -> Json.obj("type" -> Json.fromString("string")), "lastName" -> Json.obj("type" -> Json.fromString("string")), "age" -> Json.obj("type" -> Json.fromString("integer")), - "active" -> Json.obj("type" -> Json.fromString("boolean"), "default" -> Json.True)), + "role" -> Json.obj("type" -> Json.fromString("string"), "default" -> Json.fromString("e-user"), "enum" -> Json.arr(Json.fromString("e-admin"), Json.fromString("e-manager"), Json.fromString("e-user"))), + "active" -> Json.obj("type" -> Json.fromString("string"), "default" -> Json.fromString("On"), "enum" -> Json.arr(Json.fromString("On"), Json.fromString("Off"), Json.fromString("Suspended"))), + "enabledFeatures" -> Json.obj("type" -> Json.fromString("array"), "uniqueItems" -> Json.True, "default" -> Json.arr(Json.fromString("feature-0-name"), Json.fromString("feature-1-name")), "items" -> Json.obj("type" -> Json.fromString("string"), "enum" -> Json.arr(Json.fromString("feature-0-name"), Json.fromString("feature-1-name"), Json.fromString("feature-2-name")))), + "credentials" -> Json.obj("type" -> Json.fromString("object"), + "additionalProperties" -> Json.False, + "required" -> Json.arr(Json.fromString("login"), Json.fromString("password")), + "properties" -> Json.obj( + "login" -> Json.obj("type" -> Json.fromString("string")), + "password" -> Json.obj("type" -> Json.fromString("string"))), + "default" -> Json.obj("login" -> Json.fromString("anonymous"), "password" -> Json.fromString("-")))), "required" -> Json.arr(Json.fromString("age"), Json.fromString("lastName"), Json.fromString("firstName"))) } } object AsCirceSpec { - case class UserProfile( - firstName: String, - middleName: Option[String], - lastName: String, - age: Int, - active: Boolean = true) - - implicit val jsValEq: Equality[Json] = new Equality[Json] { override def areEqual(a: Json, b: Any): Boolean = a match { case Json.Null => b == Json.Null diff --git a/circe-json/src/test/scala/com/github/andyglow/jsonschema/UserProfileJson.scala b/circe-json/src/test/scala/com/github/andyglow/jsonschema/UserProfileJson.scala new file mode 100644 index 00000000..f985cd20 --- /dev/null +++ b/circe-json/src/test/scala/com/github/andyglow/jsonschema/UserProfileJson.scala @@ -0,0 +1,29 @@ +package com.github.andyglow.jsonschema + + +import com.github.andyglow.jsonschema.model._ +import io.circe._ +import io.circe.generic.semiauto._ + +object UserProfileJson { + + implicit val CredentialsW: Encoder[Credentials] = deriveEncoder[Credentials] + + implicit val BetaFeatureW: Encoder[BetaFeature] = new Encoder[BetaFeature] { + override def apply(o: BetaFeature): Json = o match { + case F0 => Json.fromString("feature-0-name") + case F1 => Json.fromString("feature-1-name") + case F2 => Json.fromString("feature-2-name") + } + } + + implicit val RoleW: Encoder[Role] = new Encoder[Role] { + import Role._ + + override def apply(o: Role): Json = o match { + case User => Json.fromString("e-user") + case Manager => Json.fromString("e-manager") + case Admin => Json.fromString("e-admin") + } + } +} From d57ea541173b53d7ddd8908ea8a93073688ef3b4 Mon Sep 17 00:00:00 2001 From: aonyshchuk Date: Mon, 14 Oct 2019 03:42:38 -0700 Subject: [PATCH 08/11] add json4s support for default value --- build.sbt | 2 +- .../github/andyglow/jsonschema/AsJson4s.scala | 55 +++++++++++++++---- .../andyglow/jsonschema/AsJson4sSpec.scala | 21 ++++--- .../andyglow/jsonschema/UserProfileJson.scala | 33 +++++++++++ 4 files changed, 90 insertions(+), 21 deletions(-) create mode 100644 json4s-json/src/test/scala/com/github/andyglow/jsonschema/UserProfileJson.scala diff --git a/build.sbt b/build.sbt index 73ba5b34..8311d0b2 100644 --- a/build.sbt +++ b/build.sbt @@ -155,7 +155,7 @@ lazy val `json4s-json` = { project in file("json4s-json") }.dependsOn(core % "co name := "scala-jsonschema-json4s-json", - libraryDependencies += "org.json4s" %% "json4s-ast" % "3.6.7" + libraryDependencies += "org.json4s" %% "json4s-core" % "3.6.7" ) lazy val `u-json` = { project in file("u-json") }.dependsOn(core % "compile->compile;test->test", api).settings( diff --git a/json4s-json/src/main/scala/com/github/andyglow/jsonschema/AsJson4s.scala b/json4s-json/src/main/scala/com/github/andyglow/jsonschema/AsJson4s.scala index 419227c4..e80f153a 100644 --- a/json4s-json/src/main/scala/com/github/andyglow/jsonschema/AsJson4s.scala +++ b/json4s-json/src/main/scala/com/github/andyglow/jsonschema/AsJson4s.scala @@ -5,6 +5,9 @@ import com.github.andyglow.json._ import json.Schema import json.schema.Version import org.json4s.JsonAST._ +import com.github.andyglow.scalamigration._ +import org.json4s.Writer + object AsJson4s { @@ -20,11 +23,12 @@ object AsJson4s { type P def adapt(x: T): P + def unadapt(x: P): T } trait LowPriorityAdapter { - implicit val anyAdapter: Adapter.Aux[Value, JValue] = Adapter make { + implicit val anyAdapter: Adapter.Aux[Value, JValue] = Adapter.make({ case `null` => JNull case `true` => JBool.True case `false` => JBool.False @@ -32,30 +36,59 @@ object AsJson4s { case x: str => Adapter.strAdapter.adapt(x) case x: arr => Adapter.arrAdapter.adapt(x) case x: obj => Adapter.objAdapter.adapt(x) - } + }, { + case JNothing => `null` + case JNull => `null` + case JBool(x) => bool(x) + case JDouble(x) => num(x) + case JDecimal(x) => num(x) + case JLong(x) => num(x) + case JInt(x) => num(x) + case JString(x) => str(x) + case JSet(x) => Adapter.arrAdapter.unadapt(JArray(x.toList)) + case x: JArray => Adapter.arrAdapter.unadapt(x) + case x: JObject => Adapter.objAdapter.unadapt(x) + }) } object Adapter extends LowPriorityAdapter { type Aux[T, PP] = Adapter[T] { type P = PP } - def make[T, PP](f: T => PP): Aux[T, PP] = new Adapter[T] { + def adapt[T, PP](value: T)(implicit a: Aux[T, PP]): PP = a.adapt(value) + + def unadapt[T, PP](value: PP)(implicit a: Aux[T, PP]): T = a.unadapt(value) + + def make[T, PP](to: T => PP, from: PP => T): Aux[T, PP] = new Adapter[T] { type P = PP - def adapt(x: T): PP = f(x) + def adapt(x: T): PP = to(x) + def unadapt(x: PP): T = from(x) } - implicit val nullAdapter: Aux[`null`.type, JNull.type] = make(_ => JNull) - implicit val trueAdapter: Aux[`true`.type, JBool] = make(_ => JBool.True) - implicit val falseAdapter: Aux[`false`.type, JBool] = make(_ => JBool.False) - implicit val numAdapter: Aux[num, JDecimal] = make(x => JDecimal(x.value)) - implicit val strAdapter: Aux[str, JString] = make(x => JString(x.value)) - implicit val arrAdapter: Aux[arr, JArray] = make(x => JArray { x.value.toList map { AsJson4s(_) } }) - implicit val objAdapter: Aux[obj, JObject] = make { x => + implicit val nullAdapter: Aux[`null`.type, JNull.type] = make(_ => JNull, _ => `null`) + implicit val trueAdapter: Aux[`true`.type, JBool] = make(_ => JBool.True, _ => `true`) + implicit val falseAdapter: Aux[`false`.type, JBool] = make(_ => JBool.False, _ => `false`) + implicit val numAdapter: Aux[num, JDecimal] = make(x => JDecimal(x.value), x => num(x.values)) + implicit val strAdapter: Aux[str, JString] = make(x => JString(x.value), x => str(x.values)) + implicit val arrAdapter: Aux[arr, JArray] = make( + x => JArray { x.value.toList map { adapt(_) } }, + x => arr { x.arr map { unadapt(_) }}) + implicit val objAdapter: Aux[obj, JObject] = make({ x => val fields = x.value.toList.map { case (k, v) => JField(k, AsJson4s.apply(v)) } JObject(fields) + }, { x => + obj { x.obj.toMap mapV { unadapt(_) } } + }) + } + + + implicit def toValue[T](implicit w: Writer[T]): ToValue[T] = new ToValue[T] { + override def apply(x: T): Value = { + val js = w.write(x) + Adapter.unadapt(js) } } } diff --git a/json4s-json/src/test/scala/com/github/andyglow/jsonschema/AsJson4sSpec.scala b/json4s-json/src/test/scala/com/github/andyglow/jsonschema/AsJson4sSpec.scala index 05c32352..6dbecee7 100644 --- a/json4s-json/src/test/scala/com/github/andyglow/jsonschema/AsJson4sSpec.scala +++ b/json4s-json/src/test/scala/com/github/andyglow/jsonschema/AsJson4sSpec.scala @@ -5,12 +5,14 @@ import org.scalatest._ import org.scalatest.Matchers._ import org.scalatest.prop.TableDrivenPropertyChecks._ import com.github.andyglow.json.Value._ +import com.github.andyglow.jsonschema.model.UserProfile import json.schema.Version.Draft04 import org.json4s.JsonAST._ import org.scalactic.Equality class AsJson4sSpec extends PropSpec{ import AsJson4sSpec._ + import UserProfileJson._ private val examples = Table[Value, JValue]( ("json" , "PlayJson"), @@ -40,21 +42,22 @@ class AsJson4sSpec extends PropSpec{ "middleName" -> JObject("type" -> JString("string")), "lastName" -> JObject("type" -> JString("string")), "age" -> JObject("type" -> JString("integer")), - "active" -> JObject("type" -> JString("boolean"), "default" -> JBool.True)), + "role" -> JObject("type" -> JString("string"), "default" -> JString("e-user"), "enum" -> JArray(List(JString("e-admin"), JString("e-manager"), JString("e-user")))), + "active" -> JObject("type" -> JString("string"), "default" -> JString("On"), "enum" -> JArray(List(JString("On"), JString("Off"), JString("Suspended")))), + "enabledFeatures" -> JObject("type" -> JString("array"), "uniqueItems" -> JBool.True, "default" -> JArray(List(JString("feature-0-name"), JString("feature-1-name"))), "items" -> JObject("type" -> JString("string"), "enum" -> JArray(List(JString("feature-0-name"), JString("feature-1-name"), JString("feature-2-name"))))), + "credentials" -> JObject("type" -> JString("object"), + "additionalProperties" -> JBool.False, + "required" -> JArray(List(JString("login"), JString("password"))), + "properties" -> JObject( + "login" -> JObject("type" -> JString("string")), + "password" -> JObject("type" -> JString("string"))), + "default" -> JObject("login" -> JString("anonymous"), "password" -> JString("-")))), "required" -> JArray(List(JString("age"), JString("lastName"), JString("firstName")))) } } object AsJson4sSpec { - case class UserProfile( - firstName: String, - middleName: Option[String], - lastName: String, - age: Int, - active: Boolean = true) - - implicit val jsValEq: Equality[JValue] = new Equality[JValue] { override def areEqual(a: JValue, b: Any): Boolean = a match { case JNull => b == JNull diff --git a/json4s-json/src/test/scala/com/github/andyglow/jsonschema/UserProfileJson.scala b/json4s-json/src/test/scala/com/github/andyglow/jsonschema/UserProfileJson.scala new file mode 100644 index 00000000..f807e761 --- /dev/null +++ b/json4s-json/src/test/scala/com/github/andyglow/jsonschema/UserProfileJson.scala @@ -0,0 +1,33 @@ +package com.github.andyglow.jsonschema + +import com.github.andyglow.jsonschema.model._ +import org.json4s.Writer +import org.json4s.JsonAST._ + +object UserProfileJson { + import org.json4s.JsonMethods + + implicit val CredentialsW: Writer[Credentials] = new Writer[Credentials] { + override def write(o: Credentials): JValue = JObject( + "login" -> JString(o.login), + "password" -> JString(o.password)) + } + + implicit val BetaFeatureW: Writer[BetaFeature] = new Writer[BetaFeature] { + override def write(o: BetaFeature): JValue = o match { + case F0 => JString("feature-0-name") + case F1 => JString("feature-1-name") + case F2 => JString("feature-2-name") + } + } + + implicit val RoleW: Writer[Role] = new Writer[Role] { + import Role._ + + override def write(o: Role): JValue = o match { + case User => JString("e-user") + case Manager => JString("e-manager") + case Admin => JString("e-admin") + } + } +} From f42a67b5c049a9b2e3874590306aa084ab0fb00d Mon Sep 17 00:00:00 2001 From: aonyshchuk Date: Mon, 14 Oct 2019 04:10:28 -0700 Subject: [PATCH 09/11] add ujson support for default value --- build.sbt | 7 ++- .../com/github/andyglow/jsonschema/AsU.scala | 45 ++++++++++++++----- .../github/andyglow/jsonschema/AsUSpec.scala | 35 +++++++++++++-- .../andyglow/jsonschema/UserProfileJson.scala | 32 +++++++++++++ 4 files changed, 101 insertions(+), 18 deletions(-) create mode 100644 u-json/src/test/scala/com/github/andyglow/jsonschema/UserProfileJson.scala diff --git a/build.sbt b/build.sbt index 8311d0b2..743ec219 100644 --- a/build.sbt +++ b/build.sbt @@ -163,13 +163,16 @@ lazy val `u-json` = { project in file("u-json") }.dependsOn(core % "compile->com name := "scala-jsonschema-ujson", - libraryDependencies += { + libraryDependencies ++= { val ujsonVersion = CrossVersion.partialVersion(scalaVersion.value) match { case Some((2, 11)) => "0.7.4" case _ => "0.7.5" } - "com.lihaoyi" %% "ujson" % ujsonVersion + Seq( + "com.lihaoyi" %% "ujson" % ujsonVersion, + "com.lihaoyi" %% "upickle" % ujsonVersion) + } ) diff --git a/u-json/src/main/scala/com/github/andyglow/jsonschema/AsU.scala b/u-json/src/main/scala/com/github/andyglow/jsonschema/AsU.scala index 65113e7d..9071bf1d 100644 --- a/u-json/src/main/scala/com/github/andyglow/jsonschema/AsU.scala +++ b/u-json/src/main/scala/com/github/andyglow/jsonschema/AsU.scala @@ -5,7 +5,7 @@ import com.github.andyglow.json.Value._ import json.Schema import com.github.andyglow.scalamigration._ import json.schema.Version - +import upickle.default._ object AsU { @@ -20,11 +20,12 @@ object AsU { type P def adapt(x: T): P + def unadapt(x: P): T } trait LowPriorityAdapter { - implicit val anyAdapter: Adapter.Aux[Value, ujson.Value] = Adapter make { + implicit val anyAdapter: Adapter.Aux[Value, ujson.Value] = Adapter.make({ case `null` => ujson.Null case `true` => ujson.True case `false` => ujson.False @@ -32,24 +33,44 @@ object AsU { case x: str => Adapter.strAdapter.adapt(x) case x: arr => Adapter.arrAdapter.adapt(x) case x: obj => Adapter.objAdapter.adapt(x) - } + }, { + case ujson.Null => `null` + case ujson.True => `true` + case ujson.False => `false` + case x: ujson.Num => Adapter.numAdapter.unadapt(x) + case x: ujson.Str => Adapter.strAdapter.unadapt(x) + case x: ujson.Arr => Adapter.arrAdapter.unadapt(x) + case x: ujson.Obj => Adapter.objAdapter.unadapt(x) + }) } object Adapter extends LowPriorityAdapter { type Aux[T, PP] = Adapter[T] { type P = PP } - def make[T, PP](f: T => PP): Aux[T, PP] = new Adapter[T] { + def adapt[T, PP](value: T)(implicit a: Aux[T, PP]): PP = a.adapt(value) + + def unadapt[T, PP](value: PP)(implicit a: Aux[T, PP]): T = a.unadapt(value) + + def make[T, PP](to: T => PP, from: PP => T): Aux[T, PP] = new Adapter[T] { type P = PP - def adapt(x: T): PP = f(x) + def adapt(x: T): PP = to(x) + def unadapt(x: PP): T = from(x) } - implicit val nullAdapter: Aux[`null`.type, ujson.Null.type] = make(_ => ujson.Null) - implicit val trueAdapter: Aux[`true`.type, ujson.True.type] = make(_ => ujson.True) - implicit val falseAdapter: Aux[`false`.type, ujson.False.type] = make(_ => ujson.False) - implicit val numAdapter: Aux[num, ujson.Num] = make(x => ujson.Num(x.value.toDouble)) - implicit val strAdapter: Aux[str, ujson.Str] = make(x => ujson.Str(x.value)) - implicit val arrAdapter: Aux[arr, ujson.Arr] = make(x => x.value map { AsU(_) }) - implicit val objAdapter: Aux[obj, ujson.Obj] = make(x => x.value.toMap mapV { AsU(_) }) + implicit val nullAdapter: Aux[`null`.type, ujson.Null.type] = make(_ => ujson.Null, _ => `null`) + implicit val trueAdapter: Aux[`true`.type, ujson.True.type] = make(_ => ujson.True, _ => `true`) + implicit val falseAdapter: Aux[`false`.type, ujson.False.type] = make(_ => ujson.False, _ => `false`) + implicit val numAdapter: Aux[num, ujson.Num] = make(x => ujson.Num(x.value.toDouble), x => num(x.value)) + implicit val strAdapter: Aux[str, ujson.Str] = make(x => ujson.Str(x.value), x => str(x.value)) + implicit val arrAdapter: Aux[arr, ujson.Arr] = make(x => x.value map { adapt(_) }, x => arr { x.value map { unadapt(_) }}) + implicit val objAdapter: Aux[obj, ujson.Obj] = make(x => x.value.toMap mapV { adapt(_) }, x => obj { x.value.toMap mapV { unadapt(_) }}) + } + + implicit def toValue[T](implicit w: Writer[T]): ToValue[T] = new ToValue[T] { + override def apply(x: T): Value = { + val js = writeJs(x) + Adapter.unadapt(js) + } } } diff --git a/u-json/src/test/scala/com/github/andyglow/jsonschema/AsUSpec.scala b/u-json/src/test/scala/com/github/andyglow/jsonschema/AsUSpec.scala index 1caba3a3..4b1b6348 100644 --- a/u-json/src/test/scala/com/github/andyglow/jsonschema/AsUSpec.scala +++ b/u-json/src/test/scala/com/github/andyglow/jsonschema/AsUSpec.scala @@ -5,12 +5,14 @@ import org.scalatest._ import org.scalatest.Matchers._ import org.scalatest.prop.TableDrivenPropertyChecks._ import com.github.andyglow.json.Value._ +import com.github.andyglow.jsonschema.model.UserProfile import json.schema.Version._ import org.scalactic.Equality class AsUSpec extends PropSpec { import AsUSpec._ + import UserProfileJson._ private val examples = Table[Value, ujson.Value]( ("json" , "uJson"), @@ -34,7 +36,7 @@ class AsUSpec extends PropSpec { implicit val ratingSchema = json.Json.schema[Rating] ratingSchema.refName - json.Json.schema[UserProfile].asU(Draft04()) shouldEqual ujson.Obj( + json.Json.schema[User].asU(Draft04()) shouldEqual ujson.Obj( f"$$schema" -> ujson.Str("http://json-schema.org/draft-04/schema#"), "type" -> ujson.Str("object"), "additionalProperties" -> ujson.False, @@ -67,7 +69,7 @@ class AsUSpec extends PropSpec { implicit val ratingSchema = json.Json.schema[Rating] ratingSchema.refName - json.Json.schema[UserProfile].asU(Draft06(id = "http://models.org/userProfile.json")) shouldEqual ujson.Obj( + json.Json.schema[User].asU(Draft06(id = "http://models.org/userProfile.json")) shouldEqual ujson.Obj( f"$$schema" -> ujson.Str("http://json-schema.org/draft-06/schema#"), f"$$id" -> ujson.Str("http://models.org/userProfile.json"), "type" -> ujson.Str("object"), @@ -102,7 +104,7 @@ class AsUSpec extends PropSpec { implicit val ratingSchema = json.Json.schema[Rating] ratingSchema.refName - json.Json.schema[UserProfile].asU(Draft07(id = "http://models.org/userProfile.json")) shouldEqual ujson.Obj( + json.Json.schema[User].asU(Draft07(id = "http://models.org/userProfile.json")) shouldEqual ujson.Obj( f"$$schema" -> ujson.Str("http://json-schema.org/draft-07/schema#"), f"$$id" -> ujson.Str("http://models.org/userProfile.json"), "type" -> ujson.Str("object"), @@ -130,6 +132,31 @@ class AsUSpec extends PropSpec { "type" -> ujson.Str("integer"))), "required" -> ujson.Arr(ujson.Str("value"))))) } + + property("Check Schema.asU") { + import AsU._ + + json.Json.schema[UserProfile].asU(Draft04()) shouldEqual ujson.Obj( + f"$$schema" -> "http://json-schema.org/draft-04/schema#", + "type" -> "object", + "additionalProperties" -> false, + "properties" -> ujson.Obj( + "firstName" -> ujson.Obj("type" -> "string"), + "middleName" -> ujson.Obj("type" -> "string"), + "lastName" -> ujson.Obj("type" -> "string"), + "age" -> ujson.Obj("type" -> "integer"), + "role" -> ujson.Obj("type" -> "string", "default" -> "e-user", "enum" -> ujson.Arr("e-admin","e-manager","e-user")), + "active" -> ujson.Obj("type" -> "string", "default" -> "On", "enum" -> ujson.Arr("On", "Off", "Suspended")), + "enabledFeatures" -> ujson.Obj("type" -> "array", "uniqueItems" -> true, "default" -> ujson.Arr("feature-0-name", "feature-1-name"), "items" -> ujson.Obj("type" -> "string", "enum" -> ujson.Arr("feature-0-name", "feature-1-name", "feature-2-name"))), + "credentials" -> ujson.Obj("type" -> "object", + "additionalProperties" -> false, + "required" -> ujson.Arr("login", "password"), + "properties" -> ujson.Obj( + "login" -> ujson.Obj("type" -> "string"), + "password" -> ujson.Obj("type" -> "string")), + "default" -> ujson.Obj("login" -> "anonymous", "password" -> "-"))), + "required" -> ujson.Arr("age", "lastName", "firstName")) + } } object AsUSpec { @@ -141,7 +168,7 @@ object AsUSpec { case class Rating(value: Int) - case class UserProfile( + case class User( name: Name, rating: Rating, age: Int, diff --git a/u-json/src/test/scala/com/github/andyglow/jsonschema/UserProfileJson.scala b/u-json/src/test/scala/com/github/andyglow/jsonschema/UserProfileJson.scala new file mode 100644 index 00000000..bf407da5 --- /dev/null +++ b/u-json/src/test/scala/com/github/andyglow/jsonschema/UserProfileJson.scala @@ -0,0 +1,32 @@ +package com.github.andyglow.jsonschema + +import com.github.andyglow.jsonschema.model._ +import upickle.core.Visitor +import upickle.default._ + +object UserProfileJson { + + implicit val CredentialsW: Writer[Credentials] = macroW[Credentials] + + implicit val BetaFeatureW: Writer[BetaFeature] = new Writer[BetaFeature] { + override def write0[V]( + out: Visitor[_, V], + o: BetaFeature): V = o match { + case F0 => out.visitString("feature-0-name", 0) + case F1 => out.visitString("feature-1-name", 0) + case F2 => out.visitString("feature-2-name", 0) + } + } + + implicit val RoleW: Writer[Role] = new Writer[Role] { + import Role._ + + override def write0[V]( + out: Visitor[_, V], + o: Role): V = o match { + case User => out.visitString("e-user", 0) + case Manager => out.visitString("e-manager", 0) + case Admin => out.visitString("e-admin", 0) + } + } +} From e5967a42bf20b435de0e5c42b7a473c4e5bacae2 Mon Sep 17 00:00:00 2001 From: aonyshchuk Date: Mon, 14 Oct 2019 04:34:44 -0700 Subject: [PATCH 10/11] prepare for release --- build.sbt | 13 +++++++------ .../andyglow/json/LowPriorityArrayImplicits.scala | 2 -- .../andyglow/json/LowPriorityArrayImplicits.scala | 2 -- .../andyglow/json/LowPriorityArrayImplicits.scala | 1 - core/src/main/scala/json/Schema.scala | 1 - .../andyglow/jsonschema/UserProfileJson.scala | 1 - 6 files changed, 7 insertions(+), 13 deletions(-) diff --git a/build.sbt b/build.sbt index 743ec219..04d83536 100644 --- a/build.sbt +++ b/build.sbt @@ -30,7 +30,8 @@ lazy val commonSettings = Seq( "-Yno-adapted-args", "-Ywarn-dead-code", "-Ywarn-numeric-widen", - "-Xfuture") + "-Xfuture", + "-language:higherKinds") // WORKAROUND https://github.com/scala/scala/pull/5402 CrossVersion.partialVersion(scalaVersion.value) match { @@ -118,7 +119,7 @@ lazy val api = { project in file("api") }.dependsOn(core, macros).settings( name := "scala-jsonschema-api" ) -lazy val `play-json` = { project in file("play-json") }.dependsOn(core % "compile->compile;test->test", api).settings( +lazy val `play-json` = { project in file("play-json") }.dependsOn(core, api % "compile->compile;test->test").settings( commonSettings, name := "scala-jsonschema-play-json", @@ -126,7 +127,7 @@ lazy val `play-json` = { project in file("play-json") }.dependsOn(core % "compil libraryDependencies += "com.typesafe.play" %% "play-json" % "2.7.4" ) -lazy val `spray-json` = { project in file("spray-json") }.dependsOn(core % "compile->compile;test->test", api).settings( +lazy val `spray-json` = { project in file("spray-json") }.dependsOn(core, api % "compile->compile;test->test").settings( commonSettings, name := "scala-jsonschema-spray-json", @@ -134,7 +135,7 @@ lazy val `spray-json` = { project in file("spray-json") }.dependsOn(core % "comp libraryDependencies += "io.spray" %% "spray-json" % "1.3.5" ) -lazy val `circe-json` = { project in file("circe-json") }.dependsOn(core % "compile->compile;test->test", api).settings( +lazy val `circe-json` = { project in file("circe-json") }.dependsOn(core, api % "compile->compile;test->test").settings( commonSettings, name := "scala-jsonschema-circe-json", @@ -150,7 +151,7 @@ lazy val `circe-json` = { project in file("circe-json") }.dependsOn(core % "comp } ) -lazy val `json4s-json` = { project in file("json4s-json") }.dependsOn(core % "compile->compile;test->test", api).settings( +lazy val `json4s-json` = { project in file("json4s-json") }.dependsOn(core, api % "compile->compile;test->test").settings( commonSettings, name := "scala-jsonschema-json4s-json", @@ -158,7 +159,7 @@ lazy val `json4s-json` = { project in file("json4s-json") }.dependsOn(core % "co libraryDependencies += "org.json4s" %% "json4s-core" % "3.6.7" ) -lazy val `u-json` = { project in file("u-json") }.dependsOn(core % "compile->compile;test->test", api).settings( +lazy val `u-json` = { project in file("u-json") }.dependsOn(core, api % "compile->compile;test->test").settings( commonSettings, name := "scala-jsonschema-ujson", diff --git a/core/src/main/scala-2.11/com/github/andyglow/json/LowPriorityArrayImplicits.scala b/core/src/main/scala-2.11/com/github/andyglow/json/LowPriorityArrayImplicits.scala index 6c635d6e..d3274f78 100644 --- a/core/src/main/scala-2.11/com/github/andyglow/json/LowPriorityArrayImplicits.scala +++ b/core/src/main/scala-2.11/com/github/andyglow/json/LowPriorityArrayImplicits.scala @@ -1,7 +1,5 @@ package com.github.andyglow.json -import scala.language.higherKinds - trait LowPriorityArrayImplicits { this: LowPriorityPrimitiveImplicits => import Value._ diff --git a/core/src/main/scala-2.12/com/github/andyglow/json/LowPriorityArrayImplicits.scala b/core/src/main/scala-2.12/com/github/andyglow/json/LowPriorityArrayImplicits.scala index 1794e8fb..609f7868 100644 --- a/core/src/main/scala-2.12/com/github/andyglow/json/LowPriorityArrayImplicits.scala +++ b/core/src/main/scala-2.12/com/github/andyglow/json/LowPriorityArrayImplicits.scala @@ -1,7 +1,5 @@ package com.github.andyglow.json -import scala.language.higherKinds - trait LowPriorityArrayImplicits { this: LowPriorityPrimitiveImplicits => import Value._ diff --git a/core/src/main/scala-2.13/com/github/andyglow/json/LowPriorityArrayImplicits.scala b/core/src/main/scala-2.13/com/github/andyglow/json/LowPriorityArrayImplicits.scala index cae62c10..e6747d87 100644 --- a/core/src/main/scala-2.13/com/github/andyglow/json/LowPriorityArrayImplicits.scala +++ b/core/src/main/scala-2.13/com/github/andyglow/json/LowPriorityArrayImplicits.scala @@ -1,6 +1,5 @@ package com.github.andyglow.json -import scala.language.higherKinds trait LowPriorityArrayImplicits { this: LowPriorityPrimitiveImplicits => import Value._ diff --git a/core/src/main/scala/json/Schema.scala b/core/src/main/scala/json/Schema.scala index 178435aa..c9623324 100644 --- a/core/src/main/scala/json/Schema.scala +++ b/core/src/main/scala/json/Schema.scala @@ -3,7 +3,6 @@ package json import com.github.andyglow.json.{ToValue, Value} import scala.annotation.implicitNotFound -import scala.language.higherKinds sealed trait Schema[+T] extends Product { diff --git a/json4s-json/src/test/scala/com/github/andyglow/jsonschema/UserProfileJson.scala b/json4s-json/src/test/scala/com/github/andyglow/jsonschema/UserProfileJson.scala index f807e761..efc718ea 100644 --- a/json4s-json/src/test/scala/com/github/andyglow/jsonschema/UserProfileJson.scala +++ b/json4s-json/src/test/scala/com/github/andyglow/jsonschema/UserProfileJson.scala @@ -5,7 +5,6 @@ import org.json4s.Writer import org.json4s.JsonAST._ object UserProfileJson { - import org.json4s.JsonMethods implicit val CredentialsW: Writer[Credentials] = new Writer[Credentials] { override def write(o: Credentials): JValue = JObject( From 934879f78d38973a19e72575aa98376bf28a6a24 Mon Sep 17 00:00:00 2001 From: aonyshchuk Date: Mon, 14 Oct 2019 19:55:55 -0700 Subject: [PATCH 11/11] upgrade sbt to 1.3.3 --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index a7237716..010613d5 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version = 1.3.0 \ No newline at end of file +sbt.version = 1.3.3 \ No newline at end of file