├── project ├── build.properties ├── plugins.sbt └── Dependencies.scala ├── version.sbt ├── src ├── test │ ├── resources │ │ ├── remotes │ │ │ ├── integer.json │ │ │ ├── folder │ │ │ │ └── folderInteger.json │ │ │ ├── bar.json │ │ │ ├── first.json │ │ │ ├── subSchemas.json │ │ │ ├── foo.json │ │ │ ├── second.json │ │ │ ├── name.json │ │ │ ├── buu.json │ │ │ ├── node.json │ │ │ ├── tree.json │ │ │ └── scope_change.json │ │ ├── simple.json │ │ ├── issue-65.jar │ │ ├── talk_type.json │ │ ├── issue-65 │ │ │ └── schemas │ │ │ │ ├── foo.schema │ │ │ │ ├── my-schema-with-protocol-less-absolute-path.schema │ │ │ │ ├── my-schema-with-protocol-ful-absolute-path.schema │ │ │ │ └── my-schema-with-protocol-less-relative-path.schema │ │ ├── simple-schema.jar │ │ ├── simple-schema-issue-65.jar │ │ ├── tincan │ │ │ ├── group.json │ │ │ ├── inversefunctional.json │ │ │ ├── activityid.json │ │ │ ├── verb.json │ │ │ ├── activity.json │ │ │ ├── agent.json │ │ │ ├── mbox.json │ │ │ ├── statement_object.json │ │ │ ├── languagemap.json │ │ │ ├── activity_definition.json │ │ │ └── statement_base.json │ │ ├── issue-99-1.json │ │ ├── issue-99-4.json │ │ ├── issue-99-5.json │ │ ├── location.json │ │ ├── date.json │ │ ├── ajv_tests │ │ │ ├── 87_$_property.json │ │ │ ├── 33_json_schema_latest.json │ │ │ ├── anyOf.json │ │ │ ├── 5_adding_dependency_after.json │ │ │ ├── 17_escaping_pattern_property.json │ │ │ ├── 28_escaping_pattern_error.json │ │ │ ├── 62_resolution_scope_change.json │ │ │ ├── 63_id_property_not_in_schema.json │ │ │ ├── 13_root_ref_in_ref_in_remote_ref.json │ │ │ ├── 1_ids_in_refs.json │ │ │ ├── 14_ref_in_remote_ref_with_id.json │ │ │ ├── 94_dependencies_fail.json │ │ │ ├── 12_restoring_root_after_resolve.json │ │ │ ├── 5_recursive_references.json │ │ │ ├── 27_recursive_reference.json │ │ │ ├── 20_failing_to_parse_schema.json │ │ │ ├── 19_required_many_properties.json │ │ │ ├── 70_1_recursive_hash_ref_in_remote_ref.json │ │ │ └── oneOf.json │ │ ├── draft4 │ │ │ ├── optional │ │ │ │ └── zeroTerminatedFloats.json │ │ │ ├── minItems.json │ │ │ ├── maxItems.json │ │ │ ├── definitions.json │ │ │ ├── pattern.json │ │ │ ├── minLength.json │ │ │ ├── maxLength.json │ │ │ ├── minProperties.json │ │ │ ├── maxProperties.json │ │ │ ├── maximum.json │ │ │ ├── minimum.json │ │ │ ├── default.json │ │ │ ├── required.json │ │ │ ├── multipleOf.json │ │ │ ├── enum.json │ │ │ ├── items.json │ │ │ ├── not.json │ │ │ ├── additionalItems.json │ │ │ └── uniqueItems.json │ │ ├── talk.json │ │ ├── geo │ │ ├── draft7 │ │ │ ├── minItems.json │ │ │ ├── maxItems.json │ │ │ ├── maximum.json │ │ │ ├── minimum.json │ │ │ ├── exclusiveMaximum.json │ │ │ ├── exclusiveMinimum.json │ │ │ ├── definitions.json │ │ │ ├── pattern.json │ │ │ ├── minLength.json │ │ │ ├── maxLength.json │ │ │ ├── minProperties.json │ │ │ ├── maxProperties.json │ │ │ ├── default.json │ │ │ ├── multipleOf.json │ │ │ ├── required.json │ │ │ ├── enum.json │ │ │ ├── propertyNames.json │ │ │ ├── const.json │ │ │ ├── additionalItems.json │ │ │ ├── uniqueItems.json │ │ │ └── contains.json │ │ └── test-schemas │ │ │ └── petstore-minimal.json │ └── scala │ │ └── com │ │ └── eclipsesource │ │ └── schema │ │ ├── IfThenElseSpec.scala │ │ ├── ErrorHelper.scala │ │ ├── ExclusiveMaximumSpec.scala │ │ ├── ExclusiveMinimumSpec.scala │ │ ├── ConstSpec.scala │ │ ├── ContainsSpec.scala │ │ ├── BooleanSchemaSpec.scala │ │ ├── PropertyNamesSpec.scala │ │ ├── NotSpec.scala │ │ ├── TypeSpec.scala │ │ ├── EnumSpec.scala │ │ ├── MinimumSpec.scala │ │ ├── PatternSpec.scala │ │ ├── DefaultSpec.scala │ │ ├── MaxPropertiesSpec.scala │ │ ├── MaximumSpec.scala │ │ ├── MinPropertiesSpec.scala │ │ ├── MinItemsSpec.scala │ │ ├── RequiredSpec.scala │ │ ├── MaxItemsSpec.scala │ │ ├── MultipleOfSpec.scala │ │ ├── PropertiesSpec.scala │ │ ├── AnyOfSpec.scala │ │ ├── UniqueItemsSpec.scala │ │ ├── DefinitionsSpec.scala │ │ ├── DependenciesSpec.scala │ │ ├── PatternPropertiesSpec.scala │ │ ├── AdditionalPropertiesSpec.scala │ │ ├── internal │ │ ├── refs │ │ │ ├── package.scala │ │ │ ├── RefsSpec.scala │ │ │ ├── ResolveNumberConstraintsSpec.scala │ │ │ ├── ResolveStringConstraintsSpec.scala │ │ │ └── ResolveArrayConstraintsSpec.scala │ │ └── serialization │ │ │ └── SchemaReadsSpec.scala │ │ ├── BigNumSpec.scala │ │ ├── test │ │ └── Assets.scala │ │ ├── SimplePerformanceSpec.scala │ │ ├── AjvSpecs.scala │ │ ├── AdditionalItemsSpec.scala │ │ ├── SchemaSpec.scala │ │ ├── OneOfSpec.scala │ │ ├── RefRemoteDraft7Spec.scala │ │ ├── MaxLengthSpec.scala │ │ ├── MinLengthSpec.scala │ │ ├── ExamplesSpec.scala │ │ ├── Issue99Spec.scala │ │ ├── ItemsSpec.scala │ │ ├── TinCanSpec.scala │ │ ├── RemoteSpecs.scala │ │ └── UrlHandlerSpec.scala └── main │ ├── scala │ └── com │ │ └── eclipsesource │ │ └── schema │ │ ├── urlhandlers │ │ ├── UrlHandler.scala │ │ └── ClasspathUrlHandler.scala │ │ ├── SchemaVersion.scala │ │ ├── internal │ │ ├── validation │ │ │ └── package.scala │ │ ├── validators │ │ │ ├── SchemaTypeValidator.scala │ │ │ ├── DefaultValidator.scala │ │ │ ├── CompoundValidator.scala │ │ │ └── ArrayValidator.scala │ │ ├── url │ │ │ └── UrlStreamResolverFactory.scala │ │ ├── refs │ │ │ └── SchemaResolutionScope.scala │ │ ├── Keywords.scala │ │ ├── constraints │ │ │ └── Constraints.scala │ │ ├── Results.scala │ │ ├── draft4 │ │ │ └── constraints │ │ │ │ ├── StringConstraints4.scala │ │ │ │ ├── ArrayConstraints4.scala │ │ │ │ ├── AnyConstraints4.scala │ │ │ │ └── NumberConstraints4.scala │ │ ├── draft7 │ │ │ └── constraints │ │ │ │ ├── StringConstraints7.scala │ │ │ │ ├── ArrayConstraints7.scala │ │ │ │ └── NumberConstraints7.scala │ │ └── SchemaUtil.scala │ │ ├── SchemaFormat.scala │ │ ├── drafts │ │ ├── Version7.scala │ │ └── Version4.scala │ │ └── JsonSource.scala │ └── resources │ └── messages.txt ├── .travis.yml └── .gitignore /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.2.8 2 | -------------------------------------------------------------------------------- /version.sbt: -------------------------------------------------------------------------------- 1 | version in ThisBuild := "0.9.6-SNAPSHOT" 2 | -------------------------------------------------------------------------------- /src/test/resources/remotes/integer.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "integer" 3 | } -------------------------------------------------------------------------------- /src/test/resources/remotes/folder/folderInteger.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "integer" 3 | } -------------------------------------------------------------------------------- /src/test/resources/simple.json: -------------------------------------------------------------------------------- 1 | { 2 | "location": { "$ref": "classpath:/location.json" } 3 | } 4 | 5 | -------------------------------------------------------------------------------- /src/test/resources/remotes/bar.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "http://localhost:1234/bar.json", 3 | "type": "string" 4 | } 5 | -------------------------------------------------------------------------------- /src/test/resources/remotes/first.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "http://localhost:1234/first.json", 3 | "type": "string" 4 | } 5 | -------------------------------------------------------------------------------- /src/test/resources/issue-65.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eclipsesource/play-json-schema-validator/HEAD/src/test/resources/issue-65.jar -------------------------------------------------------------------------------- /src/test/resources/talk_type.json: -------------------------------------------------------------------------------- 1 | { 2 | "enum": [ 3 | null, 4 | "KEYNOTE", 5 | "TALK", 6 | "LIGHTNING_TALK" 7 | ] 8 | } -------------------------------------------------------------------------------- /src/test/resources/issue-65/schemas/foo.schema: -------------------------------------------------------------------------------- 1 | { 2 | "properties": { 3 | "bar": { 4 | "type": "string" 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/test/resources/simple-schema.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eclipsesource/play-json-schema-validator/HEAD/src/test/resources/simple-schema.jar -------------------------------------------------------------------------------- /src/test/resources/simple-schema-issue-65.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eclipsesource/play-json-schema-validator/HEAD/src/test/resources/simple-schema-issue-65.jar -------------------------------------------------------------------------------- /src/test/resources/remotes/subSchemas.json: -------------------------------------------------------------------------------- 1 | { 2 | "integer": { 3 | "type": "integer" 4 | }, 5 | "refToInteger": { 6 | "$ref": "#/integer" 7 | } 8 | } -------------------------------------------------------------------------------- /src/test/resources/remotes/foo.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "http://localhost:1234/foo.json", 3 | "type": "object", 4 | "properties": { 5 | "bar": { "$ref": "bar.json" } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/test/resources/issue-65/schemas/my-schema-with-protocol-less-absolute-path.schema: -------------------------------------------------------------------------------- 1 | { 2 | "properties": { 3 | "foo": { 4 | "$ref": "/schemas/foo.schema#" 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/scala/com/eclipsesource/schema/urlhandlers/UrlHandler.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema.urlhandlers 2 | 3 | object UrlHandler { 4 | val ProtocolLessScheme = "play-schema-validator" 5 | } 6 | -------------------------------------------------------------------------------- /src/test/resources/issue-65/schemas/my-schema-with-protocol-ful-absolute-path.schema: -------------------------------------------------------------------------------- 1 | { 2 | "properties": { 3 | "foo": { 4 | "$ref": "classpath:///schemas/foo.schema#" 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/test/resources/remotes/second.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "http://localhost:1234/second.json", 3 | "type": "object", 4 | "properties": { 5 | "first": { "$ref": "first.json" } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/test/resources/remotes/name.json: -------------------------------------------------------------------------------- 1 | { 2 | "definitions": { 3 | "orNull": { 4 | "anyOf": [ 5 | { "type": "null" }, 6 | { "$ref": "#" } 7 | ] 8 | } 9 | }, 10 | "type": "string" 11 | } 12 | -------------------------------------------------------------------------------- /src/test/resources/tincan/group.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "id": "#group", 4 | "oneOf": [ 5 | {"$ref": "#anonymousgroup"}, 6 | {"$ref": "#identifiedgroup"} 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /src/test/resources/issue-65/schemas/my-schema-with-protocol-less-relative-path.schema: -------------------------------------------------------------------------------- 1 | { 2 | "properties": { 3 | "foo": { 4 | "$ref": "foo.schema#" 5 | }, 6 | "quux": { 7 | "$ref": "quux.schema#" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/test/resources/issue-99-1.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "mything": { "$ref": "#thing" } 5 | }, 6 | "definitions": { 7 | "thing": { 8 | "id": "#thing", 9 | "type": "string" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/test/resources/issue-99-4.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "mything": { "$ref": "thing" } 5 | }, 6 | "definitions": { 7 | "thing": { 8 | "id": "#thing", 9 | "type": "string" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/test/resources/issue-99-5.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "mything": { "$ref": "##thing" } 5 | }, 6 | "definitions": { 7 | "thing": { 8 | "id": "thing", 9 | "type": "string" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/test/resources/remotes/buu.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "http://localhost:1234/buu.json", 3 | "definitions": { 4 | "buu": { 5 | "type": "object", 6 | "properties": { 7 | "bar": { "$ref": "bar.json" } 8 | } 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/test/resources/remotes/node.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "http://localhost:1234/node.json", 3 | "description": "node", 4 | "type": "object", 5 | "properties": { 6 | "value": { "type": "number" }, 7 | "subtree": { "$ref": "tree.json" } 8 | }, 9 | "required": ["value"] 10 | } 11 | -------------------------------------------------------------------------------- /src/test/resources/location.json: -------------------------------------------------------------------------------- 1 | { 2 | "allOf": [ 3 | { 4 | "type": "object", 5 | "properties": { 6 | "name": { "type": "string" } 7 | }, 8 | "required": [] 9 | }, 10 | { "$ref": "http://json-schema.org/learn/examples/geographical-location.schema.json" } 11 | ] 12 | } -------------------------------------------------------------------------------- /src/main/scala/com/eclipsesource/schema/SchemaVersion.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import com.eclipsesource.schema.internal.serialization.{SchemaReads, SchemaWrites} 4 | 5 | trait SchemaVersion extends SchemaReads with SchemaWrites { 6 | def schemaLocation: String 7 | def options: SchemaConfigOptions 8 | } 9 | -------------------------------------------------------------------------------- /src/test/resources/tincan/inversefunctional.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "id": "#inversefunctional", 4 | "oneOf": [ 5 | {"$ref": "#mbox"}, 6 | {"$ref": "#mbox_sha1sum"}, 7 | {"$ref": "#openid"}, 8 | {"$ref": "#account"} 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /src/test/resources/date.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "year": { 5 | "type": "integer", 6 | "minimum": 1000, 7 | "maximum": 9999 8 | }, 9 | "month": { "type": "integer" }, 10 | "day": { "type": "integer" } 11 | }, 12 | "required": ["year", "month", "day"], 13 | "additionalProperties": false 14 | } -------------------------------------------------------------------------------- /src/test/resources/remotes/tree.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "http://localhost:1234/tree.json", 3 | "description": "tree of nodes", 4 | "type": "object", 5 | "properties": { 6 | "meta": { "type": "string" }, 7 | "nodes": { 8 | "type": "array", 9 | "items": { "$ref": "node.json"} 10 | } 11 | }, 12 | "required": ["meta", "nodes"] 13 | } 14 | -------------------------------------------------------------------------------- /src/test/resources/ajv_tests/87_$_property.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "$ in properties (#87)", 4 | "schema": { 5 | "properties": { 6 | "$": { "type": "string" } 7 | } 8 | }, 9 | "tests": [ 10 | { 11 | "description": "valid", 12 | "data": { "$": "foo" }, 13 | "valid": true 14 | } 15 | ] 16 | } 17 | ] 18 | -------------------------------------------------------------------------------- /src/test/resources/tincan/activityid.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "id": "#activityid", 4 | "type": "object", 5 | "required": ["activityId"], 6 | "properties": { 7 | "activityId": { 8 | "id": "#activityid!core", 9 | "type": "string", 10 | "format": "iri" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/test/resources/tincan/verb.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "id": "#verb", 4 | "type": "object", 5 | "required": ["id"], 6 | "properties": { 7 | "id": { 8 | "type": "string", 9 | "format": "iri" 10 | }, 11 | "display": {"$ref": "#languagemap"} 12 | }, 13 | "additionalProperties": false 14 | } 15 | -------------------------------------------------------------------------------- /src/test/resources/tincan/activity.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "id": "#activity", 4 | "type": "object", 5 | "required": ["id"], 6 | "additionalProperties": false, 7 | "properties": { 8 | "objectType": {"enum": ["Activity"]}, 9 | "id": {"$ref": "#activityid!core"}, 10 | "definition": {"$ref": "#activity_definition"} 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | // Comment to get more information during initialization 2 | logLevel := Level.Warn 3 | 4 | resolvers += Classpaths.sbtPluginReleases 5 | 6 | addSbtPlugin("org.foundweekends" % "sbt-bintray" % "0.5.4") 7 | 8 | addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.6.0") 9 | 10 | addSbtPlugin("org.scoverage" % "sbt-coveralls" % "1.2.7") 11 | 12 | addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.11") 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: scala 2 | 3 | scala: 4 | - "2.12.8" 5 | - "2.13.0" 6 | 7 | jdk: 8 | - openjdk11 9 | 10 | # These directories are cached to S3 at the end of the build 11 | cache: 12 | directories: 13 | - $HOME/.ivy2/cache 14 | - $HOME/.sbt/boot/ 15 | 16 | script: 17 | - sbt -sbt-version 1.2.8 -scala-version $TRAVIS_SCALA_VERSION clean coverage test 18 | 19 | after_success: 20 | - sbt coverageReport coveralls 21 | -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/IfThenElseSpec.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import com.eclipsesource.schema.drafts.Version7 4 | import com.eclipsesource.schema.test.JsonSpec 5 | import org.specs2.mutable.Specification 6 | 7 | class IfThenElseSpec extends Specification with JsonSpec { 8 | import Version7._ 9 | implicit val validator = SchemaValidator(Some(Version7)) 10 | validate("if-then-else", "draft7") 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.log 3 | .DS_Store 4 | 5 | # sbt specific 6 | .cache/ 7 | .history/ 8 | .lib/ 9 | .target/ 10 | dist/* 11 | target/ 12 | .target/ 13 | lib_managed/ 14 | src_managed/ 15 | project/boot/ 16 | project/plugins/project/ 17 | 18 | # Scala-IDE specific 19 | .scala_dependencies 20 | .worksheet 21 | .settings/ 22 | .classpath 23 | .project 24 | .metadata 25 | .cache 26 | 27 | # IntelliJ 28 | .idea 29 | 30 | #Coveralls 31 | repo-token.txt -------------------------------------------------------------------------------- /src/test/resources/tincan/agent.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "id": "#agent", 4 | "allOf": [{"$ref": "#inversefunctional"}], 5 | "properties": { 6 | "name": {"type": "string"}, 7 | "objectType": {"enum": ["Agent"]}, 8 | "mbox": {}, 9 | "mbox_sha1sum": {}, 10 | "account": {}, 11 | "openid": {} 12 | }, 13 | "additionalProperties": false 14 | } 15 | -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/ErrorHelper.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import play.api.libs.json._ 4 | 5 | trait ErrorHelper { 6 | 7 | def firstErrorOf[A](res: JsResult[A]): String = { 8 | val errors: collection.Seq[(JsPath, collection.Seq[JsonValidationError])] = res.asEither.left.get 9 | val firstError: JsValue = errors.toJson(0) 10 | (firstError \ "msgs").get.as[JsArray].head.as[String] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/ExclusiveMaximumSpec.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import com.eclipsesource.schema.drafts.Version7 4 | import com.eclipsesource.schema.test.JsonSpec 5 | import org.specs2.mutable.Specification 6 | 7 | class ExclusiveMaximumSpec extends Specification with JsonSpec { 8 | import Version7._ 9 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version7)) 10 | validate("exclusiveMaximum", "draft7") 11 | } 12 | -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/ExclusiveMinimumSpec.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import com.eclipsesource.schema.drafts.Version7 4 | import com.eclipsesource.schema.test.JsonSpec 5 | import org.specs2.mutable.Specification 6 | 7 | class ExclusiveMinimumSpec extends Specification with JsonSpec { 8 | import Version7._ 9 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version7)) 10 | validate("exclusiveMinimum", "draft7") 11 | } 12 | -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/ConstSpec.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import com.eclipsesource.schema.drafts.Version7 4 | import com.eclipsesource.schema.test.JsonSpec 5 | import org.specs2.mutable.Specification 6 | 7 | class ConstSpec extends Specification with JsonSpec { 8 | 9 | "const draft7" in { 10 | import Version7._ 11 | implicit val validator = SchemaValidator(Some(Version7)) 12 | validate("const", "draft7") 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/scala/com/eclipsesource/schema/internal/validation/package.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema.internal 2 | 3 | import play.api.libs.json.{JsPath, JsonValidationError} 4 | 5 | import scalaz.Validation 6 | 7 | package object validation { 8 | type Validated[E, O] = Validation[Seq[E], O] 9 | type Mapping[E, I, O] = I => Validated[E, O] 10 | type Constraint[T] = Mapping[JsonValidationError, T, T] 11 | type VA[O] = Validated[(JsPath, Seq[JsonValidationError]), O] 12 | } 13 | -------------------------------------------------------------------------------- /src/test/resources/tincan/mbox.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "id": "#mbox", 4 | "type": "object", 5 | "required": ["mbox"], 6 | "properties": { 7 | "mbox": { 8 | "id": "#mbox!core", 9 | "type": "string", 10 | "format": "mailto-iri" 11 | }, 12 | "mbox_sha1sum": {"type": "null"}, 13 | "openid": {"type": "null"}, 14 | "account": {"type": "null"} 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/ContainsSpec.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import com.eclipsesource.schema.drafts.Version7 4 | import com.eclipsesource.schema.test.JsonSpec 5 | import org.specs2.mutable.Specification 6 | 7 | class ContainsSpec extends Specification with JsonSpec { 8 | 9 | "contains draft7" in { 10 | import Version7._ 11 | implicit val validator = SchemaValidator(Some(Version7)) 12 | validate("contains", "draft7") 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/test/resources/draft4/optional/zeroTerminatedFloats.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "some languages do not distinguish between different types of numeric value", 4 | "schema": { 5 | "type": "integer" 6 | }, 7 | "tests": [ 8 | { 9 | "description": "a float is not an integer even without fractional part", 10 | "data": 1.0, 11 | "valid": false 12 | } 13 | ] 14 | } 15 | ] 16 | -------------------------------------------------------------------------------- /src/test/resources/ajv_tests/33_json_schema_latest.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "use latest json schema as v4 (#33)", 4 | "schema": { 5 | "$schema": "http://json-schema.org/schema", 6 | "type": "object", 7 | "properties": { 8 | "username": { 9 | "type": "string" 10 | } 11 | } 12 | }, 13 | "tests": [ 14 | { 15 | "description": "empty object", 16 | "data": {}, 17 | "valid": true 18 | } 19 | ] 20 | } 21 | ] 22 | -------------------------------------------------------------------------------- /src/test/resources/ajv_tests/anyOf.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "anyOf with one of schemas empty", 4 | "schema": { 5 | "anyOf": [ 6 | { "type": "number" }, 7 | {} 8 | ] 9 | }, 10 | "tests": [ 11 | { 12 | "description": "string is valid", 13 | "data": "foo", 14 | "valid": true 15 | }, 16 | { 17 | "description": "number is valid", 18 | "data": 123, 19 | "valid": true 20 | } 21 | ] 22 | } 23 | ] 24 | -------------------------------------------------------------------------------- /src/test/resources/talk.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "type": "object", 4 | "properties": { 5 | "title": { 6 | "type": "string", 7 | "minLength": 10, 8 | "maxLength": 20 9 | }, 10 | "speaker": { 11 | "type": "string", 12 | "pattern": "(Mr.|Mrs.)?[A-Za-z ]+" 13 | }, 14 | "type": { "$ref": "talk_type.json" }, 15 | "date": { "$ref": "date.json" }, 16 | "location": { "$ref": "location.json" } 17 | }, 18 | "required": ["location"] 19 | } -------------------------------------------------------------------------------- /src/test/resources/tincan/statement_object.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "id": "#statement_object", 4 | "type": "object", 5 | "oneOf": [ 6 | {"$ref": "#activity"}, 7 | { 8 | "required": ["objectType"], 9 | "oneOf":[ 10 | {"$ref": "#agent"}, 11 | {"$ref": "#group"}, 12 | {"$ref": "#statementref"}, 13 | {"$ref": "#substatement"} 14 | ] 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/BooleanSchemaSpec.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import com.eclipsesource.schema.drafts.Version7 4 | import com.eclipsesource.schema.test.JsonSpec 5 | import org.specs2.mutable.Specification 6 | 7 | class BooleanSchemaSpec extends Specification with JsonSpec { 8 | 9 | "boolean scheam draft7" in { 10 | import Version7._ 11 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version7)) 12 | validate("boolean_schema", "draft7") 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/PropertyNamesSpec.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import com.eclipsesource.schema.drafts.Version7 4 | import com.eclipsesource.schema.test.JsonSpec 5 | import org.specs2.mutable.Specification 6 | 7 | class PropertyNamesSpec extends Specification with JsonSpec { 8 | 9 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version7)) 10 | 11 | "propertyNames draft7" in { 12 | import Version7._ 13 | validate("propertyNames", "draft7") 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/test/resources/ajv_tests/5_adding_dependency_after.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "Adding dependency after dependent schema (#5)", 4 | "schema": { 5 | "$ref": "http://localhost:1234/second.json" 6 | }, 7 | "tests": [ 8 | { 9 | "description": "valid object", 10 | "data": { "first": "foo" }, 11 | "valid": true 12 | }, 13 | { 14 | "description": "invalid object", 15 | "data": { "first": 1 }, 16 | "valid": false 17 | } 18 | ] 19 | } 20 | ] 21 | -------------------------------------------------------------------------------- /src/main/scala/com/eclipsesource/schema/SchemaFormat.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import play.api.libs.json.JsValue 4 | 5 | trait SchemaFormat { 6 | /** 7 | * The name of the format. 8 | * @return the format name 9 | */ 10 | def name: String 11 | 12 | /** 13 | * Check whether the given value conforms to this format. 14 | *s 15 | * @param json the JSON value to be checked 16 | * @return whether the JSON value conforms to this format 17 | */ 18 | def validate(json: JsValue): Boolean 19 | } 20 | -------------------------------------------------------------------------------- /src/main/scala/com/eclipsesource/schema/internal/validators/SchemaTypeValidator.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema.internal.validators 2 | 3 | import com.eclipsesource.schema.SchemaResolutionContext 4 | import com.eclipsesource.schema.internal.validation.VA 5 | import com.osinka.i18n.Lang 6 | import play.api.libs.json.JsValue 7 | 8 | trait SchemaTypeValidator[S] { 9 | def validate(schema: S, json: => JsValue, resolutionContext: SchemaResolutionContext) 10 | (implicit lang: Lang = Lang.Default): VA[JsValue] 11 | } 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/test/resources/ajv_tests/17_escaping_pattern_property.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "escaping pattern property (#17)", 4 | "schema": { 5 | "type" : "object", 6 | "patternProperties": { 7 | "^.+$" : { 8 | "type" : "object", 9 | "required" : ["unit"] 10 | } 11 | }, 12 | "additionalProperties" : false 13 | }, 14 | "tests": [ 15 | { 16 | "description": "empty object", 17 | "data": {}, 18 | "valid": true 19 | } 20 | ] 21 | } 22 | ] 23 | -------------------------------------------------------------------------------- /src/test/resources/remotes/scope_change.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "http://localhost:1234/scope_change.json", 3 | "definitions": { 4 | "foo": { 5 | "id": "http://localhost:1234/scope_foo.json", 6 | "definitions": { 7 | "bar": { 8 | "type": "string" 9 | } 10 | } 11 | }, 12 | "baz": { 13 | "id": "folder/", 14 | "type": "array", 15 | "items": { "$ref": "folderInteger.json" }, 16 | "bar": { 17 | "items": { "$ref": "folderInteger.json" } 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/test/resources/ajv_tests/28_escaping_pattern_error.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "escaping pattern error (#28)", 4 | "schema": { 5 | "type" : "object", 6 | "properties": { 7 | "mediaType": { 8 | "type": "string", 9 | "pattern": "^[a-zA-Z0-9!#$%^&*_\\-+{}|'.`~]+/[a-zA-Z0-9!#$%^&*_\\-+{}|'.`~]+$" 10 | } 11 | } 12 | }, 13 | "tests": [ 14 | { 15 | "description": "empty object", 16 | "data": {}, 17 | "valid": true 18 | } 19 | ] 20 | } 21 | ] 22 | -------------------------------------------------------------------------------- /src/main/scala/com/eclipsesource/schema/urlhandlers/ClasspathUrlHandler.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema.urlhandlers 2 | 3 | import java.net.{URL, URLConnection, URLStreamHandler} 4 | 5 | object ClasspathUrlHandler { 6 | def Scheme = "classpath" 7 | def apply = new ClasspathUrlHandler 8 | } 9 | 10 | /** 11 | * URLStreamHandler that looks for a given URL on the classpath. 12 | */ 13 | class ClasspathUrlHandler extends URLStreamHandler { 14 | 15 | override def openConnection(url: URL): URLConnection = 16 | getClass.getResource(url.getPath).openConnection() 17 | } 18 | -------------------------------------------------------------------------------- /src/test/resources/geo: -------------------------------------------------------------------------------- 1 | { 2 | "id": "https://example.com/geographical-location.schema.json", 3 | "$schema": "http://json-schema.org/draft-07/schema#", 4 | "title": "Longitude and Latitude Values", 5 | "description": "A geographical coordinate.", 6 | "required": [ 7 | "latitude", 8 | "longitude" 9 | ], 10 | "type": "object", 11 | "properties": { 12 | "latitude": { 13 | "type": "number", 14 | "minimum": -90, 15 | "maximum": 90 16 | }, 17 | "longitude": { 18 | "type": "number", 19 | "minimum": -180, 20 | "maximum": 180 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/main/scala/com/eclipsesource/schema/internal/validators/DefaultValidator.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema.internal.validators 2 | 3 | import com.eclipsesource.schema.internal.validation.VA 4 | import com.eclipsesource.schema.{SchemaResolutionContext, SchemaType} 5 | import com.osinka.i18n.Lang 6 | import play.api.libs.json.JsValue 7 | 8 | class DefaultValidator[A <: SchemaType] extends SchemaTypeValidator[A] { 9 | override def validate(schema: A, json: => JsValue, context: SchemaResolutionContext) 10 | (implicit lang: Lang): VA[JsValue] = { 11 | schema.constraints.validate(schema, json, context) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/test/resources/ajv_tests/62_resolution_scope_change.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "change resolution scope - change filename (#62)", 4 | "schema": { 5 | "type" : "object", 6 | "properties": { 7 | "title": { "$ref": "http://localhost:1234/scope_foo.json#/definitions/bar" } 8 | } 9 | }, 10 | "tests": [ 11 | { 12 | "description": "string is valid", 13 | "data": { "title": "baz" }, 14 | "valid": true 15 | }, 16 | { 17 | "description": "number is invalid", 18 | "data": { "title": 1 }, 19 | "valid": false 20 | } 21 | ] 22 | } 23 | ] 24 | -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/NotSpec.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import com.eclipsesource.schema.drafts.{Version4, Version7} 4 | import com.eclipsesource.schema.test.JsonSpec 5 | import org.specs2.mutable.Specification 6 | 7 | class NotSpec extends Specification with JsonSpec { 8 | 9 | "not draft4" in { 10 | import Version4._ 11 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version4)) 12 | validate("not", "draft4") 13 | } 14 | 15 | "not draft7" in { 16 | import Version7._ 17 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version7)) 18 | validate("not", "draft7") 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/TypeSpec.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import com.eclipsesource.schema.drafts.{Version4, Version7} 4 | import com.eclipsesource.schema.test.JsonSpec 5 | import org.specs2.mutable.Specification 6 | 7 | class TypeSpec extends Specification with JsonSpec { 8 | 9 | "type draft4" in { 10 | import Version4._ 11 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version4)) 12 | validate("type", "draft4") 13 | } 14 | "type draft7" in { 15 | import Version7._ 16 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version7)) 17 | validate("type", "draft7") 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/EnumSpec.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import com.eclipsesource.schema.drafts.{Version4, Version7} 4 | import com.eclipsesource.schema.test.JsonSpec 5 | import org.specs2.mutable.Specification 6 | 7 | class EnumSpec extends Specification with JsonSpec { 8 | 9 | "enum draft4" in { 10 | import Version4._ 11 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version4)) 12 | validate("enum", "draft4") 13 | } 14 | 15 | "enum draft7" in { 16 | import Version7._ 17 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version7)) 18 | validate("enum", "draft7") 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/MinimumSpec.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import com.eclipsesource.schema.drafts.{Version4, Version7} 4 | import com.eclipsesource.schema.test.JsonSpec 5 | import org.specs2.mutable.Specification 6 | 7 | class MinimumSpec extends Specification with JsonSpec { 8 | 9 | "minimum draft4" in { 10 | import Version4._ 11 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version4)) 12 | validate("minimum", "draft4") 13 | } 14 | 15 | "minimum draft7" in { 16 | import Version7._ 17 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version7)) 18 | validate("minimum", "draft7") 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/PatternSpec.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import com.eclipsesource.schema.drafts.{Version4, Version7} 4 | import com.eclipsesource.schema.test.JsonSpec 5 | import org.specs2.mutable.Specification 6 | 7 | class PatternSpec extends Specification with JsonSpec { 8 | 9 | "pattern draft4" in { 10 | import Version4._ 11 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version4)) 12 | validate("pattern", "draft4") 13 | } 14 | 15 | "pattern draft7" in { 16 | import Version7._ 17 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version7)) 18 | validate("pattern", "draft7") 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/DefaultSpec.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import com.eclipsesource.schema.drafts.{Version4, Version7} 4 | import com.eclipsesource.schema.test.JsonSpec 5 | import org.specs2.mutable.Specification 6 | 7 | class DefaultSpec extends Specification with JsonSpec { 8 | 9 | "validate draft4" in { 10 | import Version4._ 11 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version4)) 12 | validate("default", "draft4") 13 | } 14 | 15 | "validate draft7" in { 16 | import Version7._ 17 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version7)) 18 | validate("default", "draft7") 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/MaxPropertiesSpec.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import com.eclipsesource.schema.drafts.{Version4, Version7} 4 | import com.eclipsesource.schema.test.JsonSpec 5 | import org.specs2.mutable.Specification 6 | 7 | class MaxPropertiesSpec extends Specification with JsonSpec { 8 | 9 | "maxProperties draft4" in { 10 | import Version4._ 11 | implicit val validator = SchemaValidator(Some(Version4)) 12 | validate("maxProperties", "draft4") 13 | } 14 | 15 | "maxProperties draft7" in { 16 | import Version7._ 17 | implicit val validator = SchemaValidator(Some(Version7)) 18 | validate("maxProperties", "draft7") 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/MaximumSpec.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import com.eclipsesource.schema.drafts.{Version4, Version7} 4 | import com.eclipsesource.schema.test.JsonSpec 5 | import org.specs2.mutable.Specification 6 | 7 | class MaximumSpec extends Specification with JsonSpec { 8 | 9 | "maximum draft4" in { 10 | import Version4._ 11 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version4)) 12 | validate("maximum", "draft4") 13 | } 14 | 15 | "maximum draft7" in { 16 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version7)) 17 | import Version7._ 18 | validate("maximum", "draft7") 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/MinPropertiesSpec.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import com.eclipsesource.schema.drafts.{Version4, Version7} 4 | import com.eclipsesource.schema.test.JsonSpec 5 | import org.specs2.mutable.Specification 6 | 7 | class MinPropertiesSpec extends Specification with JsonSpec { 8 | 9 | "minProperties draft4" in { 10 | import Version4._ 11 | implicit val validator = SchemaValidator(Some(Version4)) 12 | validate("minProperties", "draft4") 13 | } 14 | 15 | "minProperties draft7" in { 16 | import Version7._ 17 | implicit val validator = SchemaValidator(Some(Version7)) 18 | validate("minProperties", "draft7") 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/MinItemsSpec.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import com.eclipsesource.schema.drafts.{Version4, Version7} 4 | import com.eclipsesource.schema.test.JsonSpec 5 | import org.specs2.mutable.Specification 6 | 7 | class MinItemsSpec extends Specification with JsonSpec { 8 | 9 | "minItems draft4" in { 10 | import Version4._ 11 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version4)) 12 | validate("minItems", "draft4") 13 | } 14 | 15 | "minItems draft7" in { 16 | import Version7._ 17 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version7)) 18 | validate("minItems", "draft7") 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/RequiredSpec.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import com.eclipsesource.schema.drafts.{Version4, Version7} 4 | import com.eclipsesource.schema.test.JsonSpec 5 | import org.specs2.mutable.Specification 6 | 7 | class RequiredSpec extends Specification with JsonSpec { 8 | 9 | "required draft4" in { 10 | import Version4._ 11 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version4)) 12 | validate("required", "draft4") 13 | } 14 | 15 | "required draft7" in { 16 | import Version7._ 17 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version7)) 18 | validate("required", "draft7") 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/MaxItemsSpec.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import com.eclipsesource.schema.drafts.{Version4, Version7} 4 | import com.eclipsesource.schema.test.JsonSpec 5 | import org.specs2.mutable.Specification 6 | 7 | class MaxItemsSpec extends Specification with JsonSpec { 8 | 9 | "maxItems draft4" in { 10 | import Version4._ 11 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version4)) 12 | validate("maxItems", "draft4") 13 | } 14 | 15 | "maxItems draft7" in { 16 | import Version7._ 17 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version7)) 18 | validate("maxItems", "draft7") 19 | } 20 | } 21 | 22 | -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/MultipleOfSpec.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import com.eclipsesource.schema.drafts.{Version4, Version7} 4 | import com.eclipsesource.schema.test.JsonSpec 5 | import org.specs2.mutable.Specification 6 | 7 | class MultipleOfSpec extends Specification with JsonSpec { 8 | 9 | "multipleOf draft4" in { 10 | import Version4._ 11 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version4)) 12 | validate("multipleOf", "draft4") 13 | } 14 | 15 | "multipleOf draft7" in { 16 | import Version7._ 17 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version7)) 18 | validate("multipleOf", "draft7") 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/PropertiesSpec.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import com.eclipsesource.schema.drafts.{Version4, Version7} 4 | import com.eclipsesource.schema.test.JsonSpec 5 | import org.specs2.mutable.Specification 6 | 7 | class PropertiesSpec extends Specification with JsonSpec { 8 | 9 | "properties draft4" in { 10 | import Version4._ 11 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version4)) 12 | validate("properties", "draft4") 13 | } 14 | 15 | "properties draft7" in { 16 | import Version7._ 17 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version7)) 18 | validate("properties", "draft7") 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/AnyOfSpec.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import com.eclipsesource.schema.drafts.{Version4, Version7} 4 | import com.eclipsesource.schema.test.JsonSpec 5 | import org.specs2.mutable.Specification 6 | 7 | class AnyOfSpec extends Specification with JsonSpec { 8 | 9 | "anyOf draft4" in { 10 | import Version4._ 11 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version4)) 12 | validate("anyOf", "draft4") 13 | validate("anyOf", "ajv_tests") 14 | } 15 | 16 | "anyOf draft7" in { 17 | import Version7._ 18 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version7)) 19 | validate("anyOf", "draft7") 20 | } 21 | } -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/UniqueItemsSpec.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import com.eclipsesource.schema.drafts.{Version4, Version7} 4 | import com.eclipsesource.schema.test.JsonSpec 5 | import org.specs2.mutable.Specification 6 | 7 | class UniqueItemsSpec extends Specification with JsonSpec { 8 | 9 | "uniqueItems draft4" in { 10 | import Version4._ 11 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version4)) 12 | validate("uniqueItems", "draft4") 13 | } 14 | 15 | "uniqueItems draft7" in { 16 | import Version7._ 17 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version7)) 18 | validate("uniqueItems", "draft7") 19 | } 20 | } 21 | 22 | -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/DefinitionsSpec.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import com.eclipsesource.schema.drafts.{Version4, Version7} 4 | import com.eclipsesource.schema.test.JsonSpec 5 | import org.specs2.mutable.Specification 6 | 7 | class DefinitionsSpec extends Specification with JsonSpec { self => 8 | 9 | "validate draft4" in { 10 | import Version4._ 11 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version4)) 12 | validate("definitions", "draft4") 13 | } 14 | 15 | "validate draft7" in { 16 | import Version7._ 17 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version7)) 18 | validate("definitions", "draft7") 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/DependenciesSpec.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import com.eclipsesource.schema.drafts.{Version4, Version7} 4 | import com.eclipsesource.schema.test.JsonSpec 5 | import org.specs2.mutable.Specification 6 | 7 | class DependenciesSpec extends Specification with JsonSpec { 8 | 9 | "dependencies draft 4" in { 10 | import Version4._ 11 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version4)) 12 | validate("dependencies", "draft4") 13 | } 14 | 15 | "dependencies draft 7" in { 16 | import Version7._ 17 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version7)) 18 | validate("dependencies", "draft7") 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/PatternPropertiesSpec.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import com.eclipsesource.schema.drafts.{Version4, Version7} 4 | import com.eclipsesource.schema.test.JsonSpec 5 | import org.specs2.mutable.Specification 6 | 7 | class PatternPropertiesSpec extends Specification with JsonSpec { 8 | 9 | "patternProperties draft4" in { 10 | import Version4._ 11 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version4)) 12 | validate("patternProperties", "draft4") 13 | } 14 | 15 | "patternProperties draft7" in { 16 | import Version7._ 17 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version7)) 18 | validate("patternProperties", "draft7") 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/AdditionalPropertiesSpec.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import com.eclipsesource.schema.drafts.{Version4, Version7} 4 | import com.eclipsesource.schema.test.JsonSpec 5 | import org.specs2.mutable.Specification 6 | 7 | class AdditionalPropertiesSpec extends Specification with JsonSpec { 8 | 9 | "additionalProperties draft4" in { 10 | import Version4._ 11 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version4)) 12 | validate("additionalProperties", "draft4") 13 | } 14 | 15 | "additionalProperties draft7" in { 16 | import Version7._ 17 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version7)) 18 | validate("additionalProperties", "draft7") 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/resources/tincan/languagemap.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "id": "#languagemap", 4 | "type": "object", 5 | "patternProperties": { 6 | "^(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)|((en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)|(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang)))$": { 7 | "type": "string" 8 | } 9 | }, 10 | "additionalProperties": false 11 | } 12 | -------------------------------------------------------------------------------- /src/test/resources/ajv_tests/63_id_property_not_in_schema.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "id property in referenced schema in object that is not a schema (#63)", 4 | "schema": { 5 | "type" : "object", 6 | "properties": { 7 | "title": { "$ref": "http://json-schema.org/draft-04/schema#/properties/title" } 8 | } 9 | }, 10 | "tests": [ 11 | { 12 | "description": "empty object is valid", 13 | "data": {}, 14 | "valid": true 15 | }, 16 | { 17 | "description": "string is valid", 18 | "data": { "title": "foo" }, 19 | "valid": true 20 | }, 21 | { 22 | "description": "number is invalid", 23 | "data": { "title": 1 }, 24 | "valid": false 25 | } 26 | ] 27 | } 28 | ] 29 | -------------------------------------------------------------------------------- /src/test/resources/draft4/minItems.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "minItems validation", 4 | "schema": {"minItems": 1}, 5 | "tests": [ 6 | { 7 | "description": "longer is valid", 8 | "data": [1, 2], 9 | "valid": true 10 | }, 11 | { 12 | "description": "exact length is valid", 13 | "data": [1], 14 | "valid": true 15 | }, 16 | { 17 | "description": "too short is invalid", 18 | "data": [], 19 | "valid": false 20 | }, 21 | { 22 | "description": "ignores non-arrays", 23 | "data": "", 24 | "valid": true 25 | } 26 | ] 27 | } 28 | ] 29 | -------------------------------------------------------------------------------- /src/test/resources/draft7/minItems.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "minItems validation", 4 | "schema": {"minItems": 1}, 5 | "tests": [ 6 | { 7 | "description": "longer is valid", 8 | "data": [1, 2], 9 | "valid": true 10 | }, 11 | { 12 | "description": "exact length is valid", 13 | "data": [1], 14 | "valid": true 15 | }, 16 | { 17 | "description": "too short is invalid", 18 | "data": [], 19 | "valid": false 20 | }, 21 | { 22 | "description": "ignores non-arrays", 23 | "data": "", 24 | "valid": true 25 | } 26 | ] 27 | } 28 | ] 29 | -------------------------------------------------------------------------------- /src/test/resources/draft4/maxItems.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "maxItems validation", 4 | "schema": {"maxItems": 2}, 5 | "tests": [ 6 | { 7 | "description": "shorter is valid", 8 | "data": [1], 9 | "valid": true 10 | }, 11 | { 12 | "description": "exact length is valid", 13 | "data": [1, 2], 14 | "valid": true 15 | }, 16 | { 17 | "description": "too long is invalid", 18 | "data": [1, 2, 3], 19 | "valid": false 20 | }, 21 | { 22 | "description": "ignores non-arrays", 23 | "data": "foobar", 24 | "valid": true 25 | } 26 | ] 27 | } 28 | ] 29 | -------------------------------------------------------------------------------- /src/test/resources/draft7/maxItems.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "maxItems validation", 4 | "schema": {"maxItems": 2}, 5 | "tests": [ 6 | { 7 | "description": "shorter is valid", 8 | "data": [1], 9 | "valid": true 10 | }, 11 | { 12 | "description": "exact length is valid", 13 | "data": [1, 2], 14 | "valid": true 15 | }, 16 | { 17 | "description": "too long is invalid", 18 | "data": [1, 2, 3], 19 | "valid": false 20 | }, 21 | { 22 | "description": "ignores non-arrays", 23 | "data": "foobar", 24 | "valid": true 25 | } 26 | ] 27 | } 28 | ] 29 | -------------------------------------------------------------------------------- /src/test/resources/draft7/maximum.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "maximum validation", 4 | "schema": {"maximum": 3.0}, 5 | "tests": [ 6 | { 7 | "description": "below the maximum is valid", 8 | "data": 2.6, 9 | "valid": true 10 | }, 11 | { 12 | "description": "boundary point is valid", 13 | "data": 3.0, 14 | "valid": true 15 | }, 16 | { 17 | "description": "above the maximum is invalid", 18 | "data": 3.5, 19 | "valid": false 20 | }, 21 | { 22 | "description": "ignores non-numbers", 23 | "data": "x", 24 | "valid": true 25 | } 26 | ] 27 | } 28 | ] 29 | -------------------------------------------------------------------------------- /src/test/resources/draft7/minimum.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "minimum validation", 4 | "schema": {"minimum": 1.1}, 5 | "tests": [ 6 | { 7 | "description": "above the minimum is valid", 8 | "data": 2.6, 9 | "valid": true 10 | }, 11 | { 12 | "description": "boundary point is valid", 13 | "data": 1.1, 14 | "valid": true 15 | }, 16 | { 17 | "description": "below the minimum is invalid", 18 | "data": 0.6, 19 | "valid": false 20 | }, 21 | { 22 | "description": "ignores non-numbers", 23 | "data": "x", 24 | "valid": true 25 | } 26 | ] 27 | } 28 | ] 29 | -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/internal/refs/package.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema.internal 2 | 3 | import com.eclipsesource.schema.SchemaType 4 | import com.osinka.i18n.Lang 5 | import play.api.libs.json.{JsValue, Json, JsonValidationError, Writes} 6 | 7 | package object refs { 8 | 9 | implicit class ResolveResultExtensionOps(resolvedResult: ResolvedResult) { 10 | def toJson(implicit writes: Writes[SchemaType]): JsValue = 11 | Json.toJson(resolvedResult.resolved) 12 | } 13 | 14 | implicit class SchemaRefResolverExtensionOps(resolver: SchemaRefResolver) { 15 | def resolveFromRoot(ref: String, scope: SchemaResolutionScope) 16 | (implicit lang: Lang = Lang.Default): Either[JsonValidationError, ResolvedResult] = { 17 | resolver.resolve(scope.documentRoot, Ref(ref), scope).toEither 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/resources/ajv_tests/13_root_ref_in_ref_in_remote_ref.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "root ref in remote ref (#13)", 4 | "schema": { 5 | "id": "http://localhost:1234/object", 6 | "type": "object", 7 | "properties": { 8 | "name": { "$ref": "name.json#/definitions/orNull" } 9 | } 10 | }, 11 | "tests": [ 12 | { 13 | "description": "string is valid", 14 | "data": { 15 | "name": "foo" 16 | }, 17 | "valid": true 18 | }, 19 | { 20 | "description": "null is valid", 21 | "data": { 22 | "name": null 23 | }, 24 | "valid": true 25 | }, 26 | { 27 | "description": "object is invalid", 28 | "data": { 29 | "name": { 30 | "name": null 31 | } 32 | }, 33 | "valid": false 34 | } 35 | ] 36 | } 37 | ] 38 | -------------------------------------------------------------------------------- /src/test/resources/draft7/exclusiveMaximum.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "exclusiveMaximum validation", 4 | "schema": { 5 | "exclusiveMaximum": 3.0 6 | }, 7 | "tests": [ 8 | { 9 | "description": "below the exclusiveMaximum is valid", 10 | "data": 2.2, 11 | "valid": true 12 | }, 13 | { 14 | "description": "boundary point is invalid", 15 | "data": 3.0, 16 | "valid": false 17 | }, 18 | { 19 | "description": "above the exclusiveMaximum is invalid", 20 | "data": 3.5, 21 | "valid": false 22 | }, 23 | { 24 | "description": "ignores non-numbers", 25 | "data": "x", 26 | "valid": true 27 | } 28 | ] 29 | } 30 | ] 31 | -------------------------------------------------------------------------------- /src/test/resources/draft7/exclusiveMinimum.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "exclusiveMinimum validation", 4 | "schema": { 5 | "exclusiveMinimum": 1.1 6 | }, 7 | "tests": [ 8 | { 9 | "description": "above the exclusiveMinimum is valid", 10 | "data": 1.2, 11 | "valid": true 12 | }, 13 | { 14 | "description": "boundary point is invalid", 15 | "data": 1.1, 16 | "valid": false 17 | }, 18 | { 19 | "description": "below the exclusiveMinimum is invalid", 20 | "data": 0.6, 21 | "valid": false 22 | }, 23 | { 24 | "description": "ignores non-numbers", 25 | "data": "x", 26 | "valid": true 27 | } 28 | ] 29 | } 30 | ] 31 | -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/BigNumSpec.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import com.eclipsesource.schema.drafts.Version4 4 | import com.eclipsesource.schema.test.JsonSpec 5 | import org.specs2.mutable.Specification 6 | import play.api.libs.json.JsNumber 7 | 8 | class BigNumSpec extends Specification with JsonSpec { 9 | 10 | import Version4._ 11 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version4)) 12 | validate("optional/bignum", "draft4") 13 | 14 | "Bignum" should { 15 | 16 | "be an integer" in { 17 | val schema = JsonSource.schemaFromString(""" {"type": "integer"} """).get 18 | val instance = JsNumber(BigDecimal("12345678910111213141516171819202122232425262728293031")) 19 | val result = SchemaValidator(Some(Version4)).validate(schema)(instance) 20 | result.asOpt must beSome.which(_ == JsNumber(BigDecimal("12345678910111213141516171819202122232425262728293031"))) 21 | } 22 | 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/test/Assets.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema.test 2 | 3 | import play.api.http.{DefaultFileMimeTypes, FileMimeTypesConfiguration} 4 | import play.api.mvc.{DefaultActionBuilder, Handler} 5 | 6 | object Assets { 7 | 8 | import play.api.mvc.Results._ 9 | implicit val mimeTypes = new DefaultFileMimeTypes(FileMimeTypesConfiguration(Map("json" -> "application/json"))) 10 | 11 | def routes(Action: DefaultActionBuilder)(clazz: Class[_], prefix: String = ""): PartialFunction[(String, String), Handler] = { 12 | case (_, path) => 13 | try { 14 | val resourceName = prefix + path.substring(1) 15 | Option(clazz.getClassLoader.getResource(resourceName)) 16 | .map(_ => Action(Ok.sendResource(resourceName, clazz.getClassLoader))) 17 | .getOrElse(Action(BadRequest(s"$resourceName not found."))) 18 | } catch { 19 | case ex: Throwable => 20 | Action(BadRequest(ex.getMessage)) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/test/resources/draft4/definitions.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "valid definition", 4 | "schema": {"$ref": "http://json-schema.org/draft-04/schema#"}, 5 | "tests": [ 6 | { 7 | "description": "valid definition schema", 8 | "data": { 9 | "definitions": { 10 | "foo": {"type": "integer"} 11 | } 12 | }, 13 | "valid": true 14 | } 15 | ] 16 | }, 17 | { 18 | "description": "invalid definition", 19 | "schema": {"$ref": "http://json-schema.org/draft-04/schema#"}, 20 | "tests": [ 21 | { 22 | "description": "invalid definition schema", 23 | "data": { 24 | "definitions": { 25 | "foo": {"type": 1} 26 | } 27 | }, 28 | "valid": false 29 | } 30 | ] 31 | } 32 | ] 33 | -------------------------------------------------------------------------------- /src/test/resources/draft7/definitions.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "valid definition", 4 | "schema": {"$ref": "http://json-schema.org/draft-07/schema#"}, 5 | "tests": [ 6 | { 7 | "description": "valid definition schema", 8 | "data": { 9 | "definitions": { 10 | "foo": {"type": "integer"} 11 | } 12 | }, 13 | "valid": true 14 | } 15 | ] 16 | }, 17 | { 18 | "description": "invalid definition", 19 | "schema": {"$ref": "http://json-schema.org/draft-07/schema#"}, 20 | "tests": [ 21 | { 22 | "description": "invalid definition schema", 23 | "data": { 24 | "definitions": { 25 | "foo": {"type": 1} 26 | } 27 | }, 28 | "valid": false 29 | } 30 | ] 31 | } 32 | ] 33 | -------------------------------------------------------------------------------- /src/main/scala/com/eclipsesource/schema/internal/validators/CompoundValidator.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema.internal.validators 2 | import com.eclipsesource.schema.internal.validation.VA 3 | import com.eclipsesource.schema.internal.{Keywords, Results, ValidatorMessages} 4 | import com.eclipsesource.schema.{CompoundSchemaType, _} 5 | import com.osinka.i18n.Lang 6 | import play.api.libs.json.JsValue 7 | 8 | object CompoundValidator extends SchemaTypeValidator[CompoundSchemaType] { 9 | override def validate(schema: CompoundSchemaType, json: => JsValue, context: SchemaResolutionContext) 10 | (implicit lang: Lang): VA[JsValue] = { 11 | val result: Option[VA[JsValue]] = schema.alternatives 12 | .map(_.validate(json, context)) 13 | .find(_.isSuccess) 14 | 15 | result.getOrElse( 16 | Results.failureWithPath( 17 | Keywords.Any.Type, 18 | ValidatorMessages("comp.no.schema"), 19 | context, 20 | json 21 | ) 22 | ) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/scala/com/eclipsesource/schema/internal/url/UrlStreamResolverFactory.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema.internal.url 2 | 3 | import java.net.{URLStreamHandler, URLStreamHandlerFactory} 4 | 5 | case class UrlStreamResolverFactory( 6 | private val protocolUrlHandlers: Map[String, URLStreamHandler] = Map.empty[String, URLStreamHandler], 7 | relativeUrlHandlers: Map[String, URLStreamHandler] = Map.empty[String, URLStreamHandler] 8 | ) extends URLStreamHandlerFactory { 9 | 10 | def hasHandlerFor(scheme: String): Boolean = protocolUrlHandlers.contains(scheme) || relativeUrlHandlers.contains(scheme) 11 | 12 | override def createURLStreamHandler(protocol: String): URLStreamHandler = 13 | protocolUrlHandlers.get(protocol).orNull 14 | 15 | def addUrlHandler(protocolEntry: (String, URLStreamHandler)): UrlStreamResolverFactory = 16 | copy(protocolUrlHandlers = protocolUrlHandlers + protocolEntry) 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/test/resources/draft4/pattern.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "pattern validation", 4 | "schema": {"pattern": "^a*$"}, 5 | "tests": [ 6 | { 7 | "description": "a matching pattern is valid", 8 | "data": "aaa", 9 | "valid": true 10 | }, 11 | { 12 | "description": "a non-matching pattern is invalid", 13 | "data": "abc", 14 | "valid": false 15 | }, 16 | { 17 | "description": "ignores non-strings", 18 | "data": true, 19 | "valid": true 20 | } 21 | ] 22 | }, 23 | { 24 | "description": "pattern is not anchored", 25 | "schema": {"pattern": "a+"}, 26 | "tests": [ 27 | { 28 | "description": "matches a substring", 29 | "data": "xxaayy", 30 | "valid": true 31 | } 32 | ] 33 | } 34 | ] 35 | -------------------------------------------------------------------------------- /src/test/resources/draft7/pattern.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "pattern validation", 4 | "schema": {"pattern": "^a*$"}, 5 | "tests": [ 6 | { 7 | "description": "a matching pattern is valid", 8 | "data": "aaa", 9 | "valid": true 10 | }, 11 | { 12 | "description": "a non-matching pattern is invalid", 13 | "data": "abc", 14 | "valid": false 15 | }, 16 | { 17 | "description": "ignores non-strings", 18 | "data": true, 19 | "valid": true 20 | } 21 | ] 22 | }, 23 | { 24 | "description": "pattern is not anchored", 25 | "schema": {"pattern": "a+"}, 26 | "tests": [ 27 | { 28 | "description": "matches a substring", 29 | "data": "xxaayy", 30 | "valid": true 31 | } 32 | ] 33 | } 34 | ] 35 | -------------------------------------------------------------------------------- /src/test/resources/draft4/minLength.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "minLength validation", 4 | "schema": {"minLength": 2}, 5 | "tests": [ 6 | { 7 | "description": "longer is valid", 8 | "data": "foo", 9 | "valid": true 10 | }, 11 | { 12 | "description": "exact length is valid", 13 | "data": "fo", 14 | "valid": true 15 | }, 16 | { 17 | "description": "too short is invalid", 18 | "data": "f", 19 | "valid": false 20 | }, 21 | { 22 | "description": "ignores non-strings", 23 | "data": 1, 24 | "valid": true 25 | }, 26 | { 27 | "description": "one supplementary Unicode code point is not long enough", 28 | "data": "\uD83D\uDCA9", 29 | "valid": false 30 | } 31 | ] 32 | } 33 | ] 34 | -------------------------------------------------------------------------------- /src/test/resources/draft7/minLength.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "minLength validation", 4 | "schema": {"minLength": 2}, 5 | "tests": [ 6 | { 7 | "description": "longer is valid", 8 | "data": "foo", 9 | "valid": true 10 | }, 11 | { 12 | "description": "exact length is valid", 13 | "data": "fo", 14 | "valid": true 15 | }, 16 | { 17 | "description": "too short is invalid", 18 | "data": "f", 19 | "valid": false 20 | }, 21 | { 22 | "description": "ignores non-strings", 23 | "data": 1, 24 | "valid": true 25 | }, 26 | { 27 | "description": "one supplementary Unicode code point is not long enough", 28 | "data": "\uD83D\uDCA9", 29 | "valid": false 30 | } 31 | ] 32 | } 33 | ] 34 | -------------------------------------------------------------------------------- /src/test/resources/draft4/maxLength.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "maxLength validation", 4 | "schema": {"maxLength": 2}, 5 | "tests": [ 6 | { 7 | "description": "shorter is valid", 8 | "data": "f", 9 | "valid": true 10 | }, 11 | { 12 | "description": "exact length is valid", 13 | "data": "fo", 14 | "valid": true 15 | }, 16 | { 17 | "description": "too long is invalid", 18 | "data": "foo", 19 | "valid": false 20 | }, 21 | { 22 | "description": "ignores non-strings", 23 | "data": 100, 24 | "valid": true 25 | }, 26 | { 27 | "description": "two supplementary Unicode code points is long enough", 28 | "data": "\uD83D\uDCA9\uD83D\uDCA9", 29 | "valid": true 30 | } 31 | ] 32 | } 33 | ] 34 | -------------------------------------------------------------------------------- /src/test/resources/draft7/maxLength.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "maxLength validation", 4 | "schema": {"maxLength": 2}, 5 | "tests": [ 6 | { 7 | "description": "shorter is valid", 8 | "data": "f", 9 | "valid": true 10 | }, 11 | { 12 | "description": "exact length is valid", 13 | "data": "fo", 14 | "valid": true 15 | }, 16 | { 17 | "description": "too long is invalid", 18 | "data": "foo", 19 | "valid": false 20 | }, 21 | { 22 | "description": "ignores non-strings", 23 | "data": 100, 24 | "valid": true 25 | }, 26 | { 27 | "description": "two supplementary Unicode code points is long enough", 28 | "data": "\uD83D\uDCA9\uD83D\uDCA9", 29 | "valid": true 30 | } 31 | ] 32 | } 33 | ] 34 | -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/SimplePerformanceSpec.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import java.net.URL 4 | 5 | import com.eclipsesource.schema.drafts.Version4 6 | import org.specs2.mutable.Specification 7 | import play.api.libs.json.{JsValue, Json} 8 | 9 | class SimplePerformanceSpec extends Specification { 10 | 11 | import Version4._ 12 | 13 | def timed(name: String)(body: => Unit) { 14 | val start = System.currentTimeMillis() 15 | body 16 | println(name + ": " + (System.currentTimeMillis() - start) + " ms") 17 | } 18 | 19 | val validator = SchemaValidator(Some(Version4)) 20 | val schemaUrl: URL = getClass.getResource("/issue-99-1.json") 21 | val schema: SchemaType = JsonSource.schemaFromUrl(schemaUrl).get 22 | 23 | val instance: JsValue = Json.parse("""{ "mything": "the thing" }""".stripMargin) 24 | 25 | timed("preloaded") { 26 | for (_ <- 1 to 1000) validator.validate(schema, instance) 27 | } 28 | timed("url based") { 29 | for (_ <- 1 to 1000) validator.validate(schemaUrl)(instance) 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/test/resources/draft4/minProperties.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "minProperties validation", 4 | "schema": {"minProperties": 1}, 5 | "tests": [ 6 | { 7 | "description": "longer is valid", 8 | "data": {"foo": 1, "bar": 2}, 9 | "valid": true 10 | }, 11 | { 12 | "description": "exact length is valid", 13 | "data": {"foo": 1}, 14 | "valid": true 15 | }, 16 | { 17 | "description": "too short is invalid", 18 | "data": {}, 19 | "valid": false 20 | }, 21 | { 22 | "description": "ignores arrays", 23 | "data": [], 24 | "valid": true 25 | }, 26 | { 27 | "description": "ignores strings", 28 | "data": "", 29 | "valid": true 30 | }, 31 | { 32 | "description": "ignores other non-objects", 33 | "data": 12, 34 | "valid": true 35 | } 36 | ] 37 | } 38 | ] 39 | -------------------------------------------------------------------------------- /src/test/resources/draft7/minProperties.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "minProperties validation", 4 | "schema": {"minProperties": 1}, 5 | "tests": [ 6 | { 7 | "description": "longer is valid", 8 | "data": {"foo": 1, "bar": 2}, 9 | "valid": true 10 | }, 11 | { 12 | "description": "exact length is valid", 13 | "data": {"foo": 1}, 14 | "valid": true 15 | }, 16 | { 17 | "description": "too short is invalid", 18 | "data": {}, 19 | "valid": false 20 | }, 21 | { 22 | "description": "ignores arrays", 23 | "data": [], 24 | "valid": true 25 | }, 26 | { 27 | "description": "ignores strings", 28 | "data": "", 29 | "valid": true 30 | }, 31 | { 32 | "description": "ignores other non-objects", 33 | "data": 12, 34 | "valid": true 35 | } 36 | ] 37 | } 38 | ] 39 | -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/AjvSpecs.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import com.eclipsesource.schema.drafts.Version4 4 | import com.eclipsesource.schema.test.JsonSpec 5 | import play.api.test.PlaySpecification 6 | 7 | class AjvSpecs extends PlaySpecification with JsonSpec { 8 | 9 | import Version4._ 10 | implicit val validator = SchemaValidator(Some(Version4)) 11 | def validateAjv(testName: String) = validate(testName, "ajv_tests") 12 | 13 | validateAjv("1_ids_in_refs") 14 | validateAjv("2_root_ref_in_ref") 15 | validateAjv("17_escaping_pattern_property") 16 | validateAjv("19_required_many_properties") 17 | validateAjv("20_failing_to_parse_schema") 18 | validateAjv("27_recursive_reference") 19 | validateAjv("27_1_recursive_raml_schema") 20 | validateAjv("28_escaping_pattern_error") 21 | validateAjv("33_json_schema_latest") 22 | validateAjv("63_id_property_not_in_schema") 23 | validateAjv("70_1_recursive_hash_ref_in_remote_ref") 24 | validateAjv("70_swagger_schema") 25 | validateAjv("87_$_property") 26 | validateAjv("94_dependencies_fail") 27 | validateAjv("170_ref_and_id_in_sibling") 28 | validateAjv("226_json_with_control_chars") 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/test/resources/draft4/maxProperties.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "maxProperties validation", 4 | "schema": {"maxProperties": 2}, 5 | "tests": [ 6 | { 7 | "description": "shorter is valid", 8 | "data": {"foo": 1}, 9 | "valid": true 10 | }, 11 | { 12 | "description": "exact length is valid", 13 | "data": {"foo": 1, "bar": 2}, 14 | "valid": true 15 | }, 16 | { 17 | "description": "too long is invalid", 18 | "data": {"foo": 1, "bar": 2, "baz": 3}, 19 | "valid": false 20 | }, 21 | { 22 | "description": "ignores arrays", 23 | "data": [1, 2, 3], 24 | "valid": true 25 | }, 26 | { 27 | "description": "ignores strings", 28 | "data": "foobar", 29 | "valid": true 30 | }, 31 | { 32 | "description": "ignores other non-objects", 33 | "data": 12, 34 | "valid": true 35 | } 36 | ] 37 | } 38 | ] 39 | -------------------------------------------------------------------------------- /src/test/resources/draft7/maxProperties.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "maxProperties validation", 4 | "schema": {"maxProperties": 2}, 5 | "tests": [ 6 | { 7 | "description": "shorter is valid", 8 | "data": {"foo": 1}, 9 | "valid": true 10 | }, 11 | { 12 | "description": "exact length is valid", 13 | "data": {"foo": 1, "bar": 2}, 14 | "valid": true 15 | }, 16 | { 17 | "description": "too long is invalid", 18 | "data": {"foo": 1, "bar": 2, "baz": 3}, 19 | "valid": false 20 | }, 21 | { 22 | "description": "ignores arrays", 23 | "data": [1, 2, 3], 24 | "valid": true 25 | }, 26 | { 27 | "description": "ignores strings", 28 | "data": "foobar", 29 | "valid": true 30 | }, 31 | { 32 | "description": "ignores other non-objects", 33 | "data": 12, 34 | "valid": true 35 | } 36 | ] 37 | } 38 | ] 39 | -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/AdditionalItemsSpec.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import com.eclipsesource.schema.drafts.{Version4, Version7} 4 | import com.eclipsesource.schema.test.JsonSpec 5 | import org.specs2.mutable.Specification 6 | import play.api.libs.json.{JsArray, JsNumber} 7 | 8 | class AdditionalItemsSpec extends Specification with JsonSpec { 9 | 10 | "validate draft4" in { 11 | import Version4._ 12 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version4)) 13 | validate("additionalItems", "draft4") 14 | } 15 | 16 | "validate draft7" in { 17 | import Version7._ 18 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version7)) 19 | validate("additionalItems", "draft7") 20 | } 21 | 22 | "AdditionalItems" should { 23 | import Version7._ 24 | val schema = JsonSource.schemaFromString( 25 | """{ 26 | | "items": [{}, {}, {}], 27 | | "additionalItems": false 28 | |}""".stripMargin).get 29 | 30 | "no additional items present" in { 31 | val data = JsArray(Seq(JsNumber(1), JsNumber(2), JsNumber(3))) 32 | SchemaValidator(Some(Version4)).validate(schema, data).isSuccess must beTrue 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/SchemaSpec.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import com.eclipsesource.schema.drafts.Version4 4 | import org.specs2.mutable.Specification 5 | 6 | class SchemaSpec extends Specification { self => 7 | 8 | "Schema draft v7" should { 9 | "validate itself" in { 10 | import com.eclipsesource.schema.drafts.Version7._ 11 | val schema = JsonSource.fromUrl(self.getClass.getResource("/refs/json-schema-draft-07.json")).get 12 | val jsonSchema = JsonSource.schemaFromStream(self.getClass.getResourceAsStream("/refs/json-schema-draft-07.json")).get 13 | implicit val validator: SchemaValidator = SchemaValidator() 14 | validator.validate(jsonSchema, schema).isSuccess must beTrue 15 | } 16 | } 17 | 18 | "Schema draft v4" should { 19 | "validate itself" in { 20 | import Version4._ 21 | val schema = JsonSource.fromUrl(self.getClass.getResource("/refs/json-schema-draft-04.json")).get 22 | val jsonSchema = JsonSource.schemaFromStream(self.getClass.getResourceAsStream("/refs/json-schema-draft-04.json")).get 23 | implicit val validator: SchemaValidator = SchemaValidator() 24 | validator.validate(jsonSchema, schema).isSuccess must beTrue 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/scala/com/eclipsesource/schema/drafts/Version7.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema.drafts 2 | 3 | import com.eclipsesource.schema.internal.draft7.{SchemaReads7, SchemaWrites7} 4 | import com.eclipsesource.schema.internal.validators.DefaultFormats 5 | import com.eclipsesource.schema.{JsonSource, SchemaConfigOptions, SchemaFormat, SchemaType, SchemaVersion} 6 | 7 | trait Version7 extends SchemaVersion with SchemaReads7 with SchemaWrites7 8 | 9 | object Version7 extends Version7 { self => 10 | val SchemaUrl = "http://json-schema.org/draft-07/schema#" 11 | val schemaLocation: String = SchemaUrl 12 | lazy val Schema: SchemaType = 13 | JsonSource.schemaFromUrl(self.getClass.getResource("/json-schema-draft-07.json")) 14 | .getOrElse(throw new RuntimeException("Could not read schema file json-schema-draft-07.json.")) 15 | val options: SchemaConfigOptions = new SchemaConfigOptions { 16 | override def supportsExternalReferences: Boolean = false 17 | override def formats: Map[String, SchemaFormat] = DefaultFormats.formats 18 | } 19 | def apply(schemaOptions: SchemaConfigOptions): Version7 = { 20 | new Version7 { 21 | val schemaLocation: String = SchemaUrl 22 | override def options: SchemaConfigOptions = schemaOptions 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/scala/com/eclipsesource/schema/drafts/Version4.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema.drafts 2 | 3 | import com.eclipsesource.schema.internal.draft4.{SchemaReads4, SchemaWrites4} 4 | import com.eclipsesource.schema.internal.validators.DefaultFormats 5 | import com.eclipsesource.schema.{JsonSource, SchemaConfigOptions, SchemaFormat, SchemaType, SchemaVersion} 6 | 7 | trait Version4 extends SchemaVersion with SchemaReads4 with SchemaWrites4 8 | 9 | object Version4 extends Version4 { self => 10 | val SchemaUrl = "http://json-schema.org/draft-04/schema#" 11 | val schemaLocation: String = SchemaUrl 12 | lazy val Schema: SchemaType = 13 | JsonSource.schemaFromUrl(self.getClass.getResource("/json-schema-draft-04.json")) 14 | .getOrElse(throw new RuntimeException("Could not read schema file json-schema-draft-04.json.")) 15 | val options: SchemaConfigOptions = new SchemaConfigOptions { 16 | override def supportsExternalReferences: Boolean = true 17 | override def formats: Map[String, SchemaFormat] = DefaultFormats.formats 18 | } 19 | def apply(schemaOptions: SchemaConfigOptions): Version4 = { 20 | new Version4 { 21 | val schemaLocation: String = SchemaUrl 22 | override def options: SchemaConfigOptions = schemaOptions 23 | } 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/OneOfSpec.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import com.eclipsesource.schema.drafts.{Version4, Version7} 4 | import com.eclipsesource.schema.test.JsonSpec 5 | import org.specs2.mutable.Specification 6 | 7 | class OneOfSpec extends Specification with JsonSpec { 8 | 9 | import Version4._ 10 | 11 | "oneOf draft4" in { 12 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version4)) 13 | validate("oneOf", "draft4") 14 | validate("oneOf", "ajv_tests") 15 | } 16 | 17 | "oneOf draft7" in { 18 | import Version7._ 19 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version7)) 20 | validate("oneOf", "draft7") 21 | } 22 | 23 | "oneOf must be array of objects (invalid)" in { 24 | val schema = JsonSource.schemaFromString( 25 | """{ 26 | | "oneOf": [ 27 | | "#/definitions/foo" 28 | | ] 29 | |}""".stripMargin) 30 | schema.isError must beTrue 31 | } 32 | 33 | "oneOf must be array of objects (valid)" in { 34 | val schema = JsonSource.schemaFromString( 35 | """{ 36 | | "oneOf": [{ 37 | | "$ref": "#/definitions/foo" 38 | | }] 39 | |}""".stripMargin) 40 | schema.isSuccess must beTrue 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /project/Dependencies.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | 3 | object Version { 4 | final val playJson = "2.7.4" 5 | final val playSpecs = "2.7.3" 6 | final val scalaz = "7.2.27" 7 | final val specs2 = "4.5.1" 8 | final val guava = "19.0" 9 | final val i18n = "1.0.3" 10 | final val galimatias = "0.2.1" 11 | } 12 | 13 | object Library { 14 | final val guava = "com.google.guava" % "guava" % Version.guava 15 | final val scalaz = "org.scalaz" %% "scalaz-core" % Version.scalaz 16 | final val playJson = "com.typesafe.play" %% "play-json" % Version.playJson 17 | final val playTest = "com.typesafe.play" %% "play-specs2" % Version.playSpecs % "test" 18 | final val specs2 = "org.specs2" %% "specs2-core" % Version.specs2 % "test" 19 | final val i18n = "com.osinka.i18n" %% "scala-i18n" % Version.i18n 20 | final val galimatias = "io.mola.galimatias" % "galimatias" % Version.galimatias 21 | } 22 | 23 | 24 | object Dependencies { 25 | import Library._ 26 | 27 | val core = List( 28 | galimatias, 29 | guava, 30 | i18n, 31 | playJson, 32 | playTest, 33 | scalaz, 34 | specs2 35 | ) 36 | } -------------------------------------------------------------------------------- /src/test/resources/tincan/activity_definition.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "id": "#activity_definition", 4 | "type": "object", 5 | "oneOf": [ 6 | { 7 | "properties": { 8 | "interactionType": {"type": "null"}, 9 | "correctResponsesPattern": {"type": "null"}, 10 | "choices": {"type": "null"}, 11 | "scale": {"type": "null"}, 12 | "source": {"type": "null"}, 13 | "target": {"type": "null"}, 14 | "steps": {"type": "null"} 15 | } 16 | }, 17 | {"$ref": "#interactionactivity"} 18 | ], 19 | "additionalProperties": false, 20 | "properties": { 21 | "name": {"$ref": "#languagemap"}, 22 | "description": {"$ref": "#languagemap"}, 23 | "type": { 24 | "type": "string", 25 | "format": "iri" 26 | }, 27 | "moreInfo": { 28 | "type": "string", 29 | "format": "iri" 30 | }, 31 | "interactionType": {}, 32 | "correctResponsesPattern": {}, 33 | "choices": {}, 34 | "scale": {}, 35 | "source": {}, 36 | "target": {}, 37 | "steps": {}, 38 | "extensions": {"$ref": "#extensions"} 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/RefRemoteDraft7Spec.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import com.eclipsesource.schema.drafts.Version7 4 | import com.eclipsesource.schema.test.JsonSpec 5 | import org.specs2.mutable.Specification 6 | 7 | class RefRemoteDraft7Spec extends Specification with JsonSpec { 8 | 9 | import Version7._ 10 | 11 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version7)) 12 | .addSchema( 13 | "http://localhost:1234/integer.json", 14 | JsonSource.schemaFromStream( 15 | getClass.getResourceAsStream("/remotes/integer.json") 16 | ).get 17 | ) 18 | .addSchema( 19 | "http://localhost:1234/subSchemas.json", 20 | JsonSource.schemaFromStream( 21 | getClass.getResourceAsStream("/remotes/subSchemas.json") 22 | ).get 23 | ) 24 | .addSchema( 25 | "http://localhost:1234/folder/folderInteger.json", 26 | JsonSource.schemaFromStream( 27 | getClass.getResourceAsStream("/remotes/folder/folderInteger.json") 28 | ).get 29 | ) 30 | .addSchema( 31 | "http://localhost:1234/name.json", 32 | JsonSource.schemaFromStream( 33 | getClass.getResourceAsStream("/remotes/name.json") 34 | ).get 35 | ) 36 | 37 | "refRemote draft7" in { 38 | validate("refRemote", "draft7") 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/MaxLengthSpec.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import com.eclipsesource.schema.drafts.{Version4, Version7} 4 | import com.eclipsesource.schema.test.JsonSpec 5 | import org.specs2.mutable.Specification 6 | import play.api.libs.json._ 7 | 8 | class MaxLengthSpec extends Specification with JsonSpec { 9 | 10 | "maxLength draft4" in { 11 | import Version4._ 12 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version4)) 13 | validate("maxLength", "draft4") 14 | } 15 | 16 | "maxLength draft7" in { 17 | import Version7._ 18 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version7)) 19 | validate("maxLength", "draft7") 20 | } 21 | 22 | "MaxLength" should { 23 | import Version4._ 24 | "fail with an error in case the string is too long" in { 25 | val schema = JsonSource.schemaFromString( 26 | """{ 27 | |"maxLength": 2 28 | }""".stripMargin).get 29 | val json = JsString("foo") 30 | val result = SchemaValidator(Some(Version4)).validate(schema)(json) 31 | result.isError must beTrue 32 | result.asEither must beLeft.like { 33 | case error => (error.toJson(0) \ "msgs") == JsDefined(JsArray(Seq(JsString("'foo' exceeds maximum length of 2.")))) 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/test/resources/ajv_tests/1_ids_in_refs.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "IDs in refs without root id (#1)", 4 | "schema": { 5 | "definitions": { 6 | "int": { 7 | "id": "#int", 8 | "type": "integer" 9 | } 10 | }, 11 | "$ref": "#int" 12 | }, 13 | "tests": [ 14 | { "description": "valid", "data": 1, "valid": true }, 15 | { "description": "invalid", "data": "foo", "valid": false } 16 | ] 17 | }, 18 | { 19 | "description": "IDs in refs with root id", 20 | "schema": { 21 | "id": "http://example.com/int.json", 22 | "definitions": { 23 | "int": { 24 | "id": "#int", 25 | "type": "integer" 26 | } 27 | }, 28 | "$ref": "#int" 29 | }, 30 | "tests": [ 31 | { "description": "valid", "data": 1, "valid": true }, 32 | { "description": "invalid", "data": "foo", "valid": false } 33 | ] 34 | }, 35 | { 36 | "description": "Definitions instead of IDs", 37 | "schema": { 38 | "definitions": { 39 | "int": { 40 | "type": "integer" 41 | } 42 | }, 43 | "$ref": "#/definitions/int" 44 | }, 45 | "tests": [ 46 | { "description": "valid", "data": 1, "valid": true }, 47 | { "description": "invalid", "data": "foo", "valid": false } 48 | ] 49 | } 50 | ] 51 | -------------------------------------------------------------------------------- /src/test/resources/ajv_tests/14_ref_in_remote_ref_with_id.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "ref in remote ref with ids", 4 | "schema": { 5 | "id": "http://localhost:1234/issue14a.json", 6 | "type": "array", 7 | "items": { "$ref": "foo.json" } 8 | }, 9 | "tests": [ 10 | { 11 | "description": "string is valid", 12 | "data": [ 13 | { 14 | "bar": "any string" 15 | } 16 | ], 17 | "valid": true 18 | }, 19 | { 20 | "description": "not string is invalid", 21 | "data": [ 22 | { 23 | "bar": 1 24 | } 25 | ], 26 | "valid": false 27 | } 28 | ] 29 | }, 30 | { 31 | "description": "remote ref in definitions in remote ref with ids (#14)", 32 | "schema": { 33 | "id": "http://localhost:1234/issue14b.json", 34 | "type": "array", 35 | "items": { "$ref": "buu.json#/definitions/buu" } 36 | }, 37 | "tests": [ 38 | { 39 | "description": "string is valid", 40 | "data": [ 41 | { 42 | "bar": "any string" 43 | } 44 | ], 45 | "valid": true 46 | }, 47 | { 48 | "description": "not string is invalid", 49 | "data": [ 50 | { 51 | "bar": 1 52 | } 53 | ], 54 | "valid": false 55 | } 56 | ] 57 | } 58 | ] 59 | -------------------------------------------------------------------------------- /src/test/resources/draft4/maximum.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "maximum validation", 4 | "schema": {"maximum": 3.0}, 5 | "tests": [ 6 | { 7 | "description": "below the maximum is valid", 8 | "data": 2.6, 9 | "valid": true 10 | }, 11 | { 12 | "description": "boundary point is valid", 13 | "data": 3.0, 14 | "valid": true 15 | }, 16 | { 17 | "description": "above the maximum is invalid", 18 | "data": 3.5, 19 | "valid": false 20 | }, 21 | { 22 | "description": "ignores non-numbers", 23 | "data": "x", 24 | "valid": true 25 | } 26 | ] 27 | }, 28 | { 29 | "description": "exclusiveMaximum validation", 30 | "schema": { 31 | "maximum": 3.0, 32 | "exclusiveMaximum": true 33 | }, 34 | "tests": [ 35 | { 36 | "description": "below the maximum is still valid", 37 | "data": 2.2, 38 | "valid": true 39 | }, 40 | { 41 | "description": "boundary point is invalid", 42 | "data": 3.0, 43 | "valid": false 44 | } 45 | ] 46 | } 47 | ] 48 | -------------------------------------------------------------------------------- /src/test/resources/draft4/minimum.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "minimum validation", 4 | "schema": {"minimum": 1.1}, 5 | "tests": [ 6 | { 7 | "description": "above the minimum is valid", 8 | "data": 2.6, 9 | "valid": true 10 | }, 11 | { 12 | "description": "boundary point is valid", 13 | "data": 1.1, 14 | "valid": true 15 | }, 16 | { 17 | "description": "below the minimum is invalid", 18 | "data": 0.6, 19 | "valid": false 20 | }, 21 | { 22 | "description": "ignores non-numbers", 23 | "data": "x", 24 | "valid": true 25 | } 26 | ] 27 | }, 28 | { 29 | "description": "exclusiveMinimum validation", 30 | "schema": { 31 | "minimum": 1.1, 32 | "exclusiveMinimum": true 33 | }, 34 | "tests": [ 35 | { 36 | "description": "above the minimum is still valid", 37 | "data": 1.2, 38 | "valid": true 39 | }, 40 | { 41 | "description": "boundary point is invalid", 42 | "data": 1.1, 43 | "valid": false 44 | } 45 | ] 46 | } 47 | ] 48 | -------------------------------------------------------------------------------- /src/test/resources/draft4/default.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "invalid type for default", 4 | "schema": { 5 | "properties": { 6 | "foo": { 7 | "type": "integer", 8 | "default": [] 9 | } 10 | } 11 | }, 12 | "tests": [ 13 | { 14 | "description": "valid when property is specified", 15 | "data": {"foo": 13}, 16 | "valid": true 17 | }, 18 | { 19 | "description": "still valid when the invalid default is used", 20 | "data": {}, 21 | "valid": true 22 | } 23 | ] 24 | }, 25 | { 26 | "description": "invalid string value for default", 27 | "schema": { 28 | "properties": { 29 | "bar": { 30 | "type": "string", 31 | "minLength": 4, 32 | "default": "bad" 33 | } 34 | } 35 | }, 36 | "tests": [ 37 | { 38 | "description": "valid when property is specified", 39 | "data": {"bar": "good"}, 40 | "valid": true 41 | }, 42 | { 43 | "description": "still valid when the invalid default is used", 44 | "data": {}, 45 | "valid": true 46 | } 47 | ] 48 | } 49 | ] 50 | -------------------------------------------------------------------------------- /src/test/resources/draft7/default.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "invalid type for default", 4 | "schema": { 5 | "properties": { 6 | "foo": { 7 | "type": "integer", 8 | "default": [] 9 | } 10 | } 11 | }, 12 | "tests": [ 13 | { 14 | "description": "valid when property is specified", 15 | "data": {"foo": 13}, 16 | "valid": true 17 | }, 18 | { 19 | "description": "still valid when the invalid default is used", 20 | "data": {}, 21 | "valid": true 22 | } 23 | ] 24 | }, 25 | { 26 | "description": "invalid string value for default", 27 | "schema": { 28 | "properties": { 29 | "bar": { 30 | "type": "string", 31 | "minLength": 4, 32 | "default": "bad" 33 | } 34 | } 35 | }, 36 | "tests": [ 37 | { 38 | "description": "valid when property is specified", 39 | "data": {"bar": "good"}, 40 | "valid": true 41 | }, 42 | { 43 | "description": "still valid when the invalid default is used", 44 | "data": {}, 45 | "valid": true 46 | } 47 | ] 48 | } 49 | ] 50 | -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/MinLengthSpec.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import com.eclipsesource.schema.drafts.{Version4, Version7} 4 | import com.eclipsesource.schema.test.JsonSpec 5 | import org.specs2.mutable.Specification 6 | import play.api.libs.json.JsString 7 | 8 | class MinLengthSpec extends Specification with JsonSpec { 9 | 10 | "validate draft4" in { 11 | import Version4._ 12 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version4)) 13 | validate("minLength", "draft4") 14 | } 15 | 16 | "validate draft7" in { 17 | import Version7._ 18 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version7)) 19 | validate("minLength", "draft7") 20 | } 21 | 22 | "MinLength" should { 23 | 24 | import Version4._ 25 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version4)) 26 | 27 | "validate against numeric strings that are long enough" in { 28 | val schema = JsonSource.schemaFromString( 29 | """{ 30 | |"minLength": 3 31 | }""".stripMargin).get 32 | 33 | validator.validate(schema)(JsString("123")).isSuccess must beTrue 34 | } 35 | 36 | "not validate against numeric strings that are too short" in { 37 | val schema = JsonSource.schemaFromString( 38 | """{ 39 | |"minLength": 3 40 | }""".stripMargin).get 41 | 42 | validator.validate(schema)(JsString("12")).isError must beTrue 43 | } 44 | 45 | } 46 | } 47 | 48 | -------------------------------------------------------------------------------- /src/main/scala/com/eclipsesource/schema/internal/refs/SchemaResolutionScope.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema.internal.refs 2 | 3 | import com.eclipsesource.schema.SchemaType 4 | import play.api.libs.json.JsPath 5 | import com.eclipsesource.schema.internal._ 6 | 7 | case class SchemaResolutionScope(documentRoot: SchemaType, 8 | id: Option[Ref] = None, // current resolution scope 9 | schemaJsPath: Option[JsPath] = None, 10 | instancePath: JsPath = JsPath, 11 | depth: Int = 0, 12 | referrer: Option[JsPath] = None 13 | ) { 14 | 15 | 16 | def schemaPath: Option[String] = schemaJsPath.map(jsPath => SchemaUtil.dropSlashIfAny(jsPath.toString())) 17 | 18 | } 19 | 20 | case class DocumentCache(mapping: collection.concurrent.Map[String, SchemaType] = collection.concurrent.TrieMap.empty[String, SchemaType]) { 21 | 22 | def add(id: Ref)(schemaType: SchemaType): DocumentCache = { 23 | mapping += (id.value -> schemaType) 24 | this 25 | } 26 | 27 | def addAll(schemas: Map[String, SchemaType]): DocumentCache = { 28 | mapping ++= schemas 29 | this 30 | } 31 | 32 | def get(ref: Ref): Option[SchemaType] = mapping.get(ref.value) 33 | 34 | def get(s: String): Option[SchemaType] = mapping.get(s) 35 | 36 | def apply(ref: Ref): SchemaType = mapping(ref.value) 37 | 38 | def contains(ref: Ref): Boolean = mapping.keySet.contains(ref.value) 39 | } 40 | -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/internal/refs/RefsSpec.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema.internal.refs 2 | 3 | import org.specs2.mutable.Specification 4 | 5 | /** 6 | * Examples from http://json-schema.org/draft-04/json-schema-core.html#rfc.section.7.2.2 7 | */ 8 | class RefsSpec extends Specification { 9 | "Refs" should { 10 | "should define resolution scopes" in { 11 | 12 | Refs.mergeRefs( 13 | Ref("#foo"), 14 | Some(Ref("#bar")) 15 | ) must beEqualTo(Ref("#foo")) 16 | 17 | Refs.mergeRefs( 18 | Ref("#"), Some(Ref("http://x.y.z/rootschema.json#")) 19 | ) must beEqualTo(Ref("http://x.y.z/rootschema.json#")) 20 | 21 | Refs.mergeRefs( 22 | Ref("#foo"), Some(Ref("http://x.y.z/rootschema.json#")) 23 | ) must beEqualTo(Ref("http://x.y.z/rootschema.json#foo")) 24 | 25 | Refs.mergeRefs( 26 | Ref("otherschema.json"), Some(Ref("http://x.y.z/rootschema.json#")) 27 | ) must beEqualTo(Ref("http://x.y.z/otherschema.json#")) 28 | 29 | Refs.mergeRefs( 30 | Ref("#bar"), Some(Ref("http://x.y.z/otherschema.json")) 31 | ) must beEqualTo(Ref("http://x.y.z/otherschema.json#bar")) 32 | 33 | Refs.mergeRefs( 34 | Ref("t/inner.json#a"), Some(Ref("http://x.y.z/rootschema.json#")) 35 | ) must beEqualTo(Ref("http://x.y.z/t/inner.json#a")) 36 | 37 | Refs.mergeRefs( 38 | Ref("some://where.else/completely#"), Some(Ref("http://x.y.z/rootschema.json#")) 39 | ) must beEqualTo(Ref("some://where.else/completely#")) 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/resources/ajv_tests/94_dependencies_fail.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "second dependency is not checked (#94)", 4 | "schema": { 5 | "dependencies": { 6 | "bar" : ["baz"], 7 | "foo" : ["bar"] 8 | } 9 | }, 10 | "tests": [ 11 | { 12 | "description": "object with only foo is invalid (bar is missing)", 13 | "data": { "foo": 1 }, 14 | "valid": false 15 | }, 16 | { 17 | "description": "object with foo and bar is invalid (baz is missing)", 18 | "data": { "foo": 1, "bar": 2 }, 19 | "valid": false 20 | }, 21 | { 22 | "description": "object with foo, bar and baz is valid", 23 | "data": { "foo": 1, "bar": 2, "baz": 3 }, 24 | "valid": true 25 | } 26 | ] 27 | }, 28 | { 29 | "description": "second dependency is checked when order is changed", 30 | "schema": { 31 | "dependencies": { 32 | "foo" : ["bar"], 33 | "bar" : ["baz"] 34 | } 35 | }, 36 | "tests": [ 37 | { 38 | "description": "object with only foo is invalid (bar is missing)", 39 | "data": { "foo": 1 }, 40 | "valid": false 41 | }, 42 | { 43 | "description": "object with foo and bar is invalid (baz is missing)", 44 | "data": { "foo": 1, "bar": 2 }, 45 | "valid": false 46 | }, 47 | { 48 | "description": "object with foo, bar and baz is valid", 49 | "data": { "foo": 1, "bar": 2, "baz": 3 }, 50 | "valid": true 51 | } 52 | ] 53 | } 54 | ] 55 | -------------------------------------------------------------------------------- /src/test/resources/ajv_tests/12_restoring_root_after_resolve.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "restoring root after ref resolution (#12)", 4 | "schema": { 5 | "definitions": { 6 | "int": { "$ref": "http://localhost:1234/integer.json" }, 7 | "str": { "type": "string" } 8 | }, 9 | "anyOf": [ 10 | { "$ref": "#/definitions/int" }, 11 | { "$ref": "#/definitions/str" } 12 | ] 13 | }, 14 | "tests": [ 15 | { 16 | "description": "valid string", 17 | "data": "foo", 18 | "valid": true 19 | }, 20 | { 21 | "description": "valid number", 22 | "data": 1, 23 | "valid": true 24 | }, 25 | { 26 | "description": "invalid object", 27 | "data": {}, 28 | "valid": false 29 | } 30 | ] 31 | }, 32 | { 33 | "description": "all refs are in the same place", 34 | "schema": { 35 | "definitions": { 36 | "int": { "type": "integer" }, 37 | "str": { "type": "string" } 38 | }, 39 | "anyOf": [ 40 | { "$ref": "#/definitions/int" }, 41 | { "$ref": "#/definitions/str" } 42 | ] 43 | }, 44 | "tests": [ 45 | { 46 | "description": "valid string", 47 | "data": "foo", 48 | "valid": true 49 | }, 50 | { 51 | "description": "valid number", 52 | "data": 1, 53 | "valid": true 54 | }, 55 | { 56 | "description": "invalid object", 57 | "data": {}, 58 | "valid": false 59 | } 60 | ] 61 | } 62 | ] 63 | -------------------------------------------------------------------------------- /src/test/resources/draft4/required.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "required validation", 4 | "schema": { 5 | "properties": { 6 | "foo": {}, 7 | "bar": {} 8 | }, 9 | "required": ["foo"] 10 | }, 11 | "tests": [ 12 | { 13 | "description": "present required property is valid", 14 | "data": {"foo": 1}, 15 | "valid": true 16 | }, 17 | { 18 | "description": "non-present required property is invalid", 19 | "data": {"bar": 1}, 20 | "valid": false 21 | }, 22 | { 23 | "description": "ignores arrays", 24 | "data": [], 25 | "valid": true 26 | }, 27 | { 28 | "description": "ignores strings", 29 | "data": "", 30 | "valid": true 31 | }, 32 | { 33 | "description": "ignores other non-objects", 34 | "data": 12, 35 | "valid": true 36 | } 37 | ] 38 | }, 39 | { 40 | "description": "required default validation", 41 | "schema": { 42 | "properties": { 43 | "foo": {} 44 | } 45 | }, 46 | "tests": [ 47 | { 48 | "description": "not required by default", 49 | "data": {}, 50 | "valid": true 51 | } 52 | ] 53 | } 54 | ] 55 | -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/ExamplesSpec.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import com.eclipsesource.schema.drafts.Version4 4 | import org.specs2.mutable.Specification 5 | 6 | class ExamplesSpec extends Specification { 7 | 8 | val validator = SchemaValidator(Some(Version4)) 9 | val swaggerSchemaUrl = "/test-schemas/swagger-2.0" 10 | 11 | private def validateExample(schema: String, url: String) = { 12 | val schemaUrl = getClass.getResource(url) 13 | val instanceUrl = getClass.getResource(url) 14 | val instance = JsonSource.fromUrl(instanceUrl) 15 | val result = validator.validate(schemaUrl)(instance.get) 16 | result.isSuccess must beTrue 17 | result.get must beEqualTo(instance.get) 18 | } 19 | 20 | "Validator" should { 21 | "validate petstore-minimal" in { 22 | validateExample(swaggerSchemaUrl, "/test-schemas/petstore-minimal.json") 23 | } 24 | 25 | "validate petstore-simple" in { 26 | validateExample(swaggerSchemaUrl, "/test-schemas/petstore-simple.json") 27 | } 28 | 29 | "validate petstore-expanded" in { 30 | validateExample(swaggerSchemaUrl, "/test-schemas/petstore-expanded.json") 31 | } 32 | 33 | "validate petstore-with-external-docs" in { 34 | validateExample(swaggerSchemaUrl, "/test-schemas/petstore-with-external-docs.json") 35 | } 36 | 37 | "validate petstore" in { 38 | validateExample(swaggerSchemaUrl, "/test-schemas/petstore.json") 39 | } 40 | 41 | "validate core schema agsinst itself" in { 42 | validateExample("/test-schemas/schema", "/test-schemas/schema") 43 | } 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/scala/com/eclipsesource/schema/internal/Keywords.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema.internal 2 | 3 | object Keywords { 4 | 5 | val Schema = "$schema" 6 | val Description = "description" 7 | 8 | val Default = "default" 9 | val Ref = "$ref" 10 | 11 | object Object { 12 | val Properties = "properties" 13 | val PatternProperties = "patternProperties" 14 | val AdditionalProperties = "additionalProperties" 15 | val Required = "required" 16 | val Dependencies = "dependencies" 17 | val MinProperties = "minProperties" 18 | val MaxProperties = "maxProperties" 19 | val PropertyNames = "propertyNames" 20 | } 21 | 22 | object Any { 23 | val AllOf = "allOf" 24 | val AnyOf = "anyOf" 25 | val OneOf = "oneOf" 26 | val Not = "not" 27 | val Definitions = "definitions" 28 | val Description = "description" 29 | val Enum = "enum" 30 | val Type = "type" 31 | val If = "if" 32 | val Then = "then" 33 | val Else = "else" 34 | } 35 | 36 | object Number { 37 | val Min = "minimum" 38 | val Max = "maximum" 39 | val ExclusiveMin = "exclusiveMinimum" 40 | val ExclusiveMax = "exclusiveMaximum" 41 | val MultipleOf = "multipleOf" 42 | } 43 | 44 | object String { 45 | val MinLength = "minLength" 46 | val MaxLength = "maxLength" 47 | val Pattern = "pattern" 48 | val Format = "format" 49 | } 50 | 51 | object Array { 52 | val AdditionalItems = "additionalItems" 53 | val MinItems = "minItems" 54 | val MaxItems = "maxItems" 55 | val UniqueItems = "uniqueItems" 56 | val Items = "items" 57 | val Contains = "contains" 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /src/main/scala/com/eclipsesource/schema/internal/validators/ArrayValidator.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema.internal.validators 2 | 3 | import com.eclipsesource.schema.internal.validation.VA 4 | import com.eclipsesource.schema.internal.{Keywords, Results, SchemaUtil, ValidatorMessages} 5 | import com.eclipsesource.schema.{SchemaArray, SchemaResolutionContext} 6 | import com.osinka.i18n.Lang 7 | import play.api.libs.json.{JsArray, JsValue} 8 | import scalaz.{Failure, Success} 9 | 10 | 11 | object ArrayValidator extends SchemaTypeValidator[SchemaArray] { 12 | 13 | override def validate(schema: SchemaArray, json: => JsValue, context: SchemaResolutionContext) 14 | (implicit lang: Lang): VA[JsValue] = { 15 | json match { 16 | case JsArray(values) => 17 | val elements: Seq[VA[JsValue]] = values.toSeq.zipWithIndex.map { case (jsValue, idx) => 18 | schema.item.validate( 19 | jsValue, 20 | context.updateScope( 21 | _.copy(instancePath = context.instancePath \ idx.toString) 22 | ) 23 | ) 24 | } 25 | if (elements.exists(_.isFailure)) { 26 | Failure(elements.collect { case Failure(err) => err }.reduceLeft(_ ++ _)) 27 | } else { 28 | val updatedArr = JsArray(elements.collect { case Success(js) => js }) 29 | schema.constraints.validate(schema, updatedArr, context) 30 | } 31 | case _ => 32 | Results.failureWithPath( 33 | Keywords.Any.Type, 34 | ValidatorMessages("err.expected.type", SchemaUtil.typeOfAsString(json)), 35 | context, 36 | json 37 | ) 38 | } 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/test/resources/test-schemas/petstore-minimal.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "version": "1.0.0", 5 | "title": "Swagger Petstore", 6 | "description": "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification", 7 | "termsOfService": "http://swagger.io/terms/", 8 | "contact": { 9 | "name": "Swagger API Team" 10 | }, 11 | "license": { 12 | "name": "MIT" 13 | } 14 | }, 15 | "host": "petstore.swagger.io", 16 | "basePath": "/api", 17 | "schemes": [ 18 | "http" 19 | ], 20 | "consumes": [ 21 | "application/json" 22 | ], 23 | "produces": [ 24 | "application/json" 25 | ], 26 | "paths": { 27 | "/pets": { 28 | "get": { 29 | "description": "Returns all pets from the system that the user has access to", 30 | "produces": [ 31 | "application/json" 32 | ], 33 | "responses": { 34 | "200": { 35 | "description": "A list of pets.", 36 | "schema": { 37 | "type": "array", 38 | "items": { 39 | "$ref": "#/definitions/Pet" 40 | } 41 | } 42 | } 43 | } 44 | } 45 | } 46 | }, 47 | "definitions": { 48 | "Pet": { 49 | "type": "object", 50 | "required": [ 51 | "id", 52 | "name" 53 | ], 54 | "properties": { 55 | "id": { 56 | "type": "integer", 57 | "format": "int64" 58 | }, 59 | "name": { 60 | "type": "string" 61 | }, 62 | "tag": { 63 | "type": "string" 64 | } 65 | } 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/Issue99Spec.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import com.eclipsesource.schema.drafts.Version4 4 | import org.specs2.mutable.Specification 5 | import play.api.libs.json.Json 6 | 7 | class Issue99Spec extends Specification { self => 8 | 9 | "Issue 99 Spec" should { 10 | 11 | import Version4._ 12 | 13 | "validate issue 99 test case" in { 14 | val schema = JsonSource.schemaFromString( 15 | """ 16 | |{ 17 | | "type": "object", 18 | | "properties": { 19 | | "mything": { "$ref": "#thing" } 20 | | }, 21 | | "definitions": { 22 | | "thing": { 23 | | "id": "#thing", 24 | | "type": "string" 25 | | } 26 | | } 27 | |} 28 | """.stripMargin).get 29 | val validator = SchemaValidator(Some(Version4)) 30 | validator.validate(schema, Json.obj( 31 | "mything" -> "test" 32 | )).isSuccess must beTrue 33 | } 34 | 35 | "validate issue 99-1 test case via URL" in { 36 | val schemaUrl = self.getClass.getResource("/issue-99-1.json") 37 | val validator = SchemaValidator(Some(Version4)) 38 | val result = validator.validate(schemaUrl)(Json.obj( 39 | "mything" -> "test" 40 | )) 41 | result.isSuccess must beTrue 42 | } 43 | 44 | "not cause stack overflow for issue 99-5 test case via URL" in { 45 | val schemaUrl = self.getClass.getResource("/issue-99-5.json") 46 | val validator = SchemaValidator(Some(Version4)) 47 | // must terminate 48 | validator.validate(schemaUrl)(Json.obj( 49 | "mything" -> "test" 50 | )).isError must beTrue 51 | } 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/test/resources/draft4/multipleOf.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "by int", 4 | "schema": {"multipleOf": 2}, 5 | "tests": [ 6 | { 7 | "description": "int by int", 8 | "data": 10, 9 | "valid": true 10 | }, 11 | { 12 | "description": "int by int fail", 13 | "data": 7, 14 | "valid": false 15 | }, 16 | { 17 | "description": "ignores non-numbers", 18 | "data": "foo", 19 | "valid": true 20 | } 21 | ] 22 | }, 23 | { 24 | "description": "by number", 25 | "schema": {"multipleOf": 1.5}, 26 | "tests": [ 27 | { 28 | "description": "zero is multiple of anything", 29 | "data": 0, 30 | "valid": true 31 | }, 32 | { 33 | "description": "4.5 is multiple of 1.5", 34 | "data": 4.5, 35 | "valid": true 36 | }, 37 | { 38 | "description": "35 is not multiple of 1.5", 39 | "data": 35, 40 | "valid": false 41 | } 42 | ] 43 | }, 44 | { 45 | "description": "by small number", 46 | "schema": {"multipleOf": 0.0001}, 47 | "tests": [ 48 | { 49 | "description": "0.0075 is multiple of 0.0001", 50 | "data": 0.0075, 51 | "valid": true 52 | }, 53 | { 54 | "description": "0.00751 is not multiple of 0.0001", 55 | "data": 0.00751, 56 | "valid": false 57 | } 58 | ] 59 | } 60 | ] 61 | -------------------------------------------------------------------------------- /src/test/resources/draft7/multipleOf.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "by int", 4 | "schema": {"multipleOf": 2}, 5 | "tests": [ 6 | { 7 | "description": "int by int", 8 | "data": 10, 9 | "valid": true 10 | }, 11 | { 12 | "description": "int by int fail", 13 | "data": 7, 14 | "valid": false 15 | }, 16 | { 17 | "description": "ignores non-numbers", 18 | "data": "foo", 19 | "valid": true 20 | } 21 | ] 22 | }, 23 | { 24 | "description": "by number", 25 | "schema": {"multipleOf": 1.5}, 26 | "tests": [ 27 | { 28 | "description": "zero is multiple of anything", 29 | "data": 0, 30 | "valid": true 31 | }, 32 | { 33 | "description": "4.5 is multiple of 1.5", 34 | "data": 4.5, 35 | "valid": true 36 | }, 37 | { 38 | "description": "35 is not multiple of 1.5", 39 | "data": 35, 40 | "valid": false 41 | } 42 | ] 43 | }, 44 | { 45 | "description": "by small number", 46 | "schema": {"multipleOf": 0.0001}, 47 | "tests": [ 48 | { 49 | "description": "0.0075 is multiple of 0.0001", 50 | "data": 0.0075, 51 | "valid": true 52 | }, 53 | { 54 | "description": "0.00751 is not multiple of 0.0001", 55 | "data": 0.00751, 56 | "valid": false 57 | } 58 | ] 59 | } 60 | ] 61 | -------------------------------------------------------------------------------- /src/test/resources/ajv_tests/5_recursive_references.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "Recursive references between schemas (#5)", 4 | "schema": { 5 | "$ref": "http://localhost:1234/tree.json" 6 | }, 7 | "tests": [ 8 | { 9 | "description": "valid tree", 10 | "data": { 11 | "meta": "root", 12 | "nodes": [ 13 | { 14 | "value": 1, 15 | "subtree": { 16 | "meta": "child", 17 | "nodes": [ 18 | { "value": 1.1 }, 19 | { "value": 1.2 } 20 | ] 21 | } 22 | }, 23 | { 24 | "value": 2, 25 | "subtree": { 26 | "meta": "child", 27 | "nodes": [ 28 | { "value": 2.1 }, 29 | { "value": 2.2 } 30 | ] 31 | } 32 | } 33 | ] 34 | }, 35 | "valid": true 36 | }, 37 | { 38 | "description": "invalid tree", 39 | "data": { 40 | "meta": "root", 41 | "nodes": [ 42 | { 43 | "value": 1, 44 | "subtree": { 45 | "meta": "child", 46 | "nodes": [ 47 | { "value": "string is invalid" }, 48 | { "value": 1.2 } 49 | ] 50 | } 51 | }, 52 | { 53 | "value": 2, 54 | "subtree": { 55 | "meta": "child", 56 | "nodes": [ 57 | { "value": 2.1 }, 58 | { "value": 2.2 } 59 | ] 60 | } 61 | } 62 | ] 63 | }, 64 | "valid": false 65 | } 66 | ] 67 | } 68 | ] 69 | -------------------------------------------------------------------------------- /src/main/scala/com/eclipsesource/schema/internal/constraints/Constraints.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema.internal.constraints 2 | 3 | import com.eclipsesource.schema.internal.validation.VA 4 | import com.osinka.i18n.Lang 5 | import play.api.libs.json._ 6 | import scalaz.Success 7 | 8 | object Constraints { 9 | 10 | import com.eclipsesource.schema._ 11 | 12 | trait HasAnyConstraint extends Constraint { 13 | def any: AnyConstraints 14 | def id: Option[String] = any.id 15 | def schemaType: Option[String] = any.schemaType 16 | } 17 | 18 | trait Constraint { 19 | def id: Option[String] 20 | def schemaType: Option[String] 21 | def validate(schemaType: SchemaType, json: JsValue, context: SchemaResolutionContext) 22 | (implicit lang: Lang): VA[JsValue] 23 | private[schema] def subSchemas: Set[SchemaType] 24 | private[schema] def resolvePath(path: String): Option[SchemaType] 25 | } 26 | 27 | case class NoConstraints() extends Constraint { 28 | override def subSchemas: Set[SchemaType] = Set.empty 29 | override def resolvePath(path: String): Option[SchemaType] = None 30 | override def validate(schema: SchemaType, json: JsValue, context: SchemaResolutionContext) 31 | (implicit lang: Lang): VA[JsValue] = Success(json) 32 | override def id: Option[String] = None 33 | override def schemaType: Option[String] = None 34 | } 35 | 36 | trait AnyConstraints extends Constraint 37 | 38 | trait ArrayConstraints extends Constraint with HasAnyConstraint 39 | 40 | trait ObjectConstraints extends Constraint with HasAnyConstraint 41 | 42 | trait NumberConstraints extends Constraint 43 | 44 | trait StringConstraints extends Constraint 45 | 46 | case class Minimum(min: BigDecimal, isExclusive: Option[Boolean]) 47 | 48 | case class Maximum(max: BigDecimal, isExclusive: Option[Boolean]) 49 | 50 | case class MultipleOf(factor: BigDecimal) 51 | } -------------------------------------------------------------------------------- /src/test/resources/draft7/required.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "required validation", 4 | "schema": { 5 | "properties": { 6 | "foo": {}, 7 | "bar": {} 8 | }, 9 | "required": ["foo"] 10 | }, 11 | "tests": [ 12 | { 13 | "description": "present required property is valid", 14 | "data": {"foo": 1}, 15 | "valid": true 16 | }, 17 | { 18 | "description": "non-present required property is invalid", 19 | "data": {"bar": 1}, 20 | "valid": false 21 | }, 22 | { 23 | "description": "ignores arrays", 24 | "data": [], 25 | "valid": true 26 | }, 27 | { 28 | "description": "ignores strings", 29 | "data": "", 30 | "valid": true 31 | }, 32 | { 33 | "description": "ignores other non-objects", 34 | "data": 12, 35 | "valid": true 36 | } 37 | ] 38 | }, 39 | { 40 | "description": "required default validation", 41 | "schema": { 42 | "properties": { 43 | "foo": {} 44 | } 45 | }, 46 | "tests": [ 47 | { 48 | "description": "not required by default", 49 | "data": {}, 50 | "valid": true 51 | } 52 | ] 53 | }, 54 | { 55 | "description": "required with empty array", 56 | "schema": { 57 | "properties": { 58 | "foo": {} 59 | }, 60 | "required": [] 61 | }, 62 | "tests": [ 63 | { 64 | "description": "property not required", 65 | "data": {}, 66 | "valid": true 67 | } 68 | ] 69 | } 70 | ] 71 | -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/ItemsSpec.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import com.eclipsesource.schema.drafts.{Version4, Version7} 4 | import com.eclipsesource.schema.internal.refs.{SchemaRefResolver, SchemaResolutionScope} 5 | import com.eclipsesource.schema.internal.validators.ArrayValidator 6 | import com.eclipsesource.schema.test.JsonSpec 7 | import org.specs2.mutable.Specification 8 | import play.api.libs.json.{JsNumber, Json} 9 | 10 | class ItemsSpec extends Specification with JsonSpec { 11 | 12 | "validate draft4" in { 13 | import Version4._ 14 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version4)) 15 | validate("items", "draft4") 16 | } 17 | 18 | "validate draft7" in { 19 | import Version7._ 20 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version7)) 21 | validate("items", "draft7") 22 | } 23 | 24 | "validate array with some invalid items" in { 25 | import Version4._ 26 | implicit val validator: SchemaValidator = SchemaValidator(Some(Version4)) 27 | val schema = JsonSource.schemaFromString( 28 | """{ 29 | | "type": "array", 30 | | "items": { 31 | | "minimum": 3 32 | | } 33 | |}""".stripMargin).get 34 | 35 | val instance = Json.arr(2, 3, 4, 1) 36 | 37 | val result = validator.validate(schema, instance) 38 | result.isError must beTrue 39 | result.asEither must beLeft.like { case error => 40 | error.toJson.value must haveLength(2) 41 | } 42 | } 43 | 44 | "validate array with wrong json type" in { 45 | import Version4._ 46 | val schema = JsonSource.schemaFromString( 47 | """{ 48 | | "type": "array", 49 | | "items": { 50 | | "minimum": 3 51 | | } 52 | |}""".stripMargin).get.asInstanceOf[SchemaArray] 53 | val context = SchemaResolutionContext(SchemaRefResolver(Version4), SchemaResolutionScope(schema)) 54 | val result = ArrayValidator.validate(schema, JsNumber(2), context) 55 | result.isFailure must beTrue 56 | } 57 | } 58 | 59 | -------------------------------------------------------------------------------- /src/main/scala/com/eclipsesource/schema/internal/Results.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema.internal 2 | 3 | import com.eclipsesource.schema.SchemaResolutionContext 4 | import com.eclipsesource.schema.internal.validation.VA 5 | import play.api.libs.json._ 6 | 7 | import scalaz.{Failure, Success} 8 | 9 | object Results { 10 | 11 | def merge(va1: VA[JsValue], va2: VA[JsValue]): VA[JsValue] = { 12 | (va1, va2) match { 13 | case (Success(_), f@Failure(_)) => f 14 | case (f@Failure(_), Success(_)) => f 15 | case (f1@Failure(errs1), f2@Failure(errs2)) => Failure(errs1 ++ errs2) 16 | case (s@Success(json), Success(_)) => s 17 | } 18 | } 19 | 20 | def aggregateAsObject(validatedProps: Seq[(String, VA[JsValue])], context: SchemaResolutionContext): VA[JsValue] = { 21 | validatedProps.foldLeft[VA[JsValue]](Success(Json.obj()))((va, result) => (va, result._2) match { 22 | case (Success(_), f@Failure(_)) => f 23 | case (f@Failure(_), Success(_)) => f 24 | case (Failure(errs1), Failure(errs2)) => Failure(errs1 ++ errs2) 25 | case (Success(obj@JsObject(_)), Success(s2)) => Success(JsObject(obj.fields :+ (result._1, s2))) 26 | case (_, _) => va 27 | }) 28 | } 29 | 30 | def success(prop: (String, JsValue)): PropertyValidationResult = prop._1 -> Success(prop._2) 31 | 32 | def failureWithPath(keyword: String, 33 | msg: String, 34 | context: SchemaResolutionContext, 35 | instance: JsValue, 36 | additionalInfo: JsObject = Json.obj()): VA[JsValue] = { 37 | 38 | Failure(Seq(context.instancePath -> 39 | Seq(JsonValidationError( 40 | msg, 41 | SchemaUtil.createErrorObject(keyword, context.schemaPath, context.instancePath, instance, additionalInfo) 42 | ++ context.scope.id.fold(Json.obj())(id => Json.obj("resolutionScope" -> id.value)) 43 | ++ context.scope.referrer.fold(Json.obj())(referrer => Json.obj("referrer" -> SchemaUtil.dropSlashIfAny(referrer.toString()))) 44 | )) 45 | )) 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/internal/refs/ResolveNumberConstraintsSpec.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema.internal.refs 2 | 3 | import com.eclipsesource.schema.drafts.{Version4, Version7} 4 | import com.eclipsesource.schema.{JsonSource, SchemaType, SchemaValue} 5 | import org.specs2.mutable.Specification 6 | import play.api.libs.json.JsNumber 7 | 8 | class ResolveNumberConstraintsSpec extends Specification { 9 | 10 | "draft v4" should { 11 | 12 | import Version4._ 13 | val resolver = SchemaRefResolver(Version4) 14 | 15 | "resolve number constraints" in { 16 | val schema = JsonSource.schemaFromString( 17 | """{ 18 | | "type": "integer", 19 | | "minimum": 0, 20 | | "maximum": 10, 21 | | "multipleOf": 2 22 | |}""".stripMargin).get 23 | 24 | val scope = SchemaResolutionScope(schema) 25 | resolver.resolveFromRoot("#/minimum", scope).map(_.resolved) must beRight[SchemaType](SchemaValue(JsNumber(0))) 26 | resolver.resolveFromRoot("#/maximum", scope).map(_.resolved) must beRight[SchemaType](SchemaValue(JsNumber(10))) 27 | resolver.resolveFromRoot("#/multipleOf", scope).map(_.resolved) must beRight[SchemaType](SchemaValue(JsNumber(2))) 28 | } 29 | } 30 | 31 | "draft v7" should { 32 | 33 | import Version7._ 34 | val resolver = SchemaRefResolver(Version7) 35 | 36 | "resolve number constraints" in { 37 | val schema = JsonSource.schemaFromString( 38 | """{ 39 | | "type": "integer", 40 | | "minimum": 0, 41 | | "maximum": 10, 42 | | "multipleOf": 2 43 | |}""".stripMargin).get 44 | 45 | val scope = SchemaResolutionScope(schema) 46 | resolver.resolveFromRoot("#/minimum", scope).map(_.resolved) must beRight[SchemaType](SchemaValue(JsNumber(0))) 47 | resolver.resolveFromRoot("#/maximum", scope).map(_.resolved) must beRight[SchemaType](SchemaValue(JsNumber(10))) 48 | resolver.resolveFromRoot("#/multipleOf", scope).map(_.resolved) must beRight[SchemaType](SchemaValue(JsNumber(2))) 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/scala/com/eclipsesource/schema/internal/draft4/constraints/StringConstraints4.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema.internal.draft4.constraints 2 | 3 | import com.eclipsesource.schema.internal.Keywords 4 | import com.eclipsesource.schema.internal.constraints.Constraints.{AnyConstraints, HasAnyConstraint, StringConstraints} 5 | import com.eclipsesource.schema.internal.validation.VA 6 | import com.eclipsesource.schema.{SchemaResolutionContext, SchemaType, SchemaValue} 7 | import com.osinka.i18n.Lang 8 | import play.api.libs.json.{JsNumber, JsString, JsValue} 9 | 10 | case class StringConstraints4(minLength: Option[Int] = None, 11 | maxLength: Option[Int] = None, 12 | pattern: Option[String] = None, 13 | format: Option[String] = None, 14 | any: AnyConstraints 15 | ) extends HasAnyConstraint with StringConstraints { 16 | 17 | import com.eclipsesource.schema.internal.validators.StringValidators._ 18 | 19 | override def subSchemas: Set[SchemaType] = any.subSchemas 20 | 21 | override def resolvePath(path: String): Option[SchemaType] = path match { 22 | case Keywords.String.MinLength => minLength.map(min => SchemaValue(JsNumber(min))) 23 | case Keywords.String.MaxLength => maxLength.map(max => SchemaValue(JsNumber(max))) 24 | case Keywords.String.Pattern => pattern.map(p => SchemaValue(JsString(p))) 25 | case Keywords.String.Format => format.map(f => SchemaValue(JsString(f))) 26 | case other => any.resolvePath(other) 27 | } 28 | 29 | def validate(schema: SchemaType, json: JsValue, context: SchemaResolutionContext) 30 | (implicit lang: Lang): VA[JsValue] = { 31 | val reader = for { 32 | minLength <- validateMinLength(minLength) 33 | maxLength <- validateMaxLength(maxLength) 34 | pattern <- validatePattern(pattern) 35 | format <- validateFormat(format) 36 | } yield minLength |+| maxLength |+| pattern |+| format 37 | reader.run(context) 38 | .repath(_.compose(context.instancePath)) 39 | .validate(json) 40 | } 41 | } -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/TinCanSpec.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import org.specs2.mutable.Specification 4 | import play.api.libs.json.{JsValue, Json} 5 | 6 | import scala.util.Try 7 | 8 | class TinCanSpec extends Specification { self => 9 | 10 | import com.eclipsesource.schema.drafts.Version4 11 | 12 | val instance = JsonSource.fromString( 13 | """ 14 | |{ 15 | | "actor": { 16 | | "name": "Sally Glider", 17 | | "mbox": "mailto:sally@example.com" 18 | | }, 19 | | "verb": { 20 | | "id": "http://adlnet.gov/expapi/verbs/experienced", 21 | | "display": { "en-US": "experienced" } 22 | | }, 23 | | "object": { 24 | | "id": "http://example.com/activities/solo-hang-gliding", 25 | | "definition": { 26 | | "name": { "en-US": "Solo Hang Gliding" } 27 | | } 28 | | } 29 | |} 30 | """.stripMargin).get 31 | 32 | 33 | "Tin Can Spec" should { 34 | 35 | import Version4._ 36 | 37 | def readSchema(filename: String) = 38 | JsonSource.schemaFromStream(self.getClass.getResourceAsStream(s"/tincan/$filename.json")).get 39 | 40 | "validate basic statement object" in { 41 | 42 | val validator = SchemaValidator(Some(Version4)) 43 | .addSchema("#agent", readSchema("agent")) 44 | .addSchema("#group", readSchema("group")) 45 | .addSchema("#inversefunctional", readSchema("inversefunctional")) 46 | .addSchema("#mbox", readSchema("mbox")) 47 | .addSchema("#statement_base", readSchema("statement_base")) 48 | .addSchema("#statement_object", readSchema("statement_object")) 49 | .addSchema("#verb", readSchema("verb")) 50 | .addSchema("#languagemap", readSchema("languagemap")) 51 | .addSchema("#activity", readSchema("activity")) 52 | .addSchema("#activity_definition", readSchema("activity_definition")) 53 | .addSchema("#activityid", readSchema("activityid")) 54 | 55 | val result = validator.validate(readSchema("statement_base"), instance) 56 | result.isSuccess must beTrue 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/scala/com/eclipsesource/schema/internal/draft7/constraints/StringConstraints7.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema.internal.draft7.constraints 2 | 3 | import com.eclipsesource.schema.{SchemaResolutionContext, SchemaType, SchemaValue} 4 | import com.eclipsesource.schema.internal.Keywords 5 | import com.eclipsesource.schema.internal.constraints.Constraints.{AnyConstraints, HasAnyConstraint, StringConstraints} 6 | import com.eclipsesource.schema.internal.validation.VA 7 | import com.osinka.i18n.Lang 8 | import play.api.libs.json.{JsNumber, JsString, JsValue} 9 | 10 | case class StringConstraints7(minLength: Option[Int] = None, 11 | maxLength: Option[Int] = None, 12 | pattern: Option[String] = None, 13 | format: Option[String] = None, 14 | any: AnyConstraints = AnyConstraints7() 15 | ) extends HasAnyConstraint with StringConstraints { 16 | 17 | import com.eclipsesource.schema.internal.validators.StringValidators._ 18 | 19 | override def subSchemas: Set[SchemaType] = any.subSchemas 20 | 21 | override def resolvePath(path: String): Option[SchemaType] = path match { 22 | case Keywords.String.MinLength => minLength.map(min => SchemaValue(JsNumber(min))) 23 | case Keywords.String.MaxLength => maxLength.map(max => SchemaValue(JsNumber(max))) 24 | case Keywords.String.Pattern => pattern.map(p => SchemaValue(JsString(p))) 25 | case Keywords.String.Format => format.map(f => SchemaValue(JsString(f))) 26 | case other => any.resolvePath(other) 27 | } 28 | 29 | def validate(schema: SchemaType, json: JsValue, context: SchemaResolutionContext) 30 | (implicit lang: Lang): VA[JsValue] = { 31 | val reader = for { 32 | minLength <- validateMinLength(minLength) 33 | maxLength <- validateMaxLength(maxLength) 34 | pattern <- validatePattern(pattern) 35 | format <- validateFormat(format) 36 | } yield minLength |+| maxLength |+| pattern |+| format 37 | reader.run(context) 38 | .repath(_.compose(context.instancePath)) 39 | .validate(json) 40 | } 41 | } -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/RemoteSpecs.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import com.eclipsesource.schema.drafts.Version4 4 | import com.eclipsesource.schema.test.{Assets, JsonSpec} 5 | import org.specs2.mutable.Specification 6 | import org.specs2.specification.AfterAll 7 | import org.specs2.specification.core.Fragments 8 | import org.specs2.specification.dsl.Online 9 | import play.api.Application 10 | import play.api.inject.guice.GuiceApplicationBuilder 11 | import play.api.mvc.DefaultActionBuilder 12 | import play.api.test.TestServer 13 | 14 | class RemoteSpecs extends Specification with JsonSpec with Online with AfterAll { 15 | 16 | import Version4._ 17 | 18 | implicit val validator: SchemaValidator = { 19 | SchemaValidator(Some(Version4)).addSchema( 20 | "http://localhost:1234/scope_foo.json", 21 | JsonSource.schemaFromString( 22 | """{ 23 | | "definitions": { 24 | | "bar": { "type": "string" } 25 | | } 26 | |}""".stripMargin 27 | ).get 28 | ) 29 | } 30 | 31 | def createApp: Application = new GuiceApplicationBuilder() 32 | .appRoutes(app => { 33 | val Action = app.injector.instanceOf[DefaultActionBuilder] 34 | Assets.routes(Action)(getClass, "remotes/") 35 | }) 36 | .build() 37 | 38 | lazy val server = TestServer(port = 1234, createApp) 39 | 40 | def afterAll: Unit = { 41 | server.stop 42 | Thread.sleep(1000) 43 | } 44 | 45 | def validateAjv(testName: String): Fragments = validate(testName, "ajv_tests") 46 | 47 | sequential 48 | 49 | "Validation from remote resources is possible" >> { 50 | { 51 | server.start 52 | Thread.sleep(1000) 53 | } must not(throwAn[Exception]) continueWith { 54 | validateMultiple( 55 | "ajv_tests" -> Seq( 56 | "5_adding_dependency_after", 57 | "5_recursive_references", 58 | "12_restoring_root_after_resolve", 59 | "13_root_ref_in_ref_in_remote_ref", 60 | "14_ref_in_remote_ref_with_id", 61 | "62_resolution_scope_change" 62 | ), 63 | "draft4" -> Seq("refRemote") 64 | ) 65 | } 66 | } 67 | 68 | validateAjv("1_ids_in_refs") 69 | } 70 | -------------------------------------------------------------------------------- /src/test/resources/draft4/enum.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "simple enum validation", 4 | "schema": {"enum": [1, 2, 3]}, 5 | "tests": [ 6 | { 7 | "description": "one of the enum is valid", 8 | "data": 1, 9 | "valid": true 10 | }, 11 | { 12 | "description": "something else is invalid", 13 | "data": 4, 14 | "valid": false 15 | } 16 | ] 17 | }, 18 | { 19 | "description": "heterogeneous enum validation", 20 | "schema": {"enum": [6, "foo", [], true, {"foo": 12}]}, 21 | "tests": [ 22 | { 23 | "description": "one of the enum is valid", 24 | "data": [], 25 | "valid": true 26 | }, 27 | { 28 | "description": "something else is invalid", 29 | "data": null, 30 | "valid": false 31 | }, 32 | { 33 | "description": "objects are deep compared", 34 | "data": {"foo": false}, 35 | "valid": false 36 | } 37 | ] 38 | }, 39 | { 40 | "description": "enums in properties", 41 | "schema": { 42 | "type":"object", 43 | "properties": { 44 | "foo": {"enum":["foo"]}, 45 | "bar": {"enum":["bar"]} 46 | }, 47 | "required": ["bar"] 48 | }, 49 | "tests": [ 50 | { 51 | "description": "both properties are valid", 52 | "data": {"foo":"foo", "bar":"bar"}, 53 | "valid": true 54 | }, 55 | { 56 | "description": "missing optional property is valid", 57 | "data": {"bar":"bar"}, 58 | "valid": true 59 | }, 60 | { 61 | "description": "missing required property is invalid", 62 | "data": {"foo":"foo"}, 63 | "valid": false 64 | }, 65 | { 66 | "description": "missing all properties is invalid", 67 | "data": {}, 68 | "valid": false 69 | } 70 | ] 71 | } 72 | ] 73 | -------------------------------------------------------------------------------- /src/test/resources/draft7/enum.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "simple enum validation", 4 | "schema": {"enum": [1, 2, 3]}, 5 | "tests": [ 6 | { 7 | "description": "one of the enum is valid", 8 | "data": 1, 9 | "valid": true 10 | }, 11 | { 12 | "description": "something else is invalid", 13 | "data": 4, 14 | "valid": false 15 | } 16 | ] 17 | }, 18 | { 19 | "description": "heterogeneous enum validation", 20 | "schema": {"enum": [6, "foo", [], true, {"foo": 12}]}, 21 | "tests": [ 22 | { 23 | "description": "one of the enum is valid", 24 | "data": [], 25 | "valid": true 26 | }, 27 | { 28 | "description": "something else is invalid", 29 | "data": null, 30 | "valid": false 31 | }, 32 | { 33 | "description": "objects are deep compared", 34 | "data": {"foo": false}, 35 | "valid": false 36 | } 37 | ] 38 | }, 39 | { 40 | "description": "enums in properties", 41 | "schema": { 42 | "type":"object", 43 | "properties": { 44 | "foo": {"enum":["foo"]}, 45 | "bar": {"enum":["bar"]} 46 | }, 47 | "required": ["bar"] 48 | }, 49 | "tests": [ 50 | { 51 | "description": "both properties are valid", 52 | "data": {"foo":"foo", "bar":"bar"}, 53 | "valid": true 54 | }, 55 | { 56 | "description": "missing optional property is valid", 57 | "data": {"bar":"bar"}, 58 | "valid": true 59 | }, 60 | { 61 | "description": "missing required property is invalid", 62 | "data": {"foo":"foo"}, 63 | "valid": false 64 | }, 65 | { 66 | "description": "missing all properties is invalid", 67 | "data": {}, 68 | "valid": false 69 | } 70 | ] 71 | } 72 | ] 73 | -------------------------------------------------------------------------------- /src/test/resources/ajv_tests/27_recursive_reference.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "Recursive reference (#27)", 4 | "schema": { 5 | "$schema": "http://json-schema.org/draft-04/schema#", 6 | "id": "testrec", 7 | "type": "object", 8 | "properties": { 9 | "layout": { 10 | "id": "layout", 11 | "type": "object", 12 | "properties": { 13 | "layout": { "type": "string" }, 14 | "panels": { 15 | "type": "array", 16 | "items": { 17 | "anyOf": [ 18 | { "type": "string" }, 19 | { "$ref": "layout" } 20 | ] 21 | } 22 | } 23 | }, 24 | "required": [ 25 | "layout", 26 | "panels" 27 | ] 28 | } 29 | } 30 | }, 31 | "tests": [ 32 | { 33 | "description": "empty object is valid", 34 | "data": {}, 35 | "valid": true 36 | }, 37 | { 38 | "description": "valid object", 39 | "data": { 40 | "layout": { 41 | "layout": "test1", 42 | "panels": [ 43 | "panel1", 44 | { 45 | "layout": "test2", 46 | "panels": [ 47 | "panel2", 48 | { 49 | "layout": "test3", 50 | "panels": [ 51 | "panel3" 52 | ] 53 | } 54 | ] 55 | } 56 | ] 57 | } 58 | }, 59 | "valid": true 60 | }, 61 | { 62 | "description": "invalid object", 63 | "data": { 64 | "layout": { 65 | "layout": "test1", 66 | "panels": [ 67 | "panel1", 68 | { 69 | "layout": "test2", 70 | "panels": [ 71 | "panel2", 72 | { 73 | "layout": "test3", 74 | "panels": [ 75 | 3 76 | ] 77 | } 78 | ] 79 | } 80 | ] 81 | } 82 | }, 83 | "valid": false 84 | } 85 | ] 86 | } 87 | ] 88 | -------------------------------------------------------------------------------- /src/test/resources/tincan/statement_base.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "id": "#statement_base", 4 | "type": "object", 5 | "required": ["actor", "verb", "object"], 6 | "oneOf": [ 7 | { 8 | "required": ["object"], 9 | "properties": { 10 | "object": {"$ref": "#activity"} 11 | } 12 | }, 13 | { 14 | "required": ["object"], 15 | "properties": { 16 | "object": {"not": {"$ref": "#activity"}}, 17 | "context": { 18 | "properties": { 19 | "revision": {"type": "null"}, 20 | "platform": {"type": "null"} 21 | } 22 | } 23 | } 24 | } 25 | ], 26 | "additionalProperties": false, 27 | "properties": { 28 | "objectType": {}, 29 | "id": { 30 | "type": "string", 31 | "format": "uuid" 32 | }, 33 | "actor": { 34 | "oneOf": [ 35 | {"$ref": "#agent"}, 36 | {"$ref": "#group"} 37 | ] 38 | }, 39 | "verb": {"$ref": "#verb"}, 40 | "object": {"$ref": "#statement_object"}, 41 | "result": {"$ref": "#result"}, 42 | "context": {"$ref": "#context"}, 43 | "timestamp": { 44 | "type": "string", 45 | "format": "iso_date" 46 | }, 47 | "stored": { 48 | "type": "string", 49 | "format": "iso_date" 50 | }, 51 | "authority": { 52 | "oneOf": [ 53 | {"$ref": "#agent"}, 54 | { 55 | "allOf": [{"$ref": "#anonymousgroup"}], 56 | "properties": { 57 | "member": { 58 | "type": "array", 59 | "items": {"$ref": "#agent"}, 60 | "minItems": 2, 61 | "maxItems": 2 62 | } 63 | } 64 | } 65 | ] 66 | }, 67 | "version": {"$ref": "#version"}, 68 | "attachments": { 69 | "type": "array", 70 | "items": {"$ref": "#attachment"} 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/internal/refs/ResolveStringConstraintsSpec.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema.internal.refs 2 | 3 | import com.eclipsesource.schema.drafts.{Version4, Version7} 4 | import com.eclipsesource.schema.{JsonSource, SchemaType, SchemaValue} 5 | import org.specs2.mutable.Specification 6 | import play.api.libs.json.{JsNumber, JsString} 7 | 8 | class ResolveStringConstraintsSpec extends Specification { 9 | 10 | "draft v4" should { 11 | 12 | import Version4._ 13 | val resolver = SchemaRefResolver(Version4) 14 | 15 | "resolve string constraints" in { 16 | val schema = 17 | JsonSource.schemaFromString( 18 | """{ 19 | | "type": "string", 20 | | "minLength": 1, 21 | | "maxLength": 10, 22 | | "pattern": "^(\\([0-9]{3}\\))?[0-9]{3}-[0-9]{4}$" 23 | |}""".stripMargin).get 24 | 25 | val scope = SchemaResolutionScope(schema) 26 | resolver.resolveFromRoot("#/minLength", scope) 27 | .map(_.resolved) must beRight[SchemaType](SchemaValue(JsNumber(1))) 28 | resolver.resolveFromRoot("#/maxLength", scope) 29 | .map(_.resolved) must beRight[SchemaType](SchemaValue(JsNumber(10))) 30 | resolver.resolveFromRoot("#/pattern", scope) 31 | .map(_.resolved) must beRight[SchemaType](SchemaValue(JsString("^(\\([0-9]{3}\\))?[0-9]{3}-[0-9]{4}$"))) 32 | } 33 | } 34 | 35 | "draft v7" should { 36 | 37 | import Version7._ 38 | val resolver = SchemaRefResolver(Version7) 39 | 40 | "resolve string constraints" in { 41 | val schema = 42 | JsonSource.schemaFromString( 43 | """{ 44 | | "type": "string", 45 | | "minLength": 1, 46 | | "maxLength": 10, 47 | | "pattern": "^(\\([0-9]{3}\\))?[0-9]{3}-[0-9]{4}$" 48 | |}""".stripMargin).get 49 | 50 | val scope = SchemaResolutionScope(schema) 51 | resolver.resolveFromRoot("#/minLength", scope) 52 | .map(_.resolved) must beRight[SchemaType](SchemaValue(JsNumber(1))) 53 | resolver.resolveFromRoot("#/maxLength", scope) 54 | .map(_.resolved) must beRight[SchemaType](SchemaValue(JsNumber(10))) 55 | resolver.resolveFromRoot("#/pattern", scope) 56 | .map(_.resolved) must beRight[SchemaType](SchemaValue(JsString("^(\\([0-9]{3}\\))?[0-9]{3}-[0-9]{4}$"))) 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/test/resources/ajv_tests/20_failing_to_parse_schema.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "Failing to parse schema with required property that is not an identifier (#20)", 4 | "schema": { 5 | "type": "object", 6 | "required": [ "a-b", "a'", "a\"" ] 7 | }, 8 | "tests": [ 9 | { 10 | "description": "valid", 11 | "data": { 12 | "a-b": "test", 13 | "a'": "test", 14 | "a\"": "test" 15 | }, 16 | "valid": true 17 | }, 18 | { 19 | "description": "invalid", 20 | "data": {}, 21 | "valid": false 22 | } 23 | ] 24 | }, 25 | { 26 | "description": "Failing to parse schema with required property that is not an identifier for many properties (#20)", 27 | "schema": { 28 | "type": "object", 29 | "required": [ 30 | "a-1", 31 | "a-2", 32 | "a-3", 33 | "a-4", 34 | "a-5", 35 | "a-6", 36 | "a-7", 37 | "a-8", 38 | "a-9", 39 | "a-10", 40 | "a-11", 41 | "a-12", 42 | "a-13", 43 | "a-14", 44 | "a-15", 45 | "a-16", 46 | "a-17", 47 | "a-18", 48 | "a-19", 49 | "a-20", 50 | "a-21", 51 | "a-22", 52 | "'", 53 | "\"" 54 | ] 55 | }, 56 | "tests": [ 57 | { 58 | "description": "valid", 59 | "data": { 60 | "a-1": "test", 61 | "a-2": "test", 62 | "a-3": "test", 63 | "a-4": "test", 64 | "a-5": "test", 65 | "a-6": "test", 66 | "a-7": "test", 67 | "a-8": "test", 68 | "a-9": "test", 69 | "a-10": "test", 70 | "a-11": "test", 71 | "a-12": "test", 72 | "a-13": "test", 73 | "a-14": "test", 74 | "a-15": "test", 75 | "a-16": "test", 76 | "a-17": "test", 77 | "a-18": "test", 78 | "a-19": "test", 79 | "a-20": "test", 80 | "a-21": "test", 81 | "a-22": "test", 82 | "'": "test", 83 | "\"": "test" 84 | }, 85 | "valid": true 86 | }, 87 | { 88 | "description": "invalid", 89 | "data": {}, 90 | "valid": false 91 | } 92 | ] 93 | } 94 | ] 95 | -------------------------------------------------------------------------------- /src/test/resources/draft4/items.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "a schema given for items", 4 | "schema": { 5 | "items": {"type": "integer"} 6 | }, 7 | "tests": [ 8 | { 9 | "description": "valid items", 10 | "data": [ 1, 2, 3 ], 11 | "valid": true 12 | }, 13 | { 14 | "description": "wrong type of items", 15 | "data": [1, "x"], 16 | "valid": false 17 | }, 18 | { 19 | "description": "ignores non-arrays", 20 | "data": {"foo" : "bar"}, 21 | "valid": true 22 | }, 23 | { 24 | "description": "JavaScript pseudo-array is valid", 25 | "data": { 26 | "0": "invalid", 27 | "length": 1 28 | }, 29 | "valid": true 30 | } 31 | ] 32 | }, 33 | { 34 | "description": "an array of schemas for items", 35 | "schema": { 36 | "items": [ 37 | {"type": "integer"}, 38 | {"type": "string"} 39 | ] 40 | }, 41 | "tests": [ 42 | { 43 | "description": "correct types", 44 | "data": [ 1, "foo" ], 45 | "valid": true 46 | }, 47 | { 48 | "description": "wrong types", 49 | "data": [ "foo", 1 ], 50 | "valid": false 51 | }, 52 | { 53 | "description": "incomplete array of items", 54 | "data": [ 1 ], 55 | "valid": true 56 | }, 57 | { 58 | "description": "array with additional items", 59 | "data": [ 1, "foo", true ], 60 | "valid": true 61 | }, 62 | { 63 | "description": "empty array", 64 | "data": [ ], 65 | "valid": true 66 | }, 67 | { 68 | "description": "JavaScript pseudo-array is valid", 69 | "data": { 70 | "0": "invalid", 71 | "1": "valid", 72 | "length": 2 73 | }, 74 | "valid": true 75 | } 76 | ] 77 | } 78 | ] 79 | -------------------------------------------------------------------------------- /src/test/resources/ajv_tests/19_required_many_properties.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "Required for many properties in inner level (#19)", 4 | "schema": { 5 | "type": "array", 6 | "items": { 7 | "type": "object", 8 | "required": [ 9 | "p1", 10 | "p2", 11 | "p3", 12 | "p4", 13 | "p5", 14 | "p6", 15 | "p7", 16 | "p8", 17 | "p9", 18 | "p10", 19 | "p11", 20 | "p12", 21 | "p13", 22 | "p14", 23 | "p15", 24 | "p16", 25 | "p17", 26 | "p18", 27 | "p19", 28 | "p20", 29 | "p21", 30 | "p22" 31 | ] 32 | } 33 | }, 34 | "tests": [ 35 | { 36 | "description": "valid", 37 | "data": [ 38 | { 39 | "p1": "test", 40 | "p2": "test", 41 | "p3": "test", 42 | "p4": "test", 43 | "p5": "test", 44 | "p6": "test", 45 | "p7": "test", 46 | "p8": "test", 47 | "p9": "test", 48 | "p10": "test", 49 | "p11": "test", 50 | "p12": "test", 51 | "p13": "test", 52 | "p14": "test", 53 | "p15": "test", 54 | "p16": "test", 55 | "p17": "test", 56 | "p18": "test", 57 | "p19": "test", 58 | "p20": "test", 59 | "p21": "test", 60 | "p22": "test" 61 | } 62 | ], 63 | "valid": true 64 | }, 65 | { 66 | "description": "invalid", 67 | "data": [ 68 | { 69 | "p2": "test", 70 | "p3": "test", 71 | "p4": "test", 72 | "p5": "test", 73 | "p6": "test", 74 | "p7": "test", 75 | "p8": "test", 76 | "p9": "test", 77 | "p10": "test", 78 | "p11": "test", 79 | "p12": "test", 80 | "p13": "test", 81 | "p14": "test", 82 | "p15": "test", 83 | "p16": "test", 84 | "p17": "test", 85 | "p18": "test", 86 | "p19": "test", 87 | "p20": "test", 88 | "p21": "test", 89 | "p22": "test" 90 | } 91 | ], 92 | "valid": false 93 | } 94 | ] 95 | } 96 | ] 97 | -------------------------------------------------------------------------------- /src/test/resources/ajv_tests/70_1_recursive_hash_ref_in_remote_ref.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "hash ref inside hash ref in remote ref (#70, was passing)", 4 | "schema": { 5 | "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0" 6 | }, 7 | "tests": [ 8 | { "data": 1, "valid": true, "description": "positive integer is valid" }, 9 | { "data": 0, "valid": true, "description": "zero is valid" }, 10 | { "data": -1, "valid": false, "description": "negative integer is invalid" } 11 | ] 12 | }, 13 | { 14 | "description": "hash ref inside hash ref in remote ref with id (#70, was passing)", 15 | "schema": { 16 | "id": "http://example.com/my_schema.json", 17 | "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0" 18 | }, 19 | "tests": [ 20 | { "data": 1, "valid": true, "description": "positive integer is valid" }, 21 | { "data": 0, "valid": true, "description": "zero is valid" }, 22 | { "data": -1, "valid": false, "description": "negative integer is invalid" } 23 | ] 24 | }, 25 | { 26 | "description": "local hash ref with remote hash ref without inner hash ref (#70, was passing)", 27 | "schema": { 28 | "definitions": { 29 | "a": { "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger" } 30 | }, 31 | "properties": { 32 | "b": { "$ref": "#/definitions/a" } 33 | } 34 | }, 35 | "tests": [ 36 | { "data": { "b": 1 }, "valid": true, "description": "positive integer is valid" }, 37 | { "data": { "b": 0 }, "valid": true, "description": "zero is valid" }, 38 | { "data": { "b": -1 }, "valid": false, "description": "negative integer is invalid" } 39 | ] 40 | }, 41 | { 42 | "description": "local hash ref with remote hash ref that has inner hash ref (#70)", 43 | "schema": { 44 | "definitions": { 45 | "a": { "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0" } 46 | }, 47 | "properties": { 48 | "b": { "$ref": "#/definitions/a" } 49 | } 50 | }, 51 | "tests": [ 52 | { "data": { "b": 1 }, "valid": true, "description": "positive integer is valid" }, 53 | { "data": { "b": 0 }, "valid": true, "description": "zero is valid" }, 54 | { "data": { "b": -1 }, "valid": false, "description": "negative integer is invalid" } 55 | ] 56 | } 57 | ] 58 | -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/internal/serialization/SchemaReadsSpec.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema.internal.serialization 2 | 3 | import com.eclipsesource.schema._ 4 | import com.eclipsesource.schema.{JsonSource, SchemaType, SchemaValue} 5 | import com.eclipsesource.schema.drafts.{Version4, Version7} 6 | import com.eclipsesource.schema.internal.draft7.constraints.{AnyConstraints7, NumberConstraints7, StringConstraints7} 7 | import org.specs2.mutable.Specification 8 | import play.api.libs.json.{JsArray, JsBoolean, JsString, Json} 9 | 10 | class SchemaReadsSpec extends Specification { 11 | 12 | 13 | "Schema Reads for draft 4" should { 14 | 15 | import Version4._ 16 | 17 | "not fail with match error (#104)" in { 18 | val schema = JsonSource.schemaFromString(""" 19 | |{ 20 | | "someProp": {"type": "sting"} 21 | |}""".stripMargin) 22 | 23 | schema.isSuccess must beTrue 24 | } 25 | 26 | "not be able to read boolean schema" in { 27 | Json.fromJson[SchemaType](JsBoolean(true)).isError must beTrue 28 | } 29 | 30 | "fail if exclusiveMinimum is not a boolean" in { 31 | val result = JsonSource.schemaFromString( 32 | """{ "exclusiveMinimum": 3 }""".stripMargin 33 | ) 34 | result.asEither must beLeft.like { case error => (error.toJson(0) \ "msgs").get.as[JsArray].value.head == 35 | JsString("error.expected.jsboolean") 36 | } 37 | } 38 | } 39 | 40 | "Schema Reads for draft 7" should { 41 | 42 | import Version7._ 43 | 44 | "read boolean schema" in { 45 | val booleanSchema = Json.fromJson[SchemaType](JsBoolean(true)).get 46 | booleanSchema must beEqualTo(SchemaValue(JsBoolean(true))) 47 | } 48 | 49 | "read compound type with error" in { 50 | val schema = JsonSource.schemaFromString(""" 51 | |{ 52 | | "type": ["number", "string"] 53 | |}""".stripMargin) 54 | 55 | schema.isSuccess must beTrue 56 | schema.asOpt must beSome.which( 57 | _ == CompoundSchemaType( 58 | Seq( 59 | SchemaNumber(NumberConstraints7().copy(any = AnyConstraints7().copy(schemaType = Some("number")))), 60 | SchemaString(StringConstraints7().copy(any = AnyConstraints7().copy(schemaType = Some("string")))) 61 | ) 62 | ) 63 | ) 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/test/resources/draft7/propertyNames.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "propertyNames validation", 4 | "schema": { 5 | "propertyNames": {"maxLength": 3} 6 | }, 7 | "tests": [ 8 | { 9 | "description": "all property names valid", 10 | "data": { 11 | "f": {}, 12 | "foo": {} 13 | }, 14 | "valid": true 15 | }, 16 | { 17 | "description": "some property names invalid", 18 | "data": { 19 | "foo": {}, 20 | "foobar": {} 21 | }, 22 | "valid": false 23 | }, 24 | { 25 | "description": "object without properties is valid", 26 | "data": {}, 27 | "valid": true 28 | }, 29 | { 30 | "description": "ignores arrays", 31 | "data": [1, 2, 3, 4], 32 | "valid": true 33 | }, 34 | { 35 | "description": "ignores strings", 36 | "data": "foobar", 37 | "valid": true 38 | }, 39 | { 40 | "description": "ignores other non-objects", 41 | "data": 12, 42 | "valid": true 43 | } 44 | ] 45 | }, 46 | { 47 | "description": "propertyNames with boolean schema true", 48 | "schema": {"propertyNames": true}, 49 | "tests": [ 50 | { 51 | "description": "object with any properties is valid", 52 | "data": {"foo": 1}, 53 | "valid": true 54 | }, 55 | { 56 | "description": "empty object is valid", 57 | "data": {}, 58 | "valid": true 59 | } 60 | ] 61 | }, 62 | { 63 | "description": "propertyNames with boolean schema false", 64 | "schema": {"propertyNames": false}, 65 | "tests": [ 66 | { 67 | "description": "object with any properties is invalid", 68 | "data": {"foo": 1}, 69 | "valid": false 70 | }, 71 | { 72 | "description": "empty object is valid", 73 | "data": {}, 74 | "valid": true 75 | } 76 | ] 77 | } 78 | ] 79 | -------------------------------------------------------------------------------- /src/main/scala/com/eclipsesource/schema/JsonSource.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import java.io.InputStream 4 | import java.net.URL 5 | 6 | import play.api.libs.json._ 7 | 8 | import scala.io.Source 9 | import scala.util.{Failure, Success, Try} 10 | 11 | /** 12 | * Convenience class for obtaining JSON values and Schemas from different sources. 13 | */ 14 | object JsonSource { 15 | 16 | /** 17 | * Tries to parse the given JSON string. 18 | * 19 | * @param json the input string 20 | * @return the result wrapped in a Try 21 | */ 22 | def fromString(json: String): Try[JsValue] = Try { Json.parse(json) } 23 | 24 | /** 25 | * Fetches the content from the given URL and tries to parse the result as JSON. 26 | * 27 | * @param url the URL at which a JSON instance is expected 28 | * @return the result wrapped in a Try 29 | */ 30 | def fromUrl(url: URL): Try[JsValue] = Try { 31 | val source = Source.fromURL(url) 32 | try source.getLines.mkString("\n") finally source.close 33 | }.flatMap(fromString) 34 | 35 | 36 | /** 37 | * Tries to parse the given JSON string and convert it to a JSON schema. 38 | * 39 | * @param json the input string 40 | * @return the result wrapped in a JsResult 41 | */ 42 | def schemaFromString(json: String)(implicit reads: Reads[SchemaType]): JsResult[SchemaType] = 43 | Json.fromJson[SchemaType](Json.parse(json)) 44 | 45 | /** 46 | * Tries to read from the given InputStream and convert the result to a JSON schema. 47 | * Closes the stream in any case. 48 | * 49 | * @param inputStream the input string 50 | * @return the read result wrapped in a JsResult 51 | */ 52 | def schemaFromStream(inputStream: InputStream)(implicit reads: Reads[SchemaType]): JsResult[SchemaType] = 53 | try Json.fromJson[SchemaType](Json.parse(inputStream)) finally inputStream.close() 54 | 55 | /** 56 | * Fetches the content from the given URL and tries to parse the result as a JSON schema. 57 | * 58 | * @param url the URL at which a JSON schema is expected 59 | * @return the result wrapped in a JsResult 60 | */ 61 | def schemaFromUrl(url: URL)(implicit reads: Reads[SchemaType]): JsResult[SchemaType] = { 62 | for { 63 | schemaJson <- JsonSource.fromUrl(url) match { 64 | case Success(json) => JsSuccess(json) 65 | case Failure(throwable) => JsError(throwable.getMessage) 66 | } 67 | schema <- Json.fromJson[SchemaType](schemaJson) 68 | } yield schema 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/test/resources/draft4/not.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "not", 4 | "schema": { 5 | "not": {"type": "integer"} 6 | }, 7 | "tests": [ 8 | { 9 | "description": "allowed", 10 | "data": "foo", 11 | "valid": true 12 | }, 13 | { 14 | "description": "disallowed", 15 | "data": 1, 16 | "valid": false 17 | } 18 | ] 19 | }, 20 | { 21 | "description": "not multiple types", 22 | "schema": { 23 | "not": {"type": ["integer", "boolean"]} 24 | }, 25 | "tests": [ 26 | { 27 | "description": "valid", 28 | "data": "foo", 29 | "valid": true 30 | }, 31 | { 32 | "description": "mismatch", 33 | "data": 1, 34 | "valid": false 35 | }, 36 | { 37 | "description": "other mismatch", 38 | "data": true, 39 | "valid": false 40 | } 41 | ] 42 | }, 43 | { 44 | "description": "not more complex schema", 45 | "schema": { 46 | "not": { 47 | "type": "object", 48 | "properties": { 49 | "foo": { 50 | "type": "string" 51 | } 52 | } 53 | } 54 | }, 55 | "tests": [ 56 | { 57 | "description": "match", 58 | "data": 1, 59 | "valid": true 60 | }, 61 | { 62 | "description": "other match", 63 | "data": {"foo": 1}, 64 | "valid": true 65 | }, 66 | { 67 | "description": "mismatch", 68 | "data": {"foo": "bar"}, 69 | "valid": false 70 | } 71 | ] 72 | }, 73 | { 74 | "description": "forbidden property", 75 | "schema": { 76 | "properties": { 77 | "foo": { 78 | "not": {} 79 | } 80 | } 81 | }, 82 | "tests": [ 83 | { 84 | "description": "property present", 85 | "data": {"foo": 1, "bar": 2}, 86 | "valid": false 87 | }, 88 | { 89 | "description": "property absent", 90 | "data": {"bar": 1, "baz": 2}, 91 | "valid": true 92 | } 93 | ] 94 | } 95 | 96 | ] 97 | -------------------------------------------------------------------------------- /src/main/resources/messages.txt: -------------------------------------------------------------------------------- 1 | obj.missing.prop.dep = Missing property dependency {0}. 2 | obj.max.props = Too many properties. {0} properties found, but only a maximum of {1} is allowed. 3 | obj.min.props = Found {0} properties, but a minimum of {1} is required. 4 | obj.additional.props = Additional properties are not allowed, but found properties {0}. 5 | obj.required.prop = Property {0} missing. 6 | 7 | arr.max = Too many items. {0} items found, but only a maximum of {1} is allowed. 8 | arr.min = Found {0} items, but a minimum of {1} is required. 9 | arr.dups = Found duplicates. 10 | arr.out.of.bounds = Array index {0} out of bounds. 11 | arr.invalid.index = Invalid array index {0}. 12 | 13 | str.pattern = ''{0}'' does not match pattern ''{1}''. 14 | str.invalid.pattern = Invalid pattern ''{0}''. 15 | str.min.length = ''{0}'' does not match minimum length of {1}. 16 | str.max.length = ''{0}'' exceeds maximum length of {1}. 17 | str.format = ''{0}'' does not match format {1}. 18 | 19 | num.multiple.of = {0} is not a multiple of {1}. 20 | num.max = {0} exceeds maximum value of {1}. 21 | num.max.exclusive = {0} exceeds exclusive maximum value of {1}. 22 | num.min = {0} is smaller than required minimum value of {1}. 23 | num.min.exclusive = {0} is smaller than required exclusive minimum value of {1}. 24 | 25 | any.not = Instance matches schema although it must not. 26 | any.all = Instance does not match all schemas. 27 | any.any = Instance does not match any of the schemas. 28 | any.one.of.none = Instance does not match any schema. 29 | any.one.of.many = Instance matches more than one schema. 30 | any.enum = Instance is invalid enum value. 31 | any.const = Instance does not match const value. 32 | 33 | comp.no.schema = No schema applicable. 34 | 35 | err.expected.type = Wrong type. Expected {0}, was {1}. 36 | err.unresolved.ref = Could not resolve ref {0}. 37 | err.prop.not.found = Could not find property {0}. 38 | err.ref.expected = Expected to find ref at {0}. 39 | err.res.scope.id.empty = Resolution scope ID must not be empty. 40 | err.parse.json = Could not parse JSON. 41 | err.max.depth = Maximum recursion depth reached. 42 | err.dependencies.not.found = Dependency not found. 43 | err.definitions.not.found = Definition not found. 44 | err.patternProperties.not.found = Pattern Properties not found. 45 | err.false.schema = Boolean false schema encountered. 46 | err.contains = Array does not contain valid item. 47 | err.if.then.else = Conditional validation failed. 48 | -------------------------------------------------------------------------------- /src/main/scala/com/eclipsesource/schema/internal/draft4/constraints/ArrayConstraints4.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema.internal.draft4.constraints 2 | 3 | import com.eclipsesource.schema.internal.Keywords 4 | import com.eclipsesource.schema.{SchemaArray, SchemaResolutionContext, SchemaTuple, SchemaType, SchemaValue} 5 | import com.eclipsesource.schema.internal.constraints.Constraints.{AnyConstraints, ArrayConstraints, Constraint} 6 | import com.eclipsesource.schema.internal.validation.VA 7 | import com.eclipsesource.schema.internal.validators.TupleValidators 8 | import com.osinka.i18n.Lang 9 | import play.api.libs.json.{JsBoolean, JsNumber, JsValue} 10 | import scalaz.Success 11 | 12 | case class ArrayConstraints4(maxItems: Option[Int] = None, 13 | minItems: Option[Int] = None, 14 | additionalItems: Option[SchemaType] = None, 15 | unique: Option[Boolean] = None, 16 | any: AnyConstraints = AnyConstraints4() 17 | ) extends ArrayConstraints with Constraint { 18 | 19 | import com.eclipsesource.schema.internal.validators.ArrayConstraintValidators._ 20 | 21 | override def subSchemas: Set[SchemaType] = 22 | additionalItems.map(Set(_)).getOrElse(Set.empty) ++ any.subSchemas 23 | 24 | override def validate(schema: SchemaType, json: JsValue, resolutionContext: SchemaResolutionContext) 25 | (implicit lang: Lang): VA[JsValue] = { 26 | 27 | val reader = for { 28 | minItemsRule <- validateMinItems(minItems) 29 | maxItemsRule <- validateMaxItems(maxItems) 30 | uniqueRule <- validateUniqueness(unique) 31 | } yield { 32 | minItemsRule |+| maxItemsRule |+| uniqueRule 33 | } 34 | 35 | schema match { 36 | case t: SchemaTuple => 37 | TupleValidators 38 | .validateTuple(additionalItems, t) 39 | .flatMap(x => reader.map(f => f |+| x)) 40 | .run(resolutionContext) 41 | .repath(_.compose(resolutionContext.instancePath)) 42 | .validate(json) 43 | case _: SchemaArray => 44 | reader.run(resolutionContext) 45 | .repath(_.compose(resolutionContext.instancePath)) 46 | .validate(json) 47 | case _ => Success(json) 48 | } 49 | } 50 | 51 | def resolvePath(path: String): Option[SchemaType] = path match { 52 | case Keywords.Array.MinItems => minItems.map(min => SchemaValue(JsNumber(min))) 53 | case Keywords.Array.MaxItems => maxItems.map(max => SchemaValue(JsNumber(max))) 54 | case Keywords.Array.AdditionalItems => additionalItems 55 | case Keywords.Array.UniqueItems => unique.map(u => SchemaValue(JsBoolean(u))) 56 | case other => any.resolvePath(other) 57 | } 58 | } -------------------------------------------------------------------------------- /src/test/resources/draft7/const.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "const validation", 4 | "schema": {"const": 2}, 5 | "tests": [ 6 | { 7 | "description": "same value is valid", 8 | "data": 2, 9 | "valid": true 10 | }, 11 | { 12 | "description": "another value is invalid", 13 | "data": 5, 14 | "valid": false 15 | }, 16 | { 17 | "description": "another type is invalid", 18 | "data": "a", 19 | "valid": false 20 | } 21 | ] 22 | }, 23 | { 24 | "description": "const with object", 25 | "schema": {"const": {"foo": "bar", "baz": "bax"}}, 26 | "tests": [ 27 | { 28 | "description": "same object is valid", 29 | "data": {"foo": "bar", "baz": "bax"}, 30 | "valid": true 31 | }, 32 | { 33 | "description": "same object with different property order is valid", 34 | "data": {"baz": "bax", "foo": "bar"}, 35 | "valid": true 36 | }, 37 | { 38 | "description": "another object is invalid", 39 | "data": {"foo": "bar"}, 40 | "valid": false 41 | }, 42 | { 43 | "description": "another type is invalid", 44 | "data": [1, 2], 45 | "valid": false 46 | } 47 | ] 48 | }, 49 | { 50 | "description": "const with array", 51 | "schema": {"const": [{ "foo": "bar" }]}, 52 | "tests": [ 53 | { 54 | "description": "same array is valid", 55 | "data": [{"foo": "bar"}], 56 | "valid": true 57 | }, 58 | { 59 | "description": "another array item is invalid", 60 | "data": [2], 61 | "valid": false 62 | }, 63 | { 64 | "description": "array with additional items is invalid", 65 | "data": [1, 2, 3], 66 | "valid": false 67 | } 68 | ] 69 | }, 70 | { 71 | "description": "const with null", 72 | "schema": {"const": null}, 73 | "tests": [ 74 | { 75 | "description": "null is valid", 76 | "data": null, 77 | "valid": true 78 | }, 79 | { 80 | "description": "not null is invalid", 81 | "data": 0, 82 | "valid": false 83 | } 84 | ] 85 | } 86 | ] 87 | -------------------------------------------------------------------------------- /src/test/resources/ajv_tests/oneOf.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "oneOf with one of schemas empty", 4 | "schema": { 5 | "oneOf": [ 6 | { "type": "number" }, 7 | {} 8 | ] 9 | }, 10 | "tests": [ 11 | { 12 | "description": "string is valid", 13 | "data": "foo", 14 | "valid": true 15 | }, 16 | { 17 | "description": "number is invalid", 18 | "data": 123, 19 | "valid": false 20 | } 21 | ] 22 | }, 23 | { 24 | "description": "oneOf with required", 25 | "schema": { 26 | "type": "object", 27 | "oneOf": [ 28 | { "required": ["foo", "bar"] }, 29 | { "required": ["foo", "baz"] } 30 | ] 31 | }, 32 | "tests": [ 33 | { 34 | "description": "object with foo and bar is valid", 35 | "data": {"foo": 1, "bar": 2}, 36 | "valid": true 37 | }, 38 | { 39 | "description": "object with foo and baz is valid", 40 | "data": {"foo": 1, "baz": 3}, 41 | "valid": true 42 | }, 43 | { 44 | "description": "object with foo, bar and baz is invalid", 45 | "data": {"foo": 1, "bar": 2, "baz" : 3}, 46 | "valid": false 47 | } 48 | ] 49 | }, 50 | { 51 | "description": "oneOf with required with 20+ properties", 52 | "schema": { 53 | "type": "object", 54 | "oneOf": [ 55 | { "required": ["foo", "bar"] }, 56 | { 57 | "required": [ 58 | "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", 59 | "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", 60 | "u", "v", "w", "x", "y", "z" 61 | ] 62 | } 63 | ] 64 | }, 65 | "tests": [ 66 | { 67 | "description": "object with foo and bar is valid", 68 | "data": {"foo": 1, "bar": 2}, 69 | "valid": true 70 | }, 71 | { 72 | "description": "object with a, b, c, ... properties is valid", 73 | "data": { 74 | "a": 0, "b": 0, "c": 0, "d": 0, "e": 0, "f": 0, "g": 0, "h": 0, "i": 0, "j": 0, 75 | "k": 0, "l": 0, "m": 0, "n": 0, "o": 0, "p": 0, "q": 0, "r": 0, "s": 0, "t": 0, 76 | "u": 0, "v": 0, "w": 0, "x": 0, "y": 0, "z": 0 77 | }, 78 | "valid": true 79 | }, 80 | { 81 | "description": "object with foo, bar and a, b, c ... is invalid", 82 | "data": { 83 | "a": 0, "b": 0, "c": 0, "d": 0, "e": 0, "f": 0, "g": 0, "h": 0, "i": 0, "j": 0, 84 | "k": 0, "l": 0, "m": 0, "n": 0, "o": 0, "p": 0, "q": 0, "r": 0, "s": 0, "t": 0, 85 | "u": 0, "v": 0, "w": 0, "x": 0, "y": 0, "z": 0, 86 | "foo": 1, "bar": 2 87 | }, 88 | "valid": false 89 | } 90 | ] 91 | } 92 | ] 93 | -------------------------------------------------------------------------------- /src/test/resources/draft4/additionalItems.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "additionalItems as schema", 4 | "schema": { 5 | "items": [{}], 6 | "additionalItems": {"type": "integer"} 7 | }, 8 | "tests": [ 9 | { 10 | "description": "additional items match schema", 11 | "data": [ null, 2, 3, 4 ], 12 | "valid": true 13 | }, 14 | { 15 | "description": "additional items do not match schema", 16 | "data": [ null, 2, 3, "foo" ], 17 | "valid": false 18 | } 19 | ] 20 | }, 21 | { 22 | "description": "items is schema, no additionalItems", 23 | "schema": { 24 | "items": {}, 25 | "additionalItems": false 26 | }, 27 | "tests": [ 28 | { 29 | "description": "all items match schema", 30 | "data": [ 1, 2, 3, 4, 5 ], 31 | "valid": true 32 | } 33 | ] 34 | }, 35 | { 36 | "description": "array of items with no additionalItems", 37 | "schema": { 38 | "items": [{}, {}, {}], 39 | "additionalItems": false 40 | }, 41 | "tests": [ 42 | { 43 | "description": "fewer number of items present", 44 | "data": [ 1, 2 ], 45 | "valid": true 46 | }, 47 | { 48 | "description": "equal number of items present", 49 | "data": [ 1, 2, 3 ], 50 | "valid": true 51 | }, 52 | { 53 | "description": "additional items are not permitted", 54 | "data": [ 1, 2, 3, 4 ], 55 | "valid": false 56 | } 57 | ] 58 | }, 59 | { 60 | "description": "additionalItems as false without items", 61 | "schema": {"additionalItems": false}, 62 | "tests": [ 63 | { 64 | "description": 65 | "items defaults to empty schema so everything is valid", 66 | "data": [ 1, 2, 3, 4, 5 ], 67 | "valid": true 68 | }, 69 | { 70 | "description": "ignores non-arrays", 71 | "data": {"foo" : "bar"}, 72 | "valid": true 73 | } 74 | ] 75 | }, 76 | { 77 | "description": "additionalItems are allowed by default", 78 | "schema": {"items": [{"type": "integer"}]}, 79 | "tests": [ 80 | { 81 | "description": "only the first item is validated", 82 | "data": [1, "foo", false], 83 | "valid": true 84 | } 85 | ] 86 | } 87 | ] 88 | -------------------------------------------------------------------------------- /src/test/resources/draft7/additionalItems.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "additionalItems as schema", 4 | "schema": { 5 | "items": [{}], 6 | "additionalItems": {"type": "integer"} 7 | }, 8 | "tests": [ 9 | { 10 | "description": "additional items match schema", 11 | "data": [ null, 2, 3, 4 ], 12 | "valid": true 13 | }, 14 | { 15 | "description": "additional items do not match schema", 16 | "data": [ null, 2, 3, "foo" ], 17 | "valid": false 18 | } 19 | ] 20 | }, 21 | { 22 | "description": "items is schema, no additionalItems", 23 | "schema": { 24 | "items": {}, 25 | "additionalItems": false 26 | }, 27 | "tests": [ 28 | { 29 | "description": "all items match schema", 30 | "data": [ 1, 2, 3, 4, 5 ], 31 | "valid": true 32 | } 33 | ] 34 | }, 35 | { 36 | "description": "array of items with no additionalItems", 37 | "schema": { 38 | "items": [{}, {}, {}], 39 | "additionalItems": false 40 | }, 41 | "tests": [ 42 | { 43 | "description": "fewer number of items present", 44 | "data": [ 1, 2 ], 45 | "valid": true 46 | }, 47 | { 48 | "description": "equal number of items present", 49 | "data": [ 1, 2, 3 ], 50 | "valid": true 51 | }, 52 | { 53 | "description": "additional items are not permitted", 54 | "data": [ 1, 2, 3, 4 ], 55 | "valid": false 56 | } 57 | ] 58 | }, 59 | { 60 | "description": "additionalItems as false without items", 61 | "schema": {"additionalItems": false}, 62 | "tests": [ 63 | { 64 | "description": 65 | "items defaults to empty schema so everything is valid", 66 | "data": [ 1, 2, 3, 4, 5 ], 67 | "valid": true 68 | }, 69 | { 70 | "description": "ignores non-arrays", 71 | "data": {"foo" : "bar"}, 72 | "valid": true 73 | } 74 | ] 75 | }, 76 | { 77 | "description": "additionalItems are allowed by default", 78 | "schema": {"items": [{"type": "integer"}]}, 79 | "tests": [ 80 | { 81 | "description": "only the first item is validated", 82 | "data": [1, "foo", false], 83 | "valid": true 84 | } 85 | ] 86 | } 87 | ] 88 | -------------------------------------------------------------------------------- /src/test/resources/draft4/uniqueItems.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "uniqueItems validation", 4 | "schema": {"uniqueItems": true}, 5 | "tests": [ 6 | { 7 | "description": "unique array of integers is valid", 8 | "data": [1, 2], 9 | "valid": true 10 | }, 11 | { 12 | "description": "non-unique array of integers is invalid", 13 | "data": [1, 1], 14 | "valid": false 15 | }, 16 | { 17 | "description": "numbers are unique if mathematically unequal", 18 | "data": [1.0, 1.00, 1], 19 | "valid": false 20 | }, 21 | { 22 | "description": "unique array of objects is valid", 23 | "data": [{"foo": "bar"}, {"foo": "baz"}], 24 | "valid": true 25 | }, 26 | { 27 | "description": "non-unique array of objects is invalid", 28 | "data": [{"foo": "bar"}, {"foo": "bar"}], 29 | "valid": false 30 | }, 31 | { 32 | "description": "unique array of nested objects is valid", 33 | "data": [ 34 | {"foo": {"bar" : {"baz" : true}}}, 35 | {"foo": {"bar" : {"baz" : false}}} 36 | ], 37 | "valid": true 38 | }, 39 | { 40 | "description": "non-unique array of nested objects is invalid", 41 | "data": [ 42 | {"foo": {"bar" : {"baz" : true}}}, 43 | {"foo": {"bar" : {"baz" : true}}} 44 | ], 45 | "valid": false 46 | }, 47 | { 48 | "description": "unique array of arrays is valid", 49 | "data": [["foo"], ["bar"]], 50 | "valid": true 51 | }, 52 | { 53 | "description": "non-unique array of arrays is invalid", 54 | "data": [["foo"], ["foo"]], 55 | "valid": false 56 | }, 57 | { 58 | "description": "1 and true are unique", 59 | "data": [1, true], 60 | "valid": true 61 | }, 62 | { 63 | "description": "0 and false are unique", 64 | "data": [0, false], 65 | "valid": true 66 | }, 67 | { 68 | "description": "unique heterogeneous types are valid", 69 | "data": [{}, [1], true, null, 1], 70 | "valid": true 71 | }, 72 | { 73 | "description": "non-unique heterogeneous types are invalid", 74 | "data": [{}, [1], true, null, {}, 1], 75 | "valid": false 76 | } 77 | ] 78 | } 79 | ] 80 | -------------------------------------------------------------------------------- /src/test/resources/draft7/uniqueItems.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "uniqueItems validation", 4 | "schema": {"uniqueItems": true}, 5 | "tests": [ 6 | { 7 | "description": "unique array of integers is valid", 8 | "data": [1, 2], 9 | "valid": true 10 | }, 11 | { 12 | "description": "non-unique array of integers is invalid", 13 | "data": [1, 1], 14 | "valid": false 15 | }, 16 | { 17 | "description": "numbers are unique if mathematically unequal", 18 | "data": [1.0, 1.00, 1], 19 | "valid": false 20 | }, 21 | { 22 | "description": "unique array of objects is valid", 23 | "data": [{"foo": "bar"}, {"foo": "baz"}], 24 | "valid": true 25 | }, 26 | { 27 | "description": "non-unique array of objects is invalid", 28 | "data": [{"foo": "bar"}, {"foo": "bar"}], 29 | "valid": false 30 | }, 31 | { 32 | "description": "unique array of nested objects is valid", 33 | "data": [ 34 | {"foo": {"bar" : {"baz" : true}}}, 35 | {"foo": {"bar" : {"baz" : false}}} 36 | ], 37 | "valid": true 38 | }, 39 | { 40 | "description": "non-unique array of nested objects is invalid", 41 | "data": [ 42 | {"foo": {"bar" : {"baz" : true}}}, 43 | {"foo": {"bar" : {"baz" : true}}} 44 | ], 45 | "valid": false 46 | }, 47 | { 48 | "description": "unique array of arrays is valid", 49 | "data": [["foo"], ["bar"]], 50 | "valid": true 51 | }, 52 | { 53 | "description": "non-unique array of arrays is invalid", 54 | "data": [["foo"], ["foo"]], 55 | "valid": false 56 | }, 57 | { 58 | "description": "1 and true are unique", 59 | "data": [1, true], 60 | "valid": true 61 | }, 62 | { 63 | "description": "0 and false are unique", 64 | "data": [0, false], 65 | "valid": true 66 | }, 67 | { 68 | "description": "unique heterogeneous types are valid", 69 | "data": [{}, [1], true, null, 1], 70 | "valid": true 71 | }, 72 | { 73 | "description": "non-unique heterogeneous types are invalid", 74 | "data": [{}, [1], true, null, {}, 1], 75 | "valid": false 76 | } 77 | ] 78 | } 79 | ] 80 | -------------------------------------------------------------------------------- /src/main/scala/com/eclipsesource/schema/internal/draft7/constraints/ArrayConstraints7.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema.internal.draft7.constraints 2 | 3 | import com.eclipsesource.schema.internal.Keywords 4 | import com.eclipsesource.schema.internal.constraints.Constraints.{AnyConstraints, ArrayConstraints, Constraint} 5 | import com.eclipsesource.schema.internal.validation.VA 6 | import com.eclipsesource.schema.internal.validators.TupleValidators 7 | import com.eclipsesource.schema.{SchemaArray, SchemaResolutionContext, SchemaTuple, SchemaType, SchemaValue} 8 | import com.osinka.i18n.Lang 9 | import play.api.libs.json._ 10 | import scalaz.Success 11 | import scalaz.std.option._ 12 | import scalaz.std.set._ 13 | import scalaz.syntax.semigroup._ 14 | 15 | case class ArrayConstraints7(maxItems: Option[Int] = None, 16 | minItems: Option[Int] = None, 17 | additionalItems: Option[SchemaType] = None, 18 | contains: Option[SchemaType] = None, 19 | unique: Option[Boolean] = None, 20 | any: AnyConstraints = AnyConstraints7() 21 | ) extends ArrayConstraints with Constraint { 22 | 23 | override def subSchemas: Set[SchemaType] = 24 | (additionalItems.map(Set(_)) |+| contains.map(Set(_))).getOrElse(Set.empty) ++ any.subSchemas 25 | 26 | 27 | import com.eclipsesource.schema.internal.validators.ArrayConstraintValidators._ 28 | 29 | override def validate(schema: SchemaType, json: JsValue, resolutionContext: SchemaResolutionContext) 30 | (implicit lang: Lang): VA[JsValue] = { 31 | 32 | val reader = for { 33 | minItemsRule <- validateMinItems(minItems) 34 | maxItemsRule <- validateMaxItems(maxItems) 35 | uniqueRule <- validateUniqueness(unique) 36 | containsRule <- validateContains(contains) 37 | } yield { 38 | minItemsRule |+| maxItemsRule |+| uniqueRule |+| containsRule 39 | } 40 | 41 | schema match { 42 | case t: SchemaTuple => 43 | TupleValidators 44 | .validateTuple(additionalItems, t) 45 | .flatMap(x => reader.map(f => f |+| x)) 46 | .run(resolutionContext) 47 | .repath(_.compose(resolutionContext.instancePath)) 48 | .validate(json) 49 | case _: SchemaArray => 50 | reader.run(resolutionContext) 51 | .repath(_.compose(resolutionContext.instancePath)) 52 | .validate(json) 53 | case _ => Success(json) 54 | } 55 | } 56 | 57 | override def resolvePath(path: String): Option[SchemaType] = path match { 58 | case Keywords.Array.MinItems => minItems.map(min => SchemaValue(JsNumber(min))) 59 | case Keywords.Array.MaxItems => maxItems.map(max => SchemaValue(JsNumber(max))) 60 | case Keywords.Array.AdditionalItems => additionalItems 61 | case Keywords.Array.UniqueItems => unique.map(u => SchemaValue(JsBoolean(u))) 62 | case "contains" => contains 63 | case other => any.resolvePath(other) 64 | } 65 | } -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/internal/refs/ResolveArrayConstraintsSpec.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema.internal.refs 2 | 3 | import com.eclipsesource.schema.drafts.{Version4, Version7} 4 | import com.eclipsesource.schema.internal.constraints.Constraints.Minimum 5 | import com.eclipsesource.schema.internal.draft7.constraints.NumberConstraints7 6 | import com.eclipsesource.schema.{JsonSource, SchemaNumber, SchemaType, SchemaValue} 7 | import org.specs2.mutable.Specification 8 | import play.api.libs.json.{JsBoolean, JsNumber} 9 | 10 | class ResolveArrayConstraintsSpec extends Specification { 11 | 12 | "draft v4" should { 13 | 14 | import Version4._ 15 | val resolver = SchemaRefResolver(Version4) 16 | 17 | "resolve array constraints" in { 18 | val schema = JsonSource.schemaFromString( 19 | """{ 20 | | "items": { 21 | | "type": "integer" 22 | | }, 23 | | "minItems": 42, 24 | | "maxItems": 99, 25 | | "additionalItems": false, 26 | | "uniqueItems": false 27 | |}""".stripMargin).get 28 | 29 | val scope = SchemaResolutionScope(schema) 30 | resolver.resolveFromRoot("#/minItems", scope).map(_.resolved) must beRight[SchemaType](SchemaValue(JsNumber(42))) 31 | resolver.resolveFromRoot("#/maxItems", scope).map(_.resolved) must beRight[SchemaType](SchemaValue(JsNumber(99))) 32 | resolver.resolveFromRoot("#/additionalItems", scope).map(_.resolved) must beRight[SchemaType](SchemaValue(JsBoolean(false))) 33 | resolver.resolveFromRoot("#/uniqueItems", scope).map(_.resolved) must beRight[SchemaType](SchemaValue(JsBoolean(false))) 34 | } 35 | } 36 | 37 | "draft v7" should { 38 | 39 | import Version7._ 40 | val resolver = SchemaRefResolver(Version7) 41 | 42 | "resolve array constraints" in { 43 | val schema = JsonSource.schemaFromString( 44 | """{ 45 | | "items": { 46 | | "type": "integer" 47 | | }, 48 | | "minItems": 42, 49 | | "maxItems": 99, 50 | | "additionalItems": false, 51 | | "uniqueItems": false, 52 | | "contains": { 53 | | "minimum": 55 54 | | } 55 | |}""".stripMargin).get 56 | 57 | val scope = SchemaResolutionScope(schema) 58 | resolver.resolveFromRoot("#/minItems", scope).map(_.resolved) must beRight[SchemaType](SchemaValue(JsNumber(42))) 59 | resolver.resolveFromRoot("#/maxItems", scope).map(_.resolved) must beRight[SchemaType](SchemaValue(JsNumber(99))) 60 | resolver.resolveFromRoot("#/additionalItems", scope).map(_.resolved) must beRight[SchemaType](SchemaValue(JsBoolean(false))) 61 | resolver.resolveFromRoot("#/uniqueItems", scope).map(_.resolved) must beRight[SchemaType](SchemaValue(JsBoolean(false))) 62 | resolver.resolveFromRoot("#/contains", scope).map(_.resolved) must beRight[SchemaType]( 63 | SchemaNumber(NumberConstraints7(Some(Minimum(BigDecimal(55), Some(false))))) 64 | ) 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/scala/com/eclipsesource/schema/internal/draft4/constraints/AnyConstraints4.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema.internal.draft4.constraints 2 | 3 | import com.eclipsesource.schema.{SchemaMap, SchemaProp, SchemaResolutionContext, SchemaSeq, SchemaType, SchemaValue} 4 | import com.eclipsesource.schema.internal.Keywords 5 | import com.eclipsesource.schema.internal.constraints.Constraints.AnyConstraints 6 | import com.eclipsesource.schema.internal.validation.{Rule, VA} 7 | import com.osinka.i18n.Lang 8 | import play.api.libs.json.{JsArray, JsString, JsValue} 9 | import scalaz.std.option._ 10 | import scalaz.std.set._ 11 | import scalaz.syntax.semigroup._ 12 | 13 | case class AnyConstraints4(schemaType: Option[String] = None, 14 | allOf: Option[Seq[SchemaType]] = None, 15 | anyOf: Option[Seq[SchemaType]] = None, 16 | oneOf: Option[Seq[SchemaType]] = None, 17 | definitions: Option[Map[String, SchemaType]] = None, 18 | enum: Option[Seq[JsValue]] = None, 19 | not: Option[SchemaType] = None, 20 | description: Option[String] = None, 21 | id: Option[String] = None 22 | ) 23 | extends AnyConstraints { 24 | 25 | override def subSchemas: Set[SchemaType] = 26 | (definitions.map(_.values.toSet) |+| allOf.map(_.toSet) |+| anyOf.map(_.toSet) |+| oneOf.map(_.toSet)) 27 | .getOrElse(Set.empty[SchemaType]) 28 | 29 | override def resolvePath(path: String): Option[SchemaType] = path match { 30 | case Keywords.Any.Type => schemaType.map(t => SchemaValue(JsString(t))) 31 | case Keywords.Any.AllOf => allOf.map(types => SchemaSeq(types)) 32 | case Keywords.Any.AnyOf => anyOf.map(types => SchemaSeq(types)) 33 | case Keywords.Any.OneOf => oneOf.map(types => SchemaSeq(types)) 34 | case Keywords.Any.Definitions => definitions.map(entries => 35 | SchemaMap( 36 | Keywords.Any.Definitions, 37 | entries.toSeq.map { case (name, schema) => SchemaProp(name, schema) }) 38 | ) 39 | case Keywords.Any.Enum => enum.map(e => SchemaValue(JsArray(e))) 40 | case Keywords.Any.Not => not 41 | case "id" => id.map(id => SchemaValue(JsString(id))) 42 | case _ => None 43 | } 44 | 45 | import com.eclipsesource.schema.internal.validators.AnyConstraintValidators._ 46 | 47 | override def validate(schema: SchemaType, json: JsValue, context: SchemaResolutionContext)(implicit lang: Lang): VA[JsValue] = { 48 | val reader: scalaz.Reader[SchemaResolutionContext, Rule[JsValue, JsValue]] = for { 49 | allOfRule <- validateAllOf(schema, allOf) 50 | anyOfRule <- validateAnyOf(schema, anyOf) 51 | oneOfRule <- validateOneOf(schema, oneOf) 52 | enumRule <- validateEnum(enum) 53 | notRule <- validateNot(not) 54 | } yield allOfRule |+| anyOfRule |+| oneOfRule |+| enumRule |+| notRule 55 | reader 56 | .run(context) 57 | .repath(_.compose(context.instancePath)) 58 | .validate(json) 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/scala/com/eclipsesource/schema/internal/draft7/constraints/NumberConstraints7.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema.internal.draft7.constraints 2 | 3 | import com.eclipsesource.schema.{SchemaInteger, SchemaNumber, SchemaResolutionContext, SchemaType, SchemaValue} 4 | import com.eclipsesource.schema.internal.{Keywords, SchemaUtil, ValidatorMessages} 5 | import com.eclipsesource.schema.internal.constraints.Constraints._ 6 | import com.eclipsesource.schema.internal.validation.{Rule, VA} 7 | import com.osinka.i18n.Lang 8 | import play.api.libs.json.{JsNumber, JsString, JsValue} 9 | import scalaz.Success 10 | 11 | case class NumberConstraints7(min: Option[Minimum] = None, 12 | max: Option[Maximum] = None, 13 | multipleOf: Option[BigDecimal] = None, 14 | format: Option[String] = None, 15 | any: AnyConstraints = AnyConstraints7() 16 | ) extends HasAnyConstraint with NumberConstraints { 17 | 18 | import com.eclipsesource.schema.internal.validators.NumberValidators._ 19 | 20 | override def subSchemas: Set[SchemaType] = any.subSchemas 21 | 22 | override def resolvePath(path: String): Option[SchemaType] = path match { 23 | case Keywords.Number.Min => min.map(m => SchemaValue(JsNumber(m.min))) 24 | case Keywords.Number.Max => max.map(m => SchemaValue(JsNumber(m.max))) 25 | case Keywords.Number.MultipleOf => multipleOf.map(m => SchemaValue(JsNumber(m))) 26 | case Keywords.String.Format => format.map(f => SchemaValue(JsString(f))) 27 | case other => any.resolvePath(other) 28 | } 29 | 30 | def isInt(implicit lang: Lang): scalaz.Reader[SchemaResolutionContext, Rule[JsValue, JsValue]] = 31 | scalaz.Reader { context => 32 | Rule.fromMapping { 33 | case json@JsNumber(number) if number.isWhole => Success(json) 34 | case other => 35 | SchemaUtil.failure( 36 | Keywords.Any.Type, 37 | ValidatorMessages("err.expected.type", "integer", SchemaUtil.typeOfAsString(other)), 38 | context.schemaPath, 39 | context.instancePath, 40 | other 41 | ) 42 | } 43 | } 44 | 45 | override def validate(schema: SchemaType, json: JsValue, context: SchemaResolutionContext) 46 | (implicit lang: Lang): VA[JsValue] = { 47 | 48 | val reader = for { 49 | maxRule <- validateMax(max) 50 | minRule <- validateMin(min) 51 | multipleOfRule <- validateMultipleOf(multipleOf) 52 | format <- validateFormat(format) 53 | } yield maxRule |+| minRule |+| multipleOfRule |+| format 54 | 55 | schema match { 56 | case SchemaInteger(_) => 57 | isInt.flatMap(x => reader.map(y => x |+| y)) 58 | .run(context) 59 | .repath(_.compose(context.instancePath)) 60 | .validate(json) 61 | case SchemaNumber(_) => 62 | reader 63 | .run(context) 64 | .repath(_.compose(context.instancePath)) 65 | .validate(json) 66 | case _ => Success(json) 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /src/main/scala/com/eclipsesource/schema/internal/draft4/constraints/NumberConstraints4.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema.internal.draft4.constraints 2 | 3 | import com.eclipsesource.schema.{SchemaInteger, SchemaNumber, SchemaResolutionContext, SchemaType, SchemaValue} 4 | import com.eclipsesource.schema.internal.{Keywords, SchemaUtil, ValidatorMessages} 5 | import com.eclipsesource.schema.internal.constraints.Constraints._ 6 | import com.eclipsesource.schema.internal.validation.{Rule, VA} 7 | import com.osinka.i18n.Lang 8 | import play.api.libs.json.{JsNumber, JsString, JsValue} 9 | import scalaz.Success 10 | 11 | case class NumberConstraints4(min: Option[Minimum] = None, 12 | max: Option[Maximum] = None, 13 | multipleOf: Option[BigDecimal] = None, 14 | format: Option[String] = None, 15 | any: AnyConstraints = AnyConstraints4() 16 | ) extends HasAnyConstraint with NumberConstraints { 17 | 18 | import com.eclipsesource.schema.internal.validators.NumberValidators._ 19 | 20 | override def subSchemas: Set[SchemaType] = any.subSchemas 21 | 22 | override def resolvePath(path: String): Option[SchemaType] = path match { 23 | case Keywords.Number.Min => min.map(m => SchemaValue(JsNumber(m.min))) 24 | case Keywords.Number.Max => max.map(m => SchemaValue(JsNumber(m.max))) 25 | case Keywords.Number.MultipleOf => multipleOf.map(m => SchemaValue(JsNumber(m))) 26 | case Keywords.String.Format => format.map(f => SchemaValue(JsString(f))) 27 | case other => any.resolvePath(other) 28 | } 29 | 30 | def isInt(implicit lang: Lang): scalaz.Reader[SchemaResolutionContext, Rule[JsValue, JsValue]] = 31 | scalaz.Reader { context => 32 | Rule.fromMapping { 33 | case json@JsNumber(number) if number.isWhole => Success(json) 34 | case other => 35 | SchemaUtil.failure( 36 | Keywords.Any.Type, 37 | ValidatorMessages("err.expected.type", "integer", SchemaUtil.typeOfAsString(other)), 38 | context.schemaPath, 39 | context.instancePath, 40 | other 41 | ) 42 | } 43 | } 44 | 45 | override def validate(schema: SchemaType, json: JsValue, context: SchemaResolutionContext) 46 | (implicit lang: Lang): VA[JsValue] = { 47 | 48 | val reader = for { 49 | maxRule <- validateMax(max) 50 | minRule <- validateMin(min) 51 | multipleOfRule <- validateMultipleOf(multipleOf) 52 | format <- validateFormat(format) 53 | } yield maxRule |+| minRule |+| multipleOfRule |+| format 54 | 55 | schema match { 56 | case SchemaInteger(_) => 57 | isInt.flatMap(x => reader.map(y => x |+| y)) 58 | .run(context) 59 | .repath(_.compose(context.instancePath)) 60 | .validate(json) 61 | case SchemaNumber(_) => 62 | reader 63 | .run(context) 64 | .repath(_.compose(context.instancePath)) 65 | .validate(json) 66 | case _ => Success(json) 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/test/resources/draft7/contains.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "contains keyword validation", 4 | "schema": { 5 | "contains": {"minimum": 5} 6 | }, 7 | "tests": [ 8 | { 9 | "description": "array with item matching schema (5) is valid", 10 | "data": [3, 4, 5], 11 | "valid": true 12 | }, 13 | { 14 | "description": "array with item matching schema (6) is valid", 15 | "data": [3, 4, 6], 16 | "valid": true 17 | }, 18 | { 19 | "description": "array with two items matching schema (5, 6) is valid", 20 | "data": [3, 4, 5, 6], 21 | "valid": true 22 | }, 23 | { 24 | "description": "array without items matching schema is invalid", 25 | "data": [2, 3, 4], 26 | "valid": false 27 | }, 28 | { 29 | "description": "empty array is invalid", 30 | "data": [], 31 | "valid": false 32 | }, 33 | { 34 | "description": "not array is valid", 35 | "data": {}, 36 | "valid": true 37 | } 38 | ] 39 | }, 40 | { 41 | "description": "contains keyword with const keyword", 42 | "schema": { 43 | "contains": { "const": 5 } 44 | }, 45 | "tests": [ 46 | { 47 | "description": "array with item 5 is valid", 48 | "data": [3, 4, 5], 49 | "valid": true 50 | }, 51 | { 52 | "description": "array with two items 5 is valid", 53 | "data": [3, 4, 5, 5], 54 | "valid": true 55 | }, 56 | { 57 | "description": "array without item 5 is invalid", 58 | "data": [1, 2, 3, 4], 59 | "valid": false 60 | } 61 | ] 62 | }, 63 | { 64 | "description": "contains keyword with boolean schema true", 65 | "schema": {"contains": true}, 66 | "tests": [ 67 | { 68 | "description": "any non-empty array is valid", 69 | "data": ["foo"], 70 | "valid": true 71 | }, 72 | { 73 | "description": "empty array is invalid", 74 | "data": [], 75 | "valid": false 76 | } 77 | ] 78 | }, 79 | { 80 | "description": "contains keyword with boolean schema false", 81 | "schema": {"contains": false}, 82 | "tests": [ 83 | { 84 | "description": "any non-empty array is invalid", 85 | "data": ["foo"], 86 | "valid": false 87 | }, 88 | { 89 | "description": "empty array is invalid", 90 | "data": [], 91 | "valid": false 92 | } 93 | ] 94 | } 95 | ] 96 | -------------------------------------------------------------------------------- /src/main/scala/com/eclipsesource/schema/internal/SchemaUtil.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema.internal 2 | 3 | import com.eclipsesource.schema.internal.validation.Validated 4 | import play.api.libs.json._ 5 | 6 | import scalaz.Failure 7 | 8 | object SchemaUtil { 9 | 10 | def dropSlashIfAny(path: String): String = if (path.startsWith("/#")) path.substring(1) else path 11 | 12 | def failure(keyword: String, 13 | msg: String, 14 | schemaPath: Option[JsPath], 15 | instancePath: JsPath, 16 | instance: JsValue, 17 | additionalInfo: JsObject = Json.obj() 18 | ): Validated[JsonValidationError, JsValue] = 19 | Failure( 20 | Seq( 21 | JsonValidationError( 22 | msg, 23 | createErrorObject(keyword, schemaPath, instancePath, instance, additionalInfo) 24 | ) 25 | ) 26 | ) 27 | 28 | def createErrorObject(keyword: String, schemaPath: Option[JsPath], instancePath: JsPath, instance: JsValue, additionalInfo: JsObject): JsObject = { 29 | Json.obj( 30 | "keyword" -> keyword 31 | ).deepMerge(schemaPath.fold(Json.obj("schemaPath" -> ""))(p => Json.obj("schemaPath" -> dropSlashIfAny(p.toString())))) 32 | .deepMerge( 33 | Json.obj( 34 | "instancePath" -> instancePath.toString(), 35 | "value" -> instance, 36 | "errors" -> additionalInfo 37 | ) 38 | ) 39 | } 40 | 41 | def typeOfAsString(json: JsValue): String = { 42 | json match { 43 | case JsString(_) => "string" 44 | case JsNumber(_) => "number" 45 | case JsBoolean(_) => "boolean" 46 | case JsObject(_) => "object" 47 | case JsArray(_) => "array" 48 | case JsNull => "null" 49 | } 50 | } 51 | 52 | def toJson(errors: collection.Seq[(JsPath, collection.Seq[JsonValidationError])]): JsArray = { 53 | val emptyErrors = Json.arr() 54 | errors.foldLeft(emptyErrors) { case (accumulatedErrors, (_, validationErrors)) => 55 | val maybeError = validationErrors.foldLeft(None: Option[JsObject])((aggregatedError, err) => err.args.headOption match { 56 | case Some(o@JsObject(_)) => 57 | Some( 58 | aggregatedError.fold( 59 | deepMerge(o, Json.obj("msgs" -> err.messages)) 60 | )(errObj => deepMerge(errObj, Json.obj("msgs" -> err.messages))) 61 | ) 62 | case _ => aggregatedError 63 | }) 64 | maybeError.fold(accumulatedErrors)(o => accumulatedErrors :+ o) 65 | } 66 | } 67 | 68 | private def deepMerge(obj: JsObject, other: JsObject): JsObject = { 69 | def merge(existingObject: JsObject, otherObject: JsObject): JsObject = { 70 | val result = existingObject.fields.toMap ++ otherObject.fields.toMap.map { 71 | case (otherKey, otherValue) => 72 | val maybeExistingValue = existingObject.fields.toMap.get(otherKey) 73 | 74 | val newValue = (maybeExistingValue, otherValue) match { 75 | case (Some(e: JsObject), o: JsObject) => merge(e, o) 76 | case (Some(e: JsArray), o: JsArray) => e ++ o 77 | case _ => otherValue 78 | } 79 | otherKey -> newValue 80 | } 81 | JsObject(result) 82 | } 83 | merge(obj, other) 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/test/scala/com/eclipsesource/schema/UrlHandlerSpec.scala: -------------------------------------------------------------------------------- 1 | package com.eclipsesource.schema 2 | 3 | import com.eclipsesource.schema.drafts.{Version4, Version7} 4 | import com.eclipsesource.schema.internal.validators.DefaultFormats 5 | import com.eclipsesource.schema.urlhandlers.ClasspathUrlHandler 6 | import org.specs2.mutable.Specification 7 | import play.api.libs.json.Json 8 | 9 | class UrlHandlerSpec extends Specification with ErrorHelper { self => 10 | 11 | "UrlHandlers" should { 12 | 13 | import Version4._ 14 | 15 | val clazz = this.getClass 16 | 17 | // no handler at all 18 | "should fail to resolve absolute references on the classpath if not handler available" in { 19 | val validator = SchemaValidator(Some(Version4)) 20 | val someJson = clazz.getResourceAsStream("/schemas/my-schema-with-protocol-ful-absolute-path.schema") 21 | val schema = JsonSource.schemaFromStream(someJson) 22 | validator.validate(schema.get, Json.obj("foo" -> Json.obj("bar" -> "Munich"))) 23 | .isError must beTrue 24 | } 25 | 26 | // absolute protocol-ful handler 27 | "should resolve absolute references on the classpath with ClasspathUrlProtocolHandler" in { 28 | val validator = SchemaValidator(Some(Version4)).addUrlHandler(new ClasspathUrlHandler(), ClasspathUrlHandler.Scheme) 29 | val someJson = clazz.getResourceAsStream("/schemas/my-schema-with-protocol-ful-absolute-path.schema") 30 | val schema = JsonSource.schemaFromStream(someJson) 31 | val result = validator.validate(schema.get, Json.obj("foo" -> Json.obj("bar" -> "Munich"))) 32 | result.isSuccess must beTrue 33 | } 34 | 35 | "should resolve absolute references on classpath with ClasspathUrlProtocolHandler (version 7)" in { 36 | import Version7._ 37 | val validator = SchemaValidator(Some(Version7(new SchemaConfigOptions { 38 | override def formats: Map[String, SchemaFormat] = DefaultFormats.formats 39 | override def supportsExternalReferences: Boolean = true 40 | }))).addUrlHandler(new ClasspathUrlHandler(), ClasspathUrlHandler.Scheme) 41 | val schema = JsonSource.schemaFromString( 42 | """{ 43 | | "type": "object", 44 | | "properties": { 45 | | "schema": { 46 | | "$ref": "classpath:///refs/json-schema-draft-07.json" 47 | | } 48 | | } 49 | |} 50 | """.stripMargin) 51 | val result = validator.validate(schema.get, Json.obj("schema" -> Json.obj())) 52 | result.isSuccess must beTrue 53 | } 54 | 55 | "should resolve relative references on classpath (valid instance)" in { 56 | val validator = SchemaValidator(Some(Version4)) 57 | val url = clazz.getResource("/schemas/my-schema-with-protocol-less-relative-path.schema") 58 | validator.validate(url)(Json.obj("foo" -> Json.obj("bar" -> "Munich"))) 59 | .isSuccess must beTrue 60 | } 61 | 62 | "should resolve relative references on the classpath (invalid instance)" in { 63 | val validator = SchemaValidator(Some(Version4)) 64 | val url = clazz.getResource("/schemas/my-schema-with-protocol-less-relative-path.schema") 65 | val res = validator.validate(url)(Json.obj("foo" -> Json.obj("bar" -> 3))) 66 | firstErrorOf(res) must beEqualTo("Wrong type. Expected string, was number.") 67 | } 68 | } 69 | } 70 | --------------------------------------------------------------------------------