@@ -357,7 +357,7 @@ def _generate_dispatch_from_jsonable(interface: intermediate.Interface) -> Strip
357
357
{ I } const modelType = jsonable["modelType"];
358
358
{ I } if (modelType === undefined) {{
359
359
{ II } return newDeserializationError<AasTypes.{ interface_name } >(
360
- { III } "Expected the property modelType, but got none "
360
+ { III } "The required property modelType is missing "
361
361
{ II } );
362
362
{ I } }}
363
363
@@ -432,8 +432,10 @@ def _parse_function_for_atomic_value(
432
432
433
433
else :
434
434
assert_never (our_type )
435
+ raise AssertionError ("Unexpected code path" )
435
436
else :
436
437
assert_never (type_annotation )
438
+ raise AssertionError ("Unexpected code path" )
437
439
438
440
return Stripped (function_name )
439
441
@@ -456,30 +458,30 @@ def _generate_setter(cls: intermediate.ConcreteClass) -> Stripped:
456
458
457
459
blocks .append (Stripped (f"{ prop_name } : { prop_type } = null;" ))
458
460
459
- blocks .append (
460
- Stripped (
461
- f"""\
462
- /**
463
- * Ignore `jsonable` and do not set anything.
464
- *
465
- * @param jsonable - to be ignored instead of set
466
- * @returns error, if any
467
- */
468
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
469
- ignore(jsonable: JsonValue): DeserializationError | null {{
470
- { I } // Intentionally empty.
471
- { I } return null;
472
- }}"""
461
+ if cls .serialization .with_model_type :
462
+ # NOTE (mristin):
463
+ # If the serialization requires a model type, we consequently parse and set it
464
+ # in the setter. The model type thus obtained is *not* used for any dispatch. We
465
+ # only use this value for verification to make sure that the model type
466
+ # of the instances is consistent with the expected value for its concrete
467
+ # class. This will be performed even though the code might have had to parse
468
+ # model type before for the dispatch. We decided to double-check to cover the
469
+ # case where a dispatch is *unnecessary* (*e.g.*, the caller knows the expected
470
+ # runtime type), but the model type might still be invalid in the input. Hence,
471
+ # when the dispatch is *necessary*, the model type JSON property will be parsed
472
+ # twice, which is a cost we currently find acceptable.
473
+ prop_name = typescript_naming .property_name (Identifier ("model_type" ))
474
+ blocks .append (
475
+ Stripped (
476
+ f"""\
477
+ // Used only for verification, not for dispatch!
478
+ { prop_name } : string | null = null;"""
479
+ )
473
480
)
474
- )
475
481
476
482
for i , prop in enumerate (cls .properties ):
477
483
prop_name = typescript_naming .property_name (prop .name )
478
484
479
- method_name = typescript_naming .method_name (
480
- Identifier (f"set_{ prop .name } _from_jsonable" )
481
- )
482
-
483
485
type_anno = intermediate .beneath_optional (prop .type_annotation )
484
486
if isinstance (
485
487
type_anno ,
@@ -565,6 +567,10 @@ def _generate_setter(cls: intermediate.ConcreteClass) -> Stripped:
565
567
assert_never (type_anno )
566
568
raise AssertionError ("Unexpected execution path" )
567
569
570
+ method_name = typescript_naming .method_name (
571
+ Identifier (f"set_{ prop .name } _from_jsonable" )
572
+ )
573
+
568
574
method_writer = io .StringIO ()
569
575
method_writer .write (
570
576
f"""\
@@ -583,6 +589,39 @@ def _generate_setter(cls: intermediate.ConcreteClass) -> Stripped:
583
589
584
590
blocks .append (Stripped (method_writer .getvalue ()))
585
591
592
+ if cls .serialization .with_model_type :
593
+ method_name = typescript_naming .method_name (
594
+ Identifier ("set_model_type_from_jsonable" )
595
+ )
596
+ prop_name = typescript_naming .property_name (Identifier ("model_type" ))
597
+
598
+ blocks .append (
599
+ Stripped (
600
+ f"""\
601
+ /**
602
+ * Parse `jsonable` as the model type of the concrete instance.
603
+ *
604
+ * This is intended only for verification, and no dispatch is performed.
605
+ *
606
+ * @param jsonable - to be parsed
607
+ * @returns error, if any
608
+ */
609
+ { method_name } (
610
+ { I } jsonable: JsonValue
611
+ ): DeserializationError | null {{
612
+ { I } const parsedOrError = stringFromJsonable(
613
+ { II } jsonable
614
+ { I } );
615
+ { I } if (parsedOrError.error !== null) {{
616
+ { II } return parsedOrError.error;
617
+ { I } }} else {{
618
+ { II } this.{ prop_name } = parsedOrError.mustValue();
619
+ { II } return null;
620
+ { I } }}
621
+ }}"""
622
+ )
623
+ )
624
+
586
625
cls_name = typescript_naming .class_name (cls .name )
587
626
setter_cls_name = typescript_naming .class_name (Identifier (f"Setter_for_{ cls .name } " ))
588
627
@@ -655,22 +694,47 @@ def _generate_setter_map(cls: intermediate.ConcreteClass) -> Stripped:
655
694
{ II } [
656
695
"""
657
696
)
697
+
658
698
for identifier , expression in identifiers_expressions :
659
699
writer .write (
660
700
f"""\
661
701
{ III } [
662
702
{ IIII } { typescript_common .string_literal (identifier )} ,
663
703
{ IIII } { indent_but_first_line (expression , IIII )}
664
704
{ III } ],
705
+ """
706
+ )
707
+
708
+ if cls .serialization .with_model_type :
709
+ # NOTE (mristin):
710
+ # If the serialization requires a model type, we consequently parse and set it
711
+ # in the setter. The model type thus obtained is *not* used for any dispatch. We
712
+ # only use this value for verification to make sure that the model type
713
+ # of the instances is consistent with the expected value for its concrete
714
+ # class. This will be performed even though the code might have had to parse
715
+ # model type before for the dispatch. We decided to double-check to cover the
716
+ # case where a dispatch is *unnecessary* (*e.g.*, the caller knows the expected
717
+ # runtime type), but the model type might still be invalid in the input. Hence,
718
+ # when the dispatch is *necessary*, the model type JSON property will be parsed
719
+ # twice, which is a cost we currently find acceptable.
720
+ json_identifier = naming .json_property (Identifier ("model_type" ))
721
+ method_name = typescript_naming .method_name (
722
+ Identifier ("set_model_type_from_jsonable" )
723
+ )
724
+ expression = Stripped (f"{ setter_cls_name } .prototype.{ method_name } " )
725
+
726
+ writer .write (
727
+ f"""\
728
+ { III } [
729
+ { IIII } // The model type here is used only for verification, not for dispatch.
730
+ { IIII } { typescript_common .string_literal (json_identifier )} ,
731
+ { IIII } { indent_but_first_line (expression , IIII )}
732
+ { III } ],
665
733
"""
666
734
)
667
735
668
736
writer .write (
669
737
f"""\
670
- { III } [
671
- { IIII } "modelType",
672
- { IIII } { setter_cls_name } .prototype.ignore
673
- { III } ]
674
738
{ II } ]
675
739
{ I } );"""
676
740
)
@@ -797,6 +861,30 @@ def _generate_concrete_class_from_jsonable(
797
861
798
862
# endregion
799
863
864
+ if cls .serialization .with_model_type :
865
+ model_type = naming .json_model_type (cls .name )
866
+ prop_name = typescript_naming .property_name (Identifier ("model_type" ))
867
+
868
+ blocks .append (
869
+ Stripped (
870
+ f"""\
871
+ if (setter.{ prop_name } === null) {{
872
+ { I } return newDeserializationError<
873
+ { II } AasTypes.{ cls_name }
874
+ { I } >(
875
+ { II } "The required property 'modelType' is missing"
876
+ { I } );
877
+ }} else if (setter.{ prop_name } != "{ model_type } ") {{
878
+ { I } return newDeserializationError<
879
+ { II } AasTypes.{ cls_name }
880
+ { I } >(
881
+ { II } "Expected model type '{ model_type } ', " +
882
+ { II } `but got: ${{setter.{ prop_name } }}`
883
+ { I } );
884
+ }}"""
885
+ )
886
+ )
887
+
800
888
# region Pass in arguments to the constructor
801
889
802
890
cls_name = typescript_naming .class_name (cls .name )
@@ -1041,6 +1129,7 @@ def _generate_transform(cls: intermediate.ConcreteClass) -> Stripped:
1041
1129
1042
1130
else :
1043
1131
assert_never (type_anno )
1132
+ raise AssertionError ("Unexpected code path" )
1044
1133
1045
1134
if isinstance (prop .type_annotation , intermediate .OptionalTypeAnnotation ):
1046
1135
block = Stripped (
0 commit comments