@@ -62,34 +62,37 @@ impl NamedSchema {
6262 Self { name, schema }
6363 }
6464
65- pub fn check ( & self , val : & serde_json:: Value ) {
65+ pub fn check ( & self , val : & serde_json:: Value ) -> Result < ( ) , String > {
6666 let mut errors = self . schema . iter_errors ( val) . peekable ( ) ;
67- if errors. peek ( ) . is_some ( ) {
68- // errors don't necessarily implement Debug
69- eprintln ! ( "Schema failed to validate: {}" , self . name) ;
70- for error in errors {
71- eprintln ! ( "Validation error: {error}" ) ;
72- eprintln ! ( "Instance path: {}" , error. instance_path) ;
73- }
74- panic ! ( "Serialization test failed." ) ;
67+ if errors. peek ( ) . is_none ( ) {
68+ return Ok ( ( ) ) ;
7569 }
70+
71+ // errors don't necessarily implement Debug
72+ let mut strs = vec ! [ format!( "Schema failed to validate: {}" , self . name) ] ;
73+ strs. extend ( errors. flat_map ( |error| {
74+ [
75+ format ! ( "Validation error: {error}" ) ,
76+ format ! ( "Instance path: {}" , error. instance_path) ,
77+ ]
78+ } ) ) ;
79+ strs. push ( "Serialization test failed." . to_string ( ) ) ;
80+ Err ( strs. join ( "\n " ) )
7681 }
7782
7883 pub fn check_schemas (
7984 val : & serde_json:: Value ,
8085 schemas : impl IntoIterator < Item = & ' static Self > ,
81- ) {
82- for schema in schemas {
83- schema. check ( val) ;
84- }
86+ ) -> Result < ( ) , String > {
87+ schemas. into_iter ( ) . try_for_each ( |schema| schema. check ( val) )
8588 }
8689}
8790
8891macro_rules! include_schema {
8992 ( $name: ident, $path: literal) => {
9093 lazy_static! {
9194 static ref $name: NamedSchema =
92- NamedSchema :: new( " $name" , {
95+ NamedSchema :: new( stringify! ( $name) , {
9396 let schema_val: serde_json:: Value = serde_json:: from_str( include_str!(
9497 concat!( "../../../../specification/schema/" , $path, "_live.json" )
9598 ) )
@@ -161,7 +164,7 @@ fn ser_deserialize_check_schema<T: serde::de::DeserializeOwned>(
161164 val : serde_json:: Value ,
162165 schemas : impl IntoIterator < Item = & ' static NamedSchema > ,
163166) -> T {
164- NamedSchema :: check_schemas ( & val, schemas) ;
167+ NamedSchema :: check_schemas ( & val, schemas) . unwrap ( ) ;
165168 serde_json:: from_value ( val) . unwrap ( )
166169}
167170
@@ -171,8 +174,10 @@ fn ser_roundtrip_check_schema<TSer: Serialize, TDeser: serde::de::DeserializeOwn
171174 schemas : impl IntoIterator < Item = & ' static NamedSchema > ,
172175) -> TDeser {
173176 let val = serde_json:: to_value ( g) . unwrap ( ) ;
174- NamedSchema :: check_schemas ( & val, schemas) ;
175- serde_json:: from_value ( val) . unwrap ( )
177+ match NamedSchema :: check_schemas ( & val, schemas) {
178+ Ok ( ( ) ) => serde_json:: from_value ( val) . unwrap ( ) ,
179+ Err ( msg) => panic ! ( "ser_roundtrip_check_schema failed with {msg}, input was {val}" ) ,
180+ }
176181}
177182
178183/// Serialize a Hugr and check that it is valid against the schema.
@@ -187,7 +192,7 @@ pub(crate) fn check_hugr_serialization_schema(hugr: &Hugr) {
187192 let schemas = get_schemas ( true ) ;
188193 let hugr_ser = HugrSer ( hugr) ;
189194 let val = serde_json:: to_value ( hugr_ser) . unwrap ( ) ;
190- NamedSchema :: check_schemas ( & val, schemas) ;
195+ NamedSchema :: check_schemas ( & val, schemas) . unwrap ( ) ;
191196}
192197
193198/// Serialize and deserialize a HUGR, and check that the result is the same as the original.
@@ -225,6 +230,77 @@ fn check_testing_roundtrip(t: impl Into<SerTestingLatest>) {
225230 assert_eq ! ( before, after) ;
226231}
227232
233+ fn test_schema_val ( ) -> serde_json:: Value {
234+ serde_json:: json!( {
235+ "op_def" : null,
236+ "optype" : {
237+ "name" : "polyfunc1" ,
238+ "op" : "FuncDefn" ,
239+ "parent" : 0 ,
240+ "signature" : {
241+ "body" : {
242+ "input" : [ ] ,
243+ "output" : [ ]
244+ } ,
245+ "params" : [
246+ { "bound" : null, "tp" : "BoundedNat" }
247+ ]
248+ }
249+ } ,
250+ "poly_func_type" : null,
251+ "sum_type" : null,
252+ "typ" : null,
253+ "value" : null,
254+ "version" : "live"
255+ } )
256+ }
257+
258+ fn schema_val ( ) -> serde_json:: Value {
259+ serde_json:: json!( { "nodes" : [ ] , "edges" : [ ] , "version" : "live" } )
260+ }
261+
262+ #[ rstest]
263+ #[ case( & TESTING_SCHEMA , & TESTING_SCHEMA_STRICT , test_schema_val( ) , Some ( "optype" ) ) ]
264+ #[ case( & SCHEMA , & SCHEMA_STRICT , schema_val( ) , None ) ]
265+ fn wrong_fields (
266+ #[ case] lax_schema : & ' static NamedSchema ,
267+ #[ case] strict_schema : & ' static NamedSchema ,
268+ #[ case] mut val : serde_json:: Value ,
269+ #[ case] target_loc : impl IntoIterator < Item = & ' static str > + Clone ,
270+ ) {
271+ use serde_json:: Value ;
272+ fn get_fields (
273+ val : & mut Value ,
274+ mut path : impl Iterator < Item = & ' static str > ,
275+ ) -> & mut serde_json:: Map < String , Value > {
276+ let Value :: Object ( fields) = val else { panic ! ( ) } ;
277+ match path. next ( ) {
278+ Some ( n) => get_fields ( fields. get_mut ( n) . unwrap ( ) , path) ,
279+ None => fields,
280+ }
281+ }
282+ // First, some "known good" JSON
283+ NamedSchema :: check_schemas ( & val, [ lax_schema, strict_schema] ) . unwrap ( ) ;
284+
285+ // Now try adding an extra field
286+ let fields = get_fields ( & mut val, target_loc. clone ( ) . into_iter ( ) ) ;
287+ fields. insert (
288+ "extra_field" . to_string ( ) ,
289+ Value :: String ( "not in schema" . to_string ( ) ) ,
290+ ) ;
291+ strict_schema. check ( & val) . unwrap_err ( ) ;
292+ lax_schema. check ( & val) . unwrap ( ) ;
293+
294+ // And removing one
295+ let fields = get_fields ( & mut val, target_loc. into_iter ( ) ) ;
296+ fields. remove ( "extra_field" ) . unwrap ( ) ;
297+ let key = fields. keys ( ) . next ( ) . unwrap ( ) . clone ( ) ;
298+ fields. remove ( & key) . unwrap ( ) ;
299+
300+ lax_schema. check ( & val) . unwrap_err ( ) ;
301+ strict_schema. check ( & val) . unwrap_err ( ) ;
302+ }
303+
228304/// Generate an optype for a node with a matching amount of inputs and outputs.
229305fn gen_optype ( g : & MultiPortGraph , node : portgraph:: NodeIndex ) -> OpType {
230306 let inputs = g. num_inputs ( node) ;
@@ -544,7 +620,7 @@ fn std_extensions_valid() {
544620 let std_reg = crate :: std_extensions:: std_reg ( ) ;
545621 for ext in std_reg {
546622 let val = serde_json:: to_value ( ext) . unwrap ( ) ;
547- NamedSchema :: check_schemas ( & val, get_schemas ( true ) ) ;
623+ NamedSchema :: check_schemas ( & val, get_schemas ( true ) ) . unwrap ( ) ;
548624 // check deserialises correctly, can't check equality because of custom binaries.
549625 let deser: crate :: extension:: Extension = serde_json:: from_value ( val. clone ( ) ) . unwrap ( ) ;
550626 assert_eq ! ( serde_json:: to_value( deser) . unwrap( ) , val) ;
0 commit comments