diff --git a/.github/workflows/models.yml b/.github/workflows/models.yml new file mode 100644 index 0000000..a8c4b17 --- /dev/null +++ b/.github/workflows/models.yml @@ -0,0 +1,29 @@ +name: Node.js Models + +on: [ workflow_dispatch, pull_request ] + +jobs: + generate: + runs-on: ubuntu-latest + name: Generate Models + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Generate models + run: make models + - name: Overwrite current models + run: | + rm -rf src/typings/checkout + cp -r build/model src/typings/checkout + - name: Create Pull Request + uses: peter-evans/create-pull-request@v4 + with: + token: ${{ secrets.ADYEN_AUTOMATION_BOT_ACCESS_TOKEN }} + committer: ${{ secrets.ADYEN_AUTOMATION_BOT_EMAIL }} + author: ${{ secrets.ADYEN_AUTOMATION_BOT_EMAIL }} + base: develop + branch: automation/models + title: Update models + body: OpenAPI spec or templates produced new models. + add-paths: | + src/typings/checkout diff --git a/.gitignore b/.gitignore index d017a6f..6c05a29 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,5 @@ node_modules .viminfo coverage/ .env -lib/ \ No newline at end of file +lib/ +build/ \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..11f07e9 --- /dev/null +++ b/Makefile @@ -0,0 +1,26 @@ +generator=typescript-node +openapi-generator-cli=docker run --rm -v ${PWD}:/local -w /local openapitools/openapi-generator-cli:v5.4.0 + +clean: + rm -rf build + +# Extract templates (copy them for modifications) +.PHONY: templates +templates: + $(openapi-generator-cli) author template -g ${generator} -o build/templates/typescript + +build/spec: + mkdir -p build/spec + +# Fetch spec files (git clone/submodule?) +build/spec/CheckoutService-v69.json: build/spec + wget https://raw.githubusercontent.com/Adyen/adyen-openapi/main/json/CheckoutService-v69.json -O build/spec/CheckoutService-v69.json + sed -i 's/"openapi" : "3.[0-9].[0-9]"/"openapi" : "3.0.0"/' build/spec/CheckoutService-v69.json + +models: build/spec/CheckoutService-v69.json + $(openapi-generator-cli) generate \ + -i build/spec/CheckoutService-v69.json \ + -g ${generator} \ + -t templates/typescript \ + -o build \ + --global-property models,supportingFiles \ No newline at end of file diff --git a/templates/typescript/model.mustache b/templates/typescript/model.mustache index aabb0de..b66ef5a 100644 --- a/templates/typescript/model.mustache +++ b/templates/typescript/model.mustache @@ -7,24 +7,48 @@ import { {{classname}} } from '{{filename}}'; {{#description}} /** -* {{{description}}} +* {{{.}}} */ {{/description}} {{^isEnum}} -export class {{classname}} {{#parent}}extends {{{parent}}} {{/parent}}{ +export class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{ {{#vars}} - - /** {{#description}} - * {{{description}}} -{{/description}} -{{#deprecated}} - * - * @deprecated -{{/deprecated}} + /** + * {{{.}}} */ +{{/description}} '{{name}}'{{^required}}?{{/required}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}; {{/vars}} + + {{#discriminator}} + static discriminator: string | undefined = "{{discriminatorName}}"; + {{/discriminator}} + {{^discriminator}} + static discriminator: string | undefined = undefined; + {{/discriminator}} + + {{^isArray}} + static attributeTypeMap: Array<{name: string, baseName: string, type: string}> = [ + {{#vars}} + { + "name": "{{name}}", + "baseName": "{{baseName}}", + "type": "{{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}" + }{{^-last}}, + {{/-last}} + {{/vars}} + ]; + + static getAttributeTypeMap() { + {{#parent}} + return super.getAttributeTypeMap().concat({{classname}}.attributeTypeMap); + {{/parent}} + {{^parent}} + return {{classname}}.attributeTypeMap; + {{/parent}} + } + {{/isArray}} } {{#hasEnums}} @@ -53,4 +77,4 @@ export enum {{classname}} { } {{/isEnum}} {{/model}} -{{/models}} \ No newline at end of file +{{/models}} diff --git a/templates/typescript/models.mustache b/templates/typescript/models.mustache index 17f9bc1..a248ba0 100644 --- a/templates/typescript/models.mustache +++ b/templates/typescript/models.mustache @@ -1,6 +1,168 @@ {{>licenseInfo}} + {{#models}} {{#model}} export * from '{{{ classFilename }}}'; {{/model}} {{/models}} + +{{! Object serialization (using typing information generated with the model) }} + +{{#models}} +{{#model}} +import { {{classname}} } from '{{{ classFilename }}}'; +{{/model}} +{{/models}} + +/* tslint:disable:no-unused-variable */ +let primitives = [ + "string", + "boolean", + "double", + "integer", + "long", + "float", + "number", + "any" + ]; + +let enumsMap: {[index: string]: any} = { + {{#models}} + {{#model}} + {{#hasEnums}} + {{#vars}} + {{#isEnum}} + {{#isContainer}}"{{classname}}.{{enumName}}": {{classname}}.{{enumName}}{{/isContainer}}{{^isContainer}}"{{datatypeWithEnum}}": {{datatypeWithEnum}}{{/isContainer}}, + {{/isEnum}} + {{/vars}} + {{/hasEnums}} + {{#isEnum}} + "{{classname}}": {{classname}}, + {{/isEnum}} + {{/model}} + {{/models}} +} + +let typeMap: {[index: string]: any} = { + {{#models}} + {{#model}} + {{^isEnum}} + "{{classname}}": {{classname}}, + {{/isEnum}} + {{/model}} + {{/models}} +} + +export class ObjectSerializer { + public static findCorrectType(data: any, expectedType: string) { + if (data == undefined) { + return expectedType; + } else if (primitives.indexOf(expectedType.toLowerCase()) !== -1) { + return expectedType; + } else if (expectedType === "Date") { + return expectedType; + } else { + if (enumsMap[expectedType]) { + return expectedType; + } + + if (!typeMap[expectedType]) { + return expectedType; // w/e we don't know the type + } + + // Check the discriminator + let discriminatorProperty = typeMap[expectedType].discriminator; + if (discriminatorProperty == null) { + return expectedType; // the type does not have a discriminator. use it. + } else { + if (data[discriminatorProperty]) { + var discriminatorType = data[discriminatorProperty]; + if(typeMap[discriminatorType]){ + return discriminatorType; // use the type given in the discriminator + } else { + return expectedType; // discriminator did not map to a type + } + } else { + return expectedType; // discriminator was not present (or an empty string) + } + } + } + } + + public static serialize(data: any, type: string) { + if (data == undefined) { + return data; + } else if (primitives.indexOf(type.toLowerCase()) !== -1) { + return data; + } else if (type.lastIndexOf("Array<", 0) === 0) { // string.startsWith pre es6 + let subType: string = type.replace("Array<", ""); // Array => Type> + subType = subType.substring(0, subType.length - 1); // Type> => Type + let transformedData: any[] = []; + for (let index = 0; index < data.length; index++) { + let datum = data[index]; + transformedData.push(ObjectSerializer.serialize(datum, subType)); + } + return transformedData; + } else if (type === "Date") { + return data.toISOString(); + } else if (type === "SaleToAcquirerData") { + const dataString = JSON.stringify(data); + return Buffer.from(dataString).toString("base64"); + } else { + if (enumsMap[type]) { + return data; + } + if (!typeMap[type]) { // in case we dont know the type + return data; + } + + // Get the actual type of this object + type = this.findCorrectType(data, type); + + // get the map for the correct type. + let attributeTypes = typeMap[type].getAttributeTypeMap(); + let instance: {[index: string]: any} = {}; + for (let index = 0; index < attributeTypes.length; index++) { + let attributeType = attributeTypes[index]; + instance[attributeType.baseName] = ObjectSerializer.serialize(data[attributeType.name], attributeType.type); + } + return instance; + } + } + + public static deserialize(data: any, type: string) { + // polymorphism may change the actual type. + type = ObjectSerializer.findCorrectType(data, type); + if (data == undefined) { + return data; + } else if (primitives.indexOf(type.toLowerCase()) !== -1) { + return data; + } else if (type.lastIndexOf("Array<", 0) === 0) { // string.startsWith pre es6 + let subType: string = type.replace("Array<", ""); // Array => Type> + subType = subType.substring(0, subType.length - 1); // Type> => Type + let transformedData: any[] = []; + for (let index = 0; index < data.length; index++) { + let datum = data[index]; + transformedData.push(ObjectSerializer.deserialize(datum, subType)); + } + return transformedData; + } else if (type === "Date") { + return new Date(data); + } else { + if (enumsMap[type]) {// is Enum + return data; + } + + if (!typeMap[type]) { // dont know the type + return data; + } + let instance = new typeMap[type](); + let attributeTypes = typeMap[type].getAttributeTypeMap(); + for (let index = 0; index < attributeTypes.length; index++) { + let attributeType = attributeTypes[index]; + instance[attributeType.name] = ObjectSerializer.deserialize(data[attributeType.baseName], attributeType.type); + } + return instance; + } + } +}