Skip to content

Commit

Permalink
Fix marshal and un-marshal for choice properties (#451)
Browse files Browse the repository at this point in the history
* add test

* adding changes for default

* bug fix for default error

* adding more tests and todos

* bug fix

* add logs

* bug fix

* adding python test for choice unmarshal

* resolve merge conflicts

* add test

* handling some more choice cases
  • Loading branch information
Vibaswan authored Mar 7, 2024
1 parent 4876135 commit fc4ce58
Show file tree
Hide file tree
Showing 5 changed files with 542 additions and 25 deletions.
153 changes: 133 additions & 20 deletions openapiart/openapiartgo.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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(
Expand All @@ -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:
Expand Down Expand Up @@ -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(
"<choice_fields>",
"\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("<choice_fields>", "")
)
# enum_fields = []
# body = (
# choice_body.replace(
# "<choice_fields>",
# "\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() {{
Expand Down
68 changes: 64 additions & 4 deletions openapiart/tests/config/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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"
Expand All @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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__])
Loading

0 comments on commit fc4ce58

Please sign in to comment.