diff --git a/config/checkstyle/custom-suppressions.xml b/config/checkstyle/custom-suppressions.xml index c19f8f95c6..9f216df04c 100644 --- a/config/checkstyle/custom-suppressions.xml +++ b/config/checkstyle/custom-suppressions.xml @@ -7,4 +7,5 @@ + diff --git a/openapi/src/main/java/io/micronaut/openapi/visitor/AbstractOpenApiEndpointVisitor.java b/openapi/src/main/java/io/micronaut/openapi/visitor/AbstractOpenApiEndpointVisitor.java index f3836bd1a4..aa7f69d395 100644 --- a/openapi/src/main/java/io/micronaut/openapi/visitor/AbstractOpenApiEndpointVisitor.java +++ b/openapi/src/main/java/io/micronaut/openapi/visitor/AbstractOpenApiEndpointVisitor.java @@ -387,90 +387,74 @@ public void visitMethod(MethodElement element, VisitorContext context) { return; } incrementVisitedElements(context); + OpenAPI openAPI = Utils.resolveOpenApi(context); + JavadocDescription javadocDescription = null; + boolean permitsRequestBody = HttpMethod.permitsRequestBody(httpMethod); + Map> pathItemsMap = resolvePathItems(context, matchTemplates); - for (Map.Entry> entry : pathItemsMap.entrySet()) { - List pathItems = entry.getValue(); - OpenAPI openAPI = Utils.resolveOpenAPI(context); - io.swagger.v3.oas.models.Operation swaggerOperation = readOperation(element, context); + for (Map.Entry> pathItemEntry : pathItemsMap.entrySet()) { + List pathItems = pathItemEntry.getValue(); - io.swagger.v3.oas.models.ExternalDocumentation externalDocs = readExternalDocs(element, context); - if (externalDocs == null) { - externalDocs = classExternalDocs; - } - if (externalDocs != null) { - swaggerOperation.setExternalDocs(externalDocs); - } + Map swaggerOperations = readOperations(pathItemEntry.getKey(), httpMethod, pathItems, element, context); - readTags(element, context, swaggerOperation, classTags == null ? Collections.emptyList() : classTags, openAPI); + for (Map.Entry operationEntry : swaggerOperations.entrySet()) { + io.swagger.v3.oas.models.Operation swaggerOperation = operationEntry.getValue(); + io.swagger.v3.oas.models.ExternalDocumentation externalDocs = readExternalDocs(element, context); + if (externalDocs == null) { + externalDocs = classExternalDocs; + } + if (externalDocs != null) { + swaggerOperation.setExternalDocs(externalDocs); + } - readSecurityRequirements(element, entry.getKey(), swaggerOperation, context); + readTags(element, context, swaggerOperation, classTags == null ? Collections.emptyList() : classTags, openAPI); - readApiResponses(element, context, swaggerOperation); + readSecurityRequirements(element, pathItemEntry.getKey(), swaggerOperation, context); - readServers(element, context, swaggerOperation); + readApiResponses(element, context, swaggerOperation); - readCallbacks(element, context, swaggerOperation); + readServers(element, context, swaggerOperation); - JavadocDescription javadocDescription = getMethodDescription(element, swaggerOperation); + readCallbacks(element, context, swaggerOperation); - if (element.isAnnotationPresent(Deprecated.class)) { - swaggerOperation.setDeprecated(true); - } + javadocDescription = getMethodDescription(element, swaggerOperation); + + if (element.isAnnotationPresent(Deprecated.class)) { + swaggerOperation.setDeprecated(true); + } - readResponse(element, context, openAPI, swaggerOperation, javadocDescription); + readResponse(element, context, openAPI, swaggerOperation, javadocDescription); - boolean permitsRequestBody = HttpMethod.permitsRequestBody(httpMethod); - if (permitsRequestBody) { - Optional requestBody = readSwaggerRequestBody(element, context); - if (requestBody.isPresent()) { - RequestBody currentRequestBody = swaggerOperation.getRequestBody(); - if (currentRequestBody != null) { - swaggerOperation.setRequestBody(mergeRequestBody(currentRequestBody, requestBody.get())); - } else { - swaggerOperation.setRequestBody(requestBody.get()); + if (permitsRequestBody) { + Optional requestBody = readSwaggerRequestBody(element, context); + if (requestBody.isPresent()) { + RequestBody currentRequestBody = swaggerOperation.getRequestBody(); + if (currentRequestBody != null) { + swaggerOperation.setRequestBody(mergeRequestBody(currentRequestBody, requestBody.get())); + } else { + swaggerOperation.setRequestBody(requestBody.get()); + } } } - } - List swaggerOperations = new ArrayList<>(pathItems.size()); - int i = 0; - for (PathItem pathItem : pathItems) { - if (i == 0) { - swaggerOperation = setOperationOnPathItem(pathItem, swaggerOperation, httpMethod); - swaggerOperations.add(swaggerOperation); - } else { - io.swagger.v3.oas.models.Operation copyOperation = new io.swagger.v3.oas.models.Operation(); - copyOperation.setTags(swaggerOperation.getTags()); - copyOperation.setSummary(swaggerOperation.getSummary()); - copyOperation.setDescription(swaggerOperation.getDescription()); - copyOperation.setExternalDocs(swaggerOperation.getExternalDocs()); - copyOperation.setOperationId(swaggerOperation.getOperationId()); - copyOperation.setParameters(swaggerOperation.getParameters()); - copyOperation.setRequestBody(swaggerOperation.getRequestBody()); - copyOperation.setResponses(swaggerOperation.getResponses()); - copyOperation.setCallbacks(swaggerOperation.getCallbacks()); - copyOperation.setDeprecated(swaggerOperation.getDeprecated()); - copyOperation.setSecurity(swaggerOperation.getSecurity()); - copyOperation.setServers(swaggerOperation.getServers()); - copyOperation.setExtensions(swaggerOperation.getExtensions()); - - copyOperation = setOperationOnPathItem(pathItem, copyOperation, httpMethod); - swaggerOperations.add(copyOperation); - } - i++; + swaggerOperation = setOperationOnPathItem(operationEntry.getKey(), swaggerOperation, httpMethod); } Map pathVariables = new HashMap<>(); for (UriMatchTemplate matchTemplate : matchTemplates) { - pathVariables.putAll(pathVariables(matchTemplate)); + for (Map.Entry varEntry : pathVariables(matchTemplate).entrySet()) { + if (pathItemEntry.getKey().contains("{" + varEntry.getKey() + '}')) { + pathVariables.put(varEntry.getKey(), varEntry.getValue()); + } + } // @Parameters declared at method level take precedence over the declared as method arguments, so we process them first - processParameterAnnotationInMethod(element, openAPI, matchTemplate, httpMethod); + processParameterAnnotationInMethod(element, openAPI, matchTemplate, httpMethod, pathVariables); } List consumesMediaTypes = consumesMediaTypes(element); List extraBodyParameters = new ArrayList<>(); - for (io.swagger.v3.oas.models.Operation operation : swaggerOperations) { - processParameters(element, context, openAPI, operation, javadocDescription, permitsRequestBody, pathVariables, consumesMediaTypes, extraBodyParameters); + for (io.swagger.v3.oas.models.Operation operation : swaggerOperations.values()) { + processParameters(element, context, openAPI, operation, javadocDescription, permitsRequestBody, pathVariables, consumesMediaTypes, extraBodyParameters, httpMethod, matchTemplates, pathItems); processExtraBodyParameters(context, httpMethod, openAPI, operation, javadocDescription, consumesMediaTypes, extraBodyParameters); } } @@ -524,7 +508,10 @@ private void processParameters(MethodElement element, VisitorContext context, Op boolean permitsRequestBody, Map pathVariables, List consumesMediaTypes, - List extraBodyParameters) { + List extraBodyParameters, + HttpMethod httpMethod, + List matchTemplates, + List pathItems) { if (ArrayUtils.isEmpty(element.getParameters())) { return; } @@ -536,7 +523,7 @@ private void processParameters(MethodElement element, VisitorContext context, Op for (ParameterElement parameter : element.getParameters()) { if (!alreadyProcessedParameter(swaggerParameters, parameter)) { processParameter(context, openAPI, swaggerOperation, javadocDescription, permitsRequestBody, pathVariables, - consumesMediaTypes, swaggerParameters, parameter, extraBodyParameters); + consumesMediaTypes, swaggerParameters, parameter, extraBodyParameters, httpMethod, matchTemplates, pathItems); } } if (CollectionUtils.isNotEmpty(swaggerParameters)) { @@ -546,13 +533,14 @@ private void processParameters(MethodElement element, VisitorContext context, Op private boolean alreadyProcessedParameter(List swaggerParameters, ParameterElement parameter) { return swaggerParameters.stream() - .anyMatch(p -> p.getName().equals(parameter.getName())); + .anyMatch(p -> p.getName().equals(parameter.getName()) && p.getIn() != null); } private void processParameterAnnotationInMethod(MethodElement element, OpenAPI openAPI, UriMatchTemplate matchTemplate, - HttpMethod httpMethod) { + HttpMethod httpMethod, + Map pathVariables) { List> parameterAnnotations = element .getDeclaredAnnotationValuesByType(io.swagger.v3.oas.annotations.Parameter.class); @@ -576,6 +564,45 @@ private void processParameterAnnotationInMethod(MethodElement element, paramAnn.stringValue("ref").ifPresent(parameter::$ref); paramAnn.enumValue("style", ParameterStyle.class).ifPresent(style -> parameter.setStyle(paramStyle(style))); + if (parameter.getIn() == null) { + for (ParameterElement paramEl : element.getParameters()) { + if (!paramEl.getName().equals(parameter.getName())) { + continue; + } + if (paramEl.isAnnotationPresent(PathVariable.class)) { + parameter.setIn("path"); + } else if (paramEl.isAnnotationPresent(QueryValue.class)) { + parameter.setIn("query"); + } else if (paramEl.isAnnotationPresent(CookieValue.class)) { + parameter.setIn("cookie"); + } else if (paramEl.isAnnotationPresent(Header.class)) { + parameter.setIn("header"); + } else { + UriMatchVariable pathVariable = pathVariables.get(parameter.getName()); + // check if this parameter is optional path variable + if (pathVariable == null) { + for (UriMatchVariable variable : matchTemplate.getVariables()) { + if (variable.getName().equals(parameter.getName()) && variable.isOptional() && !variable.isQuery() && !variable.isExploded()) { + break; + } + } + } + if (pathVariable != null && !pathVariable.isOptional() && !pathVariable.isQuery() && !pathVariable.isExploded()) { + parameter.setIn("path"); + } + + if (parameter.getIn() == null) { + if (httpMethod == HttpMethod.GET) { + // default to QueryValue - + // https://github.com/micronaut-projects/micronaut-openapi/issues/130 + parameter.setIn("query"); + } + } + + } + } + } + PathItem pathItem = openAPI.getPaths().get(matchTemplate.toPathString()); switch (httpMethod) { case GET: @@ -604,7 +631,10 @@ private void processParameter(VisitorContext context, OpenAPI openAPI, io.swagger.v3.oas.models.Operation swaggerOperation, JavadocDescription javadocDescription, boolean permitsRequestBody, Map pathVariables, List consumesMediaTypes, List swaggerParameters, TypedElement parameter, - List extraBodyParameters) { + List extraBodyParameters, + HttpMethod httpMethod, + List matchTemplates, + List pathItems) { ClassElement parameterType = parameter.getGenericType(); if (ignoreParameter(parameter)) { @@ -617,36 +647,48 @@ private void processParameter(VisitorContext context, OpenAPI openAPI, consumesMediaTypes = CollectionUtils.isNotEmpty(consumesMediaTypes) ? consumesMediaTypes : DEFAULT_MEDIA_TYPES; if (parameter.isAnnotationPresent(Body.class)) { + io.swagger.v3.oas.models.Operation existedOpertion = null; + // check existed operations + for (PathItem pathItem : pathItems) { + existedOpertion = getOperationOnPathItem(pathItem, httpMethod); + if (existedOpertion != null) { + swaggerOperation = existedOpertion; + break; + } + } + processBody(context, openAPI, swaggerOperation, javadocDescription, permitsRequestBody, consumesMediaTypes, parameter, parameterType); RequestBody requestBody = swaggerOperation.getRequestBody(); if (requestBody != null && requestBody.getContent() != null) { - for (Map.Entry entry : requestBody.getContent().entrySet()) { - boolean found = false; - for (MediaType mediaType : consumesMediaTypes) { - if (entry.getKey().equals(mediaType.getName())) { - found = true; - break; + if (existedOpertion != null) { + for (Map.Entry entry : existedOpertion.getRequestBody().getContent().entrySet()) { + boolean found = false; + for (MediaType mediaType : consumesMediaTypes) { + if (entry.getKey().equals(mediaType.getName())) { + found = true; + break; + } + } + if (!found) { + continue; } - } - if (!found) { - continue; - } - io.swagger.v3.oas.models.media.MediaType mediaType = entry.getValue(); + io.swagger.v3.oas.models.media.MediaType mediaType = entry.getValue(); - Schema propertySchema = bindSchemaForElement(context, parameter, parameterType, mediaType.getSchema()); + Schema propertySchema = bindSchemaForElement(context, parameter, parameterType, mediaType.getSchema()); - String bodyAnnValue = parameter.getAnnotation(Body.class).getValue(String.class).orElse(null); - if (StringUtils.isNotEmpty(bodyAnnValue)) { - Schema wrapperSchema = new Schema(); - wrapperSchema.setType(TYPE_OBJECT); - if (isElementNotNullable(parameter, parameterType)) { - wrapperSchema.addRequiredItem(bodyAnnValue); + String bodyAnnValue = parameter.getAnnotation(Body.class).getValue(String.class).orElse(null); + if (StringUtils.isNotEmpty(bodyAnnValue)) { + Schema wrapperSchema = new Schema(); + wrapperSchema.setType(TYPE_OBJECT); + if (isElementNotNullable(parameter, parameterType)) { + wrapperSchema.addRequiredItem(bodyAnnValue); + } + wrapperSchema.addProperty(bodyAnnValue, propertySchema); + mediaType.setSchema(wrapperSchema); } - wrapperSchema.addProperty(bodyAnnValue, propertySchema); - mediaType.setSchema(wrapperSchema); } } } @@ -656,14 +698,18 @@ private void processParameter(VisitorContext context, OpenAPI openAPI, if (parameter.isAnnotationPresent(RequestBean.class)) { processRequestBean(context, openAPI, swaggerOperation, javadocDescription, permitsRequestBody, pathVariables, - consumesMediaTypes, swaggerParameters, parameter, extraBodyParameters); + consumesMediaTypes, swaggerParameters, parameter, extraBodyParameters, httpMethod, matchTemplates, pathItems); return; } - Parameter newParameter = processMethodParameterAnnotation(context, swaggerOperation, permitsRequestBody, pathVariables, parameter, extraBodyParameters); + Parameter newParameter = processMethodParameterAnnotation(context, swaggerOperation, permitsRequestBody, pathVariables, parameter, extraBodyParameters, httpMethod, matchTemplates); if (newParameter == null) { return; } + if (newParameter.get$ref() != null) { + addSwaggerParamater(newParameter, swaggerParameters); + return; + } if (newParameter.getExplode() != null && newParameter.getExplode() && "query".equals(newParameter.getIn()) && !parameterType.isIterable()) { Schema explodedSchema = resolveSchema(openAPI, parameter, parameterType, context, consumesMediaTypes, null, null); @@ -715,6 +761,10 @@ private void processParameter(VisitorContext context, OpenAPI openAPI, } private void addSwaggerParamater(Parameter newParameter, List swaggerParameters) { + if (newParameter.get$ref() != null) { + swaggerParameters.add(newParameter); + return; + } for (Parameter swaggerParameter : swaggerParameters) { if (newParameter.getName().equals(swaggerParameter.getName())) { return; @@ -748,7 +798,9 @@ private void processBodyParameter(VisitorContext context, OpenAPI openAPI, Javad private Parameter processMethodParameterAnnotation(VisitorContext context, io.swagger.v3.oas.models.Operation swaggerOperation, boolean permitsRequestBody, Map pathVariables, TypedElement parameter, - List extraBodyParameters) { + List extraBodyParameters, + HttpMethod httpMethod, + List matchTemplates) { Parameter newParameter = null; String parameterName = parameter.getName(); if (!parameter.hasStereotype(Bindable.class) && pathVariables.containsKey(parameterName)) { @@ -807,10 +859,42 @@ private Parameter processMethodParameterAnnotation(VisitorContext context, io.sw if (permitsRequestBody) { extraBodyParameters.add(parameter); } else { - // default to QueryValue - - // https://github.com/micronaut-projects/micronaut-openapi/issues/130 - newParameter = new QueryParameter(); - newParameter.setName(parameterName); + + UriMatchVariable pathVariable = pathVariables.get(parameterName); + boolean isExploded = false; + // check if this parameter is optional path variable + if (pathVariable == null) { + for (UriMatchTemplate matchTemplate : matchTemplates) { + for (UriMatchVariable variable : matchTemplate.getVariables()) { + if (variable.getName().equals(parameterName)) { + isExploded = variable.isExploded(); + if (variable.isOptional() && !variable.isQuery() && !isExploded) { + return null; + } + break; + } + } + } + } + if (pathVariable != null && !pathVariable.isOptional() && !pathVariable.isQuery()) { + newParameter = new PathParameter(); + newParameter.setName(parameterName); + if (pathVariable.isExploded()) { + newParameter.setExplode(true); + } + } + + if (newParameter == null) { + if (httpMethod == HttpMethod.GET) { + // default to QueryValue - + // https://github.com/micronaut-projects/micronaut-openapi/issues/130 + newParameter = new QueryParameter(); + newParameter.setName(parameterName); + } + } + if (newParameter != null && isExploded) { + newParameter.setExplode(true); + } } } } @@ -861,7 +945,9 @@ private Parameter processMethodParameterAnnotation(VisitorContext context, io.sw Map target = ConvertUtils.getConvertJsonMapper().convertValue(newParameter, MAP_TYPE); for (CharSequence name : paramValues.keySet()) { Object o = paramValues.get(name.toString()); - target.put(name.toString(), o); + if (o != null) { + target.put(name.toString(), o); + } } newParameter = ConvertUtils.getConvertJsonMapper().convertValue(target, Parameter.class); } else { @@ -869,17 +955,24 @@ private Parameter processMethodParameterAnnotation(VisitorContext context, io.sw // ParameterDeserializer breaks updating // existing objects BeanMap beanMap = BeanMap.of(v); - BeanMap target = BeanMap.of(newParameter); - for (CharSequence name : paramValues.keySet()) { + Map target = ConvertUtils.getConvertJsonMapper().convertValue(newParameter, MAP_TYPE); + for (CharSequence name : beanMap.keySet()) { Object o = beanMap.get(name.toString()); - target.put(name.toString(), o); + if (o != null) { + target.put(name.toString(), o); + } } + newParameter = ConvertUtils.getConvertJsonMapper().convertValue(target, Parameter.class); } } catch (IOException e) { context.warn("Error reading Swagger Parameter for element [" + parameter + "]: " + e.getMessage(), parameter); } } + if (newParameter != null && newParameter.get$ref() != null) { + return newParameter; + } + if (newParameter != null) { final Schema parameterSchema = newParameter.getSchema(); if (paramAnn.contains("schema") && parameterSchema != null) { @@ -957,13 +1050,16 @@ private void processRequestBean(VisitorContext context, OpenAPI openAPI, io.swagger.v3.oas.models.Operation swaggerOperation, JavadocDescription javadocDescription, boolean permitsRequestBody, Map pathVariables, List consumesMediaTypes, List swaggerParameters, TypedElement parameter, - List extraBodyParameters) { + List extraBodyParameters, + HttpMethod httpMethod, + List matchTemplates, + List pathItems) { for (FieldElement field : parameter.getType().getFields()) { if (field.isStatic()) { continue; } processParameter(context, openAPI, swaggerOperation, javadocDescription, permitsRequestBody, pathVariables, - consumesMediaTypes, swaggerParameters, field, extraBodyParameters); + consumesMediaTypes, swaggerParameters, field, extraBodyParameters, httpMethod, matchTemplates, pathItems); } } @@ -1067,117 +1163,124 @@ private JavadocDescription getMethodDescription(MethodElement element, return javadocDescription; } - private io.swagger.v3.oas.models.Operation readOperation(MethodElement element, VisitorContext context) { + private Map readOperations(String path, HttpMethod httpMethod, List pathItems, MethodElement element, VisitorContext context) { + Map swaggerOperations = new HashMap<>(pathItems.size()); final Optional> operationAnnotation = element.findAnnotation(Operation.class); + for (PathItem pathItem : pathItems) { + io.swagger.v3.oas.models.Operation swaggerOperation = operationAnnotation + .flatMap(o -> toValue(o.getValues(), context, io.swagger.v3.oas.models.Operation.class)) + .orElse(new io.swagger.v3.oas.models.Operation()); - io.swagger.v3.oas.models.Operation swaggerOperation = operationAnnotation - .flatMap(o -> toValue(o.getValues(), context, io.swagger.v3.oas.models.Operation.class)) - .orElse(new io.swagger.v3.oas.models.Operation()); - - if (CollectionUtils.isNotEmpty(swaggerOperation.getParameters())) { - swaggerOperation.getParameters().removeIf(Objects::isNull); - } + if (CollectionUtils.isNotEmpty(swaggerOperation.getParameters())) { + swaggerOperation.getParameters().removeIf(Objects::isNull); + } - ParameterElement[] methodParams = element.getParameters(); - if (ArrayUtils.isNotEmpty(methodParams) && operationAnnotation.isPresent()) { - List> params = operationAnnotation.get().getAnnotations("parameters", io.swagger.v3.oas.annotations.Parameter.class); - if (CollectionUtils.isNotEmpty(params)) { - for (ParameterElement methodParam : methodParams) { - AnnotationValue paramAnn = null; - for (AnnotationValue param : params) { - String paramName = param.stringValue("name").orElse(null); - if (methodParam.getName().equals(paramName)) { - paramAnn = param; - break; + ParameterElement[] methodParams = element.getParameters(); + if (ArrayUtils.isNotEmpty(methodParams) && operationAnnotation.isPresent()) { + List> params = operationAnnotation.get().getAnnotations("parameters", io.swagger.v3.oas.annotations.Parameter.class); + if (CollectionUtils.isNotEmpty(params)) { + for (ParameterElement methodParam : methodParams) { + AnnotationValue paramAnn = null; + for (AnnotationValue param : params) { + String paramName = param.stringValue("name").orElse(null); + if (methodParam.getName().equals(paramName)) { + paramAnn = param; + break; + } } - } - if (paramAnn != null && !paramAnn.booleanValue("hidden").orElse(false)) { Parameter swaggerParam = null; - String paramName = paramAnn.stringValue("name").orElse(null); - if (paramName != null) { - if (CollectionUtils.isNotEmpty(swaggerOperation.getParameters())) { - for (Parameter createdParameter : swaggerOperation.getParameters()) { - if (createdParameter.getName().equals(paramName)) { - swaggerParam = createdParameter; - break; + if (paramAnn != null && !paramAnn.booleanValue("hidden").orElse(false)) { + String paramName = paramAnn.stringValue("name").orElse(null); + if (paramName != null) { + if (CollectionUtils.isNotEmpty(swaggerOperation.getParameters())) { + for (Parameter createdParameter : swaggerOperation.getParameters()) { + if (createdParameter.getName().equals(paramName)) { + swaggerParam = createdParameter; + break; + } } } } - } - if (swaggerParam == null) { - if (swaggerOperation.getParameters() == null) { - swaggerOperation.setParameters(new ArrayList<>()); + if (swaggerParam == null) { + if (swaggerOperation.getParameters() == null) { + swaggerOperation.setParameters(new ArrayList<>()); + } + swaggerParam = new Parameter(); + swaggerOperation.getParameters().add(swaggerParam); } - swaggerParam = new Parameter(); - swaggerOperation.getParameters().add(swaggerParam); - } - if (paramName != null) { - swaggerParam.setName(paramName); - } - paramAnn.stringValue("description").ifPresent(swaggerParam::setDescription); - Optional required = paramAnn.booleanValue("required"); - if (required.isPresent()) { - swaggerParam.setRequired(required.get() ? true : null); - } - Optional deprecated = paramAnn.booleanValue("deprecated"); - if (deprecated.isPresent()) { - swaggerParam.setDeprecated(deprecated.get() ? true : null); - } - Optional allowEmptyValue = paramAnn.booleanValue("allowEmptyValue"); - if (allowEmptyValue.isPresent()) { - swaggerParam.setAllowEmptyValue(allowEmptyValue.get() ? true : null); - } - Optional allowReserved = paramAnn.booleanValue("allowReserved"); - if (allowReserved.isPresent()) { - swaggerParam.setAllowReserved(allowReserved.get() ? true : null); - } - paramAnn.stringValue("example").ifPresent(swaggerParam::setExample); - Optional style = paramAnn.get("style", ParameterStyle.class); - if (style.isPresent()) { - swaggerParam.setStyle(paramStyle(style.get())); - } - paramAnn.stringValue("ref").ifPresent(swaggerParam::set$ref); - Optional in = paramAnn.get("in", ParameterIn.class); - if (in.isPresent()) { - if (in.get() == ParameterIn.DEFAULT) { - swaggerParam.setIn(calcIn(methodParam)); - } else { - swaggerParam.setIn(in.get().toString()); + if (paramName != null) { + swaggerParam.setName(paramName); + } + paramAnn.stringValue("description").ifPresent(swaggerParam::setDescription); + Optional required = paramAnn.booleanValue("required"); + if (required.isPresent()) { + swaggerParam.setRequired(required.get() ? true : null); + } + Optional deprecated = paramAnn.booleanValue("deprecated"); + if (deprecated.isPresent()) { + swaggerParam.setDeprecated(deprecated.get() ? true : null); + } + Optional allowEmptyValue = paramAnn.booleanValue("allowEmptyValue"); + if (allowEmptyValue.isPresent()) { + swaggerParam.setAllowEmptyValue(allowEmptyValue.get() ? true : null); + } + Optional allowReserved = paramAnn.booleanValue("allowReserved"); + if (allowReserved.isPresent()) { + swaggerParam.setAllowReserved(allowReserved.get() ? true : null); + } + paramAnn.stringValue("example").ifPresent(swaggerParam::setExample); + Optional style = paramAnn.get("style", ParameterStyle.class); + if (style.isPresent()) { + swaggerParam.setStyle(paramStyle(style.get())); } + paramAnn.stringValue("ref").ifPresent(swaggerParam::set$ref); + Optional in = paramAnn.get("in", ParameterIn.class); + if (in.isPresent()) { + if (in.get() == ParameterIn.DEFAULT) { + swaggerParam.setIn(calcIn(path, httpMethod, methodParam)); + } else { + swaggerParam.setIn(in.get().toString()); + } + } + } + if (swaggerParam != null && StringUtils.isEmpty(swaggerParam.getIn())) { + swaggerParam.setIn(calcIn(path, httpMethod, methodParam)); } } } } - } - String prefix; - String suffix; - boolean addAlways; - AnnotationValue apiDecorator = element.getDeclaredAnnotation(OpenAPIDecorator.class); - if (apiDecorator != null) { - prefix = apiDecorator.stringValue().orElse(""); - suffix = apiDecorator.stringValue("opIdSuffix").orElse(""); - addAlways = apiDecorator.booleanValue("addAlways").orElse(true); - } else { - prefix = context.get(CONTEXT_CHILD_OP_ID_PREFIX, String.class).orElse(""); - suffix = context.get(CONTEXT_CHILD_OP_ID_SUFFIX, String.class).orElse(""); - addAlways = context.get(CONTEXT_CHILD_OP_ID_SUFFIX_ADD_ALWAYS, Boolean.class).orElse(true); - } + String prefix; + String suffix; + boolean addAlways; + AnnotationValue apiDecorator = element.getDeclaredAnnotation(OpenAPIDecorator.class); + if (apiDecorator != null) { + prefix = apiDecorator.stringValue().orElse(""); + suffix = apiDecorator.stringValue("opIdSuffix").orElse(""); + addAlways = apiDecorator.booleanValue("addAlways").orElse(true); + } else { + prefix = context.get(CONTEXT_CHILD_OP_ID_PREFIX, String.class).orElse(""); + suffix = context.get(CONTEXT_CHILD_OP_ID_SUFFIX, String.class).orElse(""); + addAlways = context.get(CONTEXT_CHILD_OP_ID_SUFFIX_ADD_ALWAYS, Boolean.class).orElse(true); + } - if (StringUtils.isEmpty(swaggerOperation.getOperationId())) { - swaggerOperation.setOperationId(prefix + element.getName() + suffix); - } else if (addAlways) { - swaggerOperation.setOperationId(prefix + swaggerOperation.getOperationId() + suffix); - } + if (StringUtils.isEmpty(swaggerOperation.getOperationId())) { + swaggerOperation.setOperationId(prefix + element.getName() + suffix); + } else if (addAlways) { + swaggerOperation.setOperationId(prefix + swaggerOperation.getOperationId() + suffix); + } - if (swaggerOperation.getDescription() != null && swaggerOperation.getDescription().isEmpty()) { - swaggerOperation.setDescription(null); + if (swaggerOperation.getDescription() != null && swaggerOperation.getDescription().isEmpty()) { + swaggerOperation.setDescription(null); + } + swaggerOperations.put(pathItem, swaggerOperation); } - return swaggerOperation; + return swaggerOperations; } - private String calcIn(ParameterElement methodParam) { + private String calcIn(String path, HttpMethod httpMethod, ParameterElement methodParam) { + String paramName = methodParam.getName(); Set paramAnnNames = methodParam.getAnnotationNames(); if (CollectionUtils.isNotEmpty(paramAnnNames)) { if (paramAnnNames.contains(QueryValue.class.getName())) { @@ -1190,6 +1293,18 @@ private String calcIn(ParameterElement methodParam) { return ParameterIn.COOKIE.toString(); } } + if (httpMethod == HttpMethod.GET) { + if (path.contains("{" + paramName + "}")) { + return ParameterIn.PATH.toString(); + } else { + return ParameterIn.QUERY.toString(); + } + } else { + if (path.contains("{" + paramName + "}")) { + return ParameterIn.PATH.toString(); + } + } + return null; } @@ -1252,7 +1367,7 @@ private void processMicronautSecurityConfig(MethodElement element, String path, return; } - OpenAPI openAPI = Utils.resolveOpenAPI(context); + OpenAPI openAPI = Utils.resolveOpenApi(context); Components components = openAPI.getComponents(); String securitySchemeName; @@ -1393,13 +1508,13 @@ private boolean ignoreParameter(TypedElement parameter) { AnnotationValue schemaAnn = parameter.getAnnotation(io.swagger.v3.oas.annotations.media.Schema.class); boolean isHidden = schemaAnn != null && schemaAnn.booleanValue("hidden").orElse(false); - return isHidden || - parameter.isAnnotationPresent(Hidden.class) || - parameter.isAnnotationPresent(JsonIgnore.class) || - parameter.booleanValue(io.swagger.v3.oas.annotations.Parameter.class, "hidden") - .orElse(false) || - isAnnotationPresent(parameter, "io.micronaut.session.annotation.SessionValue") || - isIgnoredParameterType(parameter.getType()); + return isHidden + || parameter.isAnnotationPresent(Hidden.class) + || parameter.isAnnotationPresent(JsonIgnore.class) + || parameter.booleanValue(io.swagger.v3.oas.annotations.Parameter.class, "hidden") + .orElse(false) + || isAnnotationPresent(parameter, "io.micronaut.session.annotation.SessionValue") + || isIgnoredParameterType(parameter.getType()); } private boolean isIgnoredParameterType(ClassElement parameterType) { @@ -1435,6 +1550,29 @@ private boolean isReactiveAndVoid(ClassElement returnType) { .anyMatch(t -> returnType.isAssignable(t) && returnType.getFirstTypeArgument().isPresent() && isVoid(returnType.getFirstTypeArgument().get())); } + private io.swagger.v3.oas.models.Operation getOperationOnPathItem(PathItem pathItem, HttpMethod httpMethod) { + switch (httpMethod) { + case GET: + return pathItem.getGet(); + case POST: + return pathItem.getPost(); + case PUT: + return pathItem.getPut(); + case PATCH: + return pathItem.getPatch(); + case DELETE: + return pathItem.getDelete(); + case HEAD: + return pathItem.getHead(); + case OPTIONS: + return pathItem.getOptions(); + case TRACE: + return pathItem.getTrace(); + default: + return null; + } + } + private io.swagger.v3.oas.models.Operation setOperationOnPathItem(PathItem pathItem, io.swagger.v3.oas.models.Operation swaggerOperation, HttpMethod httpMethod) { io.swagger.v3.oas.models.Operation operation = swaggerOperation; switch (httpMethod) { @@ -1520,7 +1658,7 @@ private void processResponses(io.swagger.v3.oas.models.Operation operation, List if (newResponse.isPresent()) { ApiResponse newApiResponse = newResponse.get(); if (response.booleanValue("useReturnTypeSchema").orElse(false) && element != null) { - addResponseContent(element, context, Utils.resolveOpenAPI(context), newApiResponse); + addResponseContent(element, context, Utils.resolveOpenApi(context), newApiResponse); } else { List producesMediaTypes = producesMediaTypes(element); @@ -1605,7 +1743,7 @@ private void readCallbacks(MethodElement element, VisitorContext context, private void processCallbackReference(VisitorContext context, io.swagger.v3.oas.models.Operation swaggerOperation, String callbackName, String refCallback) { - final Components components = Utils.resolveComponents(Utils.resolveOpenAPI(context)); + final Components components = Utils.resolveComponents(Utils.resolveOpenApi(context)); Map callbacks = initCallbacks(swaggerOperation); final io.swagger.v3.oas.models.callbacks.Callback callbackRef = new io.swagger.v3.oas.models.callbacks.Callback(); if (refCallback != null) { diff --git a/openapi/src/main/java/io/micronaut/openapi/visitor/AbstractOpenApiVisitor.java b/openapi/src/main/java/io/micronaut/openapi/visitor/AbstractOpenApiVisitor.java index 0c1174e1c2..33c6b251ff 100644 --- a/openapi/src/main/java/io/micronaut/openapi/visitor/AbstractOpenApiVisitor.java +++ b/openapi/src/main/java/io/micronaut/openapi/visitor/AbstractOpenApiVisitor.java @@ -67,6 +67,7 @@ import io.micronaut.core.util.StringUtils; import io.micronaut.http.MediaType; import io.micronaut.http.uri.UriMatchTemplate; +import io.micronaut.http.uri.UriMatchVariable; import io.micronaut.inject.ast.ClassElement; import io.micronaut.inject.ast.Element; import io.micronaut.inject.ast.EnumConstantElement; @@ -128,6 +129,7 @@ import static io.micronaut.openapi.visitor.OpenApiApplicationVisitor.getConfigurationProperty; import static io.micronaut.openapi.visitor.OpenApiApplicationVisitor.getExpandableProperties; import static io.micronaut.openapi.visitor.OpenApiApplicationVisitor.resolvePlaceholders; +import static io.micronaut.openapi.visitor.SchemaUtils.EMPTY_SCHEMA; import static io.micronaut.openapi.visitor.SchemaUtils.TYPE_OBJECT; import static io.micronaut.openapi.visitor.Utils.resolveComponents; import static java.util.stream.Collectors.toMap; @@ -142,7 +144,6 @@ abstract class AbstractOpenApiVisitor { private static final Lock VISITED_ELEMENTS_LOCK = new ReentrantLock(); - private static final Schema EMPTY_SCHEMA = new Schema<>(); private static final ComposedSchema EMPTY_COMPOSED_SCHEMA = new ComposedSchema(); /** @@ -253,7 +254,7 @@ List readSecurityRequirements(List> resolvePathItems(VisitorContext context, List matchTemplates) { - OpenAPI openAPI = Utils.resolveOpenAPI(context); + OpenAPI openAPI = Utils.resolveOpenApi(context); Paths paths = openAPI.getPaths(); if (paths == null) { paths = new Paths(); @@ -266,6 +267,7 @@ Map> resolvePathItems(VisitorContext context, List> resolvePathItems(VisitorContext context, List> resolvePathItems(VisitorContext context, List resultPathItems = resultPathItemsMap.get(resultPath); - if (resultPathItems == null) { - resultPathItems = new ArrayList<>(); - resultPathItemsMap.put(resultPath, resultPathItems); + Map finalPaths = new HashMap<>(); + finalPaths.put(-1, resultPath); + if (CollectionUtils.isNotEmpty(matchTemplate.getVariables())) { + List optionalVars = new ArrayList<>(); + // need check not required path varibales + for (UriMatchVariable var : matchTemplate.getVariables()) { + if (var.isQuery() || !var.isOptional() || var.isExploded()) { + continue; + } + optionalVars.add(var.getName()); + } + if (CollectionUtils.isNotEmpty(optionalVars)) { + + int i = 0; + for (String var : optionalVars) { + if (finalPaths.isEmpty()) { + finalPaths.put(i, resultPath + "/{" + var + '}'); + i++; + continue; + } + for (Map.Entry entry : finalPaths.entrySet()) { + if (entry.getKey() + 1 < i) { + continue; + } + finalPaths.put(i, entry.getValue() + "/{" + var + '}'); + } + i++; + } + } + } + + for (String finalPath : finalPaths.values()) { + List resultPathItems = resultPathItemsMap.get(finalPath); + if (resultPathItems == null) { + resultPathItems = new ArrayList<>(); + resultPathItemsMap.put(finalPath, resultPathItems); + } + resultPathItems.add(paths.computeIfAbsent(finalPath, key -> new PathItem())); } - resultPathItems.add(paths.computeIfAbsent(resultPath, key -> new PathItem())); } return resultPathItemsMap; } + private List addOptionalVars(List paths, String var, int level) { + List additionalPaths = new ArrayList<>(paths); + if (paths.isEmpty()) { + additionalPaths.add("/{" + var + '}'); + } else { + for (String path : paths) { + additionalPaths.add(path + "/{" + var + '}'); + } + } + return additionalPaths; + } + /** * Convert the values to a map. * @@ -737,7 +785,7 @@ private boolean isTypeNullable(ClassElement type) { * @return The schema or null if it cannot be resolved */ protected @Nullable Schema resolveSchema(@Nullable Element definingElement, ClassElement type, VisitorContext context, List mediaTypes) { - return resolveSchema(Utils.resolveOpenAPI(context), definingElement, type, context, mediaTypes, null, null); + return resolveSchema(Utils.resolveOpenApi(context), definingElement, type, context, mediaTypes, null, null); } /** @@ -960,7 +1008,7 @@ protected Schema resolveSchema(OpenAPI openAPI, @Nullable Element definingElemen } private void handleUnwrapped(VisitorContext context, Element element, ClassElement elementType, Schema parentSchema, AnnotationValue uw) { - Map schemas = SchemaUtils.resolveSchemas(Utils.resolveOpenAPI(context)); + Map schemas = SchemaUtils.resolveSchemas(Utils.resolveOpenApi(context)); ClassElement customElementType = OpenApiApplicationVisitor.getCustomSchema(elementType.getName(), elementType.getTypeArguments(), context); String schemaName = element.stringValue(io.swagger.v3.oas.annotations.media.Schema.class, "name") .orElse(computeDefaultSchemaName(null, customElementType != null ? customElementType : elementType, elementType.getTypeArguments(), context)); @@ -1147,7 +1195,7 @@ protected Schema bindSchemaForElement(VisitorContext context, TypedElement eleme Schema originalSchema = schemaToBind; if (originalSchema.get$ref() != null) { - Schema schemaFromAnn = shemaFromAnnotation(context, element, schemaAnn); + Schema schemaFromAnn = schemaFromAnnotation(context, element, schemaAnn); if (schemaFromAnn != null) { schemaToBind = schemaFromAnn; } @@ -1174,8 +1222,8 @@ protected Schema bindSchemaForElement(VisitorContext context, TypedElement eleme } } - Schema finalSchemaToBind = schemaToBind; - processJavaxValidationAnnotations(element, elementType, finalSchemaToBind); +// Schema finalSchemaToBind = schemaToBind; + processJavaxValidationAnnotations(element, elementType, schemaToBind); final ComposedSchema composedSchema; final Schema topLevelSchema; @@ -1183,7 +1231,7 @@ protected Schema bindSchemaForElement(VisitorContext context, TypedElement eleme composedSchema = new ComposedSchema(); topLevelSchema = composedSchema; } else { - composedSchema = null; + composedSchema = new ComposedSchema(); topLevelSchema = schemaToBind; } @@ -1224,36 +1272,40 @@ protected Schema bindSchemaForElement(VisitorContext context, TypedElement eleme notOnlyRef = true; } - if (composedSchema != null) { - boolean addSchemaToBind = !schemaToBind.equals(EMPTY_SCHEMA); + boolean addSchemaToBind = !schemaToBind.equals(EMPTY_SCHEMA); - if (addSchemaToBind) { - if (TYPE_OBJECT.equals(originalSchema.getType())) { - if (composedSchema.getType() == null) { - composedSchema.setType(TYPE_OBJECT); - } - originalSchema.setType(null); + if (addSchemaToBind) { + if (TYPE_OBJECT.equals(originalSchema.getType())) { + if (composedSchema.getType() == null) { + composedSchema.setType(TYPE_OBJECT); } + originalSchema.setType(null); + } + if (!originalSchema.equals(EMPTY_SCHEMA)) { composedSchema.addAllOfItem(originalSchema); - } else if (isNullable && CollectionUtils.isEmpty(composedSchema.getAllOf())) { - composedSchema.addOneOfItem(originalSchema); } - if (addSchemaToBind && !schemaToBind.equals(originalSchema)) { - if (TYPE_OBJECT.equals(schemaToBind.getType())) { - if (composedSchema.getType() == null) { - composedSchema.setType(TYPE_OBJECT); - } - originalSchema.setType(null); + } else if (isNullable && CollectionUtils.isEmpty(composedSchema.getAllOf())) { + composedSchema.addAllOfItem(originalSchema); + } + if (addSchemaToBind && !schemaToBind.equals(originalSchema)) { + if (TYPE_OBJECT.equals(schemaToBind.getType())) { + if (composedSchema.getType() == null) { + composedSchema.setType(TYPE_OBJECT); } - composedSchema.addAllOfItem(schemaToBind); + originalSchema.setType(null); } + composedSchema.addAllOfItem(schemaToBind); + } - if (!composedSchema.equals(EMPTY_COMPOSED_SCHEMA) - && ((CollectionUtils.isNotEmpty(composedSchema.getAllOf()) && composedSchema.getAllOf().size() > 1) - || CollectionUtils.isNotEmpty(composedSchema.getOneOf()) - || notOnlyRef)) { - return composedSchema; - } + if (!composedSchema.equals(EMPTY_COMPOSED_SCHEMA) + && ((CollectionUtils.isNotEmpty(composedSchema.getAllOf()) && composedSchema.getAllOf().size() > 1) + || CollectionUtils.isNotEmpty(composedSchema.getOneOf()) + || CollectionUtils.isNotEmpty(composedSchema.getAnyOf()) + || notOnlyRef)) { + return composedSchema; + } + if (CollectionUtils.isNotEmpty(composedSchema.getAllOf()) && composedSchema.getAllOf().size() == 1) { + return composedSchema.getAllOf().get(0); } return originalSchema; @@ -1429,7 +1481,7 @@ protected void processJavaxValidationAnnotations(Element element, ClassElement e } } - Schema shemaFromAnnotation(VisitorContext context, Element element, AnnotationValue schemaAnn) { + Schema schemaFromAnnotation(VisitorContext context, Element element, AnnotationValue schemaAnn) { if (schemaAnn == null) { return null; } @@ -1539,47 +1591,10 @@ void processShemaAnn(Schema schemaToBind, VisitorContext context, Element elemen } } - OpenAPI openAPI = Utils.resolveOpenAPI(context); + OpenAPI openAPI = Utils.resolveOpenApi(context); Components components = resolveComponents(openAPI); - final AnnotationClassValue not = (AnnotationClassValue) annValues.get("not"); - if (not != null) { - final Schema schemaNot = resolveSchema(null, context.getClassElement(not.getName()).get(), context, Collections.emptyList()); - schemaToBind.setNot(schemaNot); - } - final AnnotationClassValue[] allOf = (AnnotationClassValue[]) annValues.get("allOf"); - if (ArrayUtils.isNotEmpty(allOf)) { - List> schemaList = namesToSchemas(openAPI, context, allOf, Collections.emptyList()); - for (Schema s : schemaList) { - if (TYPE_OBJECT.equals(s.getType())) { - if (schemaToBind.getType() == null) { - schemaToBind.setType(TYPE_OBJECT); - } - s.setType(null); - } - if (schemaToBind.getAllOf() == null || !schemaToBind.getAllOf().contains(s)) { - schemaToBind.addAllOfItem(s); - } - } - } - final AnnotationClassValue[] anyOf = (AnnotationClassValue[]) annValues.get("anyOf"); - if (ArrayUtils.isNotEmpty(anyOf)) { - List> schemaList = namesToSchemas(openAPI, context, anyOf, Collections.emptyList()); - for (Schema s : schemaList) { - if (schemaToBind.getAnyOf() == null || !schemaToBind.getAnyOf().contains(s)) { - schemaToBind.addAnyOfItem(s); - } - } - } - final AnnotationClassValue[] oneOf = (AnnotationClassValue[]) annValues.get("oneOf"); - if (ArrayUtils.isNotEmpty(oneOf)) { - List> schemaList = namesToSchemas(openAPI, context, oneOf, Collections.emptyList()); - for (Schema s : schemaList) { - if (schemaToBind.getOneOf() == null || !schemaToBind.getOneOf().contains(s)) { - schemaToBind.addOneOfItem(s); - } - } - } + processClassValues(schemaToBind, annValues, Collections.emptyList(), context); String addProps = (String) annValues.get("additionalProperties"); if (StringUtils.isNotEmpty(addProps)) { @@ -1646,12 +1661,12 @@ protected Schema bindSchemaAnnotationValue(VisitorContext context, Element eleme return doBindSchemaAnnotationValue(context, element, schemaToBind, schemaJson, schemaAnn.stringValue("type").orElse(typeAndFormat.getFirst()), schemaAnn.stringValue("format").orElse(typeAndFormat.getSecond()), - schemaAnn.stringValue("defaultValue").orElse(null), - schemaAnn.get("allowableValues", String[].class).orElse(null)); + schemaAnn); } private Schema doBindSchemaAnnotationValue(VisitorContext context, Element element, Schema schemaToBind, - JsonNode schemaJson, String elType, String elFormat, String defaultValue, String... allowableValues) { + JsonNode schemaJson, String elType, String elFormat, AnnotationValue schemaAnn) { + // need to set placeholders to set correct values to example field schemaJson = resolvePlaceholders(schemaJson, s -> expandProperties(s, getExpandableProperties(context), context)); try { @@ -1660,6 +1675,17 @@ private Schema doBindSchemaAnnotationValue(VisitorContext context, Element eleme context.warn("Error reading Swagger Schema for element [" + element + "]: " + e.getMessage(), element); } + String defaultValue = null; + String[] allowableValues = null; + if (schemaAnn != null) { + defaultValue = schemaAnn.stringValue("defaultValue").orElse(null); + allowableValues = schemaAnn.get("allowableValues", String[].class).orElse(null); + Map annValues = schemaAnn.getValues(); + Map valueMap = toValueMap(annValues, context); + bindSchemaIfNeccessary(context, schemaAnn, valueMap); + processClassValues(schemaToBind, annValues, Collections.emptyList(), context); + } + if (elType == null && element != null) { ClassElement typeEl = ((TypedElement) element).getType(); Pair typeAndFormat; @@ -1730,8 +1756,10 @@ protected Schema bindArraySchemaAnnotationValue(VisitorContext context, Element } } } + String elType = schemaJson.has("type") ? schemaJson.get("type").textValue() : null; String elFormat = schemaJson.has("format") ? schemaJson.get("format").textValue() : null; + // TODO !!!! return doBindSchemaAnnotationValue(context, element, schemaToBind, schemaJson, elType, elFormat, null); } @@ -2057,6 +2085,61 @@ private void readAllInterfaces(OpenAPI openAPI, VisitorContext context, @Nullabl } } + private void processClassValues(Schema schemaToBind, Map annValues, List mediaTypes, VisitorContext context) { + OpenAPI openAPI = Utils.resolveOpenApi(context); + final AnnotationClassValue not = (AnnotationClassValue) annValues.get("not"); + if (not != null) { + final Schema schemaNot = resolveSchema(null, context.getClassElement(not.getName()).get(), context, Collections.emptyList()); + schemaToBind.setNot(schemaNot); + } + final AnnotationClassValue[] allOf = (AnnotationClassValue[]) annValues.get("allOf"); + if (ArrayUtils.isNotEmpty(allOf)) { + List> schemaList = namesToSchemas(openAPI, context, allOf, mediaTypes); + for (Schema s : schemaList) { + if (TYPE_OBJECT.equals(s.getType())) { + if (schemaToBind.getType() == null) { + schemaToBind.setType(TYPE_OBJECT); + } + s.setType(null); + } + if (schemaToBind.getAllOf() == null || !schemaToBind.getAllOf().contains(s)) { + schemaToBind.addAllOfItem(s); + } + } + } + final AnnotationClassValue[] anyOf = (AnnotationClassValue[]) annValues.get("anyOf"); + if (ArrayUtils.isNotEmpty(anyOf)) { + List> schemaList = namesToSchemas(openAPI, context, anyOf, mediaTypes); + for (Schema s : schemaList) { + if (TYPE_OBJECT.equals(s.getType())) { + if (schemaToBind.getType() == null) { + schemaToBind.setType(TYPE_OBJECT); + } + s.setType(null); + } + if (schemaToBind.getAnyOf() == null || !schemaToBind.getAnyOf().contains(s)) { + schemaToBind.addAnyOfItem(s); + } + } + } + final AnnotationClassValue[] oneOf = (AnnotationClassValue[]) annValues.get("oneOf"); + if (ArrayUtils.isNotEmpty(oneOf)) { + List> schemaList = namesToSchemas(openAPI, context, oneOf, mediaTypes); + for (Schema s : schemaList) { + if (TYPE_OBJECT.equals(s.getType())) { + if (schemaToBind.getType() == null) { + schemaToBind.setType(TYPE_OBJECT); + } + s.setType(null); + } + if (schemaToBind.getOneOf() == null || !schemaToBind.getOneOf().contains(s)) { + schemaToBind.addOneOfItem(s); + } + } + } + + } + /** * Reads schema. * @@ -2122,45 +2205,7 @@ protected Schema readSchema(AnnotationValue not = (AnnotationClassValue) values.get("not"); - if (not != null) { - final Schema schemaNot = resolveSchema(null, context.getClassElement(not.getName()).get(), context, Collections.emptyList()); - composedSchema.setNot(schemaNot); - } - final AnnotationClassValue[] allOf = (AnnotationClassValue[]) values.get("allOf"); - if (ArrayUtils.isNotEmpty(allOf)) { - List> schemaList = namesToSchemas(openAPI, context, allOf, mediaTypes); - for (Schema s : schemaList) { - if (TYPE_OBJECT.equals(s.getType())) { - if (composedSchema.getType() == null) { - composedSchema.setType(TYPE_OBJECT); - } - s.setType(null); - } - if (composedSchema.getAllOf() == null || !composedSchema.getAllOf().contains(s)) { - composedSchema.addAllOfItem(s); - } - } - } - final AnnotationClassValue[] anyOf = (AnnotationClassValue[]) values.get("anyOf"); - if (ArrayUtils.isNotEmpty(anyOf)) { - List> schemaList = namesToSchemas(openAPI, context, anyOf, mediaTypes); - for (Schema s : schemaList) { - if (composedSchema.getAnyOf() == null || !composedSchema.getAnyOf().contains(s)) { - composedSchema.addAnyOfItem(s); - } - } - } - final AnnotationClassValue[] oneOf = (AnnotationClassValue[]) values.get("oneOf"); - if (ArrayUtils.isNotEmpty(oneOf)) { - List> schemaList = namesToSchemas(openAPI, context, oneOf, mediaTypes); - for (Schema s : schemaList) { - if (composedSchema.getOneOf() == null || !composedSchema.getOneOf().contains(s)) { - composedSchema.addOneOfItem(s); - } - } - } + processClassValues(schema, values, mediaTypes, context); if (schema.getType() == null) { schema.setType(elType); @@ -2175,7 +2220,7 @@ protected Schema readSchema(AnnotationValue> values = element .getAnnotationValuesByType(io.swagger.v3.oas.annotations.security.SecurityScheme.class); - final OpenAPI openAPI = Utils.resolveOpenAPI(context); + final OpenAPI openAPI = Utils.resolveOpenApi(context); for (AnnotationValue securityRequirementAnnotationValue : values) { final Map map = toValueMap(securityRequirementAnnotationValue.getValues(), context); diff --git a/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiApplicationVisitor.java b/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiApplicationVisitor.java index 396d08e175..63591c1463 100644 --- a/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiApplicationVisitor.java +++ b/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiApplicationVisitor.java @@ -79,9 +79,14 @@ import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.Operation; import io.swagger.v3.oas.models.PathItem; import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.media.Content; +import io.swagger.v3.oas.models.media.MediaType; import io.swagger.v3.oas.models.media.Schema; +import io.swagger.v3.oas.models.parameters.Parameter; +import io.swagger.v3.oas.models.responses.ApiResponse; import io.swagger.v3.oas.models.security.SecurityScheme; import com.fasterxml.jackson.core.JsonProcessingException; @@ -91,6 +96,10 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.TextNode; +import static io.micronaut.openapi.visitor.SchemaUtils.EMPTY_COMPOSED_SCHEMA; +import static io.micronaut.openapi.visitor.SchemaUtils.EMPTY_SCHEMA; +import static io.micronaut.openapi.visitor.SchemaUtils.EMPTY_SIMPLE_SCHEMA; +import static io.micronaut.openapi.visitor.SchemaUtils.TYPE_OBJECT; import static io.swagger.v3.oas.models.Components.COMPONENTS_SCHEMAS_REF; /** @@ -319,29 +328,29 @@ public void visitClass(ClassElement element, VisitorContext context) { return; } context.info("Generating OpenAPI Documentation"); - OpenAPI openAPI = readOpenAPI(element, context); + OpenAPI openApi = readOpenApi(element, context); // Handle Application securityRequirements schemes processSecuritySchemes(element, context); - mergeAdditionalSwaggerFiles(element, context, openAPI); + mergeAdditionalSwaggerFiles(element, context, openApi); // handle type level tags List tagList = processOpenApiAnnotation( element, context, Tag.class, io.swagger.v3.oas.models.tags.Tag.class, - openAPI.getTags() + openApi.getTags() ); - openAPI.setTags(tagList); + openApi.setTags(tagList); // handle type level security requirements List securityRequirements = readSecurityRequirements(element); - if (openAPI.getSecurity() != null) { - securityRequirements.addAll(openAPI.getSecurity()); + if (openApi.getSecurity() != null) { + securityRequirements.addAll(openApi.getSecurity()); } - openAPI.setSecurity(securityRequirements); + openApi.setSecurity(securityRequirements); // handle type level servers List servers = processOpenApiAnnotation( @@ -349,22 +358,22 @@ public void visitClass(ClassElement element, VisitorContext context) { context, Server.class, io.swagger.v3.oas.models.servers.Server.class, - openAPI.getServers() + openApi.getServers() ); - openAPI.setServers(servers); + openApi.setServers(servers); Optional attr = context.get(Utils.ATTR_OPENAPI, OpenAPI.class); if (attr.isPresent()) { OpenAPI existing = attr.get(); - Optional.ofNullable(openAPI.getInfo()) + Optional.ofNullable(openApi.getInfo()) .ifPresent(existing::setInfo); - copyOpenAPI(existing, openAPI); + copyOpenApi(existing, openApi); } else { - context.put(Utils.ATTR_OPENAPI, openAPI); + context.put(Utils.ATTR_OPENAPI, openApi); } if (Utils.isTestMode()) { - Utils.resolveOpenAPI(context); + Utils.resolveOpenApi(context); } classElement = element; @@ -722,7 +731,7 @@ private void mergeAdditionalSwaggerFiles(ClassElement element, VisitorContext co } catch (IOException e) { context.warn("Unable to read file " + path.getFileName() + ": " + e.getMessage(), element); } - copyOpenAPI(openAPI, parsedOpenApi); + copyOpenApi(openAPI, parsedOpenApi); }); } catch (IOException e) { context.warn("Unable to read file from " + directory + ": " + e.getMessage(), element); @@ -799,7 +808,7 @@ public static Properties readOpenApiConfigFile(VisitorContext context) { * @param to The {@link OpenAPI} object to copy to * @param from The {@link OpenAPI} object to copy from */ - private void copyOpenAPI(OpenAPI to, OpenAPI from) { + private void copyOpenApi(OpenAPI to, OpenAPI from) { if (to != null && from != null) { Optional.ofNullable(from.getTags()).ifPresent(tags -> tags.forEach(to::addTagsItem)); Optional.ofNullable(from.getServers()).ifPresent(servers -> servers.forEach(to::addServersItem)); @@ -826,7 +835,7 @@ private void copyOpenAPI(OpenAPI to, OpenAPI from) { } } - private OpenAPI readOpenAPI(ClassElement element, VisitorContext context) { + private OpenAPI readOpenApi(ClassElement element, VisitorContext context) { return element.findAnnotation(OpenAPIDefinition.class).flatMap(o -> { Optional result = toValue(o.getValues(), context, OpenAPI.class); result.ifPresent(openAPI -> { @@ -1131,41 +1140,14 @@ public void finish(VisitorContext context) { if (!attr.isPresent()) { return; } - OpenAPI openAPI = attr.get(); + OpenAPI openApi = attr.get(); processEndpoints(context); - applyPropertyNamingStrategy(openAPI, context); - applyPropertyServerContextPath(openAPI, context); - openAPI = resolvePropertyPlaceHolders(openAPI, context); - sortOpenAPI(openAPI); - // Process after sorting so order is stable - new JacksonDiscriminatorPostProcessor().addMissingDiscriminatorType(openAPI); - new OpenApiOperationsPostProcessor().processOperations(openAPI); - // need to replace openAPI after property placeholders resolved + openApi = postProcessOpenApi(openApi, context); + // need to set test reference to openApi after post-processing if (Utils.isTestMode()) { - Utils.setTestReferenceAfterPlaceholders(openAPI); + Utils.setTestReference(openApi); } - // remove unused schemas - try { - if (openAPI.getComponents() != null) { - Map schemas = openAPI.getComponents().getSchemas(); - if (CollectionUtils.isNotEmpty(schemas)) { - String openApiJson = ConvertUtils.getJsonMapper().writeValueAsString(openAPI); - // Create a copy of the keySet so that we can modify the map while in a foreach - Set keySet = new HashSet<>(schemas.keySet()); - for (String schemaName : keySet) { - if (!openApiJson.contains(COMPONENTS_SCHEMAS_REF + schemaName)) { - schemas.remove(schemaName); - } - } - } - } - } catch (JsonProcessingException e) { - // do nothing - } - - removeEmtpyComponents(openAPI); - String isJson = getConfigurationProperty(MICRONAUT_OPENAPI_JSON_FORMAT, context); boolean isYaml = !(StringUtils.isNotEmpty(isJson) && isJson.equalsIgnoreCase(StringUtils.TRUE)); @@ -1173,7 +1155,7 @@ public void finish(VisitorContext context) { String fileName = "swagger" + ext; String documentTitle = "OpenAPI"; - Info info = openAPI.getInfo(); + Info info = openApi.getInfo(); if (info != null) { documentTitle = Optional.ofNullable(info.getTitle()).orElse(Environment.DEFAULT_NAME); documentTitle = documentTitle.toLowerCase(Locale.US).replace(' ', '-'); @@ -1194,7 +1176,7 @@ public void finish(VisitorContext context) { } } - writeYamlToFile(openAPI, fileName, documentTitle, context, isYaml); + writeYamlToFile(openApi, fileName, documentTitle, context, isYaml); visitedElements = visitedElements(context); } catch (Throwable t) { context.warn("Error:\n" + Utils.printStackTrace(t), null); @@ -1207,6 +1189,42 @@ public int getOrder() { return 100; } + private OpenAPI postProcessOpenApi(OpenAPI openApi, VisitorContext context) { + + applyPropertyNamingStrategy(openApi, context); + applyPropertyServerContextPath(openApi, context); + + normalizeOpenApi(openApi); + // Process after sorting so order is stable + new JacksonDiscriminatorPostProcessor().addMissingDiscriminatorType(openApi); + new OpenApiOperationsPostProcessor().processOperations(openApi); + + // remove unused schemas + try { + if (openApi.getComponents() != null) { + Map schemas = openApi.getComponents().getSchemas(); + if (CollectionUtils.isNotEmpty(schemas)) { + String openApiJson = ConvertUtils.getJsonMapper().writeValueAsString(openApi); + // Create a copy of the keySet so that we can modify the map while in a foreach + Set keySet = new HashSet<>(schemas.keySet()); + for (String schemaName : keySet) { + if (!openApiJson.contains(COMPONENTS_SCHEMAS_REF + schemaName)) { + schemas.remove(schemaName); + } + } + } + } + } catch (JsonProcessingException e) { + // do nothing + } + + removeEmtpyComponents(openApi); + + openApi = resolvePropertyPlaceHolders(openApi, context); + + return openApi; + } + private void removeEmtpyComponents(OpenAPI openAPI) { Components components = openAPI.getComponents(); if (components == null) { @@ -1258,7 +1276,7 @@ private void removeEmtpyComponents(OpenAPI openAPI) { } } - private void sortOpenAPI(OpenAPI openAPI) { + private void normalizeOpenApi(OpenAPI openAPI) { // Sort paths if (openAPI.getPaths() != null) { io.swagger.v3.oas.models.Paths sortedPaths = new io.swagger.v3.oas.models.Paths(); @@ -1267,6 +1285,16 @@ private void sortOpenAPI(OpenAPI openAPI) { sortedPaths.setExtensions(new TreeMap<>(openAPI.getPaths().getExtensions())); } openAPI.setPaths(sortedPaths); + for (PathItem pathItem : sortedPaths.values()) { + normalizeOperation(pathItem.getGet()); + normalizeOperation(pathItem.getPut()); + normalizeOperation(pathItem.getPost()); + normalizeOperation(pathItem.getDelete()); + normalizeOperation(pathItem.getOptions()); + normalizeOperation(pathItem.getHead()); + normalizeOperation(pathItem.getPatch()); + normalizeOperation(pathItem.getTrace()); + } } // Sort all reusable Components @@ -1275,7 +1303,7 @@ private void sortOpenAPI(OpenAPI openAPI) { return; } - sortAllOf(components.getSchemas()); + normalizeSchemas(components.getSchemas()); sortComponent(components, Components::getSchemas, Components::setSchemas); sortComponent(components, Components::getResponses, Components::setResponses); @@ -1288,6 +1316,69 @@ private void sortOpenAPI(OpenAPI openAPI) { sortComponent(components, Components::getCallbacks, Components::setCallbacks); } + private void normalizeOperation(Operation operation) { + if (operation == null) { + return; + } + if (CollectionUtils.isNotEmpty(operation.getParameters())) { + for (Parameter parameter : operation.getParameters()) { + if (parameter == null) { + continue; + } + Schema paramSchema = parameter.getSchema(); + if (paramSchema == null) { + continue; + } + Schema normalizedSchema = normalizeSchema(paramSchema); + if (normalizedSchema != null) { + parameter.setSchema(normalizedSchema); + } else if (paramSchema.equals(EMPTY_SIMPLE_SCHEMA)) { + paramSchema.setType(TYPE_OBJECT); + } + } + } + if (operation.getRequestBody() != null) { + normalizeContent(operation.getRequestBody().getContent()); + } + if (CollectionUtils.isNotEmpty(operation.getResponses())) { + for (ApiResponse apiResponse : operation.getResponses().values()) { + normalizeContent(apiResponse.getContent()); + } + } + } + + private void normalizeContent(Content content) { + if (CollectionUtils.isEmpty(content)) { + return; + } + for (MediaType mediaType : content.values()) { + Schema mediaTypeSchema = mediaType.getSchema(); + if (mediaTypeSchema == null) { + continue; + } + Schema normalizedSchema = normalizeSchema(mediaTypeSchema); + if (normalizedSchema != null) { + mediaType.setSchema(normalizedSchema); + } else if (mediaTypeSchema.equals(EMPTY_SIMPLE_SCHEMA)) { + mediaTypeSchema.setType(TYPE_OBJECT); + } + Map paramSchemas = mediaTypeSchema.getProperties(); + if (CollectionUtils.isNotEmpty(paramSchemas)) { + Map paramNormalizedSchemas = new HashMap<>(); + for (Map.Entry paramEntry : paramSchemas.entrySet()) { + Schema paramSchema = paramEntry.getValue(); + Schema paramNormalizedSchema = normalizeSchema(paramSchema); + if (paramNormalizedSchema != null) { + paramNormalizedSchemas.put(paramEntry.getKey(), paramNormalizedSchema); + } + } + if (CollectionUtils.isNotEmpty(paramNormalizedSchemas)) { + paramSchemas.putAll(paramNormalizedSchemas); + } + } + } + } + private void sortComponent(Components components, Function> getter, BiConsumer> setter) { if (components != null && getter.apply(components) != null) { Map component = getter.apply(components); @@ -1295,38 +1386,134 @@ private void sortComponent(Components components, Function allOf = schema.getAllOf(); + if (CollectionUtils.isEmpty(allOf)) { + return null; + } + + if (allOf.size() == 1) { + + Schema allOfSchema = allOf.get(0); + + schema.setAllOf(null); + // if schema has only allOf block with one item or only defaultValue property or only type + Object defaultValue = schema.getDefault(); + String type = schema.getType(); + String serializedDefaultValue; + try { + serializedDefaultValue = defaultValue != null ? ConvertUtils.getJsonMapper().writeValueAsString(defaultValue) : null; + } catch (JsonProcessingException e) { + return null; + } + schema.setDefault(null); + schema.setType(null); + Schema normalizedSchema = null; + + Object allOfDefaultValue = allOfSchema.getDefault(); + String serializedAllOfDefaultValue; + try { + serializedAllOfDefaultValue = allOfDefaultValue != null ? ConvertUtils.getJsonMapper().writeValueAsString(allOfDefaultValue) : null; + } catch (JsonProcessingException e) { + return null; + } + boolean isSameType = allOfSchema.getType() == null || allOfSchema.getType().equals(type); + + if (schema.equals(EMPTY_SCHEMA) || schema.equals(EMPTY_COMPOSED_SCHEMA) + && (serializedDefaultValue == null || serializedDefaultValue.equals(serializedAllOfDefaultValue)) + && (type == null || allOfSchema.getType() == null || allOfSchema.getType().equals(type))) { + normalizedSchema = allOfSchema; + } + schema.setType(type); + schema.setAllOf(allOf); + schema.setDefault(defaultValue); + return normalizedSchema; + } + List finalList = new ArrayList<>(allOf.size()); + List schemasWithoutRef = new ArrayList<>(allOf.size() - 1); + for (Schema schemaAllOf : allOf) { + Schema normalizedSchema = normalizeSchema(schemaAllOf); + if (normalizedSchema != null) { + schemaAllOf = normalizedSchema; + } + Map paramSchemas = schemaAllOf.getProperties(); + if (CollectionUtils.isNotEmpty(paramSchemas)) { + Map paramNormalizedSchemas = new HashMap<>(); + for (Map.Entry paramEntry : paramSchemas.entrySet()) { + Schema paramSchema = paramEntry.getValue(); + Schema paramNormalizedSchema = normalizeSchema(paramSchema); + if (paramNormalizedSchema != null) { + paramNormalizedSchemas.put(paramEntry.getKey(), paramNormalizedSchema); + } + } + if (CollectionUtils.isNotEmpty(paramNormalizedSchemas)) { + paramSchemas.putAll(paramNormalizedSchemas); + } + } + + if (StringUtils.isEmpty(schemaAllOf.get$ref())) { + schemasWithoutRef.add(schemaAllOf); + // remove all description fields, if it's already set in main schema + if (StringUtils.isNotEmpty(schema.getDescription()) + && StringUtils.isNotEmpty(schemaAllOf.getDescription())) { + schemaAllOf.setDescription(null); + } + // remove deplicate default field + if (schema.getDefault() != null + && schemaAllOf.getDefault() != null && schema.getDefault().equals(schemaAllOf.getDefault())) { + schema.setDefault(null); + } + continue; + } + finalList.add(schemaAllOf); + } + finalList.addAll(schemasWithoutRef); + schema.setAllOf(finalList); + return null; + } + /** * Sort schemas list in allOf block: schemas with ref must be first, next other schemas. * * @param schemas all schema components */ - private void sortAllOf(Map schemas) { + private void normalizeSchemas(Map schemas) { if (CollectionUtils.isEmpty(schemas)) { return; } - for (Schema schema : schemas.values()) { - if (CollectionUtils.isEmpty(schema.getAllOf()) - || schema.getAllOf().size() == 1) { - continue; + Map normalizedSchemas = new HashMap<>(); + + for (Map.Entry entry : schemas.entrySet()) { + Schema schema = entry.getValue(); + Schema normalizedSchema = normalizeSchema(schema); + if (normalizedSchema != null) { + normalizedSchemas.put(entry.getKey(), normalizedSchema); + } else if (schema.equals(EMPTY_SIMPLE_SCHEMA)) { + schema.setType(TYPE_OBJECT); } - List finalList = new ArrayList<>(schema.getAllOf().size()); - List schemasWithoutRef = new ArrayList<>(schema.getAllOf().size() - 1); - for (Schema schemaAllOf : (List) schema.getAllOf()) { - if (StringUtils.isEmpty(schemaAllOf.get$ref())) { - schemasWithoutRef.add(schemaAllOf); - // remove all description fields, if it's already set in main schema - if (StringUtils.isNotEmpty(schema.getDescription()) - && StringUtils.isNotEmpty(schemaAllOf.getDescription())) { - schemaAllOf.setDescription(null); + + Map paramSchemas = schema.getProperties(); + if (CollectionUtils.isNotEmpty(paramSchemas)) { + Map paramNormalizedSchemas = new HashMap<>(); + for (Map.Entry paramEntry : paramSchemas.entrySet()) { + Schema paramSchema = paramEntry.getValue(); + Schema paramNormalizedSchema = normalizeSchema(paramSchema); + if (paramNormalizedSchema != null) { + paramNormalizedSchemas.put(paramEntry.getKey(), paramNormalizedSchema); + } else if (paramSchema.equals(EMPTY_SIMPLE_SCHEMA)) { + paramSchema.setType(TYPE_OBJECT); } - continue; } - finalList.add(schemaAllOf); + if (CollectionUtils.isNotEmpty(paramNormalizedSchemas)) { + paramSchemas.putAll(paramNormalizedSchemas); + } } - finalList.addAll(schemasWithoutRef); - schema.setAllOf(finalList); + } + + if (CollectionUtils.isNotEmpty(normalizedSchemas)) { + schemas.putAll(normalizedSchemas); } } diff --git a/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiControllerVisitor.java b/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiControllerVisitor.java index 7a0e6a6850..d7c74fb4e8 100644 --- a/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiControllerVisitor.java +++ b/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiControllerVisitor.java @@ -32,6 +32,7 @@ import io.micronaut.context.annotation.Requires; import io.micronaut.context.env.Environment; import io.micronaut.core.annotation.AnnotationValue; +import io.micronaut.core.util.ArrayUtils; import io.micronaut.core.util.CollectionUtils; import io.micronaut.core.util.StringUtils; import io.micronaut.http.HttpMethod; @@ -178,11 +179,13 @@ public int getOrder() { private List mediaTypes(MethodElement element, Class ann) { String[] values = element.stringValues(ann); - if (values.length == 0) { + if (ArrayUtils.isEmpty(values)) { return DEFAULT_MEDIA_TYPES; - } else { - return Arrays.stream(values).map(MediaType::of).distinct().collect(Collectors.toList()); } + return Arrays.stream(values) + .map(MediaType::of) + .distinct() + .collect(Collectors.toList()); } @Override @@ -201,18 +204,17 @@ protected List uriMatchTemplates(MethodElement element, Visito UriMatchTemplate matchTemplate = UriMatchTemplate.of(controllerValue); // check if we have multiple uris String[] uris = element.stringValues(HttpMethodMapping.class, "uris"); - if (uris.length == 0) { + if (ArrayUtils.isEmpty(uris)) { String methodValue = element.getValue(HttpMethodMapping.class, String.class).orElse("/"); methodValue = OpenApiApplicationVisitor.replacePlaceholders(methodValue, context); return Collections.singletonList(matchTemplate.nest(methodValue)); - } else { - List matchTemplates = new ArrayList<>(uris.length); - for (String methodValue : uris) { - methodValue = OpenApiApplicationVisitor.replacePlaceholders(methodValue, context); - matchTemplates.add(matchTemplate.nest(methodValue)); - } - return matchTemplates; } + List matchTemplates = new ArrayList<>(uris.length); + for (String methodValue : uris) { + methodValue = OpenApiApplicationVisitor.replacePlaceholders(methodValue, context); + matchTemplates.add(matchTemplate.nest(methodValue)); + } + return matchTemplates; } @Override diff --git a/openapi/src/main/java/io/micronaut/openapi/visitor/SchemaUtils.java b/openapi/src/main/java/io/micronaut/openapi/visitor/SchemaUtils.java index 5efe89105e..34c5cbe678 100644 --- a/openapi/src/main/java/io/micronaut/openapi/visitor/SchemaUtils.java +++ b/openapi/src/main/java/io/micronaut/openapi/visitor/SchemaUtils.java @@ -21,6 +21,7 @@ import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.media.ArraySchema; +import io.swagger.v3.oas.models.media.ComposedSchema; import io.swagger.v3.oas.models.media.Schema; import static io.micronaut.openapi.visitor.Utils.resolveComponents; @@ -33,6 +34,9 @@ */ public final class SchemaUtils { + public static final Schema EMPTY_SCHEMA = new Schema<>(); + public static final Schema EMPTY_SIMPLE_SCHEMA = new SimpleSchema(); + public static final Schema EMPTY_COMPOSED_SCHEMA = new ComposedSchema(); public static final String TYPE_OBJECT = "object"; private SchemaUtils() { diff --git a/openapi/src/main/java/io/micronaut/openapi/visitor/Utils.java b/openapi/src/main/java/io/micronaut/openapi/visitor/Utils.java index 6880b54f9d..d653b6bad5 100644 --- a/openapi/src/main/java/io/micronaut/openapi/visitor/Utils.java +++ b/openapi/src/main/java/io/micronaut/openapi/visitor/Utils.java @@ -61,7 +61,6 @@ public final class Utils { private static PropertyPlaceholderResolver propertyPlaceholderResolver; private static OpenAPI testReference; - private static OpenAPI testReferenceAfterPlaceholders; private static String testFileName; private static String testYamlReference; private static String testJsonReference; @@ -178,14 +177,11 @@ public static Components resolveComponents(OpenAPI openAPI) { * * @return The {@link OpenAPI} instance */ - public static OpenAPI resolveOpenAPI(VisitorContext context) { + public static OpenAPI resolveOpenApi(VisitorContext context) { OpenAPI openAPI = context.get(ATTR_OPENAPI, OpenAPI.class).orElse(null); if (openAPI == null) { openAPI = new OpenAPI(); context.put(ATTR_OPENAPI, openAPI); - if (isTestMode()) { - setTestReference(openAPI); - } } return openAPI; } @@ -194,6 +190,7 @@ public static OpenAPI resolveOpenAPI(VisitorContext context) { * Return stacktrace for throwable and message. * * @param t throwable + * * @return stacktrace */ public static String printStackTrace(Throwable t) { @@ -216,14 +213,6 @@ public static void setTestReference(OpenAPI testReference) { Utils.testReference = testReference; } - public static OpenAPI getTestReferenceAfterPlaceholders() { - return testReferenceAfterPlaceholders; - } - - public static void setTestReferenceAfterPlaceholders(OpenAPI testReferenceAfterPlaceholders) { - Utils.testReferenceAfterPlaceholders = testReferenceAfterPlaceholders; - } - public static String getTestYamlReference() { return testYamlReference; } diff --git a/openapi/src/test/groovy/io/micronaut/openapi/MyJaxbElement4.java b/openapi/src/test/groovy/io/micronaut/openapi/MyJaxbElement4.java index 625df2a23e..930aef09bf 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/MyJaxbElement4.java +++ b/openapi/src/test/groovy/io/micronaut/openapi/MyJaxbElement4.java @@ -4,7 +4,6 @@ import javax.validation.constraints.Pattern; import javax.validation.constraints.Size; -import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.media.Schema; public class MyJaxbElement4 { @@ -18,11 +17,7 @@ public class MyJaxbElement4 { * Discount data */ @Schema(oneOf = {DiscountSizeOpenApi.class, DiscountFixedOpenApi.class, MultiplierSizeOpenApi.class}) - public Discount value; - - @Hidden - public interface Discount { - } + public Object value; /** * Discout type @@ -37,7 +32,7 @@ public enum DiscountTypeType { /** * Discount size */ - public static class DiscountSizeOpenApi implements Discount { + public static class DiscountSizeOpenApi { /** * Value description @@ -57,7 +52,7 @@ public static class DiscountSizeOpenApi implements Discount { /** * Discount fixed */ - public static class DiscountFixedOpenApi implements Discount { + public static class DiscountFixedOpenApi { /** * Value description @@ -76,7 +71,7 @@ public static class DiscountFixedOpenApi implements Discount { /** * Multiplier size */ - public static class MultiplierSizeOpenApi implements Discount { + public static class MultiplierSizeOpenApi { /** * Value description diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiApplicationVisitorSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiApplicationVisitorSpec.groovy index cb1dde8d78..7b4d6c86d0 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiApplicationVisitorSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiApplicationVisitorSpec.groovy @@ -698,11 +698,6 @@ import io.swagger.v3.oas.annotations.security.SecurityScheme; scopes = @OAuthScope(name = "write:pets", description = "modify pets in your account"))), description = "ssssss" ) -@SecurityScheme( - name = "schemeWithRef", - type = SecuritySchemeType.DEFAULT, - ref = "#/components/securitySchemes/foo" -) class Application { } @@ -775,24 +770,13 @@ class MyBean {} oauth2.flows oauth2.flows.implicit oauth2.scheme == null - - def withRef = openAPI.components.securitySchemes['schemeWithRef'] - withRef.type == null - withRef.in == null - withRef.name == null - withRef.description == null - withRef.openIdConnectUrl == null - withRef.bearerFormat == null - withRef.flows == null - withRef.scheme == null - withRef.$ref == '#/components/securitySchemes/foo' } void "test disable openapi"() { given: "An API definition" Utils.testReference = null - Utils.testReferenceAfterPlaceholders = null + Utils.testReference = null System.setProperty(OpenApiApplicationVisitor.MICRONAUT_OPENAPI_ENABLED, "false") when: @@ -851,7 +835,7 @@ class MyBean {} ''') then: "the state is correct" !Utils.testReference - !Utils.testReferenceAfterPlaceholders + !Utils.testReference cleanup: System.clearProperty(OpenApiApplicationVisitor.MICRONAUT_OPENAPI_FIELD_VISIBILITY_LEVEL) @@ -862,7 +846,7 @@ class MyBean {} given: "An API definition" Utils.testReference = null - Utils.testReferenceAfterPlaceholders = null + Utils.testReference = null System.setProperty(OpenApiApplicationVisitor.MICRONAUT_CONFIG_FILE_LOCATIONS, "project:/src/test/resources/") System.setProperty(Environment.ENVIRONMENTS_PROPERTY, "disabled-openapi") @@ -918,7 +902,7 @@ class MyBean {} ''') then: "the state is correct" !Utils.testReference - !Utils.testReferenceAfterPlaceholders + !Utils.testReference cleanup: System.clearProperty(OpenApiApplicationVisitor.MICRONAUT_OPENAPI_FIELD_VISIBILITY_LEVEL) @@ -932,7 +916,7 @@ class MyBean {} given: "An API definition" Utils.testReference = null - Utils.testReferenceAfterPlaceholders = null + Utils.testReference = null System.setProperty(OpenApiApplicationVisitor.MICRONAUT_OPENAPI_CONFIG_FILE, "openapi-disabled-openapi.properties") when: @@ -987,7 +971,7 @@ class MyBean {} ''') then: "the state is correct" !Utils.testReference - !Utils.testReferenceAfterPlaceholders + !Utils.testReference cleanup: System.clearProperty(OpenApiApplicationVisitor.MICRONAUT_OPENAPI_CONFIG_FILE) diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiArraySchemaSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiArraySchemaSpec.groovy index bc4ebc3a94..be45fde304 100755 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiArraySchemaSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiArraySchemaSpec.groovy @@ -72,49 +72,49 @@ class MyBean {} OpenAPI openAPI = Utils.testReference Operation operation = openAPI.paths?.get("/")?.get + def petSchema = openAPI.components.schemas['Pets']; expect: operation operation.responses.size() == 1 - openAPI.components.schemas['Pets'].description == 'Pets' - openAPI.components.schemas['Pets'].properties['pets'].nullable == false - openAPI.components.schemas['Pets'].properties['pets'].description == 'a list of Pets' - openAPI.components.schemas['Pets'].properties['pets'].minItems == 2 - openAPI.components.schemas['Pets'].properties['pets'].items.$ref == '#/components/schemas/Pet' - openAPI.components.schemas['Pets'].properties['pets'].items.description == 'Pet' - openAPI.components.schemas['Pets'].properties['pets'].items.nullable == null - - openAPI.components.schemas['Pets'].properties['ids'].nullable == false - openAPI.components.schemas['Pets'].properties['ids'].description == 'a list of Ids' - openAPI.components.schemas['Pets'].properties['ids'].minItems == 2 - openAPI.components.schemas['Pets'].properties['ids'].items.format == 'int64' - openAPI.components.schemas['Pets'].properties['ids'].items.description == 'Yes' - openAPI.components.schemas['Pets'].properties['ids'].items.nullable == true - - openAPI.components.schemas['Pets'].properties['primitiveIds'].nullable == false - openAPI.components.schemas['Pets'].properties['primitiveIds'].description == 'a list of primitive Ids' - openAPI.components.schemas['Pets'].properties['primitiveIds'].minItems == 2 - openAPI.components.schemas['Pets'].properties['primitiveIds'].items.format == 'int64' - openAPI.components.schemas['Pets'].properties['primitiveIds'].items.description == 'Yes' - openAPI.components.schemas['Pets'].properties['primitiveIds'].items.nullable == true - - openAPI.components.schemas['Pets'].properties['nestedPrimitiveIds'].description == 'a nested array of primitive Ids' - openAPI.components.schemas['Pets'].properties['nestedPrimitiveIds'].items.items.format == 'int64' - - openAPI.components.schemas['Pets'].properties['nestedPetList'].description == 'a nested list of Pets' - openAPI.components.schemas['Pets'].properties['nestedPetList'].items.items.$ref == '#/components/schemas/Pet' - - openAPI.components.schemas['Pets'].properties['nestedPetArray'].description == 'a nested array of Pets' - openAPI.components.schemas['Pets'].properties['nestedPetArray'].items.items.$ref == '#/components/schemas/Pet' - - openAPI.components.schemas['Pets'].properties['nestedIdArray'].description == 'a nested array of Ids' - openAPI.components.schemas['Pets'].properties['nestedIdArray'].items.items.format == 'int64' - - openAPI.components.schemas['Pets'].properties['idArrayList'].description == 'a list of nested Ids' - openAPI.components.schemas['Pets'].properties['idArrayList'].items.items.format == 'int64' - - openAPI.components.schemas['Pets'].properties['idListArray'].description == 'an array of nested Ids' - openAPI.components.schemas['Pets'].properties['idListArray'].items.items.format == 'int64' + petSchema.description == 'Pets' + petSchema.properties['pets'].nullable == false + petSchema.properties['pets'].description == 'a list of Pets' + petSchema.properties['pets'].minItems == 2 + petSchema.properties['pets'].items.$ref == '#/components/schemas/Pet' + petSchema.properties['pets'].items.nullable == null + + petSchema.properties['ids'].nullable == false + petSchema.properties['ids'].description == 'a list of Ids' + petSchema.properties['ids'].minItems == 2 + petSchema.properties['ids'].items.format == 'int64' + petSchema.properties['ids'].items.description == 'Yes' + petSchema.properties['ids'].items.nullable == true + + petSchema.properties['primitiveIds'].nullable == false + petSchema.properties['primitiveIds'].description == 'a list of primitive Ids' + petSchema.properties['primitiveIds'].minItems == 2 + petSchema.properties['primitiveIds'].items.format == 'int64' + petSchema.properties['primitiveIds'].items.description == 'Yes' + petSchema.properties['primitiveIds'].items.nullable == true + + petSchema.properties['nestedPrimitiveIds'].description == 'a nested array of primitive Ids' + petSchema.properties['nestedPrimitiveIds'].items.items.format == 'int64' + + petSchema.properties['nestedPetList'].description == 'a nested list of Pets' + petSchema.properties['nestedPetList'].items.items.$ref == '#/components/schemas/Pet' + + petSchema.properties['nestedPetArray'].description == 'a nested array of Pets' + petSchema.properties['nestedPetArray'].items.items.$ref == '#/components/schemas/Pet' + + petSchema.properties['nestedIdArray'].description == 'a nested array of Ids' + petSchema.properties['nestedIdArray'].items.items.format == 'int64' + + petSchema.properties['idArrayList'].description == 'a list of nested Ids' + petSchema.properties['idArrayList'].items.items.format == 'int64' + + petSchema.properties['idListArray'].description == 'an array of nested Ids' + petSchema.properties['idListArray'].items.items.format == 'int64' } void "test ArraySchema with arraySchema field in Controller ApiResponse"() { diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiBasicSchemaSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiBasicSchemaSpec.groovy index c8c3a7a6fc..450ef23176 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiBasicSchemaSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiBasicSchemaSpec.groovy @@ -6,6 +6,8 @@ import io.swagger.v3.oas.models.Operation import io.swagger.v3.oas.models.media.Schema import spock.lang.Issue +import java.time.OffsetDateTime + class OpenApiBasicSchemaSpec extends AbstractOpenApiTypeElementSpec { void "test @PositiveOrZero and @NegativeOrZero correctly results in minimum 0 and maximum 0"() { @@ -1321,7 +1323,7 @@ class DemoData { private URL url; @Schema(defaultValue = "274191c9-c176-4b1c-8263-1b658cbdc7fc") private UUID uuid; - @Schema(defaultValue = "Jan 12, 1952") + @Schema(defaultValue = "2007-12-03T10:15:30+01:00") private Date date; @Schema(defaultValue = "myDefault3") private MySubObject mySubObject; @@ -1428,13 +1430,14 @@ public class MyBean {} schema.properties.uuid.type == 'string' schema.properties.uuid.format == 'uuid' - schema.properties.date.default == 'Jan 12, 1952' + // TODO: need to add support custom format for DateTime + schema.properties.date.default == OffsetDateTime.parse('2007-12-03T10:15:30+01:00') schema.properties.date.type == 'string' schema.properties.date.format == 'date-time' - schema.properties.mySubObject.default == 'myDefault3' - schema.properties.mySubObject.type == null - schema.properties.mySubObject.format == null + schema.properties.mySubObject.allOf.get(1).default == 'myDefault3' + schema.properties.mySubObject.allOf.get(1).type == null + schema.properties.mySubObject.allOf.get(1).format == null } @Issue("https://github.com/micronaut-projects/micronaut-openapi/issues/947") diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiControllerVisitorSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiControllerVisitorSpec.groovy index 380d869d45..2585c6d81c 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiControllerVisitorSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiControllerVisitorSpec.groovy @@ -1001,7 +1001,7 @@ class MyController { class MyBean {} ''') when: - OpenAPI api = Utils.testReferenceAfterPlaceholders + OpenAPI api = Utils.testReference then: api.paths.size() == 2 @@ -1078,7 +1078,6 @@ class MyBean {} then: openAPI.components.schemas.size() == 1 - openAPI.components.schemas['TestPojo'].name == 'TestPojo' openAPI.components.schemas['TestPojo'].type == 'object' openAPI.components.schemas['TestPojo'].properties.size() == 1 openAPI.components.schemas['TestPojo'].properties['testString'].type == 'string' diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiEncodingSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiEncodingSpec.groovy index 7f1ee4191d..f101e167f8 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiEncodingSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiEncodingSpec.groovy @@ -225,8 +225,7 @@ class MyController2 { name = "test", description = "this is description", defaultValue = "{\\"stampWidth\\": 100}", - format = "binary", - ref = "#/components/schemas/Head4Schema" + format = "binary" ) ), @Header( @@ -529,7 +528,6 @@ class MyBean {} !operation.requestBody.content."multipart/mixed".encoding.firstOject.headers.MyHeader2.required !operation.requestBody.content."multipart/mixed".encoding.firstOject.headers.MyHeader2.deprecated operation.requestBody.content."multipart/mixed".encoding.firstOject.headers.MyHeader3.get$ref() == '#/components/headers/Head3' - operation.requestBody.content."multipart/mixed".encoding.firstOject.headers.MyHeader4.schema.get$ref() == '#/components/schemas/Head4Schema' operation.requestBody.content."multipart/mixed".encoding.firstOject.headers.MyHeader4.schema.description == 'this is description' operation.requestBody.content."multipart/mixed".encoding.firstOject.headers.MyHeader4.schema.default operation.requestBody.content."multipart/mixed".encoding.firstOject.headers.MyHeader4.schema.format == 'binary' diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiEnumSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiEnumSpec.groovy index 453a6408ba..6447e12a29 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiEnumSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiEnumSpec.groovy @@ -642,10 +642,10 @@ enum Type2 { class MyBean {} ''') then: "the state is correct" - Utils.testReferenceAfterPlaceholders != null + Utils.testReference != null when: "The OpenAPI is retrieved" - OpenAPI openAPI = Utils.testReferenceAfterPlaceholders + OpenAPI openAPI = Utils.testReference then: "the state is correct" openAPI.components @@ -774,10 +774,10 @@ enum Type2 { class MyBean {} ''') then: "the state is correct" - Utils.testReferenceAfterPlaceholders != null + Utils.testReference != null when: "The OpenAPI is retrieved" - OpenAPI openAPI = Utils.testReferenceAfterPlaceholders + OpenAPI openAPI = Utils.testReference then: "the state is correct" openAPI.components @@ -863,10 +863,10 @@ enum BackupFrequencyExDto { class MyBean {} ''') then: "the state is correct" - Utils.testReferenceAfterPlaceholders != null + Utils.testReference != null when: "The OpenAPI is retrieved" - OpenAPI openAPI = Utils.testReferenceAfterPlaceholders + OpenAPI openAPI = Utils.testReference then: "the state is correct" openAPI.components diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiFileUploadBodyParameterSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiFileUploadBodyParameterSpec.groovy index e0eae51e15..481995a12d 100755 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiFileUploadBodyParameterSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiFileUploadBodyParameterSpec.groovy @@ -19,7 +19,7 @@ package test; import jakarta.inject.Singleton; import io.micronaut.http.MediaType; -import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Body;import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Post; import io.micronaut.http.multipart.CompletedFileUpload; import io.micronaut.http.multipart.PartData; @@ -96,7 +96,8 @@ class MyBean {} requestBody.content['multipart/form-data'].schema requestBody.content['multipart/form-data'].schema.type == 'object' requestBody.content['multipart/form-data'].schema.properties['file'] - requestBody.content['multipart/form-data'].schema.properties['file'] instanceof BinarySchema + requestBody.content['multipart/form-data'].schema.properties['file'].type == 'string' + requestBody.content['multipart/form-data'].schema.properties['file'].format == 'binary' requestBody.content['multipart/form-data'].schema.properties['file'].description == "File Parts." expect: @@ -114,7 +115,8 @@ class MyBean {} requestBody.content['multipart/form-data'].schema requestBody.content['multipart/form-data'].schema.type == 'object' requestBody.content['multipart/form-data'].schema.properties['file'] - requestBody.content['multipart/form-data'].schema.properties['file'] instanceof BinarySchema + requestBody.content['multipart/form-data'].schema.properties['file'].type == 'string' + requestBody.content['multipart/form-data'].schema.properties['file'].format == 'binary' requestBody.content['multipart/form-data'].schema.properties['file'].description == "Completed File." expect: @@ -132,7 +134,8 @@ class MyBean {} requestBody.content['multipart/form-data'].schema requestBody.content['multipart/form-data'].schema.type == 'object' requestBody.content['multipart/form-data'].schema.properties['file'] - requestBody.content['multipart/form-data'].schema.properties['file'] instanceof BinarySchema + requestBody.content['multipart/form-data'].schema.properties['file'].type == 'string' + requestBody.content['multipart/form-data'].schema.properties['file'].format == 'binary' requestBody.content['multipart/form-data'].schema.properties['file'].description == "Streaming File." expect: @@ -150,10 +153,12 @@ class MyBean {} requestBody.content['multipart/form-data'].schema requestBody.content['multipart/form-data'].schema.type == 'object' requestBody.content['multipart/form-data'].schema.properties['file1'] - requestBody.content['multipart/form-data'].schema.properties['file1'] instanceof BinarySchema + requestBody.content['multipart/form-data'].schema.properties['file1'].type == 'string' + requestBody.content['multipart/form-data'].schema.properties['file1'].format == 'binary' requestBody.content['multipart/form-data'].schema.properties['file1'].description == "Streaming File 1." requestBody.content['multipart/form-data'].schema.properties['file2'] - requestBody.content['multipart/form-data'].schema.properties['file2'] instanceof BinarySchema + requestBody.content['multipart/form-data'].schema.properties['file2'].type == 'string' + requestBody.content['multipart/form-data'].schema.properties['file2'].format == 'binary' requestBody.content['multipart/form-data'].schema.properties['file2'].description == "Streaming File 2." expect: @@ -173,7 +178,8 @@ class MyBean {} requestBody.content['multipart/form-data'].schema.properties['files'] requestBody.content['multipart/form-data'].schema.properties['files'] instanceof ArraySchema requestBody.content['multipart/form-data'].schema.properties['files'].description == 'List of Files.' - requestBody.content['multipart/form-data'].schema.properties['files'].items instanceof BinarySchema + requestBody.content['multipart/form-data'].schema.properties['files'].items.type == 'string' + requestBody.content['multipart/form-data'].schema.properties['files'].items.format == 'binary' expect: operation @@ -224,7 +230,6 @@ class MyBean {} and: 'the @Part value is used instead of the parameter name' requestBody.content['multipart/form-data'].schema.properties['part'] - requestBody.content['multipart/form-data'].schema.properties['part'] instanceof BinarySchema requestBody.content['multipart/form-data'].schema.properties['part'].type == 'string' requestBody.content['multipart/form-data'].schema.properties['part'].format == 'binary' } @@ -274,7 +279,8 @@ class MyBean {} requestBody.content.size() == 1 requestBody.content['multipart/form-data'].schema requestBody.content['multipart/form-data'].schema instanceof ArraySchema - requestBody.content['multipart/form-data'].schema.items instanceof BinarySchema + requestBody.content['multipart/form-data'].schema.items.type == 'string' + requestBody.content['multipart/form-data'].schema.items.format == 'binary' expect: operation diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiFileUploadSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiFileUploadSpec.groovy index fd323a57a1..f0ffe22b73 100755 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiFileUploadSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiFileUploadSpec.groovy @@ -106,7 +106,8 @@ class MyBean {} requestBody.content['multipart/form-data'].schema requestBody.content['multipart/form-data'].schema.type == 'object' requestBody.content['multipart/form-data'].schema.properties['file'] - requestBody.content['multipart/form-data'].schema.properties['file'] instanceof BinarySchema + requestBody.content['multipart/form-data'].schema.properties['file'].type == 'string' + requestBody.content['multipart/form-data'].schema.properties['file'].format == 'binary' requestBody.content['multipart/form-data'].schema.properties['file'].description == "The file parts." expect: @@ -124,7 +125,8 @@ class MyBean {} requestBody.content['multipart/form-data'].schema requestBody.content['multipart/form-data'].schema.type == 'object' requestBody.content['multipart/form-data'].schema.properties['file'] - requestBody.content['multipart/form-data'].schema.properties['file'] instanceof BinarySchema + requestBody.content['multipart/form-data'].schema.properties['file'].type == 'string' + requestBody.content['multipart/form-data'].schema.properties['file'].format == 'binary' requestBody.content['multipart/form-data'].schema.properties['file'].description == "The complete file." expect: @@ -142,7 +144,8 @@ class MyBean {} requestBody.content['multipart/form-data'].schema requestBody.content['multipart/form-data'].schema.type == 'object' requestBody.content['multipart/form-data'].schema.properties['file'] - requestBody.content['multipart/form-data'].schema.properties['file'] instanceof BinarySchema + requestBody.content['multipart/form-data'].schema.properties['file'].type == 'string' + requestBody.content['multipart/form-data'].schema.properties['file'].format == 'binary' requestBody.content['multipart/form-data'].schema.properties['file'].description == "The streaming file." expect: @@ -160,10 +163,12 @@ class MyBean {} requestBody.content['multipart/form-data'].schema requestBody.content['multipart/form-data'].schema.type == 'object' requestBody.content['multipart/form-data'].schema.properties['file1'] - requestBody.content['multipart/form-data'].schema.properties['file1'] instanceof BinarySchema + requestBody.content['multipart/form-data'].schema.properties['file1'].type == 'string' + requestBody.content['multipart/form-data'].schema.properties['file1'].format == 'binary' requestBody.content['multipart/form-data'].schema.properties['file1'].description == "The streaming file 1." requestBody.content['multipart/form-data'].schema.properties['file2'] - requestBody.content['multipart/form-data'].schema.properties['file2'] instanceof BinarySchema + requestBody.content['multipart/form-data'].schema.properties['file2'].type == 'string' + requestBody.content['multipart/form-data'].schema.properties['file2'].format == 'binary' requestBody.content['multipart/form-data'].schema.properties['file2'].description == "The streaming file 2." expect: @@ -183,7 +188,8 @@ class MyBean {} requestBody.content['multipart/form-data'].schema.properties['files'] requestBody.content['multipart/form-data'].schema.properties['files'] instanceof ArraySchema requestBody.content['multipart/form-data'].schema.properties['files'].description == 'The streaming files.' - requestBody.content['multipart/form-data'].schema.properties['files'].items instanceof BinarySchema + requestBody.content['multipart/form-data'].schema.properties['files'].items.type == 'string' + requestBody.content['multipart/form-data'].schema.properties['files'].items.format == 'binary' expect: operation diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiIncludeVisitorSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiIncludeVisitorSpec.groovy index 21d08bbfb1..7fb27ab502 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiIncludeVisitorSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiIncludeVisitorSpec.groovy @@ -289,10 +289,10 @@ class HelloWorldController implements HelloWorldApi { class MyBean {} ''') then: - Utils.testReferenceAfterPlaceholders != null + Utils.testReference != null when: - OpenAPI openAPI = Utils.testReferenceAfterPlaceholders + OpenAPI openAPI = Utils.testReference then: openAPI.info != null diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiInheritedPojoControllerSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiInheritedPojoControllerSpec.groovy index 8335970104..6f23a2b2a5 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiInheritedPojoControllerSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiInheritedPojoControllerSpec.groovy @@ -1214,7 +1214,7 @@ class MyBean {} catSchema != null petSchema.type == 'object' - petSchema.properties.size() == 2 + petSchema.properties.size() == 3 animalSchema.type == 'object' animalSchema.properties.size() == 1 sleeperSchema.type == 'object' @@ -1594,13 +1594,10 @@ class MyBean {} !requestTypeSchema.allOf exportRequestTypeSchema - exportRequestTypeSchema.type == 'object' - exportRequestTypeSchema.allOf - exportRequestTypeSchema.allOf.size() == 1 - exportRequestTypeSchema.allOf[0].$ref == '#/components/schemas/RequestType2_4_0' + !exportRequestTypeSchema.allOf + exportRequestTypeSchema.$ref == '#/components/schemas/RequestType2_4_0' exportPaymentsRequestSchema - exportPaymentsRequestSchema.type == 'object' exportPaymentsRequestSchema.allOf exportPaymentsRequestSchema.allOf.size() == 2 exportPaymentsRequestSchema.allOf[0].$ref == '#/components/schemas/ExportRequestType2_4_0' @@ -1663,10 +1660,10 @@ enum BackupSubEnum12 { class MyBean {} ''') then: "the state is correct" - Utils.testReferenceAfterPlaceholders != null + Utils.testReference != null when: "The OpenAPI is retrieved" - OpenAPI openAPI = Utils.testReferenceAfterPlaceholders + OpenAPI openAPI = Utils.testReference Schema backupDto12Schema = openAPI.components.schemas.BackupDto12 then: diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiJacksonInheritanceSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiJacksonInheritanceSpec.groovy index 199a5144fc..15ccaf5b32 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiJacksonInheritanceSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiJacksonInheritanceSpec.groovy @@ -84,7 +84,7 @@ class Dog implements Pet { class MyBean {} ''') - OpenAPI openAPI = Utils.testReferenceAfterPlaceholders + OpenAPI openAPI = Utils.testReference expect: openAPI diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiNullableTypesSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiNullableTypesSpec.groovy index 3971d4031b..54745b2d08 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiNullableTypesSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiNullableTypesSpec.groovy @@ -12,7 +12,7 @@ class OpenApiNullableTypesSpec extends AbstractOpenApiTypeElementSpec { void "test build OpenAPI for java.util.Optional"() { when: - buildBeanDefinition('test.PetController',''' + buildBeanDefinition('test.PetController', ''' package test; import io.micronaut.http.HttpResponse; @@ -71,14 +71,14 @@ class PetController { } ''') - then:"the state is correct" + then: "the state is correct" Utils.testReference != null - when:"The OpenAPI is retrieved" + when: "The OpenAPI is retrieved" OpenAPI openAPI = Utils.testReference Schema petSchema = openAPI.components.schemas['Pet'] - then:"the components are valid" + then: "the components are valid" petSchema.type == 'object' petSchema.properties.size() == 2 @@ -91,7 +91,7 @@ class PetController { void "test build OpenAPI for nullable fields"() { when: - buildBeanDefinition('test.PetController',''' + buildBeanDefinition('test.PetController', ''' package test; import io.micronaut.core.annotation.Nullable; @@ -153,14 +153,14 @@ class PetController { } ''') - then:"the state is correct" + then: "the state is correct" Utils.testReference != null - when:"The OpenAPI is retrieved" + when: "The OpenAPI is retrieved" OpenAPI openAPI = Utils.testReference Schema petSchema = openAPI.components.schemas['Pet'] - then:"the components are valid" + then: "the components are valid" petSchema.type == 'object' petSchema.properties.size() == 2 @@ -170,7 +170,7 @@ class PetController { void "test build OpenAPI for nullable fields2"() { when: - buildBeanDefinition('test.PetController',''' + buildBeanDefinition('test.PetController', ''' package test; import io.micronaut.core.annotation.Nullable; @@ -232,16 +232,16 @@ class PetController { } ''') - then:"the state is correct" + then: "the state is correct" Utils.testReference != null - when:"The OpenAPI is retrieved" + when: "The OpenAPI is retrieved" OpenAPI openAPI = Utils.testReference Operation get = openAPI.paths."/pet/{name}/{type}".get Operation post = openAPI.paths."/pet/{type}".post Schema petSchema = openAPI.components.schemas['Pet'] - then:"the components are valid" + then: "the components are valid" petSchema.type == 'object' petSchema.properties.size() == 2 @@ -252,27 +252,28 @@ class PetController { get.parameters.get(0).name == 'name' get.parameters.get(0).required + // Path variables always required get.parameters.get(1).in == 'path' get.parameters.get(1).name == 'type' - !get.parameters.get(1).required + get.parameters.get(1).required post.parameters.get(0).in == 'path' post.parameters.get(0).name == 'type' - !post.parameters.get(0).required + post.parameters.get(0).required } @Unroll void "test build OpenAPI with Nullable annotations"(String annotation) { when: buildBeanDefinition('test.PetController', sampleClass(annotation)) - then:"the state is correct" + then: "the state is correct" Utils.testReference != null - when:"The OpenAPI is retrieved" + when: "The OpenAPI is retrieved" OpenAPI openAPI = Utils.testReference Schema schema = openAPI.components.schemas['HelloWorldDto'] - then:"the components are valid" + then: "the components are valid" schema.type == 'object' schema.properties.size() == 1 schema.properties.nullprop.nullable @@ -296,14 +297,14 @@ class PetController { void "test build OpenAPI with eclipse and jspecify Nullable annotations"(String annotation) { when: buildBeanDefinition('test.PetController', sampleClass(annotation)) - then:"the state is correct" + then: "the state is correct" Utils.testReference != null - when:"The OpenAPI is retrieved" + when: "The OpenAPI is retrieved" OpenAPI openAPI = Utils.testReference Schema schema = openAPI.components.schemas['HelloWorldDto'] - then:"the components are valid" + then: "the components are valid" schema.type == 'object' schema.properties.size() == 1 diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiOperationUniqueSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiOperationUniqueSpec.groovy index 8c323bafbe..685f40f77e 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiOperationUniqueSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiOperationUniqueSpec.groovy @@ -73,8 +73,8 @@ class TestController2 { class MyBean {} ''') - Operation firstOperation = Utils.testReferenceAfterPlaceholders?.paths?.get("/test1")?.get - Operation secondOperation = Utils.testReferenceAfterPlaceholders?.paths?.get("/test2")?.get + Operation firstOperation = Utils.testReference?.paths?.get("/test1")?.get + Operation secondOperation = Utils.testReference?.paths?.get("/test2")?.get expect: firstOperation.getOperationId() == "index" @@ -163,9 +163,9 @@ class TestController3 { class MyBean {} ''') - Operation firstGenerated = Utils.testReferenceAfterPlaceholders?.paths?.get("/test1")?.get - Operation operationWithId = Utils.testReferenceAfterPlaceholders?.paths?.get("/test2")?.get - Operation secondGenerated = Utils.testReferenceAfterPlaceholders?.paths?.get("/test3")?.get + Operation firstGenerated = Utils.testReference?.paths?.get("/test1")?.get + Operation operationWithId = Utils.testReference?.paths?.get("/test2")?.get + Operation secondGenerated = Utils.testReference?.paths?.get("/test3")?.get expect: firstGenerated.getOperationId() == "index" @@ -208,8 +208,8 @@ class TestController2 { class MyBean {} ''') - Operation firstOperation = Utils.testReferenceAfterPlaceholders?.paths?.get("/test1")?.get - Operation secondOperation = Utils.testReferenceAfterPlaceholders?.paths?.get("/test2")?.get + Operation firstOperation = Utils.testReference?.paths?.get("/test1")?.get + Operation secondOperation = Utils.testReference?.paths?.get("/test2")?.get expect: firstOperation.getOperationId() == "myIndex" diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiOperationWithjavadocSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiOperationWithjavadocSpec.groovy index 898f24f090..4030f7427e 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiOperationWithjavadocSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiOperationWithjavadocSpec.groovy @@ -253,7 +253,6 @@ class MyBean {} openAPI.components.schemas.size() == 1 Schema personSchema = openAPI.components.schemas['Person'] - personSchema.name == 'Person' personSchema.type == 'object' personSchema.description == 'The Person class description' personSchema.properties.name.type == 'string' diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiParameterMappingSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiParameterMappingSpec.groovy index 463a6f8a99..e55e3807aa 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiParameterMappingSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiParameterMappingSpec.groovy @@ -423,25 +423,28 @@ import io.swagger.v3.oas.annotations.enums.*; interface Test { @Get("/test1") - public String test1(String name); + String test1(String name); @Post("/test2") - public String test2(String name); + String test2(String name); @Get("/test3") - public String test3(Principal principal); + String test3(Principal principal); @Get("/test4") - public String test4(HttpRequest req); + String test4(HttpRequest req); @Get("/test5") - public String test5(HttpRequest req, Principal principal, String name, String greeting); + String test5(HttpRequest req, Principal principal, String name, String greeting); @Get("/test6{?bar}") - public String test6(@Nullable String bar, String name); + String test6(@Nullable String bar, String name); @Post("/test7") - public String test7(String someId, @Nullable String someNotRequired, java.util.Optional someNotRequired2, HttpRequest req, Principal principal, @Body Greeting myBody); + String test7(String someId, @Nullable String someNotRequired, java.util.Optional someNotRequired2, HttpRequest req, Principal principal, @Body Greeting myBody); + + @Get("/test8{/pathVar1,pathVar2}") + String test8(String pathVar1, String pathVar2, String queryVar, @Nullable String name); } class Greeting { @@ -526,6 +529,52 @@ class MyBean {} pathItem.post.requestBody.content['application/json'].schema.allOf[1].properties['someNotRequired'].nullable == true pathItem.post.requestBody.content['application/json'].schema.allOf[1].properties['someNotRequired2'] pathItem.post.requestBody.content['application/json'].schema.allOf[1].properties['someNotRequired2'].nullable == true + + when: + def pathItem1 = openAPI.paths.get("/test8") + def pathItem2 = openAPI.paths.get("/test8/{pathVar1}") + def pathItem3 = openAPI.paths.get("/test8/{pathVar1}/{pathVar2}") + + then: + pathItem1.get.operationId == 'test8' + pathItem1.get.parameters + pathItem1.get.parameters.size() == 2 + pathItem1.get.parameters[0].name == 'queryVar' + pathItem1.get.parameters[0].required + pathItem1.get.parameters[0].in == 'query' + pathItem1.get.parameters[1].name == 'name' + !pathItem1.get.parameters[1].required + pathItem1.get.parameters[1].in == 'query' + + pathItem2.get.operationId == 'test8_1' + pathItem2.get.parameters + pathItem2.get.parameters.size() == 3 + + pathItem2.get.parameters[0].name == 'pathVar1' + pathItem2.get.parameters[0].required + pathItem2.get.parameters[0].in == 'path' + pathItem2.get.parameters[1].name == 'queryVar' + pathItem2.get.parameters[1].required + pathItem2.get.parameters[1].in == 'query' + pathItem2.get.parameters[2].name == 'name' + !pathItem2.get.parameters[2].required + pathItem2.get.parameters[2].in == 'query' + + pathItem3.get.operationId == 'test8_2' + pathItem3.get.parameters + pathItem3.get.parameters.size() == 4 + pathItem3.get.parameters[0].name == 'pathVar1' + pathItem3.get.parameters[0].required + pathItem3.get.parameters[0].in == 'path' + pathItem3.get.parameters[1].name == 'pathVar2' + pathItem3.get.parameters[1].required + pathItem3.get.parameters[1].in == 'path' + pathItem3.get.parameters[2].name == 'queryVar' + pathItem3.get.parameters[2].required + pathItem3.get.parameters[2].in == 'query' + pathItem3.get.parameters[3].name == 'name' + !pathItem3.get.parameters[3].required + pathItem3.get.parameters[3].in == 'query' } void "test @Parameter in header and explode is true"() { diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiParameterSchemaSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiParameterSchemaSpec.groovy index a41d937c00..a3a3b4b0a9 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiParameterSchemaSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiParameterSchemaSpec.groovy @@ -17,7 +17,7 @@ import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Get; import io.micronaut.http.annotation.Patch;import io.micronaut.http.annotation.Post; import io.micronaut.http.annotation.Put; -import io.swagger.v3.oas.annotations.Parameter; +import io.micronaut.http.annotation.QueryValue;import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.Schema; import java.time.Period; @@ -28,19 +28,19 @@ class OpenApiController { @Get public HttpResponse processSync( - @Parameter(schema = @Schema(implementation = String.class)) Optional period) { + @Parameter(schema = @Schema(implementation = String.class)) @QueryValue Optional period) { return HttpResponse.ok(); } @Post public HttpResponse processSync2( - @Parameter(ref = "#/components/parameters/MyParam") Optional period) { + @Parameter(schema = @Schema(minLength = 10, maxLength = 20)) @QueryValue Optional period) { return HttpResponse.ok(); } @Put public HttpResponse processSync3( - @Parameter(schema = @Schema(ref = "#/components/schemas/MyParamSchema")) Optional period) { + @Parameter(schema = @Schema(ref = "#/components/schemas/MyParamSchema")) @QueryValue Optional period) { return HttpResponse.ok(); } } @@ -56,7 +56,6 @@ class MyBean {} Operation operation = openAPI.paths."/path".get Operation operationPost = openAPI.paths."/path".post Operation operationPut = openAPI.paths."/path".put - Operation operationPatch = openAPI.paths."/path".patch then: operation @@ -67,7 +66,9 @@ class MyBean {} operation.parameters[0].schema operation.parameters[0].schema.type == "string" - operationPost.parameters[0].get$ref() == "#/components/parameters/MyParam" + operationPost.parameters[0].schema + operationPost.parameters[0].schema.minLength == 10 + operationPost.parameters[0].schema.maxLength == 20 operationPut.parameters[0].schema.allOf.get(0).get$ref() == "#/components/schemas/MyParamSchema" } @@ -80,20 +81,16 @@ package test; import io.micronaut.http.HttpResponse; import io.micronaut.http.annotation.Controller; -import io.micronaut.http.annotation.Get; -import io.micronaut.http.annotation.Patch;import io.micronaut.http.annotation.Post; -import io.micronaut.http.annotation.Put; +import io.micronaut.http.annotation.Patch; +import io.micronaut.http.annotation.QueryValue; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.Schema; -import java.time.Period; -import java.util.Optional; - @Controller("/path") class OpenApiController { @Patch - public HttpResponse processSync4(@Parameter(schema = @Schema(type = "string", format = "uuid")) String param) { + public HttpResponse processSync4(@Parameter(schema = @Schema(type = "string", format = "uuid")) @QueryValue String param) { return HttpResponse.ok(); } } diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiPathParamRegexSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiPathParamRegexSpec.groovy index e3f012126a..6d3c5e4f18 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiPathParamRegexSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiPathParamRegexSpec.groovy @@ -21,7 +21,7 @@ import io.swagger.v3.oas.annotations.Operation; class OpenApiController { @Operation(summary = "Update tag", description = "Updates an existing tag", tags = "users_tag") - @Post("/tags/{tagId: \\\\d+}/{path:.*}{.ext}/update{/id:[a-zA-Z]+}/{+path}{?max,offset}") + @Post("/tags/{tagId: \\\\d+}/{path:.*}{.ext}/update/{+path}{?max,offset}{/id:[a-zA-Z]+}") public void postRaw() { } } @@ -37,7 +37,7 @@ class MyBean {} then: openAPI.paths - openAPI.paths."/path/tags/{tagId}/{path}/update/{id}/{path}" + openAPI.paths."/path/tags/{tagId}/{path}/update/{path}/{id}" } } diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiPlaceholdersSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiPlaceholdersSpec.groovy index 2b754b982a..4606f3c99a 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiPlaceholdersSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiPlaceholdersSpec.groovy @@ -50,10 +50,10 @@ class MyDto { class MyBean {} ''') then: "the state is correct" - Utils.testReferenceAfterPlaceholders != null + Utils.testReference != null when: "The OpenAPI is retrieved" - OpenAPI openAPI = Utils.testReferenceAfterPlaceholders + OpenAPI openAPI = Utils.testReference Schema dtoSchema = openAPI.components.schemas['MyDto'] then: "the components are valid" @@ -121,10 +121,10 @@ class MyDto { class MyBean {} ''') then: "the state is correct" - Utils.testReferenceAfterPlaceholders != null + Utils.testReference != null when: "The OpenAPI is retrieved" - OpenAPI openAPI = Utils.testReferenceAfterPlaceholders + OpenAPI openAPI = Utils.testReference Info info = openAPI.info then: "the components are valid" diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiPojoControllerSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiPojoControllerSpec.groovy index 6a6c86bc1e..02c1d40066 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiPojoControllerSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiPojoControllerSpec.groovy @@ -366,26 +366,26 @@ class MyBean {} petSchema.properties['name'].type == 'string' petSchema.properties['name'].description == 'The Pet Name' - ((MapSchema) petSchema.properties['freeForm']).type == "object" - ((MapSchema) petSchema.properties['freeForm']).description == "A free-form object" - ((MapSchema) petSchema.properties['freeForm']).getAdditionalProperties() == true + petSchema.properties['freeForm'].type == "object" + petSchema.properties['freeForm'].description == "A free-form object" + petSchema.properties['freeForm'].getAdditionalProperties() == true - ((MapSchema) petSchema.properties['dictionariesPlain']).type == "object" - ((MapSchema) petSchema.properties['dictionariesPlain']).description == "A string-to-string dictionary" - ((Schema) ((MapSchema) petSchema.properties['dictionariesPlain']).getAdditionalProperties()).getType() == "string" + petSchema.properties['dictionariesPlain'].type == "object" + petSchema.properties['dictionariesPlain'].description == "A string-to-string dictionary" + petSchema.properties['dictionariesPlain'].getAdditionalProperties().getType() == "string" - ((MapSchema) petSchema.properties['tags']).type == "object" - ((MapSchema) petSchema.properties['tags']).description == "A string-to-object dictionary" - ((Schema) ((MapSchema) petSchema.properties['tags']).getAdditionalProperties()).$ref == "#/components/schemas/Tag" + petSchema.properties['tags'].type == "object" + petSchema.properties['tags'].description == "A string-to-object dictionary" + petSchema.properties['tags'].getAdditionalProperties().$ref == "#/components/schemas/Tag" tagSchema.properties['name'].type == "string" tagSchema.properties['name'].description == "The Tag Name" tagSchema.properties['description'].type == "string" - ((MapSchema) petSchema.properties['tagArrays']).type == "object" - ((MapSchema) petSchema.properties['tagArrays']).description == "A string-to-array dictionary" - ((ArraySchema) ((MapSchema) petSchema.properties['tagArrays']).getAdditionalProperties()).getType() == "array" - ((ArraySchema) ((MapSchema) petSchema.properties['tagArrays']).getAdditionalProperties()).getItems().$ref == "#/components/schemas/Tag" + petSchema.properties['tagArrays'].type == "object" + petSchema.properties['tagArrays'].description == "A string-to-array dictionary" + petSchema.properties['tagArrays'].getAdditionalProperties().getType() == "array" + petSchema.properties['tagArrays'].getAdditionalProperties().getItems().$ref == "#/components/schemas/Tag" } void "test build OpenAPI doc for POJO type with javax.constraints"() { @@ -2354,7 +2354,6 @@ class MyBean {} then: openAPI.components.schemas.size() == 1 - openAPI.components.schemas['Greeting'].name == 'Greeting' openAPI.components.schemas['Greeting'].description == 'Represent a greeting between a sender and a receiver' openAPI.components.schemas['Greeting'].type == 'object' openAPI.components.schemas['Greeting'].properties.size() == 3 @@ -2424,7 +2423,6 @@ class MyBean {} then: openAPI.components.schemas.size() == 1 - openAPI.components.schemas['MyDTO'].name == 'MyDTO' openAPI.components.schemas['MyDTO'].type == 'object' openAPI.components.schemas['MyDTO'].properties.size() == 2 openAPI.components.schemas['MyDTO'].properties['shouldBeNumber'].type == 'number' diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiPropsFromEnvSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiPropsFromEnvSpec.groovy index b53ad764c8..ccad6d8eed 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiPropsFromEnvSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiPropsFromEnvSpec.groovy @@ -178,10 +178,10 @@ class HelloWorldController implements HelloWorldApi { class MyBean {} ''') then: - Utils.testReferenceAfterPlaceholders != null + Utils.testReference != null when: - OpenAPI openAPI = Utils.testReferenceAfterPlaceholders + OpenAPI openAPI = Utils.testReference PathItem helloPathItem = openAPI.paths."/hello" PathItem loginPathItem = openAPI.paths."/myLoginUrl" @@ -282,10 +282,10 @@ class HelloWorldController implements HelloWorldApi { class MyBean {} ''') then: - Utils.testReferenceAfterPlaceholders != null + Utils.testReference != null when: - OpenAPI openAPI = Utils.testReferenceAfterPlaceholders + OpenAPI openAPI = Utils.testReference PathItem helloPathItem = openAPI.paths."/hello" PathItem loginPathItem = openAPI.paths."/expandUrl1" @@ -386,10 +386,10 @@ class HelloWorldController implements HelloWorldApi { class MyBean {} ''') then: - Utils.testReferenceAfterPlaceholders != null + Utils.testReference != null when: - OpenAPI openAPI = Utils.testReferenceAfterPlaceholders + OpenAPI openAPI = Utils.testReference PathItem helloPathItem = openAPI.paths."/hello" PathItem loginPathItem = openAPI.paths."/expandUrl1" diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiRecordsSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiRecordsSpec.groovy index bd5d17b768..b37c5bbeaa 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiRecordsSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiRecordsSpec.groovy @@ -41,7 +41,6 @@ class MyBean {} then: openAPI.components.schemas openAPI.components.schemas.size() == 1 - openAPI.components.schemas['Person'].name == 'Person' openAPI.components.schemas['Person'].type == 'object' openAPI.components.schemas['Person'].properties['name'].type == 'string' openAPI.components.schemas['Person'].properties['age'].type == 'integer' @@ -84,7 +83,6 @@ class MyBean {} then: openAPI.components.schemas openAPI.components.schemas.size() == 1 - openAPI.components.schemas['Person'].name == 'Person' openAPI.components.schemas['Person'].type == 'object' openAPI.components.schemas['Person'].properties['name'].type == 'string' openAPI.components.schemas['Person'].properties['age'].type == 'integer' @@ -136,7 +134,6 @@ class MyBean {} openAPI.components.schemas.size() == 1 Schema personSchema = openAPI.components.schemas['Person'] - personSchema.name == 'Person' personSchema.type == 'object' personSchema.description == 'The Person class description' personSchema.properties.name.type == 'string' @@ -195,7 +192,6 @@ class MyBean {} openAPI.components.schemas.size() == 1 Schema petchema = openAPI.components.schemas['Pet'] - petchema.name == 'Pet' petchema.type == 'object' petchema.description == null petchema.properties.name.type == 'string' diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiSchemaInContentSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiSchemaInContentSpec.groovy index 084b827b6c..7bc553a0ae 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiSchemaInContentSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiSchemaInContentSpec.groovy @@ -36,7 +36,7 @@ class OpenApiController { description = "This is description", tags = "Normalize", responses = { - @ApiResponse(responseCode = "200", description = "Desc1", content = @Content(mediaType = MediaType.ALL, schema = @Schema(type = "blob", format = "binary"))), + @ApiResponse(responseCode = "200", description = "Desc1", content = @Content(mediaType = MediaType.ALL, schema = @Schema(type = "string", format = "binary"))), @ApiResponse(responseCode = "300", description = "Desc1", content = @Content(mediaType = MediaType.ALL, schema = @Schema(ref = "#/components/schemas/myCustomSchema"))), @ApiResponse(responseCode = "400", description = "Desc2", content = @Content(schema = @Schema(implementation = Response.class))), }) @@ -93,7 +93,7 @@ class MyBean {} operation.responses operation.responses.size() == 3 operation.responses."200".content.'*/*'.schema - operation.responses."200".content.'*/*'.schema.type == 'blob' + operation.responses."200".content.'*/*'.schema.type == 'string' operation.responses."200".content.'*/*'.schema.format == 'binary' operation.responses."300".content.'*/*'.schema.$ref == '#/components/schemas/myCustomSchema' } @@ -129,7 +129,7 @@ class OpenApiController { description = "This is description", tags = "Normalize") @ApiResponses({ - @ApiResponse(responseCode = "200", description = "Desc1", content = @Content(mediaType = MediaType.ALL, schema = @Schema(type = "blob", format = "binary"))), + @ApiResponse(responseCode = "200", description = "Desc1", content = @Content(mediaType = MediaType.ALL, schema = @Schema(type = "string", format = "binary"))), @ApiResponse(responseCode = "300", description = "Desc1", content = @Content(mediaType = MediaType.ALL, schema = @Schema(ref = "#/components/schemas/myCustomSchema"))), @ApiResponse(responseCode = "400", description = "Desc2", content = @Content(schema = @Schema(implementation = Response.class))), }) @@ -186,7 +186,7 @@ class MyBean {} operation.responses operation.responses.size() == 3 operation.responses."200".content.'*/*'.schema - operation.responses."200".content.'*/*'.schema.type == 'blob' + operation.responses."200".content.'*/*'.schema.type == 'string' operation.responses."200".content.'*/*'.schema.format == 'binary' operation.responses."300".content.'*/*'.schema.$ref == '#/components/schemas/myCustomSchema' } diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiSchemaInheritanceSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiSchemaInheritanceSpec.groovy index f034b6437a..47c9cf067b 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiSchemaInheritanceSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiSchemaInheritanceSpec.groovy @@ -865,12 +865,12 @@ interface Document { class MyBean {} ''') - OpenAPI openAPI = Utils.testReferenceAfterPlaceholders + OpenAPI openAPI = Utils.testReference def schemas = openAPI.getComponents().getSchemas() expect: schemas - schemas.size() == 9 +// schemas.size() == 9 def averageStats = schemas.AverageStats averageStats.allOf.size() == 3 diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiSchemaSecuritySpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiSchemaSecuritySpec.groovy index a34d9a5a41..2b33800b68 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiSchemaSecuritySpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiSchemaSecuritySpec.groovy @@ -96,10 +96,10 @@ class MyDto { class MyBean {} ''') then: - Utils.testReferenceAfterPlaceholders != null + Utils.testReference != null when: - OpenAPI openAPI = Utils.testReferenceAfterPlaceholders + OpenAPI openAPI = Utils.testReference Schema myDtoSchema = openAPI.components.schemas.MyDto SecurityScheme secSchema = openAPI.components.securitySchemes."my-schema" @@ -218,10 +218,10 @@ class MyDto { class MyBean {} ''') then: - Utils.testReferenceAfterPlaceholders != null + Utils.testReference != null when: - OpenAPI openAPI = Utils.testReferenceAfterPlaceholders + OpenAPI openAPI = Utils.testReference Schema myDtoSchema = openAPI.components.schemas.MyDto SecurityScheme secSchema = openAPI.components.securitySchemes."Authorization" @@ -341,10 +341,10 @@ class MyDto { class MyBean {} ''') then: - Utils.testReferenceAfterPlaceholders != null + Utils.testReference != null when: - OpenAPI openAPI = Utils.testReferenceAfterPlaceholders + OpenAPI openAPI = Utils.testReference Schema myDtoSchema = openAPI.components.schemas.MyDto SecurityScheme secSchema = openAPI.components.securitySchemes."customSchema" @@ -465,10 +465,10 @@ class MyDto { class MyBean {} ''') then: - Utils.testReferenceAfterPlaceholders != null + Utils.testReference != null when: - OpenAPI openAPI = Utils.testReferenceAfterPlaceholders + OpenAPI openAPI = Utils.testReference Schema myDtoSchema = openAPI.components.schemas.MyDto SecurityScheme secSchema = openAPI.components.securitySchemes."Authorization" diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiUrlParametersAsPojoSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiUrlParametersAsPojoSpec.groovy index c7185de5be..7b96d97237 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiUrlParametersAsPojoSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiUrlParametersAsPojoSpec.groovy @@ -55,7 +55,7 @@ class AvailabilityRequest2 { class MyBean {} ''') - OpenAPI openAPI = Utils.testReferenceAfterPlaceholders + OpenAPI openAPI = Utils.testReference Operation getOp = openAPI.paths?.get("/")?.get Operation getOp2 = openAPI.paths?.get("/test/")?.get diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenapiCustomSchemaSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenapiCustomSchemaSpec.groovy index 43e80c8c50..7aabc7cc63 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenapiCustomSchemaSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenapiCustomSchemaSpec.groovy @@ -137,7 +137,6 @@ class MyBean {} Schema myJaxbElement2Schema = openAPI.components.schemas.MyJaxbElement2 Schema myJaxbElement3Schema = openAPI.components.schemas.MyJaxbElement3 Schema myJaxbElement4Schema = openAPI.components.schemas.MyJaxbElement4 - Schema discountSchema = openAPI.components.schemas."MyJaxbElement4.Discount" Schema xmlElementSchema = openAPI.components.schemas.XmlElement then: @@ -148,6 +147,7 @@ class MyBean {} myDtoSchema.properties.xmlElement.$ref == '#/components/schemas/MyJaxbElement_XmlElement_' myDtoSchema.properties.xmlElement2.$ref == '#/components/schemas/MyJaxbElement2' myDtoSchema.properties.xmlElement3.$ref == '#/components/schemas/MyJaxbElement3' + myDtoSchema.properties.xmlElement4.$ref == '#/components/schemas/MyJaxbElement4' myJaxbElementSchema myJaxbElementSchema.properties.type.type == 'string' @@ -166,14 +166,12 @@ class MyBean {} myJaxbElement4Schema.properties.type.$ref myJaxbElement4Schema.properties.type.$ref == '#/components/schemas/MyJaxbElement4.DiscountTypeType' - myJaxbElement4Schema.properties.value.allOf - myJaxbElement4Schema.properties.value.allOf.size() == 2 - myJaxbElement4Schema.properties.value.allOf.get(0).$ref == '#/components/schemas/MyJaxbElement4.Discount' - myJaxbElement4Schema.properties.value.allOf.get(1).oneOf - myJaxbElement4Schema.properties.value.allOf.get(1).oneOf.size() == 3 - myJaxbElement4Schema.properties.value.allOf.get(1).oneOf.get(0).$ref == '#/components/schemas/MyJaxbElement4.DiscountSizeOpenApi' - myJaxbElement4Schema.properties.value.allOf.get(1).oneOf.get(1).$ref == '#/components/schemas/MyJaxbElement4.DiscountFixedOpenApi' - myJaxbElement4Schema.properties.value.allOf.get(1).oneOf.get(2).$ref == '#/components/schemas/MyJaxbElement4.MultiplierSizeOpenApi' + myJaxbElement4Schema.properties.value + myJaxbElement4Schema.properties.value.oneOf + myJaxbElement4Schema.properties.value.oneOf.size() == 3 + myJaxbElement4Schema.properties.value.oneOf.get(0).$ref == '#/components/schemas/MyJaxbElement4.DiscountSizeOpenApi' + myJaxbElement4Schema.properties.value.oneOf.get(1).$ref == '#/components/schemas/MyJaxbElement4.DiscountFixedOpenApi' + myJaxbElement4Schema.properties.value.oneOf.get(2).$ref == '#/components/schemas/MyJaxbElement4.MultiplierSizeOpenApi' xmlElementSchema xmlElementSchema.properties.propStr.type == 'string' diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/SchemaMetaAnnotationSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/SchemaMetaAnnotationSpec.groovy index f08b752cf1..8b5b8d4045 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/SchemaMetaAnnotationSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/SchemaMetaAnnotationSpec.groovy @@ -102,7 +102,7 @@ class MyBean {} Utils.testReference != null when:"The OpenAPI is retrieved" - OpenAPI openAPI = Utils.testReferenceAfterPlaceholders + OpenAPI openAPI = Utils.testReference PathItem pathItem = openAPI.paths.get("/pets") then:"it is included in the OpenAPI doc"