From d0a5e1ea88d3018ada48fd7662b0639467d60a61 Mon Sep 17 00:00:00 2001 From: Andrew Rouse Date: Thu, 6 Jun 2024 16:58:56 +0100 Subject: [PATCH] Read PathItem and PathItemOperation annotations - New annotations PathItem and PathItemOperation - Read Components.pathItems - Read OpenAPIDefinition.webhooks --- .../openapi/api/models/PathItemImpl.java | 2 +- .../openapi/runtime/io/ComponentsIO.java | 1 + .../openapi/runtime/io/IOContext.java | 12 +-- .../smallrye/openapi/runtime/io/ModelIO.java | 8 +- .../io/smallrye/openapi/runtime/io/Names.java | 4 + .../runtime/io/OpenAPIDefinitionIO.java | 1 + .../openapi/runtime/io/PathItemIO.java | 47 +++++++++--- .../runtime/io/PathItemOperationIO.java | 76 +++++++++++++++++++ .../openapi/runtime/io/ReferenceType.java | 2 +- .../runtime/io/callbacks/CallbackIO.java | 14 +++- .../openapi/runtime/io/tags/TagIO.java | 16 ++++ 11 files changed, 158 insertions(+), 25 deletions(-) create mode 100644 core/src/main/java/io/smallrye/openapi/runtime/io/PathItemOperationIO.java diff --git a/core/src/main/java/io/smallrye/openapi/api/models/PathItemImpl.java b/core/src/main/java/io/smallrye/openapi/api/models/PathItemImpl.java index b28d51876..32cd6aa11 100644 --- a/core/src/main/java/io/smallrye/openapi/api/models/PathItemImpl.java +++ b/core/src/main/java/io/smallrye/openapi/api/models/PathItemImpl.java @@ -46,7 +46,7 @@ public String getRef() { @Override public void setRef(String ref) { if (ref != null && !ref.contains("/")) { - ref = ReferenceType.PATH_ITEMS.referenceOf(ref); + ref = ReferenceType.PATH_ITEM.referenceOf(ref); } this.ref = ref; } diff --git a/core/src/main/java/io/smallrye/openapi/runtime/io/ComponentsIO.java b/core/src/main/java/io/smallrye/openapi/runtime/io/ComponentsIO.java index 9a5071117..0f89853d5 100644 --- a/core/src/main/java/io/smallrye/openapi/runtime/io/ComponentsIO.java +++ b/core/src/main/java/io/smallrye/openapi/runtime/io/ComponentsIO.java @@ -43,6 +43,7 @@ public Components read(AnnotationInstance annotation) { components.setHeaders(headerIO().readMap(annotation.value(PROP_HEADERS))); components.setLinks(linkIO().readMap(annotation.value(PROP_LINKS))); components.setParameters(parameterIO().readMap(annotation.value(PROP_PARAMETERS))); + components.setPathItems(pathItemIO().readMap(annotation.value(PROP_PATH_ITEMS))); components.setRequestBodies(requestBodyIO().readMap(annotation.value(PROP_REQUEST_BODIES))); components.setResponses(apiResponseIO().readMap(annotation.value(PROP_RESPONSES))); components.setSchemas(schemaIO().readMap(annotation.value(PROP_SCHEMAS))); diff --git a/core/src/main/java/io/smallrye/openapi/runtime/io/IOContext.java b/core/src/main/java/io/smallrye/openapi/runtime/io/IOContext.java index 81c83b928..e8690aa42 100644 --- a/core/src/main/java/io/smallrye/openapi/runtime/io/IOContext.java +++ b/core/src/main/java/io/smallrye/openapi/runtime/io/IOContext.java @@ -41,8 +41,8 @@ public class IOContext { private OpenAPIDefinitionIO openApiDefinitionIO = new OpenAPIDefinitionIO<>(this); private OperationIO operationIO = new OperationIO<>(this); private CallbackOperationIO callbackOperationIO = new CallbackOperationIO<>(this); - private PathItemIO pathItemIO = new PathItemIO<>(this, operationIO); - private PathItemIO pathItemCallbackIO = new PathItemIO<>(this, callbackOperationIO); + private PathItemOperationIO pathItemOperationIO = new PathItemOperationIO<>(this); + private PathItemIO pathItemIO = new PathItemIO<>(this); private PathsIO pathsIO = new PathsIO<>(this); private CallbackIO callbackIO = new CallbackIO<>(this); private ExtensionIO extensionIO = new ExtensionIO<>(this); @@ -122,12 +122,12 @@ public OperationIO operationIO() { return operationIO; } - public PathItemIO pathItemIO() { - return pathItemIO; + public PathItemOperationIO pathItemOperationIO() { + return pathItemOperationIO; } - public PathItemIO pathItemCallbackIO() { - return pathItemCallbackIO; + public PathItemIO pathItemIO() { + return pathItemIO; } public PathsIO pathsIO() { diff --git a/core/src/main/java/io/smallrye/openapi/runtime/io/ModelIO.java b/core/src/main/java/io/smallrye/openapi/runtime/io/ModelIO.java index 1d101ddb4..b311a86aa 100644 --- a/core/src/main/java/io/smallrye/openapi/runtime/io/ModelIO.java +++ b/core/src/main/java/io/smallrye/openapi/runtime/io/ModelIO.java @@ -198,12 +198,12 @@ public OperationIO operationIO() { return context.operationIO(); } - public PathItemIO pathItemIO() { - return context.pathItemIO(); + public PathItemOperationIO pathItemOperationIO() { + return context.pathItemOperationIO(); } - public PathItemIO pathItemCallbackIO() { - return context.pathItemCallbackIO(); + public PathItemIO pathItemIO() { + return context.pathItemIO(); } public PathsIO pathsIO() { diff --git a/core/src/main/java/io/smallrye/openapi/runtime/io/Names.java b/core/src/main/java/io/smallrye/openapi/runtime/io/Names.java index c17591fbe..005ecf5ea 100644 --- a/core/src/main/java/io/smallrye/openapi/runtime/io/Names.java +++ b/core/src/main/java/io/smallrye/openapi/runtime/io/Names.java @@ -7,6 +7,8 @@ import org.eclipse.microprofile.openapi.annotations.ExternalDocumentation; import org.eclipse.microprofile.openapi.annotations.OpenAPIDefinition; import org.eclipse.microprofile.openapi.annotations.Operation; +import org.eclipse.microprofile.openapi.annotations.PathItem; +import org.eclipse.microprofile.openapi.annotations.PathItemOperation; import org.eclipse.microprofile.openapi.annotations.callbacks.Callback; import org.eclipse.microprofile.openapi.annotations.callbacks.CallbackOperation; import org.eclipse.microprofile.openapi.annotations.callbacks.Callbacks; @@ -88,6 +90,8 @@ public static DotName containerOf(DotName repeatable) { public static final DotName OPERATION = create(Operation.class); public static final DotName PARAMETER = create(Parameter.class); public static final DotName PARAMETERS = create(Parameters.class); + public static final DotName PATH_ITEM = create(PathItem.class); + public static final DotName PATH_ITEM_OPERATION = create(PathItemOperation.class); public static final DotName REQUEST_BODY = create(RequestBody.class); public static final DotName REQUEST_BODY_SCHEMA = create(RequestBodySchema.class); public static final DotName SCHEMA = create(Schema.class); diff --git a/core/src/main/java/io/smallrye/openapi/runtime/io/OpenAPIDefinitionIO.java b/core/src/main/java/io/smallrye/openapi/runtime/io/OpenAPIDefinitionIO.java index ff002e10f..02bec4707 100644 --- a/core/src/main/java/io/smallrye/openapi/runtime/io/OpenAPIDefinitionIO.java +++ b/core/src/main/java/io/smallrye/openapi/runtime/io/OpenAPIDefinitionIO.java @@ -37,6 +37,7 @@ public OpenAPI read(AnnotationInstance annotation) { openApi.setSecurity( securityIO().readRequirements(annotation.value(PROP_SECURITY), annotation.value(PROP_SECURITY_SETS))); openApi.setExternalDocs(extDocIO().read(annotation.value(PROP_EXTERNAL_DOCS))); + openApi.setWebhooks(pathItemIO().readMap(annotation.value(PROP_WEBHOOKS))); openApi.setComponents(componentsIO().read(annotation.value(PROP_COMPONENTS))); openApi.setExtensions(extensionIO().readExtensible(annotation)); diff --git a/core/src/main/java/io/smallrye/openapi/runtime/io/PathItemIO.java b/core/src/main/java/io/smallrye/openapi/runtime/io/PathItemIO.java index 093f0f929..a6dba2153 100644 --- a/core/src/main/java/io/smallrye/openapi/runtime/io/PathItemIO.java +++ b/core/src/main/java/io/smallrye/openapi/runtime/io/PathItemIO.java @@ -7,10 +7,12 @@ import java.util.Set; import java.util.stream.Collectors; +import org.eclipse.microprofile.openapi.annotations.callbacks.CallbackOperation; import org.eclipse.microprofile.openapi.models.Operation; import org.eclipse.microprofile.openapi.models.PathItem; import org.eclipse.microprofile.openapi.models.PathItem.HttpMethod; import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationValue; import io.smallrye.openapi.api.models.PathItemImpl; @@ -27,21 +29,48 @@ public class PathItemIO extends MapModelIO< .map(String::toLowerCase) .collect(Collectors.toSet()); - private final OperationIO operationIO; + // Annotation properties + private static final String PROP_OPERATIONS = "operations"; - public PathItemIO(IOContext context, OperationIO operationIO) { - super(context, null, Names.create(PathItem.class)); - this.operationIO = operationIO; + public PathItemIO(IOContext context) { + super(context, Names.PATH_ITEM, Names.create(PathItem.class)); } @Override public PathItem read(AnnotationInstance annotation) { - throw new UnsupportedOperationException("@PathItem annotation does not exist"); + IoLogging.logger.singleAnnotation("@PathItem"); + PathItem pathItem = new PathItemImpl(); + + pathItem.setRef(ReferenceType.PATH_ITEM.refValue(annotation)); + pathItem.setDescription(value(annotation, PROP_DESCRIPTION)); + pathItem.setSummary(value(annotation, PROP_SUMMARY)); + pathItem.setServers(serverIO().readList(annotation.value(PROP_SERVERS))); + pathItem.setParameters(parameterIO().readList(annotation.value(PROP_PARAMETERS))); + + Optional.ofNullable(annotation.value(PROP_OPERATIONS)) + .map(AnnotationValue::asNestedArray) + .ifPresent(annotations -> readOperationsInto(pathItem, annotations, pathItemOperationIO())); + + pathItem.setExtensions(extensionIO().readExtensible(annotation)); + return pathItem; } - public PathItem read(AnnotationInstance[] annotations) { + /** + * Convert an array of {@link CallbackOperation} annotations into a {@code PathItem}. + * + * @param annotations the {@code CallbackOperation} annotation instances + * @return the path item + */ + public PathItem readCallbackOperations(AnnotationInstance[] annotations) { PathItem pathItem = new PathItemImpl(); + readOperationsInto(pathItem, annotations, callbackOperationIO()); + + return pathItem; + } + + private void readOperationsInto(PathItem pathItem, AnnotationInstance[] annotations, + OperationIO operationIO) { Arrays.stream(annotations) .filter(annotation -> Objects.nonNull(value(annotation, "method"))) .forEach(annotation -> { @@ -50,8 +79,6 @@ public PathItem read(AnnotationInstance[] annotations) { operation.setExtensions(extensionIO().readExtensible(annotation)); pathItem.setOperation(HttpMethod.valueOf(method.toUpperCase(Locale.ROOT)), operation); }); - - return pathItem; } @Override @@ -67,7 +94,7 @@ public PathItem readObject(O node) { .filter(entry -> OPERATION_PROPS.contains(entry.getKey())) .forEach(entry -> { HttpMethod method = HttpMethod.valueOf(entry.getKey().toUpperCase(Locale.ROOT)); - Operation operation = operationIO.readValue(entry.getValue()); + Operation operation = operationIO().readValue(entry.getValue()); pathItem.setOperation(method, operation); }); @@ -90,7 +117,7 @@ private OB write(PathItem model, OB node) { model.getOperations() .forEach( - (method, operation) -> setIfPresent(node, method.name().toLowerCase(), operationIO.write(operation))); + (method, operation) -> setIfPresent(node, method.name().toLowerCase(), operationIO().write(operation))); setIfPresent(node, PROP_PARAMETERS, parameterIO().write(model.getParameters())); setIfPresent(node, PROP_SERVERS, serverIO().write(model.getServers())); diff --git a/core/src/main/java/io/smallrye/openapi/runtime/io/PathItemOperationIO.java b/core/src/main/java/io/smallrye/openapi/runtime/io/PathItemOperationIO.java new file mode 100644 index 000000000..ae413a761 --- /dev/null +++ b/core/src/main/java/io/smallrye/openapi/runtime/io/PathItemOperationIO.java @@ -0,0 +1,76 @@ +package io.smallrye.openapi.runtime.io; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +import org.eclipse.microprofile.openapi.models.Operation; +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationValue; + +import io.smallrye.openapi.api.models.OperationImpl; +import io.smallrye.openapi.runtime.util.ModelUtil; + +public class PathItemOperationIO extends OperationIO { + + public PathItemOperationIO(IOContext context) { + super(context, Names.PATH_ITEM_OPERATION); + } + + @Override + public Operation read(AnnotationInstance annotation) { + IoLogging.logger.singleAnnotation("@PathItemOperation"); + Operation operation = new OperationImpl(); + + operation.setTags(processTags(annotation.value(PROP_TAGS))); + operation.setSummary(value(annotation, PROP_SUMMARY)); + operation.setDescription(value(annotation, PROP_DESCRIPTION)); + operation.setExternalDocs(extDocIO().read(annotation.value(PROP_EXTERNAL_DOCS))); + operation.setOperationId(value(annotation, PROP_OPERATION_ID)); + operation.setParameters(parameterIO().readList(annotation.value(PROP_PARAMETERS))); + operation.setRequestBody(requestBodyIO().read(annotation.value(PROP_REQUEST_BODY))); + operation.setResponses(apiResponsesIO().read(annotation.value(PROP_RESPONSES))); + operation.setCallbacks(callbackIO().readMap(annotation.value(PROP_CALLBACKS))); + operation.setDeprecated(value(annotation, PROP_DEPRECATED)); + operation.setSecurity(securityIO().readRequirements( + annotation.value(PROP_SECURITY), + annotation.value(PROP_SECURITY_SETS))); + operation.setServers(serverIO().readList(annotation.value(PROP_SERVERS))); + operation.setExtensions(extensionIO().readExtensible(annotation)); + + return operation; + } + + private List processTags(AnnotationValue tagAnnotations) { + return Optional.ofNullable(tagAnnotations) + .map(AnnotationValue::asNestedArray) + .map(this::processTags) + .orElse(null); + } + + /** + * Read an array of {@code Tag} annotations, collecting the names and adding any new definitions to the top-level OpenAPI + * object. + * + * @param tagAnnotations the annotations + * @return the list of tag names + */ + private List processTags(AnnotationInstance[] tagAnnotations) { + Set tagNames = new LinkedHashSet<>(); + + tagIO().readList(tagAnnotations) + .stream() + .filter(tag -> Objects.nonNull(tag.getName())) + .forEach(tag -> { + tagNames.add(tag.getName()); + ModelUtil.addTag(scannerContext().getOpenApi(), tag); + }); + + tagIO().readReferences(tagAnnotations).forEach(tagNames::add); + + return new ArrayList<>(tagNames); + } +} diff --git a/core/src/main/java/io/smallrye/openapi/runtime/io/ReferenceType.java b/core/src/main/java/io/smallrye/openapi/runtime/io/ReferenceType.java index f982ce0fa..b2956babf 100644 --- a/core/src/main/java/io/smallrye/openapi/runtime/io/ReferenceType.java +++ b/core/src/main/java/io/smallrye/openapi/runtime/io/ReferenceType.java @@ -21,7 +21,7 @@ public enum ReferenceType { PARAMETER("parameters"), EXAMPLE("examples"), REQUEST_BODY("requestBodies"), - PATH_ITEMS("pathItems"); + PATH_ITEM("pathItems"); private static final Pattern COMPONENT_KEY_PATTERN = Pattern.compile("^[a-zA-Z0-9\\.\\-_]+$"); public static final String PROP_ANNOTATION = "ref"; diff --git a/core/src/main/java/io/smallrye/openapi/runtime/io/callbacks/CallbackIO.java b/core/src/main/java/io/smallrye/openapi/runtime/io/callbacks/CallbackIO.java index c8f101aea..ad9c03295 100644 --- a/core/src/main/java/io/smallrye/openapi/runtime/io/callbacks/CallbackIO.java +++ b/core/src/main/java/io/smallrye/openapi/runtime/io/callbacks/CallbackIO.java @@ -6,6 +6,7 @@ import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.DotName; +import io.smallrye.openapi.api.models.PathItemImpl; import io.smallrye.openapi.api.models.callbacks.CallbackImpl; import io.smallrye.openapi.runtime.io.IOContext; import io.smallrye.openapi.runtime.io.IoLogging; @@ -20,6 +21,7 @@ public class CallbackIO extends MapModelIO< private static final String PROP_OPERATIONS = "operations"; private static final String PROP_CALLBACK_URL_EXPRESSION = "callbackUrlExpression"; + private static final String PROP_PATH_ITEM_REF = "pathItemRef"; public CallbackIO(IOContext context) { super(context, Names.CALLBACK, DotName.createSimple(Callback.class)); @@ -31,9 +33,15 @@ public Callback read(AnnotationInstance annotation) { Callback callback = new CallbackImpl(); callback.setRef(ReferenceType.CALLBACK.refValue(annotation)); + Optional.ofNullable(this. value(annotation, PROP_PATH_ITEM_REF)) + .map(ReferenceType.PATH_ITEM::referenceOf) + .ifPresent(ref -> callback.addPathItem( + value(annotation, PROP_CALLBACK_URL_EXPRESSION), + new PathItemImpl().ref(ref))); + Optional.ofNullable(value(annotation, PROP_OPERATIONS)) .map(AnnotationInstance[].class::cast) - .map(pathItemCallbackIO()::read) + .map(pathItemIO()::readCallbackOperations) .ifPresent(pathItem -> callback.addPathItem( value(annotation, PROP_CALLBACK_URL_EXPRESSION), pathItem)); @@ -53,7 +61,7 @@ public Callback readObject(O node) { .filter(not(ExtensionIO::isExtension)) .filter(not(this::isReference)) .filter(property -> jsonIO().isObject(property.getValue())) - .map(property -> entry(property.getKey(), pathItemCallbackIO().readValue(property.getValue()))) + .map(property -> entry(property.getKey(), pathItemIO().readValue(property.getValue()))) .forEach(pathItem -> callback.addPathItem(pathItem.getKey(), pathItem.getValue())); extensionIO().readMap(node).forEach(callback::addExtension); @@ -69,7 +77,7 @@ public Optional write(Callback model) { } else { Optional.ofNullable(model.getPathItems()) .ifPresent(items -> items - .forEach((key, value) -> setIfPresent(node, key, pathItemCallbackIO().write(value)))); + .forEach((key, value) -> setIfPresent(node, key, pathItemIO().write(value)))); setAllIfPresent(node, extensionIO().write(model)); } diff --git a/core/src/main/java/io/smallrye/openapi/runtime/io/tags/TagIO.java b/core/src/main/java/io/smallrye/openapi/runtime/io/tags/TagIO.java index fd8b4888d..0a063fe6b 100644 --- a/core/src/main/java/io/smallrye/openapi/runtime/io/tags/TagIO.java +++ b/core/src/main/java/io/smallrye/openapi/runtime/io/tags/TagIO.java @@ -1,5 +1,7 @@ package io.smallrye.openapi.runtime.io.tags; +import static java.util.stream.Collectors.toList; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -47,6 +49,20 @@ public List readReferences(AnnotationTarget target) { return Stream.concat(tagsRefs, tagRefs).collect(Collectors.toList()); } + public List readReferences(AnnotationInstance[] annotations) { + return Optional.ofNullable(annotations) + .map(Arrays::asList) + .map(this::readReferences) + .orElse(null); + } + + public List readReferences(Collection annotations) { + return annotations.stream() + .map(a -> this. value(a, PROP_REF)) + .filter(Objects::nonNull) + .collect(toList()); + } + public List readList(AnnotationTarget target) { return readList(getRepeatableAnnotations(target)); }