genezio
Version:
Command line utility to interact with Genezio infrastructure.
456 lines (446 loc) • 17 kB
JavaScript
import Mustache from "mustache";
import { AstNodeType, } from "../../models/genezioModels.js";
import { TriggerType } from "../../projectConfiguration/yaml/models.js";
import { goSdk } from "../templates/goSdk.js";
// const GO_FORBIDDEN_WORDS_REGEX = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
const GO_RESERVED_WORDS = [
"break",
"default",
"func",
"interface",
"select",
"case",
"defer",
"go",
"map",
"struct",
"chan",
"else",
"goto",
"package",
"switch",
"const",
"fallthrough",
"if",
"range",
"type",
"continue",
"for",
"import",
"return",
"var",
"true",
"false",
"iota",
"nil",
"int",
"int8",
"int16",
"int32",
"int64",
"uint",
"uint8",
"uint16",
"uint32",
"uint64",
"float32",
"float64",
"complex64",
"complex128",
"string",
"bool",
"byte",
"rune",
"uintptr",
"array",
"slice",
"make",
"new",
"panic",
"recover",
];
const modelsView = `/**
* This is an auto generated code. This code should not be modified since the file can be overwritten
* if new genezio commands are executed.
*/
package genezioSdk
{{#externalTypes}}
type {{name}} struct {
{{#fields}}
{{goFieldName}} {{{type}}} ` +
'`json:"{{fieldName}}"`' +
`
{{/fields}}
}
{{/externalTypes}}
{{#enumTypes}}
const (
{{#fields}}
{{fieldName}} {{{type}}}
{{/fields}}
)
{{/enumTypes}}
`;
const template = `/**
* This is an auto generated code. This code should not be modified since the file can be overwritten
* if new genezio commands are executed.
*/
package genezioSdk
{{#imports}}
import {{#named}}{{{name}}} {{/named}}"{{{path}}}"
{{/imports}}
{{#classDocLines}}// {{{.}}}{{/classDocLines}}
type {{{className}}} struct {
remote *Remote
}
func New{{{className}}}() *{{{className}}} {
return &{{{className}}}{remote: &Remote{URL: "{{{_url}}}" }}
}
{{#methods}}
{{#methodDocLines}}
// {{.}}
{{/methodDocLines}}
func (f *{{{className}}}) {{{name}}}({{#parameters}}{{{name}}}{{^last}}, {{/last}}{{/parameters}}) {{#isVoid}}error{{/isVoid}}{{^isVoid}}({{{returnType}}}, error){{/isVoid}} {
{{#isVoid}}_{{/isVoid}}{{^isVoid}}result{{/isVoid}}, err := f.remote.Call({{{methodCaller}}}{{#sendParameters}}{{{name}}}{{^last}}, {{/last}}{{/sendParameters}})
{{^isVoid}}
var castResult {{{returnType}}}
{{/isVoid}}
if err != nil {
return {{^isVoid}}castResult, {{/isVoid}}err
}
{{^isVoid}}
{{#isPrimitiveReturnType}}
{{#isInt}}
castResult = int(result.(float64))
{{/isInt}}
{{^isInt}}
castResult = result.({{{returnType}}})
{{/isInt}}
{{/isPrimitiveReturnType}}
{{^isPrimitiveReturnType}}
jsonMap, err := json.Marshal(result)
if err != nil {
return {{^isVoid}}castResult, {{/isVoid}}err
}
err = json.Unmarshal(jsonMap, &castResult)
if err != nil {
return {{^isVoid}}castResult, {{/isVoid}}err
}
{{/isPrimitiveReturnType}}
{{/isVoid}}
return {{^isVoid}}castResult, {{/isVoid}}err
}
{{/methods}}
`;
class SdkGenerator {
async generateSdk(sdkGeneratorInput) {
const generateSdkOutput = {
files: [],
};
const _url = "%%%link_to_be_replace%%%";
const typesView = {
externalTypes: [],
enumTypes: [],
};
for (const classInfo of sdkGeneratorInput.classesInfo) {
const view = {
classDocLines: undefined,
className: undefined,
_url: _url,
methods: [],
imports: [],
};
const externalTypes = [];
const classConfiguration = classInfo.classConfiguration;
let classDefinition = undefined;
if (classInfo.program.body === undefined) {
continue;
}
for (const elem of classInfo.program.body) {
if (elem.type === AstNodeType.ClassDefinition) {
classDefinition = elem;
}
else if (elem.type == AstNodeType.StructLiteral) {
externalTypes.push(elem);
const structLiteral = elem;
for (let i = 0; i < structLiteral.typeLiteral.properties.length; i++) {
const property = structLiteral.typeLiteral.properties[i];
if (property.name == "undefined") {
property.name = structLiteral.name + i;
}
}
!this.checkIfTypeExists(structLiteral.name, typesView) &&
typesView.externalTypes.push({
name: structLiteral.name,
fields: structLiteral.typeLiteral.properties.map((e) => ({
type: this.getParamType(e),
goFieldName: e.name[0].toUpperCase() + e.name.slice(1),
fieldName: e.name,
})),
});
}
else if (elem.type == AstNodeType.Enum) {
const enumType = elem;
!this.checkIfTypeExists(enumType.name, typesView) &&
typesView.enumTypes.push({
name: enumType.name[0].toUpperCase() + enumType.name.slice(1),
fields: enumType.cases.map((e) => ({
type: typeof e.value == "number"
? ` = ${e.value}`
: ` = "${e.value}"`,
goFieldName: e.name[0].toUpperCase() + e.name.slice(1),
fieldName: e.name,
})),
});
}
}
if (classDefinition === undefined) {
continue;
}
view.className = classDefinition.name;
view.classDocLines = classDefinition.docString?.replace(/\n+$/, "").split("\n");
let exportClassChecker = false;
for (const methodDefinition of classDefinition.methods) {
const methodConfiguration = classConfiguration.methods.find((e) => e.name === methodDefinition.name);
const methodConfigurationType = methodConfiguration?.type || classConfiguration.type;
if (methodConfigurationType !== TriggerType.jsonrpc ||
classConfiguration.type !== TriggerType.jsonrpc) {
continue;
}
exportClassChecker = true;
const methodView = {
name: methodDefinition.name[0].toUpperCase() + methodDefinition.name.slice(1),
parameters: [],
returnType: this.getReturnType(methodDefinition.returnType),
isVoid: methodDefinition.returnType?.type === AstNodeType.VoidLiteral,
isPrimitiveReturnType: !(methodDefinition.returnType?.type === AstNodeType.CustomNodeLiteral ||
methodDefinition.returnType?.type === AstNodeType.ArrayType ||
(methodDefinition.returnType?.type === AstNodeType.PromiseType &&
(methodDefinition.returnType?.generic.type ===
AstNodeType.CustomNodeLiteral ||
methodDefinition.returnType?.generic.type ===
AstNodeType.ArrayType))),
isInt: methodDefinition.returnType?.type === AstNodeType.IntegerLiteral,
methodCaller: methodDefinition.params.length === 0
? `"${classDefinition.name}.${methodDefinition.name}"`
: `"${classDefinition.name}.${methodDefinition.name}", `,
sendParameters: [],
methodDocLines: methodDefinition.docString?.replace(/\n+$/, "").split("\n"),
};
if (!methodView.isPrimitiveReturnType &&
view.imports.find((e) => e.path === "encoding/json") === undefined) {
view.imports.push({
name: "encoding/json",
path: "encoding/json",
named: false,
});
}
const sanitizeGoIdentifier = (input) => {
// Replace characters that are not allowed with underscores
const sanitized = input.replace(/[^a-zA-Z0-9_]/g, "_");
// Ensure the identifier starts with a letter or underscore
if (/^[^a-zA-Z_]/.test(sanitized)) {
return "_" + sanitized;
}
return sanitized;
};
methodView.parameters = methodDefinition.params.map((e) => {
return {
name: (GO_RESERVED_WORDS.includes(e.name)
? "_" + e.name
: sanitizeGoIdentifier(e.name)) +
" " +
(e.optional ? "*" : "") +
this.getParamType(e.paramType),
last: false,
};
});
methodView.sendParameters = methodDefinition.params.map((e) => {
return {
name: GO_RESERVED_WORDS.includes(e.name)
? "_" + e.name
: sanitizeGoIdentifier(e.name),
last: false,
};
});
if (methodView.parameters.length > 0) {
methodView.parameters[methodView.parameters.length - 1].last = true;
methodView.sendParameters[methodView.sendParameters.length - 1].last = true;
}
view.methods.push(methodView);
}
if (!exportClassChecker) {
continue;
}
const rawSdkClassName = `${classDefinition.name}.sdk.go`;
const sdkClassName = rawSdkClassName.charAt(0).toLowerCase() + rawSdkClassName.slice(1);
generateSdkOutput.files.push({
path: sdkClassName,
data: Mustache.render(template, view),
className: classDefinition.name,
});
}
// generate models.go
(typesView.enumTypes.length > 0 || typesView.externalTypes.length > 0) &&
generateSdkOutput.files.push({
className: "Models",
path: "models.go",
data: Mustache.render(modelsView, typesView),
});
// generate remote.js
generateSdkOutput.files.push({
className: "Remote",
path: "remote.go",
data: goSdk,
});
return generateSdkOutput;
}
checkIfTypeExists(name, view) {
return (view.externalTypes.some((e) => e.name === name) ||
view.enumTypes.some((e) => e.name === name));
}
getReturnType(returnType) {
if (!returnType || returnType.type === AstNodeType.VoidLiteral) {
return "";
}
const value = this.getParamType(returnType);
return `${value}`;
}
getParamType(elem) {
if (elem.type === AstNodeType.CustomNodeLiteral) {
return elem.rawValue;
}
else if (elem.type === AstNodeType.StringLiteral) {
return "string";
}
else if (elem.type === AstNodeType.IntegerLiteral) {
return "int";
}
else if (elem.type === AstNodeType.FloatLiteral ||
elem.type === AstNodeType.DoubleLiteral) {
return "float64";
}
else if (elem.type === AstNodeType.BooleanLiteral) {
return "bool";
}
else if (elem.type === AstNodeType.AnyLiteral) {
return "interface{}";
}
else if (elem.type === AstNodeType.ArrayType) {
return `[]${this.getParamType(elem.generic)}`;
}
else if (elem.type === AstNodeType.PromiseType) {
return `${this.getParamType(elem.generic)}`;
}
else if (elem.type === AstNodeType.Enum) {
return elem.name;
}
else if (elem.type === AstNodeType.TypeAlias) {
return elem.name;
}
else if (elem.type === AstNodeType.UnionType) {
return elem.params.map((e) => this.getParamType(e)).join(" | ");
}
else if (elem.type === AstNodeType.TypeLiteral) {
const elemTypeLiteral = elem;
if (elemTypeLiteral.properties.length === 1 &&
elemTypeLiteral.properties[0].type.type === AstNodeType.MapType) {
return this.getParamType(elemTypeLiteral.properties[0].type);
}
return `struct {${elemTypeLiteral.properties
.map((e) => `${e.name} ${e.optional ? "*" : ""}${this.getParamType(e.type)}`)
.join("; ")}}`;
}
else if (elem.type === AstNodeType.MapType) {
return `map[${this.getParamType(elem.genericKey)}]${this.getParamType(elem.genericValue)}`;
}
else if (elem.type === AstNodeType.DateType) {
return "string";
}
else if (elem !== undefined) {
const prop = elem;
return `${prop.optional ? "*" : ""}${this.getParamType(prop.type)}`;
}
return "interface{}";
}
generateExternalType(type) {
if (type.type === AstNodeType.TypeAlias) {
const typeAlias = type;
return `type ${typeAlias.name} = ${this.getParamType(typeAlias.aliasType)};`;
}
else if (type.type === AstNodeType.Enum) {
const enumType = type;
return `enum ${enumType.name} {${enumType.cases
.map((c) => {
if (c.type === AstNodeType.StringLiteral) {
return `${c.name} = "${c.value}"`;
}
else if (c.type === AstNodeType.DoubleLiteral) {
if (c.value !== undefined && c.value !== null) {
return `${c.name} = ${c.value}`;
}
else {
return `${c.name}`;
}
}
})
.join(", ")}}`;
}
else if (type.type === AstNodeType.StructLiteral) {
const typeAlias = type;
return `type ${typeAlias.name} = ${this.getParamType(typeAlias.typeLiteral)};`;
}
return "";
}
isExternalTypeUsedByOtherType(externalType, type) {
if (type.type === AstNodeType.TypeAlias) {
const typeAlias = type;
return this.isExternalTypeUsedByOtherType(externalType, typeAlias.aliasType);
}
else if (type.type === AstNodeType.Enum) {
return false;
}
else if (type.type === AstNodeType.StructLiteral) {
const typeAlias = type;
return this.isExternalTypeUsedByOtherType(externalType, typeAlias.typeLiteral);
}
else if (type.type === AstNodeType.ArrayType) {
return this.isExternalTypeUsedByOtherType(externalType, type.generic);
}
else if (type.type === AstNodeType.PromiseType) {
return this.isExternalTypeUsedByOtherType(externalType, type.generic);
}
else if (type.type === AstNodeType.UnionType) {
return type.params.some((e) => this.isExternalTypeUsedByOtherType(externalType, e));
}
else if (type.type === AstNodeType.TypeLiteral) {
return type.properties.some((e) => this.isExternalTypeUsedByOtherType(externalType, e.type));
}
else if (type.type === AstNodeType.DateType) {
return false;
}
else if (type.type === AstNodeType.CustomNodeLiteral) {
if (type.rawValue === externalType.name) {
return true;
}
return false;
}
else if (type.type === AstNodeType.StringLiteral) {
return false;
}
else if (type.type === AstNodeType.IntegerLiteral ||
type.type === AstNodeType.FloatLiteral ||
type.type === AstNodeType.DoubleLiteral) {
return false;
}
else if (type.type === AstNodeType.BooleanLiteral) {
return false;
}
else if (type.type === AstNodeType.AnyLiteral) {
return false;
}
return false;
}
}
const supportedLanguages = ["go", "golang"];
export default { SdkGenerator, supportedLanguages };