RicoSuter / NSwag

The Swagger/OpenAPI toolchain for .NET, ASP.NET Core and TypeScript.
http://NSwag.org
MIT License
6.74k stars 1.29k forks source link

Trouble with typescript generator for x-www-form-urlencoded endpoint #1338

Open tsmith-sdm opened 6 years ago

tsmith-sdm commented 6 years ago

For an OAuth compliant endpoint using formData parameters the data is expected to be in application/x-www-form-urlencoded format which looks like this:

grant_type=password&client_id=123456&username=test&password=test

However the typescript generator is using JSON.stringify which generates something like this:

{'grand_type':'password','client_id':'123456','username':'test','password':'test'}

https://github.com/RSuter/NSwag/blob/master/src/NSwag.CodeGeneration.TypeScript/Templates/Client.RequestBody.liquid#L26

How can I go about replacing the call to JSON.stringify() with something else? Can I provide a custom Client.RequestBody.liquid template to nswag?

I need to replace the JSON.stringify() with something like this:

    token(token: OAuth2Request): Observable<SimpleAccessTokenResult> {
        let url_ = this.baseUrl + "/api/public/v1/oauth2/token";

        //const content_ = JSON.stringify(token);

        const content_ = Object.keys(token).map((key) => {
            return encodeURIComponent(key) + '=' + encodeURIComponent(token[key]);
        }).join('&');        

        let options_ : any = {
            body: content_,
            observe: "response",
            responseType: "blob",
            headers: new HttpHeaders({
                "Content-Type": "application/x-www-form-urlencoded", 
                "Accept": "application/json"
            })
        };
RicoSuter commented 6 years ago

You can override a template: https://github.com/RSuter/NSwag/wiki/Templates

But this should only be a temporary solution because the models maychange and you wont get fixes in the copied template.

We should try to support this case out-of-the-box. Can you post a swagger spec to reproduce this?

tsmith-sdm commented 6 years ago

Here is an abbreviated version containing the oauth2 endpoint:

{
    "swagger": "2.0",
    "info": {
      "version": "v1",
      "title": "Rapidity.Web"
    },
    "host": "api.stratasysdirect.com",
    "basePath": "/Rapidity",
    "schemes": [
      "https"
    ],
    "paths": {
      "/api/public/v1/oauth2/token": {
        "post": {
          "tags": [
            "OAuth2"
          ],
          "summary": "Create or refresh an access_token for a registered user using oauth2 password flow",
          "description": "Generates an access_token for registered user's only",
          "operationId": "OAuth2_token",
          "consumes": [
            "application/x-www-form-urlencoded"
          ],
          "produces": [
            "application/json",
            "text/json"
          ],
          "parameters": [
            {
              "name": "client_id",
              "in": "formData",
              "description": "API Key",
              "required": true,
              "type": "string"
            },
            {
              "name": "grant_type",
              "in": "formData",
              "description": "Supported grant types: [password, refresh_token]",
              "required": true,
              "type": "string",
              "default": "password"
            },
            {
              "name": "username",
              "in": "formData",
              "description": "user's email address",
              "required": false,
              "type": "string"
            },
            {
              "name": "password",
              "in": "formData",
              "description": "user's password",
              "required": false,
              "type": "string"
            },
            {
              "name": "refresh_token",
              "in": "formData",
              "description": "refresh token",
              "required": false,
              "type": "string"
            }
          ],
          "responses": {
            "200": {
              "description": "OK",
              "schema": {
                "$ref": "#/definitions/AccessTokenResult"
              }
            },
            "400": {
              "description": "BadRequest"
            },
            "500": {
              "description": "InternalServerError"
            }
          }
        }
      },
    "definitions": {
      "AccessTokenResult": {
        "type": "object",
        "properties": {
          "access_token": {
            "type": "string"
          },
          "refresh_token": {
            "type": "string"
          },
          "token_type": {
            "type": "string"
          },
          "expires_in": {
            "format": "int64",
            "type": "integer"
          }
        }
      }
    },
    "securityDefinitions": {
      "api_key": {
        "type": "apiKey",
        "description": "API Key Authentication",
        "name": "X-SDM-ApiKey",
        "in": "header"
      },
      "oauth2": {
        "type": "oauth2",
        "description": "OAuth2 Password Grant",
        "flow": "password",
        "tokenUrl": "/api/public/v1/oauth2/token",
        "scopes": {

        }
      }
    }
  }
tsmith-sdm commented 6 years ago

Adding a templateDirectory setting the my .nswag config along with a custom Client.RequestBody.liquid did the trick for now.

service.config.nswag

{
  "swaggerGenerator": {
  ...
  },
  "codeGenerators": {
    "swaggerToTypeScriptClient": {
    ...
      "templateDirectory": "."     
    }
  }
}

Client.RequestBody.liquid

{% if operation.Consumes == "application/x-www-form-urlencoded" -%}
content_ = this.urlEncodeFormData(content_);
{%         endif -%}
RicoSuter commented 6 years ago

Can you also post this.urlEncodeFormData?

RicoSuter commented 6 years ago

My output is completely different (multiple params instead of single object) and looks good:

image