openapi-generator icon indicating copy to clipboard operation
openapi-generator copied to clipboard

[BUG] [Dart] Default enums generate syntacticallly incorrect Dart files

Open 0xNF opened this issue 3 years ago • 0 comments

Bug Report Checklist

  • [x] Have you provided a full/minimal spec to reproduce the issue?
  • [x] Have you validated the input using an OpenAPI validator (example)?
  • [x] Have you tested with the latest master to confirm the issue still exists?
  • [x] Have you searched for related issues/PRs?
  • [x] What's the actual output vs expected output?
  • [ ] [Optional] Sponsorship to speed up the bug fix or feature request (example)
Description
with_enums.dart
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.12

// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars

part of openapi.api;

class WithEnums {
  /// Returns a new [WithEnums] instance.
  WithEnums({
    this.stringEnum = const WithEnumsStringEnumEnum._('strC'),
    this.intEnum = const WithEnumsIntEnumEnum._(WithEnumsIntEnumEnum.number2),
  });

  WithEnumsStringEnumEnum stringEnum;

  WithEnumsIntEnumEnum intEnum;

  @override
  bool operator ==(Object other) => identical(this, other) || other is WithEnums && other.stringEnum == stringEnum && other.intEnum == intEnum;

  @override
  int get hashCode =>
      // ignore: unnecessary_parenthesis
      (stringEnum.hashCode) + (intEnum.hashCode);

  @override
  String toString() => 'WithEnums[stringEnum=$stringEnum, intEnum=$intEnum]';

  Map<String, dynamic> toJson() {
    final json = <String, dynamic>{};
    json[r'stringEnum'] = this.stringEnum;
    json[r'intEnum'] = this.intEnum;
    return json;
  }

  /// Returns a new [WithEnums] instance and imports its values from
  /// [value] if it's a [Map], null otherwise.
  // ignore: prefer_constructors_over_static_methods
  static WithEnums? fromJson(dynamic value) {
    if (value is Map) {
      final json = value.cast<String, dynamic>();

      // Ensure that the map contains the required keys.
      // Note 1: the values aren't checked for validity beyond being non-null.
      // Note 2: this code is stripped in release mode!
      assert(() {
        requiredKeys.forEach((key) {
          assert(json.containsKey(key), 'Required key "WithEnums[$key]" is missing from JSON.');
          assert(json[key] != null, 'Required key "WithEnums[$key]" has a null value in JSON.');
        });
        return true;
      }());

      return WithEnums(
        stringEnum: WithEnumsStringEnumEnum.fromJson(json[r'stringEnum']) ?? 'strC',
        intEnum: WithEnumsIntEnumEnum.fromJson(json[r'intEnum']) ?? WithEnumsIntEnumEnum.number2,
      );
    }
    return null;
  }

  static List<WithEnums>? listFromJson(
    dynamic json, {
    bool growable = false,
  }) {
    final result = <WithEnums>[];
    if (json is List && json.isNotEmpty) {
      for (final row in json) {
        final value = WithEnums.fromJson(row);
        if (value != null) {
          result.add(value);
        }
      }
    }
    return result.toList(growable: growable);
  }

  static Map<String, WithEnums> mapFromJson(dynamic json) {
    final map = <String, WithEnums>{};
    if (json is Map && json.isNotEmpty) {
      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
      for (final entry in json.entries) {
        final value = WithEnums.fromJson(entry.value);
        if (value != null) {
          map[entry.key] = value;
        }
      }
    }
    return map;
  }

  // maps a json object with a list of WithEnums-objects as value to a dart map
  static Map<String, List<WithEnums>> mapListFromJson(
    dynamic json, {
    bool growable = false,
  }) {
    final map = <String, List<WithEnums>>{};
    if (json is Map && json.isNotEmpty) {
      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
      for (final entry in json.entries) {
        final value = WithEnums.listFromJson(
          entry.value,
          growable: growable,
        );
        if (value != null) {
          map[entry.key] = value;
        }
      }
    }
    return map;
  }

  /// The list of required keys that must be present in a JSON.
  static const requiredKeys = <String>{};
}

class WithEnumsStringEnumEnum {
  /// Instantiate a new enum with the provided [value].
  const WithEnumsStringEnumEnum._(this.value);

  /// The underlying value of this enum member.
  final String value;

  @override
  String toString() => value;

  String toJson() => value;

  static const strA = WithEnumsStringEnumEnum._(r'strA');
  static const strB = WithEnumsStringEnumEnum._(r'strB');
  static const strC = WithEnumsStringEnumEnum._(r'strC');

  /// List of all possible values in this [enum][WithEnumsStringEnumEnum].
  static const values = <WithEnumsStringEnumEnum>[
    strA,
    strB,
    strC,
  ];

  static WithEnumsStringEnumEnum? fromJson(dynamic value) => WithEnumsStringEnumEnumTypeTransformer().decode(value);

  static List<WithEnumsStringEnumEnum>? listFromJson(
    dynamic json, {
    bool growable = false,
  }) {
    final result = <WithEnumsStringEnumEnum>[];
    if (json is List && json.isNotEmpty) {
      for (final row in json) {
        final value = WithEnumsStringEnumEnum.fromJson(row);
        if (value != null) {
          result.add(value);
        }
      }
    }
    return result.toList(growable: growable);
  }
}

/// Transformation class that can [encode] an instance of [WithEnumsStringEnumEnum] to String,
/// and [decode] dynamic data back to [WithEnumsStringEnumEnum].
class WithEnumsStringEnumEnumTypeTransformer {
  factory WithEnumsStringEnumEnumTypeTransformer() => _instance ??= const WithEnumsStringEnumEnumTypeTransformer._();

