OpenAPITools / openapi-generator

OpenAPI Generator allows generation of API client libraries (SDK generation), server stubs, documentation and configuration automatically given an OpenAPI Spec (v2, v3)
https://openapi-generator.tech
Apache License 2.0
21.86k stars 6.59k forks source link

[TypeScript/JavaScript] Emit all necessary info as metadata object; defer as much as possible to a runtime library #1233

Open cspotcode opened 6 years ago

cspotcode commented 6 years ago
Description

This is related to discussion in #802 about re-thinking the various TypeScript generators as a single generator that is far more configurable.

Philosophically, I think the new generator should generate as little code as possible via the generator, delegating as much behavior as possible to a runtime that is driven by a "spec metadata" structure. This enables a better developer experience for us creating the generator and makes it easier for plugins to be written that customize the generated API's behavior.

Here's an example:

https://github.com/OpenAPITools/openapi-generator/blob/master/modules/openapi-generator/src/main/resources/typescript-fetch/api.mustache#L165-L177

if ({{paramName}} !== undefined) {
                {{#isDateTime}}
                localVarQueryParameter['{{baseName}}'] = ({{paramName}} as any).toISOString();
                {{/isDateTime}}
                {{^isDateTime}}
                {{#isDate}}
                localVarQueryParameter['{{baseName}}'] = ({{paramName}} as any).toISOString();
                {{/isDate}}
                {{^isDate}}
                localVarQueryParameter['{{baseName}}'] = {{paramName}};
                {{/isDate}}
                {{/isDateTime}}
            }

Instead, we should write this as plain TypeScript:

// Assume that generated code has created and passed us a `params` and `paramSpec` object
const {isDateTime, baseName, isDate} = paramSpec;
const paramValue = params[paramName];
if (paramValue !== undefined) {
                if(isDateTime) {
                localVarQueryParameter[baseName] = paramValue.toISOString();
                } else if(isDate) {
                    localVarQueryParameter[baseName] = paramValue.toISOString();
                } else {
                    localVarQueryParameter[baseName] = paramValue;
                }
            }

The mustache template's job is now much easier. It can generate something like this:

function fooOperation(param1: string, param2: Date, param3: boolean): Promise<Whatever> {
    // Delegate to our runtime, passing it a reference to operation metadata
    return invokeOperation({param1, param2, param3}, specMeta.operations.foo); // specMeta is an object containing all the metadata our runtime needs to perform API operations
}

...and then elsewhere, generate the specMeta object: (this code is not perfect but hopefully explains the idea)

export const specMeta: SpecMeta = {
    operations: {
    {{#operations}}
        {{#operation}}
        {{operationName}}: {
            parameters: [
            {{#parameters}}
            {
                paramName: '{{paramName}}',
                isDateTime: {{isDateTime}},
                isDate: {{isDate}},
                baseName: '{{baseName}}',
            },
            {{/parameters}}
            ]
        },
        {{/operation}}
    {{/operations}}
    }
}

Things the generator should generate:

A few things for which the generator should not generate code:

All the necessary information should be emitted into a metadata file that is read by runtime libraries to perform the necessary tasks.

In non-JS languages, this might require messy reflection. But in JS, reflection is the norm, and its performance is not unreasonably slow compared to JS code written without reflection. (especially when we consider we're creating an API client that sends HTTP requests, not a compression algorithm)

Writing TS code in mustache templates is inconvenient because we lose the benefits of syntax highlighting and code completion. We can instead write an SDK runtime driven by the metadata emitted by openapi-generator. The metadata object can be strongly typed, so we shouldn't lose safety.

This also makes it a bit more obvious that the runtime can delegate lots of heavy lifting to third-party libraries. For example, schema validation can be implemented as a plugin that uses a third-party validator.

The generator will still be generating function, class, and method signatures for the entire API spec, which is very important to ensure a good developer experience for anyone consuming the generated API client.

EDIT: added an example of mustache template for the spec metadata object.

fuzzzerd commented 5 years ago

This makes a lot of sense to me. I'd like to see something like this implemented.