Skip to content

[BUG][DART-DIO] Generation of response with content type 'application/octet-stream' returns 'MultipartFile' instead of 'Stream<Unit8List>' #20682

Open
@joelbrostrom

Description

@joelbrostrom

Bug Report Checklist

  • Have you provided a full/minimal spec to reproduce the issue?
  • Have you validated the input using an OpenAPI validator (example)?
  • Have you tested with the latest master to confirm the issue still exists?
  • Have you searched for related issues/PRs?
  • What's the actual output vs expected output?
  • [Optional] Sponsorship to speed up the bug fix or feature request (example)
Description

When generating a Get request with content-type application/octet-stream or text/event-stream the output returns a MultipartFile instead of the expected Stream<Uint8List>.
The generated code then calls the deserialize which fails with the exception Cannot deserialize.
I expect the generated method to return Stream<Uint8List>.

openapi-generator version

openapi-generator-cli 7.12.0-SNAPSHOT

OpenAPI declaration file content or url

ymal schema:

/api/open-ai/session/{id}:
  get:
    operationId: open_ai_session_retrieve
    parameters:
    - in: path
      name: id
      schema:
        type: string
        format: uuid
      required: true
    - in: path
      name: message_id
      schema:
        type: string
        format: uuid
      required: true
    tags:
    - open-ai
    security:
    - cookieAuth: []
    responses:
      '200':
        content:
          application/octet-stream:
            schema:
              type: string
              format: binary
        description: ''

Output:

/// openAiChatSessionMessageRetrieve
  ///
  ///
  /// Parameters:
  /// * [id]
  /// * [messageId]
  /// * [cancelToken] - A [CancelToken] that can be used to cancel the operation
  /// * [headers] - Can be used to add additional headers to the request
  /// * [extras] - Can be used to add flags to the request
  /// * [validateStatus] - A [ValidateStatus] callback that can be used to determine request success based on the HTTP status of the response
  /// * [onSendProgress] - A [ProgressCallback] that can be used to get the send progress
  /// * [onReceiveProgress] - A [ProgressCallback] that can be used to get the receive progress
  ///
  /// Returns a [Future] containing a [Response] with a [MultipartFile] as data
  /// Throws [DioException] if API call or serialization fails
  Future<Response<MultipartFile>> openAiChatSessionMessageRetrieve({
    required String id,
    CancelToken? cancelToken,
    Map<String, dynamic>? headers,
    Map<String, dynamic>? extra,
    ValidateStatus? validateStatus,
    ProgressCallback? onSendProgress,
    ProgressCallback? onReceiveProgress,
  }) async {
    final _path = r'/api/open-ai/session/{id}/'
        .replaceAll('{' r'id' '}', id.toString())
    final _options = Options(
      method: r'GET',
      responseType: ResponseType.bytes,
      headers: <String, dynamic>{
        ...?headers,
      },
      extra: <String, dynamic>{
        ...?extra,
      },
      validateStatus: validateStatus,
    );

    final _response = await _dio.request<Object>(
      _path,
      options: _options,
      cancelToken: cancelToken,
      onSendProgress: onSendProgress,
      onReceiveProgress: onReceiveProgress,
    );

    MultipartFile? _responseData;

    try {
      final rawData = _response.data;
      _responseData = rawData == null
          ? null
          : deserialize<MultipartFile, MultipartFile>(rawData, 'MultipartFile',
              growable: true); // CRASH ---> deserialize cannot create MultipartFile
    } catch (error, stackTrace) {
      throw DioException(
        requestOptions: _response.requestOptions,
        response: _response,
        type: DioExceptionType.unknown,
        error: error,
        stackTrace: stackTrace,
      );
    }

    return Response<MultipartFile>(
      data: _responseData,
      headers: _response.headers,
      isRedirect: _response.isRedirect,
      requestOptions: _response.requestOptions,
      redirects: _response.redirects,
      statusCode: _response.statusCode,
      statusMessage: _response.statusMessage,
      extra: _response.extra,
    );
  }

deserializer:

final _regList = RegExp(r'^List<(.*)>$');
final _regSet = RegExp(r'^Set<(.*)>$');
final _regMap = RegExp(r'^Map<String,(.*)>$');

ReturnType deserialize<ReturnType, BaseType>(dynamic value, String targetType,
    {bool growable = true}) {
  switch (targetType) {
    case 'String':
      return '$value' as ReturnType;
    case 'int':
      return (value is int ? value : int.parse('$value')) as ReturnType;
    case 'bool':
      if (value is bool) {
        return value as ReturnType;
      }
      final valueString = '$value'.toLowerCase();
      return (valueString == 'true' || valueString == '1') as ReturnType;
    case 'double':
      return (value is double ? value : double.parse('$value')) as ReturnType;
    // Rest of models, but no MultipartFile
    default:
      RegExpMatch? match;

      if (value is List && (match = _regList.firstMatch(targetType)) != null) {
        targetType = match![1]!; // ignore: parameter_assignments
        return value
            .map<BaseType>((dynamic v) => deserialize<BaseType, BaseType>(
                v, targetType,
                growable: growable))
            .toList(growable: growable) as ReturnType;
      }
      if (value is Set && (match = _regSet.firstMatch(targetType)) != null) {
        targetType = match![1]!; // ignore: parameter_assignments
        return value
            .map<BaseType>((dynamic v) => deserialize<BaseType, BaseType>(
                v, targetType,
                growable: growable))
            .toSet() as ReturnType;
      }
      if (value is Map && (match = _regMap.firstMatch(targetType)) != null) {
        targetType = match![1]!.trim(); // ignore: parameter_assignments
        return Map<String, BaseType>.fromIterables(
          value.keys as Iterable<String>,
          value.values.map((dynamic v) => deserialize<BaseType, BaseType>(
              v, targetType,
              growable: growable)),
        ) as ReturnType;
      }
      break;
  }
  throw Exception('Cannot deserialize');
}
Generation Details

open-generator-config.ymal

# Config options for the dart-dio generator
pubName: app_api
pubVersion: 0.0.1
pubDescription: "App API"
dateLibrary: core
serializationLibrary: json_serializable
equalityCheckMethod: equatable
Steps to reproduce

run

openapi-generator generate -i openapi_docs.yaml -g dart-dio -c open-generator-config.yaml --enable-post-process-file

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions