Browse Source

Add an ISP schema validator

Gu1 11 years ago
parent
commit
646018a1f2

+ 17 - 0
ffdnispdb/schemas/address.json

@@ -0,0 +1,17 @@
+{
+    "description": "An Address following the convention of http://microformats.org/wiki/hcard",
+    "type": "object",
+    "properties": {
+        "post-office-box": { "type": "string" },
+        "extended-address": { "type": "string" },
+        "street-address": { "type": "string" },
+        "locality":{ "type": "string", "required": true },
+        "region": { "type": "string", "required": true },
+        "postal-code": { "type": "string" },
+        "country-name": { "type": "string", "required": true}
+    },
+    "dependencies": {
+        "post-office-box": "street-address",
+        "extended-address": "street-address"
+    }
+}

+ 8 - 0
ffdnispdb/schemas/geo.json

@@ -0,0 +1,8 @@
+{
+    "description": "A geographical coordinate",
+    "type": "object",
+    "properties": {
+        "latitude": { "type": "number" },
+        "longitude": { "type": "number" }
+    }
+}

+ 8 - 0
ffdnispdb/schemas/geojson/bbox.json

@@ -0,0 +1,8 @@
+{
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "id": "http://json-schema.org/geojson/bbox.json#",
+    "description": "A bounding box as defined by GeoJSON",
+    "FIXME": "unenforceable constraint: even number of elements in array",
+    "type": "array",
+    "items": { "type": "number" }
+}

+ 54 - 0
ffdnispdb/schemas/geojson/crs.json

@@ -0,0 +1,54 @@
+{
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "title": "crs",
+    "description": "a Coordinate Reference System object",
+    "type": [ "object", "null" ],
+    "required": [ "type", "properties" ],
+    "properties": {
+        "type": { "type": "string" },
+        "properties": { "type": "object" }
+    },
+    "additionalProperties": false,
+    "oneOf": [
+        { "$ref": "#/definitions/namedCrs" },
+        { "$ref": "#/definitions/linkedCrs" }
+    ],
+    "definitions": {
+        "namedCrs": {
+            "properties": {
+                "type": { "enum": [ "name" ] },
+                "properties": {
+                    "required": [ "name" ],
+                    "additionalProperties": false,
+                    "properties": {
+                        "name": {
+                            "type": "string",
+                            "FIXME": "semantic validation necessary"
+                        }
+                    }
+                }
+            }
+        },
+        "linkObject": {
+            "type": "object",
+            "required": [ "href" ],
+            "properties": {
+                "href": {
+                    "type": "string",
+                    "format": "uri",
+                    "FIXME": "spec says \"dereferenceable\", cannot enforce that"
+                },
+                "type": {
+                    "type": "string",
+                    "description": "Suggested values: proj4, ogjwkt, esriwkt"
+                }
+            }
+        },
+        "linkedCRS": {
+            "properties": {
+                "type": { "enum": [ "link" ] },
+                "properties": { "$ref": "#/definitions/linkedObject" }
+            }
+        }
+    }
+}

+ 61 - 0
ffdnispdb/schemas/geojson/geojson.json

