-
-
Notifications
You must be signed in to change notification settings - Fork 197
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Artur Ciocanu
committed
Dec 16, 2023
1 parent
7f7c3b3
commit 80c3e34
Showing
16 changed files
with
898 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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( | ||
word, | ||
RESERVED_SCALA_KEYWORDS, | ||
forceLowerCase | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
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( | ||
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( | ||
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 | ||
}); | ||
} | ||
} |
Oops, something went wrong.