From b48c8aa76be43cbfe9d43f0bdc73e100f50c4c05 Mon Sep 17 00:00:00 2001 From: Seyon Sivarajah Date: Mon, 15 Jan 2024 11:29:31 +0000 Subject: [PATCH] test: add schema validation to roundtrips requires minor fixes to schema --- Cargo.toml | 1 + devenv.nix | 2 + specification/schema/hugr_schema_v0.json | 201 +++++++---------------- src/hugr/serialize.rs | 43 ++++- 4 files changed, 102 insertions(+), 145 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6bc0549f9..e324d7a7d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,6 +61,7 @@ webbrowser = "0.8.10" urlencoding = "2.1.2" cool_asserts = "2.0.3" insta = { version = "1.34.0", features = ["yaml"] } +jsonschema = "0.17.1" [[bench]] name = "bench_main" diff --git a/devenv.nix b/devenv.nix index bc9fdba1c..adc035dbc 100644 --- a/devenv.nix +++ b/devenv.nix @@ -16,6 +16,8 @@ in (with pkgs.darwin.apple_sdk; [ frameworks.CoreServices frameworks.CoreFoundation + # added for json schema validation tess + frameworks.SystemConfiguration ]); # https://devenv.sh/scripts/ diff --git a/specification/schema/hugr_schema_v0.json b/specification/schema/hugr_schema_v0.json index 6cf15c6b9..db5ad9db3 100644 --- a/specification/schema/hugr_schema_v0.json +++ b/specification/schema/hugr_schema_v0.json @@ -74,11 +74,7 @@ "type": "integer" }, "input_extensions": { - "items": { - "type": "string" - }, - "title": "Input Extensions", - "type": "array" + "$ref": "#/$defs/InputExtensions" }, "op": { "const": "CFG", @@ -103,11 +99,7 @@ "type": "integer" }, "input_extensions": { - "items": { - "type": "string" - }, - "title": "Input Extensions", - "type": "array" + "$ref": "#/$defs/InputExtensions" }, "op": { "const": "Call", @@ -132,11 +124,7 @@ "type": "integer" }, "input_extensions": { - "items": { - "type": "string" - }, - "title": "Input Extensions", - "type": "array" + "$ref": "#/$defs/InputExtensions" }, "op": { "const": "CallIndirect", @@ -161,11 +149,7 @@ "type": "integer" }, "input_extensions": { - "items": { - "type": "string" - }, - "title": "Input Extensions", - "type": "array" + "$ref": "#/$defs/InputExtensions" }, "op": { "const": "Case", @@ -190,11 +174,7 @@ "type": "integer" }, "input_extensions": { - "items": { - "type": "string" - }, - "title": "Input Extensions", - "type": "array" + "$ref": "#/$defs/InputExtensions" }, "op": { "const": "Conditional", @@ -247,11 +227,7 @@ "type": "integer" }, "input_extensions": { - "items": { - "type": "string" - }, - "title": "Input Extensions", - "type": "array" + "$ref": "#/$defs/InputExtensions" }, "op": { "const": "Const", @@ -281,11 +257,7 @@ "type": "integer" }, "input_extensions": { - "items": { - "type": "string" - }, - "title": "Input Extensions", - "type": "array" + "$ref": "#/$defs/InputExtensions" }, "op": { "const": "LeafOp", @@ -355,11 +327,7 @@ "type": "integer" }, "input_extensions": { - "items": { - "type": "string" - }, - "title": "Input Extensions", - "type": "array" + "$ref": "#/$defs/InputExtensions" }, "op": { "const": "DFG", @@ -384,11 +352,7 @@ "type": "integer" }, "input_extensions": { - "items": { - "type": "string" - }, - "title": "Input Extensions", - "type": "array" + "$ref": "#/$defs/InputExtensions" }, "op": { "const": "DataflowBlock", @@ -441,11 +405,7 @@ "type": "integer" }, "input_extensions": { - "items": { - "type": "string" - }, - "title": "Input Extensions", - "type": "array" + "$ref": "#/$defs/InputExtensions" }, "op": { "const": "ExitBlock", @@ -520,11 +480,7 @@ "type": "integer" }, "input_extensions": { - "items": { - "type": "string" - }, - "title": "Input Extensions", - "type": "array" + "$ref": "#/$defs/InputExtensions" }, "op": { "const": "FuncDecl", @@ -554,11 +510,7 @@ "type": "integer" }, "input_extensions": { - "items": { - "type": "string" - }, - "title": "Input Extensions", - "type": "array" + "$ref": "#/$defs/InputExtensions" }, "op": { "const": "FuncDefn", @@ -665,11 +617,7 @@ "type": "integer" }, "input_extensions": { - "items": { - "type": "string" - }, - "title": "Input Extensions", - "type": "array" + "$ref": "#/$defs/InputExtensions" }, "op": { "const": "Input", @@ -690,6 +638,20 @@ "title": "Input", "type": "object" }, + "InputExtensions": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null + }, "USize": { "description": "Unsigned integer size type.", "properties": { @@ -760,11 +722,7 @@ "type": "integer" }, "input_extensions": { - "items": { - "type": "string" - }, - "title": "Input Extensions", - "type": "array" + "$ref": "#/$defs/InputExtensions" }, "op": { "const": "LoadConstant", @@ -790,11 +748,7 @@ "type": "integer" }, "input_extensions": { - "items": { - "type": "string" - }, - "title": "Input Extensions", - "type": "array" + "$ref": "#/$defs/InputExtensions" }, "op": { "const": "LeafOp", @@ -828,11 +782,7 @@ "type": "integer" }, "input_extensions": { - "items": { - "type": "string" - }, - "title": "Input Extensions", - "type": "array" + "$ref": "#/$defs/InputExtensions" }, "op": { "const": "Module", @@ -854,11 +804,7 @@ "type": "integer" }, "input_extensions": { - "items": { - "type": "string" - }, - "title": "Input Extensions", - "type": "array" + "$ref": "#/$defs/InputExtensions" }, "op": { "const": "LeafOp", @@ -1040,11 +986,7 @@ "type": "integer" }, "input_extensions": { - "items": { - "type": "string" - }, - "title": "Input Extensions", - "type": "array" + "$ref": "#/$defs/InputExtensions" }, "op": { "const": "Output", @@ -1155,11 +1097,7 @@ "type": "integer" }, "input_extensions": { - "items": { - "type": "string" - }, - "title": "Input Extensions", - "type": "array" + "$ref": "#/$defs/InputExtensions" }, "op": { "const": "LeafOp", @@ -1199,11 +1137,7 @@ "type": "integer" }, "input_extensions": { - "items": { - "type": "string" - }, - "title": "Input Extensions", - "type": "array" + "$ref": "#/$defs/InputExtensions" }, "op": { "const": "TailLoop", @@ -1303,31 +1237,32 @@ "title": "TupleType", "type": "object" }, + "SumType": { + "discriminator": { + "mapping": { + "General": "#/$defs/GeneralSum", + "Unit": "#/$defs/UnitSum" + }, + "propertyName": "s" + }, + "oneOf": [ + { + "$ref": "#/$defs/UnitSum" + }, + { + "$ref": "#/$defs/GeneralSum" + } + ] + }, "Type": { "discriminator": { "mapping": { "Array": "#/$defs/Array", "G": "#/$defs/PolyFuncType", - "I": "#/ $defs/USize", + "I": "#/$defs/USize", "Opaque": "#/$defs/Opaque", "Q": "#/$defs/Qubit", - "Sum": { - "discriminator": { - "mapping": { - "General": "#/$defs/GeneralSum", - "Unit": "#/$defs/UnitSum" - }, - "propertyName": "s" - }, - "oneOf": [ - { - "$ref": "#/$defs/UnitSum" - }, - { - "$ref": "#/$defs/GeneralSum" - } - ] - }, + "Sum": "#/$defs/SumType", "Tuple": "#/$defs/TupleType", "V": "#/$defs/Variable" }, @@ -1341,7 +1276,7 @@ "$ref": "#/$defs/Variable" }, { - "$ref": "#/ $defs/USize" + "$ref": "#/$defs/USize" }, { "$ref": "#/$defs/PolyFuncType" @@ -1353,21 +1288,7 @@ "$ref": "#/$defs/TupleType" }, { - "discriminator": { - "mapping": { - "General": "#/$defs/GeneralSum", - "Unit": "#/$defs/UnitSum" - }, - "propertyName": "s" - }, - "oneOf": [ - { - "$ref": "#/$defs/UnitSum" - }, - { - "$ref": "#/$defs/GeneralSum" - } - ] + "$ref": "#/$defs/SumType" }, { "$ref": "#/$defs/Opaque" @@ -1407,11 +1328,7 @@ "type": "integer" }, "input_extensions": { - "items": { - "type": "string" - }, - "title": "Input Extensions", - "type": "array" + "$ref": "#/$defs/InputExtensions" }, "op": { "const": "LeafOp", @@ -1567,11 +1484,7 @@ "type": "integer" }, "input_extensions": { - "items": { - "type": "string" - }, - "title": "Input Extensions", - "type": "array" + "$ref": "#/$defs/InputExtensions" }, "op": { "const": "LeafOp", diff --git a/src/hugr/serialize.rs b/src/hugr/serialize.rs index 796b36aa4..5155e89db 100644 --- a/src/hugr/serialize.rs +++ b/src/hugr/serialize.rs @@ -268,14 +268,33 @@ pub mod test { use crate::types::{FunctionType, Type}; use crate::OutgoingPort; use itertools::Itertools; + use jsonschema::{Draft, JSONSchema}; + use lazy_static::lazy_static; use portgraph::LinkView; use portgraph::{ multiportgraph::MultiPortGraph, Hierarchy, LinkMut, PortMut, PortView, UnmanagedDenseMap, }; + use std::fs; const NAT: Type = crate::extension::prelude::USIZE_T; const QB: Type = crate::extension::prelude::QB_T; + lazy_static! { + static ref SCHEMA: JSONSchema = { + let path = format!( + "{}/specification/schema/hugr_schema_v0.json", + env!("CARGO_MANIFEST_DIR") + ); + let data = fs::read_to_string(path).unwrap(); + let schema_val: serde_json::Value = serde_json::from_str(&data).unwrap(); + + JSONSchema::options() + .with_draft(Draft::Draft7) + .compile(&schema_val) + .expect("A valid schema") + }; + } + #[test] fn empty_hugr_serialize() { let hg = Hugr::default(); @@ -284,7 +303,29 @@ pub mod test { /// Serialize and deserialize a value. pub fn ser_roundtrip(g: &T) -> T { + ser_roundtrip_validate(g, None) + } + + /// Serialize and deserialize a value, optionally validating against a schema. + pub fn ser_roundtrip_validate( + g: &T, + schema: Option<&JSONSchema>, + ) -> T { let s = serde_json::to_string(g).unwrap(); + let val: serde_json::Value = serde_json::from_str(&s).unwrap(); + + if let Some(schema) = schema { + let validate = schema.validate(&val); + + if let Err(errors) = validate { + // errors don't necessarily implement Debug + for error in errors { + println!("Validation error: {}", error); + println!("Instance path: {}", error.instance_path); + } + panic!("Serialization test failed."); + } + } serde_json::from_str(&s).unwrap() } @@ -292,7 +333,7 @@ pub mod test { /// /// Returns the deserialized HUGR. pub fn check_hugr_roundtrip(hugr: &Hugr) -> Hugr { - let new_hugr: Hugr = ser_roundtrip(hugr); + let new_hugr: Hugr = ser_roundtrip_validate(hugr, Some(&SCHEMA)); // Original HUGR, with canonicalized node indices //