Skip to content

Commit

Permalink
Adding Scala generator scaffolding
Browse files Browse the repository at this point in the history
  • Loading branch information
Artur Ciocanu committed Dec 16, 2023
1 parent 7f7c3b3 commit 80c3e34
Show file tree
Hide file tree
Showing 16 changed files with 898 additions and 0 deletions.
14 changes: 14 additions & 0 deletions src/generators/scala/Constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { checkForReservedKeyword } from '../../helpers';

export const RESERVED_SCALA_KEYWORDS = ['abstract', 'continue'];

export function isReservedScalaKeyword(
word: string,
forceLowerCase = true
): boolean {
return checkForReservedKeyword(

Check failure on line 9 in src/generators/scala/Constants.ts

View workflow job for this annotation

GitHub Actions / Test NodeJS PR - ubuntu-latest

Replace `⏎····word,⏎····RESERVED_SCALA_KEYWORDS,⏎····forceLowerCase⏎··` with `word,·RESERVED_SCALA_KEYWORDS,·forceLowerCase`
word,
RESERVED_SCALA_KEYWORDS,
forceLowerCase
);
}
62 changes: 62 additions & 0 deletions src/generators/scala/ScalaConstrainer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { Constraints, TypeMapping } from '../../helpers';
import {
defaultEnumKeyConstraints,
defaultEnumValueConstraints
} from './constrainer/EnumConstrainer';
import { defaultModelNameConstraints } from './constrainer/ModelNameConstrainer';
import { defaultPropertyKeyConstraints } from './constrainer/PropertyKeyConstrainer';
import { defaultConstantConstraints } from './constrainer/ConstantConstrainer';
import { ScalaDependencyManager } from './ScalaDependencyManager';
import { ScalaOptions } from './ScalaGenerator';

export const ScalaDefaultTypeMapping: TypeMapping<
ScalaOptions,
ScalaDependencyManager
> = {
Object({ constrainedModel }): string {
//Returning name here because all object models have been split out
return constrainedModel.name;
},
Reference({ constrainedModel }): string {
return constrainedModel.name;
},
Any(): string {
return '';
},
Float(): string {
return '';
},
Integer(): string {
return '';
},
String(): string {
return '';
},
Boolean(): string {
return '';
},
Tuple(): string {
return '';
},
Array(): string {
return '';
},
Enum({ constrainedModel }): string {
//Returning name here because all enum models have been split out
return constrainedModel.name;
},
Union(): string {
return '';
},
Dictionary(): string {
return '';
}
};

export const ScalaDefaultConstraints: Constraints = {
enumKey: defaultEnumKeyConstraints(),
enumValue: defaultEnumValueConstraints(),
modelName: defaultModelNameConstraints(),
propertyKey: defaultPropertyKeyConstraints(),
constant: defaultConstantConstraints()
};
11 changes: 11 additions & 0 deletions src/generators/scala/ScalaDependencyManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { AbstractDependencyManager } from '../AbstractDependencyManager';
import { ScalaOptions } from './ScalaGenerator';

export class ScalaDependencyManager extends AbstractDependencyManager {
constructor(
public options: ScalaOptions,
dependencies: string[] = []
) {
super(dependencies);
}
}
45 changes: 45 additions & 0 deletions src/generators/scala/ScalaFileGenerator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { ScalaGenerator, ScalaRenderCompleteModelOptions } from '.';
import { InputMetaModel, OutputModel } from '../../models';
import * as path from 'path';
import { AbstractFileGenerator } from '../AbstractFileGenerator';
import { FileHelpers } from '../../helpers';

