diff --git a/103280/TS_103_280.schema.json b/103280/TS_103_280.schema.json index a6cca0c8e5af008e2ec6a4a572ec45b2f38f4e77..339c478cca85e9c82505d03568e2d28be18a3329 100644 --- a/103280/TS_103_280.schema.json +++ b/103280/TS_103_280.schema.json @@ -1,4 +1,5 @@ { +<<<<<<< HEAD "$id": "ts_103280_2017_07", "$defs": { "ShortString": { @@ -399,3 +400,393 @@ } } } +======= + "$id": "ts_103280_2017_07", + "$defs": { + "ShortString": { + "type": "string", + "maxLength": 255 + }, + "LongString": { + "type": "string", + "maxLength": 65535 + }, + "LIID": { + "type": "string", + "pattern": "^([!-~]{1,25})|([0-9a-f]{26,50})$" + }, + "UTCDateTime": { + "type": "string", + "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$" + }, + "UTCMicrosecondDateTime": { + "type": "string", + "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{6}Z$" + }, + "QualifiedDateTime": { + "type": "string", + "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(Z|[+-][0-9]{2}:[0-9]{2})$" + }, + "QualifiedMicrosecondDateTime": { + "type": "string", + "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{6}(Z|[+-][0-9]{2}:[0-9]{2})$" + }, + "InternationalE164": { + "type": "string", + "pattern": "^[0-9]{1,15}$" + }, + "IMSI": { + "type": "string", + "pattern": "^[0-9]{6,15}$" + }, + "IMEI": { + "type": "string", + "pattern": "^[0-9]{14}$" + }, + "IMEICheckDigit": { + "type": "string", + "pattern": "^[0-9]{15}$" + }, + "IMEISV": { + "type": "string", + "pattern": "^[0-9]{16}$" + }, + "IPv4Address": { + "type": "string", + "pattern": "^((25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])$" + }, + "IPv4CIDR": { + "type": "string", + "pattern": "^((25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])/([1-2]?[0-9]|3[0-2])$" + }, + "IPv6Address": { + "type": "string", + "pattern": "^([0-9a-f]{4}:){7}([0-9a-f]{4})$" + }, + "IPv6CIDR": { + "type": "string", + "pattern": "^([0-9a-f]{4}:){7}([0-9a-f]{4})/(([1-9][0-9]?)|(1[0-1][0-9])|(12[0-8]))$" + }, + "TCPPort": { + "type": "integer", + "exclusiveMinimum": 1, + "maximum": 65535 + }, + "UDPPort": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "MACAddress": { + "type": "string", + "pattern": "^([a-f0-9]{2}:){5}[a-f0-9]{2}$" + }, + "EmailAddress": { + "allOf": [ + { + "$ref": "#/$defs/ShortString" + }, + { + "type": "string", + "pattern": "^[a-zA-Z0-9\\.!#$%&'\\*\\+\\\\/=\\?\\^_`\\{\\|\\}~\\-]+@[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$" + } + ] + }, + "UUID": { + "type": "string", + "pattern": "^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$" + }, + "ISOCountryCode": { + "type": "string", + "pattern": "^[A-Z]{2}$" + }, + "SIPURI": { + "type": "string", + "pattern": "^sips?:[a-zA-Z0-9!#$&-;=?-\\[\\]_~%]+$" + }, + "TELURI": { + "type": "string", + "pattern": "^tel:[a-zA-Z0-9!#$&-;=?-\\[\\]_~%]+$" + }, + "WGS84LatitudeDecimal": { + "type": "string", + "pattern": "^[NS][0-9]{2}\\.[0-9]{6}$" + }, + "WGS84LongitudeDecimal": { + "type": "string", + "pattern": "^[EW][0-9]{3}\\.[0-9]{6}$" + }, + "WGS84LatitudeAngular": { + "type": "string", + "pattern": "^[NS][0-9]{6}\\.[0-9]{2}$" + }, + "WGS84LongitudeAngular": { + "type": "string", + "pattern": "^[EW][0-9]{7}\\.[0-9]{2}$" + }, + "SUPIIMSI": { + "$ref": "#/$defs/IMSI" + }, + "SUPINAI": { + "$ref": "#/$defs/NAI" + }, + "SUCI": { + "type": "string", + "pattern": "^([a-fA-F0-9]{2})*$" + }, + "PEIIMEI": { + "$ref": "#/$defs/IMEI" + }, + "PEIIMEICheckDigit": { + "$ref": "#/$defs/IMEICheckDigit" + }, + "PEIIMEISV": { + "$ref": "#/$defs/IMEISV" + }, + "GPSIMSISDN": { + "type": "string", + "pattern": "^[0-9]{1,15}$" + }, + "GPSINAI": { + "$ref": "#/$defs/NAI" + }, + "NAI": { + "type": "string" + }, + "LDID": { + "type": "string", + "pattern": "^([A-Z]{2}-.+-.+)$" + }, + "InternationalizedEmailAddress": { + "allOf": [ + { + "$ref": "#/$defs/ShortString" + }, + { + "type": "string", + "pattern": "^.+@.+$" + } + ] + }, + "EUI64": { + "type": "string", + "pattern": "^([a-f0-9]{2}:){7}[a-f0-9]{2}$" + }, + "CGI": { + "type": "string", + "pattern": "^[0-9]{3}-[0-9]{2,3}-[a-f0-9]{4}-[a-f0-9]{4}$" + }, + "ECGI": { + "type": "string", + "pattern": "^[0-9]{3}-[0-9]{2,3}-[a-f0-9]{7}$" + }, + "NCGI": { + "type": "string", + "pattern": "^[0-9]{3}-[0-9]{2,3}-[a-f0-9]{9}$" + }, + "ICCID": { + "type": "string", + "pattern": "^[0-9]{19,20}$" + }, + "IPProtocol": { + "type": "integer", + "minimum": 0, + "maximum": 255 + }, + "IPAddress": { + "oneOf": [ + { + "type": "object", + "properties": { + "etsi280:IPv4Address": { + "$ref": "#/$defs/IPv4Address" + } + }, + "required": [ + "etsi280:IPv4Address" + ] + }, + { + "type": "object", + "properties": { + "etsi280:IPv6Address": { + "$ref": "#/$defs/IPv6Address" + } + }, + "required": [ + "etsi280:IPv6Address" + ] + } + ] + }, + "IPCIDR": { + "oneOf": [ + { + "type": "object", + "properties": { + "etsi280:IPv4CIDR": { + "$ref": "#/$defs/IPv4CIDR" + } + }, + "required": [ + "etsi280:IPv4CIDR" + ] + }, + { + "type": "object", + "properties": { + "etsi280:IPv6CIDR": { + "$ref": "#/$defs/IPv6CIDR" + } + }, + "required": [ + "etsi280:IPv6CIDR" + ] + } + ] + }, + "TCPPortRange": { + "type": "object", + "properties": { + "etsi280:start": { + "$ref": "#/$defs/TCPPort" + }, + "etsi280:end": { + "$ref": "#/$defs/TCPPort" + } + }, + "required": [ + "etsi280:start", + "etsi280:end" + ] + }, + "UDPPortRange": { + "type": "object", + "properties": { + "etsi280:start": { + "$ref": "#/$defs/UDPPort" + }, + "etsi280:end": { + "$ref": "#/$defs/UDPPort" + } + }, + "required": [ + "etsi280:start", + "etsi280:end" + ] + }, + "Port": { + "oneOf": [ + { + "type": "object", + "properties": { + "etsi280:TCPPort": { + "$ref": "#/$defs/TCPPort" + } + }, + "required": [ + "etsi280:TCPPort" + ] + }, + { + "type": "object", + "properties": { + "etsi280:UDPPort": { + "$ref": "#/$defs/UDPPort" + } + }, + "required": [ + "etsi280:UDPPort" + ] + } + ] + }, + "PortRange": { + "oneOf": [ + { + "type": "object", + "properties": { + "etsi280:TCPPortRange": { + "$ref": "#/$defs/TCPPortRange" + } + }, + "required": [ + "etsi280:TCPPortRange" + ] + }, + { + "type": "object", + "properties": { + "etsi280:UDPPortRange": { + "$ref": "#/$defs/UDPPortRange" + } + }, + "required": [ + "etsi280:UDPPortRange" + ] + } + ] + }, + "IPAddressPort": { + "type": "object", + "properties": { + "etsi280:address": { + "$ref": "#/$defs/IPAddress" + }, + "etsi280:port": { + "$ref": "#/$defs/Port" + } + }, + "required": [ + "etsi280:address", + "etsi280:port" + ] + }, + "IPAddressPortRange": { + "type": "object", + "properties": { + "etsi280:address": { + "$ref": "#/$defs/IPAddress" + }, + "etsi280:portRange": { + "$ref": "#/$defs/PortRange" + } + }, + "required": [ + "etsi280:address", + "etsi280:portRange" + ] + }, + "WGS84CoordinateDecimal": { + "type": "object", + "properties": { + "etsi280:latitude": { + "$ref": "#/$defs/WGS84LatitudeDecimal" + }, + "etsi280:longitude": { + "$ref": "#/$defs/WGS84LongitudeDecimal" + } + }, + "required": [ + "etsi280:latitude", + "etsi280:longitude" + ] + }, + "WGS84CoordinateAngular": { + "type": "object", + "properties": { + "etsi280:latitude": { + "$ref": "#/$defs/WGS84LatitudeAngular" + }, + "etsi280:longitude": { + "$ref": "#/$defs/WGS84LongitudeAngular" + } + }, + "required": [ + "etsi280:latitude", + "etsi280:longitude" + ] + } + } + } +>>>>>>> 9858681dff53fe4c17c10beee5799d32c277614e diff --git a/103705/examples/csp_a.schema.json b/103705/examples/csp_a.schema.json new file mode 100644 index 0000000000000000000000000000000000000000..febb70a677066e6adfab844137c86cee48960a28 --- /dev/null +++ b/103705/examples/csp_a.schema.json @@ -0,0 +1,64 @@ +{ + "$id": "example.com_csp-a-schema_1.2.3", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "Example extended schema for CSP A. This CSP only ever has one person associated with a subscription - the account holder - and so composes a Subscription record from the ETSI-standard Subscription and PersonDetails entities", + "allOf": [ + { + "$ref": "http://etsi.org/temp/705" + }, + { + "type": "object", + "properties": { + "recordSet": { + "type": "array", + "items": { + "$ref": "#/$defs/definedRecords" + } + } + }, + "required": [ + "recordSet" + ] + } + ], + "$defs": { + "definedRecords": { + "title" : "Defined Records", + "description" : "Sets out what the valid set of Record types is for this CSP. It is expressed as a 'oneOf' list, each of which combines a type identifier and a reference to the type that is used to validate the record", + "oneOf": [ + { + "allOf": [ + { + "type": "object", + "title" : "Subscriber record", + "description" : "Uses the ETSI-standard subscriber record (the first $ref), extended to include person details (the second $ref)", + "properties": { + "type": { + "const": "Subscription" + } + } + }, + { + "description" : "Uses the basic ETSI-standard subscriber record fields as a base", + "$ref": "http://etsi.org/temp/705/entities#/$defs/Subscription" + }, + { + "description" : "Extends the basic ETSI standard subscriber record according to the definition in extendedSubscriberRecord", + "$ref": "#/$defs/extendedSubscriberRecord" + } + ] + } + ] + }, + "extendedSubscriberRecord" : { + "title" : "Extended Subscriber Record", + "description" : "Here CSP A can describe all the extra fields that they want to put in their subscriber record. In this case, they just add the details of the account holder using the ETSI-standard PersonDetails type", + "properties" : { + "title" : "Account Holder", + "description" : "Name and date of birth of the account holder for the description", + "accountHolder" : { "$ref" : "http://etsi.org/temp/705/entities#/$defs/PersonDetails"} + }, + "required" : ["personDetails"] + } + } +} \ No newline at end of file diff --git a/103705/examples/csp_a_results.json b/103705/examples/csp_a_results.json new file mode 100644 index 0000000000000000000000000000000000000000..e1424859479742c48f9bbfa862eabe132153dbcb --- /dev/null +++ b/103705/examples/csp_a_results.json @@ -0,0 +1,22 @@ +{ + "recordSetDescription" : { + "schemaId" : "etsi.org/ts_103_705/1.1.1", + "resultSetId" : "788e190d-fb2e-416e-9798-12a66ebe0a1a", + "queryReference" : "LDID_1", + "created" : "2023-06-08T09:31:56.000000Z", + "recordTypes" : { + "Subscription" : "example.com_csp-a-schema_1.2.3#$defs/extendedSubscriberRecord" + } + }, + "recordSet" : [ + { + "id" : "1e997322-b813-437b-b2e2-dae732f0cf7f", + "type" : "Subscription", + "subscriptionId" : "{unique CSP subscription ID}", + "personDetails" : { + "lastName" : "Last", + "firstName" : "First" + } + } + ] +} \ No newline at end of file diff --git a/103705/generate_docs.py b/103705/generate_docs.py new file mode 100644 index 0000000000000000000000000000000000000000..8837e1ffa86e27c7a09b690e6f575948496cd4e1 --- /dev/null +++ b/103705/generate_docs.py @@ -0,0 +1,2 @@ +import json +from pathlib import Path diff --git a/103705/schema/etsi_types.schema.json b/103705/schema/etsi_types.schema.json new file mode 100644 index 0000000000000000000000000000000000000000..34e34a748883c999b54708180f920de96b846fa7 --- /dev/null +++ b/103705/schema/etsi_types.schema.json @@ -0,0 +1,82 @@ +{ + "$id": "etsi.org_ts103705_1.3.1_etsi-types", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "ETSI TS 103 705 Types schema", + "description": "JSON schema for ETSI-defined types (see clause 6.1 and Annex A)", + "$defs": { + "Pointer" : { + "type" : "object", + "title" : "Pointer", + "description" : "Allows one Record in a RecordSet to point to data in another Record in the same Recordset. See clause 5.4 and Annex A.2.2", + "properties" : { + "destination" : { + "type" : "string", + "title" : "Pointer Destination", + "description" : "Identifies the Record in the RecordSet containined the referred-to data (see clause 5.3.2 and A.2.2)" + }, + "relationship" : { + "type" : "string", + "title" : "Relationship", + "description" : "Indicates the nature of the relationship. Valid values should be indicated by the CSP schema" + }, + "required" : ["destination"] + } + }, + "Subscription": { + "type": "object", + "title": "Subscription", + "description": "Basic subscription record", + "properties": { + "subscriptionId": { + "title" : "Subscription Identifier", + "description" : "Unique identifier for a subscription within the CSP", + "type": "string" + }, + "startDate": { + "title" : "Subscription Start Date", + "description" : "Date that the subscription started", + "$ref": "ts_103280_2017_07#/$defs/UTCDateTime" + }, + "endDate": { + "title" : "Subscription End Date", + "description" : "Date that the subscription ended", + "$ref": "ts_103280_2017_07#/$defs/UTCDateTime" + } + }, + "required" : ["subscriptionId"] + }, + "PersonDetails": { + "type": "object", + "title": "Person Details", + "description": "Details about a person", + "properties": { + "forename": { + "title" : "Forename", + "description" : "Forename(s)", + "type": "string" + }, + "middeName": { + "title" : "First Name", + "description" : "Middle name(s), given as a space-delimited string", + "type": "string" + }, + "lastName" : { + "title" : "Family Name", + "description" : "Family name(s)", + "type" : "string" + }, + "birthDate" : { + "title" : "Date of Birth", + "description" : "Date of birth (time part can be ignored)", + "$ref" : "ts_103280_2017_07#/$defs/DateTime" + }, + "gender" : { + "title" : "Gender", + "description" : "Gender of the person", + "type" : "string" + } + }, + "required" : ["lastName"] + } + } +} \ No newline at end of file diff --git a/103705/schema/response.schema.json b/103705/schema/response.schema.json new file mode 100644 index 0000000000000000000000000000000000000000..57e0e1216f7a810f8dc75e5bdecb100f134b01dd --- /dev/null +++ b/103705/schema/response.schema.json @@ -0,0 +1,77 @@ +{ + "$id": "etsi.org_ts103705_1.3.1", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "ETSI TS 103 705 Response", + "description": "Overall Response structure, representing the results of a lawful disclosure query (see clause 5)", + "type:": "object", + "properties": { + "recordSetDescription": { + "$ref": "#/$defs/recordSetDescription" + }, + "recordSet": { + "$ref": "#/$defs/recordSet" + } + }, + "required": [ + "recordSetDescription", + "recordSet" + ], + "$defs": { + "recordSetDescription": { + "type": "object", + "title": "RecordSetDescription", + "description": "Provides metadata about the records being delivered (see clause 5.2)", + "properties": { + "schemaId" : { "$ref" : "#/$defs/schemaId" }, + "resultSetId" : { "$ref" : "#/$defs/resultSetId"}, + "queryReference" : { "$ref" : "#/$defs/queryReference" }, + "created" : {"$ref" : "ts_103280_2017_07#/$defs/QualifiedMicrosecondDateTime"}, + "recordTypes" : {"$ref" : "#/$defs/recordTypes"} + }, + "required": [ + "schemaId", "resultSetId", "queryReference", "created", "recordTypes" + ] + }, + "schemaId": { + "type": "string" + }, + "resultSetId": { + "type": "string" + }, + "queryReference": { + "type": "string" + }, + "recordSet": { + "type": "array", + "title": "RecordSet", + "description": "RecordSet (see clause 5.3). Contains a set of Records.", + "items": { + "$ref": "#$defs/record" + } + }, + "recordTypes" : { + "type" : "object", + "title" : "RecordTypes", + "description" : "Dictionary of types used by the Response document (see clause 5.2.6)" + }, + "record": { + "type": "object", + "title": "Record", + "description": "Response record (see clause 5.3)", + "properties": { + "id": { + "title": "id", + "description": "Unique identifier for the Record within the RecordSet (see clause 5.3.2).", + "type": "string" + }, + "type" : { + "title" : "type", + "description" : "Type identifier, shall be mapped to a schema reference via the recrdTypes map (see clauses 5.2.6 and 5.3.3)" + } + }, + "required": [ + "id", "type" + ] + } + } +} \ No newline at end of file diff --git a/103705/validate.py b/103705/validate.py new file mode 100644 index 0000000000000000000000000000000000000000..fa5490974ca2b5e4c1dea110df7a5d6de24c0a72 --- /dev/null +++ b/103705/validate.py @@ -0,0 +1,95 @@ +import sys +from jsonschema import validate, RefResolver, Draft202012Validator +import json +from pathlib import Path +import logging +import argparse + + + +# filename = sys.argv[1] + +# def load_json (path): +# with open(path) as f: +# s = json.load(f) +# return s + +# schema_store = {} + +# json_instance = load_json(filename) +# print (json_instance) + +# etsi_schema = load_json('response.schema.json') +# ext_schema = load_json('extended.schema.json') +# ext_ent_schema = load_json("extended_entities.schema.json") +# schema_store = { +# etsi_schema['$id'] : etsi_schema, +# ext_schema['$id'] : ext_schema, +# ext_ent_schema['$id'] : ext_ent_schema +# } + +# resolver = RefResolver(None, referrer=None, store=schema_store) + +# print (etsi_schema) + +# v = Draft202012Validator(ext_schema, resolver=resolver) +# v.validate(json_instance) + +# validate(json_instance, ext_schema) +# print ("OK") + +def handle_uri(u): + print(u) + +def load_json(path : str): + with open(path) as f: + return json.load(f) + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + + parser.add_argument('-s','--schemadir', action="append", help="Directory containing supporting schema files to use for validation") + parser.add_argument('-v', '--verbose', action="count", help="Verbose logging (can be specified multiple times)") + parser.add_argument('-i', '--input', type=argparse.FileType('r'), default=sys.stdin, help="Path to input file (if absent, stdin is used)") + parser.add_argument('schema', help="Primary schema to validate against") + + args = parser.parse_args() + + match args.verbose: + case v if v and v >= 2: + logging.basicConfig(level=logging.DEBUG) + case 1: + logging.basicConfig(level=logging.INFO) + case _: + logging.basicConfig(level=logging.WARNING) + + logging.debug(f"Arguments: {args}") + + instance_doc = json.loads(args.input.read()) + args.input.close() + main_schema = load_json(args.schema) + schema_dict = { main_schema['$id'] : main_schema } + + if args.schemadir: + schema_paths = [] + for d in args.schemadir: + schema_paths += [f for f in Path(d).rglob("*.schema.json")] + logging.info(f"Schema files loaded: {schema_paths}") + + schemas_json = [json.load(p.open()) for p in schema_paths] + schema_dict = schema_dict | { s['$id'] : s for s in schemas_json } + + logging.info(f"Schema IDs loaded: {[k for k in schema_dict.keys()]}") + + logging.debug (f"Instance doc: {instance_doc}") + logging.debug (f"Main schema: {main_schema}") + + resolver = RefResolver(None, + referrer=None, + store=schema_dict) + + v = Draft202012Validator(main_schema, resolver=resolver) + + v.validate(instance_doc) + + logging.info("Done") \ No newline at end of file diff --git a/103705/validation/validate_705.py b/103705/validation/validate_705.py new file mode 100644 index 0000000000000000000000000000000000000000..bb6913d2a2e5ce032b1b0f6ce39413cef1b5036e --- /dev/null +++ b/103705/validation/validate_705.py @@ -0,0 +1,97 @@ +import sys +from jsonschema import validate, RefResolver, Draft202012Validator +import json +from pathlib import Path +import logging +import argparse + +def handle_uri(u): + print(u) + +def load_json(path : str): + with open(path) as f: + return json.load(f) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + + parser.add_argument('-s','--schemadir', action="append", help="Directory containing supporting schema files to use for validation") + parser.add_argument('-v', '--verbose', action="count", help="Verbose logging (can be specified multiple times)") + parser.add_argument('-i', '--input', type=argparse.FileType('r'), default=sys.stdin, help="Path to input file (if absent, stdin is used)") + + args = parser.parse_args() + + match args.verbose: + case v if v and v >= 2: + logging.basicConfig(level=logging.DEBUG) + case 1: + logging.basicConfig(level=logging.INFO) + case _: + logging.basicConfig(level=logging.WARNING) + + logging.debug(f"Arguments: {args}") + + instance_doc = json.loads(args.input.read()) + args.input.close() + + config = { + 'schema_include_dirs' : [ + '../schema/', + '../../103280/', + ], + 'main_schema_doc' : '../schema/response.schema.json' + } + + rootPath = Path(sys.argv[0]).parent + main_schema = load_json(str(rootPath / config['main_schema_doc'])) + schema_dict = { main_schema['$id'] : main_schema } + + schema_paths = [] + for d in config['schema_include_dirs']: + schema_paths += [f for f in (rootPath / Path(d)).rglob("*.schema.json")] + logging.info(f"Core schema files loaded: {schema_paths}") + if args.schemadir: + for d in args.schemadir: + schema_paths += [f for f in Path(d).rglob("*.schema.json")] + logging.info(f"CSP schema files loaded: {schema_paths}") + else: + logging.info(f"No CSP schema files loaded") + schemas_json = [json.load(p.open()) for p in schema_paths] + schema_dict = schema_dict | { s['$id'] : s for s in schemas_json } + + logging.info(f"Schema IDs loaded: {[k for k in schema_dict.keys()]}") + + logging.debug (f"Instance doc: {instance_doc}") + logging.debug (f"Main schema: {main_schema}") + + resolver = RefResolver(None, + referrer=None, + store=schema_dict) + + logging.info("Performing ETSI validation") + v = Draft202012Validator(main_schema, resolver=resolver) + v.validate(instance_doc) + + logging.info("Building record type dictionary") + type_dict = instance_doc['recordSetDescription']['recordTypes'] + logging.debug(type_dict) + ref_dict = { k : {"$ref" : v} for k,v in type_dict.items()} + validator_dict = { k : Draft202012Validator(ref_dict[k], resolver=resolver) for k,v in ref_dict.items()} + logging.debug(ref_dict) + + logging.info("Validating records") + for r in instance_doc['recordSet']: + type_key = r['type'] + if type_key not in type_dict.keys(): + logging.error(f"Record {r['id']} has type {type_key}, not in recordType dict") + type_ref = type_dict[type_key] + type_schema_id = type_ref.split('#')[0] + logging.info(f"Using {type_schema_id} to validate {type_ref} in record {r['id']}") + if not (type_key in validator_dict.keys()): + logging.error(f'Type key {type_key} from type {type_ref} in record {r["id"]} not in validator dictionary') + print(ref_dict) + v = validator_dict[type_key] + v.validate(r) + + logging.info("Done") \ No newline at end of file