Skip to content

Commit 0f8ab49

Browse files
authored
Merge pull request #9463 from AndrewAsseily/nyandrew/add-required-to-constraints
Add required field validation to nested structures
2 parents f43ce44 + c9ba464 commit 0f8ab49

File tree

2 files changed

+50
-11
lines changed

2 files changed

+50
-11
lines changed

awscli/clidocs.py

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -248,21 +248,23 @@ def _document_nested_structure(self, model, doc):
248248
"""Recursively documents parameters in nested structures"""
249249
member_type_name = getattr(model, 'type_name', None)
250250
if member_type_name == 'structure':
251+
required_members = model.metadata.get('required', [])
251252
for member_name, member_shape in model.members.items():
253+
is_required = member_name in required_members
252254
self._doc_member(
253-
doc, member_name, member_shape, stack=[model.name]
255+
doc, member_name, member_shape, stack=[model.name], required=is_required
254256
)
255257
elif member_type_name == 'list':
256-
self._doc_member(doc, '', model.member, stack=[model.name])
258+
self._doc_member(doc, '', model.member, stack=[model.name], required=False)
257259
elif member_type_name == 'map':
258260
key_shape = model.key
259261
key_name = key_shape.serialization.get('name', 'key')
260-
self._doc_member(doc, key_name, key_shape, stack=[model.name])
262+
self._doc_member(doc, key_name, key_shape, stack=[model.name], required=False)
261263
value_shape = model.value
262264
value_name = value_shape.serialization.get('name', 'value')
263-
self._doc_member(doc, value_name, value_shape, stack=[model.name])
265+
self._doc_member(doc, value_name, value_shape, stack=[model.name], required=False)
264266

265-
def _doc_member(self, doc, member_name, member_shape, stack):
267+
def _doc_member(self, doc, member_name, member_shape, stack, required=False):
266268
if member_shape.name in stack:
267269
# Document the recursion once, otherwise just
268270
# note the fact that it's recursive and return.
@@ -272,11 +274,11 @@ def _doc_member(self, doc, member_name, member_shape, stack):
272274
return
273275
stack.append(member_shape.name)
274276
try:
275-
self._do_doc_member(doc, member_name, member_shape, stack)
277+
self._do_doc_member(doc, member_name, member_shape, stack, required)
276278
finally:
277279
stack.pop()
278280

279-
def _do_doc_member(self, doc, member_name, member_shape, stack):
281+
def _do_doc_member(self, doc, member_name, member_shape, stack, required=False):
280282
docs = member_shape.documentation
281283
type_name = self._get_argument_type_name(
282284
member_shape, member_shape.type_name
@@ -290,22 +292,29 @@ def _do_doc_member(self, doc, member_name, member_shape, stack):
290292
doc.include_doc_string(docs)
291293
if is_tagged_union_type(member_shape):
292294
self._add_tagged_union_note(member_shape, doc)
295+
296+
if required:
297+
doc.style.new_paragraph()
298+
doc.write('This parameter is required.')
299+
293300
self._document_enums(member_shape, doc)
294301
self._document_constraints(member_shape, doc)
295302
doc.style.new_paragraph()
296303
member_type_name = member_shape.type_name
297304
if member_type_name == 'structure':
305+
required_members = member_shape.metadata.get('required', [])
298306
for sub_name, sub_shape in member_shape.members.items():
299-
self._doc_member(doc, sub_name, sub_shape, stack)
307+
sub_required = sub_name in required_members
308+
self._doc_member(doc, sub_name, sub_shape, stack, required=sub_required)
300309
elif member_type_name == 'map':
301310
key_shape = member_shape.key
302311
key_name = key_shape.serialization.get('name', 'key')
303-
self._doc_member(doc, key_name, key_shape, stack)
312+
self._doc_member(doc, key_name, key_shape, stack, required=False)
304313
value_shape = member_shape.value
305314
value_name = value_shape.serialization.get('name', 'value')
306-
self._doc_member(doc, value_name, value_shape, stack)
315+
self._doc_member(doc, value_name, value_shape, stack, required=False)
307316
elif member_type_name == 'list':
308-
self._doc_member(doc, '', member_shape.member, stack)
317+
self._doc_member(doc, '', member_shape.member, stack, required=False)
309318
doc.style.dedent()
310319
doc.style.new_paragraph()
311320

tests/unit/test_clidocs.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,36 @@ def test_tagged_union_comes_after_docstring_output(self):
499499
rendered = help_command.doc.getvalue().decode('utf-8')
500500
self.assertRegex(rendered, r'FooBar[\s\S]*Tagged Union')
501501

502+
def test_documents_required_parameters(self):
503+
"""Tests that required parameters are correctly documented."""
504+
shape_map = {
505+
'ParentStructure': {
506+
'type': 'structure',
507+
'required': ['RequiredParameter'],
508+
'members': {
509+
'RequiredParameter': {'shape': 'StringMember'},
510+
'OptionalParameter': {'shape': 'StringMember'},
511+
}
512+
},
513+
'StringMember': {'type': 'string'},
514+
}
515+
516+
resolver = ShapeResolver(shape_map)
517+
parent_shape = StructureShape(
518+
'ParentStructure',
519+
shape_map['ParentStructure'],
520+
resolver
521+
)
522+
523+
rendered = self.get_help_docs_for_argument(parent_shape)
524+
525+
required_index = rendered.find('RequiredParameter -> (string)')
526+
optional_index = rendered.find('OptionalParameter -> (string)')
527+
528+
self.assertIn('This parameter is required', rendered[required_index:optional_index])
529+
optional_text = rendered[optional_index:]
530+
self.assertNotIn('This parameter is required', optional_text)
531+
502532
def test_documents_constraints(self):
503533
shape = {'type': 'string', 'min': 0, 'max': 10, 'pattern': '.*'}
504534
shape = StringShape('ConstrainedArg', shape)

0 commit comments

Comments
 (0)