From 870d81a7c5dc11053335c4f431635743f6500272 Mon Sep 17 00:00:00 2001 From: myznikov Date: Wed, 5 Feb 2025 16:05:59 +0300 Subject: [PATCH 1/6] feat: added templates for Java + Spring. added new params for generation --- .../OperationGenerationModel.swift | 6 +- .../CodeGenerator/Models/PropertyModel.swift | 20 ++++++- .../Models/ServiceMethod/OperationModel.swift | 8 ++- .../TreeParserStage/TreeParser/Resolver.swift | 16 ++++- .../TreeParser/TreeParser.swift | 7 ++- .../Builders/AnySchemaBuilder.swift | 58 ++++++++++++++----- .../Builders/AnyServiceBuilder.swift | 8 +-- Sources/GASTTree/PropertyNode.swift | 17 +++++- Sources/GASTTree/Services/OperationNode.swift | 7 ++- .../OperationGenerationModel.swift | 14 +++-- .../Parsers/OperationNodeParser.swift | 12 ++-- Templates/v2/Java/api.stencil | 25 ++++++++ Templates/v2/Java/config/java.config.yaml | 31 ++++++++++ Templates/v2/Java/controller.stencil | 45 ++++++++++++++ Templates/v2/Java/dto.stencil | 24 ++++++++ Templates/v2/Java/enum.stencil | 30 ++++++++++ 16 files changed, 287 insertions(+), 41 deletions(-) create mode 100644 Templates/v2/Java/api.stencil create mode 100644 Templates/v2/Java/config/java.config.yaml create mode 100644 Templates/v2/Java/controller.stencil create mode 100644 Templates/v2/Java/dto.stencil create mode 100644 Templates/v2/Java/enum.stencil diff --git a/Sources/CodeGenerator/Models/GenerationModels/OperationGenerationModel.swift b/Sources/CodeGenerator/Models/GenerationModels/OperationGenerationModel.swift index fc6ea8c8..56cd2209 100644 --- a/Sources/CodeGenerator/Models/GenerationModels/OperationGenerationModel.swift +++ b/Sources/CodeGenerator/Models/GenerationModels/OperationGenerationModel.swift @@ -1,6 +1,6 @@ // // OperationGenerationModel.swift -// +// // // Created by Александр Кравченков on 19.10.2021. // @@ -63,11 +63,13 @@ public struct OperationGenerationModel: Encodable { public let requestModel: Reference? public let pathParameters: [ParameterModel] + public let headerParameters: [ParameterModel] public let queryParameters: [ParameterModel] public let requestGenerationModel: DataGenerationModel? public let responseGenerationModel: Keyed? public let allGenerationResponses: [ResponseGenerationModel]? + public let id: String? init(operationModel: OperationModel) { self.httpMethod = operationModel.httpMethod @@ -82,6 +84,7 @@ public struct OperationGenerationModel: Encodable { .map { $0.value } .sorted { $0.name < $1.name } self.pathParameters = allParameters.filter { $0.location == .path } + self.headerParameters = allParameters.filter { $0.location == .header } self.queryParameters = allParameters.filter { $0.location == .query } let request = operationModel.requestModel?.value @@ -108,6 +111,7 @@ public struct OperationGenerationModel: Encodable { responses: response.values.map { DataGenerationModel(dataModel: $0) } ) } + self.id = operationModel.id } } diff --git a/Sources/CodeGenerator/Models/PropertyModel.swift b/Sources/CodeGenerator/Models/PropertyModel.swift index ddf0207a..e46382ec 100644 --- a/Sources/CodeGenerator/Models/PropertyModel.swift +++ b/Sources/CodeGenerator/Models/PropertyModel.swift @@ -80,6 +80,12 @@ public struct PropertyModel { public let type: PossibleType public let isNullable: Bool public let pattern: String? + public let example: Any? + public let format: String? + public let minimum: Double? + public let maximum: Double? + public let maxLength: Int? + public let minLength: Int? /// This value will be used as type for generation public let typeModel: ItemTypeModel @@ -88,7 +94,13 @@ public struct PropertyModel { description: String?, type: PropertyModel.PossibleType, isNullable: Bool, - pattern: String?) { + pattern: String?, + example: Any?, + format: String?, + minimum: Double?, + maximum: Double?, + minLength: Int?, + maxLength: Int?) { self.name = name self.description = description self.type = type @@ -99,6 +111,12 @@ public struct PropertyModel { enumTypeName: type.enumTypeName, aliasTypeName: type.aliasTypeName) self.pattern = pattern + self.example = example + self.format = format + self.minimum = minimum + self.maximum = maximum + self.minLength = minLength + self.maxLength = maxLength } } diff --git a/Sources/CodeGenerator/Models/ServiceMethod/OperationModel.swift b/Sources/CodeGenerator/Models/ServiceMethod/OperationModel.swift index 94fd48ac..381041b4 100644 --- a/Sources/CodeGenerator/Models/ServiceMethod/OperationModel.swift +++ b/Sources/CodeGenerator/Models/ServiceMethod/OperationModel.swift @@ -1,6 +1,6 @@ // // OperationModel.swift -// +// // // Created by Александр Кравченков on 17.12.2020. // @@ -56,19 +56,21 @@ public struct OperationModel: Encodable { public let parameters: [Reference]? public let responses: [Reference]? public let requestModel: Reference? + public let id: String? init(httpMethod: String, summary: String?, description: String?, parameters: [Reference]?, responses: [Reference]?, - requestModel: Reference?) { + requestModel: Reference?, + id: String?) { self.httpMethod = httpMethod self.summary = summary - self.description = description self.parameters = parameters self.responses = responses self.requestModel = requestModel + self.id = id } } diff --git a/Sources/CodeGenerator/Stages/TreeParserStage/TreeParser/Resolver.swift b/Sources/CodeGenerator/Stages/TreeParserStage/TreeParser/Resolver.swift index 4e1ca357..376df47b 100644 --- a/Sources/CodeGenerator/Stages/TreeParserStage/TreeParser/Resolver.swift +++ b/Sources/CodeGenerator/Stages/TreeParserStage/TreeParser/Resolver.swift @@ -191,13 +191,25 @@ public class Resolver { description: property.description, type: .array(.init(name: "", itemsType: try arrayItemTypeUnwrapper(arr.itemsType))), isNullable: property.nullable, - pattern: property.pattern) + pattern: property.pattern, + example: property.example, + format: property.format, + minimum: property.minimum, + maximum: property.maximum, + minLength: property.minLength, + maxLength: property.maxLength) case .simple(let val): return .init(name: property.name, description: property.description, type: try propertyTypeUnwrapper(val), isNullable: property.nullable, - pattern: property.pattern) + pattern: property.pattern, + example: property.example, + format: property.format, + minimum: property.minimum, + maximum: property.maximum, + minLength: property.minLength, + maxLength: property.maxLength) } } diff --git a/Sources/CodeGenerator/Stages/TreeParserStage/TreeParser/TreeParser.swift b/Sources/CodeGenerator/Stages/TreeParserStage/TreeParser/TreeParser.swift index 331fbf1b..01f774aa 100644 --- a/Sources/CodeGenerator/Stages/TreeParserStage/TreeParser/TreeParser.swift +++ b/Sources/CodeGenerator/Stages/TreeParserStage/TreeParser/TreeParser.swift @@ -1,6 +1,6 @@ // // File.swift -// +// // // Created by Александр Кравченков on 17.12.2020. // @@ -67,7 +67,7 @@ public struct TreeParser { func parse(operation: OperationNode, current: DependencyWithTree, other: [DependencyWithTree]) throws -> OperationModel { let params = try operation.parameters.map { parameter -> Reference in - + return try wrap( self.parametersParser.parse(parameter: parameter, current: current, other: other), message: "While parsing parameter \(parameter.view)") @@ -94,7 +94,8 @@ public struct TreeParser { description: operation.description, parameters: params, responses: responses, - requestModel: requestBody + requestModel: requestBody, + id: operation.id ) } } diff --git a/Sources/GASTBuilder/Builders/AnySchemaBuilder.swift b/Sources/GASTBuilder/Builders/AnySchemaBuilder.swift index 5d25b976..094d95ab 100644 --- a/Sources/GASTBuilder/Builders/AnySchemaBuilder.swift +++ b/Sources/GASTBuilder/Builders/AnySchemaBuilder.swift @@ -161,34 +161,66 @@ public struct AnySchemaBuilder: SchemaBuilder { func build(object: ObjectSchema, meta: Metadata, name: String, apiDefinitionFileRef: String) throws -> SchemaModelNode { let properties = try object.properties.map { property -> PropertyNode in - let type = try wrap(property.schema.extractType(), - message: "In object \(name), in property \(property.name)") - + let schema = property.schema + let type = try wrap( + schema.extractType(), + message: "In object \(name), in property \(property.name)" + ) var isNullable = property.isNullable - if self.useNewNullableDeterminationStrategy { - isNullable = property.schema.metadata.nullable + isNullable = schema.metadata.nullable } var pattern: String? + var format: String? + var minimum: Double? + var maximum: Double? + var minLength: Int? + var maxLength: Int? - switch property.schema.type { + switch schema.type { case .string(let stringSchema): pattern = stringSchema.pattern + format = stringSchema.format?.rawValue + minLength = stringSchema.minLength + maxLength = stringSchema.maxLength + + case .number(let numberSchema): + format = numberSchema.format?.rawValue + minimum = numberSchema.minimum + maximum = numberSchema.maximum + + case .integer(let integerSchema): + format = integerSchema.format?.rawValue + minimum = integerSchema.minimum.flatMap(Double.init) + maximum = integerSchema.maximum.flatMap(Double.init) + default: break } - return PropertyNode(name: property.name, - type: type, - description: property.schema.metadata.description, - example: property.schema.metadata.example, - nullable: isNullable, - pattern: pattern) + return PropertyNode( + name: property.name, + type: type, + description: schema.metadata.description, + example: schema.metadata.example, + nullable: isNullable, + pattern: pattern, + format: format, + minimum: minimum, + maximum: maximum, + maxLength: maxLength, + minLength: minLength + ) } - return SchemaModelNode(name: name, properties: properties, description: meta.description, apiDefinitionFileRef: apiDefinitionFileRef) + return SchemaModelNode( + name: name, + properties: properties, + description: meta.description, + apiDefinitionFileRef: apiDefinitionFileRef + ) } func build(array: ArraySchema, name: String, apiDefinitionFileRef: String) throws -> SchemaArrayNode { diff --git a/Sources/GASTBuilder/Builders/AnyServiceBuilder.swift b/Sources/GASTBuilder/Builders/AnyServiceBuilder.swift index 375a8ae8..b4105d2a 100644 --- a/Sources/GASTBuilder/Builders/AnyServiceBuilder.swift +++ b/Sources/GASTBuilder/Builders/AnyServiceBuilder.swift @@ -1,6 +1,6 @@ // // AnyServiceBuilder.swift -// +// // // Created by Александр Кравченков on 14.12.2020. // @@ -17,7 +17,7 @@ public protocol ServiceBuilder { } /// Default implementation for `ServiceBuilder` -/// +/// /// Builds `path` elements of Open-API spec /// /// **WARNING** @@ -34,7 +34,6 @@ public struct AnyServiceBuilder: ServiceBuilder { let schemaBuilder: SchemaBuilder let requestBodyBuilder: RequestBodyBuilder let responseBuilder: ResponseBuilder - public init(parameterBuilder: ParametersBuilder, schemaBuilder: SchemaBuilder, requestBodyBuilder: RequestBodyBuilder, @@ -93,7 +92,8 @@ extension AnyServiceBuilder { summary: operation.summary, parameters: params, requestBody: requestBody, - responses: responses) + responses: responses, + id: operation.generatedIdentifier) } } diff --git a/Sources/GASTTree/PropertyNode.swift b/Sources/GASTTree/PropertyNode.swift index e9d30922..ede3bedb 100644 --- a/Sources/GASTTree/PropertyNode.swift +++ b/Sources/GASTTree/PropertyNode.swift @@ -20,19 +20,34 @@ public struct PropertyNode { public let example: Any? public let nullable: Bool public let pattern: String? + public let format: String? + public let minimum: Double? + public let maximum: Double? + public let maxLength: Int? + public let minLength: Int? public init(name: String, type: PossibleType, description: String?, example: Any?, nullable: Bool, - pattern: String?) { + pattern: String?, + format: String?, + minimum: Double?, + maximum: Double?, + maxLength: Int?, + minLength: Int?) { self.name = name self.type = type self.description = description self.example = example self.nullable = nullable self.pattern = pattern + self.format = format + self.minimum = minimum + self.maximum = maximum + self.minLength = minLength + self.maxLength = maxLength } } diff --git a/Sources/GASTTree/Services/OperationNode.swift b/Sources/GASTTree/Services/OperationNode.swift index c5713fdd..ca0b5299 100644 --- a/Sources/GASTTree/Services/OperationNode.swift +++ b/Sources/GASTTree/Services/OperationNode.swift @@ -1,6 +1,6 @@ // // OperationNode.swift -// +// // // Created by Александр Кравченков on 14.12.2020. // @@ -27,18 +27,21 @@ public struct OperationNode { public let parameters: [Referenced] public let requestBody: Referenced? public let responses: [ResponseBody] + public let id: String? public init(method: String, description: String?, summary: String?, parameters: [Referenced], requestBody: Referenced?, - responses: [ResponseBody]) { + responses: [ResponseBody], + id: String?) { self.method = method self.description = description self.summary = summary self.parameters = parameters self.requestBody = requestBody self.responses = responses + self.id = id } } diff --git a/Sources/SurfGenKit/Models/GenerationModels/OperationGenerationModel.swift b/Sources/SurfGenKit/Models/GenerationModels/OperationGenerationModel.swift index 92841f42..c3335391 100644 --- a/Sources/SurfGenKit/Models/GenerationModels/OperationGenerationModel.swift +++ b/Sources/SurfGenKit/Models/GenerationModels/OperationGenerationModel.swift @@ -1,6 +1,6 @@ // // OperationGenerationModel.swift -// +// // // Created by Dmitry Demyanov on 04.11.2020. // @@ -11,7 +11,7 @@ enum HttpMethod: String { case patch case put case delete - + var name: String { switch self { case .get, .post, .delete: @@ -29,7 +29,7 @@ enum ResponseBody: Equatable { } public struct OperationGenerationModel { - + private enum Constants { static let multipartModel = "MultipartModel" } @@ -46,11 +46,12 @@ public struct OperationGenerationModel { let hasBody: Bool var requestBody: RequestBodyGenerationModel? + let id: String? private(set) var hasUndefinedResponseBody = false private(set) var hasResponseModel = false private(set) var responseModel: String? - + init(name: String, description: String?, path: PathGenerationModel, @@ -58,7 +59,8 @@ public struct OperationGenerationModel { pathParameters: [ParameterGenerationModel], queryParameters: [ParameterGenerationModel], requestBody: RequestBodyGenerationModel.BodyType?, - responseBody: ResponseBody) { + responseBody: ResponseBody, + id: String?) { self.name = name self.hasDescription = description != nil self.description = description @@ -70,7 +72,7 @@ public struct OperationGenerationModel { self.queryParameters = queryParameters self.hasBody = requestBody != nil self.requestBody = RequestBodyGenerationModel(type: requestBody) - + self.id = id switch responseBody { case .model(let modelName): self.hasResponseModel = true diff --git a/Sources/SurfGenKit/Parsers/OperationNodeParser.swift b/Sources/SurfGenKit/Parsers/OperationNodeParser.swift index 68746a0d..b4ad4d10 100644 --- a/Sources/SurfGenKit/Parsers/OperationNodeParser.swift +++ b/Sources/SurfGenKit/Parsers/OperationNodeParser.swift @@ -1,6 +1,6 @@ // // OperationNodeParser.swift -// +// // // Created by Dmitry Demyanov on 07.11.2020. // @@ -17,7 +17,7 @@ class OperationNodeParser { private let mediaContentParser: MediaContentNodeParser private let parametersParser: ParametersNodeParser - + public var logger: Loger = DefaultLogger.default private let platform: Platform init(mediaContentParser: MediaContentNodeParser, parametersParser: ParametersNodeParser, platform: Platform) { @@ -80,7 +80,7 @@ class OperationNodeParser { let responseBody = try wrap(mediaContentParser.parseResponseBody(node: operation.subNodes.responseBodyNode, forOperationName: name), with: ErrorMessages.errorMessage(for: name)) - + return OperationGenerationModel(name: name, description: description, @@ -88,8 +88,10 @@ class OperationNodeParser { httpMethod: method, pathParameters: parameters.filter { $0.location == .path }, queryParameters: parameters.filter { $0.location == .query }, + headerParameters: parameters.filter { $0.location == .header }, requestBody: requestBody, - responseBody: responseBody) + responseBody: responseBody, + id: name) } - + } diff --git a/Templates/v2/Java/api.stencil b/Templates/v2/Java/api.stencil new file mode 100644 index 00000000..527bdf98 --- /dev/null +++ b/Templates/v2/Java/api.stencil @@ -0,0 +1,25 @@ +package ru.surf.surfgen.api.contract; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "{{ service.name }} API") +public interface {{ service.name }}Api { + {%for path in service.paths%} + {%for operation in path.operations%} + @Operation(summary = "{{ operation.summary }}") + {%if operation.httpMethod|lowercase == "delete"%} + @ResponseStatus(HttpStatus.NO_CONTENT) + {%endif%} + @{{ operation.httpMethod|capitalizeFirstLetter }}Mapping("{{ path.path }}") + {%if operation.httpMethod|lowercase == "delete"%}void{%else%}ResponseEntity<{{ operation.responseGenerationModel.value.typeNames|join }}>{%endif%} {{operation.id}}({%for parameter in operation.pathParameters%} + @PathVariable("{{ parameter.name }}") {{ parameter.typeModel.name|capitalizeFirstLetter }} {{ parameter.name|snakeCaseToCamelCase }}{%endfor%} + {%for parameter in operation.headerParameters%} + @RequestHeader(name = "{{ parameter.name }}", required={{ parameter.isRequired }}) String {{ parameter.schema.type|capitalizeFirstLetter }} {{ parameter.name|snakeCaseToCamelCase }}{%endfor%} + {%for parameter in operation.queryParameters%}@RequestParam(name = "{{ parameter.name }}", required={{ parameter.isRequired }}) {{ parameter.typeModel.name|capitalizeFirstLetter }} {{ parameter.name|snakeCaseToCamelCase }}{%endfor%} + {%for requestModelName in operation.requestGenerationModel.typeNames%}@Valid @RequestBody {{ requestModelName|capitalizeFirstLetter }} {{ requestModelName|snakeCaseToCamelCase }}{%endfor%}); + {%endfor%} + {%endfor%} +} \ No newline at end of file diff --git a/Templates/v2/Java/config/java.config.yaml b/Templates/v2/Java/config/java.config.yaml new file mode 100644 index 00000000..78afcb1b --- /dev/null +++ b/Templates/v2/Java/config/java.config.yaml @@ -0,0 +1,31 @@ +useNewNullableDeterminationStrategy: false + +prefixesToCutDownInServiceNames: + - /api/v1.1 + - /api/1.1 + +analytcsConfig: + logstashEnpointURI: http://logs.ps.surfstudio.ru + payload: + project: TEST + +templates: + - type: service + nameSuffix: Api + fileExtension: java + templatePath: + destinationPath: + - type: service + nameSuffix: Controller + fileExtension: java + templatePath: + destinationPath: + - type: model + nameSuffix: + fileExtension: java + templatePath: + destinationPath: + - type: enum + fileExtension: java + templatePath: + destinationPath: \ No newline at end of file diff --git a/Templates/v2/Java/controller.stencil b/Templates/v2/Java/controller.stencil new file mode 100644 index 00000000..cf997484 --- /dev/null +++ b/Templates/v2/Java/controller.stencil @@ -0,0 +1,45 @@ +package ru.surf.surfgen.controller; + +import ru.surf.surfgen.api.contract.{{ service.name }}Api; +import ru.surf.surfgen.service.{{ service.name }}Service; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +public class {{ service.name }}Controller implements {{ service.name }}Api { + private final {{ service.name }}Service {{ service.name|lowercaseFirstLetter }}Service; + + {%for path in service.paths%} + {%for operation in path.operations%} + @Override + public {%if operation.responseGenerationModel.key == "204"%}void{%else%}ResponseEntity<{{ operation.responseGenerationModel.value.typeNames|join }}>{%endif%} {{ operation.id }}( + {%for parameter in operation.pathParameters%}{{ parameter.typeModel.name|capitalizeFirstLetter }} {{ parameter.name|snakeCaseToCamelCase }}{%endfor%} + {%for parameter in operation.headerParameters%}{{ parameter.typeModel.name|capitalizeFirstLetter }} {{ parameter.name|snakeCaseToCamelCase }}{%endfor%} + {%for parameter in operation.queryParameters%}{{ parameter.typeModel.name|capitalizeFirstLetter }} {{ parameter.name|snakeCaseToCamelCase }}{%endfor%} + {%for requestModelName in operation.requestGenerationModel.typeNames%} {{ requestModelName|capitalizeFirstLetter }} {{ requestModelName|snakeCaseToCamelCase }}{%endfor%}) { + {%if operation.responseGenerationModel.key == "204"%} + {{ service.name|lowercaseFirstLetter }}Service.{{ operation.id }}( + {%for parameter in operation.pathParameters%}{{ parameter.name|snakeCaseToCamelCase }}{%endfor%} + {%for parameter in operation.headerParameters%}{{ parameter.typeModel.name|capitalizeFirstLetter }} {{ parameter.name|snakeCaseToCamelCase }}{%endfor%} + {%for parameter in operation.queryParameters%}{{ parameter.name|snakeCaseToCamelCase }}{%endfor%} + {%for requestModelName in operation.requestGenerationModel.typeNames%}{{ requestModelName|snakeCaseToCamelCase }}{%endfor%}); + {%else%} + return {%if operation.responseGenerationModel.key == "201"%}new ResponseEntity<>({{ service.name|lowercaseFirstLetter }}Service.{{ operation.id }}( + {%for parameter in operation.pathParameters%}{{ parameter.name|snakeCaseToCamelCase }}{%endfor%} + {%for parameter in operation.queryParameters%}{{ parameter.name|snakeCaseToCamelCase }}{%endfor%} + {%for parameter in operation.headerParameters%}{{ parameter.name|snakeCaseToCamelCase }}{%endfor%} + {%for requestModelName in operation.requestGenerationModel.typeNames%}{{ requestModelName|snakeCaseToCamelCase }}{%endfor%}), HttpStatus.CREATED); + {%else%} + ResponseEntity.ok({{ service.name|lowercaseFirstLetter }}Service.{{ operation.id }}( + {%for parameter in operation.pathParameters%}{{ parameter.name|snakeCaseToCamelCase }}{%endfor%} + {%for parameter in operation.queryParameters%}{{ parameter.name|snakeCaseToCamelCase }}{%endfor%} + {%for parameter in operation.headerParameters%}{{ parameter.name|snakeCaseToCamelCase }}{%endfor%} + {%for requestModelName in operation.requestGenerationModel.typeNames%}{{ requestModelName|snakeCaseToCamelCase }}{%endfor%})); + {%endif%} + {%endif%} + } + {%endfor%} + {%endfor%} +} \ No newline at end of file diff --git a/Templates/v2/Java/dto.stencil b/Templates/v2/Java/dto.stencil new file mode 100644 index 00000000..fd3f4ce5 --- /dev/null +++ b/Templates/v2/Java/dto.stencil @@ -0,0 +1,24 @@ +package ru.surf.surfgen.api.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.NoArgsConstructor; +import javax.validation.constraints.*; +import java.util.List; +import java.time.LocalDate; +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +public class {{ model.name }} { + {% for property in model.properties %} + @Schema( description = "{{ property.description }}"{% if property.example %}, example = "{{ property.example }}"{% else %}){% endif %}{% if not property.isNullable %} + @NotNull{% if property.typeModel.name == "string" %} + @NotBlank{% endif %}{% elif property.isNullable %} + @Nullable{% endif %}{% if property.typeModel.name == "string" %}{% if property.minLength or property.maxLength %} + @Size(min = {{ property.minLength }}, max = {{ property.maxLength }}){% endif %}{% elif property.typeModel.name == "integer" %}{% if property.minimum or property.maximum %} + @Min({{ property.minimum}}) + @Max({{ property.maximum }}){% endif %}{% endif %} + private {% if property.typeModel.isArray %}List<{{ property.typeModel.name|capitalizeFirstLetter }}>{% else %}{% if property.typeModel.name == "integer" %}{% if property.format == "int64" %}Long{% else %}Integer{% endif %}{% elif property.typeModel.name == "number" %}Double{% elif property.typeModel.name == "string" %}{% if property.format == "date-time" %}LocalDateTime{% elif property.format == "date" %}LocalDate{% else %}String{% endif %}{% else %}{{ property.typeModel.name|capitalizeFirstLetter }}{% endif %}{% endif %} {{ property.name|snakeCaseToCamelCase }}; + {% endfor %} +} \ No newline at end of file diff --git a/Templates/v2/Java/enum.stencil b/Templates/v2/Java/enum.stencil new file mode 100644 index 00000000..4ffdb749 --- /dev/null +++ b/Templates/v2/Java/enum.stencil @@ -0,0 +1,30 @@ +{% if enum.description %}/** + {% for line in enum.description|splitLines %}* {{ line }} + {% endfor %}*/ +{% endif %}public enum {{ enum.name }} { + {% for caseValue in enum.cases %}{# + #}{{ caseValue|uppercase }}("{{ caseValue }}"){% if not forloop.last %},{% else %};{% endif %} + {% endfor %} + + private final String value; + + {{ enum.name }}(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public static {{ enum.name }} getBy(String value) { + if (value == null) { + return null; + } + for ({{ enum.name }} item : values()) { + if (item.value.equals(value)) { + return item; + } + } + return null; + } +} \ No newline at end of file From 86d2d62fcbd0359930d1400c60592f802057444f Mon Sep 17 00:00:00 2001 From: Andrey Frolov Date: Thu, 13 Feb 2025 16:14:20 +0100 Subject: [PATCH 2/6] fix templates --- Templates/v2/Java/api.stencil | 35 ++++++++------ Templates/v2/Java/controller.stencil | 71 +++++++++++++++++----------- Templates/v2/Java/dto.stencil | 7 ++- Templates/v2/Java/enum.stencil | 2 +- 4 files changed, 70 insertions(+), 45 deletions(-) diff --git a/Templates/v2/Java/api.stencil b/Templates/v2/Java/api.stencil index 527bdf98..882557a3 100644 --- a/Templates/v2/Java/api.stencil +++ b/Templates/v2/Java/api.stencil @@ -7,19 +7,26 @@ import org.springframework.web.bind.annotation.*; @Tag(name = "{{ service.name }} API") public interface {{ service.name }}Api { - {%for path in service.paths%} - {%for operation in path.operations%} - @Operation(summary = "{{ operation.summary }}") - {%if operation.httpMethod|lowercase == "delete"%} - @ResponseStatus(HttpStatus.NO_CONTENT) - {%endif%} + +{%- paths: for path in service.paths %}{% operations: for operation in path.operations %} + @Operation(summary = "{{ operation.summary }}") +{%- if operation.httpMethod|lowercase == "delete" %} + @ResponseStatus(HttpStatus.NO_CONTENT) +{%- endif %} @{{ operation.httpMethod|capitalizeFirstLetter }}Mapping("{{ path.path }}") - {%if operation.httpMethod|lowercase == "delete"%}void{%else%}ResponseEntity<{{ operation.responseGenerationModel.value.typeNames|join }}>{%endif%} {{operation.id}}({%for parameter in operation.pathParameters%} - @PathVariable("{{ parameter.name }}") {{ parameter.typeModel.name|capitalizeFirstLetter }} {{ parameter.name|snakeCaseToCamelCase }}{%endfor%} - {%for parameter in operation.headerParameters%} - @RequestHeader(name = "{{ parameter.name }}", required={{ parameter.isRequired }}) String {{ parameter.schema.type|capitalizeFirstLetter }} {{ parameter.name|snakeCaseToCamelCase }}{%endfor%} - {%for parameter in operation.queryParameters%}@RequestParam(name = "{{ parameter.name }}", required={{ parameter.isRequired }}) {{ parameter.typeModel.name|capitalizeFirstLetter }} {{ parameter.name|snakeCaseToCamelCase }}{%endfor%} - {%for requestModelName in operation.requestGenerationModel.typeNames%}@Valid @RequestBody {{ requestModelName|capitalizeFirstLetter }} {{ requestModelName|snakeCaseToCamelCase }}{%endfor%}); - {%endfor%} - {%endfor%} +{%- if operation.httpMethod|lowercase == "delete" %} + void{% else %} + ResponseEntity<{{ operation.responseGenerationModel.value.typeNames|join }}> + {%- endif %} {{operation.id}}( + {%- for parameter in operation.pathParameters %}@PathVariable("{{ parameter.name }}") {{ parameter.typeModel.name|capitalizeFirstLetter }} {{ parameter.name|snakeCaseToCamelCase }} + {%- if not forloop.last or operation.headerParameters or operation.queryParameters or operation.requestGenerationModel.typeNames%}, {% endif -%}{% endfor -%} + {% for parameter in operation.headerParameters %}@RequestHeader(name = "{{ parameter.name }}", required={{ parameter.isRequired }}) {{ parameter.typeModel.name|capitalizeFirstLetter }} {{ parameter.name|snakeCaseToCamelCase }} + {%- if not forloop.last or operation.queryParameters or operation.requestGenerationModel.typeNames%}, {% endif -%}{% endfor -%} + {% for parameter in operation.queryParameters%}@RequestParam(name = "{{ parameter.name }}", required={{ parameter.isRequired }}) {{ parameter.typeModel.name|capitalizeFirstLetter }} {{ parameter.name|snakeCaseToCamelCase }} + {%- if not forloop.last or operation.requestGenerationModel.typeNames%}, {% endif -%}{% endfor -%} + {% for requestModelName in operation.requestGenerationModel.typeNames %}@Valid @RequestBody {{ requestModelName|capitalizeFirstLetter }} {{ requestModelName|snakeCaseToCamelCase }} + {%- if not forloop.last%}, {% endif -%}{% endfor -%}); + {%- if not forloop.paths.last or not forloop.operations.last %} + {% endif -%} +{% endfor %}{% endfor %} } \ No newline at end of file diff --git a/Templates/v2/Java/controller.stencil b/Templates/v2/Java/controller.stencil index cf997484..8d509dad 100644 --- a/Templates/v2/Java/controller.stencil +++ b/Templates/v2/Java/controller.stencil @@ -11,35 +11,50 @@ import org.springframework.web.bind.annotation.*; public class {{ service.name }}Controller implements {{ service.name }}Api { private final {{ service.name }}Service {{ service.name|lowercaseFirstLetter }}Service; - {%for path in service.paths%} - {%for operation in path.operations%} + {%- for path in service.paths -%} + {%- for operation in path.operations%} + @Override - public {%if operation.responseGenerationModel.key == "204"%}void{%else%}ResponseEntity<{{ operation.responseGenerationModel.value.typeNames|join }}>{%endif%} {{ operation.id }}( - {%for parameter in operation.pathParameters%}{{ parameter.typeModel.name|capitalizeFirstLetter }} {{ parameter.name|snakeCaseToCamelCase }}{%endfor%} - {%for parameter in operation.headerParameters%}{{ parameter.typeModel.name|capitalizeFirstLetter }} {{ parameter.name|snakeCaseToCamelCase }}{%endfor%} - {%for parameter in operation.queryParameters%}{{ parameter.typeModel.name|capitalizeFirstLetter }} {{ parameter.name|snakeCaseToCamelCase }}{%endfor%} - {%for requestModelName in operation.requestGenerationModel.typeNames%} {{ requestModelName|capitalizeFirstLetter }} {{ requestModelName|snakeCaseToCamelCase }}{%endfor%}) { - {%if operation.responseGenerationModel.key == "204"%} + public {% if operation.responseGenerationModel.key == "204" %}void{% else %}ResponseEntity<{{ operation.responseGenerationModel.value.typeNames|join }}>{% endif %} {{ operation.id }}( + {%- for parameter in operation.pathParameters %}{{ parameter.typeModel.name|capitalizeFirstLetter }} {{ parameter.name|snakeCaseToCamelCase }} + {%- if not forloop.last or operation.headerParameters or operation.queryParameters or operation.requestGenerationModel.typeNames%}, {% endif -%}{% endfor -%} + {% for parameter in operation.headerParameters %}{{ parameter.typeModel.name|capitalizeFirstLetter }} {{ parameter.name|snakeCaseToCamelCase }} + {%- if not forloop.last or operation.queryParameters or operation.requestGenerationModel.typeNames%}, {% endif -%}{% endfor -%} + {% for parameter in operation.queryParameters %}{{ parameter.typeModel.name|capitalizeFirstLetter }} {{ parameter.name|snakeCaseToCamelCase }} + {%- if not forloop.last or operation.requestGenerationModel.typeNames%}, {% endif -%}{% endfor -%} + {% for requestModelName in operation.requestGenerationModel.typeNames -%}{{ requestModelName|capitalizeFirstLetter }} {{ requestModelName|snakeCaseToCamelCase }} + {%- if not forloop.last%}, {% endif -%}{% endfor -%}) { + {%- if operation.responseGenerationModel.key == "204" %} {{ service.name|lowercaseFirstLetter }}Service.{{ operation.id }}( - {%for parameter in operation.pathParameters%}{{ parameter.name|snakeCaseToCamelCase }}{%endfor%} - {%for parameter in operation.headerParameters%}{{ parameter.typeModel.name|capitalizeFirstLetter }} {{ parameter.name|snakeCaseToCamelCase }}{%endfor%} - {%for parameter in operation.queryParameters%}{{ parameter.name|snakeCaseToCamelCase }}{%endfor%} - {%for requestModelName in operation.requestGenerationModel.typeNames%}{{ requestModelName|snakeCaseToCamelCase }}{%endfor%}); - {%else%} - return {%if operation.responseGenerationModel.key == "201"%}new ResponseEntity<>({{ service.name|lowercaseFirstLetter }}Service.{{ operation.id }}( - {%for parameter in operation.pathParameters%}{{ parameter.name|snakeCaseToCamelCase }}{%endfor%} - {%for parameter in operation.queryParameters%}{{ parameter.name|snakeCaseToCamelCase }}{%endfor%} - {%for parameter in operation.headerParameters%}{{ parameter.name|snakeCaseToCamelCase }}{%endfor%} - {%for requestModelName in operation.requestGenerationModel.typeNames%}{{ requestModelName|snakeCaseToCamelCase }}{%endfor%}), HttpStatus.CREATED); - {%else%} + {%- for parameter in operation.pathParameters %}{{ parameter.name|snakeCaseToCamelCase }} + {%- if not forloop.last or operation.headerParameters or operation.queryParameters or operation.requestGenerationModel.typeNames%}, {% endif -%}{% endfor -%} + {%- for parameter in operation.headerParameters %}{{ parameter.typeModel.name|capitalizeFirstLetter }} {{ parameter.name|snakeCaseToCamelCase }} + {%- if not forloop.last or operation.queryParameters or operation.requestGenerationModel.typeNames%}, {% endif -%}{% endfor -%} + {%- for parameter in operation.queryParameters %}{{ parameter.name|snakeCaseToCamelCase }} + {%- if not forloop.last or operation.requestGenerationModel.typeNames%}, {% endif -%}{% endfor -%} + {%- for requestModelName in operation.requestGenerationModel.typeNames %}{{ requestModelName|snakeCaseToCamelCase }} + {%- if not forloop.last%}, {% endif -%}{% endfor -%}); + {%- else %} + return {% if operation.responseGenerationModel.key == "201" %}new ResponseEntity<>({{ service.name|lowercaseFirstLetter }}Service.{{ operation.id }}( + {%- for parameter in operation.pathParameters %}{{ parameter.name|snakeCaseToCamelCase }} + {%- if not forloop.last or operation.headerParameters or operation.queryParameters or operation.requestGenerationModel.typeNames%}, {% endif -%}{% endfor -%} + {% for parameter in operation.queryParameters %}{{ parameter.name|snakeCaseToCamelCase }} + {%- if not forloop.last or operation.queryParameters or operation.requestGenerationModel.typeNames%}, {% endif -%}{% endfor -%} + {% for parameter in operation.headerParameters %}{{ parameter.name|snakeCaseToCamelCase }} + {%- if not forloop.last or operation.requestGenerationModel.typeNames%}, {% endif -%}{% endfor -%} + {% for requestModelName in operation.requestGenerationModel.typeNames %}{{ requestModelName|snakeCaseToCamelCase }} + {%- if not forloop.last%}, {% endif -%}{% endfor -%}), HttpStatus.CREATED); + {%- else -%} ResponseEntity.ok({{ service.name|lowercaseFirstLetter }}Service.{{ operation.id }}( - {%for parameter in operation.pathParameters%}{{ parameter.name|snakeCaseToCamelCase }}{%endfor%} - {%for parameter in operation.queryParameters%}{{ parameter.name|snakeCaseToCamelCase }}{%endfor%} - {%for parameter in operation.headerParameters%}{{ parameter.name|snakeCaseToCamelCase }}{%endfor%} - {%for requestModelName in operation.requestGenerationModel.typeNames%}{{ requestModelName|snakeCaseToCamelCase }}{%endfor%})); - {%endif%} - {%endif%} - } - {%endfor%} - {%endfor%} + {%- for parameter in operation.pathParameters %}{{ parameter.name|snakeCaseToCamelCase }} + {%- if not forloop.last or operation.headerParameters or operation.queryParameters or operation.requestGenerationModel.typeNames%}, {% endif -%}{% endfor -%} + {% for parameter in operation.queryParameters %}{{ parameter.name|snakeCaseToCamelCase }} + {%- if not forloop.last or operation.queryParameters or operation.requestGenerationModel.typeNames%}, {% endif -%}{% endfor -%} + {% for parameter in operation.headerParameters %}{{ parameter.name|snakeCaseToCamelCase }} + {%- if not forloop.last or operation.requestGenerationModel.typeNames%}, {% endif -%}{% endfor -%} + {% for requestModelName in operation.requestGenerationModel.typeNames %}{{ requestModelName|snakeCaseToCamelCase }} + {%- if not forloop.last%}, {% endif -%}{% endfor -%})); + {%- endif -%} + {%- endif %} + }{% endfor %}{% endfor %} } \ No newline at end of file diff --git a/Templates/v2/Java/dto.stencil b/Templates/v2/Java/dto.stencil index fd3f4ce5..396293da 100644 --- a/Templates/v2/Java/dto.stencil +++ b/Templates/v2/Java/dto.stencil @@ -11,8 +11,9 @@ import java.time.LocalDateTime; @Data @NoArgsConstructor public class {{ model.name }} { - {% for property in model.properties %} - @Schema( description = "{{ property.description }}"{% if property.example %}, example = "{{ property.example }}"{% else %}){% endif %}{% if not property.isNullable %} + {%- for property in model.properties %} + @Schema(description = "{{ property.description }}"{% if property.example %}, example = "{{ property.example }}"{% else %}){% endif %}{% if property.pattern %} + @Pattern(regexp = "{{ property.pattern|withEscapedCharacters }}"){% endif %}{% if not property.isNullable %} @NotNull{% if property.typeModel.name == "string" %} @NotBlank{% endif %}{% elif property.isNullable %} @Nullable{% endif %}{% if property.typeModel.name == "string" %}{% if property.minLength or property.maxLength %} @@ -20,5 +21,7 @@ public class {{ model.name }} { @Min({{ property.minimum}}) @Max({{ property.maximum }}){% endif %}{% endif %} private {% if property.typeModel.isArray %}List<{{ property.typeModel.name|capitalizeFirstLetter }}>{% else %}{% if property.typeModel.name == "integer" %}{% if property.format == "int64" %}Long{% else %}Integer{% endif %}{% elif property.typeModel.name == "number" %}Double{% elif property.typeModel.name == "string" %}{% if property.format == "date-time" %}LocalDateTime{% elif property.format == "date" %}LocalDate{% else %}String{% endif %}{% else %}{{ property.typeModel.name|capitalizeFirstLetter }}{% endif %}{% endif %} {{ property.name|snakeCaseToCamelCase }}; + {%- if not forloop.last %} + {% endif -%} {% endfor %} } \ No newline at end of file diff --git a/Templates/v2/Java/enum.stencil b/Templates/v2/Java/enum.stencil index 4ffdb749..c9a74b3e 100644 --- a/Templates/v2/Java/enum.stencil +++ b/Templates/v2/Java/enum.stencil @@ -4,7 +4,7 @@ {% endif %}public enum {{ enum.name }} { {% for caseValue in enum.cases %}{# #}{{ caseValue|uppercase }}("{{ caseValue }}"){% if not forloop.last %},{% else %};{% endif %} - {% endfor %} + {%- endfor %} private final String value; From feea22bb9345792c810aba32ae5d55fd13d6d353 Mon Sep 17 00:00:00 2001 From: myznikov Date: Fri, 14 Feb 2025 01:11:30 +0300 Subject: [PATCH 3/6] fixed templates: added import logic added new filters --- .../CodeGenerator/Models/PropertyModel.swift | 8 ++-- .../DefaultTemplateFiller.swift | 8 ++++ Sources/Common/Extensions/String.swift | 43 +++++++++++++++++++ .../Builders/AnySchemaBuilder.swift | 12 +++--- Sources/GASTTree/PropertyNode.swift | 8 ++-- Templates/v2/Java/api.stencil | 14 +++--- Templates/v2/Java/controller.stencil | 30 +++++++------ Templates/v2/Java/dto.stencil | 33 ++++++++++---- Templates/v2/Java/enum.stencil | 22 ++++------ 9 files changed, 126 insertions(+), 52 deletions(-) diff --git a/Sources/CodeGenerator/Models/PropertyModel.swift b/Sources/CodeGenerator/Models/PropertyModel.swift index e46382ec..45dff6ed 100644 --- a/Sources/CodeGenerator/Models/PropertyModel.swift +++ b/Sources/CodeGenerator/Models/PropertyModel.swift @@ -82,8 +82,8 @@ public struct PropertyModel { public let pattern: String? public let example: Any? public let format: String? - public let minimum: Double? - public let maximum: Double? + public let minimum: Int? + public let maximum: Int? public let maxLength: Int? public let minLength: Int? @@ -97,8 +97,8 @@ public struct PropertyModel { pattern: String?, example: Any?, format: String?, - minimum: Double?, - maximum: Double?, + minimum: Int?, + maximum: Int?, minLength: Int?, maxLength: Int?) { self.name = name diff --git a/Sources/CodeGenerator/Stages/GenerationStage/TemplateFiller/DefaultTemplateFiller.swift b/Sources/CodeGenerator/Stages/GenerationStage/TemplateFiller/DefaultTemplateFiller.swift index 530c6bcc..cd3a0bcd 100644 --- a/Sources/CodeGenerator/Stages/GenerationStage/TemplateFiller/DefaultTemplateFiller.swift +++ b/Sources/CodeGenerator/Stages/GenerationStage/TemplateFiller/DefaultTemplateFiller.swift @@ -76,6 +76,14 @@ public class DefaultTemplateFiller: TemplateFiller { $0.withEscapedCharacters() } + templateExtension.registerStringFilter("splitByHyphenAndGetLastNonEmpty") { + $0.splitByHyphenAndGetLastNonEmpty() + } + + templateExtension.registerStringFilter("splitByUppercaseAndGetLast") { + $0.splitByUppercaseAndGetLast() + } + return templateExtension } diff --git a/Sources/Common/Extensions/String.swift b/Sources/Common/Extensions/String.swift index b0543e3c..86133a5f 100644 --- a/Sources/Common/Extensions/String.swift +++ b/Sources/Common/Extensions/String.swift @@ -144,6 +144,49 @@ extension String { .joined() } + /// Splits string by '-' and returns last element. + public func splitByHyphenAndGetLastNonEmpty() -> String { + let parts = self.components(separatedBy: "-").filter { !$0.isEmpty } + return parts.last ?? "" + } + + /// Splits string by Uppercase letter and returns last element. + /// Example: UpdateProfileRequest returns Request + public func splitByUppercaseAndGetLast() -> String { + do { + let pattern = "(? String { return self .split(whereSeparator: { $0 == "/" || $0 == "_" }) diff --git a/Sources/GASTBuilder/Builders/AnySchemaBuilder.swift b/Sources/GASTBuilder/Builders/AnySchemaBuilder.swift index 094d95ab..dc2b1a65 100644 --- a/Sources/GASTBuilder/Builders/AnySchemaBuilder.swift +++ b/Sources/GASTBuilder/Builders/AnySchemaBuilder.swift @@ -174,8 +174,8 @@ public struct AnySchemaBuilder: SchemaBuilder { var pattern: String? var format: String? - var minimum: Double? - var maximum: Double? + var minimum: Int? + var maximum: Int? var minLength: Int? var maxLength: Int? @@ -188,13 +188,13 @@ public struct AnySchemaBuilder: SchemaBuilder { case .number(let numberSchema): format = numberSchema.format?.rawValue - minimum = numberSchema.minimum - maximum = numberSchema.maximum + minimum = numberSchema.minimum.flatMap { Int($0) } + maximum = numberSchema.maximum.flatMap { Int($0) } case .integer(let integerSchema): format = integerSchema.format?.rawValue - minimum = integerSchema.minimum.flatMap(Double.init) - maximum = integerSchema.maximum.flatMap(Double.init) + minimum = integerSchema.minimum + maximum = integerSchema.maximum default: break diff --git a/Sources/GASTTree/PropertyNode.swift b/Sources/GASTTree/PropertyNode.swift index ede3bedb..61dba710 100644 --- a/Sources/GASTTree/PropertyNode.swift +++ b/Sources/GASTTree/PropertyNode.swift @@ -21,8 +21,8 @@ public struct PropertyNode { public let nullable: Bool public let pattern: String? public let format: String? - public let minimum: Double? - public let maximum: Double? + public let minimum: Int? + public let maximum: Int? public let maxLength: Int? public let minLength: Int? @@ -33,8 +33,8 @@ public struct PropertyNode { nullable: Bool, pattern: String?, format: String?, - minimum: Double?, - maximum: Double?, + minimum: Int?, + maximum: Int?, maxLength: Int?, minLength: Int?) { self.name = name diff --git a/Templates/v2/Java/api.stencil b/Templates/v2/Java/api.stencil index 882557a3..d07c55df 100644 --- a/Templates/v2/Java/api.stencil +++ b/Templates/v2/Java/api.stencil @@ -1,8 +1,12 @@ -package ru.surf.surfgen.api.contract; +package {{package}}.api.contract; +import {{package}}.api.dto.*; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @Tag(name = "{{ service.name }} API") @@ -18,13 +22,13 @@ public interface {{ service.name }}Api { void{% else %} ResponseEntity<{{ operation.responseGenerationModel.value.typeNames|join }}> {%- endif %} {{operation.id}}( - {%- for parameter in operation.pathParameters %}@PathVariable("{{ parameter.name }}") {{ parameter.typeModel.name|capitalizeFirstLetter }} {{ parameter.name|snakeCaseToCamelCase }} + {%- for parameter in operation.pathParameters %}@PathVariable("{{ parameter.name }}") final {{ parameter.typeModel.name|capitalizeFirstLetter }} {{ parameter.name|snakeCaseToCamelCase }} {%- if not forloop.last or operation.headerParameters or operation.queryParameters or operation.requestGenerationModel.typeNames%}, {% endif -%}{% endfor -%} - {% for parameter in operation.headerParameters %}@RequestHeader(name = "{{ parameter.name }}", required={{ parameter.isRequired }}) {{ parameter.typeModel.name|capitalizeFirstLetter }} {{ parameter.name|snakeCaseToCamelCase }} + {% for parameter in operation.headerParameters %}@RequestHeader(name = "{{ parameter.name }}", required={{ parameter.isRequired }}) final {{ parameter.typeModel.name|capitalizeFirstLetter }} {{ parameter.name|splitByHyphenAndGetLastNonEmpty }} {%- if not forloop.last or operation.queryParameters or operation.requestGenerationModel.typeNames%}, {% endif -%}{% endfor -%} - {% for parameter in operation.queryParameters%}@RequestParam(name = "{{ parameter.name }}", required={{ parameter.isRequired }}) {{ parameter.typeModel.name|capitalizeFirstLetter }} {{ parameter.name|snakeCaseToCamelCase }} + {% for parameter in operation.queryParameters%}@RequestParam(name = "{{ parameter.name }}", required={{ parameter.isRequired }}) final {{ parameter.typeModel.name|capitalizeFirstLetter }} {{ parameter.name|snakeCaseToCamelCase }} {%- if not forloop.last or operation.requestGenerationModel.typeNames%}, {% endif -%}{% endfor -%} - {% for requestModelName in operation.requestGenerationModel.typeNames %}@Valid @RequestBody {{ requestModelName|capitalizeFirstLetter }} {{ requestModelName|snakeCaseToCamelCase }} + {% for requestModelName in operation.requestGenerationModel.typeNames %}@Valid @NotNull @RequestBody final {{ requestModelName|capitalizeFirstLetter }} {{ requestModelName|snakeCaseToCamelCase }} {%- if not forloop.last%}, {% endif -%}{% endfor -%}); {%- if not forloop.paths.last or not forloop.operations.last %} {% endif -%} diff --git a/Templates/v2/Java/controller.stencil b/Templates/v2/Java/controller.stencil index 8d509dad..aeda6b90 100644 --- a/Templates/v2/Java/controller.stencil +++ b/Templates/v2/Java/controller.stencil @@ -1,10 +1,13 @@ -package ru.surf.surfgen.controller; +package {{package}}.controller; -import ru.surf.surfgen.api.contract.{{ service.name }}Api; -import ru.surf.surfgen.service.{{ service.name }}Service; +import {{package}}.api.contract.{{ service.name }}Api; +import {{package}}.service.{{ service.name }}Service; +import {{package}}.api.dto.*; import lombok.RequiredArgsConstructor; +import org.jetbrains.annotations.NotNull; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.RestController; @RestController @RequiredArgsConstructor @@ -13,22 +16,25 @@ public class {{ service.name }}Controller implements {{ service.name }}Api { {%- for path in service.paths -%} {%- for operation in path.operations%} - + @Override + {%- if operation.responseGenerationModel.key != "204" %} + @NotNull + {%- endif %} public {% if operation.responseGenerationModel.key == "204" %}void{% else %}ResponseEntity<{{ operation.responseGenerationModel.value.typeNames|join }}>{% endif %} {{ operation.id }}( - {%- for parameter in operation.pathParameters %}{{ parameter.typeModel.name|capitalizeFirstLetter }} {{ parameter.name|snakeCaseToCamelCase }} + {%- for parameter in operation.pathParameters %}final {{ parameter.typeModel.name|capitalizeFirstLetter }} {{ parameter.name|snakeCaseToCamelCase }} {%- if not forloop.last or operation.headerParameters or operation.queryParameters or operation.requestGenerationModel.typeNames%}, {% endif -%}{% endfor -%} - {% for parameter in operation.headerParameters %}{{ parameter.typeModel.name|capitalizeFirstLetter }} {{ parameter.name|snakeCaseToCamelCase }} + {% for parameter in operation.headerParameters %}final {{ parameter.typeModel.name|capitalizeFirstLetter }} {{ parameter.name|splitByHyphenAndGetLastNonEmpty }} {%- if not forloop.last or operation.queryParameters or operation.requestGenerationModel.typeNames%}, {% endif -%}{% endfor -%} - {% for parameter in operation.queryParameters %}{{ parameter.typeModel.name|capitalizeFirstLetter }} {{ parameter.name|snakeCaseToCamelCase }} + {% for parameter in operation.queryParameters %}final {{ parameter.typeModel.name|capitalizeFirstLetter }} {{ parameter.name|snakeCaseToCamelCase }} {%- if not forloop.last or operation.requestGenerationModel.typeNames%}, {% endif -%}{% endfor -%} - {% for requestModelName in operation.requestGenerationModel.typeNames -%}{{ requestModelName|capitalizeFirstLetter }} {{ requestModelName|snakeCaseToCamelCase }} + {% for requestModelName in operation.requestGenerationModel.typeNames -%}final {{ requestModelName|capitalizeFirstLetter }} {{ requestModelName|snakeCaseToCamelCase }} {%- if not forloop.last%}, {% endif -%}{% endfor -%}) { {%- if operation.responseGenerationModel.key == "204" %} {{ service.name|lowercaseFirstLetter }}Service.{{ operation.id }}( {%- for parameter in operation.pathParameters %}{{ parameter.name|snakeCaseToCamelCase }} {%- if not forloop.last or operation.headerParameters or operation.queryParameters or operation.requestGenerationModel.typeNames%}, {% endif -%}{% endfor -%} - {%- for parameter in operation.headerParameters %}{{ parameter.typeModel.name|capitalizeFirstLetter }} {{ parameter.name|snakeCaseToCamelCase }} + {%- for parameter in operation.headerParameters %}{{ parameter.name|splitByHyphenAndGetLastNonEmpty }} {%- if not forloop.last or operation.queryParameters or operation.requestGenerationModel.typeNames%}, {% endif -%}{% endfor -%} {%- for parameter in operation.queryParameters %}{{ parameter.name|snakeCaseToCamelCase }} {%- if not forloop.last or operation.requestGenerationModel.typeNames%}, {% endif -%}{% endfor -%} @@ -40,7 +46,7 @@ public class {{ service.name }}Controller implements {{ service.name }}Api { {%- if not forloop.last or operation.headerParameters or operation.queryParameters or operation.requestGenerationModel.typeNames%}, {% endif -%}{% endfor -%} {% for parameter in operation.queryParameters %}{{ parameter.name|snakeCaseToCamelCase }} {%- if not forloop.last or operation.queryParameters or operation.requestGenerationModel.typeNames%}, {% endif -%}{% endfor -%} - {% for parameter in operation.headerParameters %}{{ parameter.name|snakeCaseToCamelCase }} + {% for parameter in operation.headerParameters %}{{ parameter.name|splitByHyphenAndGetLastNonEmpty }} {%- if not forloop.last or operation.requestGenerationModel.typeNames%}, {% endif -%}{% endfor -%} {% for requestModelName in operation.requestGenerationModel.typeNames %}{{ requestModelName|snakeCaseToCamelCase }} {%- if not forloop.last%}, {% endif -%}{% endfor -%}), HttpStatus.CREATED); @@ -50,7 +56,7 @@ public class {{ service.name }}Controller implements {{ service.name }}Api { {%- if not forloop.last or operation.headerParameters or operation.queryParameters or operation.requestGenerationModel.typeNames%}, {% endif -%}{% endfor -%} {% for parameter in operation.queryParameters %}{{ parameter.name|snakeCaseToCamelCase }} {%- if not forloop.last or operation.queryParameters or operation.requestGenerationModel.typeNames%}, {% endif -%}{% endfor -%} - {% for parameter in operation.headerParameters %}{{ parameter.name|snakeCaseToCamelCase }} + {% for parameter in operation.headerParameters %}{{ parameter.name|splitByHyphenAndGetLastNonEmpty }} {%- if not forloop.last or operation.requestGenerationModel.typeNames%}, {% endif -%}{% endfor -%} {% for requestModelName in operation.requestGenerationModel.typeNames %}{{ requestModelName|snakeCaseToCamelCase }} {%- if not forloop.last%}, {% endif -%}{% endfor -%})); diff --git a/Templates/v2/Java/dto.stencil b/Templates/v2/Java/dto.stencil index 396293da..e551110f 100644 --- a/Templates/v2/Java/dto.stencil +++ b/Templates/v2/Java/dto.stencil @@ -1,24 +1,41 @@ -package ru.surf.surfgen.api.dto; +package {{package}}.api.dto; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.NoArgsConstructor; -import javax.validation.constraints.*; +import lombok.AllArgsConstructor; +{%- if model.name|splitByUppercaseAndGetLast == "Request" %} +import jakarta.validation.constraints.*; +import jakarta.annotation.Nullable; +{%- else %} +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +{%- endif -%} +{%- for property in model.properties %} +{%- if property.typeModel.isArray %} import java.util.List; -import java.time.LocalDate; +{%- elif property.format == "date-time" %} import java.time.LocalDateTime; +{%- elif property.format == "date" %} +import java.time.LocalDate; +{%- elif property.format == "uuid" %} +import org.hibernate.validator.constraints.UUID; +{%- endif -%} +{% endfor %} @Data @NoArgsConstructor +@AllArgsConstructor public class {{ model.name }} { {%- for property in model.properties %} - @Schema(description = "{{ property.description }}"{% if property.example %}, example = "{{ property.example }}"{% else %}){% endif %}{% if property.pattern %} - @Pattern(regexp = "{{ property.pattern|withEscapedCharacters }}"){% endif %}{% if not property.isNullable %} - @NotNull{% if property.typeModel.name == "string" %} - @NotBlank{% endif %}{% elif property.isNullable %} + @Schema(description = "{{ property.description }}"{% if property.example %}, example = "{{ property.example }}"{% else %}){% endif %}{% if property.format == "uuid" %} + @UUID{% endif %}{% if property.pattern %} + @Pattern(regexp = "{{ property.pattern|withEscapedCharacters }}"){% endif %}{% if not property.isNullable and property.typeModel.name != "string" %} + @NotNull{% elif not property.isNullable and property.typeModel.name == "string" %} + @NotBlank{% elif property.isNullable %} @Nullable{% endif %}{% if property.typeModel.name == "string" %}{% if property.minLength or property.maxLength %} @Size(min = {{ property.minLength }}, max = {{ property.maxLength }}){% endif %}{% elif property.typeModel.name == "integer" %}{% if property.minimum or property.maximum %} - @Min({{ property.minimum}}) + @Min({{ property.minimum }}) @Max({{ property.maximum }}){% endif %}{% endif %} private {% if property.typeModel.isArray %}List<{{ property.typeModel.name|capitalizeFirstLetter }}>{% else %}{% if property.typeModel.name == "integer" %}{% if property.format == "int64" %}Long{% else %}Integer{% endif %}{% elif property.typeModel.name == "number" %}Double{% elif property.typeModel.name == "string" %}{% if property.format == "date-time" %}LocalDateTime{% elif property.format == "date" %}LocalDate{% else %}String{% endif %}{% else %}{{ property.typeModel.name|capitalizeFirstLetter }}{% endif %}{% endif %} {{ property.name|snakeCaseToCamelCase }}; {%- if not forloop.last %} diff --git a/Templates/v2/Java/enum.stencil b/Templates/v2/Java/enum.stencil index c9a74b3e..f236a6de 100644 --- a/Templates/v2/Java/enum.stencil +++ b/Templates/v2/Java/enum.stencil @@ -1,22 +1,18 @@ -{% if enum.description %}/** - {% for line in enum.description|splitLines %}* {{ line }} - {% endfor %}*/ -{% endif %}public enum {{ enum.name }} { +package {{package}}.api.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum {{ enum.name }} { {% for caseValue in enum.cases %}{# #}{{ caseValue|uppercase }}("{{ caseValue }}"){% if not forloop.last %},{% else %};{% endif %} {%- endfor %} private final String value; - {{ enum.name }}(String value) { - this.value = value; - } - - public String getValue() { - return value; - } - - public static {{ enum.name }} getBy(String value) { + public static {{ enum.name }} getByValue(String value) { if (value == null) { return null; } From 547278b17ba09d6977776122c033bc6a393baab3 Mon Sep 17 00:00:00 2001 From: myznikov Date: Fri, 14 Feb 2025 10:37:04 +0300 Subject: [PATCH 4/6] fixed enum template --- Templates/v2/Java/enum.stencil | 2 -- 1 file changed, 2 deletions(-) diff --git a/Templates/v2/Java/enum.stencil b/Templates/v2/Java/enum.stencil index f236a6de..629d8f6f 100644 --- a/Templates/v2/Java/enum.stencil +++ b/Templates/v2/Java/enum.stencil @@ -1,9 +1,7 @@ package {{package}}.api.dto; import lombok.AllArgsConstructor; -import lombok.Getter; -@Getter @AllArgsConstructor public enum {{ enum.name }} { {% for caseValue in enum.cases %}{# From 769543e6585d5368f90cc781bb89ec1ac0ac7673 Mon Sep 17 00:00:00 2001 From: myznikov Date: Fri, 14 Feb 2025 12:48:00 +0300 Subject: [PATCH 5/6] fixed api and controller template. added package to config --- Templates/v2/Java/api.stencil | 3 +++ Templates/v2/Java/config/java.config.yaml | 2 ++ Templates/v2/Java/controller.stencil | 3 --- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Templates/v2/Java/api.stencil b/Templates/v2/Java/api.stencil index d07c55df..7682ad33 100644 --- a/Templates/v2/Java/api.stencil +++ b/Templates/v2/Java/api.stencil @@ -18,6 +18,9 @@ public interface {{ service.name }}Api { @ResponseStatus(HttpStatus.NO_CONTENT) {%- endif %} @{{ operation.httpMethod|capitalizeFirstLetter }}Mapping("{{ path.path }}") +{%- if operation.responseGenerationModel.key != "204" %} + @NotNull +{%- endif %} {%- if operation.httpMethod|lowercase == "delete" %} void{% else %} ResponseEntity<{{ operation.responseGenerationModel.value.typeNames|join }}> diff --git a/Templates/v2/Java/config/java.config.yaml b/Templates/v2/Java/config/java.config.yaml index 78afcb1b..7a4d0d85 100644 --- a/Templates/v2/Java/config/java.config.yaml +++ b/Templates/v2/Java/config/java.config.yaml @@ -3,6 +3,8 @@ useNewNullableDeterminationStrategy: false prefixesToCutDownInServiceNames: - /api/v1.1 - /api/1.1 +environment: + package: analytcsConfig: logstashEnpointURI: http://logs.ps.surfstudio.ru diff --git a/Templates/v2/Java/controller.stencil b/Templates/v2/Java/controller.stencil index aeda6b90..6b98ddf8 100644 --- a/Templates/v2/Java/controller.stencil +++ b/Templates/v2/Java/controller.stencil @@ -18,9 +18,6 @@ public class {{ service.name }}Controller implements {{ service.name }}Api { {%- for operation in path.operations%} @Override - {%- if operation.responseGenerationModel.key != "204" %} - @NotNull - {%- endif %} public {% if operation.responseGenerationModel.key == "204" %}void{% else %}ResponseEntity<{{ operation.responseGenerationModel.value.typeNames|join }}>{% endif %} {{ operation.id }}( {%- for parameter in operation.pathParameters %}final {{ parameter.typeModel.name|capitalizeFirstLetter }} {{ parameter.name|snakeCaseToCamelCase }} {%- if not forloop.last or operation.headerParameters or operation.queryParameters or operation.requestGenerationModel.typeNames%}, {% endif -%}{% endfor -%} From 219f38b3a62d99617620f642d7c8163aee1e09a7 Mon Sep 17 00:00:00 2001 From: myznikov Date: Thu, 20 Feb 2025 15:59:05 +0300 Subject: [PATCH 6/6] feat: added @Schema for model description --- Templates/v2/Java/dto.stencil | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Templates/v2/Java/dto.stencil b/Templates/v2/Java/dto.stencil index e551110f..c1c87655 100644 --- a/Templates/v2/Java/dto.stencil +++ b/Templates/v2/Java/dto.stencil @@ -26,6 +26,8 @@ import org.hibernate.validator.constraints.UUID; @Data @NoArgsConstructor @AllArgsConstructor +{%- if model.description %} +@Schema(description = "{{ model.description }}"){% endif %} public class {{ model.name }} { {%- for property in model.properties %} @Schema(description = "{{ property.description }}"{% if property.example %}, example = "{{ property.example }}"{% else %}){% endif %}{% if property.format == "uuid" %}