0
votes

i try to generate a typescript-angular sdk based on the openapi spec from loopback4, but swagger-codegen keeps ignoring my query parameter objects.

In the spec below you see the path GET /plans which can have a filter query param. In the generated class you the the generated method planControllerFind(?:, observe: ...) which has just ?: as the first parameter instead of planControllerFind(filter?: FilterClass, observe: ...)

I've tried some different spec structures and also tried different versions from swagger-codegen (3.0.11, 3.0.16, 3.0.18) but nothing seems to work. The Online Version also generates incomplete code.

Can anyone give me a hint ?

the code was generated with:

swagger-codegen generate -i http://localhost:3000/explorer/openapi.json -l typescript-angular -o app/src/app/core/lb4-sdk -c ./swagger-sdk.options.json

where the /swagger-sdk.options.json-file looks like this:

{
  "npmName": "@ilem0n/ng-budget-api",
  "npmVersion": "0.0.1",
  "snapshot": true,
  "ngVersion": "5.0.0"
}

here is my spec which is generated from loopback4:

openapi: 3.0.0
info:
  title: ngbm-api - NGBM API v1.0
  version: 1.0.0
  contact: {}
paths:
  /plans:
    post:
      x-controller-name: PlanController
      x-operation-name: create
      tags:
        - PlanController
      responses:
        '200':
          description: User model instance
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Plan'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PlanNew'
      operationId: PlanController.create
    get:
      x-controller-name: PlanController
      x-operation-name: find
      tags:
        - PlanController
      responses:
        '200':
          description: Array of User has many Plan
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Plan'
      parameters:
        - name: filter
          in: query
          content:
            application/json:
              schema:
                type: object
                title: Plan.Filter
                properties:
                  offset:
                    type: integer
                    minimum: 0
                  limit:
                    type: integer
                    minimum: 1
                    example: 100
                  skip:
                    type: integer
                    minimum: 0
                  order:
                    type: array
                    items:
                      type: string
                  where:
                    title: Plan.WhereFilter
                    type: object
                    additionalProperties: true
                  fields:
                    title: Plan.Fields
                    type: object
                    properties:
                      id:
                        type: boolean
                      name:
                        type: boolean
                      ownerId:
                        type: boolean
                    additionalProperties: false
                  include:
                    title: Plan.IncludeFilter
                    type: array
                    items:
                      title: Plan.IncludeFilter.Items
                      type: object
                      properties:
                        relation:
                          type: string
                        scope:
                          properties:
                            offset:
                              type: integer
                              minimum: 0
                            limit:
                              type: integer
                              minimum: 1
                              example: 100
                            skip:
                              type: integer
                              minimum: 0
                            order:
                              type: array
                              items:
                                type: string
                            where:
                              type: object
                              additionalProperties: true
                            fields:
                              type: object
                              properties: {}
                              additionalProperties: true
                          additionalProperties: false
                          title: Plan.ScopeFilter
                additionalProperties: false
      operationId: PlanController.find
  /users/login:
    post:
      x-controller-name: UserController
      x-operation-name: login
      tags:
        - UserController
      responses:
        '200':
          description: Auth & User data
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Auth'
      requestBody:
        description: The input of login function
        required: true
        content:
          application/json:
            schema:
              title: Credentials
              type: object
              required:
                - email
                - password
              properties:
                email:
                  type: string
                  format: email
                password:
                  type: string
                  minLength: 8
      operationId: UserController.login
  /users/me:
    get:
      x-controller-name: UserController
      x-operation-name: getUserData
      tags:
        - UserController
      security:
        - name:
            - Bearer
      responses:
        '200':
          description: Current user data
          content:
            application/json:
              schema:
                title: User
                type: object
                required:
                  - id
                  - name
                  - email
                properties:
                  id:
                    type: string
                  name:
                    type: string
                  email:
                    type: string
      operationId: UserController.getUserData
  /users:
    post:
      x-controller-name: UserController
      x-operation-name: create
      tags:
        - UserController
      responses:
        '200':
          description: User model instance
          content:
            application/json:
              schema:
                title: User
                type: object
                required:
                  - id
                  - name
                  - email
                properties:
                  id:
                    type: string
                  name:
                    type: string
                  email:
                    type: string
      requestBody:
        description: The input of register function
        required: true
        content:
          application/json:
            schema:
              title: NewUser
              type: object
              required:
                - name
                - email
                - password
              properties:
                name:
                  type: string
                  minLength: 3
                email:
                  type: string
                  format: email
                password:
                  type: string
                  minLength: 8
      operationId: UserController.create
servers:
  - url: /
components:
  schemas:
    Plan:
      title: Plan
      description: '(Schema options: { title: ''Plan'', exclude: [ ''ownerId'' ] })'
      properties:
        id:
          type: string
        name:
          type: string
      required:
        - name
      additionalProperties: false
    PlanNew:
      title: PlanNew
      description: '(Schema options: { title: ''PlanNew'', exclude: [ ''id'', ''ownerId'' ] })'
      properties:
        name:
          type: string
      required:
        - name
      additionalProperties: false
    Auth:
      title: Auth
      properties:
        token:
          type: string
        expiresAt:
          type: number
        user:
          type: object
      required:
        - token
        - expiresAt
        - user
      additionalProperties: false

from this spec ive got the following class for the planController:

/**
 * ngbm-api - NGBM API v1.0
 * No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen)
 *
 * OpenAPI spec version: 1.0.0
 * 
 *
 * NOTE: This class is auto generated by the swagger code generator program.
 * https://github.com/swagger-api/swagger-codegen.git
 * Do not edit the class manually.
 *//* tslint:disable:no-unused-variable member-ordering */

