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
94 stars 43 forks source link

Content type for generated client #45

Closed lehejcek closed 1 year ago

lehejcek commented 1 year ago

Swagger Parser does not seem to respect content type for generated client of the following example. I have to add contentType: 'application/x-www-form-urlencoded' and data: _data.toJson() to the generated client or am i missing something?

# https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md
openapi: 3.0.3
info:
  title: Keycloak Token
  version: 1.0.0
servers:
  - url: 'http://localhost:8009/auth'
tags:
  - name: KeycloakToken
paths:
  /realms/{realm}/protocol/openid-connect/token:
    post:
      tags:
        - KeycloakToken
      requestBody:
        required: true
        content:
          # Bug: Content-Type missing after code generation
          # _KeycloakTokenClient
          # contentType: 'application/x-www-form-urlencoded',
          # data: _data.toJson(),
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/KeycloakAuthenticationIdentifier'
      operationId: getKeycloakToken
      parameters:
        - name: realm
          in: path
          required: true
          example: testing
          schema:
            type: string
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/KeycloakOpenIdentityConnect'
components:
  schemas:
    KeycloakOpenIdentityConnect:
      type: object
      properties:
        access_token:
          type: string
        expires_in:
          type: integer
          example: 300
        refresh_expires_in:
          type: integer
          example: 1800
        refresh_token:
          type: string
        token_type:
          type: string
          example: Bearer
        scope:
          type: string
          example: 'profile email'
      required:
        - access_token
        - expires_in
        - refresh_expires_in
        - refresh_token
        - token_type
        - scope
    KeycloakAuthenticationIdentifier:
      type: object
      properties:
        client_id:
          type: string
          description: The ID of the client.
          example: 'testing-client'
        client_secret:
          type: string
          description: The secret of the client.
          example: '**********'
        username:
          type: string
          description: The username of the user.
          example: 'alice'
        password:
          type: string
          description: The password of the user.
          example: '123456'
        grant_type:
          type: string
          description: The grant type.
          example: 'password'
      required:
        - client_id
        - client_secret
        - username
        - password
        - grant_type
Carapacik commented 1 year ago

Can you send here an example of expected code and actual? And dart version with retrofit and dio version

lehejcek commented 1 year ago

I have generated the code for the above OpenAPI definition with the following versions:

Dart SDK 2.19.6
Flutter SDK 3.7.12
Retrofit 4.0.1
Dio 5.1.2

I get this class:

// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'keycloak_token_client.dart';

// **************************************************************************
// RetrofitGenerator
// **************************************************************************

// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers

class _KeycloakTokenClient implements KeycloakTokenClient {
  _KeycloakTokenClient(
    this._dio, {
    this.baseUrl,
  });

  final Dio _dio;

  String? baseUrl;

  @override
  Future<KeycloakOpenIdentityConnect> getKeycloakToken({
    required String realm,
    required KeycloakAuthenticationIdentifier body,
  }) async {
    const _extra = <String, dynamic>{};
    final queryParameters = <String, dynamic>{};
    final _headers = <String, dynamic>{};
    final _data = <String, dynamic>{};
    _data.addAll(body.toJson());
    final _result = await _dio.fetch<Map<String, dynamic>>(
        _setStreamType<KeycloakOpenIdentityConnect>(Options(
      method: 'POST',
      headers: _headers,
      extra: _extra,
    )
            .compose(
              _dio.options,
              '/realms/${realm}/protocol/openid-connect/token',
              queryParameters: queryParameters,
              data: _data,
            )
            .copyWith(baseUrl: baseUrl ?? _dio.options.baseUrl)));
    final value = KeycloakOpenIdentityConnect.fromJson(_result.data!);
    return value;
  }

  RequestOptions _setStreamType<T>(RequestOptions requestOptions) {
    if (T != dynamic &&
        !(requestOptions.responseType == ResponseType.bytes ||
            requestOptions.responseType == ResponseType.stream)) {
      if (T == String) {
        requestOptions.responseType = ResponseType.plain;
      } else {
        requestOptions.responseType = ResponseType.json;
      }
    }
    return requestOptions;
  }
}

But expected that:

// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'keycloak_token_client.dart';

// **************************************************************************
// RetrofitGenerator
// **************************************************************************

// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers

class _KeycloakTokenClient implements KeycloakTokenClient {
  _KeycloakTokenClient(
    this._dio, {
    this.baseUrl,
  });

  final Dio _dio;

  String? baseUrl;

  @override
  Future<KeycloakOpenIdentityConnect> getKeycloakToken({
    required String realm,
    required KeycloakAuthenticationIdentifier body,
  }) async {
    const _extra = <String, dynamic>{};
    final queryParameters = <String, dynamic>{};
    final _headers = <String, dynamic>{};
    final _data = <String, dynamic>{};
    _data.addAll(body.toJson());
    final _result = await _dio.fetch<Map<String, dynamic>>(
        _setStreamType<KeycloakOpenIdentityConnect>(Options(
      method: 'POST',
      headers: _headers,
      contentType: 'application/x-www-form-urlencoded',
      extra: _extra,
    )
            .compose(
              _dio.options,
              '/realms/${realm}/protocol/openid-connect/token',
              queryParameters: queryParameters,
              data: _data,
            )
            .copyWith(baseUrl: baseUrl ?? _dio.options.baseUrl)));
    final value = KeycloakOpenIdentityConnect.fromJson(_result.data!);
    return value;
  }

  RequestOptions _setStreamType<T>(RequestOptions requestOptions) {
    if (T != dynamic &&
        !(requestOptions.responseType == ResponseType.bytes ||
            requestOptions.responseType == ResponseType.stream)) {
      if (T == String) {
        requestOptions.responseType = ResponseType.plain;
      } else {
        requestOptions.responseType = ResponseType.json;
      }
    }
    return requestOptions;
  }
}
Carapacik commented 1 year ago

Try to add @FormUrlEncoded() to your method. I think this is a bug, since this is also a separate case like multipart.

import 'package:dio/dio.dart';
import 'package:retrofit/retrofit.dart';

import '../shared_models/keycloak_authentication_identifier.dart';
import '../shared_models/keycloak_open_identity_connect.dart';

part 'keycloak_token_client.g.dart';

@RestApi()
abstract class KeycloakTokenClient {
  factory KeycloakTokenClient(Dio dio, {String baseUrl}) = _KeycloakTokenClient;

  @FormUrlEncoded() // <---- add this
  @POST('/realms/{realm}/protocol/openid-connect/token')
  Future<KeycloakOpenIdentityConnect> getKeycloakToken({
    @Path('realm') required String realm,
    @Body() required KeycloakAuthenticationIdentifier body,
  });
}
Carapacik commented 1 year ago

Try this

swagger_parser:
  git:
    url: https://github.com/Carapacik/swagger_parser.git
    ref: form-url-enc-fix
    path: swagger_parser/
lehejcek commented 1 year ago

Currently i see Package not available (the pubspec for swagger_parser 1.0.2 from git has version 1.0.1).

I did the checkout manually and changed the version to 1.0.1.

Annotation @FormUrlEncoded() was present after code generation.

And thanks for instantly providing a fix 💙