{
  "title": "Sparnatural query schema",
  "version": "10.0.0",
  "description": "A JSON schema describing the structure of queries as read or written by the Sparnatural query builder UI",
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://sparnatural.eu/def/schemas/sparnatural-query-schema.json",
  "$defs": {

    "Query": {
      "description": "A full Sparnatural query, with selected variables, query criterias, order clause",
      "type" : "object",
      "properties": {
        "branches": {
          "description": "The query criterias, forming the 'WHERE' clause of the query",
          "type":"array",
          "minItems": 1,
          "items": {
            "$ref": "#/$defs/Branch"
          }
        },
        "distinct": {
          "description": "Whether the SELECT clause will use a DISTINCT keyword.",
          "type":"boolean"
        },
        "order": {
          "description": "The order criteria to be applied to the first column of the query, either ascending, descending, or no order",
          "type":"string",
          "enum": ["asc","desc","noord"]
        },
        "variables": {
          "description": "The variables in the SELECT clause of the query, either simple variables, or aggregated variables",
          "type":"array",
          "minItems": 1,
          "items": {
            "anyOf": [
              {"$ref": "#/$defs/VariableTerm"},
              {"$ref": "#/$defs/VariableExpression"},  
            ]
          }
        }
      },
      "required":["variables","branches"],
      "additionalProperties":false
    },

    "VariableTerm": {
      "description": "A SPARQL variable with its name, such as '?x'",
      "type":"object",
      "properties": {
        "termType": {
          "type" : "string",
          "const": "Variable"
        },
        "value": {
          "type": "string"
        }
      },
      "additionalProperties":false
    },

    "VariableExpression": {
      "description": "A SPARQL aggregation expression, with the aggregation function on the original variable and the resulting variable, such as '(COUNT(?x) AS ?count)' ",
      "type":"object",
      "properties": {
        "variable" : {
          "description": "The resulting variable of the aggregation function, such as '?count' in '(COUNT(?x) AS ?count)' ",
          "$ref": "#/$defs/VariableTerm"
        },
        "expression": {
          "description": "An aggregation expression, with an aggregation function and the variable being aggregated, such as 'COUNT(?x)'",
          "type":"object",
          "properties": {
            "type": {
              "type":"string",
              "const":"aggregate"
            },
            "aggregation": {
              "description": "The aggregation function name, as one of the allowed SPARQL aggregation functions",
              "type":"string",
              "enum": ["count","max","min","sample","sum","avg","group_concat"]
            },
            "distinct": {
              "description": "Whether the aggregation function should use the DISTINCT keywork",
              "type":"boolean"
            },
            "expression": {
              "description": "The variable being aggregated, such as '?x' 'in COUNT(?x)'",
              "$ref": "#/$defs/VariableTerm"
            }
          },
          "required":["type","aggregation","expression"],
          "additionalProperties":false
        }
      },
      "required":["variable","expression"],
      "additionalProperties":false
    },
    
    "Branch": {
      "description": "A Sparnatural query branch, consisting of a criteria line (subject, predicate, object), and optional children branches",
      "type" : "object",
      "properties": {
        "line" : {
          "description": "The criteria line",
          "$ref": "#/$defs/CriteriaLine"
        },
        "children": {
          "description": "The children branches. This is a recusrsive reference. The array may be empty if there are no children.",
          "type":"array",
          "items": {
            "$ref": "#/$defs/Branch"
          }
        },
        "optional": {
          "description": "Whether the OPTIONAL SPARQL keyword is applied at this level, on this branch and all children branches",
          "type":"boolean"
        },
        "notExists": {
          "description": "Whether the FILTER NOT EXISTS SPARQL keyword is applied at this level, on this branch and all children branches",
          "type":"boolean"
        }
      },
      "required":["line","children"],
      "additionalProperties":false
    },

    "CriteriaLine": {
      "description": "One line of criteria in Sparnatural, corresponding to a subject, a predicate, an object, and also the type (rdf:type) of the subject and the object",
      "type":"object",
      "properties": {
        "s" : {
          "description": "The subject variable name, such as '?Person_1'",
          "type": "string"
        },
        "p" : {
          "description": "The full URI of the predicate",
          "type": "string",
          "format": "iri"
        },
        "o" : {
          "description": "The object variable name, such as '?Country_2'",
          "type": "string"
        },
        "sType" : {
          "description": "The full URI of the type of the subject",
          "type": "string",
          "format": "iri"
        },
        "oType" : {
          "description": "The full URI of the type of the object",
          "type": "string",
          "format": "iri"
        },
        "values": {
          "description": "The values selected as search criteria in this line. This is optional, no values may be selected.",
          "type": "array",
          "items": {
             "oneOf" : [
                {"$ref": "#/$defs/RDFTermValue"},
                {"$ref": "#/$defs/BooleanValue"},
                {"$ref": "#/$defs/DateValue"},
                {"$ref": "#/$defs/MapValue"},
                {"$ref": "#/$defs/NumberValue"},
                {"$ref": "#/$defs/SearchRegexWidgetValue"}
              ],
          }
        }
      },
      "required": ["s","p","o","sType","oType"],
      "additionalProperties": false
    },

    "WidgetValue": {
      "description": "An abstract selected value. Every value has a human-readable display label.",
      "type":"object",
      "properties": {
        "label" : {
          "description": "The human-readable display label of the value",
          "type": "string"
        }
      },
      "required" : ["label"]
    },
    "RDFTermValue": {
      "description": "An RDF term value, either an IRI or a Literal",
      "allOf" : [{"$ref": "#/$defs/WidgetValue"}],
      "properties": {
        "type" : {
          "description": "The type of the RDF node, either literal, blank node or URI",
          "type" : "string",
          "enum" : ["literal","bnode","uri"]
        },
        "value" : {
          "description": "The value, either the URI itself, the lexical form of the literal, or the blank node identifier",
          "type" : "string"
        },
        "xml:lang" : {
          "description": "The language code of the value, if the term is a literal with a language.",
          "type" : "string"
        },
        "datatype" : {
          "description": "The full URI of the datatype of the Literal, it it has one.",
          "type" : "string",
          "format": "iri"
        }
      },
      "required" : ["type", "value"],
      "additionalProperties": false
    },
    "BooleanValue": {
      "description": "A boolean value criteria",
      "allOf" : [{"$ref": "#/$defs/WidgetValue"}],
      "properties": {
        "boolean": {
          "description": "The boolean value, either tru or false",
          "type":"boolean"
        }
      },
      "required" : ["boolean"],
      "additionalProperties": false
    },
    "DateValue": {
      "description": "A date range criteria, with a start date and an end date. At least a start date or an end date must be provided.",
      "allOf" : [{"$ref": "#/$defs/WidgetValue"}],
      "properties": {
        "start": {
          "description": "The start date of the range, either in a date format, a year format, or a date-time format",
          "type":"string"
        },
        "stop": {
          "description": "The end date of the range, either in a date format, a year format, or a date-time format",
          "type":"string"
        }
      },
      "oneOf": [
        {"required":["start"]},
        {"required":["stop"]}
      ],
      "additionalProperties": false
    },
    "MapValue": {
      "description": "A GeoSPARQL query criteria. This is a list of shapes, either polygons or rectangles, each polygons containing a list of latitude and longitude pairs",
      "allOf" : [{"$ref": "#/$defs/WidgetValue"}],
      "properties": {
        "type": {
          "description": "The type of the searched shape, either 'Rectangle' or 'Polygon'.",
          "type": "string",
          "enum": ["Rectangle","Polygon"]
        },
        "coordinates": {
          "description": "The coordinates of the shapes, a list of latitude/longitude pairs",
          "type" : "array",
          "items" : {
            "type" : "array",
            "items" : {
              "type": "object",
              "properties": {
                "lat": {
                  "description": "A latitude",
                  "type":"number"
                },
                "long": {
                  "description": "A longitude",
                  "type":"number"
                }
              }
            },
            "required":["lat","long"]
          }
        }
      },
      "required":["type","coordinates"],
      "additionalProperties": false
    },
    "NumberValue": {
      "description": "A number search range, with a minimum and maximum value. At least the minimum or maximum should be provided.",
      "allOf" : [{"$ref": "#/$defs/WidgetValue"}],
      "properties": {
        "min" : {
          "type":"number"
        },
        "max" : {
          "type":"number"
        }
      },
      "oneOf": [
        {"required":["min"]},
        {"required":["max"]}
      ],
      "additionalProperties": false
    },
    "SearchRegexWidgetValue" : {
      "description": "A string search criteria that will typically be translated to a FILTER(REGEX(...)) SPARQL function",
      "allOf" : [{"$ref": "#/$defs/WidgetValue"}],
      "properties": {
        "regex": {
          "description": "The string to search on, that can be a regular expression",
          "type":"string"
        }
      },
      "required":["regex"],
      "additionalProperties": false
    }
  },
  "type": "object",
  "$ref": "#/$defs/Query"
}