@@ -0,0 +1,61 @@
+{
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "id": "http://json-schema.org/geojson/geojson.json#",
+    "title": "Geo JSON object",
+    "description": "Schema for a Geo JSON object",
+    "type": "object",
+    "required": [ "type" ],
+    "properties": {
+        "crs": { "$ref": "http://json-schema.org/geojson/crs.json#" },
+        "bbox": { "$ref": "http://json-schema.org/geojson/bbox.json#" }
+    },
+    "oneOf": [
+        { "$ref": "http://json-schema.org/geojson/geometry.json#" },
+        { "$ref": "#/definitions/geometryCollection" },
+        { "$ref": "#/definitions/feature" },
+        { "$ref": "#/definitions/featureCollection" }
+    ],
+    "definitions": {
+        "geometryCollection": {
+            "title": "GeometryCollection",
+            "description": "A collection of geometry objects",
+            "required": [ "geometries" ],
+            "properties": {
+                "type": { "enum": [ "GeometryCollection" ] },
+                "geometries": {
+                    "type": "array",
+                    "items": { "$ref": "http://json-schema.org/geojson/geometry.json#" }
+                }
+            }
+        },
+        "feature": {
+            "title": "Feature",
+            "description": "A Geo JSON feature object",
+            "required": [ "geometry", "properties" ],
+            "properties": {
+                "type": { "enum": [ "Feature" ] },
+                "geometry": {
+                    "oneOf": [
+                        { "type": "null" },
+                        { "$ref": "http://json-schema.org/geojson/geometry.json#" }
+                    ]
+                },
+                "properties": { "type": [ "object", "null" ] },
+                "id": { "FIXME": "may be there, type not known (string? number?)" }
+            }
+        },
+        "featureCollection": {
+            "title": "FeatureCollection",
+            "description": "A Geo JSON feature collection",
+            "required": [ "features" ],
+            "properties": {
+                "type": { "enum": [ "FeatureCollection" ] },
+                "features": {
+                    "type": "array",
+                    "items": { "$ref": "#/definitions/feature" }
+                }
+            }
+        }
+    }
+}
+

+ 91 - 0
ffdnispdb/schemas/geojson/geometry.json

@@ -0,0 +1,91 @@
+{
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "id": "http://json-schema.org/geojson/geometry.json#",
+    "title": "geometry",
+    "description": "One geometry as defined by GeoJSON",
+    "type": "object",
+    "required": [ "type", "coordinates" ],
+    "oneOf": [
+        {
+            "title": "Point",
+            "properties": {
+                "type": { "enum": [ "Point" ] },
+                "coordinates": { "$ref": "#/definitions/position" }
+            }
+        },
+        {
+            "title": "MultiPoint",
+            "properties": {
+                "type": { "enum": [ "MultiPoint" ] },
+                "coordinates": { "$ref": "#/definitions/positionArray" }
+            }
+        },
+        {
+            "title": "LineString",
+            "properties": {
+                "type": { "enum": [ "LineString" ] },
+                "coordinates": { "$ref": "#/definitions/lineString" }
+            }
+        },
+        {
+            "title": "MultiLineString",
+            "properties": {
+                "type": { "enum": [ "MultiLineString" ] },
+                "coordinates": {
+                    "type": "array",
+                    "items": { "$ref": "#/definitions/lineString" }
+                }
+            }
+        },
+        {
+            "title": "Polygon",
+            "properties": {
+                "type": { "enum": [ "Polygon" ] },
+                "coordinates": { "$ref": "#/definitions/polygon" }
+            }
+        },
+        {
+            "title": "MultiPolygon",
+            "properties": {
+                "type": { "enum": [ "MultiPolygon" ] },
+                "coordinates": {
+                    "type": "array",
+                    "items": { "$ref": "#/definitions/polygon" }
+                }
+            }
+        }
+    ],
+    "definitions": {
+        "position": {
+            "description": "A single position",
+            "type": "array",
+            "minItems": 2,
+            "items": [ { "type": "number" }, { "type": "number" } ],
+            "additionalItems": false
+        },
+        "positionArray": {
+            "description": "An array of positions",
+            "type": "array",
+            "items": { "$ref": "#/definitions/position" }
+        },
+        "lineString": {
+            "description": "An array of two or more positions",
+            "allOf": [
+                { "$ref": "#/definitions/positionArray" },
+                { "minItems": 2 }
+            ]
+        },
+        "linearRing": {
+            "description": "An array of four positions where the first equals the last",
+            "allOf": [
+                { "$ref": "#/definitions/positionArray" },
+                { "minItems": 4 }
+            ]
+        },
+        "polygon": {
+            "description": "An array of linear rings",
+            "type": "array",
+            "items": { "$ref": "#/definitions/linearRing" }
+        }
+    }
+}

