UNPKG

@openapi-generator-plus/java-jaxrs-generator-common

Version:
741 lines (670 loc) 30.3 kB
import { CodegenSchemaType, CodegenConfig, CodegenGeneratorContext, CodegenDocument, CodegenGenerator, isCodegenObjectSchema, isCodegenEnumSchema, CodegenNativeType, CodegenProperty, CodegenAllOfStrategy, CodegenAnyOfStrategy, CodegenOneOfStrategy, CodegenLogLevel, isCodegenInterfaceSchema, isCodegenWrapperSchema, CodegenSchema, CodegenSchemaPurpose } from '@openapi-generator-plus/types' import { CodegenOptionsJava } from './types' import path from 'path' import Handlebars from 'handlebars' import { loadTemplates, emit, registerStandardHelpers, sourcePosition, ActualHelperOptions } from '@openapi-generator-plus/handlebars-templates' import { javaLikeGenerator, ConstantStyle, options as javaLikeOptions, JavaLikeContext, EnumMemberStyle } from '@openapi-generator-plus/java-like-generator-helper' import { capitalize, commonGenerator, configBoolean, configNumber, configObject, configString, configStringArray, debugStringify, nullableConfigString } from '@openapi-generator-plus/generator-common' import * as idx from '@openapi-generator-plus/indexed-type' export { CodegenOptionsJava } from './types' function escapeString(value: string | number | boolean) { if (typeof value !== 'string' && typeof value !== 'number' && typeof value !== 'boolean') { throw new Error(`escapeString called with unsupported type: ${typeof value} (${value})`) } value = String(value) value = value.replace(/\\/g, '\\\\') value = value.replace(/"/g, '\\"') value = value.replace(/\r/g, '\\r') value = value.replace(/\n/g, '\\n') return value } /** * Turns a Java package name into a path * @param packageName Java package name */ export function packageToPath(packageName: string): string { return packageName.replace(/\./g, path.sep) } function computeCustomTemplatesPath(configPath: string | undefined, customTemplatesPath: string) { if (configPath) { return path.resolve(path.dirname(configPath), customTemplatesPath) } else { return customTemplatesPath } } function computeRelativeSourceOutputPath(config: CodegenConfig) { const maven = config.maven const defaultPath = maven ? path.join('src', 'main', 'java') : '' return configString(config, 'relativeSourceOutputPath', defaultPath) } function computeRelativeResourcesOutputPath(config: CodegenConfig) { const maven = config.maven const defaultPath = maven ? path.join('src', 'main', 'resources') : undefined return configString(config, 'relativeResourcesOutputPath', defaultPath) } function computeRelativeTestOutputPath(config: CodegenConfig) { const maven = config.maven const defaultPath = maven ? path.join('src', 'test', 'java') : '' return configString(config, 'relativeTestOutputPath', defaultPath) } function computeRelativeTestResourcesOutputPath(config: CodegenConfig) { const maven = config.maven const defaultPath = maven ? path.join('src', 'test', 'resources') : undefined return configString(config, 'relativeTestResourcesOutputPath', defaultPath) } export interface JavaGeneratorContext extends CodegenGeneratorContext { loadAdditionalTemplates?: (hbs: typeof Handlebars) => Promise<void> additionalWatchPaths?: () => string[] additionalExportTemplates?: (outputPath: string, doc: CodegenDocument, hbs: typeof Handlebars, rootContext: Record<string, unknown>) => Promise<void> additionalCleanPathPatterns?: () => string[] /** * Override the class used to capture application/x-www-form-urlencoded messages. */ formUrlEncodedImplementation?: () => CodegenNativeType } interface AugmentedCodegenSchema { __cannotUseDeduction: boolean } const RESERVED_WORDS = [ 'abstract', 'assert', 'boolean', 'break', 'byte', 'case', 'catch', 'char', 'class', 'const', 'continue', 'default', 'double', 'do', 'else', 'enum', 'extends', 'false', 'final', 'finally', 'float', 'for', 'goto', 'if', 'implements', 'import', 'instanceof', 'int', 'interface', 'long', 'native', 'new', 'null', 'package', 'private', 'protected', 'public', 'return', 'short', 'static', 'strictfp', 'super', 'switch', 'synchronized', 'this', 'throw', 'throws', 'transient', 'true', 'try', 'void', 'volatile', 'while', ] export function options(config: CodegenConfig, context: JavaGeneratorContext): CodegenOptionsJava { const packageName = configString(config, 'package', 'com.example') const apiPackage = configString(config, 'apiPackage', packageName) const maven = configObject(config, 'maven', undefined) const customizations = configObject(config, 'customizations', undefined) const customTemplates = configString(config, 'customTemplates', undefined) const relativeSourceOutputPath = computeRelativeSourceOutputPath(config) const options: CodegenOptionsJava = { ...javaLikeOptions(config, createJavaLikeContext(context)), apiPackage, apiImplPackage: configString(config, 'apiImplPackage', `${apiPackage}.impl`), apiParamsPackage: nullableConfigString(config, 'apiParamsPackage', `${apiPackage}.params`), modelPackage: configString(config, 'modelPackage', `${packageName}.model`), useBeanValidation: configBoolean(config, 'useBeanValidation', true), validationPackage: configString(config, 'validationPackage', `${packageName}.validation`), includeTests: configBoolean(config, 'includeTests', false), junitVersion: configNumber(config, 'junitVersion', 5), dateImplementation: configString(config, 'dateImplementation', 'java.time.LocalDate'), timeImplementation: configString(config, 'timeImplementation', 'java.time.LocalTime'), dateTimeImplementation: configString(config, 'dateTimeImplementation', 'java.time.OffsetDateTime'), binaryRepresentation: configString(config, 'binaryRepresentation', 'byte[]'), fileRepresentation: configString(config, 'fileRepresentation', configString(config, 'binaryRepresentation', 'byte[]')), hideGenerationTimestamp: configBoolean(config, 'hideGenerationTimestamp', true), imports: configStringArray(config, 'imports', null), maven: maven ? { groupId: configString(maven, 'groupId', 'com.example', 'maven.'), artifactId: configString(maven, 'artifactId', 'api', 'maven.'), version: configString(maven, 'version', '0.0.1', 'maven.'), versions: configObject(maven, 'versions', {}, 'maven.'), } : null, relativeSourceOutputPath, relativeApiSourceOutputPath: configString(config, 'relativeApiSourceOutputPath', relativeSourceOutputPath), relativeApiImplSourceOutputPath: configString(config, 'relativeApiImplSourceOutputPath', relativeSourceOutputPath), relativeResourcesOutputPath: computeRelativeResourcesOutputPath(config), relativeTestOutputPath: computeRelativeTestOutputPath(config), relativeTestResourcesOutputPath: computeRelativeTestResourcesOutputPath(config), customTemplatesPath: customTemplates && computeCustomTemplatesPath(config.configPath, customTemplates), useJakarta: configBoolean(config, 'useJakarta', false), useLombok: configBoolean(config, 'useLombok', false), customizations: { classes: configObject(customizations || {}, 'classes', {}, 'customizations.') as CodegenOptionsJava['customizations'], }, } return options } function createJavaLikeContext(context: JavaGeneratorContext): JavaLikeContext { const javaLikeContext: JavaLikeContext = { ...context, reservedWords: () => RESERVED_WORDS, defaultConstantStyle: ConstantStyle.allCapsSnake, defaultEnumMemberStyle: EnumMemberStyle.constant, } return javaLikeContext } export default function createGenerator(config: CodegenConfig, context: JavaGeneratorContext): Omit<CodegenGenerator, 'generatorType'> { const generatorOptions = options(config, context) const baseGenerator = context.baseGenerator(config, context) const aCommonGenerator = commonGenerator(config, context) return { ...baseGenerator, ...aCommonGenerator, ...javaLikeGenerator(config, createJavaLikeContext(context)), toLiteral: (value, options) => { if (value === undefined) { const defaultValue = context.generator().defaultValue(options) if (defaultValue === null) { return null } return defaultValue.literalValue } const { type, format, required, nullable, schemaType } = options if (value === null) { if (nullable) { return 'null' } const defaultValue = context.generator().defaultValue(options) if (defaultValue === null) { return null } return defaultValue.literalValue } if (schemaType === CodegenSchemaType.ENUM) { return `${options.nativeType.concreteType}.${context.generator().toEnumMemberName(String(value))}` } /* We use the same logic as in nativeTypeUsageTransformer */ const primitive = required && !nullable switch (type) { case 'integer': { if (typeof value === 'string') { const parsedValue = parseInt(value) if (isNaN(parsedValue)) { throw new Error(`toLiteral with type integer called with non-number: ${typeof value} (${value})`) } value = parsedValue } if (typeof value !== 'number') { throw new Error(`toLiteral with type integer called with non-number: ${typeof value} (${value})`) } if (format === 'int32' || !format) { return !primitive ? `java.lang.Integer.valueOf(${value})` : `${value}` } else if (format === 'int64') { return !primitive ? `java.lang.Long.valueOf(${value}L)` : `${value}l` } else { throw new Error(`Unsupported ${type} format: ${format}`) } } case 'number': { if (typeof value === 'string') { const parsedValue = parseFloat(value) if (isNaN(parsedValue)) { throw new Error(`toLiteral with type number called with non-number: ${typeof value} (${value})`) } value = parsedValue } if (typeof value !== 'number') { throw new Error(`toLiteral with type number called with non-number: ${typeof value} (${value})`) } if (!format) { return `new java.math.BigDecimal("${value}")` } else if (format === 'float') { return !primitive ? `java.lang.Float.valueOf(${value}f)` : `${value}f` } else if (format === 'double') { return !primitive ? `java.lang.Double.valueOf(${value}d)` : `${value}d` } else { throw new Error(`Unsupported ${type} format: ${format}`) } } case 'string': { if (typeof value !== 'string' && typeof value !== 'number' && typeof value !== 'boolean') { throw new Error(`toLiteral with type string called with unsupported type: ${typeof value} (${value})`) } if (format === 'byte') { return `"${escapeString(value)}"` } else if (format === 'binary') { return `"${escapeString(value)}".getBytes(java.nio.charset.StandardCharsets.UTF_8)` } else if (format === 'date') { return `${generatorOptions.dateImplementation}.parse("${escapeString(value)}")` } else if (format === 'time') { return `${generatorOptions.timeImplementation}.parse("${escapeString(value)}")` } else if (format === 'date-time') { return `${generatorOptions.dateTimeImplementation}.parse("${escapeString(value)}")` } else if (format === 'uuid') { return `java.util.UUID.fromString("${escapeString(value)}")` } else { return `"${escapeString(value)}"` } } case 'boolean': if (typeof value === 'string') { value = value.toLowerCase() === 'true' } if (typeof value === 'number') { value = value !== 0 } if (typeof value !== 'boolean') { throw new Error(`toLiteral with type boolean called with non-boolean: ${typeof value} (${value})`) } return !primitive ? `java.lang.Boolean.valueOf(${value})` : `${value}` case 'object': if (typeof value === 'string') { if (value) { return value } else { return 'null' } } else { context.log(CodegenLogLevel.WARN, `Literal is unsupported for schema type object: ${debugStringify(value)}`) return 'null' } break case 'file': throw new Error(`Cannot format literal for type ${type}`) case 'array': { const arrayValue = Array.isArray(value) ? value : [value] const component = options.component if (!component) { throw new Error(`toLiteral cannot format array literal without a component type: ${value}`) } return `java.util.Arrays.asList(${arrayValue.map(v => context.generator().toLiteral(v, { ...component.schema, ...component })).join(', ')})` } } throw new Error(`Unsupported literal type name "${type}" in options: ${debugStringify(options)}`) }, toNativeType: (options) => { const { format, schemaType, vendorExtensions } = options /* Note that we return separate componentTypes in this function in case the type is transformed, using nativeTypeTransformer, and the native type becomes primitive as the component type must still be non-primitive. */ if (vendorExtensions && vendorExtensions['x-java-type']) { return new context.NativeType(String(vendorExtensions['x-java-type'])) } /* See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types */ switch (schemaType) { case CodegenSchemaType.INTEGER: { if (format === 'int32' || !format) { return new context.NativeType('java.lang.Integer') } else if (format === 'int64') { return new context.NativeType('java.lang.Long') } else { throw new Error(`Unsupported integer format: ${format}`) } } case CodegenSchemaType.NUMBER: { if (!format) { return new context.NativeType('java.math.BigDecimal') } else if (format === 'float') { return new context.NativeType('java.lang.Float') } else if (format === 'double') { return new context.NativeType('java.lang.Double') } else { throw new Error(`Unsupported number format: ${format}`) } } case CodegenSchemaType.DATE: return new context.NativeType(generatorOptions.dateImplementation, { serializedType: 'java.lang.String', }) case CodegenSchemaType.TIME: return new context.NativeType(generatorOptions.timeImplementation, { serializedType: 'java.lang.String', }) case CodegenSchemaType.DATETIME: return new context.NativeType(generatorOptions.dateTimeImplementation, { serializedType: 'java.lang.String', }) case CodegenSchemaType.STRING: { if (format === 'byte') { /* base64 encoded characters */ return new context.NativeType('java.lang.String') } else if (format === 'uuid') { return new context.NativeType('java.util.UUID', { serializedType: 'java.lang.String', }) } else if (format === 'url') { return new context.NativeType('java.net.URL', { serializedType: 'java.lang.String', }) } else { return new context.NativeType('java.lang.String') } } case CodegenSchemaType.BOOLEAN: { return new context.NativeType('java.lang.Boolean') } case CodegenSchemaType.BINARY: { return new context.NativeType(generatorOptions.binaryRepresentation) } case CodegenSchemaType.FILE: { return new context.NativeType(generatorOptions.fileRepresentation) } case CodegenSchemaType.ANY: { return new context.NativeType('java.lang.Object') } } throw new Error(`Unsupported schema type: ${schemaType}`) }, toNativeObjectType: function(options) { const { scopedName } = options let modelName = `${generatorOptions.modelPackage}` for (const name of scopedName) { modelName += `.${context.generator().toClassName(name)}` } return new context.NativeType(modelName) }, toNativeArrayType: (options) => { const { componentNativeType, uniqueItems } = options if (uniqueItems) { if (options.purpose !== CodegenSchemaPurpose.PARAMETER) { return new context.TransformingNativeType(componentNativeType, { /* We use LinkedHashSet everywhere to make it clear to all users of the API that it's ordered and unique. This also means we don't need to tell Jackson to use LinkedHashSet when deserializing. NOTE: CXF doesn't support LinkedHashSet for parameters (see InjectionUtils.getCollectionType()) so we exclude parameters. */ default: (nativeType) => `java.util.LinkedHashSet<${(nativeType.componentType || nativeType).nativeType}>`, literalType: () => 'java.util.LinkedHashSet', concreteType: (nativeType) => `java.util.LinkedHashSet<${(nativeType.componentType || nativeType).nativeType}>`, }) } else { return new context.TransformingNativeType(componentNativeType, { default: (nativeType) => `java.util.Set<${(nativeType.componentType || nativeType).nativeType}>`, literalType: () => 'java.util.Set', concreteType: (nativeType) => `java.util.HashSet<${(nativeType.componentType || nativeType).nativeType}>`, }) } } else { return new context.TransformingNativeType(componentNativeType, { default: (nativeType) => `java.util.List<${(nativeType.componentType || nativeType).nativeType}>`, literalType: () => 'java.util.List', concreteType: (nativeType) => `java.util.ArrayList<${(nativeType.componentType || nativeType).nativeType}>`, }) } }, toNativeMapType: (options) => { const { keyNativeType, componentNativeType } = options return new context.ComposingNativeType([keyNativeType, componentNativeType], { default: ([keyNativeType, componentNativeType]) => `java.util.Map<${(keyNativeType.componentType || keyNativeType).nativeType}, ${(componentNativeType.componentType || componentNativeType).nativeType}>`, literalType: () => 'java.util.Map', concreteType: ([keyNativeType, componentNativeType]) => `java.util.HashMap<${(keyNativeType.componentType || keyNativeType).nativeType}, ${(componentNativeType.componentType || componentNativeType).nativeType}>`, }) }, nativeTypeUsageTransformer: ({ required, nullable }) => ({ nativeType: function(nativeType, nativeTypeString) { const primitive = required && !nullable if (primitive) { if (nativeTypeString === 'java.lang.Integer') { return 'int' } else if (nativeTypeString === 'java.lang.Boolean') { return 'boolean' } else if (nativeTypeString === 'java.lang.Long') { return 'long' } else if (nativeTypeString === 'java.lang.Float') { return 'float' } else if (nativeTypeString === 'java.lang.Double') { return 'double' } else if (nativeTypeString === 'java.lang.Byte') { return 'byte' } } return nativeTypeString }, componentType: { default: function(nativeType, nativeTypeString) { /* Return the original type so none of our transformations above apply to the type when used as a component. Particularly, we mustn't use our primitive transformations as primitives can't be components, e.g. java.util.List<int> */ return nativeTypeString }, }, }), defaultValue: (options) => { const { schemaType } = options switch (schemaType) { case CodegenSchemaType.ENUM: case CodegenSchemaType.DATE: case CodegenSchemaType.TIME: case CodegenSchemaType.DATETIME: case CodegenSchemaType.BINARY: case CodegenSchemaType.FILE: case CodegenSchemaType.OBJECT: case CodegenSchemaType.STRING: case CodegenSchemaType.ARRAY: case CodegenSchemaType.MAP: case CodegenSchemaType.INTERFACE: return { value: null, literalValue: 'null' } case CodegenSchemaType.NUMBER: { const literalValue = context.generator().toLiteral(0.0, options) if (literalValue === null) { return null } return { value: 0.0, literalValue } } case CodegenSchemaType.INTEGER: { const literalValue = context.generator().toLiteral(0, options) if (literalValue === null) { return null } return { value: 0, literalValue } } case CodegenSchemaType.BOOLEAN: { const literalValue = context.generator().toLiteral(false, options) if (literalValue === null) { return null } return { value: false, literalValue } } } throw new Error(`Unsupported default value type: ${schemaType}`) }, initialValue: (options) => { const { required, schemaType, nativeType, defaultValue } = options /* Default values in the spec are intended to be applied when a client or server receives a response or request, respectively, and values are missing. This implementation means that properties with defaults will get those default values as their initial value, meaning that any properties that are omitted in the _received_ request or response will have the default value. TODO But it also means that any requests or responses _sent_ will _also_ have the default values, rather than omitting the property and letting the receiving side apply the default value. This is NOT according to the spec and should be fixed. */ if (defaultValue) { return defaultValue } if (!required) { return null } /* We create empty collections for required properties in the Java code. This is because we generate convenience methods for collections that initialise the collection when adding the first element, which would mean if we didn't initialise required collection properties we might end up sending a null collection value if the code didn't _add_ any elements. This would then require explicitly initialising each required collection in user code, either every time, or whenever no elements are added to it. Therfore we are not able to detect whether the user code has forgotten to populate the collection, like we are with scalar required properties, so we populate it with an empty collection so we always generate a valid result. */ switch (schemaType) { case CodegenSchemaType.ARRAY: /* Initialise required array properties with an empty array */ return { value: [], literalValue: `new ${nativeType.concreteType}()` } case CodegenSchemaType.MAP: /* Initialise empty map properties with an empty map */ return { value: {}, literalValue: `new ${nativeType.concreteType}()` } default: return null } }, operationGroupingStrategy: () => { return context.operationGroupingStrategies.addToGroupsByTagOrPath }, allOfStrategy: () => CodegenAllOfStrategy.OBJECT, anyOfStrategy: () => CodegenAnyOfStrategy.OBJECT, oneOfStrategy: () => CodegenOneOfStrategy.INTERFACE, supportsInheritance: () => true, supportsMultipleInheritance: () => false, nativeCompositionCanBeScope: () => false, nativeComposedSchemaRequiresName: () => false, nativeComposedSchemaRequiresObjectLikeOrWrapper: () => false, interfaceCanBeNested: () => true, watchPaths: () => { const result = [path.resolve(__dirname, '..', 'templates')] if (context.additionalWatchPaths) { result.push(...context.additionalWatchPaths()) } if (generatorOptions.customTemplatesPath) { result.push(generatorOptions.customTemplatesPath) } return result }, cleanPathPatterns: () => { const relativeSourceOutputPath = generatorOptions.relativeSourceOutputPath const modelPackagePath = packageToPath(generatorOptions.modelPackage) const validationPackagePath = packageToPath(generatorOptions.validationPackage) const result = [ path.join(relativeSourceOutputPath, modelPackagePath, '*.java'), path.join(relativeSourceOutputPath, validationPackagePath, 'Request.java'), path.join(relativeSourceOutputPath, validationPackagePath, 'Response.java'), ] if (context.additionalCleanPathPatterns) { result.push(...context.additionalCleanPathPatterns()) } return result }, templateRootContext: () => { return { ...aCommonGenerator.templateRootContext(), ...generatorOptions, generatorClass: '@openapi-generator-plus/java-jaxrs-generator', } }, exportTemplates: async(outputPath, doc) => { const hbs = Handlebars.create() registerStandardHelpers(hbs, context) hbs.registerHelper('getter', function(property: CodegenProperty) { if (property.schema.schemaType === CodegenSchemaType.BOOLEAN && property.required && !property.nullable) { return `is${capitalize(context.generator().toIdentifier(property.name))}` } else { return `get${capitalize(context.generator().toIdentifier(property.name))}` } }) hbs.registerHelper('setter', function(property: CodegenProperty) { return `set${capitalize(context.generator().toIdentifier(property.name))}` }) hbs.registerHelper('escapeString', function(value: string) { // eslint-disable-next-line prefer-rest-params const options = arguments[arguments.length - 1] as ActualHelperOptions try { return escapeString(value) } catch (error) { throw new Error(`${error instanceof Error ? error.message : error} @ ${sourcePosition(options)}`) } }) hbs.registerHelper('javax', function() { if (generatorOptions.useJakarta) { return 'jakarta' } else { return 'javax' } }) await loadTemplates(path.resolve(__dirname, '..', 'templates'), hbs) if (context.loadAdditionalTemplates) { await context.loadAdditionalTemplates(hbs) } if (generatorOptions.customTemplatesPath) { await loadTemplates(generatorOptions.customTemplatesPath, hbs) } const rootContext = context.generator().templateRootContext() const relativeSourceOutputPath = generatorOptions.relativeSourceOutputPath const relativeTestOutputPath = generatorOptions.relativeTestOutputPath /* Augment operations */ for (const groups of doc.groups) { for (const operation of groups.operations) { /* We use a params object if an operation has multiple parameters */ operation.useParamsClasses = generatorOptions.apiParamsPackage && operation.parameters ? idx.size(operation.parameters) > 1 : false } } const modelPackagePath = packageToPath(generatorOptions.modelPackage) for (const schema of context.utils.values(doc.schemas)) { if (isCodegenObjectSchema(schema)) { await emit('pojo', path.join(outputPath, relativeSourceOutputPath, modelPackagePath, `${context.generator().toClassName(schema.name)}.java`), { ...rootContext, pojo: schema }, true, hbs) } else if (isCodegenEnumSchema(schema)) { await emit('enum', path.join(outputPath, relativeSourceOutputPath, modelPackagePath, `${context.generator().toClassName(schema.name)}.java`), { ...rootContext, enum: schema }, true, hbs) } else if (isCodegenInterfaceSchema(schema)) { await emit('interface', path.join(outputPath, relativeSourceOutputPath, modelPackagePath, `${context.generator().toClassName(schema.name)}.java`), { ...rootContext, interface: schema }, true, hbs) } else if (isCodegenWrapperSchema(schema)) { await emit('wrapper', path.join(outputPath, relativeSourceOutputPath, modelPackagePath, `${context.generator().toClassName(schema.name)}.java`), { ...rootContext, schema }, true, hbs) } } if (generatorOptions.useBeanValidation) { const validationPackagePath = packageToPath(generatorOptions.validationPackage) await emit('validation/Request', path.join(outputPath, relativeSourceOutputPath, validationPackagePath, 'Request.java'), rootContext, true, hbs) await emit('validation/Response', path.join(outputPath, relativeSourceOutputPath, validationPackagePath, 'Response.java'), rootContext, true, hbs) } const maven = generatorOptions.maven if (maven) { await emit('pom', path.join(outputPath, 'pom.xml'), { ...rootContext, ...maven }, false, hbs) } if (generatorOptions.includeTests && hbs.partials['tests/apiTest']) { const apiPackagePath = packageToPath(generatorOptions.apiPackage) for (const group of doc.groups) { const operations = group.operations if (!operations.length) { continue } await emit('tests/apiTest', path.join(outputPath, relativeTestOutputPath, apiPackagePath, `${context.generator().toClassName(group.name)}ApiTest.java`), { ...rootContext, ...group }, false, hbs) } } if (context.additionalExportTemplates) { await context.additionalExportTemplates(outputPath, doc, hbs, rootContext) } }, postProcessDocument: (doc) => { for (const group of doc.groups) { for (const op of group.operations) { /* Fix form post request bodies, as we can't properly handle them using https://docs.oracle.com/javaee/7/api/javax/ws/rs/BeanParam.html yet */ if (op.requestBody && op.consumes && op.consumes[0].mimeType === 'application/x-www-form-urlencoded') { op.requestBody.nativeType = context.formUrlEncodedImplementation ? context.formUrlEncodedImplementation() : new context.NativeType(`${generatorOptions.useJakarta ? 'jakarta' : 'javax'}.ws.rs.core.MultivaluedHashMap<java.lang.String, java.lang.String>`) } } } }, postProcessSchema(schema) { checkCannotUseDeduction(schema) }, checkPropertyCompatibility: (parentProp, childProp) => { if (!baseGenerator.checkPropertyCompatibility(parentProp, childProp)) { return false } /* Because in Java we use a java.util.Optional if a property is nullable, properties are not compatible if their nullability varies. */ if (!parentProp.nullable !== !childProp.nullable) { return false } return true }, } } /** * Identify polymorphic schemas that cannot use Jackson's DEDUCTION polymorphism, as it only supports object structures. */ function checkCannotUseDeduction(schema: CodegenSchema) { if (isCodegenObjectSchema(schema) && schema.polymorphic && schema.children) { (schema as unknown as AugmentedCodegenSchema).__cannotUseDeduction = false for (const child of schema.children) { if (child.type !== 'object') { (schema as unknown as AugmentedCodegenSchema).__cannotUseDeduction = true break } } } if (isCodegenInterfaceSchema(schema) && schema.polymorphic && schema.implementors) { (schema as unknown as AugmentedCodegenSchema).__cannotUseDeduction = false for (const impl of schema.implementors) { if (impl.type !== 'object') { (schema as unknown as AugmentedCodegenSchema).__cannotUseDeduction = true break } } } }