import { Inject, Injectable, Optional }                      from '@angular/core';
import { HttpClient, HttpHeaders, HttpParams,
         HttpResponse, HttpEvent }                           from '@angular/common/http';
import { CustomHttpUrlEncodingCodec }                        from '../encoder';

import { Observable }                                        from 'rxjs/Observable';

import { Plan } from '../model/plan';
import { PlanNew } from '../model/planNew';

import { BASE_PATH, COLLECTION_FORMATS }                     from '../variables';
import { Configuration }                                     from '../configuration';


@Injectable()
export class PlanControllerService {

    protected basePath = 'http://localhost:3000/';
    public defaultHeaders = new HttpHeaders();
    public configuration = new Configuration();

    constructor(protected httpClient: HttpClient, @Optional()@Inject(BASE_PATH) basePath: string, @Optional() configuration: Configuration) {
        if (basePath) {
            this.basePath = basePath;
        }
        if (configuration) {
            this.configuration = configuration;
            this.basePath = basePath || configuration.basePath || this.basePath;
        }
    }

    /**
     * @param consumes string[] mime-types
     * @return true: consumes contains 'multipart/form-data', false: otherwise
     */
    private canConsumeForm(consumes: string[]): boolean {
        const form = 'multipart/form-data';
        for (const consume of consumes) {
            if (form === consume) {
                return true;
            }
        }
        return false;
    }


    /**
     * 
     * 
     * @param body 
     * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
     * @param reportProgress flag to report request and response progress.
     */
    public planControllerCreate(body?: PlanNew, observe?: 'body', reportProgress?: boolean): Observable<Plan>;
    public planControllerCreate(body?: PlanNew, observe?: 'response', reportProgress?: boolean): Observable<HttpResponse<Plan>>;
    public planControllerCreate(body?: PlanNew, observe?: 'events', reportProgress?: boolean): Observable<HttpEvent<Plan>>;
    public planControllerCreate(body?: PlanNew, observe: any = 'body', reportProgress: boolean = false ): Observable<any> {


        let headers = this.defaultHeaders;

        // to determine the Accept header
        let httpHeaderAccepts: string[] = [
            'application/json'
        ];
        const httpHeaderAcceptSelected: string | undefined = this.configuration.selectHeaderAccept(httpHeaderAccepts);
        if (httpHeaderAcceptSelected != undefined) {
            headers = headers.set('Accept', httpHeaderAcceptSelected);
        }

        // to determine the Content-Type header
        const consumes: string[] = [
            'application/json'
        ];
        const httpContentTypeSelected: string | undefined = this.configuration.selectHeaderContentType(consumes);
        if (httpContentTypeSelected != undefined) {
            headers = headers.set('Content-Type', httpContentTypeSelected);
        }

        return this.httpClient.request<Plan>('post',`${this.basePath}/plans`,
            {
                body: body,
                withCredentials: this.configuration.withCredentials,
                headers: headers,
                observe: observe,
                reportProgress: reportProgress
            }
        );
    }

    /**
     * 
     * 
     * @param  
     * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
     * @param reportProgress flag to report request and response progress.
     */
    public planControllerFind(?: , observe?: 'body', reportProgress?: boolean): Observable<Array<Plan>>;
    public planControllerFind(?: , observe?: 'response', reportProgress?: boolean): Observable<HttpResponse<Array<Plan>>>;
    public planControllerFind(?: , observe?: 'events', reportProgress?: boolean): Observable<HttpEvent<Array<Plan>>>;
    public planControllerFind(?: , observe: any = 'body', reportProgress: boolean = false ): Observable<any> {


        let queryParameters = new HttpParams({encoder: new CustomHttpUrlEncodingCodec()});
        if ( !== undefined &&  !== null) {
            queryParameters = queryParameters.set('filter', <any>);
        }

        let headers = this.defaultHeaders;

        // to determine the Accept header
        let httpHeaderAccepts: string[] = [
            'application/json'
        ];
        const httpHeaderAcceptSelected: string | undefined = this.configuration.selectHeaderAccept(httpHeaderAccepts);
        if (httpHeaderAcceptSelected != undefined) {
            headers = headers.set('Accept', httpHeaderAcceptSelected);
        }

        // to determine the Content-Type header
        const consumes: string[] = [
        ];

        return this.httpClient.request<Array<Plan>>('get',`${this.basePath}/plans`,
            {
                params: queryParameters,
                withCredentials: this.configuration.withCredentials,
                headers: headers,
                observe: observe,
                reportProgress: reportProgress
            }
        );
    }

}

1
1) Your query parameter is defined using content.application-json, which means the parameter is serialized as a JSON string i.e. /plans?filter={"offset":0,"limit":0, ...}. Is this what you actually need? Complex nested objects should ideally be sent in the POST request body, not in the GET query string. 2) Most likely, Codegen doesn't support parameters with content because they are rarely used. Open an issue at github.com/swagger-api/swagger-codegen/issues.Helen
The query parameter should be an URI encoded string like /plans/filter=%7B%22offset%22%3A0%2C%22limit%22%3A0%7D. So the consent would be to make a post request for each request which defines some query parameters ? I had a configuration which works well, than had to update some loopback packages the deployment break in this point. Now I want to figure out why it breaks. But im not so deep into openapi to figure this out.Peter C. Glade

1 Answers

1
votes

I've raised an issue at swagger-api/swagger-codegen https://github.com/swagger-api/swagger-codegen/issues/10158 but couldn't find a solution yet.

I've switched to https://github.com/OpenAPITools/openapi-generator which resolve the problem for me. Actually no problems with that.