+ 120 - 0
ffdnispdb/schemas/isp-0.1.json

@@ -0,0 +1,120 @@
+{
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "id": "http://ffdn.org/ispschema/draft-01/schema#",
+    "title": "ISP",
+    "description": "Object describing a local ISP",
+    "type": "object",
+    "required": ["name", "email", "memberCount", "subscriberCount", "coveredAreas", "version"],
+
+    "properties": {
+        "name": {
+            "description": "The ISP's name",
+            "type": "string"
+        },
+        "shortname": {
+            "description": "Shorter name",
+            "type": "string",
+            "maxLength": 15
+        },
+        "description": {
+            "description": "Short text describing the project",
+            "type": "string"
+        },
+        "logoURL": {
+            "description": "HTTP(S) URL of the ISP's logo",
+            "type": "string",
+            "format": "uri"
+        },
+        "website": {
+            "description": "URL to the official website",
+            "type": "string",
+            "format": "uri"
+        },
+        "otherWebsites": {
+            "description": "Additional websites hosted by the ISP (wiki, etherpad, ..)",
+            "type": "object",
+            "patternProperties": {
+                "^.+$": { "type": "string", "format": "uri" }
+            },
+            "additionalProperties": false
+        },
+        "email": {
+            "description": "Contact email address",
+            "type": "string",
+            "format": "email"
+        },
+        "mainMailingList": {
+            "description": "Main public mailing-list",
+            "type": "string",
+            "format": "email"
+        },
+        "creationDate": {
+            "description": "Date of creation for legal structure, in ISO8601 format",
+            "type": "string"
+        },
+        "ffdnMemberSince": {
+            "description": "Date at wich the ISP joined the Federation, in ISO8601 format",
+            "type": "string"
+        },
+        "progressStatus": {
+            "description": "Progression status of the ISP",
+            "type": "integer",
+            "minimum": 1,
+            "maximum": 7
+        },
+        "memberCount": {
+            "description": "Number of members",
+            "type": "integer",
+            "minimum": 0
+        },
+        "subscriberCount": {
+            "description": "Number of subscribers to an internet access",
+            "type": "integer",
+            "minimum": 0
+        },
+        "chatrooms": {
+            "description": "URIs to the various chat-rooms",
+            "type": "array",
+            "items": { "type": "string", "format": "uri" },
+            "uniqueItems": true
+        },
+        "registeredOffice": {
+            "description": "address of the registered office (siège social)",
+            "$ref": "http://json-schema.org/address"
+        },
+        "coordinates": {
+            "description": "Coordinates of the registered office",
+            "$ref": "http://json-schema.org/geo"
+        },
+        "coveredAreas": {
+            "description": "Array of covered area in GeoJSON format",
+            "type": "array",
+            "items": { "$ref": "#/definitions/coveredArea" }
+        },
+        "version": {
+            "description": "Version of the specification implemented",
+            "type": "number",
+            "enum": [0.1]
+        }
+    },
+
+    "definitions": {
+        "coveredArea": {
+            "type": "object",
+            "required": ["name", "technologies"],
+            "properties": {
+                "name": {
+                    "type": "string"
+                },
+                "technologies": {
+                    "type": "array",
+                    "items": { "enum": [ "ftth", "dsl", "wifi" ] }
+                },
+                "area": {
+                    "$ref": "http://json-schema.org/geojson/geojson.json#"
+                }
+            },
+            "additionalProperties": false
+        }
+    }
+}

+ 102 - 0
ffdnispdb/schemavalidator.py

