Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG][Dart] Generator creates a model class name that conflicts with a core lib class name (Type) #9029

Closed
4 of 6 tasks
robrbecker opened this issue Mar 21, 2021 · 14 comments · Fixed by #9167
Closed
4 of 6 tasks

Comments

@robrbecker
Copy link

robrbecker commented Mar 21, 2021

Bug Report Checklist

  • Have you provided a full/minimal spec to reproduce the issue? Yes:
  • 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 Dart code, an OAS is using a reserved(ish) word (Type) which the generator defines a class for which then conflicts and causes errors. (It's not technically a reserved word of the language, it's a class in the core library. Something that's always available and you can't import it with a namespace)

image

openapi-generator version

5.1.0 release
on both dart-dio and dart-dio-next

OpenAPI declaration file content or url

https://cdn-prod.wdesk.com/endpoints-admin/1.342.0/openapi/platform-v1.yaml

Problem arises from line 813

Generation Details

I used the jar release for 5.1.0 with this command:
java -jar openapi-generator-cli.jar generate -i https://cdn-prod.wdesk.com/endpoints-admin/1.342.0/openapi/platform-v1.yaml -g dart-dio -o ./ --additional-properties="pubDescription=Workiva Platform API (Dart),pubName=wk,pubVersion=1.0.0"

Steps to reproduce

