Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: java generator could generate illegal package names #1084

Merged
merged 1 commit into from
Jan 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 30 additions & 22 deletions src/generators/java/JavaGenerator.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {
AbstractGenerator,
import {
AbstractGenerator,
CommonGeneratorOptions,
defaultGeneratorOptions
} from '../AbstractGenerator';
Expand All @@ -10,9 +10,9 @@ import { ClassRenderer } from './renderers/ClassRenderer';
import { EnumRenderer } from './renderers/EnumRenderer';
import { isReservedJavaKeyword } from './Constants';
import { Logger } from '../../';
import { constrainMetaModel, Constraints } from '../../helpers/ConstrainHelpers';
import { constrainMetaModel, Constraints } from '../../helpers';
import { JavaDefaultConstraints, JavaDefaultTypeMapping } from './JavaConstrainer';
import { DeepPartial, mergePartialAndDefault } from '../../utils/Partials';
import { DeepPartial, mergePartialAndDefault } from '../../utils';

export interface JavaOptions extends CommonGeneratorOptions<JavaPreset> {
collectionType: 'List' | 'Array';
Expand All @@ -25,7 +25,7 @@ export interface JavaRenderCompleteModelOptions {
export class JavaGenerator extends AbstractGenerator<JavaOptions, JavaRenderCompleteModelOptions> {
static defaultOptions: JavaOptions = {
...defaultGeneratorOptions,
defaultPreset: JAVA_DEFAULT_PRESET,
defaultPreset: JAVA_DEFAULT_PRESET,
collectionType: 'Array',
typeMapping: JavaDefaultTypeMapping,
constraints: JavaDefaultConstraints
Expand All @@ -37,20 +37,20 @@ export class JavaGenerator extends AbstractGenerator<JavaOptions, JavaRenderComp
const realizedOptions = mergePartialAndDefault(JavaGenerator.defaultOptions, options) as JavaOptions;
super('Java', realizedOptions);
}

splitMetaModel(model: MetaModel): MetaModel[] {
//These are the models that we have separate renderers for
const metaModelsToSplit = {
splitEnum: true,
splitEnum: true,
splitObject: true
};
return split(model, metaModelsToSplit);
}

constrainToMetaModel(model: MetaModel): ConstrainedMetaModel {
return constrainMetaModel<JavaOptions>(
this.options.typeMapping,
this.options.constraints,
this.options.typeMapping,
this.options.constraints,
{
metaModel: model,
options: this.options,
Expand All @@ -61,33 +61,31 @@ export class JavaGenerator extends AbstractGenerator<JavaOptions, JavaRenderComp

/**
* Render a scattered model, where the source code and library and model dependencies are separated.
*
* @param model
* @param inputModel
*
* @param model
* @param inputModel
*/
render(model: ConstrainedMetaModel, inputModel: InputMetaModel): Promise<RenderOutput> {
if (model instanceof ConstrainedObjectModel) {
return this.renderClass(model, inputModel);
} else if (model instanceof ConstrainedEnumModel) {
return this.renderEnum(model, inputModel);
}
}
Logger.warn(`Java generator, cannot generate this type of model, ${model.name}`);
return Promise.resolve(RenderOutput.toRenderOutput({ result: '', renderedName: '', dependencies: [] }));
}

/**
* Render a complete model result where the model code, library and model dependencies are all bundled appropriately.
*
*
* For Java you need to specify which package the model is placed under.
*
* @param model
* @param inputModel
*
* @param model
* @param inputModel
* @param options used to render the full output
*/
async renderCompleteModel(model: ConstrainedMetaModel, inputModel: InputMetaModel, options: JavaRenderCompleteModelOptions): Promise<RenderOutput> {
if (isReservedJavaKeyword(options.packageName)) {
throw new Error(`You cannot use reserved Java keyword (${options.packageName}) as package name, please use another.`);
}
this.assertPackageIsValid(options);

const outputModel = await this.render(model, inputModel);
const modelDependencies = model.getNearestDependencies().map((dependencyModel) => {
Expand All @@ -96,10 +94,20 @@ export class JavaGenerator extends AbstractGenerator<JavaOptions, JavaRenderComp
const outputContent = `package ${options.packageName};
${modelDependencies.join('\n')}
${outputModel.dependencies.join('\n')}
${outputModel.result}`;
${outputModel.result}`;
return RenderOutput.toRenderOutput({result: outputContent, renderedName: outputModel.renderedName, dependencies: outputModel.dependencies});
}

private assertPackageIsValid(options: JavaRenderCompleteModelOptions) {
const reservedWords = options.packageName
.split('.')
.filter(subpackage => isReservedJavaKeyword(subpackage, true));

if (reservedWords.length > 0) {
throw new Error(`You cannot use '${options.packageName}' as a package name, contains reserved keywords: [${reservedWords.join(', ')}]`);
}
}

async renderClass(model: ConstrainedObjectModel, inputModel: InputMetaModel): Promise<RenderOutput> {
const presets = this.getPresets('class');
const renderer = new ClassRenderer(this.options, this, presets, model, inputModel);
Expand All @@ -108,7 +116,7 @@ ${outputModel.result}`;
}

async renderEnum(model: ConstrainedEnumModel, inputModel: InputMetaModel): Promise<RenderOutput> {
const presets = this.getPresets('enum');
const presets = this.getPresets('enum');
const renderer = new EnumRenderer(this.options, this, presets, model, inputModel);
const result = await renderer.runSelfPreset();
return RenderOutput.toRenderOutput({result, renderedName: model.name, dependencies: renderer.dependencies});
Expand Down
33 changes: 14 additions & 19 deletions test/generators/java/JavaGenerator.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { JavaGenerator } from '../../../src/generators';
import { JavaGenerator } from '../../../src/generators';

describe('JavaGenerator', () => {
let generator: JavaGenerator;
Expand Down Expand Up @@ -91,12 +91,11 @@ describe('JavaGenerator', () => {
type: 'string',
enum: ['Texas', 'Alabama', 'California', 'New York'],
};
const expectedDependencies = [];

const models = await generator.generate(doc);
expect(models).toHaveLength(1);
expect(models[0].result).toMatchSnapshot();
expect(models[0].dependencies).toEqual(expectedDependencies);
expect(models[0].dependencies).toEqual([]);
});

test('should render `enum` type (integer type)', async () => {
Expand All @@ -105,12 +104,11 @@ describe('JavaGenerator', () => {
type: 'integer',
enum: [0, 1, 2, 3],
};
const expectedDependencies = [];


const models = await generator.generate(doc);
expect(models).toHaveLength(1);
expect(models[0].result).toMatchSnapshot();
expect(models[0].dependencies).toEqual(expectedDependencies);
expect(models[0].dependencies).toEqual([]);
});

test('should render `enum` type (union type)', async () => {
Expand All @@ -119,12 +117,11 @@ describe('JavaGenerator', () => {
type: ['string', 'integer', 'boolean'],
enum: ['Texas', 'Alabama', 0, 1, '1', true, {test: 'test'}],
};
const expectedDependencies = [];


const models = await generator.generate(doc);
expect(models).toHaveLength(1);
expect(models[0].result).toMatchSnapshot();
expect(models[0].dependencies).toEqual(expectedDependencies);
expect(models[0].dependencies).toEqual([]);
});

test('should render custom preset for `enum` type', async () => {
Expand All @@ -144,25 +141,23 @@ describe('JavaGenerator', () => {
}
}
] });
const expectedDependencies = [];


const models = await generator.generate(doc);
expect(models).toHaveLength(1);
expect(models[0].result).toMatchSnapshot();
expect(models[0].dependencies).toEqual(expectedDependencies);
expect(models[0].dependencies).toEqual([]);
});

test('should render enums with translated special characters', async () => {
const doc = {
$id: 'States',
enum: ['test+', 'test', 'test-', 'test?!', '*test']
};
const expectedDependencies = [];


const models = await generator.generate(doc);
expect(models).toHaveLength(1);
expect(models[0].result).toMatchSnapshot();
expect(models[0].dependencies).toEqual(expectedDependencies);
expect(models[0].dependencies).toEqual([]);
});

test('should render List type for collections', async () => {
Expand Down Expand Up @@ -205,13 +200,13 @@ describe('JavaGenerator', () => {
},
required: ['street_name', 'city', 'state', 'house_number', 'array_type'],
};
const config = {packageName: 'test.package'};
const config = {packageName: 'test.packageName'};
const models = await generator.generateCompleteModels(doc, config);
expect(models).toHaveLength(2);
expect(models[0].result).toMatchSnapshot();
expect(models[1].result).toMatchSnapshot();
});
test('should throw error when reserved keyword is used for package name', async () => {
test('should throw error when reserved keyword is used in any part of the package name', async () => {
const doc = {
$id: 'Address',
type: 'object',
Expand All @@ -231,8 +226,8 @@ describe('JavaGenerator', () => {
},
required: ['street_name', 'city', 'state', 'house_number', 'array_type'],
};
const config = {packageName: 'package'};
const expectedError = new Error('You cannot use reserved Java keyword (package) as package name, please use another.');
const config = {packageName: 'valid.package.correct.class'};
const expectedError = new Error('You cannot use \'valid.package.correct.class\' as a package name, contains reserved keywords: [package, class]');
await expect(generator.generateCompleteModels(doc, config)).rejects.toEqual(expectedError);
});
});
6 changes: 3 additions & 3 deletions test/generators/java/__snapshots__/JavaGenerator.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -211,8 +211,8 @@ exports[`JavaGenerator should render enums with translated special characters 1`
`;
exports[`JavaGenerator should render models and their dependencies 1`] = `
"package test.package;
import test.package.OtherModel;
"package test.packageName;
import test.packageName.OtherModel;
import java.util.Map;
public class Address {
private String streetName;
Expand Down Expand Up @@ -255,7 +255,7 @@ public class Address {
`;
exports[`JavaGenerator should render models and their dependencies 2`] = `
"package test.package;
"package test.packageName;
import java.util.Map;
public class OtherModel {
Expand Down