@@ -0,0 +1,102 @@
+# -*- coding: utf-8 -*-
+
+from jsonschema import Draft4Validator, RefResolver, draft4_format_checker
+from jsonschema.exceptions import RefResolutionError, SchemaError, ValidationError
+import json
+import os.path
+from urlparse import urlparse
+
+
+class MyRefResolver(RefResolver):
+    def resolve_remote(self, uri):
+        # Prevent remote resolving
+        raise RefResolutionError("LOL NOPE")
+
+
+def load_schema(name):
+    """
+    Load a schema from ./schemas/``name``.json and return it.
+
+    """
+    schemadir = os.path.join(
+        os.path.dirname(os.path.abspath(__file__)),
+        'schemas'
+    )
+    schemapath = os.path.join(schemadir, '%s.json' % (name,))
+    with open(schemapath) as f:
+        return json.load(f)
+
+
+schemas={
+    0.1: load_schema('isp-0.1')
+}
+
+resources={
+    'http://json-schema.org/geo': load_schema('geo'),
+    'http://json-schema.org/address': load_schema('address'),
+    'http://json-schema.org/geojson/geojson.json#': load_schema('geojson/geojson'),
+    'http://json-schema.org/geojson/geometry.json#': load_schema('geojson/geometry'),
+    'http://json-schema.org/geojson/bbox.json#': load_schema('geojson/bbox'),
+    'http://json-schema.org/geojson/crs.json#': load_schema('geojson/crs'),
+}
+
+def validate_diyisp(jdict):
+    """
+    Validate a json-object against the diyisp json-schema
+    """
+    if not 'version' in jdict:
+        raise ValidationError(u'version is a required property')
+    try:
+        schema=schemas.get(jdict['version'])
+    except (AttributeError, TypeError):
+        raise ValidationError(u'version %r unsupported'%jdict['version'])
+
+    v=Draft4Validator(
+        schema,
+        resolver=MyRefResolver.from_schema(schema, store=resources),
+        format_checker=draft4_format_checker,
+    )
+
+    for err in v.iter_errors(jdict):
+        yield err
+
+    def is_valid_url(u):
+        try:
+            pu=urlparse(u)
+        except:
+            return False
+        if pu.scheme not in ('', 'http', 'https'):
+            return False
+        if not pu.netloc:
+            return False
+        return True
+
+    if 'website' in jdict and not is_valid_url(jdict['website']):
+        yield ValidationError(u'%r must be an absolute HTTP URL'%u'website',
+                              instance=jdict[u'website'], schema=schema[u'properties'][u'website'],
+                              path=[u'website'], schema_path=[u'properties', u'website', u'description'],
+                              validator=u'validate_url', validator_value=jdict['website'])
+
+    if 'logoURL' in jdict and not is_valid_url(jdict['logoURL']):
+        yield ValidationError(u'%r must be an absolute HTTP URL'%u'logoURL',
+                              instance=jdict[u'logoURL'], schema=schema[u'properties'][u'logoURL'],
+                              path=[u'logoURL'], schema_path=[u'properties', u'logoURL', u'description'],
+                              validator=u'validate_url', validator_value=jdict['logoURL'])
+
+    sch=schema[u'properties'][u'otherWebsites'][u'patternProperties'][u'^.+$']
+    for name, url in jdict.get('otherWebsites', {}).iteritems():
+        if is_valid_url(url):
+            continue
+        yield ValidationError(u'%r must be an absolute HTTP URL'%name,
+                              instance=url, schema=sch, path=[u'otherWebsite', name],
+                              schema_path=[u'properties', u'otherWebsites', u'patternProperties', u'^.+$', 'description'],
+                              validator=u'validate_url', validator_value=url)
+
+
+if __name__ == '__main__':
+    import sys
+    j=json.load(open(sys.argv[1]))
+    for e in validate_diyisp(j):
+        print e
+
+

+ 1 - 0
requirements.txt

@@ -8,3 +8,4 @@ Werkzeug==0.9.3
 argparse==1.2.1
 itsdangerous==0.23
 wsgiref==0.1.2
+jsonschema==2.0.0