@@ -163,8 +163,21 @@ def binary_format_validator(validator, types, instance, schema):
163163 )
164164
165165
166+ def required_validator (validator , required , instance , schema ):
167+ # Do the normal validation
168+ yield from jsonschema ._validators .required (validator , required , instance , schema )
169+
170+ # For struct codec if a property is not required, then it must have a default
171+ for prop , sub_schema in instance ["properties" ].items ():
172+ if prop not in instance ["required" ] and "default" not in sub_schema :
173+ yield jsonschema .ValidationError (
174+ f"Optional property '{ prop } ' must have" f" a default value"
175+ )
176+
177+
166178StructCodecSchemaValidator = jsonschema .validators .extend (
167- TSKITMetadataSchemaValidator , {"type" : binary_format_validator }
179+ TSKITMetadataSchemaValidator ,
180+ {"type" : binary_format_validator , "required" : required_validator },
168181)
169182META_SCHEMA : Mapping [str , Any ] = copy .deepcopy (StructCodecSchemaValidator .META_SCHEMA )
170183# No union types
@@ -410,9 +423,22 @@ def make_object_encode(cls, sub_schema):
410423 key : StructCodec .make_encode (prop )
411424 for key , prop in sub_schema ["properties" ].items ()
412425 }
413- return lambda obj : b"" .join (
414- sub_encoder (obj [key ]) for key , sub_encoder in sub_encoders .items ()
415- )
426+ defaults = {
427+ key : prop ["default" ]
428+ for key , prop in sub_schema ["properties" ].items ()
429+ if "default" in prop
430+ }
431+
432+ def object_encode (obj ):
433+ values = []
434+ for key , sub_encoder in sub_encoders .items ():
435+ try :
436+ values .append (sub_encoder (obj [key ]))
437+ except KeyError :
438+ values .append (sub_encoder (defaults [key ]))
439+ return b"" .join (values )
440+
441+ return object_encode
416442
417443 @classmethod
418444 def make_object_or_null_encode (cls , sub_schema ):
@@ -445,7 +471,7 @@ def make_numeric_encode(cls, sub_schema):
445471
446472 @classmethod
447473 def modify_schema (cls , schema : Mapping ) -> Mapping :
448- # This codec requires that all properties are required and additional ones
474+ # This codec requires that additional properties are
449475 # not allowed. Rather than get schema authors to repeat that everywhere
450476 # we add it here, sadly we can't do this in the metaschema as "default" isn't
451477 # used by the validator.
@@ -454,12 +480,19 @@ def enforce_fixed_properties(obj):
454480 return [enforce_fixed_properties (j ) for j in obj ]
455481 elif type (obj ) == dict :
456482 ret = {k : enforce_fixed_properties (v ) for k , v in obj .items ()}
457- if ret .get ("type" ) == "object" :
483+ if "object" in ret .get ("type" , []) :
458484 if ret .get ("additional_properties" ):
459485 raise ValueError (
460486 "Struct codec does not support additional_properties"
461487 )
462- ret ["required" ] = list (ret .get ("properties" , {}).keys ())
488+ # To prevent authors having to list required properties the default
489+ # is that all without a default are required.
490+ if "required" not in ret :
491+ ret ["required" ] = [
492+ prop
493+ for prop , sub_schema in ret .get ("properties" , {}).items ()
494+ if "default" not in sub_schema
495+ ]
463496 ret ["additionalProperties" ] = False
464497 return ret
465498 else :
0 commit comments