  const WithEnumsStringEnumEnumTypeTransformer._();

  String encode(WithEnumsStringEnumEnum data) => data.value;

  /// Decodes a [dynamic value][data] to a WithEnumsStringEnumEnum.
  ///
  /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
  /// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
  /// cannot be decoded successfully, then an [UnimplementedError] is thrown.
  ///
  /// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
  /// and users are still using an old app with the old code.
  WithEnumsStringEnumEnum? decode(dynamic data, {bool allowNull = true}) {
    if (data != null) {
      switch (data.toString()) {
        case r'strA':
          return WithEnumsStringEnumEnum.strA;
        case r'strB':
          return WithEnumsStringEnumEnum.strB;
        case r'strC':
          return WithEnumsStringEnumEnum.strC;
        default:
          if (!allowNull) {
            throw ArgumentError('Unknown enum value to decode: $data');
          }
      }
    }
    return null;
  }

  /// Singleton [WithEnumsStringEnumEnumTypeTransformer] instance.
  static WithEnumsStringEnumEnumTypeTransformer? _instance;
}

class WithEnumsIntEnumEnum {
  /// Instantiate a new enum with the provided [value].
  const WithEnumsIntEnumEnum._(this.value);

  /// The underlying value of this enum member.
  final int value;

  @override
  String toString() => value.toString();

  int toJson() => value;

  static const number0 = WithEnumsIntEnumEnum._(0);
  static const number1 = WithEnumsIntEnumEnum._(1);
  static const number2 = WithEnumsIntEnumEnum._(2);

  /// List of all possible values in this [enum][WithEnumsIntEnumEnum].
  static const values = <WithEnumsIntEnumEnum>[
    number0,
    number1,
    number2,
  ];

  static WithEnumsIntEnumEnum? fromJson(dynamic value) => WithEnumsIntEnumEnumTypeTransformer().decode(value);

  static List<WithEnumsIntEnumEnum>? listFromJson(
    dynamic json, {
    bool growable = false,
  }) {
    final result = <WithEnumsIntEnumEnum>[];
    if (json is List && json.isNotEmpty) {
      for (final row in json) {
        final value = WithEnumsIntEnumEnum.fromJson(row);
        if (value != null) {
          result.add(value);
        }
      }
    }
    return result.toList(growable: growable);
  }
}

/// Transformation class that can [encode] an instance of [WithEnumsIntEnumEnum] to int,
/// and [decode] dynamic data back to [WithEnumsIntEnumEnum].
class WithEnumsIntEnumEnumTypeTransformer {
  factory WithEnumsIntEnumEnumTypeTransformer() => _instance ??= const WithEnumsIntEnumEnumTypeTransformer._();

  const WithEnumsIntEnumEnumTypeTransformer._();

  int encode(WithEnumsIntEnumEnum data) => data.value;

  /// Decodes a [dynamic value][data] to a WithEnumsIntEnumEnum.
  ///
  /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
  /// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
  /// cannot be decoded successfully, then an [UnimplementedError] is thrown.
  ///
  /// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
  /// and users are still using an old app with the old code.
  WithEnumsIntEnumEnum? decode(dynamic data, {bool allowNull = true}) {
    if (data != null) {
      switch (data.toString()) {
        case 0:
          return WithEnumsIntEnumEnum.number0;
        case 1:
          return WithEnumsIntEnumEnum.number1;
        case 2:
          return WithEnumsIntEnumEnum.number2;
        default:
          if (!allowNull) {
            throw ArgumentError('Unknown enum value to decode: $data');
          }
      }
    }
    return null;
  }

  /// Singleton [WithEnumsIntEnumEnumTypeTransformer] instance.
  static WithEnumsIntEnumEnumTypeTransformer? _instance;
}

Default enums are generating with uncompilable code. This appears in two different ways depending on the type of enum:

For integer enums, the constructor is invalid:

  WithEnums({
    this.intEnum = const WithEnumsIntEnumEnum._(WithEnumsIntEnumEnum.number2),
  });

WithEnumsIntEnumEnum.number2 isn't a valid input to the enum constructor.

For string enums, the constructor is fine, but the fromJson method contains errors:

 static WithEnums? fromJson(dynamic value) {
    if (value is Map) {
      final json = value.cast<String, dynamic>();
	/* omitted */
      return WithEnums(
        stringEnum: WithEnumsStringEnumEnum.fromJson(json[r'stringEnum']) ?? 'strC',
      );
    }
    return null;
  }

This is incorrect because a raw string value isn't a valid enum value.

openapi-generator version

Latest commit as of 9/18/22 (863dbc7c3ee39af1ac931d2949388f7f84896ec4)

OpenAPI declaration file content or url
Specfile
openapi: 3.0.3
info:
  version: "1.1"
  title: Dart Uint8list Demo
servers:
  - url: "localhost"
    variables:
      host:
        default: localhost
paths:
  /item:
    get:
      operationId: GetItem
      description: "Should return an Item"
      responses:
        "200":
          description: items
          content:
            application/json:
              schema:
                $ref: "#components/schemas/item"
components:
  schemas:
    WithEnums:
      type: object
      properties:
        stringEnum:
          type: string
          enum:
            - strA
            - strB
            - strC
          default: strC
        intEnum:
          type: integer
          enum:
            - 0
            - 1
            - 2
          default: 2
Generation Details
Suggest a fix

Mustache template tweaks are required.

0xNF avatar Sep 18 '22 05:09 0xNF