export class ScalaFileGenerator
extends ScalaGenerator
implements AbstractFileGenerator<ScalaRenderCompleteModelOptions>
{
/**
* Generates all the models to an output directory each model with their own separate files.
*
* @param input
* @param outputDirectory where you want the models generated to
* @param options
*/
public async generateToFiles(
input: Record<string, unknown> | InputMetaModel,
outputDirectory: string,
options?: ScalaRenderCompleteModelOptions,
ensureFilesWritten = false
): Promise<OutputModel[]> {
let generatedModels = await this.generateCompleteModels(
input,
options || {}
);
//Filter anything out that have not been successfully generated
generatedModels = generatedModels.filter((outputModel) => {
return outputModel.modelName !== '';
});
for (const outputModel of generatedModels) {
const filePath = path.resolve(
outputDirectory,
`${outputModel.modelName}.MYEXTENSION`
);
await FileHelpers.writerToFileSystem(
outputModel.result,
filePath,
ensureFilesWritten
);
}
return generatedModels;
}
}
245 changes: 245 additions & 0 deletions src/generators/scala/ScalaGenerator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
import {
AbstractGenerator,
AbstractGeneratorRenderArgs,
AbstractGeneratorRenderCompleteModelArgs,
CommonGeneratorOptions,
defaultGeneratorOptions
} from '../AbstractGenerator';
import {
ConstrainedEnumModel,
ConstrainedMetaModel,
ConstrainedObjectModel,
InputMetaModel,
MetaModel,
RenderOutput
} from '../../models';
import { split, TypeMapping } from '../../helpers';
import { ScalaPreset, SCALA_DEFAULT_PRESET } from './ScalaPreset';
import { ClassRenderer } from './renderers/ClassRenderer';
import { EnumRenderer } from './renderers/EnumRenderer';
import { isReservedScalaKeyword } from './Constants';
import { Logger } from '../..';
import {
constrainMetaModel,
Constraints
} from '../../helpers/ConstrainHelpers';
import {
ScalaDefaultConstraints,
ScalaDefaultTypeMapping
} from './ScalaConstrainer';
import { DeepPartial, mergePartialAndDefault } from '../../utils/Partials';
import { ScalaDependencyManager } from './ScalaDependencyManager';

export interface ScalaOptions

Check failure on line 33 in src/generators/scala/ScalaGenerator.ts

View workflow job for this annotation

GitHub Actions / Test NodeJS PR - ubuntu-latest

