Source
...
Target
Commits (21)
[flake8]
# Black formats code with max line length of 88
# Bugbears B950 warning allows lines to be 10% longer than flake8's max
max-line-length = 80
# Errors and warnings to report
select =
C,
E,
F,
W,
B,
# Allow max line length to be exceeded by 10% before warning.
B950
# Errors and warnings to ignore
ignore =
# Black puts space around slice operator which flake8 errors on.
E203,
# No error on max line length exceeded as B950 is doing this for us.
E501,
# Black puts line breaks before binary operators so don't warn this.
W503
B036
E999
# Folders to ignore
exclude =
.git,
*.pyc,
__pycache__,
./docker,
./docs,
./kustomize
./requirements.txt
./requirements-dev.txt
./CHANGELOG.md
./CONTRIBUTING.md
......@@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
## [Unreleased]
### Changed
- Store remote schemas as committed local files
This prevents the live runtime loading untested changes
- Inject remote geojson schema definitions
- Use geojson Polygon instead of region_schema
- Upgraded openapi-spec-validator to latest release (0.7.1)
- Upgraded openapi-schema-validator to latest release (0.6.2)
## [v1.0.0] - 2024-08-28
### Changed
......@@ -18,6 +27,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Refactor schema script to remove invalid definitions object automatically
- Refactor generate_schema_config script to output file on -f flag
### Fixed
- Add `--remove-orphans` to javascript CI test docker compose arguments
## [v0.2.0] - 2024-02-06
### Added
......
......@@ -93,7 +93,7 @@ python3 -m unittest discover
2. Test 2 - Use javascript validators
```
# Compile schema and run javascript validation tests in docker
python3 test-js.py
bash test-js.sh
```
## Quick Links
......
......@@ -4,7 +4,7 @@
"timestamp": "2022-11-16T00:00:00Z",
"version": 2,
"source": "gui",
"destination": "soar.all.all.all.from_platform.planning_configuration",
"destination": "",
"delivery_type": "broadcast",
"encoded": false
},
......@@ -13,7 +13,8 @@
"planning_config_ID": 1,
"exclusion_zones": [
{
"geometry_coordinates": [
"type": "Polygon",
"coordinates": [
[
[
-4.1777839187560915,
......@@ -41,7 +42,8 @@
],
"region_of_interest": [
{
"geometry_coordinates": [
"type": "Polygon",
"coordinates": [
[
[
-4.1777839187560915,
......
......@@ -4,8 +4,8 @@
"timestamp": "2022-11-16T00:00:00Z",
"version": 2,
"source": "gui",
"destination": "soar.all.all.all.from_platform.planning_configuration",
"delivery_type": "publish",
"destination": "",
"delivery_type": "broadcast",
"encoded": false
},
"payload":{
......@@ -13,7 +13,8 @@
"planning_config_ID": 1,
"exclusion_zones": [
{
"geometry_coordinates": [
"type": "Polygon",
"coordinates": [
[
[
-4.1777839187560915,
......@@ -41,7 +42,8 @@
],
"region_of_interest": [
{
"geometry_coordinates": [
"type": "Polygon",
"coordinates": [
[
[
-4.1777839187560915,
......
......@@ -36,7 +36,6 @@ emergency_schema = {
"required": [
"target_waypoint_latitude",
"target_waypoint_longitude",
"target_depth",
],
}
......@@ -181,25 +180,6 @@ platform_schema = {
],
}
region_schema = {
"type": "object",
"properties": {
"geometry_coordinates": {
"type": "array",
"example": [
[
[-4.1777839187560915, 50.34173405662855],
[-4.1777839187560915, 50.33820949229701],
[-4.143667777943875, 50.33820949229701],
[-4.143667777943875, 50.34173405662855],
[-4.1777839187560915, 50.34173405662855],
]
],
},
},
"description": "Using GEOJSON, exact 4-point region (rectangle shaped - 5 points)",
}
squad_metadata_schema = {
"type": "object",
"properties": {
......@@ -251,12 +231,16 @@ planning_configuration_schema = {
},
"region_of_interest": {
"type": "array",
"items": region_schema,
"items": {
"$ref": "https://geojson.org/schema/Polygon.json",
},
"description": "Region of interest for the entire operation",
},
"exclusion_zones": {
"type": "array",
"items": region_schema,
"items": {
"$ref": "https://geojson.org/schema/Polygon.json",
},
"description": "Exclusion zones for all platforms",
},
"squads": {
......
......@@ -17,6 +17,9 @@ from flask import Flask
import argparse
import json
import os
import re
import requests
from urllib.parse import urlparse
# Enable running on domain sub-path
......@@ -28,88 +31,244 @@ FLASK_PORT = os.getenv("FLASK_PORT", 5000)
# Switch on debug mode if env var is truthy
FLASK_DEBUG = os.getenv("FLASK_DEBUG", "False").lower() in ("true", "1", "t")
swagger_config = {
"openapi": "3.0.2",
"swagger_ui": True,
"specs_route": "/",
"info": {
"title": "SoAR Backbone Message Formats",
"version": "1.0",
"description": "SoAR message protocol in schemas",
},
"specs": [
{
"endpoint": "swagger",
"route": "/soar_protocol.json",
}
],
"url_prefix": URL_PREFIX,
"paths": {},
"components": {
"schemas": {
"MESSAGE": {
"type": "object",
"description": "Full message definition with"
+ " message-metadata in `header` and different"
+ " message type schemas under `payload`",
"properties": {
"header": {
"$ref": "#/components/schemas/header",
def get_swagger_config(reload=False):
if reload:
print("Reload specified: Ignoring cached refs")
swagger_config = {
"openapi": "3.0.2",
"swagger_ui": True,
"specs_route": "/",
"info": {
"title": "SoAR Backbone Message Formats",
"version": "1.0",
"description": "SoAR message protocol in schemas",
},
"specs": [
{
"endpoint": "swagger",
"route": "/soar_protocol.json",
}
],
"url_prefix": URL_PREFIX,
"paths": {},
"components": {
"schemas": {
"MESSAGE": {
"type": "object",
"description": "Full message definition with"
+ " message-metadata in `header` and different"
+ " message type schemas under `payload`",
"properties": {
"header": {
"$ref": "#/components/schemas/header",
},
"payload": {"$ref": "#/components/schemas/payload"},
},
"payload": {"$ref": "#/components/schemas/payload"},
"required": ["header", "payload"],
},
"required": ["header", "payload"],
},
"payload": {
"discriminator": {
"propertyName": "message_type",
"mapping": {
"alert": "#/components/schemas/alert",
"mission_plan": "#/components/schemas/mission_plan",
"mission_plan_encoded": "#/components/schemas/"
+ "mission_plan_encoded",
"observation": "#/components/schemas/observation",
"observation_encoded": "#/components/schemas/"
+ "observation_encoded",
"planning_configuration": "#/components/schemas/"
+ "planning_configuration",
"platform_status": "#/components/schemas/platform_status",
"platform_status_encoded": "#/components/schemas/"
+ "platform_status_encoded",
"acknowledgement": "#/components/schemas/acknowledgement",
"survey": "#/components/schemas/survey",
"survey_encoded": "#/components/schemas/" + "survey_encoded",
"payload": {
"discriminator": {
"propertyName": "message_type",
"mapping": {
"alert": "#/components/schemas/alert",
"mission_plan": "#/components/schemas/mission_plan",
"mission_plan_encoded": "#/components/schemas/"
+ "mission_plan_encoded",
"observation": "#/components/schemas/observation",
"observation_encoded": "#/components/schemas/"
+ "observation_encoded",
"planning_configuration": "#/components/schemas/"
+ "planning_configuration",
"platform_status": "#/components/schemas/platform_status",
"platform_status_encoded": "#/components/schemas/"
+ "platform_status_encoded",
"acknowledgement": "#/components/schemas/acknowledgement",
"survey": "#/components/schemas/survey",
"survey_encoded": "#/components/schemas/"
+ "survey_encoded",
},
},
"oneOf": [
{"$ref": "#/components/schemas/alert"},
{"$ref": "#/components/schemas/acknowledgement"},
{"$ref": "#/components/schemas/mission_plan"},
{"$ref": "#/components/schemas/mission_plan_encoded"},
{"$ref": "#/components/schemas/observation"},
{"$ref": "#/components/schemas/observation_encoded"},
{"$ref": "#/components/schemas/planning_configuration"},
{"$ref": "#/components/schemas/platform_status"},
{"$ref": "#/components/schemas/platform_status_encoded"},
{"$ref": "#/components/schemas/survey"},
{"$ref": "#/components/schemas/survey_encoded"},
],
},
"oneOf": [
{"$ref": "#/components/schemas/alert"},
{"$ref": "#/components/schemas/acknowledgement"},
{"$ref": "#/components/schemas/mission_plan"},
{"$ref": "#/components/schemas/mission_plan_encoded"},
{"$ref": "#/components/schemas/observation"},
{"$ref": "#/components/schemas/observation_encoded"},
{"$ref": "#/components/schemas/planning_configuration"},
{"$ref": "#/components/schemas/platform_status"},
{"$ref": "#/components/schemas/platform_status_encoded"},
{"$ref": "#/components/schemas/survey"},
{"$ref": "#/components/schemas/survey_encoded"},
],
},
"header": message_header,
"mission_plan": mission_plan_schema,
"mission_plan_encoded": mission_plan_encoded_schema,
"observation": observation_schema,
"observation_encoded": observation_encoded_schema,
"planning_configuration": planning_configuration_schema,
"platform_status": platform_status_schema,
"platform_status_encoded": platform_status_encoded_schema,
"survey": survey_schema,
"survey_encoded": survey_encoded_schema,
"acknowledgement": acknowledgement_schema,
"alert": alert_schema,
}
},
}
"header": message_header,
"mission_plan": mission_plan_schema,
"mission_plan_encoded": mission_plan_encoded_schema,
"observation": observation_schema,
"observation_encoded": observation_encoded_schema,
"planning_configuration": planning_configuration_schema,
"platform_status": platform_status_schema,
"platform_status_encoded": platform_status_encoded_schema,
"survey": survey_schema,
"survey_encoded": survey_encoded_schema,
"acknowledgement": acknowledgement_schema,
"alert": alert_schema,
}
},
}
import_remote_refs(swagger_config, reload)
return swagger_config
def resolve_ref(ref):
"""
Get schema URL, parse JSON
Return None if either fails
"""
try:
res = requests.get(ref)
if res.status_code == 200:
return res.json()
else:
return None
except (json.JSONDecodeError, ValueError):
return None
def rename_ref(ref):
"""
Convert remote ref URL into a name that can
be used for a local ref in the schema
Remote the URL scheme and replace / with .
"""
# remove url scheme
deschemed = re.sub(r"^[htps]*\:*[/]{2}", "", ref)
# replace / with . since the name will be in a path
return re.sub(r"[/]", ".", deschemed)
def nested_replace(source, key, value, replace_with):
"""
Find all instances of a key value pair in a nested
dictionary and replace the value with replace_with
"""
for k, v in source.items():
if k == key and v == value:
source[k] = replace_with
elif type(v) is list:
for item in v:
if type(item) is dict:
nested_replace(item, key, value, replace_with)
if type(v) is dict:
nested_replace(v, key, value, replace_with)
def downgrade_schema_30x_compatible(schema):
"""
The published GeoJSON schemas are OpenAPI v3.1
Moving to v3.1 is not trivial
There isn't a CommonJS validator for v3.1
There isn't a python source of the v3.0.x defs
Remove $id and $schema
Remove oneOf: [{type:null}]
Iterate over oneOf and items child schemas
"""
if "$id" in schema:
del schema["$id"]
if "$schema" in schema:
del schema["$schema"]
if "properties" in schema:
for propConfig in schema["properties"].values():
if "oneOf" in propConfig:
try:
propConfig["oneOf"].remove({"type": "null"})
except ValueError:
pass
for child_schema in propConfig["oneOf"]:
downgrade_schema_30x_compatible(child_schema)
if "items" in propConfig:
downgrade_schema_30x_compatible(propConfig["items"])
def get_remote_ref_cache_path(remote_ref):
parsed_ref = urlparse(remote_ref)
return f"remotes/{parsed_ref.hostname}{parsed_ref.path}"
def get_cached_ref(remote_ref):
ref_path = get_remote_ref_cache_path(remote_ref)
ref = None
if os.path.exists(ref_path):
print(f"loading cached ref: {remote_ref}")
with open(ref_path, "r") as ref_file:
ref = json.load(ref_file)
return ref
def store_cached_ref(remote_ref, definition):
ref_path = get_remote_ref_cache_path(remote_ref)
ref_dirs = re.sub(r"\/[^\/]+$", "", ref_path)
os.makedirs(ref_dirs, 0o775, True)
with open(ref_path, "w") as ref_file:
json.dump(definition, ref_file, indent=2)
def inject_schema(schema, remote_ref, reload=False):
"""
Given a parent schema and a remote ref
1. get the remote ref schema
2. create a local reference name (without path separators)
3. insert into components.schemas
4. replace remote references with local references
returns True if resolved and injected
"""
local_name = rename_ref(remote_ref)
local_ref = f"#/components/schemas/{local_name}"
# get schema from cache if present
ref_schema = None if reload else get_cached_ref(remote_ref)
if not ref_schema:
print(f"ref not cached: {remote_ref}")
ref_schema = resolve_ref(remote_ref)
downgrade_schema_30x_compatible(ref_schema)
store_cached_ref(remote_ref, ref_schema)
if ref_schema is not None:
nested_replace(schema, "$ref", remote_ref, local_ref)
schema["components"]["schemas"][local_name] = ref_schema
return True
else:
return False
def import_remote_refs(swagger_config, reload=False):
"""
inject the following remote refs into the schema
and replace the remote refs with local refs
returns True if all schemas resolved and injected
"""
# For some reason importing Feature or FeatureCollection
# makes the schema fail to validate
ref_imports = [
"https://geojson.org/schema/FeatureCollection.json",
"https://geojson.org/schema/Feature.json",
"https://geojson.org/schema/LineString.json",
"https://geojson.org/schema/MultiLineString.json",
"https://geojson.org/schema/MultiPoint.json",
"https://geojson.org/schema/MultiPolygon.json",
"https://geojson.org/schema/Point.json",
"https://geojson.org/schema/Polygon.json",
]
return all([inject_schema(swagger_config, ref, reload) for ref in ref_imports])
def configure_flask(swagger_config):
......@@ -213,11 +372,19 @@ def get_options():
help="Save output to schema file",
default=False,
)
parser.add_argument(
"-r",
"--reload",
dest="reload_schemas",
action="store_true",
help="Overwrite local copies of remote reference schemas",
default=False,
)
parser.add_argument("filename", nargs="?", default="project/soar/swagger.json")
args = parser.parse_args()
config = vars(args)
# If no flag is specified default to running the flask server
if all(v is False for v in config.values()):
if not (config["run_flask"] or config["output_file"]):
config["run_flask"] = True
return config
......@@ -226,6 +393,8 @@ if __name__ == "__main__":
# Parse script args
config = get_options()
swagger_config = get_swagger_config(config.get("reload_schemas"))
# Output compiled schema
if config.get("output_file"):
write_schema(swagger_config, config.get("filename"))
......
......@@ -2,6 +2,7 @@ test-js:
stage: test
script:
- cd tests-js/docker
- export COMPOSE_IGNORE_ORPHANS=True
- docker compose up --build
tags:
- shell
......
This diff is collapsed.
# Local store for remote refs
You don't want to retrieve remote refs live for reliability and change
control.
The schema retains the references to the remotes.
The actual schemas are stored and committed locally.
This means if there are breaking changes you can decide how and when to
move to the new definitions.
The validation is then running against a collection of local refs.
\ No newline at end of file
{
"title": "GeoJSON Feature",
"type": "object",
"required": [
"type",
"properties",
"geometry"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Feature"
]
},
"id": {
"oneOf": [
{
"type": "number"
},
{
"type": "string"
}
]
},
"properties": {
"oneOf": [
{
"type": "object"
}
]
},
"geometry": {
"oneOf": [
{
"title": "GeoJSON Point",
"type": "object",
"required": [
"type",
"coordinates"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Point"
]
},
"coordinates": {
"type": "array",
"minItems": 2,
"items": {
"type": "number"
}
},
"bbox": {
"type": "array",
"minItems": 4,
"items": {
"type": "number"
}
}
}
},
{
"title": "GeoJSON LineString",
"type": "object",
"required": [
"type",
"coordinates"
],
"properties": {
"type": {
"type": "string",
"enum": [
"LineString"
]
},
"coordinates": {
"type": "array",
"minItems": 2,
"items": {
"type": "array",
"minItems": 2,
"items": {
"type": "number"
}
}
},
"bbox": {
"type": "array",
"minItems": 4,
"items": {
"type": "number"
}
}
}
},
{
"title": "GeoJSON Polygon",
"type": "object",
"required": [
"type",
"coordinates"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Polygon"
]
},
"coordinates": {
"type": "array",
"items": {
"type": "array",
"minItems": 4,
"items": {
"type": "array",
"minItems": 2,
"items": {
"type": "number"
}
}
}
},
"bbox": {
"type": "array",
"minItems": 4,
"items": {
"type": "number"
}
}
}
},
{
"title": "GeoJSON MultiPoint",
"type": "object",
"required": [
"type",
"coordinates"
],
"properties": {
"type": {
"type": "string",
"enum": [
"MultiPoint"
]
},
"coordinates": {
"type": "array",
"items": {
"type": "array",
"minItems": 2,
"items": {
"type": "number"
}
}
},
"bbox": {
"type": "array",
"minItems": 4,
"items": {
"type": "number"
}
}
}
},
{
"title": "GeoJSON MultiLineString",
"type": "object",
"required": [
"type",
"coordinates"
],
"properties": {
"type": {
"type": "string",
"enum": [
"MultiLineString"
]
},
"coordinates": {
"type": "array",
"items": {
"type": "array",
"minItems": 2,
"items": {
"type": "array",
"minItems": 2,
"items": {
"type": "number"
}
}
}
},
"bbox": {
"type": "array",
"minItems": 4,
"items": {
"type": "number"
}
}
}
},
{
"title": "GeoJSON MultiPolygon",
"type": "object",
"required": [
"type",
"coordinates"
],
"properties": {
"type": {
"type": "string",
"enum": [
"MultiPolygon"
]
},
"coordinates": {
"type": "array",
"items": {
"type": "array",
"items": {
"type": "array",
"minItems": 4,
"items": {
"type": "array",
"minItems": 2,
"items": {
"type": "number"
}
}
}
}
},
"bbox": {
"type": "array",
"minItems": 4,
"items": {
"type": "number"
}
}
}
},
{
"title": "GeoJSON GeometryCollection",
"type": "object",
"required": [
"type",
"geometries"
],
"properties": {
"type": {
"type": "string",
"enum": [
"GeometryCollection"
]
},
"geometries": {
"type": "array",
"items": {
"oneOf": [
{
"title": "GeoJSON Point",
"type": "object",
"required": [
"type",
"coordinates"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Point"
]
},
"coordinates": {
"type": "array",
"minItems": 2,
"items": {
"type": "number"
}
},
"bbox": {
"type": "array",
"minItems": 4,
"items": {
"type": "number"
}
}
}
},
{
"title": "GeoJSON LineString",
"type": "object",
"required": [
"type",
"coordinates"
],
"properties": {
"type": {
"type": "string",
"enum": [
"LineString"
]
},
"coordinates": {
"type": "array",
"minItems": 2,
"items": {
"type": "array",
"minItems": 2,
"items": {
"type": "number"
}
}
},
"bbox": {
"type": "array",
"minItems": 4,
"items": {
"type": "number"
}
}
}
},
{
"title": "GeoJSON Polygon",
"type": "object",
"required": [
"type",
"coordinates"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Polygon"
]
},
"coordinates": {
"type": "array",
"items": {
"type": "array",
"minItems": 4,
"items": {
"type": "array",
"minItems": 2,
"items": {
"type": "number"
}
}
}
},
"bbox": {
"type": "array",
"minItems": 4,
"items": {
"type": "number"
}
}
}
},
{
"title": "GeoJSON MultiPoint",
"type": "object",
"required": [
"type",
"coordinates"
],
"properties": {
"type": {
"type": "string",
"enum": [
"MultiPoint"
]
},
"coordinates": {
"type": "array",
"items": {
"type": "array",
"minItems": 2,
"items": {
"type": "number"
}
}
},
"bbox": {
"type": "array",
"minItems": 4,
"items": {
"type": "number"
}
}
}
},
{
"title": "GeoJSON MultiLineString",
"type": "object",
"required": [
"type",
"coordinates"
],
"properties": {
"type": {
"type": "string",
"enum": [
"MultiLineString"
]
},
"coordinates": {
"type": "array",
"items": {
"type": "array",
"minItems": 2,
"items": {
"type": "array",
"minItems": 2,
"items": {
"type": "number"
}
}
}
},
"bbox": {
"type": "array",
"minItems": 4,
"items": {
"type": "number"
}
}
}
},
{
"title": "GeoJSON MultiPolygon",
"type": "object",
"required": [
"type",
"coordinates"
],
"properties": {
"type": {
"type": "string",
"enum": [
"MultiPolygon"
]
},
"coordinates": {
"type": "array",
"items": {
"type": "array",
"items": {
"type": "array",
"minItems": 4,
"items": {
"type": "array",
"minItems": 2,
"items": {
"type": "number"
}
}
}
}
},
"bbox": {
"type": "array",
"minItems": 4,
"items": {
"type": "number"
}
}
}
}
]
}
},
"bbox": {
"type": "array",
"minItems": 4,
"items": {
"type": "number"
}
}
}
}
]
},
"bbox": {
"type": "array",
"minItems": 4,
"items": {
"type": "number"
}
}
}
}
\ No newline at end of file
{
"title": "GeoJSON FeatureCollection",
"type": "object",
"required": [
"type",
"features"
],
"properties": {
"type": {
"type": "string",
"enum": [
"FeatureCollection"
]
},
"features": {
"type": "array",
"items": {
"title": "GeoJSON Feature",
"type": "object",
"required": [
"type",
"properties",
"geometry"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Feature"
]
},
"id": {
"oneOf": [
{
"type": "number"
},
{
"type": "string"
}
]
},
"properties": {
"oneOf": [
{
"type": "object"
}
]
},
"geometry": {
"oneOf": [
{
"title": "GeoJSON Point",
"type": "object",
"required": [
"type",
"coordinates"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Point"
]
},
"coordinates": {
"type": "array",
"minItems": 2,
"items": {
"type": "number"
}
},
"bbox": {
"type": "array",
"minItems": 4,
"items": {
"type": "number"
}
}
}
},
{
"title": "GeoJSON LineString",
"type": "object",
"required": [
"type",
"coordinates"
],
"properties": {
"type": {
"type": "string",
"enum": [
"LineString"
]
},
"coordinates": {
"type": "array",
"minItems": 2,
"items": {
"type": "array",
"minItems": 2,
"items": {
"type": "number"
}
}
},
"bbox": {
"type": "array",
"minItems": 4,
"items": {
"type": "number"
}
}
}
},
{
"title": "GeoJSON Polygon",
"type": "object",
"required": [
"type",
"coordinates"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Polygon"
]
},
"coordinates": {
"type": "array",
"items": {
"type": "array",
"minItems": 4,
"items": {
"type": "array",
"minItems": 2,
"items": {
"type": "number"
}
}
}
},
"bbox": {
"type": "array",
"minItems": 4,
"items": {
"type": "number"
}
}
}
},
{
"title": "GeoJSON MultiPoint",
"type": "object",
"required": [
"type",
"coordinates"
],
"properties": {
"type": {
"type": "string",
"enum": [
"MultiPoint"
]
},
"coordinates": {
"type": "array",
"items": {
"type": "array",
"minItems": 2,
"items": {
"type": "number"
}
}
},
"bbox": {
"type": "array",
"minItems": 4,
"items": {
"type": "number"
}
}
}
},
{
"title": "GeoJSON MultiLineString",
"type": "object",
"required": [
"type",
"coordinates"
],
"properties": {
"type": {
"type": "string",
"enum": [
"MultiLineString"
]
},
"coordinates": {
"type": "array",
"items": {
"type": "array",
"minItems": 2,
"items": {
"type": "array",
"minItems": 2,
"items": {
"type": "number"
}
}
}
},
"bbox": {
"type": "array",
"minItems": 4,
"items": {
"type": "number"
}
}
}
},
{
"title": "GeoJSON MultiPolygon",
"type": "object",
"required": [
"type",
"coordinates"
],
"properties": {
"type": {
"type": "string",
"enum": [
"MultiPolygon"
]
},
"coordinates": {
"type": "array",
"items": {
"type": "array",
"items": {
"type": "array",
"minItems": 4,
"items": {
"type": "array",
"minItems": 2,
"items": {
"type": "number"
}
}
}
}
},
"bbox": {
"type": "array",
"minItems": 4,
"items": {
"type": "number"
}
}
}
},
{
"title": "GeoJSON GeometryCollection",
"type": "object",
"required": [
"type",
"geometries"
],
"properties": {
"type": {
"type": "string",
"enum": [
"GeometryCollection"
]
},
"geometries": {
"type": "array",
"items": {
"oneOf": [
{
"title": "GeoJSON Point",
"type": "object",
"required": [
"type",
"coordinates"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Point"
]
},
"coordinates": {
"type": "array",
"minItems": 2,
"items": {
"type": "number"
}
},
"bbox": {
"type": "array",
"minItems": 4,
"items": {
"type": "number"
}
}
}
},
{
"title": "GeoJSON LineString",
"type": "object",
"required": [
"type",
"coordinates"
],
"properties": {
"type": {
"type": "string",
"enum": [
"LineString"
]
},
"coordinates": {
"type": "array",
"minItems": 2,
"items": {
"type": "array",
"minItems": 2,
"items": {
"type": "number"
}
}
},
"bbox": {
"type": "array",
"minItems": 4,
"items": {
"type": "number"
}
}
}
},
{
"title": "GeoJSON Polygon",
"type": "object",
"required": [
"type",
"coordinates"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Polygon"
]
},
"coordinates": {
"type": "array",
"items": {
"type": "array",
"minItems": 4,
"items": {
"type": "array",
"minItems": 2,
"items": {
"type": "number"
}
}
}
},
"bbox": {
"type": "array",
"minItems": 4,
"items": {
"type": "number"
}
}
}
},
{
"title": "GeoJSON MultiPoint",
"type": "object",
"required": [
"type",
"coordinates"
],
"properties": {
"type": {
"type": "string",
"enum": [
"MultiPoint"
]
},
"coordinates": {
"type": "array",
"items": {
"type": "array",
"minItems": 2,
"items": {
"type": "number"
}
}
},
"bbox": {
"type": "array",
"minItems": 4,
"items": {
"type": "number"
}
}
}
},
{
"title": "GeoJSON MultiLineString",
"type": "object",
"required": [
"type",
"coordinates"
],
"properties": {
"type": {
"type": "string",
"enum": [
"MultiLineString"
]
},
"coordinates": {
"type": "array",
"items": {
"type": "array",
"minItems": 2,
"items": {
"type": "array",
"minItems": 2,
"items": {
"type": "number"
}
}
}
},
"bbox": {
"type": "array",
"minItems": 4,
"items": {
"type": "number"
}
}
}
},
{
"title": "GeoJSON MultiPolygon",
"type": "object",
"required": [
"type",
"coordinates"
],
"properties": {
"type": {
"type": "string",
"enum": [
"MultiPolygon"
]
},
"coordinates": {
"type": "array",
"items": {
"type": "array",
"items": {
"type": "array",
"minItems": 4,
"items": {
"type": "array",
"minItems": 2,
"items": {
"type": "number"
}
}
}
}
},
"bbox": {
"type": "array",
"minItems": 4,
"items": {
"type": "number"
}
}
}
}
]
}
},
"bbox": {
"type": "array",
"minItems": 4,
"items": {
"type": "number"
}
}
}
}
]
},
"bbox": {
"type": "array",
"minItems": 4,
"items": {
"type": "number"
}
}
}
}
},
"bbox": {
"type": "array",
"minItems": 4,
"items": {
"type": "number"
}
}
}
}
\ No newline at end of file
{
"title": "GeoJSON LineString",
"type": "object",
"required": [
"type",
"coordinates"
],
"properties": {
"type": {
"type": "string",
"enum": [
"LineString"
]
},
"coordinates": {
"type": "array",
"minItems": 2,
"items": {
"type": "array",
"minItems": 2,
"items": {
"type": "number"
}
}
},
"bbox": {
"type": "array",
"minItems": 4,
"items": {
"type": "number"
}
}
}
}
\ No newline at end of file
{
"title": "GeoJSON MultiLineString",
"type": "object",
"required": [
"type",
"coordinates"
],
"properties": {
"type": {
"type": "string",
"enum": [
"MultiLineString"
]
},
"coordinates": {
"type": "array",
"items": {
"type": "array",
"minItems": 2,
"items": {
"type": "array",
"minItems": 2,
"items": {
"type": "number"
}
}
}
},
"bbox": {
"type": "array",
"minItems": 4,
"items": {
"type": "number"
}
}
}
}
\ No newline at end of file
{
"title": "GeoJSON MultiPoint",
"type": "object",
"required": [
"type",
"coordinates"
],
"properties": {
"type": {
"type": "string",
"enum": [
"MultiPoint"
]
},
"coordinates": {
"type": "array",
"items": {
"type": "array",
"minItems": 2,
"items": {
"type": "number"
}
}
},
"bbox": {
"type": "array",
"minItems": 4,
"items": {
"type": "number"
}
}
}
}
\ No newline at end of file
{
"title": "GeoJSON MultiPolygon",
"type": "object",
"required": [
"type",
"coordinates"
],
"properties": {
"type": {
"type": "string",
"enum": [
"MultiPolygon"
]
},
"coordinates": {
"type": "array",
"items": {
"type": "array",
"items": {
"type": "array",
"minItems": 4,
"items": {
"type": "array",
"minItems": 2,
"items": {
"type": "number"
}
}
}
}
},
"bbox": {
"type": "array",
"minItems": 4,
"items": {
"type": "number"
}
}
}
}
\ No newline at end of file
{
"title": "GeoJSON Point",
"type": "object",
"required": [
"type",
"coordinates"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Point"
]
},
"coordinates": {
"type": "array",
"minItems": 2,
"items": {
"type": "number"
}
},
"bbox": {
"type": "array",
"minItems": 4,
"items": {
"type": "number"
}
}
}
}
\ No newline at end of file
{
"title": "GeoJSON Polygon",
"type": "object",
"required": [
"type",
"coordinates"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Polygon"
]
},
"coordinates": {
"type": "array",
"items": {
"type": "array",
"minItems": 4,
"items": {
"type": "array",
"minItems": 2,
"items": {
"type": "number"
}
}
}
},
"bbox": {
"type": "array",
"minItems": 4,
"items": {
"type": "number"
}
}
}
}
\ No newline at end of file
......@@ -3,5 +3,5 @@ Flask
flask-restx
flasgger
flask-marshmallow
openapi-schema-validator==0.4.1
openapi-spec-validator==0.5.2
\ No newline at end of file
openapi-schema-validator==0.6.2
openapi-spec-validator==0.7.1
\ No newline at end of file