Skip to content
Merged
10 changes: 9 additions & 1 deletion linodecli/baked/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,12 +182,20 @@ def _parse_request_model(
depth=depth,
)

# Handle arrays of objects that not marked as JSON
# Handle arrays of objects that are not marked as JSON.
# NOTE: We only expand an array of objects into individual child
# arguments when it is not already nested under another list
# (i.e. parent is None). The CLI can only associate one level of
# nested list objects, so a list of objects nested within another
# list must be treated as JSON instead.
# Otherwise, we would generate child arguments that can never
# be used because they always conflict with their implicit JSON parent.
elif (
v.type == "array"
and v.items
and v.items.type == "object"
and v.extensions.get("linode-cli-format") != "json"
and parent is None
):
# handle lists of objects as a special case, where each property
# of the object in the list is its own argument
Expand Down
13 changes: 13 additions & 0 deletions tests/fixtures/api_request_test_foobar_post.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,19 @@ components:
description: An arbitrary deeply nested array.
items:
type: string
field_object_array:
type: array
description: An arbitrary array of objects nested within a list.
items:
type: object
description: An arbitrary deeply nested object.
properties:
nested_object_string:
type: string
description: A string on a deeply nested list object.
nested_object_int:
type: number
description: An int on a deeply nested list object.
field_string:
type: string
description: An arbitrary field.
Expand Down
49 changes: 49 additions & 0 deletions tests/unit/test_operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ def test_parse_args_object_list(self, create_operation):
"field_int": 123,
"field_dict": {"nested_string": "test2", "nested_int": 789},
"field_array": ExplicitJsonValue(json_value=["foo", "bar"]),
"field_object_array": None, # We expect this to be filtered out later
"nullable_string": None, # We expect this to be filtered out later
},
{"field_int": 456, "field_dict": {"nested_string": "test3"}},
Expand All @@ -218,6 +219,54 @@ def test_parse_args_object_list_json(self, create_operation):

assert result.object_list.json_value == expected

def test_nested_object_list_treated_as_json(self, create_operation):
"""
An array of objects nested within another array of objects (e.g.
--object_list.field_object_array) can only be specified as JSON.
It should not be expanded into unusable child arguments (e.g.
--object_list.field_object_array.nested_object_string).
"""
args_by_path = {arg.path: arg for arg in create_operation.args}

nested = args_by_path.get("object_list.field_object_array")
assert nested is not None
assert nested.format == "json"
assert nested.datatype == "object"
assert nested.is_child
assert nested.parent == "object_list"
assert not nested.is_parent

# No child arguments should have been generated for its properties.
assert "object_list.field_object_array.nested_object_string" not in (
args_by_path
)
assert "object_list.field_object_array.nested_object_int" not in (
args_by_path
)

def test_parse_args_nested_object_list_json(self, create_operation):
"""
A nested array of objects should be accepted as a JSON string value
associated with each entry of its parent list.
"""
result = create_operation.parse_args(
[
"--object_list.field_string",
"test1",
"--object_list.field_object_array",
json.dumps([{"nested_object_string": "foo"}]),
]
)

assert result.object_list == [
{
"field_string": "test1",
"field_object_array": ExplicitJsonValue(
json_value=[{"nested_object_string": "foo"}]
),
},
]

def test_parse_args_conflicting_parent_child(self, create_operation):
stderr_buf = io.StringIO()

Expand Down