def resolve( components: t.Dict[str, t.Dict[str, t.Any]], work_item: t.Dict[str, t.Any], ) -> model.OASType[t.Any]: if '$ref' in work_item: logger.opt(lazy=True).trace( 'Following reference {ref}', ref=lambda: work_item['$ref'], ) return resolve( components, ref.resolve(components, work_item['$ref']), ) elif 'type' in work_item: oas_type = work_item['type'] logger.opt(lazy=True).trace( 'Resolving schema of type={type}', type=lambda: oas_type, ) resolvers = { 'number': functools.partial(_resolve_oas_number, float), 'integer': functools.partial(_resolve_oas_number, int), 'boolean': _resolve_oas_boolean, 'string': _resolve_oas_string, 'array': functools.partial(_resolve_oas_array, components), 'object': functools.partial(_resolve_oas_object, components), } # type: t.Dict[str, t.Callable[[t.Dict[str, t.Any]], model.OASType[t.Any]]] return resolvers[oas_type](work_item) elif 'anyOf' in work_item: return _resolve_any_of( components=components, work_item=work_item, ) elif 'oneOf' in work_item: return _resolve_one_of( components=components, work_item=work_item, ) elif 'allOf' in work_item: return _resolve_all_of( components=components, work_item=work_item, ) else: return _resolve_oas_any(work_item)
def _resolve_all_of( components: t.Dict[str, t.Dict[str, t.Any]], work_item: t.Dict[str, t.Any], ) -> model.OASType[t.Any]: # check what allOf stuff we build # - check if there is a conflict in definitions # - https://json-schema.org/understanding-json-schema/reference/combining.html#allof # pick the type and go with normal resolution along with # merging the resolved models mix_definition: t.List[t.Dict[str, t.Any]] = work_item.get('allOf', []) resolved_mix_def = list( map( lambda mx: ref.resolve(components, mx['$ref']) if '$ref' in mx else mx, mix_definition, ), ) schema_types: t.Set[str] = set( filter( lambda mx: mx is not None, map( lambda mx: mx.get('type', None), # type: ignore resolved_mix_def, ), ), ) if len(schema_types) == 0: oas_type = 'any' elif len(schema_types) > 1: raise exceptions.OASConflict( f'allOf cannot combine more then one OAS type. ' f'Detected those types [{", ".join(iter(map(repr, schema_types)))}]', ) else: oas_type = list(schema_types)[0] logger.opt(lazy=True).trace( 'allOf resolves into type: {oas_type}', oas_type=lambda: oas_type, ) all_of_merged = all_of.merge(oas_type, {}, work_item) for sub_schema in resolved_mix_def: all_of_merged = all_of.merge(oas_type, all_of_merged, sub_schema) return resolve(components=components, work_item=all_of_merged)
def _resolve_parameters( components: t.Dict[str, t.Any], parameters: t.List[t.Dict[str, t.Any]], ) -> model.OASParameters: logger.opt(lazy=True).trace( 'Resolving {count} of parameters', count=lambda: len(parameters), ) resolved_parameters: t.List[model.OASParameter] = [] for param in parameters: if '$ref' in param: logger.trace( 'Parameter defined as $ref, following $ref={ref}', ref=param['$ref'], ) param_def = parse_ref.resolve(components, param['$ref']) else: param_def = param param_type_to_cls: t.Mapping[str, t.Type[P]] = { 'header': model.OASHeaderParameter, 'path': model.OASPathParameter, 'query': model.OASQueryParameter, 'cookie': model.OASCookieParameter, } param_in = param_type_to_cls[param_def['in']] param_name = param_def['name'] logger.trace( 'Resolving param={param_name} defined in={param_in}', param_name=param_name, param_in=param_in, ) resolved_parameters.append( _resolve_parameter( components=components, param_name=param_name, param_def=param_def, param_in=param_in, ), ) return frozenset(resolved_parameters)
def _resolve_content( components: t.Dict[str, t.Dict[str, t.Any]], work_item: t.Dict[str, t.Any], ) -> model.OASContent: if '$ref' in work_item: return _resolve_content( components, parse_ref.resolve(components, work_item['$ref']), ) elif 'content' in work_item: work_item = work_item['content'] def _build_media_type( mime_type: str, media_type_def: t.Dict[str, t.Any], ) -> model.OASMediaType: # raw_example = media_type_def.get('example', None) # raw_examples = media_type_def.get('examples', {}) # raw_encoding = media_type_def.get('encoding', None) # if not (raw_example and raw_examples) and schema.example is not None: # raw_examples = [{mime_type: schema.example}] # elif raw_examples is None and raw_example is not None: # raw_examples = [{mime_type: raw_example}] return model.OASMediaType(schema=parse_type.resolve( components, media_type_def['schema'], ), ) return { model.MimeType(mime_type): _build_media_type(mime_type, work_item[mime_type]) for mime_type in work_item } else: return {}
def test_spec_load_follow_ref( reference: str, expected_def: t.Dict[str, t.Any], ) -> None: components = { 'requestBodies': { 'Example': { 'description': 'category to add to the system', 'content': { 'application/json': { 'schema': { '$ref': '#/components/schemas/Category', }, 'examples': { 'user': { 'summary': 'Category Example', 'externalValue': 'http://category-example.json', }, }, }, }, }, }, 'schemas': { 'Category': { 'type': 'object', 'properties': { 'id': { 'type': 'integer', 'format': 'int64', }, 'name': { 'type': 'string', }, }, }, 'UltraCategory': { '$ref': '#/components/schemas/Category', }, }, 'headers': { 'X-Rate-Limit': { 'description': 'Rate limit for this API', 'required': True, 'schema': { 'type': 'integer', 'format': 'int32', }, }, }, 'parameters': { 'limitParam': { 'name': 'limit', 'in': 'query', 'description': 'max records to return', 'required': True, 'schema': { 'type': 'integer', 'format': 'int32', }, }, }, 'responses': { 'NotFound': { 'description': 'Entity not found.', }, }, 'securitySchemes': { 'apiKey': { 'type': 'apiKey', 'name': 'api_key', 'in': 'header', }, }, } assert expected_def == ref.resolve(components, reference)
def test_spec_load_follow_ref_no_such_ref() -> None: with pytest.raises(KeyError): ref.resolve({}, '#/components/schemas/Dummy')
def _resolve_parameter( components: t.Dict[str, t.Dict[str, t.Any]], param_name: str, param_def: t.Dict[str, t.Any], param_in: t.Type[P], ) -> P: if '$ref' in param_def: return _resolve_parameter( components=components, param_name=param_name, param_def=parse_ref.resolve(components, param_def['$ref']), param_in=param_in, ) else: # needed to determine proper content carried by the field # either schema or content will bet set, otherwise OAS is invalid schema = param_def.get('schema', None) style = model.OASParameterStyles[param_def.get( 'style', param_in.default_style, )] content = param_def.get('content', None) # post processing fields explode = bool(param_def.get('explode', style.name == 'form')) required = bool(param_def.get('required', False)) deprecated = bool(param_def.get('deprecated', False)) example = param_def.get('example', None) # those fields are valid either for cookie or header allow_empty_value: t.Optional[ bool] = None if 'style' in param_def else bool( param_def.get('allowEmptyValue', False), ) final_schema: t.Union[t.Tuple[model.OASType[t.Any], model.OASParameterStyle, ], model.OASContent] if content is not None: final_schema = _resolve_content(components, param_def) else: if param_in not in style.locations: raise ValueError( f'Path param {param_name} has incompatible style {style.name}', ) final_schema = ( parse_type.resolve(components, schema), style, ) if issubclass(param_in, model.OASHeaderParameter): if param_name in model.OASReservedHeaders: raise ValueError( f'Header parameter name {param_name} is reserved thus invalid', ) return model.OASHeaderParameter( name=model.OASParameterName(param_name), example=example, required=required, explode=explode, deprecated=deprecated, schema=final_schema, ) elif issubclass(param_in, model.OASPathParameter): if not required: raise ValueError( f'Path parameter {param_name} must have required set to True', ) return model.OASPathParameter( name=model.OASParameterName(param_name), example=example, explode=explode, deprecated=deprecated, schema=final_schema, ) elif issubclass(param_in, model.OASQueryParameter): allow_reserved = bool(param_def.get('allowReserved', False)) return model.OASQueryParameter( name=model.OASParameterName(param_name), example=example, required=required, explode=explode, deprecated=deprecated, schema=final_schema, allow_empty_value=allow_empty_value, allow_reserved=allow_reserved, ) elif issubclass(param_in, model.OASCookieParameter): return model.OASCookieParameter( name=model.OASParameterName(param_name), example=example, required=required, explode=explode, deprecated=deprecated, schema=final_schema, ) else: raise ValueError( f'Cannot build parameter {param_name} ' f'definition from {param_in}', )