Delete `⏎·`
extends CommonGeneratorOptions<ScalaPreset> {
typeMapping: TypeMapping<ScalaOptions, ScalaDependencyManager>;
constraints: Constraints;
}
export interface ScalaRenderCompleteModelOptions {
packageName: string;
}
export class ScalaGenerator extends AbstractGenerator<
ScalaOptions,
ScalaRenderCompleteModelOptions
> {
static defaultOptions: ScalaOptions = {
...defaultGeneratorOptions,
defaultPreset: SCALA_DEFAULT_PRESET,
typeMapping: ScalaDefaultTypeMapping,
constraints: ScalaDefaultConstraints
};

static defaultCompleteModelOptions: ScalaRenderCompleteModelOptions = {
packageName: 'Asyncapi.Models'
};

constructor(options?: DeepPartial<ScalaOptions>) {
const realizedOptions = ScalaGenerator.getScalaOptions(options);
super('Scala', realizedOptions);
}

/**
* Returns the Scala options by merging custom options with default ones.
*/
static getScalaOptions(

Check failure on line 64 in src/generators/scala/ScalaGenerator.ts

View workflow job for this annotation

GitHub Actions / Test NodeJS PR - ubuntu-latest

Replace `⏎····options?:·DeepPartial<ScalaOptions>⏎··` with `options?:·DeepPartial<ScalaOptions>`
options?: DeepPartial<ScalaOptions>
): ScalaOptions {
const optionsToUse = mergePartialAndDefault(
ScalaGenerator.defaultOptions,
options
) as ScalaOptions;
//Always overwrite the dependency manager unless user explicitly state they want it (ignore default temporary dependency manager)
if (options?.dependencyManager === undefined) {
optionsToUse.dependencyManager = () => {
return new ScalaDependencyManager(optionsToUse);
};
}
return optionsToUse;
}

/**
* Wrapper to get an instance of the dependency manager
*/
getDependencyManager(options: ScalaOptions): ScalaDependencyManager {
return this.getDependencyManagerInstance(

Check failure on line 84 in src/generators/scala/ScalaGenerator.ts

View workflow job for this annotation

GitHub Actions / Test NodeJS PR - ubuntu-latest

Replace `⏎······options⏎····` with `options`
options
) as ScalaDependencyManager;
}

/**
* This function makes sure we split up the MetaModels accordingly to what we want to render as models.
*/
splitMetaModel(model: MetaModel): MetaModel[] {
//These are the models that we have separate renderers for
const metaModelsToSplit = {
splitEnum: true,
splitObject: true
};
return split(model, metaModelsToSplit);
}

constrainToMetaModel(
model: MetaModel,
options: DeepPartial<ScalaOptions>
): ConstrainedMetaModel {
const optionsToUse = ScalaGenerator.getScalaOptions({
...this.options,
...options
});
const dependencyManagerToUse = this.getDependencyManager(optionsToUse);
return constrainMetaModel<ScalaOptions, ScalaDependencyManager>(
this.options.typeMapping,
this.options.constraints,
{
metaModel: model,
dependencyManager: dependencyManagerToUse,
options: this.options,
constrainedName: '' //This is just a placeholder, it will be constrained within the function
}
);
}

/**
* Render a scattered model, where the source code and library and model dependencies are separated.
*
* @param model
* @param inputModel
*/
render(
args: AbstractGeneratorRenderArgs<ScalaOptions>
): Promise<RenderOutput> {
if (args.constrainedModel instanceof ConstrainedObjectModel) {
return this.renderClass(args.constrainedModel, args.inputModel);
} else if (args.constrainedModel instanceof ConstrainedEnumModel) {
return this.renderEnum(args.constrainedModel, args.inputModel);
}
Logger.warn(
`Scala generator, cannot generate this type of model, ${args.constrainedModel.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 Scala you need to specify which package the model is placed under.
*
* @param model
* @param inputModel
* @param options used to render the full output
*/
async renderCompleteModel(
args: AbstractGeneratorRenderCompleteModelArgs<
ScalaOptions,
ScalaRenderCompleteModelOptions
>
): Promise<RenderOutput> {
const completeModelOptionsToUse =
mergePartialAndDefault<ScalaRenderCompleteModelOptions>(
ScalaGenerator.defaultCompleteModelOptions,
args.completeOptions
);

if (isReservedScalaKeyword(completeModelOptionsToUse.packageName)) {
throw new Error(
`You cannot use reserved Scala keyword (${args.completeOptions.packageName}) as package name, please use another.`
);
}

const outputModel = await this.render(args);
const modelDependencies = args.constrainedModel
.getNearestDependencies()
.map((dependencyModel) => {
return `import ${completeModelOptionsToUse.packageName}.${dependencyModel.name};`;
});
const outputContent = `package ${completeModelOptionsToUse.packageName};
${modelDependencies.join('\n')}
${outputModel.dependencies.join('\n')}
${outputModel.result}`;
return RenderOutput.toRenderOutput({
result: outputContent,
renderedName: outputModel.renderedName,
dependencies: outputModel.dependencies
});
}

async renderClass(
model: ConstrainedObjectModel,
inputModel: InputMetaModel,
options?: Partial<ScalaOptions>
): Promise<RenderOutput> {
const optionsToUse = ScalaGenerator.getScalaOptions({
...this.options,
...options
});
const dependencyManagerToUse = this.getDependencyManager(optionsToUse);
const presets = this.getPresets('class');
const renderer = new ClassRenderer(
this.options,
this,
presets,
model,
inputModel,
dependencyManagerToUse
);
const result = await renderer.runSelfPreset();
return RenderOutput.toRenderOutput({
result,
renderedName: model.name,
dependencies: dependencyManagerToUse.dependencies
});
}

async renderEnum(
model: ConstrainedEnumModel,
inputModel: InputMetaModel,
options?: Partial<ScalaOptions>
): Promise<RenderOutput> {
const optionsToUse = ScalaGenerator.getScalaOptions({
...this.options,
...options
});
const dependencyManagerToUse = this.getDependencyManager(optionsToUse);
const presets = this.getPresets('enum');
const renderer = new EnumRenderer(
this.options,
this,
presets,
model,
inputModel,
dependencyManagerToUse
);
const result = await renderer.runSelfPreset();
return RenderOutput.toRenderOutput({
result,
renderedName: model.name,
dependencies: dependencyManagerToUse.dependencies
});
}
}
Loading

0 comments on commit 80c3e34

Please sign in to comment.