Skip to content

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

@0xNF

Description

@0xNF

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
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 (863dbc7)

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.

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