From a4e7964b19af668fbf6a1db0671178aa38481455 Mon Sep 17 00:00:00 2001 From: Vibaswan Date: Tue, 16 Jan 2024 16:06:46 +0530 Subject: [PATCH] making changes for supporting oid format (#459) * making changes for supporting oid format * updating mopdels guide * fixing an error related to choice with no properties in python --- MODELGUIDE.md | 2 + openapiart/bundler.py | 6 +- openapiart/common.go | 21 ++++ openapiart/common.py | 22 +++- openapiart/openapiartgo.py | 3 +- openapiart/tests/config/config.yaml | 10 ++ openapiart/tests/pattern/pattern.yaml | 9 ++ .../tests/test_choice_with_no_property.py | 28 +++++ openapiart/tests/test_oid_format.py | 111 ++++++++++++++++++ .../tests/test_validate_x_field_pattern.py | 2 +- pkg/choice_test.go | 29 +++++ pkg/common.go | 21 ++++ pkg/oid_format_test.go | 54 +++++++++ 13 files changed, 309 insertions(+), 9 deletions(-) create mode 100644 openapiart/tests/test_oid_format.py create mode 100644 pkg/oid_format_test.go diff --git a/MODELGUIDE.md b/MODELGUIDE.md index d0cc66ea..de4985e8 100644 --- a/MODELGUIDE.md +++ b/MODELGUIDE.md @@ -173,6 +173,7 @@ x-field-pattern: - ipv6 - integer - checksum + - oid length: description: >- The length of integer values in bits. @@ -287,6 +288,7 @@ x-field-pattern: - ipv6 - integer - enum + - oid length: description: >- The length of integer values in bits. diff --git a/openapiart/bundler.py b/openapiart/bundler.py index 0aebbb28..fe379d18 100644 --- a/openapiart/bundler.py +++ b/openapiart/bundler.py @@ -618,7 +618,7 @@ def _length_restriction(self, value): if ( len(intersect_keys) > 0 and "format" in value.keys() - and value["format"] in ["ipv4", "ipv6", "mac"] + and value["format"] in ["ipv4", "ipv6", "mac", "oid"] ): stacks = inspect.stack() property = "{}/{}/{}".format( @@ -696,7 +696,7 @@ def _validate_x_field_pattern(self, xpattern_path): str(xpattern["signed"]), str(xpattern_path.full_path) ) ) - valid_formats = ["integer", "ipv4", "ipv6", "mac", "checksum"] + valid_formats = ["integer", "ipv4", "ipv6", "mac", "checksum", "oid"] if xpattern["format"] not in valid_formats: self._errors.append( "%s has unspported format %s , valid formats are %s" @@ -746,7 +746,7 @@ def _resolve_x_field_pattern(self): ) fmt = None type_name = xpattern["format"] - if type_name in ["ipv4", "ipv6", "mac", "x-enum"]: + if type_name in ["ipv4", "ipv6", "mac", "x-enum", "oid"]: fmt = type_name type_name = "string" description = "TBD" diff --git a/openapiart/common.go b/openapiart/common.go index dcbc2026..c05d1f67 100644 --- a/openapiart/common.go +++ b/openapiart/common.go @@ -330,6 +330,21 @@ func (obj *validation) validateHex(hex string) error { return nil } +func (obj *validation) validateOid(oid string) error { + segments := strings.Split(oid, ".") + if len(segments) < 2 { + return fmt.Errorf(fmt.Sprintf("Invalid oid value %s", oid)) + } + + for _, segment := range segments { + number, err := strconv.Atoi(segment) + if err != nil || 0 > number || number > 4294967295 { + return fmt.Errorf(fmt.Sprintf("Invalid oid value %s", oid)) + } + } + return nil +} + func (obj *validation) validateSlice(valSlice []string, sliceType string) error { indices := []string{} var err error @@ -343,6 +358,8 @@ func (obj *validation) validateSlice(valSlice []string, sliceType string) error err = obj.validateIpv6(val) } else if sliceType == "hex" { err = obj.validateHex(val) + } else if sliceType == "oid" { + err = obj.validateOid(val) } else { return fmt.Errorf(fmt.Sprintf("Invalid slice type received <%s>", sliceType)) } @@ -375,6 +392,10 @@ func (obj *validation) validateHexSlice(hex []string) error { return obj.validateSlice(hex, "hex") } +func (obj *validation) validateOidSlice(oid []string) error { + return obj.validateSlice(oid, "oid") +} + // TODO: restore behavior // func (obj *validation) createMap(objName string) { // if obj.constraints == nil { diff --git a/openapiart/common.py b/openapiart/common.py index e913c7e1..efcfe834 100644 --- a/openapiart/common.py +++ b/openapiart/common.py @@ -487,6 +487,17 @@ def validate_binary(self, value): ] ) + def validate_oid(self, value): + segments = value.split(".") + if len(segments) < 2: + return False + for segment in segments: + if not segment.isnumeric(): + return False + if not (0 <= int(segment) <= 4294967295): + return False + return True + def types_validation( self, value, @@ -698,10 +709,13 @@ def _get_property( "_DEFAULTS" in dir(self._properties[name]) and "choice" in self._properties[name]._DEFAULTS ): - getattr( - self._properties[name], - self._properties[name]._DEFAULTS["choice"], - ) + choice_str = self._properties[name]._DEFAULTS["choice"] + + if choice_str in self._properties[name]._TYPES: + getattr( + self._properties[name], + self._properties[name]._DEFAULTS["choice"], + ) else: if default_value is None and name in self._DEFAULTS: self._set_choice(name) diff --git a/openapiart/openapiartgo.py b/openapiart/openapiartgo.py index 50a2b89b..266d86b6 100644 --- a/openapiart/openapiartgo.py +++ b/openapiart/openapiartgo.py @@ -2702,7 +2702,8 @@ def _validate_types(self, new, field): "ipv4", "ipv6", "hex", - ] or field.format in ["mac", "ipv4", "ipv6", "hex"]: + "oid", + ] or field.format in ["mac", "ipv4", "ipv6", "hex", "oid"]: if field.format is None: field.format = field.itemformat inner_body = """ diff --git a/openapiart/tests/config/config.yaml b/openapiart/tests/config/config.yaml index 623effa3..fb008f18 100644 --- a/openapiart/tests/config/config.yaml +++ b/openapiart/tests/config/config.yaml @@ -310,6 +310,12 @@ components: signed_integer_pattern: $ref: "../pattern/pattern.yaml#/components/schemas/SignedIntegerPattern" x-field-uid: 53 + oid_pattern: + $ref: "../pattern/pattern.yaml#/components/schemas/OidPattern" + x-field-uid: 54 + choice_default: + $ref: "#/components/schemas/ChoiceObject" + x-field-uid: 55 WObject: required: [w_name] @@ -565,6 +571,10 @@ components: type: string format: hex x-field-uid: 8 + oid: + type: string + format: oid + x-field-uid: 9 Mandate: description: "Object to Test required Parameter" type: object diff --git a/openapiart/tests/pattern/pattern.yaml b/openapiart/tests/pattern/pattern.yaml index 1323dbc2..03df6844 100644 --- a/openapiart/tests/pattern/pattern.yaml +++ b/openapiart/tests/pattern/pattern.yaml @@ -61,3 +61,12 @@ components: length: 8 features: [count] x-field-uid: 1 + OidPattern: + description: Test oid pattern + type: object + properties: + oid: + x-field-pattern: + format: oid + default: "0.1" + x-field-uid: 1 diff --git a/openapiart/tests/test_choice_with_no_property.py b/openapiart/tests/test_choice_with_no_property.py index dc7073cf..b14eb051 100644 --- a/openapiart/tests/test_choice_with_no_property.py +++ b/openapiart/tests/test_choice_with_no_property.py @@ -90,6 +90,34 @@ def test_choice_in_choice_heirarchy(api): assert c_obj.f_obj.choice == "f_c" +def test_choice_in_choice_default(api): + config = api.prefix_config() + + # default choice with no properties should be set properly + c_obj = config.choice_default + assert c_obj.choice == "no_obj" + assert len(c_obj._properties) == 1 + + # acesing of objects with choice set to choice with no property should work + f_obj = c_obj.f_obj + + # check default in child + assert f_obj.choice == "f_a" + assert f_obj._properties.get("f_a", None) is not None + + # setting choice with no properties in child as well + f_obj.choice = "f_c" + assert f_obj._properties.get("choice", None) == "f_c" + len(f_obj._properties) == 1 + + # serialize and deserialize should have no problem + s_c_obj = c_obj.serialize() + c_obj.deserialize(s_c_obj) + assert c_obj.choice == "f_obj" + assert c_obj._properties.get("f_obj") is not None + assert c_obj.f_obj.choice == "f_c" + + def test_choice_with_invalid_enum_and_none_value(api): config = api.prefix_config() f_obj = config.f diff --git a/openapiart/tests/test_oid_format.py b/openapiart/tests/test_oid_format.py new file mode 100644 index 00000000..43119a3b --- /dev/null +++ b/openapiart/tests/test_oid_format.py @@ -0,0 +1,111 @@ +import importlib +import pytest + +module = importlib.import_module("sanity") + + +def test_oid_in_normal_attr(default_config): + default_config.m_object.string_param = "abc" + default_config.m_object.integer = 44 + default_config.m_object.float = 1.23 + default_config.m_object.double = 3.45 + default_config.m_object.mac = "00:00:00:00:00:00" + default_config.m_object.ipv4 = "1.2.3.4" + default_config.m_object.ipv6 = "::" + default_config.m_object.hex = "0f" + default_config.m_object.oid = "." + + with pytest.raises(Exception) as execinfo: + default_config.serialize("dict") + error_value = execinfo.value.args[0] + assert "Invalid . format, expected oid" in error_value + + default_config.m_object.oid = "1." + with pytest.raises(Exception) as execinfo: + default_config.serialize("dict") + error_value = execinfo.value.args[0] + assert "Invalid 1. format, expected oid" in error_value + + default_config.m_object.oid = "0.42949672967.22.44.55" + with pytest.raises(Exception) as execinfo: + default_config.serialize("dict") + error_value = execinfo.value.args[0] + assert "Invalid 0.42949672967.22.44.55 format, expected oid" in error_value + + default_config.m_object.oid = "1.2.abcd" + with pytest.raises(Exception) as execinfo: + default_config.serialize("dict") + error_value = execinfo.value.args[0] + assert "Invalid 1.2.abcd format, expected oid" in error_value + + default_config.m_object.oid = "1.2.3.4.5" + data = default_config.serialize("dict") + + data["m_object"]["oid"] = "." + with pytest.raises(Exception) as execinfo: + default_config.deserialize(data) + error_value = execinfo.value.args[0] + assert "Invalid . format, expected oid" in error_value + + data["m_object"]["oid"] = "1." + with pytest.raises(Exception) as execinfo: + default_config.deserialize(data) + error_value = execinfo.value.args[0] + assert "Invalid 1. format, expected oid" in error_value + + data["m_object"]["oid"] = "0.42949672967.22.44.55" + with pytest.raises(Exception) as execinfo: + default_config.deserialize(data) + error_value = execinfo.value.args[0] + assert "Invalid 0.42949672967.22.44.55 format, expected oid" in error_value + + data["m_object"]["oid"] = "1.2.3.4" + default_config.deserialize(data) + + +def test_oid_x_field_pattern(default_config): + default_config.oid_pattern.oid.value = "1." + + with pytest.raises(Exception) as execinfo: + default_config.serialize("dict") + error_value = execinfo.value.args[0] + assert "Invalid 1. format, expected oid" in error_value + + default_config.oid_pattern.oid.value = "1.2.3.4" + data = default_config.serialize("dict") + + data["oid_pattern"]["oid"]["value"] = "1." + with pytest.raises(Exception) as execinfo: + default_config.deserialize(data) + error_value = execinfo.value.args[0] + assert "Invalid 1. format, expected oid" in error_value + + default_config.oid_pattern.oid.values = [ + "0.42949672967.22.44.55", + "1.2.3.4.5", + "0.", + ".", + "44.55555.77777", + ] + with pytest.raises(Exception) as execinfo: + default_config.serialize("dict") + error_value = execinfo.value.args[0] + assert "['0.42949672967.22.44.55', '0.', '.'] are not valid" in error_value + + default_config.oid_pattern.oid.values = ["1.2.3.4.5", "66.789.6789.56789"] + data = default_config.serialize("dict") + + data["oid_pattern"]["oid"]["values"] = [ + "0.42949672967.22.44.55", + "1.2.3.4.5", + "0.", + ".", + "44.55555.77777", + ] + with pytest.raises(Exception) as execinfo: + default_config.deserialize(data) + error_value = execinfo.value.args[0] + assert "['0.42949672967.22.44.55', '0.', '.'] are not valid" in error_value + + data["oid_pattern"]["oid"]["values"] = ["1.2.3.4.5", "66.789.6789.56789"] + default_config.deserialize(data) diff --git a/openapiart/tests/test_validate_x_field_pattern.py b/openapiart/tests/test_validate_x_field_pattern.py index 79d7fd7f..f13c5462 100644 --- a/openapiart/tests/test_validate_x_field_pattern.py +++ b/openapiart/tests/test_validate_x_field_pattern.py @@ -24,7 +24,7 @@ def str_compare(validte_str, entire_str): def test_validate_pattern(): error_msgs = [ "components.schemas.Config.properties.integer.x-field-pattern property using x-field-pattern with format integer must contain length property", - "components.schemas.Config.properties.wrong.x-field-pattern has unspported format random , valid formats are ['integer', 'ipv4', 'ipv6', 'mac', 'checksum']", + "components.schemas.Config.properties.wrong.x-field-pattern has unspported format random , valid formats are ['integer', 'ipv4', 'ipv6', 'mac', 'checksum', 'oid']", "components.schemas.Config.properties.int_128.x-field-pattern property using x-field-pattern with format integer cannot have length greater than 64", "signed property can only be used if the format is set to integer in property components.schemas.Config.properties.signed_value_without_int.x-field-pattern", "invalid value 45 in components.schemas.Config.properties.wrong_int_signed_value.x-field-pattern, signed property can either be true or false", diff --git a/pkg/choice_test.go b/pkg/choice_test.go index 54f9ac3f..886cafc5 100644 --- a/pkg/choice_test.go +++ b/pkg/choice_test.go @@ -94,3 +94,32 @@ func TestChoiceWithNoPropertiesForChoiceHeirarchy(t *testing.T) { _, err = choiceObj.Marshal().ToYaml() assert.Nil(t, err) } + +func TestChoiceWithNoPropertiesForChoiceDefault(t *testing.T) { + config := openapiart.NewPrefixConfig() + + choiceObj := config.ChoiceObject().Add() + + // check default should be no_obj + assert.Equal(t, choiceObj.Choice(), openapiart.ChoiceObjectChoice.NO_OBJ) + _, err := choiceObj.Marshal().ToYaml() + assert.Nil(t, err) + + fObj := choiceObj.FObj() + + // check default for child obj + assert.Equal(t, choiceObj.Choice(), openapiart.ChoiceObjectChoice.F_OBJ) + assert.Equal(t, fObj.Choice(), openapiart.FObjectChoice.F_A) + assert.True(t, fObj.HasFA()) + assert.Equal(t, fObj.FA(), "some string") + + // set choice with no properties in child obj + fObj.FC() + assert.Equal(t, fObj.Choice(), openapiart.FObjectChoice.F_C) + assert.False(t, fObj.HasFA()) + assert.False(t, fObj.HasFB()) + + // validate the whole object + _, err = choiceObj.Marshal().ToYaml() + assert.Nil(t, err) +} diff --git a/pkg/common.go b/pkg/common.go index 586742b8..3f8dc885 100644 --- a/pkg/common.go +++ b/pkg/common.go @@ -331,6 +331,21 @@ func (obj *validation) validateHex(hex string) error { return nil } +func (obj *validation) validateOid(oid string) error { + segments := strings.Split(oid, ".") + if len(segments) < 2 { + return fmt.Errorf(fmt.Sprintf("Invalid oid value %s", oid)) + } + + for _, segment := range segments { + number, err := strconv.Atoi(segment) + if err != nil || 0 > number || number > 4294967295 { + return fmt.Errorf(fmt.Sprintf("Invalid oid value %s", oid)) + } + } + return nil +} + func (obj *validation) validateSlice(valSlice []string, sliceType string) error { indices := []string{} var err error @@ -344,6 +359,8 @@ func (obj *validation) validateSlice(valSlice []string, sliceType string) error err = obj.validateIpv6(val) } else if sliceType == "hex" { err = obj.validateHex(val) + } else if sliceType == "oid" { + err = obj.validateOid(val) } else { return fmt.Errorf(fmt.Sprintf("Invalid slice type received <%s>", sliceType)) } @@ -376,6 +393,10 @@ func (obj *validation) validateHexSlice(hex []string) error { return obj.validateSlice(hex, "hex") } +func (obj *validation) validateOidSlice(oid []string) error { + return obj.validateSlice(oid, "oid") +} + // TODO: restore behavior // func (obj *validation) createMap(objName string) { // if obj.constraints == nil { diff --git a/pkg/oid_format_test.go b/pkg/oid_format_test.go new file mode 100644 index 00000000..29b27f47 --- /dev/null +++ b/pkg/oid_format_test.go @@ -0,0 +1,54 @@ +package openapiart_test + +import ( + "testing" + + openapiart "github.com/open-traffic-generator/openapiart/pkg" + "github.com/stretchr/testify/assert" +) + +func TestOid(t *testing.T) { + config := openapiart.NewPrefixConfig() + m := config.MObject() + m.SetDouble(1.23) + m.SetFloat(3.45) + m.SetHex("0f") + m.SetIpv4("1.2.3.4") + m.SetIpv6("::") + m.SetMac("00:00:00:00:00:00") + m.SetStringParam("abcd") + m.SetInteger(34) + + m.SetOid("1.abc") + _, err := m.Marshal().ToJson() + assert.NotNil(t, err) + assert.Contains(t, err.Error(), "Invalid oid value 1.abc on MObject.Oid") + + m.SetOid("1.") + _, err = m.Marshal().ToJson() + assert.NotNil(t, err) + assert.Contains(t, err.Error(), "Invalid oid value 1. on MObject.Oid") + + m.SetOid("1.-1.33.44.5678.9876") + _, err = m.Marshal().ToJson() + assert.NotNil(t, err) + assert.Contains(t, err.Error(), "Invalid oid value 1.-1.33.44.5678.9876 on MObject.Oid") + + m.SetOid("1.2.3.4.5.6.7") + _, err = m.Marshal().ToJson() + assert.Nil(t, err) +} + +func TestOidSlice(t *testing.T) { + config := openapiart.NewPrefixConfig() + oid := config.OidPattern().Oid() + + oid.SetValues([]string{"1.2.3.4", "3.4.5.6"}) + _, err := oid.Marshal().ToJson() + assert.Nil(t, err) + + oid.SetValues([]string{"1.2.3.4", "3.4.5.6", "-1.3.4.5", "1", ".", "11111.33333", "abcd.23", "1.2.3.4294967298"}) + _, err = oid.Marshal().ToJson() + assert.NotNil(t, err) + assert.Contains(t, err.Error(), "Invalid oid addresses at indices 2,3,4,6,7 on PatternOidPatternOid.Values") +}