Carapacik / swagger_parser

Dart package that takes an OpenApi definition file and generates REST clients based on retrofit and data classes for your project.
https://pub.dev/packages/swagger_parser
MIT License
97 stars 45 forks source link

Is it possible to generate generic classes? #156

Closed dlabs-matic-leva closed 8 months ago

dlabs-matic-leva commented 8 months ago

I'm using NestJs to generate OpenAPI specs and NestJS recommends using schema+references to express generic responses. Below is example OpenAPI specs that is generated following this advice:

openapi: 3.0.0
paths:
  /api/clients:
    get:
      operationId: clients.list
      summary: ''
      parameters: []
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaginatedResponse'
      tags:
        - Clients
      security:
        - oauth2: []
info:
  title: ...
  description: ...
  version: '1.0'
  contact: {}
tags: []
servers: []
components:
  securitySchemes:
    oauth2:
      type: oauth2
      flows:
        implicit:
          authorizationUrl: ...
          scopes:
            offline_access: offline_access
            openid: openid
            profile: profile
            email: email
  schemas:
    Client:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        surname:
          type: string
      required:
        - id
        - name
        - surname
    PaginatedResponse:
      type: object
      properties:
        page:
          type: number
        pageSize:
          type: number
        results:
          type: array
          items:
            $ref: '#/components/schemas/Client'
      required:
        - page
        - pageSize
        - results

This is Dart code that was generated from specs:

@RestApi()
abstract class ClientsClient {
  factory ClientsClient(Dio dio, {String? baseUrl}) = _ClientsClient;
  @GET('/api/clients')
  Future<Object> clientsList();
}
[...]
@JsonSerializable()
class PaginatedResponse {
  const PaginatedResponse({
    required this.page,
    required this.pageSize,
  });

  factory PaginatedResponse.fromJson(Map<String, Object?> json) => _$PaginatedResponseFromJson(json);

  final num page;
  final num pageSize;

  Map<String, Object?> toJson() => _$PaginatedResponseToJson(this);
}

Observe that PaginatedResponse.results is missing and return type of clientsList() is Future<Object>. I would expect to see code similar to this:

@RestApi()
abstract class ClientsClient {
  factory ClientsClient(Dio dio, {String? baseUrl}) = _ClientsClient;
  @GET('/api/clients')
  Future<PaginatedResponse<Client>> clientsList();
}
[...]
@JsonSerializable()
class PaginatedResponse<Data> {
  const PaginatedResponse({
    required this.page,
    required this.pageSize,
    required this.results,
  });

  factory PaginatedResponse.fromJson(Map<String, Object?> json) =>
      _$PaginatedResponseFromJson(json);

  final num page;
  final num pageSize;
  final Data results;

  Map<String, Object?> toJson() => _$PaginatedResponseToJson(this);
}

Is this possible to achieve?

dlabs-matic-leva commented 8 months ago

Current workaround I'm doing is to create separate class for each paginated endpoint (PaginatedResponseForClients, PaginatedResponseForAccounts) but it would be nice to use generics.

StarProxima commented 8 months ago

OpenAPI does not directly support generics for classes. The implementation would be quite problematic and could not be universal for every specification.

There is not enough data in your NestJS-generated specification to realize that PaginatedResponse should be a generic class.

Usually, for such purposes, the spec automatically creates separate classes for each type as you described. This is what FastAPI does, for example.