diff --git a/almdrlib/client.py b/almdrlib/client.py index bf5e84d..b5b4b39 100644 --- a/almdrlib/client.py +++ b/almdrlib/client.py @@ -13,7 +13,11 @@ import json import jsonschema from jsonschema.validators import validator_for +from collections import OrderedDict +from functools import reduce + import alsdkdefs +from almdrlib.util import * from almdrlib.exceptions import AlmdrlibValueError from almdrlib.config import Config @@ -247,7 +251,7 @@ def add_content(self, content_type, schema, al_schema): if not schema: return - datatype = schema.get(OpenAPIKeyWord.TYPE) + datatype = schema.get(OpenAPIKeyWord.TYPE, derive_type_from_decomposed_schema(schema)) name = al_schema.get(OpenAPIKeyWord.NAME, OpenAPIKeyWord.DATA) if datatype == OpenAPIKeyWord.OBJECT: parameter = RequestBodyObjectParameter( @@ -786,18 +790,65 @@ def __getattr__(self, op_name): def _normalize_schema(name, schema, required=False): properties = schema.get(OpenAPIKeyWord.PROPERTIES) + oneof = schema.get(OpenAPIKeyWord.ONE_OF) + anyof = schema.get(OpenAPIKeyWord.ANY_OF) + allof = schema.get(OpenAPIKeyWord.ALL_OF) + compound_schema = oneof or anyof or allof + if properties and bool(properties): return schema - result = { - OpenAPIKeyWord.TYPE: OpenAPIKeyWord.OBJECT, - OpenAPIKeyWord.PROPERTIES: { + if compound_schema: + # TODO move to alertlogic-sdk-definitions normalize_node + if oneof: + xof = OpenAPIKeyWord.ONE_OF + elif anyof: + xof = OpenAPIKeyWord.ANY_OF + elif allof: + xof = OpenAPIKeyWord.ALL_OF + + def reduce_props(acc, prop): + (k, v) = prop + existing_prop = acc.get(k, []) + if existing_prop and isinstance(existing_prop, list): + existing_prop.append(v) + acc[k] = existing_prop + return acc + elif existing_prop: + if existing_prop == v: + return acc + acc[k] = [v, existing_prop] + return acc + else: + acc[k] = v + return acc + + def add_xof(item): + (k, v) = item + if isinstance(v, list): + return k, OrderedDict({xof: v}) + else: + return item + + allprops = reduce(lambda acc, s: acc + list(s.get(OpenAPIKeyWord.PROPERTIES, OrderedDict()).items()), + compound_schema, []) + reduced = reduce(reduce_props, allprops, OrderedDict()) + compound_props = OrderedDict(map(add_xof, reduced.items())) + properties = compound_props + + if not properties: + properties = OrderedDict({ name: schema - } - } + }) + result = OrderedDict({ + OpenAPIKeyWord.TYPE: derive_type_from_decomposed_schema(schema) or OpenAPIKeyWord.OBJECT, + OpenAPIKeyWord.PROPERTIES: properties + }) + if required: result[OpenAPIKeyWord.REQUIRED] = [name] + return result diff --git a/almdrlib/util.py b/almdrlib/util.py new file mode 100644 index 0000000..932fc15 --- /dev/null +++ b/almdrlib/util.py @@ -0,0 +1,14 @@ +from alsdkdefs import OpenAPIKeyWord + + +def derive_type_from_decomposed_schema(schema): + oneof = schema.get(OpenAPIKeyWord.ONE_OF) + anyof = schema.get(OpenAPIKeyWord.ANY_OF) + allof = schema.get(OpenAPIKeyWord.ALL_OF) + + if oneof or anyof or allof: + # Attempt to find type in the decomposed schema + find_type = [t.get('type') for t in allof or anyof or oneof if t.get('type')] + return find_type[0] if find_type else None + else: + return None diff --git a/tests/apis/testapi/testapi.v1.yaml b/tests/apis/testapi/testapi.v1.yaml index 57ad07b..0c4f596 100644 --- a/tests/apis/testapi/testapi.v1.yaml +++ b/tests/apis/testapi/testapi.v1.yaml @@ -16,6 +16,35 @@ servers: description: integration x-alertlogic-session-endpoint: true paths: + '/testapi/v1/postdata': + post: + operationId: post_data_compound + requestBody: + content: + application/json: + schema: + oneOf: + - type: object + properties: + prop: + type: string + additionalProperties: true + - type: object + properties: + prop: + type: integer + - type: string + responses: + '200': + description: 200 resp + content: + application/json: + schema: + type: object + properties: + prop: + type: string + '/testapi/v1/{account_id}/test_get_data': get: summary: Test Get Data Operation diff --git a/tests/test_open_api_support.py b/tests/test_open_api_support.py index 1810ffe..88be1ad 100644 --- a/tests/test_open_api_support.py +++ b/tests/test_open_api_support.py @@ -11,7 +11,7 @@ from almdrlib.client import Config from almdrlib.client import Operation from alsdkdefs import OpenAPIKeyWord - +from collections import OrderedDict class TestSdk_open_api_support(unittest.TestCase): """Tests for `python_boilerplate` package.""" @@ -83,3 +83,14 @@ def test_003_default_objects_creation(self): self.assertIsInstance(Session(), Session) self.assertIsInstance(Config(), Config) self.assertIsInstance(Client(self._service_name), Client) + + def test_004_test_operations_compound_schema(self): + """Test request body properties are properly normalized when schema is compound at the top level""" + client = Client(self._service_name) + operation = client.operations.get('post_data_compound') + self.assertEqual(OrderedDict([('prop', + OrderedDict([('oneOf', [ + OrderedDict([('type', 'integer')]), + OrderedDict([('type', 'string')]) + ])]))]), + operation.body._content['application/json']._properties)