diff --git a/openapiart/openapiartgo.py b/openapiart/openapiartgo.py index 266d86b6..38e5d52a 100644 --- a/openapiart/openapiartgo.py +++ b/openapiart/openapiartgo.py @@ -2853,19 +2853,28 @@ def _write_default_method(self, new): interface_fields = new.interface_fields hasChoiceConfig = [] choice_enums = [] + enum_map = {} + choice_enum_map = {} for index, field in enumerate(interface_fields): - if field.default is None: - continue if field.name == "Choice": - choice_enums = [ - self._get_external_struct_name(e) - for e in field.enums - if e != field.default - ] - hasChoiceConfig = [ - "Choice", - self._get_external_struct_name(field.default), - ] + for enum in field.enums: + enum_str = self._get_external_struct_name(enum) + enum_map[enum_str] = "" + enum_value = """{struct}Choice.{value}""".format( + struct=self._get_external_struct_name(new.struct), + value=enum.upper(), + ) + choice_enum_map[enum_str] = enum_value + if field.default is not None: + choice_enums = [ + self._get_external_struct_name(e) + for e in field.enums + if e != field.default + ] + hasChoiceConfig = [ + "Choice", + self._get_external_struct_name(field.default), + ] interface_fields.insert(0, interface_fields.pop(index)) break @@ -2874,6 +2883,22 @@ def _write_default_method(self, new): for field in interface_fields: # if hasChoiceConfig != [] and field.name not in hasChoiceConfig: # continue + ext_name = self._get_external_struct_name(field.name) + if field.name in enum_map or ext_name in enum_map: + if field.name != ext_name and ext_name in enum_map: + del enum_map[ext_name] + enum_map[field.name] = "" + choice_enum_map[field.name] = choice_enum_map[ext_name] + del choice_enum_map[ext_name] + type = "" + if field.isEnum: + type = "enum" + elif field.isPointer: + type = "pointer" + else: + type = field.type + enum_map[field.name] = type + if ( field.default is None or field.isOptional is False @@ -2979,7 +3004,12 @@ def _write_default_method(self, new): ) ) else: - body += """if obj.obj.{name} == nil {{ + choice_cond = "" + if field.name in choice_enum_map: + choice_cond += ( + "&& choice == %s " % choice_enum_map[field.name] + ) + body += """if obj.obj.{name} == nil {choice_check}{{ obj.Set{external_name}({value}) }} """.format( @@ -2990,6 +3020,7 @@ def _write_default_method(self, new): value='"{0}"'.format(field.default) if field.type == "string" else field.default, + choice_check=choice_cond, ) else: if field.name in hasChoiceConfig: @@ -3017,16 +3048,98 @@ def _write_default_method(self, new): if field.type == "string" else field.default, ) - if choice_body is not None: - enum_fields = [] - body = ( - choice_body.replace( - "", - "\n".join(enum_fields) if enum_fields != [] else "", - ) - + body + + # write default case if object has choice property + choice_code = "" + # TODO: we need to propagate error from setdefault along the whole heirarchy + if len(enum_map) > 0: + choice_code = ( + "var choices_set int = 0\nvar choice %sChoiceEnum\n" + % new.interface + ) + for enum in enum_map: + field_type = enum_map[enum] + value = "nil" + if field_type.startswith("int") or field_type.startswith( + "float" + ): + value = "0" + elif field_type == "string": + value = '""' + elif field_type == "pointer": + value = "nil" + elif field_type == "": + # signifies choice with no property + continue + + if field_type.startswith("[]"): + choice_code += """ + if len(obj.obj.{prop}) > 0{{ + choices_set += 1 + choice = {choice_val} + }} + """.format( + prop=enum, + choice_val=choice_enum_map[enum], + ) + else: + enum_check_code = " && obj.obj.%s.Number() != 0 " % enum + choice_code += """ + if obj.obj.{prop} != {val}{enum_check}{{ + choices_set += 1 + choice = {choice_val} + }} + """.format( + prop=enum, + val=value, + choice_val=choice_enum_map[enum], + enum_check=enum_check_code + if field_type == "enum" + else "", + ) + + # TODO: we need to throw error if more that one choice properties are set + # choice_code += """ + # if choices_set > 1 {{ + # obj.validationErrors = append(obj.validationErrors, "more than one choices are set in Interface {intf}") + # }}""".format( + # intf=new.interface + # ) + + if choice_body is not None: + choice_code += """if choices_set == 0 {{ + {body} + }}""".format( + body=choice_body.replace("", "") + ) + # enum_fields = [] + # body = ( + # choice_body.replace( + # "", + # "\n".join(enum_fields) if enum_fields != [] else "", + # ) + # + body + # ) + + choice_code += """{el}if choices_set == 1 && choice != "" {{ + if obj.obj.Choice != nil {{ + if obj.Choice() != choice {{ + obj.validationErrors = append(obj.validationErrors, "choice not matching with property in {intf}") + }} + }} else {{ + intVal := {pb_pkg_name}.{intf}_Choice_Enum_value[string(choice)] + enumValue := {pb_pkg_name}.{intf}_Choice_Enum(intVal) + obj.obj.Choice = &enumValue + }} + }} + """.format( + intf=new.interface, + pb_pkg_name=self._protobuf_package_name, + el=" else " if choice_body is not None else "", ) + body = choice_code + "\n" + body + body = body.replace("SetChoice", "setChoice") self._write( """func (obj *{struct}) setDefault() {{ diff --git a/openapiart/tests/config/config.yaml b/openapiart/tests/config/config.yaml index fb008f18..4cc3ecc8 100644 --- a/openapiart/tests/config/config.yaml +++ b/openapiart/tests/config/config.yaml @@ -307,15 +307,21 @@ components: minimum: 64 maximum: 9000 x-field-uid: 52 + choice_test: + $ref: "#/components/schemas/ChoiceTestObj" + x-field-uid: 53 signed_integer_pattern: $ref: "../pattern/pattern.yaml#/components/schemas/SignedIntegerPattern" - x-field-uid: 53 + x-field-uid: 54 oid_pattern: $ref: "../pattern/pattern.yaml#/components/schemas/OidPattern" - x-field-uid: 54 + x-field-uid: 55 choice_default: $ref: "#/components/schemas/ChoiceObject" - x-field-uid: 55 + x-field-uid: 56 + choice_required_default: + $ref: "#/components/schemas/ChoiceRequiredAndDefault" + x-field-uid: 57 WObject: required: [w_name] @@ -696,7 +702,6 @@ components: x-field-uid: 2 f_a: type: string - default: "some string" x-field-uid: 2 leaf: $ref: "#/components/schemas/RequiredChoiceIntermeLeaf" @@ -707,3 +712,58 @@ components: name: type: string x-field-uid: 1 + + ChoiceTestObj: + properties: + choice: + type: string + x-field-uid: 1 + x-enum: + e_obj: + x-field-uid: 1 + f_obj: + x-field-uid: 2 + no_obj: + x-field-uid: 3 + ieee_802_1qbb: + x-field-uid: 4 + ieee_802_3x: + x-field-uid: 5 + e_obj: + $ref: "#/components/schemas/EObject" + x-field-uid: 2 + f_obj: + $ref: "#/components/schemas/FObject" + x-field-uid: 3 + ieee_802_1qbb: + type: string + x-field-uid: 4 + ieee_802_3x: + type: string + x-field-uid: 5 + + ChoiceRequiredAndDefault: + type: object + required: [choice] + properties: + choice: + type: string + x-field-uid: 1 + x-enum: + ipv4: + x-field-uid: 1 + ipv6: + x-field-uid: 2 + ipv4: + type: string + format: ipv4 + default: "0.0.0.0" + x-field-uid: 2 + ipv6: + description: |- + A list of ipv6 + type: array + items: + type: string + format: ipv6 + x-field-uid: 3 diff --git a/openapiart/tests/test_choice_with_no_property.py b/openapiart/tests/test_choice.py similarity index 91% rename from openapiart/tests/test_choice_with_no_property.py rename to openapiart/tests/test_choice.py index b14eb051..91d0fdd1 100644 --- a/openapiart/tests/test_choice_with_no_property.py +++ b/openapiart/tests/test_choice.py @@ -145,5 +145,21 @@ def test_choice_with_invalid_enum_and_none_value(api): assert execinfo.value.args[0] == choice_error +def test_choice_unmarhsalling(api): + data = {} + data["a"] = "asd" + data["b"] = 12 + data["c"] = 13 + data["required_object"] = {"e_a": 1.23, "e_b": 23} + data["choice_test"] = {"f_obj": {"f_b": 22.34}} + + config = api.prefix_config() + config.deserialize(data) + + assert config.choice_test.choice == "f_obj" + assert config.choice_test.f_obj.choice == "f_b" + assert config.choice_test.f_obj.f_b == 22.34 + + if __name__ == "__main__": pytest.main(["-v", "-s", __file__]) diff --git a/pkg/choice_test.go b/pkg/choice_test.go index 886cafc5..330ce31d 100644 --- a/pkg/choice_test.go +++ b/pkg/choice_test.go @@ -1,10 +1,12 @@ package openapiart_test import ( + "fmt" "testing" openapiart "github.com/open-traffic-generator/openapiart/pkg" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestChoiceWithNoPropertiesForLeafNode(t *testing.T) { @@ -123,3 +125,329 @@ func TestChoiceWithNoPropertiesForChoiceDefault(t *testing.T) { _, err = choiceObj.Marshal().ToYaml() assert.Nil(t, err) } + +func TestChoiceMarshall(t *testing.T) { + config := openapiart.NewPrefixConfig() + config.SetA("asd").SetB(1).SetC(22).RequiredObject().SetEA(1.23).SetEB(2.34) + + choiceObj := config.ChoiceTest() + choiceObj.EObj().SetEA(1.23).SetEB(2.34) + s, err := config.Marshal().ToJson() + fmt.Println(s) + assert.Nil(t, err) + exp_json := `{ + "required_object": { + "e_a": 1.23, + "e_b": 2.34 + }, + "response": "status_200", + "a": "asd", + "b": 1, + "c": 22, + "h": true, + "choice_test": { + "choice": "e_obj", + "e_obj": { + "e_a": 1.23, + "e_b": 2.34 + } + } + }` + require.JSONEq(t, exp_json, s) + + choiceObj.FObj().SetFA("s1").SetFB(34.5678) + s, err = config.Marshal().ToJson() + fmt.Println(s) + assert.Nil(t, err) + exp_json = `{ + "required_object": { + "e_a": 1.23, + "e_b": 2.34 + }, + "response": "status_200", + "a": "asd", + "b": 1, + "c": 22, + "h": true, + "choice_test": { + "choice": "f_obj", + "f_obj": { + "choice": "f_b", + "f_b": 34.5678 + } + } + }` + require.JSONEq(t, exp_json, s) + + choiceObj.NoObj() + s, err = config.Marshal().ToJson() + fmt.Println(s) + assert.Nil(t, err) + exp_json = `{ + "required_object": { + "e_a": 1.23, + "e_b": 2.34 + }, + "response": "status_200", + "a": "asd", + "b": 1, + "c": 22, + "h": true, + "choice_test": { + "choice": "no_obj" + } + }` + require.JSONEq(t, exp_json, s) +} + +func TestChoiceUnMarshall(t *testing.T) { + exp_json := `{ + "a": "asd", + "b": 1, + "c": 22, + "required_object": { + "e_a": 1.23, + "e_b": 2.34 + }, + "choice_test": { + "choice": "e_obj", + "e_obj": { + "e_a": 1.23, + "e_b": 22.3456 + } + } + }` + + config := openapiart.NewPrefixConfig() + err := config.Unmarshal().FromJson(exp_json) + assert.Nil(t, err) + cObj := config.ChoiceTest() + assert.Equal(t, cObj.Choice(), openapiart.ChoiceTestObjChoice.E_OBJ) + assert.Equal(t, cObj.EObj().EA(), float32(1.23)) + assert.Equal(t, cObj.EObj().EB(), float64(22.3456)) + + exp_json = `{ + "a": "asd", + "b": 1, + "c": 22, + "required_object": { + "e_a": 1.23, + "e_b": 2.34 + }, + "choice_test": { + "choice": "f_obj", + "f_obj": { + "f_a": "s1", + "f_b": 22.3456 + } + } + }` + + config = openapiart.NewPrefixConfig() + err = config.Unmarshal().FromJson(exp_json) + assert.Nil(t, err) + cObj = config.ChoiceTest() + assert.Equal(t, cObj.Choice(), openapiart.ChoiceTestObjChoice.F_OBJ) + + exp_json = `{ + "a": "asd", + "b": 1, + "c": 22, + "required_object": { + "e_a": 1.23, + "e_b": 2.34 + }, + "choice_test": { + "choice": "no_obj" + } + }` + + config = openapiart.NewPrefixConfig() + err = config.Unmarshal().FromJson(exp_json) + assert.Nil(t, err) + cObj = config.ChoiceTest() + assert.Equal(t, cObj.Choice(), openapiart.ChoiceTestObjChoice.NO_OBJ) + + // json without choice + json := `{ + "a": "asd", + "b": 1, + "c": 22, + "required_object": { + "e_a": 1.23, + "e_b": 2.34 + }, + "choice_test": { + "e_obj": { + "e_a": 1.23, + "e_b": 22.3456 + } + } + }` + + config = openapiart.NewPrefixConfig() + err = config.Unmarshal().FromJson(json) + assert.Nil(t, err) + cObj = config.ChoiceTest() + fmt.Println(config) + assert.Equal(t, cObj.EObj().EA(), float32(1.23)) + assert.Equal(t, cObj.EObj().EB(), float64(22.3456)) + assert.Equal(t, cObj.Choice(), openapiart.ChoiceTestObjChoice.E_OBJ) + + // json without choice + json = `{ + "a": "asd", + "b": 1, + "c": 22, + "required_object": { + "e_a": 1.23, + "e_b": 2.34 + }, + "choice_test": { + "ieee_802_1qbb": "hello!" + } + }` + + config = openapiart.NewPrefixConfig() + err = config.Unmarshal().FromJson(json) + assert.Nil(t, err) + cObj = config.ChoiceTest() + fmt.Println(config) + assert.Equal(t, cObj.Choice(), openapiart.ChoiceTestObjChoice.IEEE_802_1QBB) + assert.Equal(t, cObj.Ieee8021Qbb(), "hello!") + + //json without choice in hierarchy with non-primitive type + exp_json = `{ + "a": "asd", + "b": 1, + "c": 22, + "required_object": { + "e_a": 1.23, + "e_b": 2.34 + }, + "required_choice_object": { + "intermediate_obj": { + "leaf": { + "name": "name1" + } + } + } + }` + + config = openapiart.NewPrefixConfig() + err = config.Unmarshal().FromJson(exp_json) + assert.Nil(t, err) + r := config.RequiredChoiceObject() + fmt.Println(r) + assert.Equal(t, r.Choice(), openapiart.RequiredChoiceParentChoice.INTERMEDIATE_OBJ) + ir := r.IntermediateObj() + assert.Equal(t, ir.Choice(), openapiart.RequiredChoiceIntermediateChoice.LEAF) + assert.Equal(t, ir.Leaf().Name(), "name1") + + // json without choice in hierarchy with primitive type + exp_json = `{ + "a": "asd", + "b": 1, + "c": 22, + "required_object": { + "e_a": 1.23, + "e_b": 2.34 + }, + "required_choice_object": { + "intermediate_obj": { + "f_a": "name1" + } + } + }` + + config = openapiart.NewPrefixConfig() + err = config.Unmarshal().FromJson(exp_json) + assert.Nil(t, err) + fmt.Println(config) + r = config.RequiredChoiceObject() + assert.Equal(t, r.Choice(), openapiart.RequiredChoiceParentChoice.INTERMEDIATE_OBJ) + ir = r.IntermediateObj() + assert.Equal(t, ir.Choice(), openapiart.RequiredChoiceIntermediateChoice.F_A) + assert.Equal(t, ir.FA(), "name1") + + // json without choice for checksum pattern with enum choice properties + config = openapiart.NewPrefixConfig() + config.SetA("asd").RequiredObject() + config.HeaderChecksum().SetCustom(123) + fmt.Println(config) + exp_json = `{ + "a": "asd", + "b": 1, + "c": 22, + "required_object": { + "e_a": 1.23, + "e_b": 2.34 + }, + "header_checksum": { + "custom": 12345, + "generated": "unspecified" + } + }` + config = openapiart.NewPrefixConfig() + err = config.Unmarshal().FromJson(exp_json) + assert.Nil(t, err) + fmt.Println(config) + hc := config.HeaderChecksum() + assert.Equal(t, hc.Choice(), openapiart.PatternPrefixConfigHeaderChecksumChoice.CUSTOM) + assert.Equal(t, hc.Custom(), uint32(12345)) + + // json without choice for checksum pattern with enum choice properties + exp_json = `{ + "a": "asd", + "b": 1, + "c": 22, + "required_object": { + "e_a": 1.23, + "e_b": 2.34 + }, + "header_checksum": { + "generated": "bad" + } + }` + config = openapiart.NewPrefixConfig() + err = config.Unmarshal().FromJson(exp_json) + assert.Nil(t, err) + fmt.Println(config) + hc = config.HeaderChecksum() + assert.Equal(t, hc.Choice(), openapiart.PatternPrefixConfigHeaderChecksumChoice.GENERATED) + assert.Equal(t, hc.Generated(), openapiart.PatternPrefixConfigHeaderChecksumGenerated.BAD) + +} + +func TestDefaultChoiceOverwrite(t *testing.T) { + config := openapiart.NewPrefixConfig() + crd := config.ChoiceRequiredDefault() + crd.Ipv4() + + // test default choice and values + fmt.Println(crd) + assert.Equal(t, crd.Choice(), openapiart.ChoiceRequiredAndDefaultChoice.IPV4) + assert.True(t, crd.HasIpv4()) + assert.Equal(t, crd.Ipv4(), "0.0.0.0") + + // setting of other choices should work as usual + crd.SetIpv6([]string{"1::2"}) + + c, err := crd.Marshal().ToYaml() + fmt.Println(c) + assert.Nil(t, err) + + assert.Equal(t, crd.Choice(), openapiart.ChoiceRequiredAndDefaultChoice.IPV6) + assert.True(t, len(crd.Ipv6()) > 0) + assert.Equal(t, crd.Ipv6()[0], "1::2") + + crd.SetIpv4("1.2.3.4") + + c, err = crd.Marshal().ToYaml() + fmt.Println(c) + assert.Nil(t, err) + + assert.Equal(t, crd.Choice(), openapiart.ChoiceRequiredAndDefaultChoice.IPV4) + assert.True(t, crd.HasIpv4()) + assert.Equal(t, crd.Ipv4(), "1.2.3.4") +} diff --git a/pkg/unit_test.go b/pkg/unit_test.go index 26375048..c8fcb572 100644 --- a/pkg/unit_test.go +++ b/pkg/unit_test.go @@ -646,7 +646,7 @@ func TestRequiredEnumField(t *testing.T) { rc.IntermediateObj() assert.Contains(t, err.Error(), "Choice is required field on interface RequiredChoiceParent") _, err = rc.Marshal().ToYaml() - assert.Nil(t, err) + assert.Contains(t, err.Error(), "Choice is required field on interface RequiredChoiceIntermediate") } func TestOptionalDefault(t *testing.T) {