diff --git a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/Constants.java b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/Constants.java index ecea70098..a9fa6ba08 100644 --- a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/Constants.java +++ b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/Constants.java @@ -336,4 +336,12 @@ public String toString() { public static final String JSON_EXTENSION = ".json"; public static final String YML_EXTENSION = ".yml"; public static final String UNDERSCORE = "_"; + + //openapi:ResourceInFo annotation + public static final String OPENAPI_RESOURCE_INFO = "openapi:ResourceInfo"; + public static final String TAGS = "tags"; + public static final String SUMMARY = "summary"; + public static final String EXAMPLES = "examples"; + public static final String OPERATION_ID = "operationId"; + public static final String RESPONSE_ATTRIBUTE = "response"; } diff --git a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/ServiceMapperFactory.java b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/ServiceMapperFactory.java index f411c7648..eab5cfcdc 100644 --- a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/ServiceMapperFactory.java +++ b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/ServiceMapperFactory.java @@ -36,6 +36,8 @@ import io.ballerina.openapi.service.mapper.interceptor.model.RequestParameterInfo; import io.ballerina.openapi.service.mapper.interceptor.model.ResponseInfo; import io.ballerina.openapi.service.mapper.interceptor.pipeline.InterceptorPipeline; +import io.ballerina.openapi.service.mapper.metainfo.MetaInfoMapper; +import io.ballerina.openapi.service.mapper.metainfo.MetaInfoMapperImpl; import io.ballerina.openapi.service.mapper.model.AdditionalData; import io.ballerina.openapi.service.mapper.model.ModuleMemberVisitor; import io.ballerina.openapi.service.mapper.model.OperationInventory; @@ -81,6 +83,7 @@ public class ServiceMapperFactory { private final ConstraintMapper constraintMapper; private final HateoasMapper hateoasMapper; private final InterceptorPipeline interceptorPipeline; + private final MetaInfoMapper metaInfoMapper; public ServiceMapperFactory(OpenAPI openAPI, SemanticModel semanticModel, ModuleMemberVisitor moduleMemberVisitor, List diagnostics, ServiceDeclarationNode serviceDefinition) { @@ -92,6 +95,7 @@ public ServiceMapperFactory(OpenAPI openAPI, SemanticModel semanticModel, Module this.typeMapper = new TypeMapperImpl(getComponents(openAPI), additionalData); this.constraintMapper = new ConstraintMapperImpl(openAPI, moduleMemberVisitor, diagnostics); this.hateoasMapper = new HateoasMapperImpl(); + this.metaInfoMapper = new MetaInfoMapperImpl(); } public ServersMapper getServersMapper(Set endpoints, ServiceDeclarationNode serviceNode) { @@ -162,6 +166,11 @@ public HateoasMapper getHateoasMapper() { return hateoasMapper; } + + public MetaInfoMapper getMetaInfoMapper() { + return metaInfoMapper; + } + private Components getComponents(OpenAPI openAPI) { Components components = openAPI.getComponents(); if (Objects.isNull(components)) { diff --git a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/ServiceToOpenAPIMapper.java b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/ServiceToOpenAPIMapper.java index f7e692289..d55cf2192 100644 --- a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/ServiceToOpenAPIMapper.java +++ b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/ServiceToOpenAPIMapper.java @@ -33,6 +33,7 @@ import io.ballerina.openapi.service.mapper.diagnostic.ExceptionDiagnostic; import io.ballerina.openapi.service.mapper.diagnostic.OpenAPIMapperDiagnostic; import io.ballerina.openapi.service.mapper.hateoas.HateoasMapper; +import io.ballerina.openapi.service.mapper.metainfo.MetaInfoMapper; import io.ballerina.openapi.service.mapper.model.ModuleMemberVisitor; import io.ballerina.openapi.service.mapper.model.OASGenerationMetaInfo; import io.ballerina.openapi.service.mapper.model.OASResult; @@ -206,6 +207,9 @@ public static OASResult generateOAS(OASGenerationMetaInfo oasGenerationMetaInfo) HateoasMapper hateoasMapper = serviceMapperFactory.getHateoasMapper(); hateoasMapper.setOpenApiLinks(serviceDefinition, openapi); + MetaInfoMapper metaInfoMapper = serviceMapperFactory.getMetaInfoMapper(); + metaInfoMapper.setResourceMetaData(openapi, serviceDefinition); + if (openapi.getComponents().getSchemas().isEmpty()) { openapi.setComponents(null); } diff --git a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/metainfo/MetaInfoMapper.java b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/metainfo/MetaInfoMapper.java new file mode 100644 index 000000000..0a874a2c3 --- /dev/null +++ b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/metainfo/MetaInfoMapper.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.openapi.service.mapper.metainfo; + +import io.ballerina.compiler.syntax.tree.ServiceDeclarationNode; +import io.swagger.v3.oas.models.OpenAPI; + +/** + * Interface for Meta information mapper. + * + * @since 2.0.1 + */ +public interface MetaInfoMapper { + void setResourceMetaData(OpenAPI openAPI, ServiceDeclarationNode serviceNode); +} diff --git a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/metainfo/MetaInfoMapperImpl.java b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/metainfo/MetaInfoMapperImpl.java new file mode 100644 index 000000000..e34632960 --- /dev/null +++ b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/metainfo/MetaInfoMapperImpl.java @@ -0,0 +1,319 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.openapi.service.mapper.metainfo; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.ballerina.compiler.syntax.tree.AnnotationNode; +import io.ballerina.compiler.syntax.tree.BasicLiteralNode; +import io.ballerina.compiler.syntax.tree.ExpressionNode; +import io.ballerina.compiler.syntax.tree.FunctionDefinitionNode; +import io.ballerina.compiler.syntax.tree.ListConstructorExpressionNode; +import io.ballerina.compiler.syntax.tree.MappingConstructorExpressionNode; +import io.ballerina.compiler.syntax.tree.MappingFieldNode; +import io.ballerina.compiler.syntax.tree.MetadataNode; +import io.ballerina.compiler.syntax.tree.Node; +import io.ballerina.compiler.syntax.tree.NodeList; +import io.ballerina.compiler.syntax.tree.QualifiedNameReferenceNode; +import io.ballerina.compiler.syntax.tree.SeparatedNodeList; +import io.ballerina.compiler.syntax.tree.ServiceDeclarationNode; +import io.ballerina.compiler.syntax.tree.SpecificFieldNode; +import io.ballerina.compiler.syntax.tree.SyntaxKind; +import io.ballerina.compiler.syntax.tree.Token; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.Paths; +import io.swagger.v3.oas.models.examples.Example; +import io.swagger.v3.oas.models.media.Content; +import io.swagger.v3.oas.models.media.MediaType; +import io.swagger.v3.oas.models.responses.ApiResponse; +import io.swagger.v3.oas.models.responses.ApiResponses; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import static io.ballerina.openapi.service.mapper.Constants.EXAMPLES; +import static io.ballerina.openapi.service.mapper.Constants.OPENAPI_RESOURCE_INFO; +import static io.ballerina.openapi.service.mapper.Constants.OPERATION_ID; +import static io.ballerina.openapi.service.mapper.Constants.RESPONSE_ATTRIBUTE; +import static io.ballerina.openapi.service.mapper.Constants.SUMMARY; +import static io.ballerina.openapi.service.mapper.Constants.TAGS; +import static io.ballerina.openapi.service.mapper.utils.MapperCommonUtils.getOperationId; + +/** + * This class is for updating meta details into openAPI spec. + * + * @since 2.0.1 + */ +public class MetaInfoMapperImpl implements MetaInfoMapper { + + public void setResourceMetaData(OpenAPI openAPI, ServiceDeclarationNode serviceNode) { + NodeList functions = serviceNode.members(); + Map resourceMetaData = new HashMap<>(); + for (Node function : functions) { + SyntaxKind kind = function.kind(); + if (kind.equals(SyntaxKind.RESOURCE_ACCESSOR_DEFINITION)) { + FunctionDefinitionNode resourceNode = (FunctionDefinitionNode) function; + Optional optMetadata = resourceNode.metadata(); + if (optMetadata.isEmpty()) { + continue; + } + String operationId = getOperationId(resourceNode); + ResourceMetaInfoAnnotation.Builder resMetaInfoBuilder = new ResourceMetaInfoAnnotation.Builder(); + MetadataNode metadataNode = optMetadata.get(); + NodeList annotations = metadataNode.annotations(); + //check annotation + for (AnnotationNode annotation : annotations) { + if (annotation.annotReference().kind() == SyntaxKind.QUALIFIED_NAME_REFERENCE) { + QualifiedNameReferenceNode ref = (QualifiedNameReferenceNode) annotation.annotReference(); + String annotationName = ref.modulePrefix().text() + ":" + ref.identifier().text(); + if (annotationName.equals(OPENAPI_RESOURCE_INFO)) { + Optional optExpressionNode = annotation.annotValue(); + if (optExpressionNode.isEmpty()) { + continue; + } + MappingConstructorExpressionNode mappingConstructorExpressionNode = optExpressionNode.get(); + SeparatedNodeList fields = mappingConstructorExpressionNode.fields(); + for (MappingFieldNode field : fields) { + String fieldName = ((SpecificFieldNode) field).fieldName().toString().trim(); + Optional value = ((SpecificFieldNode) field).valueExpr(); + String fieldValue; + if (value.isEmpty()) { + continue; + } + ExpressionNode expressionNode = value.get(); + if (expressionNode.toString().trim().isBlank()) { + continue; + } + fieldValue = expressionNode.toString().trim().replaceAll("\"", ""); + switch (fieldName) { + case OPERATION_ID -> resMetaInfoBuilder.operationId(fieldValue); + case SUMMARY -> resMetaInfoBuilder.summary(fieldValue); + case TAGS -> { + if (expressionNode instanceof ListConstructorExpressionNode listNode) { + List values = extractListItems(listNode); + resMetaInfoBuilder.tags(values); + } + } + case EXAMPLES -> handleExamples(resMetaInfoBuilder, expressionNode); + default -> { } + } + } + } + } + } + resourceMetaData.put(operationId, resMetaInfoBuilder.build()); + } + } + + Paths paths = openAPI.getPaths(); + updateOASWithMetaData(resourceMetaData, paths); + } + + private static void handleExamples(ResourceMetaInfoAnnotation.Builder resMetaInfoBuilder, + ExpressionNode expressionNode) { + if (expressionNode instanceof MappingConstructorExpressionNode mapNode) { + SeparatedNodeList fields1 = mapNode.fields(); + for (MappingFieldNode resultField : fields1) { + //parse as json object + SpecificFieldNode resultField1 = (SpecificFieldNode) resultField; + String fName = resultField1.fieldName().toSourceCode().trim().replaceAll("\"", ""); + if (fName.equals(RESPONSE_ATTRIBUTE)) { + Optional optExamplesValue = resultField1.valueExpr(); + if (optExamplesValue.isEmpty()) { + continue; + } + ExpressionNode expressValue = optExamplesValue.get(); + String sourceCode = expressValue.toSourceCode(); + ObjectMapper objectMapper = new ObjectMapper(); + try { + Map map = objectMapper.readValue(sourceCode, Map.class); + // Handle response + if (map instanceof LinkedHashMap responseSet) { + //>> + Map>> responseExamples = new HashMap<>(); + for (Map.Entry statusCodeValuePair : responseSet.entrySet()) { + Object key = statusCodeValuePair.getKey(); + if (!(key instanceof String)) { + continue; + } + String statusCode = key.toString().trim(); + Object valuePairValue = statusCodeValuePair.getValue(); + if (valuePairValue instanceof LinkedHashMap responseMap) { + Set> entries = responseMap.entrySet(); + for (Map.Entry entry : entries) { + if (entry.getKey().equals("examples")) { + extractResponseExamples(responseExamples, statusCode, entry); + } + //todo: headers + } + } + } + resMetaInfoBuilder.responseExamples(responseExamples); + } + //todo: request body, parameters + } catch (JsonProcessingException e) { + //ignore; + //todo will handle this with future design + } + } + } + } + } + + private static void extractResponseExamples(Map>> responseExamples, + String statusCode, Map.Entry entry) { + Object exampleValues = entry.getValue(); + if (exampleValues instanceof LinkedHashMap exampleValueMap) { + Set> sets = exampleValueMap.entrySet(); + //> + Map> mediaTypeExampleMap = new HashMap<>(); + + for (Map.Entry valuePair : sets) { + String mediaType = valuePair.getKey().toString(); + Object responesExamples = valuePair.getValue(); + if (responesExamples instanceof LinkedHashMap resExampleMaps) { + Map examples = (Map) resExampleMaps; + mediaTypeExampleMap.put(mediaType, examples); + } + } + responseExamples.put(statusCode, mediaTypeExampleMap); + } + } + + private static void updateOASWithMetaData(Map resourceMetaData, Paths paths) { + if (paths != null) { + paths.forEach((path, pathItem) -> { + if (pathItem.getGet() != null) { + Operation operation = pathItem.getGet(); + updateOASOperationWithMetaData(resourceMetaData, operation); + pathItem.setGet(operation); + } + if (pathItem.getPost() != null) { + Operation operation = pathItem.getPost(); + updateOASOperationWithMetaData(resourceMetaData, operation); + pathItem.setPost(operation); + } + if (pathItem.getPut() != null) { + Operation operation = pathItem.getPut(); + updateOASOperationWithMetaData(resourceMetaData, operation); + pathItem.setPut(operation); + } + if (pathItem.getDelete() != null) { + Operation operation = pathItem.getDelete(); + updateOASOperationWithMetaData(resourceMetaData, operation); + pathItem.setDelete(operation); + } + if (pathItem.getPatch() != null) { + Operation operation = pathItem.getPatch(); + updateOASOperationWithMetaData(resourceMetaData, operation); + pathItem.setPatch(operation); + } + if (pathItem.getOptions() != null) { + Operation operation = pathItem.getOptions(); + updateOASOperationWithMetaData(resourceMetaData, operation); + pathItem.setOptions(operation); + } + if (pathItem.getHead() != null) { + Operation operation = pathItem.getHead(); + updateOASOperationWithMetaData(resourceMetaData, operation); + pathItem.setHead(operation); + } + if (pathItem.getTrace() != null) { + Operation operation = pathItem.getTrace(); + updateOASOperationWithMetaData(resourceMetaData, operation); + pathItem.setTrace(operation); + } + }); + } + } + + private static void updateOASOperationWithMetaData(Map resourceMetaData, + Operation operation) { + String operationId = operation.getOperationId(); + if (!resourceMetaData.isEmpty() && resourceMetaData.containsKey(operationId)) { + ResourceMetaInfoAnnotation resourceMetaInfo = resourceMetaData.get(operationId); + String userProvideOperationId = resourceMetaInfo.getOperationId(); + if (userProvideOperationId != null && !userProvideOperationId.isBlank()) { + operation.setOperationId(userProvideOperationId); + } + operation.setTags(resourceMetaInfo.getTags()); + operation.setSummary(resourceMetaInfo.getSummary()); + ApiResponses responses = operation.getResponses(); + Map>> responseExamples = resourceMetaInfo.getResponseExamples(); + for (Map.Entry response : responses.entrySet()) { + String statusCode = response.getKey(); + Map> mediaTypeExampleMap = responseExamples.get(statusCode); + if (mediaTypeExampleMap == null) { + continue; + } + ApiResponse oasApiResponse = response.getValue(); + Content oasContent = oasApiResponse.getContent(); + for (Map.Entry> entry : mediaTypeExampleMap.entrySet()) { + String mediaTypeKey = entry.getKey(); + MediaType oasMediaType = oasContent.get(mediaTypeKey); + Map exampleMap = new HashMap<>(); + Map examples = entry.getValue(); + for (Map.Entry example : examples.entrySet()) { + Object value = example.getValue(); + if (value instanceof LinkedHashMap exampleValue) { + value = exampleValue.get("value"); + } + Example oasExample = new Example(); + oasExample.setValue(value); + exampleMap.put(example.getKey(), oasExample); + } + oasMediaType.setExamples(exampleMap); + oasContent.put(mediaTypeKey, oasMediaType); + } + oasApiResponse.setContent(oasContent); + responses.put(response.getKey(), oasApiResponse); + } + operation.setResponses(responses); + } + } + + private static List extractListItems(ListConstructorExpressionNode list) { + SeparatedNodeList expressions = list.expressions(); + Iterator iterator = expressions.iterator(); + List values = new ArrayList<>(); + while (iterator.hasNext()) { + Node item = iterator.next(); + if (item.kind() == SyntaxKind.STRING_LITERAL && !item.toString().isBlank()) { + Token stringItem = ((BasicLiteralNode) item).literalToken(); + String text = stringItem.text(); + // Here we need to do some preprocessing by removing '"' from the given values. + if (text.length() > 1 && text.charAt(0) == '"' && text.charAt(text.length() - 1) == '"') { + text = text.substring(1, text.length() - 1); + } else { + // Missing end quote case + text = text.substring(1); + } + values.add(text); + } + } + return values; + } +} diff --git a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/metainfo/ResourceMetaInfoAnnotation.java b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/metainfo/ResourceMetaInfoAnnotation.java new file mode 100644 index 000000000..dc3939493 --- /dev/null +++ b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/metainfo/ResourceMetaInfoAnnotation.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.openapi.service.mapper.metainfo; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Model for store resource function meta data. + * + * @since 2.0.1 + */ +public class ResourceMetaInfoAnnotation { + String summary; + String operationId; + List tags; + + //>> + Map>> responseExamples; + + private ResourceMetaInfoAnnotation(Builder builder) { + this.summary = builder.summary; + this.operationId = builder.operationId; + this.tags = builder.tags; + this.responseExamples = builder.responseExamples; + } + + public String getSummary() { + return summary; + } + + public String getOperationId() { + return operationId; + } + + public List getTags() { + return tags; + } + + public Map>> getResponseExamples() { + return responseExamples; + } + + public static class Builder { + private String summary; + private String operationId; + private List tags = new ArrayList<>(); + private Map>> responseExamples = new HashMap<>(); + + public Builder summary(String summary) { + this.summary = summary; + return this; + } + + public Builder operationId(String operationId) { + this.operationId = operationId; + return this; + } + + public Builder tags(List tags) { + this.tags = tags; + return this; + } + + public Builder responseExamples(Map>> responseExamples) { + this.responseExamples = responseExamples; + return this; + } + + public ResourceMetaInfoAnnotation build() { + return new ResourceMetaInfoAnnotation(this); + } + } +} diff --git a/module-ballerina-openapi/Ballerina.toml b/module-ballerina-openapi/Ballerina.toml index 3f7db9168..49e3f3887 100644 --- a/module-ballerina-openapi/Ballerina.toml +++ b/module-ballerina-openapi/Ballerina.toml @@ -1,10 +1,10 @@ [package] org= "ballerina" name= "openapi" -version= "1.9.0" +version= "@toml.version@" [[platform.java17.dependency]] -path = "../openapi-validator/build/libs/openapi-validator-1.9.0-SNAPSHOT.jar" +path = "../openapi-validator/build/libs/openapi-validator-@project.version@.jar" groupId = "ballerina" artifactId = "openapi" -version = "1.9.0-SNAPSHOT" +version = "@project.version@" diff --git a/module-ballerina-openapi/CompilerPlugin.toml b/module-ballerina-openapi/CompilerPlugin.toml index 0e743c2c6..c4c4205c0 100644 --- a/module-ballerina-openapi/CompilerPlugin.toml +++ b/module-ballerina-openapi/CompilerPlugin.toml @@ -3,7 +3,7 @@ id = "openapi-tools" class = "io.ballerina.openapi.validator.OpenAPIValidatorPlugin" [[dependency]] -path = "../openapi-validator/build/libs/openapi-validator-1.9.0-SNAPSHOT.jar" +path = "../openapi-validator/build/libs/openapi-validator-@project.version@.jar" groupId = "ballerina" artifactId = "openapi" -version = "1.9.0-SNAPSHOT" +version = "@project.version@." diff --git a/module-ballerina-openapi/annotation.bal b/module-ballerina-openapi/annotation.bal index 11f2c449d..9eeccc451 100644 --- a/module-ballerina-openapi/annotation.bal +++ b/module-ballerina-openapi/annotation.bal @@ -51,6 +51,44 @@ public type ServiceInformation record {| // "// This file is auto-generated by the Ballerina OpenAPI tool.\n"; // |}; +# This annotation represents a record for storing resource meta information. +# +# + summary - A brief summary of the resource. +# + tags - Tags associated with the resource. +# + operationId - Unique identifier for the operation. +# + examples - This section contains detailed examples for responses and request bodies. +public type ResourceInformation record {| + string summary?; + string[] tags?; + string operationId?; + Examples examples?; +|}; + +# Represents an example of a response for a specific status code. +# +# + headers - The headers for the response. +# + examples - Detailed examples of the response content. +public type ResponseExample record { + map headers?; + map> examples?; + +}; + +# Represents an example of a request body for a specific media type. +public type RequestExamples map; + +# Represents examples for resource function. +# +# + response - Response examples +# + requestBody - Request examples +public type Examples record {| + map response?; + RequestExamples requestBody?; +|}; + +# Annotation for additional OpenAPI information of a Ballerina resource function. +public const annotation ResourceInformation ResourceInfo on object function; + # Annotation for additional OpenAPI information of a Ballerina service. public annotation ServiceInformation ServiceInfo on service; // # Annotation for additional OpenAPI configurations of a Ballerina client.