From 377913f5c42913a956494739dda1086c6d5d317c Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 18 Sep 2024 13:00:18 +0530 Subject: [PATCH 1/8] Add name generation support from ballerina extension --- .../client/parameter/ParameterGenerator.java | 15 ++- .../generators/common/GeneratorConstants.java | 6 ++ .../generators/common/GeneratorUtils.java | 65 ++++++++++++- .../parameter/HeaderParameterGenerator.java | 18 ++-- .../parameter/QueryParameterGenerator.java | 91 +++++++++++-------- .../type/generators/ArrayTypeGenerator.java | 8 +- .../type/generators/RecordTypeGenerator.java | 29 ++++-- .../generators/ReferencedTypeGenerator.java | 4 - 8 files changed, 168 insertions(+), 68 deletions(-) diff --git a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/client/parameter/ParameterGenerator.java b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/client/parameter/ParameterGenerator.java index 364ad3480..9a55d4f41 100644 --- a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/client/parameter/ParameterGenerator.java +++ b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/client/parameter/ParameterGenerator.java @@ -23,10 +23,14 @@ import io.swagger.v3.oas.models.media.Schema; import io.swagger.v3.oas.models.parameters.Parameter; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Optional; +import static io.ballerina.openapi.core.generators.common.GeneratorConstants.X_PARAM_TYPE; + public interface ParameterGenerator { //type handler attribute Optional generateParameterNode(); @@ -37,9 +41,14 @@ default Schema getSchemaWithDetails(Parameter parameter) { if (Objects.isNull(schema)) { return null; } - schema.setDescription(parameter.getDescription()); - schema.setDeprecated(parameter.getDeprecated()); - schema.extensions(parameter.getExtensions()); + Optional.ofNullable(parameter.getDescription()).ifPresent(schema::setDescription); + Optional.ofNullable(parameter.getDeprecated()).ifPresent(schema::setDeprecated); + Map extensions = parameter.getExtensions(); + if (Objects.isNull(extensions)) { + extensions = new HashMap<>(); + } + extensions.put(X_PARAM_TYPE, parameter.getIn()); + schema.setExtensions(extensions); return schema; } } diff --git a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/GeneratorConstants.java b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/GeneratorConstants.java index ab185bf08..11b571359 100644 --- a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/GeneratorConstants.java +++ b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/GeneratorConstants.java @@ -30,6 +30,9 @@ */ public class GeneratorConstants { + public static final String X_BALLERINA_NAME = "x-ballerina-name"; + public static final String X_PARAM_TYPE = "x-param-type"; + /** * Enum to select the code generation mode. * Ballerina service, mock and client generation is available @@ -423,4 +426,7 @@ public String getValue() { public static final String DECIMAL = "decimal"; public static final String RETURN = "return"; public static final String OPTIONAL_ERROR = "error?"; + public static final String NAME_ANNOTATION = "jsondata:Name"; + public static final String QUERY_ANNOTATION = "http:Query"; + public static final String HEADER_ANNOTATION = "http:Header"; } diff --git a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/GeneratorUtils.java b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/GeneratorUtils.java index 43e2009cc..7a41ee695 100644 --- a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/GeneratorUtils.java +++ b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/GeneratorUtils.java @@ -19,6 +19,7 @@ package io.ballerina.openapi.core.generators.common; import io.ballerina.compiler.syntax.tree.AbstractNodeFactory; +import io.ballerina.compiler.syntax.tree.AnnotationNode; import io.ballerina.compiler.syntax.tree.ArrayDimensionNode; import io.ballerina.compiler.syntax.tree.BuiltinSimpleNameReferenceNode; import io.ballerina.compiler.syntax.tree.CaptureBindingPatternNode; @@ -27,6 +28,8 @@ import io.ballerina.compiler.syntax.tree.IdentifierToken; import io.ballerina.compiler.syntax.tree.ImportDeclarationNode; import io.ballerina.compiler.syntax.tree.ImportOrgNameNode; +import io.ballerina.compiler.syntax.tree.MappingConstructorExpressionNode; +import io.ballerina.compiler.syntax.tree.MetadataNode; import io.ballerina.compiler.syntax.tree.Minutiae; import io.ballerina.compiler.syntax.tree.MinutiaeList; import io.ballerina.compiler.syntax.tree.Node; @@ -90,20 +93,26 @@ import java.util.Locale; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; +import static io.ballerina.compiler.syntax.tree.AbstractNodeFactory.createEmptyMinutiaeList; import static io.ballerina.compiler.syntax.tree.AbstractNodeFactory.createEmptyNodeList; import static io.ballerina.compiler.syntax.tree.AbstractNodeFactory.createIdentifierToken; +import static io.ballerina.compiler.syntax.tree.AbstractNodeFactory.createLiteralValueToken; import static io.ballerina.compiler.syntax.tree.AbstractNodeFactory.createNodeList; import static io.ballerina.compiler.syntax.tree.AbstractNodeFactory.createSeparatedNodeList; import static io.ballerina.compiler.syntax.tree.AbstractNodeFactory.createToken; +import static io.ballerina.compiler.syntax.tree.NodeFactory.createAnnotationNode; import static io.ballerina.compiler.syntax.tree.NodeFactory.createArrayTypeDescriptorNode; +import static io.ballerina.compiler.syntax.tree.NodeFactory.createBasicLiteralNode; import static io.ballerina.compiler.syntax.tree.NodeFactory.createBuiltinSimpleNameReferenceNode; import static io.ballerina.compiler.syntax.tree.NodeFactory.createCaptureBindingPatternNode; import static io.ballerina.compiler.syntax.tree.NodeFactory.createExpressionStatementNode; import static io.ballerina.compiler.syntax.tree.NodeFactory.createMappingConstructorExpressionNode; +import static io.ballerina.compiler.syntax.tree.NodeFactory.createMetadataNode; import static io.ballerina.compiler.syntax.tree.NodeFactory.createRequiredExpressionNode; import static io.ballerina.compiler.syntax.tree.NodeFactory.createResourcePathParameterNode; import static io.ballerina.compiler.syntax.tree.NodeFactory.createSimpleNameReferenceNode; @@ -111,6 +120,7 @@ import static io.ballerina.compiler.syntax.tree.NodeFactory.createTypedBindingPatternNode; import static io.ballerina.compiler.syntax.tree.NodeFactory.createUnionTypeDescriptorNode; import static io.ballerina.compiler.syntax.tree.NodeFactory.createVariableDeclarationNode; +import static io.ballerina.compiler.syntax.tree.SyntaxKind.AT_TOKEN; import static io.ballerina.compiler.syntax.tree.SyntaxKind.CLOSE_BRACE_TOKEN; import static io.ballerina.compiler.syntax.tree.SyntaxKind.CLOSE_BRACKET_TOKEN; import static io.ballerina.compiler.syntax.tree.SyntaxKind.COLON_TOKEN; @@ -123,6 +133,7 @@ import static io.ballerina.compiler.syntax.tree.SyntaxKind.SEMICOLON_TOKEN; import static io.ballerina.compiler.syntax.tree.SyntaxKind.SLASH_TOKEN; import static io.ballerina.compiler.syntax.tree.SyntaxKind.STRING_KEYWORD; +import static io.ballerina.compiler.syntax.tree.SyntaxKind.STRING_LITERAL; import static io.ballerina.compiler.syntax.tree.SyntaxKind.TYPE_KEYWORD; import static io.ballerina.openapi.core.generators.common.GeneratorConstants.APPLICATION_FORM_URLENCODED; import static io.ballerina.openapi.core.generators.common.GeneratorConstants.APPLICATION_OCTET_STREAM; @@ -132,8 +143,11 @@ import static io.ballerina.openapi.core.generators.common.GeneratorConstants.CATCH_ALL_PATH; import static io.ballerina.openapi.core.generators.common.GeneratorConstants.CLOSE_CURLY_BRACE; import static io.ballerina.openapi.core.generators.common.GeneratorConstants.EXPLODE; +import static io.ballerina.openapi.core.generators.common.GeneratorConstants.NAME_ANNOTATION; import static io.ballerina.openapi.core.generators.common.GeneratorConstants.GET; import static io.ballerina.openapi.core.generators.common.GeneratorConstants.HEAD; +import static io.ballerina.openapi.core.generators.common.GeneratorConstants.HEADER; +import static io.ballerina.openapi.core.generators.common.GeneratorConstants.HEADER_ANNOTATION; import static io.ballerina.openapi.core.generators.common.GeneratorConstants.HTTP_REQUEST; import static io.ballerina.openapi.core.generators.common.GeneratorConstants.HTTP_RESPONSE; import static io.ballerina.openapi.core.generators.common.GeneratorConstants.IMAGE_PNG; @@ -147,6 +161,8 @@ import static io.ballerina.openapi.core.generators.common.GeneratorConstants.OBJECT; import static io.ballerina.openapi.core.generators.common.GeneratorConstants.OPENAPI_TYPE_TO_FORMAT_MAP; import static io.ballerina.openapi.core.generators.common.GeneratorConstants.OPEN_CURLY_BRACE; +import static io.ballerina.openapi.core.generators.common.GeneratorConstants.QUERY; +import static io.ballerina.openapi.core.generators.common.GeneratorConstants.QUERY_ANNOTATION; import static io.ballerina.openapi.core.generators.common.GeneratorConstants.REGEX_ONLY_NUMBERS_OR_NUMBERS_WITH_SPECIAL_CHARACTERS; import static io.ballerina.openapi.core.generators.common.GeneratorConstants.REGEX_WITHOUT_SPECIAL_CHARACTERS; import static io.ballerina.openapi.core.generators.common.GeneratorConstants.REGEX_WORDS_STARTING_WITH_NUMBERS; @@ -158,6 +174,8 @@ import static io.ballerina.openapi.core.generators.common.GeneratorConstants.STYLE; import static io.ballerina.openapi.core.generators.common.GeneratorConstants.TEXT_EVENT_STREAM; import static io.ballerina.openapi.core.generators.common.GeneratorConstants.UNSUPPORTED_OPENAPI_VERSION_PARSER_MESSAGE; +import static io.ballerina.openapi.core.generators.common.GeneratorConstants.X_BALLERINA_NAME; +import static io.ballerina.openapi.core.generators.common.GeneratorConstants.X_PARAM_TYPE; import static io.ballerina.openapi.core.generators.common.GeneratorConstants.YAML_EXTENSION; import static io.ballerina.openapi.core.generators.common.GeneratorConstants.YML_EXTENSION; import static io.ballerina.openapi.core.generators.common.diagnostic.CommonDiagnosticMessages.OAS_COMMON_101; @@ -173,6 +191,9 @@ public class GeneratorUtils { public static final List BAL_KEYWORDS = SyntaxInfo.keywords(); public static final MinutiaeList SINGLE_END_OF_LINE_MINUTIAE = getEndOfLineMinutiae(); private static final PrintStream OUT_STREAM = System.err; + public static final String NAME = "name"; + public static final String VALUE = "value"; + public static final char CHAR = '"'; private static HashMap recordCountMap; private static final List primitiveTypeList = @@ -450,6 +471,14 @@ public static String extractReferenceType(String referenceVariable) throws Inval } } + public static Optional getBallerinaNameExtension(Schema schema) { + return Optional.ofNullable(schema.getExtensions()) + .map(extensions -> extensions.get(X_BALLERINA_NAME)) + .filter(String.class::isInstance) + .map(String.class::cast) + .map(String::trim); + } + public static boolean hasTags(List tags, List filterTags) { return !Collections.disjoint(filterTags, tags); } @@ -627,7 +656,7 @@ public static void setGeneratedFileName(List listFiles, GenSrcFile gFile, */ public static void createEncodingMap(List filedOfMap, String style, Boolean explode, String key) { - IdentifierToken fieldName = createIdentifierToken('"' + key + '"'); + IdentifierToken fieldName = createIdentifierToken(CHAR + key + CHAR); Token colon = createToken(COLON_TOKEN); SpecificFieldNode styleField = createSpecificFieldNode(null, createIdentifierToken(STYLE), createToken(COLON_TOKEN), @@ -1198,4 +1227,38 @@ public static String generateOperationUniqueId(Operation operation, String path, return Objects.nonNull(operation.getOperationId()) ? operation.getOperationId() : method + getValidName(path, true); } + + public static MetadataNode getNameAnnotationMetadataNode(Schema fieldSchema) { + String annotationType = getAnnotationType(fieldSchema); + AnnotationNode annotationNode = getNameAnnotationNode(fieldSchema, annotationType); + return createMetadataNode(null, createNodeList(annotationNode)); + } + + public static AnnotationNode getNameAnnotationNode(Schema fieldSchema, String annotationType) { + String fieldName = fieldSchema.getName().trim(); + SimpleNameReferenceNode annotType = createSimpleNameReferenceNode(createIdentifierToken(annotationType)); + SpecificFieldNode nameField = createSpecificFieldNode(null, + createIdentifierToken(annotationType.equals(NAME_ANNOTATION) ? VALUE : NAME), + createToken(COLON_TOKEN), createBasicLiteralNode(STRING_LITERAL, + createLiteralValueToken(SyntaxKind.STRING_LITERAL_TOKEN, + CHAR + fieldName + CHAR, createEmptyMinutiaeList(), + createEmptyMinutiaeList()))); + MappingConstructorExpressionNode recExp = createMappingConstructorExpressionNode(createToken(OPEN_BRACE_TOKEN), + createSeparatedNodeList(nameField), createToken(CLOSE_BRACE_TOKEN)); + return createAnnotationNode(createToken(AT_TOKEN), annotType, recExp); + } + + private static String getAnnotationType(Schema schema) { + Map extensions = schema.getExtensions(); + if (Objects.isNull(extensions) || Objects.isNull(extensions.get(X_PARAM_TYPE)) || + !(extensions.get(X_PARAM_TYPE) instanceof String type)) { + return NAME_ANNOTATION; + } + + return switch (type) { + case QUERY -> QUERY_ANNOTATION; + case HEADER -> HEADER_ANNOTATION; + default -> NAME_ANNOTATION; + }; + } } diff --git a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/service/parameter/HeaderParameterGenerator.java b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/service/parameter/HeaderParameterGenerator.java index 9de1f8b64..5e7ad183a 100644 --- a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/service/parameter/HeaderParameterGenerator.java +++ b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/service/parameter/HeaderParameterGenerator.java @@ -45,7 +45,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Locale; +import java.util.Optional; import static io.ballerina.compiler.syntax.tree.AbstractNodeFactory.createIdentifierToken; import static io.ballerina.compiler.syntax.tree.AbstractNodeFactory.createNodeList; @@ -58,6 +58,7 @@ import static io.ballerina.compiler.syntax.tree.NodeFactory.createSimpleNameReferenceNode; import static io.ballerina.compiler.syntax.tree.SyntaxKind.CLOSE_PAREN_TOKEN; import static io.ballerina.compiler.syntax.tree.SyntaxKind.OPEN_PAREN_TOKEN; +import static io.ballerina.openapi.core.generators.common.GeneratorConstants.HEADER; import static io.ballerina.openapi.core.generators.common.GeneratorConstants.NILLABLE; import static io.ballerina.openapi.core.generators.common.GeneratorUtils.convertOpenAPITypeToBallerina; import static io.ballerina.openapi.core.generators.common.GeneratorUtils.escapeIdentifier; @@ -83,14 +84,21 @@ public HeaderParameterGenerator(OASServiceMetadata oasServiceMetadata) { public ParameterNode generateParameterNode(Parameter parameter) throws UnsupportedOASDataTypeException, InvalidReferenceException, InvalidHeaderNameException { Schema schema = parameter.getSchema(); + String paramName = parameter.getName().trim(); + AnnotationNode headerNode = ServiceGenerationUtils.getAnnotationNode(GeneratorConstants.HEADER_ANNOT, null); + NodeList headerAnnotations = createNodeList(headerNode); + Optional nameFromExt = GeneratorUtils.getBallerinaNameExtension(schema); + if (nameFromExt.isPresent()) { + paramName = nameFromExt.get(); + headerAnnotations = createNodeList(GeneratorUtils.getNameAnnotationNode(schema, HEADER)); + } String headerType = GeneratorConstants.STRING; TypeDescriptorNode headerTypeName; if (parameter.getName().isBlank()) { throw new InvalidHeaderNameException(); } - IdentifierToken parameterName = createIdentifierToken(GeneratorUtils.escapeIdentifier(parameter.getName() - .toLowerCase(Locale.ENGLISH)), AbstractNodeFactory.createEmptyMinutiaeList(), - GeneratorUtils.SINGLE_WS_MINUTIAE); + IdentifierToken parameterName = createIdentifierToken(GeneratorUtils.escapeIdentifier(paramName), + AbstractNodeFactory.createEmptyMinutiaeList(), GeneratorUtils.SINGLE_WS_MINUTIAE); if (getOpenAPIType(schema) == null && schema.get$ref() == null) { // Header example: @@ -159,8 +167,6 @@ public ParameterNode generateParameterNode(Parameter parameter) throws Unsupport // createToken(SyntaxKind.OPEN_BRACE_TOKEN), NodeFactory.createSeparatedNodeList(), // createToken(SyntaxKind.CLOSE_BRACE_TOKEN)); - AnnotationNode headerNode = ServiceGenerationUtils.getAnnotationNode(GeneratorConstants.HEADER_ANNOT, null); - NodeList headerAnnotations = createNodeList(headerNode); // Handle optional values in headers if (!parameter.getRequired()) { // If optional it behaves like default value with null ex:(string? header) diff --git a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/service/parameter/QueryParameterGenerator.java b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/service/parameter/QueryParameterGenerator.java index 529267f36..15b1e7a93 100644 --- a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/service/parameter/QueryParameterGenerator.java +++ b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/service/parameter/QueryParameterGenerator.java @@ -19,10 +19,12 @@ package io.ballerina.openapi.core.generators.service.parameter; import io.ballerina.compiler.syntax.tree.AbstractNodeFactory; +import io.ballerina.compiler.syntax.tree.AnnotationNode; import io.ballerina.compiler.syntax.tree.ArrayDimensionNode; import io.ballerina.compiler.syntax.tree.BuiltinSimpleNameReferenceNode; import io.ballerina.compiler.syntax.tree.IdentifierToken; import io.ballerina.compiler.syntax.tree.NodeFactory; +import io.ballerina.compiler.syntax.tree.NodeList; import io.ballerina.compiler.syntax.tree.OptionalTypeDescriptorNode; import io.ballerina.compiler.syntax.tree.ParameterNode; import io.ballerina.compiler.syntax.tree.RequiredParameterNode; @@ -56,6 +58,7 @@ import static io.ballerina.compiler.syntax.tree.NodeFactory.createOptionalTypeDescriptorNode; import static io.ballerina.compiler.syntax.tree.NodeFactory.createRequiredParameterNode; import static io.ballerina.compiler.syntax.tree.NodeFactory.createSimpleNameReferenceNode; +import static io.ballerina.openapi.core.generators.common.GeneratorConstants.QUERY; import static io.ballerina.openapi.core.generators.common.GeneratorConstants.STRING; import static io.ballerina.openapi.core.generators.common.GeneratorUtils.convertOpenAPITypeToBallerina; import static io.ballerina.openapi.core.generators.common.GeneratorUtils.escapeIdentifier; @@ -82,8 +85,15 @@ public QueryParameterGenerator(OASServiceMetadata oasServiceMetadata) { public ParameterNode generateParameterNode(Parameter parameter) throws InvalidReferenceException, UnsupportedOASDataTypeException { Schema schema = parameter.getSchema(); + String paramName = parameter.getName().trim(); + NodeList annotations = createEmptyNodeList(); + Optional nameFromExt = GeneratorUtils.getBallerinaNameExtension(schema); + if (nameFromExt.isPresent()) { + paramName = nameFromExt.get(); + annotations = createNodeList(GeneratorUtils.getNameAnnotationNode(schema, QUERY)); + } IdentifierToken parameterName = createIdentifierToken( - GeneratorUtils.escapeIdentifier(parameter.getName().trim()), + GeneratorUtils.escapeIdentifier(paramName), AbstractNodeFactory.createEmptyMinutiaeList(), GeneratorUtils.SINGLE_WS_MINUTIAE); boolean isSchemaNotSupported = schema == null || getOpenAPIType(schema) == null; //Todo: will enable when header parameter support objects @@ -91,27 +101,27 @@ public ParameterNode generateParameterNode(Parameter parameter) throws InvalidRe if (schema != null && schema.get$ref() != null) { String refType = extractReferenceType(schema.get$ref()); Schema refSchema = openAPI.getComponents().getSchemas().get(refType); - return handleReferencedQueryParameter(parameter, refSchema, parameterName); + return handleReferencedQueryParameter(parameter, refSchema, parameterName, annotations); } else if (parameter.getContent() != null) { Content content = parameter.getContent(); for (Map.Entry mediaTypeEntry : content.entrySet()) { - return handleMapJsonQueryParameter(parameter, parameterName, mediaTypeEntry); + return handleMapJsonQueryParameter(parameter, parameterName, mediaTypeEntry, annotations); } } else if (isSchemaNotSupported) { diagnostics.add(new ServiceDiagnostic(ServiceDiagnosticMessages.OAS_SERVICE_102, getOpenAPIType(parameter.getSchema()))); OptionalTypeDescriptorNode optionalNode = createOptionalTypeDescriptorNode( createIdentifierToken(STRING), createToken(SyntaxKind.QUESTION_MARK_TOKEN)); - return createRequiredParameterNode(createEmptyNodeList(), optionalNode, parameterName); + return createRequiredParameterNode(annotations, optionalNode, parameterName); } else if (parameter.getSchema().getDefault() != null) { // When query parameter has default value - return handleDefaultQueryParameter(schema, parameterName); + return handleDefaultQueryParameter(schema, parameterName, annotations); } else if (parameter.getRequired() && schema.getNullable() == null) { // Required typeDescriptor - return handleRequiredQueryParameter(schema, parameterName); + return handleRequiredQueryParameter(schema, parameterName, annotations); } else { // Optional typeDescriptor - return handleOptionalQueryParameter(schema, parameterName); + return handleOptionalQueryParameter(schema, parameterName, annotations); } return null; } @@ -131,7 +141,8 @@ public ParameterNode generateParameterNode(Parameter parameter) throws InvalidRe * */ private RequiredParameterNode handleMapJsonQueryParameter(Parameter parameter, IdentifierToken parameterName, - Map.Entry mediaTypeEntry) + Map.Entry mediaTypeEntry, + NodeList annotations) throws InvalidReferenceException { Schema parameterSchema; if (mediaTypeEntry.getValue().getSchema() != null && mediaTypeEntry.getValue().getSchema().get$ref() != null) { @@ -142,30 +153,32 @@ private RequiredParameterNode handleMapJsonQueryParameter(Parameter parameter, I parameterSchema = mediaTypeEntry.getValue().getSchema(); } if (mediaTypeEntry.getKey().equals(GeneratorConstants.APPLICATION_JSON) && isMapSchema(parameterSchema)) { - return getMapJsonParameterNode(parameterName, parameter); + return getMapJsonParameterNode(parameterName, parameter, annotations); } String type = GeneratorUtils.getBallerinaMediaType(mediaTypeEntry.getKey(), false); diagnostics.add(new ServiceDiagnostic(ServiceDiagnosticMessages.OAS_SERVICE_102, type)); OptionalTypeDescriptorNode optionalNode = createOptionalTypeDescriptorNode( createIdentifierToken(STRING), createToken(SyntaxKind.QUESTION_MARK_TOKEN)); - return createRequiredParameterNode(createEmptyNodeList(), optionalNode, parameterName); + return createRequiredParameterNode(annotations, optionalNode, parameterName); } - private RequiredParameterNode getMapJsonParameterNode(IdentifierToken parameterName, Parameter parameter) { + private RequiredParameterNode getMapJsonParameterNode(IdentifierToken parameterName, Parameter parameter, + NodeList annotations) { BuiltinSimpleNameReferenceNode rTypeName = createBuiltinSimpleNameReferenceNode(null, createIdentifierToken(io.ballerina.openapi.core.generators.type.GeneratorConstants.MAP_JSON)); if (parameter.getRequired()) { - return createRequiredParameterNode(createEmptyNodeList(), rTypeName, parameterName); + return createRequiredParameterNode(annotations, rTypeName, parameterName); } OptionalTypeDescriptorNode optionalNode = createOptionalTypeDescriptorNode(rTypeName, createToken(SyntaxKind.QUESTION_MARK_TOKEN)); - return createRequiredParameterNode(createEmptyNodeList(), optionalNode, parameterName); + return createRequiredParameterNode(annotations, optionalNode, parameterName); } /** * This function is to handle query schema which does not have required as true. */ - private ParameterNode handleOptionalQueryParameter(Schema schema, IdentifierToken parameterName) + private ParameterNode handleOptionalQueryParameter(Schema schema, IdentifierToken parameterName, + NodeList annotations) throws UnsupportedOASDataTypeException { if (isArraySchema(schema)) { Schema items = schema.getItems(); @@ -173,23 +186,23 @@ private ParameterNode handleOptionalQueryParameter(Schema schema, IdentifierT // Resource function doesn't support to query parameters with array type which doesn't have an // item type. diagnostics.add(new ServiceDiagnostic(ServiceDiagnosticMessages.OAS_SERVICE_101)); - return createStringArrayParameterNode(parameterName); + return createStringArrayParameterNode(parameterName, annotations); } else if ((!(isObjectSchema(items)) && !(getOpenAPIType(items) != null && getOpenAPIType(items).equals(GeneratorConstants.ARRAY))) || items.get$ref() != null) { Optional typeDescriptorNode = TypeHandler.getInstance() .getTypeNodeFromOASSchema(schema, true); OptionalTypeDescriptorNode optionalNode = createOptionalTypeDescriptorNode(typeDescriptorNode.get(), createToken(SyntaxKind.QUESTION_MARK_TOKEN)); - return createRequiredParameterNode(createEmptyNodeList(), optionalNode, parameterName); + return createRequiredParameterNode(annotations, optionalNode, parameterName); } else if (getOpenAPIType(items).equals(GeneratorConstants.ARRAY)) { // Resource function doesn't support to the nested array type query parameters. diagnostics.add(new ServiceDiagnostic(ServiceDiagnosticMessages.OAS_SERVICE_100)); - return createStringArrayParameterNode(parameterName); + return createStringArrayParameterNode(parameterName, annotations); } else { diagnostics.add(new ServiceDiagnostic(ServiceDiagnosticMessages.OAS_SERVICE_102, "object")); OptionalTypeDescriptorNode optionalNode = createOptionalTypeDescriptorNode( createIdentifierToken(STRING), createToken(SyntaxKind.QUESTION_MARK_TOKEN)); - return createRequiredParameterNode(createEmptyNodeList(), optionalNode, parameterName); + return createRequiredParameterNode(annotations, optionalNode, parameterName); } } else { Token name; @@ -209,11 +222,12 @@ private ParameterNode handleOptionalQueryParameter(Schema schema, IdentifierT queryParamType = createOptionalTypeDescriptorNode(queryParamType, createToken(SyntaxKind.QUESTION_MARK_TOKEN)); } - return createRequiredParameterNode(createEmptyNodeList(), queryParamType, parameterName); + return createRequiredParameterNode(annotations, queryParamType, parameterName); } } - private static RequiredParameterNode createStringArrayParameterNode(IdentifierToken parameterName) { + private static RequiredParameterNode createStringArrayParameterNode(IdentifierToken parameterName, + NodeList annotations) { ArrayDimensionNode arrayDimensionNode = NodeFactory.createArrayDimensionNode( createToken(SyntaxKind.OPEN_BRACKET_TOKEN), null, createToken(SyntaxKind.CLOSE_BRACKET_TOKEN)); @@ -221,11 +235,12 @@ private static RequiredParameterNode createStringArrayParameterNode(IdentifierTo createIdentifierToken(STRING)), createNodeList(arrayDimensionNode)); OptionalTypeDescriptorNode optionalNode = createOptionalTypeDescriptorNode( arrayTypedescNode, createToken(SyntaxKind.QUESTION_MARK_TOKEN)); - return createRequiredParameterNode(createEmptyNodeList(), optionalNode, parameterName); + return createRequiredParameterNode(annotations, optionalNode, parameterName); } private ParameterNode handleReferencedQueryParameter(Parameter parameter, Schema refSchema, - IdentifierToken parameterName) { + IdentifierToken parameterName, + NodeList annotations) { Token refTypeNameNode; if (refSchema.getAnyOf() != null || refSchema.getOneOf() != null) { refTypeNameNode = createIdentifierToken(STRING); @@ -238,46 +253,47 @@ private ParameterNode handleReferencedQueryParameter(Parameter parameter, Schema if (refSchema.getDefault() != null) { String defaultValue = getOpenAPIType(refSchema).equals(STRING) ? String.format("\"%s\"", refSchema.getDefault().toString()) : refSchema.getDefault().toString(); - return createDefaultableParameterNode(createEmptyNodeList(), refTypeNameNode, parameterName, + return createDefaultableParameterNode(annotations, refTypeNameNode, parameterName, createToken(SyntaxKind.EQUAL_TOKEN), createSimpleNameReferenceNode(createIdentifierToken(defaultValue))); } else if (parameter.getRequired() && (refSchema.getNullable() == null || (!refSchema.getNullable()))) { - return createRequiredParameterNode(createEmptyNodeList(), refTypeNameNode, parameterName); + return createRequiredParameterNode(annotations, refTypeNameNode, parameterName); } else { OptionalTypeDescriptorNode optionalNode = createOptionalTypeDescriptorNode(refTypeNameNode, createToken(SyntaxKind.QUESTION_MARK_TOKEN)); - return createRequiredParameterNode(createEmptyNodeList(), optionalNode, parameterName); + return createRequiredParameterNode(annotations, optionalNode, parameterName); } } - private ParameterNode handleRequiredQueryParameter(Schema schema, IdentifierToken parameterName) { + private ParameterNode handleRequiredQueryParameter(Schema schema, IdentifierToken parameterName, + NodeList annotations) { if (isArraySchema(schema)) { Schema items = schema.getItems(); if (!(isArraySchema(items)) && (getOpenAPIType(items) != null || (items.get$ref() != null))) { Optional typeDescriptorNode = TypeHandler.getInstance() .getTypeNodeFromOASSchema(schema, true); Token arrayTypeName = createIdentifierToken(typeDescriptorNode.get().toSourceCode()); - return createRequiredParameterNode(createEmptyNodeList(), arrayTypeName, parameterName); + return createRequiredParameterNode(annotations, arrayTypeName, parameterName); } else if (getOpenAPIType(items) == null) { // Resource function doesn't support query parameters for array types that doesn't have an item type. diagnostics.add(new ServiceDiagnostic(ServiceDiagnosticMessages.OAS_SERVICE_101)); - return createStringArrayParameterNode(parameterName); + return createStringArrayParameterNode(parameterName, annotations); } else if (isObjectSchema(items)) { diagnostics.add(new ServiceDiagnostic(ServiceDiagnosticMessages.OAS_SERVICE_102, "object")); OptionalTypeDescriptorNode optionalNode = createOptionalTypeDescriptorNode( createIdentifierToken(STRING), createToken(SyntaxKind.QUESTION_MARK_TOKEN)); - return createRequiredParameterNode(createEmptyNodeList(), optionalNode, parameterName); + return createRequiredParameterNode(annotations, optionalNode, parameterName); } else { // Resource function doesn't support to the nested array type query parameters. diagnostics.add(new ServiceDiagnostic(ServiceDiagnosticMessages.OAS_SERVICE_100)); - return createStringArrayParameterNode(parameterName); + return createStringArrayParameterNode(parameterName, annotations); } } else { Optional typeDescriptorNode = TypeHandler.getInstance() .getTypeNodeFromOASSchema(schema, true); Token name = createIdentifierToken(typeDescriptorNode.get().toSourceCode()); BuiltinSimpleNameReferenceNode rTypeName = createBuiltinSimpleNameReferenceNode(null, name); - return createRequiredParameterNode(createEmptyNodeList(), rTypeName, parameterName); + return createRequiredParameterNode(annotations, rTypeName, parameterName); } } @@ -297,7 +313,8 @@ private ParameterNode handleRequiredQueryParameter(Schema schema, IdentifierT * generated ballerina -> int limit = 10; */ - private ParameterNode handleDefaultQueryParameter(Schema schema, IdentifierToken parameterName) { + private ParameterNode handleDefaultQueryParameter(Schema schema, IdentifierToken parameterName, + NodeList annotations) { if (isArraySchema(schema)) { Schema items = schema.getItems(); @@ -305,17 +322,17 @@ private ParameterNode handleDefaultQueryParameter(Schema schema, IdentifierTo Optional typeDescriptorNode = TypeHandler.getInstance() .getTypeNodeFromOASSchema(schema, true); Token arrayTypeName = createIdentifierToken(typeDescriptorNode.get().toSourceCode()); - return createDefaultableParameterNode(createEmptyNodeList(), arrayTypeName, parameterName, + return createDefaultableParameterNode(annotations, arrayTypeName, parameterName, createToken(SyntaxKind.EQUAL_TOKEN), createSimpleNameReferenceNode(createIdentifierToken(schema.getDefault().toString()))); } else if (getOpenAPIType(items) == null) { // Resource function doesn't support to query parameters with array type which hasn't item type. diagnostics.add(new ServiceDiagnostic(ServiceDiagnosticMessages.OAS_SERVICE_101)); - return createStringArrayParameterNode(parameterName); + return createStringArrayParameterNode(parameterName, annotations); } else { // Resource function doesn't support to the nested array type query parameters. diagnostics.add(new ServiceDiagnostic(ServiceDiagnosticMessages.OAS_SERVICE_100)); - return createStringArrayParameterNode(parameterName); + return createStringArrayParameterNode(parameterName, annotations); } } else { Optional typeDescriptorNode = TypeHandler.getInstance() @@ -323,12 +340,12 @@ private ParameterNode handleDefaultQueryParameter(Schema schema, IdentifierTo Token name = createIdentifierToken(typeDescriptorNode.get().toSourceCode()); BuiltinSimpleNameReferenceNode rTypeName = createBuiltinSimpleNameReferenceNode(null, name); if (getOpenAPIType(schema).equals(STRING)) { - return createDefaultableParameterNode(createEmptyNodeList(), rTypeName, parameterName, + return createDefaultableParameterNode(annotations, rTypeName, parameterName, createToken(SyntaxKind.EQUAL_TOKEN), createSimpleNameReferenceNode(createIdentifierToken('"' + schema.getDefault().toString() + '"'))); } - return createDefaultableParameterNode(createEmptyNodeList(), rTypeName, parameterName, + return createDefaultableParameterNode(annotations, rTypeName, parameterName, createToken(SyntaxKind.EQUAL_TOKEN), createSimpleNameReferenceNode(createIdentifierToken(schema.getDefault().toString()))); } diff --git a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/type/generators/ArrayTypeGenerator.java b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/type/generators/ArrayTypeGenerator.java index 1cb745844..5a53e1570 100644 --- a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/type/generators/ArrayTypeGenerator.java +++ b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/type/generators/ArrayTypeGenerator.java @@ -183,13 +183,7 @@ public Optional getTypeDescNodeForArraySchema(Schema schema, createIdentifierToken(validTypeName)); subTypesMap.put(validTypeName, typeDefinitionNode); } - try { - member = createBuiltinSimpleNameReferenceNode(null, - createIdentifierToken(GeneratorUtils.escapeIdentifier(GeneratorUtils. - extractReferenceType(schema.getItems().get$ref())))); - } catch (BallerinaOpenApiException e) { - throw new OASTypeGenException(e.getMessage()); - } + member = createBuiltinSimpleNameReferenceNode(null, createIdentifierToken(validTypeName)); } else if (schemaType != null && (schemaType.equals(GeneratorConstants.INTEGER) || schemaType.equals(GeneratorConstants.NUMBER) || schemaType.equals(GeneratorConstants.BOOLEAN) || schemaType.equals(GeneratorConstants.STRING))) { diff --git a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/type/generators/RecordTypeGenerator.java b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/type/generators/RecordTypeGenerator.java index bcb8cfed6..753845237 100644 --- a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/type/generators/RecordTypeGenerator.java +++ b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/type/generators/RecordTypeGenerator.java @@ -22,6 +22,7 @@ import io.ballerina.compiler.syntax.tree.AbstractNodeFactory; import io.ballerina.compiler.syntax.tree.ExpressionNode; import io.ballerina.compiler.syntax.tree.IdentifierToken; +import io.ballerina.compiler.syntax.tree.MetadataNode; import io.ballerina.compiler.syntax.tree.NameReferenceNode; import io.ballerina.compiler.syntax.tree.Node; import io.ballerina.compiler.syntax.tree.NodeFactory; @@ -49,6 +50,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; import static io.ballerina.compiler.syntax.tree.AbstractNodeFactory.createIdentifierToken; @@ -69,6 +71,7 @@ import static io.ballerina.compiler.syntax.tree.SyntaxKind.RECORD_KEYWORD; import static io.ballerina.compiler.syntax.tree.SyntaxKind.SEMICOLON_TOKEN; import static io.ballerina.openapi.core.generators.common.GeneratorUtils.convertOpenAPITypeToBallerina; +import static io.ballerina.openapi.core.generators.common.GeneratorUtils.getNameAnnotationMetadataNode; /** * Generate TypeDefinitionNode and TypeDescriptorNode for object type schema. @@ -277,15 +280,21 @@ public ImmutablePair, Set> updateRecordFieldListWithImports( List required, List recordFieldList, Map.Entry> field, Schema fieldSchema, IdentifierToken fieldName, TypeDescriptorNode fieldTypeName) { Set imports = new HashSet<>(); + MetadataNode metadataNode = null; + Optional fieldNameFromExt = GeneratorUtils.getBallerinaNameExtension(fieldSchema); + if (fieldNameFromExt.isPresent()) { + fieldName = createIdentifierToken(fieldNameFromExt.get()); + metadataNode = GeneratorUtils.getNameAnnotationMetadataNode(fieldSchema); + } if (required != null && required.contains(field.getKey().trim())) { - setRequiredFields(recordFieldList, fieldSchema, fieldName, fieldTypeName); + setRequiredFields(recordFieldList, fieldSchema, fieldName, fieldTypeName, metadataNode); } else if (fieldSchema.getDefault() != null) { RecordFieldWithDefaultValueNode recordFieldWithDefaultValueNode = - getRecordFieldWithDefaultValueNode(fieldSchema, fieldName, fieldTypeName); + getRecordFieldWithDefaultValueNode(fieldSchema, fieldName, fieldTypeName, metadataNode); recordFieldList.add(recordFieldWithDefaultValueNode); } else { - RecordFieldNode recordFieldNode = NodeFactory.createRecordFieldNode(null, null, + RecordFieldNode recordFieldNode = NodeFactory.createRecordFieldNode(metadataNode, null, fieldTypeName, fieldName, createToken(QUESTION_MARK_TOKEN), createToken(SEMICOLON_TOKEN)); recordFieldList.add(recordFieldNode); } @@ -293,14 +302,14 @@ public ImmutablePair, Set> updateRecordFieldListWithImports( } private void setRequiredFields(List recordFieldList, Schema fieldSchema, IdentifierToken fieldName, - TypeDescriptorNode fieldTypeName) { + TypeDescriptorNode fieldTypeName, MetadataNode metadataNode) { if (Objects.nonNull(fieldSchema.getDefault())) { RecordFieldWithDefaultValueNode defaultNode = - getRecordFieldWithDefaultValueNode(fieldSchema, fieldName, fieldTypeName); + getRecordFieldWithDefaultValueNode(fieldSchema, fieldName, fieldTypeName, metadataNode); recordFieldList.add(defaultNode); } else { - RecordFieldNode recordFieldNode = NodeFactory.createRecordFieldNode(null, null, + RecordFieldNode recordFieldNode = NodeFactory.createRecordFieldNode(metadataNode, null, fieldTypeName, fieldName, null, createToken(SEMICOLON_TOKEN)); recordFieldList.add(recordFieldNode); } @@ -308,7 +317,8 @@ private void setRequiredFields(List recordFieldList, Schema fieldSchema private RecordFieldWithDefaultValueNode getRecordFieldWithDefaultValueNode(Schema fieldSchema, IdentifierToken fieldName, - TypeDescriptorNode fieldTypeName) { + TypeDescriptorNode fieldTypeName, + MetadataNode metadataNode) { Token defaultValueToken; Object defaultValueNode = fieldSchema.getDefault(); String defaultValue = defaultValueNode.toString().trim(); @@ -325,8 +335,7 @@ private RecordFieldWithDefaultValueNode getRecordFieldWithDefaultValueNode(Schem expressionNode = createRequiredExpressionNode(defaultValueToken); } - return NodeFactory.createRecordFieldWithDefaultValueNode - (null, null, fieldTypeName, fieldName, createToken(EQUAL_TOKEN), - expressionNode, createToken(SEMICOLON_TOKEN)); + return NodeFactory.createRecordFieldWithDefaultValueNode(metadataNode, null, fieldTypeName, + fieldName, createToken(EQUAL_TOKEN), expressionNode, createToken(SEMICOLON_TOKEN)); } } diff --git a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/type/generators/ReferencedTypeGenerator.java b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/type/generators/ReferencedTypeGenerator.java index 9a59f9f3c..83b7144d2 100644 --- a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/type/generators/ReferencedTypeGenerator.java +++ b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/type/generators/ReferencedTypeGenerator.java @@ -94,10 +94,6 @@ public TypeDescriptorNode generateTypeDescriptorNode() throws OASTypeGenExceptio typeDescriptorNode, createToken(SEMICOLON_TOKEN))); } - if (refSchema == null) { - throw new OASTypeGenException(String.format("Undefined $ref: '%s' in openAPI contract.", - schema.get$ref())); - } return TypeGeneratorUtils.getNullableType(refSchema, nameReferenceNode, ignoreNullableFlag); } } From 3ebeb3365a123e32878137937afd946edc42e6ef Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 19 Sep 2024 08:42:39 +0530 Subject: [PATCH 2/8] Add imports for annotations --- .../openapi/core/generators/common/GeneratorUtils.java | 3 +++ .../ballerina/openapi/core/generators/common/TypeHandler.java | 4 ++++ .../core/generators/type/generators/RecordTypeGenerator.java | 1 - 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/GeneratorUtils.java b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/GeneratorUtils.java index 7a41ee695..29c6dfc6f 100644 --- a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/GeneratorUtils.java +++ b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/GeneratorUtils.java @@ -194,6 +194,8 @@ public class GeneratorUtils { public static final String NAME = "name"; public static final String VALUE = "value"; public static final char CHAR = '"'; + public static final String JSONDATA_IMPORT = "import ballerina/data.jsondata;"; + public static final String HTTP_IMPORT = "import ballerina/http;"; private static HashMap recordCountMap; private static final List primitiveTypeList = @@ -1230,6 +1232,7 @@ public static String generateOperationUniqueId(Operation operation, String path, public static MetadataNode getNameAnnotationMetadataNode(Schema fieldSchema) { String annotationType = getAnnotationType(fieldSchema); + TypeHandler.getInstance().addImport(annotationType.equals(NAME_ANNOTATION) ? JSONDATA_IMPORT : HTTP_IMPORT); AnnotationNode annotationNode = getNameAnnotationNode(fieldSchema, annotationType); return createMetadataNode(null, createNodeList(annotationNode)); } diff --git a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/TypeHandler.java b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/TypeHandler.java index f4f042aaf..5f4e0141b 100644 --- a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/TypeHandler.java +++ b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/TypeHandler.java @@ -107,6 +107,10 @@ public void addTypeDefinitionNode(String key, TypeDefinitionNode typeDefinitionN typeDefinitionNodes.put(key, typeDefinitionNode); } + public void addImport(String importValue) { + imports.add(importValue); + } + public SyntaxTree generateTypeSyntaxTree() { NodeList typeMembers = getTypeMembers(); NodeList imports = generateImportNodes(); diff --git a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/type/generators/RecordTypeGenerator.java b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/type/generators/RecordTypeGenerator.java index 753845237..915fb6567 100644 --- a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/type/generators/RecordTypeGenerator.java +++ b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/type/generators/RecordTypeGenerator.java @@ -71,7 +71,6 @@ import static io.ballerina.compiler.syntax.tree.SyntaxKind.RECORD_KEYWORD; import static io.ballerina.compiler.syntax.tree.SyntaxKind.SEMICOLON_TOKEN; import static io.ballerina.openapi.core.generators.common.GeneratorUtils.convertOpenAPITypeToBallerina; -import static io.ballerina.openapi.core.generators.common.GeneratorUtils.getNameAnnotationMetadataNode; /** * Generate TypeDefinitionNode and TypeDescriptorNode for object type schema. From fc8848958510547a141d84ce96b2f69eda7e9c7d Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 19 Sep 2024 08:42:46 +0530 Subject: [PATCH 3/8] Fix test failures --- .../default_value_generation_service_contract.bal | 2 +- .../service/ballerina/headers/header_parameters.bal | 2 +- .../ballerina/headers/header_with_reference.bal | 2 +- .../service/ballerina/parameters_with_enum.bal | 8 ++++---- .../ballerina/parameters_with_nullable_enums.bal | 4 ++-- .../openapi/core/generators/common/GeneratorUtils.java | 3 ++- .../project_openapi_bal_ext/result_1.yaml | 10 +++++----- .../resources/client/expected/project-09/types.bal | 2 ++ 8 files changed, 18 insertions(+), 15 deletions(-) diff --git a/openapi-cli/src/test/resources/expected_gen/default_value_generation_service_contract.bal b/openapi-cli/src/test/resources/expected_gen/default_value_generation_service_contract.bal index 4d17843a2..b7339f067 100644 --- a/openapi-cli/src/test/resources/expected_gen/default_value_generation_service_contract.bal +++ b/openapi-cli/src/test/resources/expected_gen/default_value_generation_service_contract.bal @@ -6,7 +6,7 @@ import ballerina/http; @http:ServiceConfig {basePath: "/payloadV"} type OASServiceType service object { *http:ServiceContract; - resource function get albums/[string id](string q1 = "query1", int q2 = -1, @http:Header string x\-header = "header1") returns Album; + resource function get albums/[string id](string q1 = "query1", int q2 = -1, @http:Header string X\-HEADER = "header1") returns Album; }; public type Album record {| diff --git a/openapi-cli/src/test/resources/generators/service/ballerina/headers/header_parameters.bal b/openapi-cli/src/test/resources/generators/service/ballerina/headers/header_parameters.bal index 0ce0b0e9a..55702b5f1 100644 --- a/openapi-cli/src/test/resources/generators/service/ballerina/headers/header_parameters.bal +++ b/openapi-cli/src/test/resources/generators/service/ballerina/headers/header_parameters.bal @@ -8,6 +8,6 @@ service /v1 on ep0 { # + return - returns can be any of following types # http:Ok (Expected response to a valid request) # http:DefaultStatusCodeResponse (unexpected error) - resource function get pets(@http:Header string x\-request\-id, @http:Header string[] x\-request\-client) returns http:Ok|ErrorDefault { + resource function get pets(@http:Header string X\-Request\-ID, @http:Header string[] X\-Request\-Client) returns http:Ok|ErrorDefault { } } diff --git a/openapi-cli/src/test/resources/generators/service/ballerina/headers/header_with_reference.bal b/openapi-cli/src/test/resources/generators/service/ballerina/headers/header_with_reference.bal index 9b18ddb3c..21784ab33 100644 --- a/openapi-cli/src/test/resources/generators/service/ballerina/headers/header_with_reference.bal +++ b/openapi-cli/src/test/resources/generators/service/ballerina/headers/header_with_reference.bal @@ -4,6 +4,6 @@ listener http:Listener ep0 = new (80, config = {host: "petstore.openapi.io"}); service /v1 on ep0 { # + return - Ok - resource function get ping(@http:Header XClient x\-client, @http:Header XContent? x\-content, @http:Header string? consent\-id, @http:Header XCount x\-count, @http:Header XValid? x\-valid, @http:Header XSequence? x\-sequence, @http:Header float? x\-rate, @http:Header boolean? x\-modified, @http:Header XClient[]? x\-client\-profiles) returns http:Ok { + resource function get ping(@http:Header XClient x\-client, @http:Header XContent? x\-content, @http:Header string? Consent\-ID, @http:Header XCount x\-count, @http:Header XValid? x\-valid, @http:Header XSequence? x\-sequence, @http:Header float? X\-Rate, @http:Header boolean? X\-Modified, @http:Header XClient[]? X\-Client\-Profiles) returns http:Ok { } } diff --git a/openapi-cli/src/test/resources/generators/service/ballerina/parameters_with_enum.bal b/openapi-cli/src/test/resources/generators/service/ballerina/parameters_with_enum.bal index 6f11f99c0..55e6da1fe 100644 --- a/openapi-cli/src/test/resources/generators/service/ballerina/parameters_with_enum.bal +++ b/openapi-cli/src/test/resources/generators/service/ballerina/parameters_with_enum.bal @@ -5,16 +5,16 @@ listener http:Listener ep0 = new (443, config = {host: "petstore3.swagger.io"}); service /api/v3 on ep0 { # List meetings # - # + group - Employee group # + 'type - The meeting types. Scheduled, live or upcoming # + status - Status values that need to be considered for filter - # + x\-date\-format - Date time format (cf. [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) & [leettime.de](http://leettime.de/)) - # + x\-time\-zones - Time Zones of attendees + # + group - Employee group + # + X\-Date\-Format - Date time format (cf. [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) & [leettime.de](http://leettime.de/)) + # + X\-Time\-Zones - Time Zones of attendees # + location - Meeting location # + format - The response format you would like # + return - returns can be any of following types # MeetingList (HTTP Status Code:200. List of meetings returned.) # http:NotFound (HTTP Status Code:404 User ID not found. Error Code:1001, User not exist or not belong to this account.) - resource function get users/meetings/["Admin"|"HR"|"Engineering" group](("available"|"pending")[]? status, @http:Header "UTC"|"LOCAL"|"OFFSET"|"EPOCH"|"LEET"? x\-date\-format, @http:Header ("IST"|"GMT"|"UTC")[] x\-time\-zones, "json"|"jsonp"|"msgpack"|"html"? format, "scheduled"|"live"|"upcoming" 'type = "live", RoomNo location = "R5") returns MeetingList|http:NotFound { + resource function get users/meetings/["Admin"|"HR"|"Engineering" group](("available"|"pending")[]? status, @http:Header "UTC"|"LOCAL"|"OFFSET"|"EPOCH"|"LEET"? X\-Date\-Format, @http:Header ("IST"|"GMT"|"UTC")[] X\-Time\-Zones, "json"|"jsonp"|"msgpack"|"html"? format, "scheduled"|"live"|"upcoming" 'type = "live", RoomNo location = "R5") returns MeetingList|http:NotFound { } } diff --git a/openapi-cli/src/test/resources/generators/service/ballerina/parameters_with_nullable_enums.bal b/openapi-cli/src/test/resources/generators/service/ballerina/parameters_with_nullable_enums.bal index 3767f255e..e4c23b8e4 100644 --- a/openapi-cli/src/test/resources/generators/service/ballerina/parameters_with_nullable_enums.bal +++ b/openapi-cli/src/test/resources/generators/service/ballerina/parameters_with_nullable_enums.bal @@ -7,12 +7,12 @@ service /api/v3 on ep0 { # # + 'type - The meeting types. Scheduled, live or upcoming # + status - Status values that need to be considered for filter - # + x\-date\-format - Date time format (cf. [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) & [leettime.de](http://leettime.de/)) + # + X\-Date\-Format - Date time format (cf. [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) & [leettime.de](http://leettime.de/)) # + location - Meeting location # + format - The response format you would like # + return - returns can be any of following types # MeetingList (HTTP Status Code:200. List of meetings returned.) # http:NotFound (HTTP Status Code:404 User ID not found. Error Code:1001, User not exist or not belong to this account.) - resource function get users/meetings("scheduled"|"live"|"upcoming"? 'type, ("available"|"pending"?)[]? status, @http:Header "UTC"|"LOCAL"|"OFFSET"|"EPOCH"|"LEET"? x\-date\-format, "json"|"jsonp"|"msgpack"|"html"? format, RoomNo location = "R5") returns MeetingList|http:NotFound { + resource function get users/meetings("scheduled"|"live"|"upcoming"? 'type, ("available"|"pending"?)[]? status, @http:Header "UTC"|"LOCAL"|"OFFSET"|"EPOCH"|"LEET"? X\-Date\-Format, "json"|"jsonp"|"msgpack"|"html"? format, RoomNo location = "R5") returns MeetingList|http:NotFound { } } diff --git a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/GeneratorUtils.java b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/GeneratorUtils.java index 29c6dfc6f..aaeb556c4 100644 --- a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/GeneratorUtils.java +++ b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/GeneratorUtils.java @@ -474,7 +474,8 @@ public static String extractReferenceType(String referenceVariable) throws Inval } public static Optional getBallerinaNameExtension(Schema schema) { - return Optional.ofNullable(schema.getExtensions()) + return Optional.ofNullable(schema) + .map(Schema::getExtensions) .map(extensions -> extensions.get(X_BALLERINA_NAME)) .filter(String.class::isInstance) .map(String.class::cast) diff --git a/openapi-integration-tests/src/test/resources/ballerina_sources/project_openapi_bal_ext/result_1.yaml b/openapi-integration-tests/src/test/resources/ballerina_sources/project_openapi_bal_ext/result_1.yaml index 0dafce369..996094380 100644 --- a/openapi-integration-tests/src/test/resources/ballerina_sources/project_openapi_bal_ext/result_1.yaml +++ b/openapi-integration-tests/src/test/resources/ballerina_sources/project_openapi_bal_ext/result_1.yaml @@ -57,7 +57,7 @@ components: orgName: ballerina pkgName: time moduleName: time - version: 2.4.0 + version: 2.5.0 modulePrefix: time name: Date DateFields: @@ -81,7 +81,7 @@ components: orgName: ballerina pkgName: time moduleName: time - version: 2.4.0 + version: 2.5.0 modulePrefix: time name: DateFields OptionalTimeOfDayFields: @@ -100,7 +100,7 @@ components: orgName: ballerina pkgName: time moduleName: time - version: 2.4.0 + version: 2.5.0 modulePrefix: time name: OptionalTimeOfDayFields Seconds: @@ -111,7 +111,7 @@ components: orgName: ballerina pkgName: time moduleName: time - version: 2.4.0 + version: 2.5.0 modulePrefix: time name: Seconds Student: @@ -165,6 +165,6 @@ components: orgName: ballerina pkgName: time moduleName: time - version: 2.4.0 + version: 2.5.0 modulePrefix: time name: ZoneOffset diff --git a/openapi-integration-tests/src/test/resources/client/expected/project-09/types.bal b/openapi-integration-tests/src/test/resources/client/expected/project-09/types.bal index 4ebedcb46..347517198 100644 --- a/openapi-integration-tests/src/test/resources/client/expected/project-09/types.bal +++ b/openapi-integration-tests/src/test/resources/client/expected/project-09/types.bal @@ -130,6 +130,7 @@ public type extensionsChatCompletionsRequest record { # Represents the Queries record for the operation: ExtensionsChatCompletions_Create public type ExtensionsChatCompletionsCreateQueries record { + #apiversion string api\-version; }; @@ -215,6 +216,7 @@ public type chatCompletionResponseMessage record { # Represents the Queries record for the operation: ChatCompletions_Create public type ChatCompletionsCreateQueries record { + #apiversion string api\-version; }; From 527ef0a72a614b519f62dced7181d385676fba51 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 19 Sep 2024 13:18:01 +0530 Subject: [PATCH 4/8] Fix sonar cloud issues --- .../service/parameter/QueryParameterGenerator.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/service/parameter/QueryParameterGenerator.java b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/service/parameter/QueryParameterGenerator.java index 15b1e7a93..281bf8cc1 100644 --- a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/service/parameter/QueryParameterGenerator.java +++ b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/service/parameter/QueryParameterGenerator.java @@ -104,8 +104,10 @@ public ParameterNode generateParameterNode(Parameter parameter) throws InvalidRe return handleReferencedQueryParameter(parameter, refSchema, parameterName, annotations); } else if (parameter.getContent() != null) { Content content = parameter.getContent(); - for (Map.Entry mediaTypeEntry : content.entrySet()) { - return handleMapJsonQueryParameter(parameter, parameterName, mediaTypeEntry, annotations); + // Only consider the first content + Optional> mediaTypeEntry = content.entrySet().stream().findFirst(); + if (mediaTypeEntry.isPresent()) { + return handleMapJsonQueryParameter(parameter, parameterName, mediaTypeEntry.get(), annotations); } } else if (isSchemaNotSupported) { diagnostics.add(new ServiceDiagnostic(ServiceDiagnosticMessages.OAS_SERVICE_102, From 880a2d447ee6d54fcfe96cfa580fda8b0710edea Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 19 Sep 2024 14:43:15 +0530 Subject: [PATCH 5/8] Fix bugs in the implementation --- .../generators/common/GeneratorUtils.java | 28 +++++++++++++------ .../common/SingleFileGenerator.java | 2 +- .../core/generators/common/TypeHandler.java | 20 +++++-------- .../parameter/HeaderParameterGenerator.java | 7 +++-- .../parameter/QueryParameterGenerator.java | 6 ++-- .../type/generators/RecordTypeGenerator.java | 2 +- 6 files changed, 36 insertions(+), 29 deletions(-) diff --git a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/GeneratorUtils.java b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/GeneratorUtils.java index aaeb556c4..04850b089 100644 --- a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/GeneratorUtils.java +++ b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/GeneratorUtils.java @@ -473,10 +473,23 @@ public static String extractReferenceType(String referenceVariable) throws Inval } } + public static Optional getBallerinaNameExtension(Parameter parameter) { + if (Objects.isNull(parameter) || Objects.isNull(parameter.getExtensions())) { + return Optional.empty(); + } + return getBallerinaNameExtension(parameter.getExtensions()); + } + public static Optional getBallerinaNameExtension(Schema schema) { - return Optional.ofNullable(schema) - .map(Schema::getExtensions) - .map(extensions -> extensions.get(X_BALLERINA_NAME)) + if (Objects.isNull(schema) || Objects.isNull(schema.getExtensions())) { + return Optional.empty(); + } + return getBallerinaNameExtension(schema.getExtensions()); + } + + public static Optional getBallerinaNameExtension(Map extensions) { + return Optional.ofNullable(extensions) + .map(ext -> ext.get(X_BALLERINA_NAME)) .filter(String.class::isInstance) .map(String.class::cast) .map(String::trim); @@ -1231,15 +1244,14 @@ public static String generateOperationUniqueId(Operation operation, String path, operation.getOperationId() : method + getValidName(path, true); } - public static MetadataNode getNameAnnotationMetadataNode(Schema fieldSchema) { + public static MetadataNode getNameAnnotationMetadataNode(String fieldName, Schema fieldSchema) { String annotationType = getAnnotationType(fieldSchema); - TypeHandler.getInstance().addImport(annotationType.equals(NAME_ANNOTATION) ? JSONDATA_IMPORT : HTTP_IMPORT); - AnnotationNode annotationNode = getNameAnnotationNode(fieldSchema, annotationType); + AnnotationNode annotationNode = getNameAnnotationNode(fieldName, annotationType); return createMetadataNode(null, createNodeList(annotationNode)); } - public static AnnotationNode getNameAnnotationNode(Schema fieldSchema, String annotationType) { - String fieldName = fieldSchema.getName().trim(); + public static AnnotationNode getNameAnnotationNode(String fieldName, String annotationType) { + TypeHandler.getInstance().addImport(annotationType.equals(NAME_ANNOTATION) ? JSONDATA_IMPORT : HTTP_IMPORT); SimpleNameReferenceNode annotType = createSimpleNameReferenceNode(createIdentifierToken(annotationType)); SpecificFieldNode nameField = createSpecificFieldNode(null, createIdentifierToken(annotationType.equals(NAME_ANNOTATION) ? VALUE : NAME), diff --git a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/SingleFileGenerator.java b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/SingleFileGenerator.java index cdc9d9e0b..dbd9318fc 100644 --- a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/SingleFileGenerator.java +++ b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/SingleFileGenerator.java @@ -54,7 +54,7 @@ public static SyntaxTree combineSyntaxTrees(SyntaxTree ...syntaxTrees) { Collection removingImports = new ArrayList<>(); importDeclarationNodes.stream().forEach(importDecNode -> { appendingImportDeclarationNodes.forEach(newImportNode -> { - if (importDecNode.toString().equals(newImportNode.toString())) { + if (importDecNode.toString().trim().equals(newImportNode.toString().trim())) { removingImports.add(newImportNode); } }); diff --git a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/TypeHandler.java b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/TypeHandler.java index 5f4e0141b..628d2255e 100644 --- a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/TypeHandler.java +++ b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/TypeHandler.java @@ -75,6 +75,7 @@ import static io.ballerina.compiler.syntax.tree.SyntaxKind.TYPE_KEYWORD; import static io.ballerina.openapi.core.generators.common.GeneratorConstants.DEFAULT_STATUS; import static io.ballerina.openapi.core.generators.common.GeneratorConstants.DEFAULT_STATUS_CODE_RESPONSE; +import static io.ballerina.openapi.core.generators.common.GeneratorUtils.HTTP_IMPORT; public class TypeHandler { private static TypeHandler typeHandlerInstance; @@ -144,7 +145,7 @@ private NodeList generateImportNodes() { Set importDeclarationNodes = new LinkedHashSet<>(); // Imports for the http module, when record has http type inclusions. if (!typeDefinitionNodes.isEmpty()) { - importsForTypeDefinitions(importDeclarationNodes); + importsForTypeDefinitions(imports); } //Imports for constraints if (!imports.isEmpty()) { @@ -159,17 +160,13 @@ private NodeList generateImportNodes() { return createNodeList(importDeclarationNodes); } - private void importsForTypeDefinitions(Set imports) { + private void importsForTypeDefinitions(Set imports) { for (TypeDefinitionNode node : typeDefinitionNodes.values()) { if (!(node.typeDescriptor() instanceof RecordTypeDescriptorNode)) { continue; } - boolean isHttpImportExist = imports.stream().anyMatch(importNode -> importNode.moduleName().stream() - .anyMatch(moduleName -> moduleName.text().equals(GeneratorConstants.HTTP))); - if (node.typeName().text().equals(GeneratorConstants.CONNECTION_CONFIG) && !isHttpImportExist) { - ImportDeclarationNode importForHttp = GeneratorUtils.getImportDeclarationNode( - GeneratorConstants.BALLERINA, GeneratorConstants.HTTP); - imports.add(importForHttp); + if (node.typeName().text().equals(GeneratorConstants.CONNECTION_CONFIG)) { + imports.add(HTTP_IMPORT); } RecordTypeDescriptorNode record = (RecordTypeDescriptorNode) node.typeDescriptor(); for (Node field : record.fields()) { @@ -180,11 +177,8 @@ private void importsForTypeDefinitions(Set imports) { TypeReferenceNode recordField = (TypeReferenceNode) field; QualifiedNameReferenceNode typeInclusion = (QualifiedNameReferenceNode) recordField.typeName(); - if (!isHttpImportExist && typeInclusion.modulePrefix().text().equals(GeneratorConstants.HTTP)) { - ImportDeclarationNode importForHttp = GeneratorUtils.getImportDeclarationNode( - GeneratorConstants.BALLERINA, - GeneratorConstants.HTTP); - imports.add(importForHttp); + if (typeInclusion.modulePrefix().text().equals(GeneratorConstants.HTTP)) { + imports.add(HTTP_IMPORT); break; } } diff --git a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/service/parameter/HeaderParameterGenerator.java b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/service/parameter/HeaderParameterGenerator.java index 5e7ad183a..e9285d6ef 100644 --- a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/service/parameter/HeaderParameterGenerator.java +++ b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/service/parameter/HeaderParameterGenerator.java @@ -58,7 +58,7 @@ import static io.ballerina.compiler.syntax.tree.NodeFactory.createSimpleNameReferenceNode; import static io.ballerina.compiler.syntax.tree.SyntaxKind.CLOSE_PAREN_TOKEN; import static io.ballerina.compiler.syntax.tree.SyntaxKind.OPEN_PAREN_TOKEN; -import static io.ballerina.openapi.core.generators.common.GeneratorConstants.HEADER; +import static io.ballerina.openapi.core.generators.common.GeneratorConstants.HEADER_ANNOTATION; import static io.ballerina.openapi.core.generators.common.GeneratorConstants.NILLABLE; import static io.ballerina.openapi.core.generators.common.GeneratorUtils.convertOpenAPITypeToBallerina; import static io.ballerina.openapi.core.generators.common.GeneratorUtils.escapeIdentifier; @@ -87,10 +87,11 @@ public ParameterNode generateParameterNode(Parameter parameter) throws Unsupport String paramName = parameter.getName().trim(); AnnotationNode headerNode = ServiceGenerationUtils.getAnnotationNode(GeneratorConstants.HEADER_ANNOT, null); NodeList headerAnnotations = createNodeList(headerNode); - Optional nameFromExt = GeneratorUtils.getBallerinaNameExtension(schema); + Optional nameFromExt = GeneratorUtils.getBallerinaNameExtension(parameter); if (nameFromExt.isPresent()) { paramName = nameFromExt.get(); - headerAnnotations = createNodeList(GeneratorUtils.getNameAnnotationNode(schema, HEADER)); + headerAnnotations = createNodeList(GeneratorUtils.getNameAnnotationNode(parameter.getName(), + HEADER_ANNOTATION)); } String headerType = GeneratorConstants.STRING; TypeDescriptorNode headerTypeName; diff --git a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/service/parameter/QueryParameterGenerator.java b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/service/parameter/QueryParameterGenerator.java index 281bf8cc1..f118b466a 100644 --- a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/service/parameter/QueryParameterGenerator.java +++ b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/service/parameter/QueryParameterGenerator.java @@ -58,7 +58,7 @@ import static io.ballerina.compiler.syntax.tree.NodeFactory.createOptionalTypeDescriptorNode; import static io.ballerina.compiler.syntax.tree.NodeFactory.createRequiredParameterNode; import static io.ballerina.compiler.syntax.tree.NodeFactory.createSimpleNameReferenceNode; -import static io.ballerina.openapi.core.generators.common.GeneratorConstants.QUERY; +import static io.ballerina.openapi.core.generators.common.GeneratorConstants.QUERY_ANNOTATION; import static io.ballerina.openapi.core.generators.common.GeneratorConstants.STRING; import static io.ballerina.openapi.core.generators.common.GeneratorUtils.convertOpenAPITypeToBallerina; import static io.ballerina.openapi.core.generators.common.GeneratorUtils.escapeIdentifier; @@ -87,10 +87,10 @@ public ParameterNode generateParameterNode(Parameter parameter) throws InvalidRe Schema schema = parameter.getSchema(); String paramName = parameter.getName().trim(); NodeList annotations = createEmptyNodeList(); - Optional nameFromExt = GeneratorUtils.getBallerinaNameExtension(schema); + Optional nameFromExt = GeneratorUtils.getBallerinaNameExtension(parameter); if (nameFromExt.isPresent()) { paramName = nameFromExt.get(); - annotations = createNodeList(GeneratorUtils.getNameAnnotationNode(schema, QUERY)); + annotations = createNodeList(GeneratorUtils.getNameAnnotationNode(parameter.getName(), QUERY_ANNOTATION)); } IdentifierToken parameterName = createIdentifierToken( GeneratorUtils.escapeIdentifier(paramName), diff --git a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/type/generators/RecordTypeGenerator.java b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/type/generators/RecordTypeGenerator.java index 915fb6567..08cbc8e66 100644 --- a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/type/generators/RecordTypeGenerator.java +++ b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/type/generators/RecordTypeGenerator.java @@ -284,7 +284,7 @@ public ImmutablePair, Set> updateRecordFieldListWithImports( Optional fieldNameFromExt = GeneratorUtils.getBallerinaNameExtension(fieldSchema); if (fieldNameFromExt.isPresent()) { fieldName = createIdentifierToken(fieldNameFromExt.get()); - metadataNode = GeneratorUtils.getNameAnnotationMetadataNode(fieldSchema); + metadataNode = GeneratorUtils.getNameAnnotationMetadataNode(field.getKey(), fieldSchema); } if (required != null && required.contains(field.getKey().trim())) { setRequiredFields(recordFieldList, fieldSchema, fieldName, fieldTypeName, metadataNode); From 2266979a2272f7cfa4e3e3698b5cf9e89459c796 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 19 Sep 2024 14:43:23 +0530 Subject: [PATCH 6/8] Add test cases --- .../ballerina/openapi/CodeGeneratorTest.java | 76 ++++ .../src/test/resources/bal_name_ext.yaml | 189 +++++++++ .../resources/bal_name_ext_sanitized.yaml | 202 ++++++++++ .../expected_gen/bal_name_ext_client.bal | 372 ++++++++++++++++++ .../bal_name_ext_service_contract.bal | 51 +++ 5 files changed, 890 insertions(+) create mode 100644 openapi-cli/src/test/resources/bal_name_ext.yaml create mode 100644 openapi-cli/src/test/resources/bal_name_ext_sanitized.yaml create mode 100644 openapi-cli/src/test/resources/expected_gen/bal_name_ext_client.bal create mode 100644 openapi-cli/src/test/resources/expected_gen/bal_name_ext_service_contract.bal diff --git a/openapi-cli/src/test/java/io/ballerina/openapi/CodeGeneratorTest.java b/openapi-cli/src/test/java/io/ballerina/openapi/CodeGeneratorTest.java index 66478a699..4511c9c11 100644 --- a/openapi-cli/src/test/java/io/ballerina/openapi/CodeGeneratorTest.java +++ b/openapi-cli/src/test/java/io/ballerina/openapi/CodeGeneratorTest.java @@ -920,6 +920,82 @@ public void testDefaultHeadersNameConflictWithQuery() { } } + @Test + public void testBallerinaNameExtensionInClient() { + String definitionPath = RES_DIR.resolve("bal_name_ext_sanitized.yaml").toString(); + BallerinaCodeGenerator generator = new BallerinaCodeGenerator(); + try { + String expectedClientContent = getStringFromGivenBalFile(expectedDirPath, "bal_name_ext_client.bal"); + generator.generateClient(definitionPath, resourcePath.toString(), filter, + new ClientGeneratorOptions(false, true, false, false, + true, false)); + if (Files.exists(resourcePath.resolve("client.bal"))) { + String generatedClient = getStringFromGivenBalFile(resourcePath, "client.bal"); + generatedClient = (generatedClient.trim()).replaceAll("\\s+", ""); + expectedClientContent = (expectedClientContent.trim()).replaceAll("\\s+", ""); + Assert.assertTrue(generatedClient.contains(expectedClientContent)); + } else { + Assert.fail("Client was not generated"); + } + } catch (IOException | BallerinaOpenApiException | + OASTypeGenException | FormatterException e) { + Assert.fail("Error while generating the client: " + e.getMessage()); + } finally { + deleteGeneratedFiles("client.bal"); + } + } + + @Test + public void testBallerinaNameExtensionWithSanitization() { + String definitionPath = RES_DIR.resolve("bal_name_ext.yaml").toString(); + BallerinaCodeGenerator generator = new BallerinaCodeGenerator(); + try { + String expectedClientContent = getStringFromGivenBalFile(expectedDirPath, "bal_name_ext_client.bal"); + generator.generateClient(definitionPath, resourcePath.toString(), filter, + new ClientGeneratorOptions(false, true, false, false, + true, true)); + if (Files.exists(resourcePath.resolve("client.bal"))) { + String generatedClient = getStringFromGivenBalFile(resourcePath, "client.bal"); + generatedClient = (generatedClient.trim()).replaceAll("\\s+", ""); + expectedClientContent = (expectedClientContent.trim()).replaceAll("\\s+", ""); + Assert.assertTrue(generatedClient.contains(expectedClientContent)); + } else { + Assert.fail("Client was not generated"); + } + } catch (IOException | BallerinaOpenApiException | + OASTypeGenException | FormatterException e) { + Assert.fail("Error while generating the client: " + e.getMessage()); + } finally { + deleteGeneratedFiles("client.bal"); + } + } + + @Test + public void testBallerinaNameExtensionInService() { + String definitionPath = RES_DIR.resolve("bal_name_ext_sanitized.yaml").toString(); + BallerinaCodeGenerator generator = new BallerinaCodeGenerator(); + try { + String serviceName = "bal_name_ext"; + String expectedServiceContractContent = getStringFromGivenBalFile( + expectedDirPath, "bal_name_ext_service_contract.bal"); + ServiceGeneratorOptions options = new ServiceGeneratorOptions(false, false, + true, false, true, false); + generator.generateService(definitionPath, serviceName, resourcePath.toString(), filter, options); + if (Files.exists(resourcePath.resolve("bal_name_ext_service.bal"))) { + String generatedServiceContract = getStringFromGivenBalFile(resourcePath, "bal_name_ext_service.bal"); + generatedServiceContract = (generatedServiceContract.trim()).replaceAll("\\s+", ""); + expectedServiceContractContent = (expectedServiceContractContent.trim()).replaceAll("\\s+", ""); + Assert.assertTrue(generatedServiceContract.contains(expectedServiceContractContent)); + } else { + Assert.fail("Service contract was not generated"); + } + } catch (IOException | BallerinaOpenApiException | FormatterException e) { + Assert.fail("Error while generating the service contract: " + e.getMessage()); + } finally { + deleteGeneratedFiles("bal_name_ext_sanitized_service.bal"); + } + } + private String getStringFromGivenBalFile(Path expectedServiceFile, String s) throws IOException { Stream expectedServiceLines = Files.lines(expectedServiceFile.resolve(s)); diff --git a/openapi-cli/src/test/resources/bal_name_ext.yaml b/openapi-cli/src/test/resources/bal_name_ext.yaml new file mode 100644 index 000000000..522fcba50 --- /dev/null +++ b/openapi-cli/src/test/resources/bal_name_ext.yaml @@ -0,0 +1,189 @@ +openapi: 3.0.1 +info: + title: Api V1 + version: 0.0.0 +servers: + - url: "http://{server}:{port}/api/v1" + variables: + server: + default: localhost + port: + default: "8080" +paths: + /albums: + get: + tags: + - albums + operationId: getAlbums + parameters: + - name: _artists_ + in: query + schema: + type: array + items: + type: string + default: [] + - name: X-API-VERSION + in: header + schema: + type: string + default: v1 + responses: + "200": + description: Ok + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/album" + "400": + description: BadRequest + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorPayload" + post: + tags: + - albums + operationId: postAlbum + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/album" + required: true + responses: + "201": + description: Created + content: + application/json: + schema: + $ref: "#/components/schemas/album" + "400": + description: BadRequest + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorPayload" + /albums/{_id}: + get: + tags: + - albums + operationId: getAlbumById + parameters: + - name: _id + in: path + required: true + schema: + type: string + responses: + "200": + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/album" + "404": + description: NotFound + content: + application/json: + schema: + $ref: "#/components/schemas/message" + "400": + description: BadRequest + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorPayload" + /albums/{id}/artist: + get: + tags: + - artists + operationId: getArtistByAlbum + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + "200": + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/album_aRTIST" + "400": + description: BadRequest + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorPayload" +components: + schemas: + ErrorPayload: + required: + - message + - method + - path + - reason + - status + - timestamp + type: object + properties: + timestamp: + type: string + status: + type: integer + format: int64 + reason: + type: string + message: + type: string + path: + type: string + method: + type: string + album: + required: + - _id + - artist + - title + type: object + properties: + _id: + type: string + title: + type: string + artist: + type: string + additionalProperties: false + album_aRTIST: + required: + - albums + - id + - name + type: object + properties: + id: + type: string + name: + type: string + albums: + type: array + items: + $ref: "#/components/schemas/album" + additionalProperties: false + message: + required: + - code + - message + type: object + properties: + message: + type: string + code: + type: integer + format: int64 + additionalProperties: false diff --git a/openapi-cli/src/test/resources/bal_name_ext_sanitized.yaml b/openapi-cli/src/test/resources/bal_name_ext_sanitized.yaml new file mode 100644 index 000000000..27ed58694 --- /dev/null +++ b/openapi-cli/src/test/resources/bal_name_ext_sanitized.yaml @@ -0,0 +1,202 @@ +openapi: 3.0.1 +info: + title: Api V1 + version: 0.0.0 +servers: + - url: "http://{server}:{port}/api/v1" + variables: + server: + default: localhost + port: + default: "8080" +paths: + /albums: + get: + tags: + - albums + operationId: getAlbums + parameters: + - name: _artists_ + in: query + required: false + style: form + explode: true + schema: + type: array + items: + type: string + default: [] + x-ballerina-name: artists + - name: X-API-VERSION + in: header + required: false + style: simple + explode: false + schema: + type: string + default: v1 + x-ballerina-name: xAPIVERSION + responses: + "200": + description: Ok + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Album" + "400": + description: BadRequest + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorPayload" + post: + tags: + - albums + operationId: postAlbum + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/Album" + required: true + responses: + "201": + description: Created + content: + application/json: + schema: + $ref: "#/components/schemas/Album" + "400": + description: BadRequest + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorPayload" + /albums/{id}: + get: + tags: + - albums + operationId: getAlbumById + parameters: + - name: id + in: path + required: true + style: simple + explode: false + schema: + type: string + responses: + "200": + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/Album" + "404": + description: NotFound + content: + application/json: + schema: + $ref: "#/components/schemas/Message" + "400": + description: BadRequest + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorPayload" + /albums/{id}/artist: + get: + tags: + - artists + operationId: getArtistByAlbum + parameters: + - name: id + in: path + required: true + style: simple + explode: false + schema: + type: string + responses: + "200": + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/AlbumARTIST" + "400": + description: BadRequest + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorPayload" +components: + schemas: + AlbumARTIST: + required: + - albums + - id + - name + type: object + properties: + albums: + type: array + items: + $ref: "#/components/schemas/Album" + name: + type: string + id: + type: string + additionalProperties: false + ErrorPayload: + required: + - message + - method + - path + - reason + - status + - timestamp + type: object + properties: + reason: + type: string + path: + type: string + method: + type: string + message: + type: string + timestamp: + type: string + status: + type: integer + format: int64 + Message: + required: + - code + - message + type: object + properties: + code: + type: integer + format: int64 + message: + type: string + additionalProperties: false + Album: + required: + - _id + - artist + - title + type: object + properties: + artist: + type: string + _id: + type: string + x-ballerina-name: id + title: + type: string + additionalProperties: false diff --git a/openapi-cli/src/test/resources/expected_gen/bal_name_ext_client.bal b/openapi-cli/src/test/resources/expected_gen/bal_name_ext_client.bal new file mode 100644 index 000000000..055968c1e --- /dev/null +++ b/openapi-cli/src/test/resources/expected_gen/bal_name_ext_client.bal @@ -0,0 +1,372 @@ +// AUTO-GENERATED FILE. DO NOT MODIFY. +// This file is auto-generated by the Ballerina OpenAPI tool. + +import ballerina/data.jsondata; +import ballerina/http; +import ballerina/url; + +public isolated client class Client { + final http:Client clientEp; + # Gets invoked to initialize the `connector`. + # + # + config - The configurations to be used when initializing the `connector` + # + serviceUrl - URL of the target service + # + return - An error if connector initialization failed + public isolated function init(ConnectionConfig config = {}, string serviceUrl = "http://localhost:8080/api/v1") returns error? { + http:ClientConfiguration httpClientConfig = {httpVersion: config.httpVersion, timeout: config.timeout, forwarded: config.forwarded, poolConfig: config.poolConfig, compression: config.compression, circuitBreaker: config.circuitBreaker, retryConfig: config.retryConfig, validation: config.validation}; + do { + if config.http1Settings is ClientHttp1Settings { + ClientHttp1Settings settings = check config.http1Settings.ensureType(ClientHttp1Settings); + httpClientConfig.http1Settings = {...settings}; + } + if config.http2Settings is http:ClientHttp2Settings { + httpClientConfig.http2Settings = check config.http2Settings.ensureType(http:ClientHttp2Settings); + } + if config.cache is http:CacheConfig { + httpClientConfig.cache = check config.cache.ensureType(http:CacheConfig); + } + if config.responseLimits is http:ResponseLimitConfigs { + httpClientConfig.responseLimits = check config.responseLimits.ensureType(http:ResponseLimitConfigs); + } + if config.secureSocket is http:ClientSecureSocket { + httpClientConfig.secureSocket = check config.secureSocket.ensureType(http:ClientSecureSocket); + } + if config.proxy is http:ProxyConfig { + httpClientConfig.proxy = check config.proxy.ensureType(http:ProxyConfig); + } + } + http:Client httpEp = check new (serviceUrl, httpClientConfig); + self.clientEp = httpEp; + return; + } + + # + headers - Headers to be sent with the request + # + queries - Queries to be sent with the request + # + return - Ok + resource isolated function get albums(GetAlbumsHeaders headers = {}, *GetAlbumsQueries queries) returns Album[]|error { + string resourcePath = string `/albums`; + map queryParamEncoding = {"_artists_": {style: FORM, explode: true}}; + resourcePath = resourcePath + check getPathForQueryParam(queries, queryParamEncoding); + map httpHeaders = getMapForHeaders(headers); + return self.clientEp->get(resourcePath, httpHeaders); + } + + # + headers - Headers to be sent with the request + # + return - Ok + resource isolated function get albums/[string id](map headers = {}) returns Album|error { + string resourcePath = string `/albums/${getEncodedUri(id)}`; + return self.clientEp->get(resourcePath, headers); + } + + # + headers - Headers to be sent with the request + # + return - Ok + resource isolated function get albums/[string id]/artist(map headers = {}) returns AlbumARTIST|error { + string resourcePath = string `/albums/${getEncodedUri(id)}/artist`; + return self.clientEp->get(resourcePath, headers); + } + + # + headers - Headers to be sent with the request + # + return - Created + resource isolated function post albums(Album payload, map headers = {}) returns Album|error { + string resourcePath = string `/albums`; + http:Request request = new; + json jsonBody = payload.toJson(); + request.setPayload(jsonBody, "application/json"); + return self.clientEp->post(resourcePath, request, headers); + } +} + +type SimpleBasicType string|boolean|int|float|decimal; + +# Represents encoding mechanism details. +type Encoding record { + # Defines how multiple values are delimited + string style = FORM; + # Specifies whether arrays and objects should generate as separate fields + boolean explode = true; + # Specifies the custom content type + string contentType?; + # Specifies the custom headers + map headers?; +}; + +enum EncodingStyle { + DEEPOBJECT, FORM, SPACEDELIMITED, PIPEDELIMITED +} + +final Encoding & readonly defaultEncoding = {}; + +# Serialize the record according to the deepObject style. +# +# + parent - Parent record name +# + anyRecord - Record to be serialized +# + return - Serialized record as a string +isolated function getDeepObjectStyleRequest(string parent, record {} anyRecord) returns string { + string[] recordArray = []; + foreach [string, anydata] [key, value] in anyRecord.entries() { + if value is SimpleBasicType { + recordArray.push(parent + "[" + key + "]" + "=" + getEncodedUri(value.toString())); + } else if value is SimpleBasicType[] { + recordArray.push(getSerializedArray(parent + "[" + key + "]" + "[]", value, DEEPOBJECT, true)); + } else if value is record {} { + string nextParent = parent + "[" + key + "]"; + recordArray.push(getDeepObjectStyleRequest(nextParent, value)); + } else if value is record {}[] { + string nextParent = parent + "[" + key + "]"; + recordArray.push(getSerializedRecordArray(nextParent, value, DEEPOBJECT)); + } + recordArray.push("&"); + } + _ = recordArray.pop(); + return string:'join("", ...recordArray); +} + +# Serialize the record according to the form style. +# +# + parent - Parent record name +# + anyRecord - Record to be serialized +# + explode - Specifies whether arrays and objects should generate separate parameters +# + return - Serialized record as a string +isolated function getFormStyleRequest(string parent, record {} anyRecord, boolean explode = true) returns string { + string[] recordArray = []; + if explode { + foreach [string, anydata] [key, value] in anyRecord.entries() { + if value is SimpleBasicType { + recordArray.push(key, "=", getEncodedUri(value.toString())); + } else if value is SimpleBasicType[] { + recordArray.push(getSerializedArray(key, value, explode = explode)); + } else if value is record {} { + recordArray.push(getFormStyleRequest(parent, value, explode)); + } + recordArray.push("&"); + } + _ = recordArray.pop(); + } else { + foreach [string, anydata] [key, value] in anyRecord.entries() { + if value is SimpleBasicType { + recordArray.push(key, ",", getEncodedUri(value.toString())); + } else if value is SimpleBasicType[] { + recordArray.push(getSerializedArray(key, value, explode = false)); + } else if value is record {} { + recordArray.push(getFormStyleRequest(parent, value, explode)); + } + recordArray.push(","); + } + _ = recordArray.pop(); + } + return string:'join("", ...recordArray); +} + +# Serialize arrays. +# +# + arrayName - Name of the field with arrays +# + anyArray - Array to be serialized +# + style - Defines how multiple values are delimited +# + explode - Specifies whether arrays and objects should generate separate parameters +# + return - Serialized array as a string +isolated function getSerializedArray(string arrayName, anydata[] anyArray, string style = "form", boolean explode = true) returns string { + string key = arrayName; + string[] arrayValues = []; + if anyArray.length() > 0 { + if style == FORM && !explode { + arrayValues.push(key, "="); + foreach anydata i in anyArray { + arrayValues.push(getEncodedUri(i.toString()), ","); + } + } else if style == SPACEDELIMITED && !explode { + arrayValues.push(key, "="); + foreach anydata i in anyArray { + arrayValues.push(getEncodedUri(i.toString()), "%20"); + } + } else if style == PIPEDELIMITED && !explode { + arrayValues.push(key, "="); + foreach anydata i in anyArray { + arrayValues.push(getEncodedUri(i.toString()), "|"); + } + } else if style == DEEPOBJECT { + foreach anydata i in anyArray { + arrayValues.push(key, "[]", "=", getEncodedUri(i.toString()), "&"); + } + } else { + foreach anydata i in anyArray { + arrayValues.push(key, "=", getEncodedUri(i.toString()), "&"); + } + } + _ = arrayValues.pop(); + } + return string:'join("", ...arrayValues); +} + +# Serialize the array of records according to the form style. +# +# + parent - Parent record name +# + value - Array of records to be serialized +# + style - Defines how multiple values are delimited +# + explode - Specifies whether arrays and objects should generate separate parameters +# + return - Serialized record as a string +isolated function getSerializedRecordArray(string parent, record {}[] value, string style = FORM, boolean explode = true) returns string { + string[] serializedArray = []; + if style == DEEPOBJECT { + int arayIndex = 0; + foreach var recordItem in value { + serializedArray.push(getDeepObjectStyleRequest(parent + "[" + arayIndex.toString() + "]", recordItem), "&"); + arayIndex = arayIndex + 1; + } + } else { + if !explode { + serializedArray.push(parent, "="); + } + foreach var recordItem in value { + serializedArray.push(getFormStyleRequest(parent, recordItem, explode), ","); + } + } + _ = serializedArray.pop(); + return string:'join("", ...serializedArray); +} + +# Get Encoded URI for a given value. +# +# + value - Value to be encoded +# + return - Encoded string +isolated function getEncodedUri(anydata value) returns string { + string|error encoded = url:encode(value.toString(), "UTF8"); + if encoded is string { + return encoded; + } else { + return value.toString(); + } +} + +# Generate query path with query parameter. +# +# + queryParam - Query parameter map +# + encodingMap - Details on serialization mechanism +# + return - Returns generated Path or error at failure of client initialization +isolated function getPathForQueryParam(map queryParam, map encodingMap = {}) returns string|error { + string[] param = []; + if queryParam.length() > 0 { + param.push("?"); + foreach var [key, value] in queryParam.entries() { + if value is () { + _ = queryParam.remove(key); + continue; + } + Encoding encodingData = encodingMap.hasKey(key) ? encodingMap.get(key) : defaultEncoding; + if value is SimpleBasicType { + param.push(key, "=", getEncodedUri(value.toString())); + } else if value is SimpleBasicType[] { + param.push(getSerializedArray(key, value, encodingData.style, encodingData.explode)); + } else if value is record {} { + if encodingData.style == DEEPOBJECT { + param.push(getDeepObjectStyleRequest(key, value)); + } else { + param.push(getFormStyleRequest(key, value, encodingData.explode)); + } + } else { + param.push(key, "=", value.toString()); + } + param.push("&"); + } + _ = param.pop(); + } + string restOfPath = string:'join("", ...param); + return restOfPath; +} + +# Generate header map for given header values. +# +# + headerParam - Headers map +# + return - Returns generated map or error at failure of client initialization +isolated function getMapForHeaders(map headerParam) returns map { + map headerMap = {}; + foreach var [key, value] in headerParam.entries() { + if value is SimpleBasicType[] { + headerMap[key] = from SimpleBasicType data in value + select data.toString(); + } else { + headerMap[key] = value.toString(); + } + } + return headerMap; +} + +public type AlbumARTIST record {| + Album[] albums; + string name; + string id; +|}; + +# Provides settings related to HTTP/1.x protocol. +public type ClientHttp1Settings record {| + # Specifies whether to reuse a connection for multiple requests + http:KeepAlive keepAlive = http:KEEPALIVE_AUTO; + # The chunking behaviour of the request + http:Chunking chunking = http:CHUNKING_AUTO; + # Proxy server related options + ProxyConfig proxy?; +|}; + +public type Album record {| + string artist; + @jsondata:Name {value: "_id"} + string id; + string title; +|}; + +# Represents the Queries record for the operation: getAlbums +public type GetAlbumsQueries record { + @http:Query {name: "_artists_"} + string[] artists = []; +}; + +# Proxy server configurations to be used with the HTTP client endpoint. +public type ProxyConfig record {| + # Host name of the proxy server + string host = ""; + # Proxy server port + int port = 0; + # Proxy server username + string userName = ""; + # Proxy server password + @display {label: "", kind: "password"} + string password = ""; +|}; + +# Represents the Headers record for the operation: getAlbums +public type GetAlbumsHeaders record { + @http:Header {name: "X-API-VERSION"} + string xAPIVERSION = "v1"; +}; + +# Provides a set of configurations for controlling the behaviours when communicating with a remote HTTP endpoint. +@display {label: "Connection Config"} +public type ConnectionConfig record {| + # The HTTP version understood by the client + http:HttpVersion httpVersion = http:HTTP_2_0; + # Configurations related to HTTP/1.x protocol + ClientHttp1Settings http1Settings?; + # Configurations related to HTTP/2 protocol + http:ClientHttp2Settings http2Settings?; + # The maximum time to wait (in seconds) for a response before closing the connection + decimal timeout = 60; + # The choice of setting `forwarded`/`x-forwarded` header + string forwarded = "disable"; + # Configurations associated with request pooling + http:PoolConfiguration poolConfig?; + # HTTP caching related configurations + http:CacheConfig cache?; + # Specifies the way of handling compression (`accept-encoding`) header + http:Compression compression = http:COMPRESSION_AUTO; + # Configurations associated with the behaviour of the Circuit Breaker + http:CircuitBreakerConfig circuitBreaker?; + # Configurations associated with retrying + http:RetryConfig retryConfig?; + # Configurations associated with inbound response size limits + http:ResponseLimitConfigs responseLimits?; + # SSL/TLS-related options + http:ClientSecureSocket secureSocket?; + # Proxy server related options + http:ProxyConfig proxy?; + # Enables the inbound payload validation functionality which provided by the constraint package. Enabled by default + boolean validation = true; +|}; diff --git a/openapi-cli/src/test/resources/expected_gen/bal_name_ext_service_contract.bal b/openapi-cli/src/test/resources/expected_gen/bal_name_ext_service_contract.bal new file mode 100644 index 000000000..0de481531 --- /dev/null +++ b/openapi-cli/src/test/resources/expected_gen/bal_name_ext_service_contract.bal @@ -0,0 +1,51 @@ +// AUTO-GENERATED FILE. +// This file is auto-generated by the Ballerina OpenAPI tool. + +import ballerina/data.jsondata; +import ballerina/http; + +@http:ServiceConfig {basePath: "/api/v1"} +type OASServiceType service object { + *http:ServiceContract; + resource function get albums(@http:Query {name: "_artists_"} string[] artists = [], @http:Header {name: "X-API-VERSION"} string? xAPIVERSION = "v1") returns Album[]|ErrorPayloadBadRequest; + resource function post albums(@http:Payload Album payload) returns Album|ErrorPayloadBadRequest; + resource function get albums/[string id]() returns Album|MessageNotFound|ErrorPayloadBadRequest; + resource function get albums/[string id]/artist() returns AlbumARTIST|ErrorPayloadBadRequest; +}; + +public type AlbumARTIST record {| + Album[] albums; + string name; + string id; +|}; + +public type ErrorPayloadBadRequest record {| + *http:BadRequest; + ErrorPayload body; +|}; + +public type ErrorPayload record { + string reason; + string path; + string method; + string message; + string timestamp; + int status; +}; + +public type Message record {| + int code; + string message; +|}; + +public type Album record {| + string artist; + @jsondata:Name {value: "_id"} + string id; + string title; +|}; + +public type MessageNotFound record {| + *http:NotFound; + Message body; +|}; From bdbb2ec58e1b51247d307d4462d79d7c7c863716 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 19 Sep 2024 15:03:57 +0530 Subject: [PATCH 7/8] Fix test failures --- .../src/test/resources/expected_gen/sanitize_array_member.bal | 4 ++-- .../test/resources/expected_gen/type_name_with_mixed_case.bal | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openapi-cli/src/test/resources/expected_gen/sanitize_array_member.bal b/openapi-cli/src/test/resources/expected_gen/sanitize_array_member.bal index 7c972f5a5..2a29fd307 100644 --- a/openapi-cli/src/test/resources/expected_gen/sanitize_array_member.bal +++ b/openapi-cli/src/test/resources/expected_gen/sanitize_array_member.bal @@ -311,8 +311,8 @@ public type ProxyConfig record {| # Represents the Headers record for the operation: getAlbums public type GetAlbumsHeaders record { - # API Version - VERSION API\-VERSION?; + @http:Header {name: "API-VERSION"} + VERSION aPIVERSION?; }; # Provides a set of configurations for controlling the behaviours when communicating with a remote HTTP endpoint. diff --git a/openapi-cli/src/test/resources/expected_gen/type_name_with_mixed_case.bal b/openapi-cli/src/test/resources/expected_gen/type_name_with_mixed_case.bal index a54b92a74..4ff6dc44f 100644 --- a/openapi-cli/src/test/resources/expected_gen/type_name_with_mixed_case.bal +++ b/openapi-cli/src/test/resources/expected_gen/type_name_with_mixed_case.bal @@ -311,8 +311,8 @@ public type ProxyConfig record {| # Represents the Headers record for the operation: getAlbums public type GetAlbumsHeaders record { - # API Version - VERSION API\-VERSION?; + @http:Header {name: "API-VERSION"} + VERSION aPIVERSION?; }; # Provides a set of configurations for controlling the behaviours when communicating with a remote HTTP endpoint. From 5103a101bbac38312604d132cf4866f1c9df4bb9 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 19 Sep 2024 15:08:59 +0530 Subject: [PATCH 8/8] Fix spot bug issues --- .../openapi/core/generators/common/GeneratorUtils.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/GeneratorUtils.java b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/GeneratorUtils.java index 04850b089..fba4b160d 100644 --- a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/GeneratorUtils.java +++ b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/GeneratorUtils.java @@ -481,13 +481,12 @@ public static Optional getBallerinaNameExtension(Parameter parameter) { } public static Optional getBallerinaNameExtension(Schema schema) { - if (Objects.isNull(schema) || Objects.isNull(schema.getExtensions())) { - return Optional.empty(); - } - return getBallerinaNameExtension(schema.getExtensions()); + return Optional.ofNullable(schema) + .map(Schema::getExtensions) + .flatMap(GeneratorUtils::getBallerinaNameExtension); } - public static Optional getBallerinaNameExtension(Map extensions) { + public static Optional getBallerinaNameExtension(Map extensions) { return Optional.ofNullable(extensions) .map(ext -> ext.get(X_BALLERINA_NAME)) .filter(String.class::isInstance)