Generate Dart code with the above command.
Have Dart installed (2.12.2)
Then run
pub get
pub run build_runner build --delete-conflicting-outputs -o build
(then remove the build directory .. you only need to run a build to do built_value code generation
Then either open in an IDE with the Dart plugin (VS Code, intelliJ) or just run dartanalyzer . which will show the errors.

Related issues/PRs
Suggest a fix

I'm not actually sure what a good fix would be. I might actually prefer if generation failed when creating the code and suggested a way to work around it, possibly by using the [--reserved-words-mappings <reserved word mappings>...] option ?

@robrbecker
Copy link
Author

So far, the only workaround I've found is to use
--model-name-suffix or --model-name-prefix
which changes the names of ALL model files, which is suboptimal.
I'd really prefer to just be able to remap the name of a single model. In this case something like:

--model-mapping="Type=RecordType"

@robrbecker robrbecker changed the title [BUG] Description [BUG][Dart] Generator creates a model class name that conflicts with a core lib class name Mar 21, 2021
@robrbecker robrbecker changed the title [BUG][Dart] Generator creates a model class name that conflicts with a core lib class name [BUG][Dart] Generator creates a model class name that conflicts with a core lib class name (Type) Mar 21, 2021
@kuhnroyal
Copy link
Contributor

I could add this to the reserved words, it is not optimal but we also did this for Request, Response and so on.

@kuhnroyal
Copy link
Contributor

Actually we have this place where it should be added:

        // DataTypes of the above values which are automatically imported.
        // They are also not allowed to be model names.
        defaultIncludes = Sets.newHashSet(
                "String",
                "bool",
                "int",
                "num",
                "double",
                "dynamic",
                "List",
                "Set",
                "Map",
                "DateTime",
                "Object",
                "MultipartFile"
        );

@kuhnroyal
Copy link
Contributor

Can you try running this with --type-mappings=Type=RecordType in the mean time.

@kuhnroyal
Copy link
Contributor

I created a PR, would be great if you could test this with the --type-mappings=Type=RecordType parameter.

@robrbecker
Copy link
Author

Oops I missed the earlier notification. Yes, let me give it a try.

@robrbecker
Copy link
Author

@kuhnroyal I built and verified that #9167 works great to map Type to RecordType with --type-mappings=Type=RecordType !!
Works great for my use case, thanks!

@lam-nv
Copy link

lam-nv commented Apr 12, 2021

Hi @kuhnroyal

I have an issue with imports in *_api.dart (generated by .templates/dart-dio/api.mustache), see details below:

.templates/dart-dio/api.mustache

{{>header}}
import 'dart:async';
import 'package:dio/dio.dart';
import 'package:built_value/serializer.dart';

{{#operations}}
{{#imports}}import '{{.}}';
{{/imports}}

class {{classname}} {

  final Dio _dio;

  final Serializers _serializers;

  const {{classname}}(this._dio, this._serializers);

  {{#operation}}
  /// {{{summary}}}
  ///
  /// {{{notes}}}
  Future<Response<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}}>> {{nickname}}({{^hasRequiredParams}}{ {{/hasRequiredParams}}{{#requiredParams}}
    {{{dataType}}} {{paramName}},{{#-last}} { {{/-last}}{{/requiredParams}}{{#optionalParams}}
    {{{dataType}}} {{paramName}},{{/optionalParams}}
    CancelToken cancelToken,
    Map<String, dynamic> headers,
    Map<String, dynamic> extra,
    ValidateStatus validateStatus,
    ProgressCallback onSendProgress,
    ProgressCallback onReceiveProgress,
  }) async {
    final _request = RequestOptions(
      path: r'{{{path}}}'{{#pathParams}}.replaceAll('{' r'{{{baseName}}}' '}', {{{paramName}}}.toString()){{/pathParams}},
      method: '{{#lambda.uppercase}}{{httpMethod}}{{/lambda.uppercase}}',
      {{#isResponseFile}}
      responseType: ResponseType.bytes,
      {{/isResponseFile}}
      headers: <String, dynamic>{
        {{#httpUserAgent}}
        r'User-Agent': r'{{{.}}}',
        {{/httpUserAgent}}
        {{#headerParams}}
        {{^required}}{{^nullable}}if ({{{paramName}}} != null) {{/nullable}}{{/required}}r'{{baseName}}': {{paramName}},
        {{/headerParams}}
        ...?headers,
      },
      {{#hasQueryParams}}
      queryParameters: <String, dynamic>{
        {{#queryParams}}
        {{^required}}{{^nullable}}if ({{{paramName}}} != null) {{/nullable}}{{/required}}r'{{baseName}}': {{paramName}},
        {{/queryParams}}
      },
      {{/hasQueryParams}}
      extra: <String, dynamic>{
        'secure': <Map<String, String>>[{{^hasAuthMethods}}],{{/hasAuthMethods}}{{#hasAuthMethods}}
          {{#authMethods}}{
            'type': '{{type}}',
            'name': '{{name}}',{{#isApiKey}}
            'keyName': '{{keyParamName}}',
            'where': '{{#isKeyInQuery}}query{{/isKeyInQuery}}{{#isKeyInHeader}}header{{/isKeyInHeader}}',{{/isApiKey}}
          },{{/authMethods}}
        ],{{/hasAuthMethods}}
        ...?extra,
      },
      validateStatus: validateStatus,
      contentType: [{{^hasConsumes}}
        'application/json',{{/hasConsumes}}{{#hasConsumes}}{{#consumes}}
        '{{{mediaType}}}',{{/consumes}}{{/hasConsumes}}
      ].first,
      cancelToken: cancelToken,
      onSendProgress: onSendProgress,
      onReceiveProgress: onReceiveProgress,
    );

    dynamic _bodyData;
    {{#hasFormParams}}

    _bodyData = {{#isMultipart}}FormData.fromMap({{/isMultipart}}<String, dynamic>{
      {{#formParams}}
      {{^required}}{{^nullable}}if ({{{paramName}}} != null) {{/nullable}}{{/required}}r'{{{baseName}}}': {{#isFile}}MultipartFile.fromBytes({{{paramName}}}, filename: r'{{{baseName}}}'){{/isFile}}{{^isFile}}encodeFormParameter(_serializers, {{{paramName}}}, const FullType({{^isContainer}}{{{baseType}}}{{/isContainer}}{{#isContainer}}Built{{#isMap}}Map{{/isMap}}{{#isArray}}{{#uniqueItems}}Set{{/uniqueItems}}{{^uniqueItems}}List{{/uniqueItems}}{{/isArray}}, [{{#isMap}}FullType(String), {{/isMap}}FullType({{{baseType}}})]{{/isContainer}})){{/isFile}},
      {{/formParams}}
    }{{#isMultipart}}){{/isMultipart}};
    {{/hasFormParams}}
    {{#bodyParam}}

    {{#isPrimitiveType}}
    _bodyData = {{paramName}};
    {{/isPrimitiveType}}
    {{^isPrimitiveType}}
    {{#isContainer}}
    const _type = FullType(Built{{#isMap}}Map{{/isMap}}{{#isArray}}{{#uniqueItems}}Set{{/uniqueItems}}{{^uniqueItems}}List{{/uniqueItems}}{{/isArray}}, [{{#isMap}}FullType(String), {{/isMap}}FullType({{{baseType}}})]);
    _bodyData = _serializers.serialize({{paramName}}, specifiedType: _type);
    {{/isContainer}}
    {{^isContainer}}
    const _type = FullType({{{baseType}}});
    _bodyData = _serializers.serialize({{paramName}}, specifiedType: _type);
    {{/isContainer}}
    {{/isPrimitiveType}}
    {{/bodyParam}}

    final _response = await _dio.request<dynamic>(
      _request.path,
      data: _bodyData,
      options: _request,
    );
    {{#returnType}}

    {{#isResponseFile}}
    final {{{returnType}}} _responseData = _response.data as {{{returnType}}};
    {{/isResponseFile}}
    {{^isResponseFile}}
    {{#returnSimpleType}}
    {{#returnTypeIsPrimitive}}
    final {{{returnType}}} _responseData = _response.data as {{{returnType}}};
    {{/returnTypeIsPrimitive}}
    {{^returnTypeIsPrimitive}}
    const _responseType = FullType({{{returnType}}});
    final _responseData = _serializers.deserialize(
      _response.data,
      specifiedType: _responseType,
    ) as {{{returnType}}};
    {{/returnTypeIsPrimitive}}
    {{/returnSimpleType}}
    {{^returnSimpleType}}
    const _responseType = FullType(Built{{#isArray}}{{#uniqueItems}}Set{{/uniqueItems}}{{^uniqueItems}}List{{/uniqueItems}}{{/isArray}}{{#isMap}}Map{{/isMap}}, [{{#isMap}}FullType(String), {{/isMap}}FullType({{{returnBaseType}}})]);
    final {{{returnType}}} _responseData = _serializers.deserialize(
      _response.data,
      specifiedType: _responseType,
    ) as {{{returnType}}};
    {{/returnSimpleType}}
    {{/isResponseFile}}

    return Response<{{{returnType}}}>(
      data: _responseData,
      headers: _response.headers,
      isRedirect: _response.isRedirect,
      request: _response.request,
      redirects: _response.redirects,
      statusCode: _response.statusCode,
      statusMessage: _response.statusMessage,
      extra: _response.extra,
    );{{/returnType}}{{^returnType}}
    return _response;{{/returnType}}
  }

  {{/operation}}
}
{{/operations}}

*_api.dart

// ignore_for_file: unused_import

import 'dart:async';
import 'package:dio/dio.dart';
import 'package:built_value/serializer.dart';


import '{import&#x3D;lib.model.Body, classname&#x3D;Body}';
import '{import&#x3D;lib.model.Body1, classname&#x3D;Body1}';
import '{import&#x3D;lib.model.Body10, classname&#x3D;Body10}';
import '{import&#x3D;lib.model.Body11, classname&#x3D;Body11}';
import '{import&#x3D;lib.model.Body12, classname&#x3D;Body12}';
import '{import&#x3D;lib.model.Body13, classname&#x3D;Body13}';
import '{import&#x3D;lib.model.Body15, classname&#x3D;Body15}';
import '{import&#x3D;lib.model.Body2, classname&#x3D;Body2}';
import '{import&#x3D;lib.model.Body3, classname&#x3D;Body3}';
import '{import&#x3D;lib.model.Body4, classname&#x3D;Body4}';
import '{import&#x3D;lib.model.Body5, classname&#x3D;Body5}';
import '{import&#x3D;lib.model.Body6, classname&#x3D;Body6}';
import '{import&#x3D;lib.model.Body7, classname&#x3D;Body7}';
import '{import&#x3D;lib.model.Body8, classname&#x3D;Body8}';
import '{import&#x3D;lib.model.Body9, classname&#x3D;Body9}';
import '{import&#x3D;lib.model.InlineResponse200, classname&#x3D;InlineResponse200}';
import '{import&#x3D;lib.model.InlineResponse2001, classname&#x3D;InlineResponse2001}';
import '{import&#x3D;lib.model.InlineResponse20010, classname&#x3D;InlineResponse20010}';
import '{import&#x3D;lib.model.InlineResponse20011, classname&#x3D;InlineResponse20011}';
import '{import&#x3D;lib.model.InlineResponse20012, classname&#x3D;InlineResponse20012}';
import '{import&#x3D;lib.model.InlineResponse20013, classname&#x3D;InlineResponse20013}';
import '{import&#x3D;lib.model.InlineResponse20015, classname&#x3D;InlineResponse20015}';
import '{import&#x3D;lib.model.InlineResponse2002, classname&#x3D;InlineResponse2002}';
import '{import&#x3D;lib.model.InlineResponse2003, classname&#x3D;InlineResponse2003}';
import '{import&#x3D;lib.model.InlineResponse2004, classname&#x3D;InlineResponse2004}';
import '{import&#x3D;lib.model.InlineResponse2005, classname&#x3D;InlineResponse2005}';
import '{import&#x3D;lib.model.InlineResponse2006, classname&#x3D;InlineResponse2006}';
import '{import&#x3D;lib.model.InlineResponse2007, classname&#x3D;InlineResponse2007}';
import '{import&#x3D;lib.model.InlineResponse2008, classname&#x3D;InlineResponse2008}';
import '{import&#x3D;lib.model.InlineResponse2009, classname&#x3D;InlineResponse2009}';
import '{import&#x3D;lib.model.InlineResponse503, classname&#x3D;InlineResponse503}';

class AuthenticationApi {

  final Dio _dio;

  final Serializers _serializers;

config.yaml

generatorName: dart-dio
inputSpec: spec.yaml
outputFolder: .
templateDir: .templates/dart-dio
pubName: openapi
pubLibrary: openapi
useEnumExtension: true
serializationLibrary: built_value
additionalProperties:
  clientName: openApi
  serializableModel: "true"
  dateLibrary: core
  # # dateLibrary: timeMachine
  hideGenerationTimestamp: "true"
  useBuiltValue: "true"
typeMappings:
  Array: BuiltList
  array: BuiltList
  List: BuiltList
  set: BuiltSet
  map: BuiltMap
  file: Uint8List
  binary: Uint8List
  object: JsonObject
  AnyType: JsonObject
  Type: RecordType

generator version is 5.1.0

.Could you have a look? or help me correct it?

@kuhnroyal
Copy link
Contributor

The changes from #9167 (which you are trying to use) have not been released yet. You need to build from master if you want to use this.

@lam-nv
Copy link

lam-nv commented Apr 13, 2021

The changes from #9167 (which you are trying to use) have not been released yet. You need to build from master if you want to use this.

Hi @kuhnroyal,

I just checked with the build from master (5.1.1-SNAPSHOT), but it still the same result.

Kindly check with this statement:

{{#imports}}import '{{.}}';

Could you have a look?

@kuhnroyal
Copy link
Contributor

Not sure what you want me to check, the samples and the test are working. This is very likely a problem on your end. Please provided a fully reproducible sample if you think that is not the case.

@lam-nv
Copy link

lam-nv commented Apr 13, 2021

Not sure what you want me to check, the samples and the test are working. This is very likely a problem on your end. Please provided a fully reproducible sample if you think that is not the case.

Ah, I checked the samples, it's working, I think I need to check again my spec.yaml and local template as well.

Thanks for supported, @kuhnroyal.

@lam-nv
Copy link

lam-nv commented Apr 13, 2021

Hi @kuhnroyal

I have generated it successfully by customizing the generate-samples.sh file to generate.sh in my project like below:

#!/usr/bin/env bash
# this bash script generates all samples.
# it ensures that all changes are committed into the 'samples/' folder
# shellcheck disable=SC2155
declare cwd="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

declare root="$(cd "$cwd" && cd ../ && pwd)"
declare executable=".openapi-generator/target/openapi-generator-cli-5.1.1.jar"

export JAVA_OPTS="${JAVA_OPTS} -ea -server -Duser.timezone=UTC"
export BATCH_OPTS="${BATCH_OPTS:-}"

files=()
args=()
end_option=false
while [[ $# -gt 0 ]]; do
  key="$1"
  if [ "--" == "$key" ]; then
    end_option=true
  else
    if [[ "$end_option" = true ]]; then
      args+=("$1")
    else
      files+=("$1")
    fi
  fi
  shift
done

header="# START SCRIPT: $0
This script generates all configs under bin/configs by default.
You may generate a targeted script or set of scripts using glob patterns.

For example:
    $0 bin/configs/java-*

You may generate a single config with additional options if you use -- to
separate the single config file from the generator arguments.

For example:
    $0 bin/configs/java-vertx.yaml -- --global-property debugModels=true

"

echo "$header"
echo "$executable"

if [[ ${#files[@]} -eq 1 && "${files[0]}" != *'*'* ]]; then
    # shellcheck disable=SC2086
    # shellcheck disable=SC2068
    java ${JAVA_OPTS} -jar "$executable" generate -c ${files[0]} ${args[@]}
else
    echo "Please press CTRL+C to stop or the script will continue in 5 seconds."

    sleep 5

    if [ ${#files[@]} -eq 0 ]; then
      files=("${root}"/bin/configs/*.yaml)
    fi

    # shellcheck disable=SC2086
    # shellcheck disable=SC2068
    java ${JAVA_OPTS} -jar "$executable" batch ${BATCH_OPTS} --includes-base-dir "${root}" --fail-fast  -- ${files[@]}
fi

thanks for your support

@kuhnroyal
Copy link
Contributor

Whatever works for you. You can also always use docker with the cli-latest tag: https://hub.docker.com/r/openapitools/openapi-generator

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants