hydra-0.15.0: src/main/haskell/Hydra/Sources/Json/Schema.hs
module Hydra.Sources.Json.Schema where
-- Standard imports for type-level sources outside of the kernel
import Hydra.Kernel
import Hydra.Dsl.Annotations
import Hydra.Dsl.Bootstrap
import Hydra.Dsl.Types ((>:))
import qualified Hydra.Dsl.Types as T
import qualified Hydra.Sources.Kernel.Types.Core as Core
import qualified Data.List as L
import qualified Data.Map as M
import qualified Data.Set as S
import qualified Data.Maybe as Y
-- Additional imports
import qualified Hydra.Sources.Json.Model as JsonModel
module_ :: Module
module_ = Module {
moduleNamespace = ns,
moduleDefinitions = (map toTypeDef definitions),
moduleTermDependencies = [JsonModel.ns],
moduleTypeDependencies = [Core.ns],
moduleDescription = Just ("A model for JSON Schema. Based on https://cswr.github.io/JsonSchema/spec/grammar")}
where
ns = Namespace "hydra.json.schema"
def = datatype ns
js = typeref ns
json = typeref $ JsonModel.ns
keywordSchemaMap = T.map (js "Keyword") (js "Schema")
keywordSchemaOrArrayMap = T.map (js "Keyword") (js "SchemaOrArray")
regexSchemaMap = T.map (js "RegularExpression") (js "Schema")
definitions = [
-- Json Documents and Schemas
-- Let JDOC be an arbitrary JSON Schema Document. We can define its syntax using the following grammar:
--
-- JSDoc := { ( id, )? ( defs, )? JSch }
def "Document" $ T.record [
"id">: T.maybe T.string,
"definitions">: T.maybe keywordSchemaMap,
"root">: js "Schema"],
-- id := "id": "uri"
-- defs := "definitions": { kSch (, kSch)*}
-- kSch := kword: { JSch }
def "Keyword" $
T.wrap T.string,
-- JSch := ( res (, res)*)
def "Schema" $
T.wrap $ nonemptyList $ js "Restriction",
-- res := type | strRes | numRes | arrRes | objRes | multRes | refSch | title | description
def "Restriction" $
T.union [
"type">: js "Type",
"string">: js "StringRestriction",
"number">: js "NumericRestriction",
"array">: js "ArrayRestriction",
"object">: js "ObjectRestriction",
"multiple">: js "MultipleRestriction",
"reference">: js "SchemaReference",
"title">: T.string,
"description">: T.string],
-- type := "type" : ([typename (, typename)*] | typename)
def "Type" $
T.union [
"single">: js "TypeName",
"multiple">: nonemptyList $ js "TypeName"],
-- typename := "string" | "integer" | "number" | "boolean" | "null" | "array" | "object"
def "TypeName" $
T.enum ["string", "integer", "number", "boolean", "null", "array", "object"],
-- title := "title": string
-- description := "description": string
-- Here each res and typename must be different from each other(otherwise they would be superfluous).
-- We must also note that each kword is representing a keyword that must be unique in the nest level that is occurs.
-- Besides, string is any string to describe either the title or de description of the nested schema.
-- Finally, a uri is any possible uri as defined in the standard. Next we specify the remaining restrictions:
-- strRes, numRes, arrRes, objRes and multRes, as well as referred schemas refSch.
--
-- String Restrictions
-- strRes := minLen | maxLen | pattern
def "StringRestriction" $
T.union [
"minLength">: T.int32,
"maxLength">: T.int32,
"pattern">: js "RegularExpression"],
-- minLen := "minLength": n
-- maxLen := "maxLength": n
-- pattern := "pattern": "regExp"
def "RegularExpression" $
T.wrap T.string,
-- Here n is a natural number and r is a regular expression.
--
-- Numeric Restrictions
-- numRes := min | max | multiple
def "NumericRestriction" $
T.union [
"minimum">: js "Limit",
"maximum">: js "Limit",
"multipleOf">: T.nonNegativeInt32],
def "Limit" $
T.record [
"value">: T.int32,
"exclusive">: T.boolean],
-- min := "minimum": r (,exMin)?
-- exMin := "exclusiveMinimum": bool
-- max := "maximum": r (,exMax)?
-- exMax := "exclusiveMaximum": bool
-- multiple := "multipleOf": r (r >= 0)
-- Here r is a decimal number and bool is either true or false.
--
-- Array Restrictions
-- arrRes := items | additems | minitems | maxitems | unique
def "ArrayRestriction" $
T.union [
"items">: js "Items",
"additionalItems">: js "AdditionalItems",
"minItems">: T.nonNegativeInt32,
"maxItems">: T.nonNegativeInt32,
"uniqueItems">: T.boolean],
-- items := ( sameitems | varitems )
def "Items" $
T.union [
"sameItems">: js "Schema",
"varItems">: nonemptyList $ js "Schema"],
-- sameitems := "items": { JSch }
-- varitems := "items": [{ JSch }(,{ JSch })*]
-- additems := "additionalItems": (bool | { JSch })
def "AdditionalItems" $
T.union [
"any">: T.boolean,
"schema">: js "Schema"],
-- minitems := "minItems": n
-- maxitems := "maxItems": n
-- unique := "uniqueItems": bool
-- Here n is a natural number and bool is either true or false.
--
-- Object Restrictions
-- objRes := prop | addprop | req | minprop | maxprop | dep | pattprop
def "ObjectRestriction" $
T.union [
"properties">: keywordSchemaMap,
"additionalProperties">: js "AdditionalItems",
"required">: nonemptyList $ js "Keyword",
"minProperties">: T.nonNegativeInt32,
"maxProperties">: T.nonNegativeInt32,
"dependencies">: keywordSchemaOrArrayMap,
"patternProperties">: regexSchemaMap],
-- prop := "properties": { kSch (, kSch)*}
-- kSch := kword: { JSch }
-- addprop := "additionalProperties": (bool | { JSch })
-- req := "required": [ kword (, kword)*]
-- minprop := "minProperties": n
-- maxprop := "maxProperties": n
-- dep := "dependencies": { kDep (, kDep)*}
-- kDep := (kArr | kSch)
def "SchemaOrArray" $
T.union [
"schema">: js "Schema",
"array">: T.list $ js "Keyword"],
-- kArr := kword: [ kword (, kword)*]
-- pattprop := "patternProperties": { patSch (, patSch)*}
-- patSch := "regExp": { JSch }
-- Here n is a natural number, bool is either true or false and regExp is a regular expression. As above, each kword is representing a keyword that must be unique in the nest level that is occurs.
--
-- Multiple Restrictions
-- multRes := allOf | anyOf| oneOf | not | enum
def "MultipleRestriction" $
T.union [
"allOf">: nonemptyList $ js "Schema",
"anyOf">: nonemptyList $ js "Schema",
"oneOf">: nonemptyList $ js "Schema",
"not">: js "Schema",
"enum">: nonemptyList $ json "Value"],
-- anyOf := "anyOf": [ { JSch } (, { JSch }) * ]
-- allOf := "allOf": [ { JSch } (, { JSch }) * ]
-- oneOf := "oneOf": [ { JSch } (, { JSch }) * ]
-- not := "not": { JSch }
-- enum := "enum": [Jval (, Jval)*]
-- Here Jval is either a string, number, array, object, bool or a null value. Moreover each Jval must be different from each other(otherwise they would be superfluous).
-- Referred Schemas
-- Note that uriRef below is the same grammar we defined earlier for URIs.
--
-- refSch := "$ref": "uriRef"
def "SchemaReference" $
T.wrap T.string]
-- uriRef := ( address )? ( # / JPointer )?
-- JPointer := ( / path )
-- path := ( unescaped | escaped )
-- escaped := ~0 | ~1
-- Where unescaped can be any character except for / and ~. Also, address corresponds to any URI that does not use the # symbol, or more precisely to any URI-reference constructed using the following grammar, as defined in the official standard:
--
-- address = (scheme : )? hier-part (? query )