diff --git a/CHANGELOG.md b/CHANGELOG.md index 13be6ba..6e87b71 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,10 @@ # Changelog ## [7.0.0] - 2023-08-14 - *BREAKING CHANGE*: Every type is now defined inline, this means that 'required' is no longer supported, if a field isn't nullable it is automatically required. This also means that the 'array' type is no longer supported and is instead just defined like 'List'. +- *BREAKING CHANGE*: The way enums are defined has changed, see readme for more information. You can now add properties to enums, optional and default values are supported. +- *BREAKING CHANGE*: Enums are now by default not uppercase anymore, you can still enable this my adding 'uppercase_enums: true' to your pubspec or enum configuration - Logs of build runner now get shown in real time. +- You are now allowed to have no properties configured for a class. ## [6.3.0] - 2023-06-05 - Fixed the deprecated `ignore` field. Added diff --git a/README.md b/README.md index 6cfb8e0..74545ae 100755 --- a/README.md +++ b/README.md @@ -333,7 +333,7 @@ DateTimeConverter: ## Inline types (since 6.0.0) -In some cases, writing the full specification for simple fields is very verbose. Since 6.0.0 it is possible to write simple fields inline, without nesting below the field name: +In some cases, writing the full specification for simple fields is very verbose. Since 6.0.0 it is possible to write simple fields inline, without nesting below the field name, since 7.0.0 nested lists and list in maps is also supported: ```yaml UserModel: @@ -345,6 +345,7 @@ UserModel: created_at: DateTime roles: List customProperties: Map? + customPropertiesList: Map>? ``` since 7.0.0 inline types are supported now even when adding extra configuration: @@ -373,67 +374,161 @@ BookCase: include_if_null: false ``` -Currently all basic types are supported, simple Lists and Maps (no nested types, no nullable generic parameters) as well as references to other objects. +Currently all basic types are supported, simple Lists and Maps (no nullable generic parameters) as well as references to other objects. Items post-fixed with `?` will be marked optional. -## Enum support +## Enum support (as of v7.0.0 enums now support properties) -Add enums with custom values (can be mapped to String,double,int) +Add simple enums, the name of the enum value (MALE, FEMALE, X, Y) will be used when parsing from json ```yaml Gender: path: webservice/user type: enum - properties: + values: MALE: - value: _mAl3 FEMALE: - value: femAle X: - value: X Y: ``` -### Generate mapping +By default enums will be generated with a property called jsonValue. this is the value of the enum used when parsing from json. This will only be used when there isn't already a custom jsonValue defined using 'is_json_value: true' in the properties of the enum. To turn this behavior of you can use 'use_default_json_value: false'. + +```yaml +Gender: + path: webservice/user + use_default_json_value: false + type: enum + values: + MALE: + FEMALE: + X: + Y: +``` -For enums, it is also possible to have a map generated that maps from the enum value to its string representation and reverse. To enable this, use `generate_map: true` +Add enums with custom properties (currently supported types are int, double, bool and String) ```yaml Gender: path: webservice/user type: enum - generate_map: true properties: + abbreviation: String + values: MALE: - value: _mAl3 + properties: + abbreviation: m FEMALE: - value: femAle + properties: + abbreviation: f X: - value: X + properties: + abbreviation: x Y: + properties: + abbreviation: y ``` -### Generate mapping extensions +Define custom json key using is_json_value, the value of this property will then be used to parse from json + +```yaml +Gender: + path: webservice/user + type: enum + properties: + key: + type: String + is_json_value: true + abbreviation: String + values: + MALE: + properties: + key: male + abbreviation: m + FEMALE: + properties: + key: female + abbreviation: f + X: + properties: + key: x + abbreviation: x + Y: + properties: + key: y + abbreviation: y +``` -When generating maps, it is also possible to specify that special extension functions should be added that return either the string value or that takes a string value and tries to -convert it to the enum value. To enable this, use `generate_map: true` **AND** `generate_extensions: true` +Optional and default values are supported. If value isn't defined for a property then it will use the defaultValue. If a property is optional and no value is given it is null. ```yaml Gender: path: webservice/user type: enum - generate_map: true - generate_extensions: true properties: + key: + type: String + is_json_value: true + abbreviation: + type: String + default_value: m + lastName: String? + values: MALE: - value: _mAl3 + properties: + key: male FEMALE: - value: femAle + properties: + key: female + abbreviation: f X: - value: X + properties: + key: x Y: + properties: + key: y + lastName: lastName ``` +### Generate mapping extensions + +It is possible to generate an extension for the enum that can turn the enum into it's corresponding jsonValue and the reverse. + +```yaml +Person: + path: test/enum/ + type: enum + generate_extension: true + properties: + jsonValue: + is_json_value: true + type: int + firstName: String + lastName: String + values: + MAN: + properties: + jsonKey: 1 + firstName: firstName1 + lastName: lastName1 + WOMAN: + properties: + jsonKey: 2 + firstName: firstName2 + lastName: lastName2 + +``` +The above configuration will generate an enum with this extension. + +```dart +extension PersonExtension on Person { + static Person? fromJsonValue(int value) => Person.values.firstWhereOrNull((enumValue) => enumValue.jsonKey == value); + + int toJsonValue() => jsonKey; +} +``` + +### Generate mapping is no longer supported as of V7.0.0, use properties instead ### Use unknownEnumValue ```yaml @@ -445,14 +540,14 @@ UnknownEnumTestObject: type: Gender ``` -### Automatic case conversion +### Automatic case conversion(v7.0.0) -By default all fields will be converted into uppercase. You can control this behavior globally for all enums or per-enum by setting the `uppercase_enums` property to `true` ( -default) or `false` +As of v7.0.0 by default all fields will be converted into lowercase camelcase instead of uppercase like before. You can control this behavior globally for all enums or per-enum by setting the `uppercase_enums` property to `false` ( +default) or `true`. This only affects the name of the enum when using it in dart code. the jsonValue will still be the name you type in the config. ```yaml model_generator: - uppercase_enums: false + uppercase_enums: true ``` or @@ -460,7 +555,7 @@ or ```yaml UnknownEnumTestObject: path: webservice - uppercase_enums: false + uppercase_enums: true properties: path: ``` @@ -546,6 +641,8 @@ DateTimeConverter: You can specify `description` on models, enum, fields and on enum entries. This description will be used verbatim to generate a code comment for that class/enum/field +Example for a class: + ```yaml UserModel: path: webservice/user @@ -557,6 +654,23 @@ UserModel: changedAt: DateTime ``` +Example for a enum: + +```yaml +Person: + path: test/enum/ + type: enum + description: This is a enum of a person + values: + MAN: + description: enum of a man + WOMAN: + description: enum of a woman + OTHER: + description: enum of a other +``` + + ## Static creator support You can specify `static_create` on objects or globally in the `pubspec.yaml` file. If this is specified, a static creator method called `create` will be generated referencing the diff --git a/assets/example.gif b/assets/example.gif deleted file mode 100644 index 39a2a82..0000000 Binary files a/assets/example.gif and /dev/null differ diff --git a/bin/model_generator.dart b/bin/model_generator.dart index df0a25d..172b4f1 100644 --- a/bin/model_generator.dart +++ b/bin/model_generator.dart @@ -1,17 +1,5 @@ -import 'dart:async'; -import 'dart:io'; - import 'package:args/args.dart'; -import 'package:model_generator/config/pubspec_config.dart'; -import 'package:model_generator/config/yml_generator_config.dart'; -import 'package:model_generator/model/model/custom_model.dart'; -import 'package:model_generator/model/model/enum_model.dart'; -import 'package:model_generator/model/model/json_converter_model.dart'; -import 'package:model_generator/model/model/object_model.dart'; -import 'package:model_generator/run_process/run_process.dart'; -import 'package:model_generator/writer/enum_model_writer.dart'; -import 'package:model_generator/writer/object_model_writer.dart'; -import 'package:path/path.dart'; +import 'package:model_generator/main.dart'; Future main(List args) async { final argParser = ArgParser() @@ -26,123 +14,5 @@ Future main(List args) async { print(argParser.usage); return; } - - final pubspecYaml = File(join(Directory.current.path, 'pubspec.yaml')); - if (!pubspecYaml.existsSync()) { - throw Exception( - 'This program should be run from the root of a flutter/dart project'); - } - final pubspecContent = pubspecYaml.readAsStringSync(); - final pubspecConfig = PubspecConfig(pubspecContent); - - final configPath = results['path'] ?? pubspecConfig.configPath; - String absolutePath; - if (isAbsolute(configPath)) { - absolutePath = configPath; - } else { - absolutePath = join(Directory.current.path, configPath); - } - final FileSystemEntity configEntity; - switch (FileSystemEntity.typeSync(absolutePath)) { - case FileSystemEntityType.directory: - configEntity = Directory(absolutePath); - break; - case FileSystemEntityType.file: - default: - configEntity = File(absolutePath); - break; - } - - if (!configEntity.existsSync()) { - throw Exception( - 'This program requires a config file/dir. `$configPath` does not exist'); - } - final YmlGeneratorConfig modelGeneratorConfig; - if (configEntity is Directory) { - modelGeneratorConfig = - readConfigFilesInDirectory(pubspecConfig, configEntity, configPath); - } else { - final modelGeneratorContent = (configEntity as File).readAsStringSync(); - modelGeneratorConfig = - YmlGeneratorConfig(pubspecConfig, modelGeneratorContent, configPath); - } - modelGeneratorConfig.checkIfTypesAvailable(); - if (modelGeneratorConfig.models.isEmpty) { - print('No models defined in config files, skipping generation'); - } - - writeToFiles(pubspecConfig, modelGeneratorConfig); - await generateJsonGeneratedModels(useFvm: pubspecConfig.useFvm); - print('Done!!!'); -} - -YmlGeneratorConfig readConfigFilesInDirectory( - PubspecConfig config, Directory configEntity, String directoryPath) { - final configFiles = configEntity - .listSync(recursive: true) - .whereType() - .where((element) => - extension(element.path) == '.yaml' || - extension(element.path) == '.yml'); - final configs = configFiles.map((e) => - YmlGeneratorConfig(config, e.readAsStringSync(), relative(e.path))); - return YmlGeneratorConfig.merge(configs, directoryPath); -} - -void writeToFiles( - PubspecConfig pubspecConfig, YmlGeneratorConfig modelGeneratorConfig) { - for (final model in modelGeneratorConfig.models) { - final modelDirectory = Directory(join('lib', model.baseDirectory)); - if (!modelDirectory.existsSync()) { - modelDirectory.createSync(recursive: true); - } - String? content; - if (model is ObjectModel) { - content = ObjectModelWriter( - pubspecConfig, - model, - modelGeneratorConfig, - ).write(); - } else if (model is EnumModel) { - content = EnumModelWriter(model).write(); - } else if (model is JsonConverterModel) { - continue; - } else if (model is CustomModel) { - continue; - } - if (content == null) { - throw Exception( - 'content is null for ${model.name}. File a bug report on github. This is not normal. https://github.com/icapps/flutter-model-generator/issues'); - } - File file; - if (model.path == null) { - file = File(join('lib', model.baseDirectory, '${model.fileName}.dart')); - } else { - file = File(join( - 'lib', model.baseDirectory, model.path, '${model.fileName}.dart')); - } - if (!file.existsSync()) { - file.createSync(recursive: true); - } - file.writeAsStringSync(content); - } -} - -Future generateJsonGeneratedModels({required bool useFvm}) async { - final arguments = [ - if (useFvm) ...[ - 'fvm', - ], - 'flutter', - 'packages', - 'pub', - 'run', - 'build_runner', - 'build', - '--delete-conflicting-outputs', - ]; - await ProcessRunner.runProcessVerbose( - arguments.first, - arguments.skip(1).toList(), - ); + ModelGenerator.generate(results); } diff --git a/example/lib/model/ogm.dart b/example/lib/model/ogm.dart index 1cf58fb..aebfd7e 100644 --- a/example/lib/model/ogm.dart +++ b/example/lib/model/ogm.dart @@ -9,19 +9,21 @@ part 'ogm.g.dart'; @JsonSerializable(explicitToJson: true) @DateTimeConverter() class OGM { - @JsonKey(name: 'beneficiary', required: true, includeIfNull: false) + @JsonKey(name: 'beneficiary', required: true) final String beneficiary; - @JsonKey(name: 'beneficiaryIBAN', required: true, includeIfNull: false) + @JsonKey(name: 'beneficiaryIBAN', required: true) final String beneficiaryIBAN; - @JsonKey(name: 'test_Test', required: true, includeIfNull: false) + @JsonKey(name: 'test_Test', required: true) final String testTest; - @JsonKey(name: 'some_Thing', required: true, includeIfNull: false) + @JsonKey(name: 'some_Thing', required: true) final String someThing; - @JsonKey(name: 'some_ThinG_huGE', required: true, includeIfNull: false) + @JsonKey(name: 'some_ThinG_huGE', required: true) final String someThinGHuGE; @JsonKey(name: 'simpleFields', required: true) final List simpleFields; - @JsonKey(name: 'structuredMessage') + @JsonKey(name: 'listMap', required: true) + final Map> listMap; + @JsonKey(name: 'structuredMessage', includeIfNull: false) final String? structuredMessage; @JsonKey(name: 'securityIndicator', includeIfNull: false) final String? securityRole; @@ -31,7 +33,7 @@ class OGM { final DateTime? dateChange; @JsonKey(name: 'fields', includeIfNull: false) final List>? fields; - @JsonKey(name: 'simpleMap') + @JsonKey(name: 'simpleMap', includeIfNull: false) final Map? simpleMap; OGM({ @@ -41,6 +43,7 @@ class OGM { required this.someThing, required this.someThinGHuGE, required this.simpleFields, + required this.listMap, this.structuredMessage, this.securityRole, this.mutableProperty, @@ -64,6 +67,7 @@ class OGM { someThing == other.someThing && someThinGHuGE == other.someThinGHuGE && simpleFields == other.simpleFields && + listMap == other.listMap && structuredMessage == other.structuredMessage && securityRole == other.securityRole && mutableProperty == other.mutableProperty && @@ -79,6 +83,7 @@ class OGM { someThing.hashCode ^ someThinGHuGE.hashCode ^ simpleFields.hashCode ^ + listMap.hashCode ^ structuredMessage.hashCode ^ securityRole.hashCode ^ mutableProperty.hashCode ^ @@ -94,6 +99,7 @@ class OGM { 'someThing: $someThing, ' 'someThinGHuGE: $someThinGHuGE, ' 'simpleFields: $simpleFields, ' + 'listMap: $listMap, ' 'structuredMessage: $structuredMessage, ' 'securityRole: $securityRole, ' 'mutableProperty: $mutableProperty, ' diff --git a/example/lib/model/ogm.g.dart b/example/lib/model/ogm.g.dart index 922ffc5..f5c2704 100644 --- a/example/lib/model/ogm.g.dart +++ b/example/lib/model/ogm.g.dart @@ -15,7 +15,8 @@ OGM _$OGMFromJson(Map json) { 'test_Test', 'some_Thing', 'some_ThinG_huGE', - 'simpleFields' + 'simpleFields', + 'listMap' ], ); return OGM( @@ -27,6 +28,10 @@ OGM _$OGMFromJson(Map json) { simpleFields: (json['simpleFields'] as List) .map((e) => Testing.fromJson(e as Map)) .toList(), + listMap: (json['listMap'] as Map).map( + (k, e) => MapEntry( + int.parse(k), (e as List).map((e) => e as String).toList()), + ), structuredMessage: json['structuredMessage'] as String?, securityRole: json['securityIndicator'] as String?, mutableProperty: json['mutableProperty'] as String?, @@ -51,7 +56,7 @@ Map _$OGMToJson(OGM instance) { 'some_Thing': instance.someThing, 'some_ThinG_huGE': instance.someThinGHuGE, 'simpleFields': instance.simpleFields.map((e) => e.toJson()).toList(), - 'structuredMessage': instance.structuredMessage, + 'listMap': instance.listMap.map((k, e) => MapEntry(k.toString(), e)), }; void writeNotNull(String key, dynamic value) { @@ -60,6 +65,7 @@ Map _$OGMToJson(OGM instance) { } } + writeNotNull('structuredMessage', instance.structuredMessage); writeNotNull('securityIndicator', instance.securityRole); writeNotNull('mutableProperty', instance.mutableProperty); writeNotNull( @@ -68,7 +74,8 @@ Map _$OGMToJson(OGM instance) { instance.dateChange, const DateTimeConverter().toJson)); writeNotNull('fields', instance.fields?.map((e) => e.map((e) => e.toJson()).toList()).toList()); - val['simpleMap'] = instance.simpleMap?.map((k, e) => MapEntry(k, e.toJson())); + writeNotNull( + 'simpleMap', instance.simpleMap?.map((k, e) => MapEntry(k, e.toJson()))); return val; } diff --git a/example/lib/model/status/double_status.dart b/example/lib/model/status/double_status.dart index f888ab3..47fe45d 100644 --- a/example/lib/model/status/double_status.dart +++ b/example/lib/model/status/double_status.dart @@ -3,34 +3,26 @@ import 'package:json_annotation/json_annotation.dart'; enum DoubleStatus { - @JsonValue(0.0) - STATUS_0, - @JsonValue(1.0) - STATUS_1, - @JsonValue(2.0) - STATUS_2, - @JsonValue(3.0) - STATUS_3, -} - -const doubleStatusMapping = { - DoubleStatus.STATUS_0: 0.0, - DoubleStatus.STATUS_1: 1.0, - DoubleStatus.STATUS_2: 2.0, - DoubleStatus.STATUS_3: 3.0, -}; + @JsonValue(0.1) + status0( + value: 0.1, + ), + @JsonValue(1.1) + status1( + value: 1.1, + ), + @JsonValue(2.2) + status2( + value: 2.2, + ), + @JsonValue(3.3) + status3( + value: 3.3, + ); -final reverseDoubleStatusMapping = { - 0.0: DoubleStatus.STATUS_0, - 1.0: DoubleStatus.STATUS_1, - 2.0: DoubleStatus.STATUS_2, - 3.0: DoubleStatus.STATUS_3, -}; - -extension DoubleStatusExtension on DoubleStatus { - double get doubleValue => doubleStatusMapping[this]!; -} + final double value; -extension DoubleStatusDoubleExtension on double { - DoubleStatus? get asDoubleStatus => reverseDoubleStatusMapping[this]; + const DoubleStatus({ + required this.value, + }); } diff --git a/example/lib/model/status/status.dart b/example/lib/model/status/status.dart index a0d45ff..678b9b5 100644 --- a/example/lib/model/status/status.dart +++ b/example/lib/model/status/status.dart @@ -4,33 +4,25 @@ import 'package:json_annotation/json_annotation.dart'; enum Status { @JsonValue(0) - STATUS_0, + status0( + value: 0, + ), @JsonValue(1) - STATUS_1, + status1( + value: 1, + ), @JsonValue(2) - STATUS_2, + status2( + value: 2, + ), @JsonValue(3) - STATUS_3, -} - -const statusMapping = { - Status.STATUS_0: 0, - Status.STATUS_1: 1, - Status.STATUS_2: 2, - Status.STATUS_3: 3, -}; + status3( + value: 3, + ); -const reverseStatusMapping = { - 0: Status.STATUS_0, - 1: Status.STATUS_1, - 2: Status.STATUS_2, - 3: Status.STATUS_3, -}; - -extension StatusExtension on Status { - int get intValue => statusMapping[this]!; -} + final int value; -extension StatusIntExtension on int { - Status? get asStatus => reverseStatusMapping[this]; + const Status({ + required this.value, + }); } diff --git a/example/lib/model/user/person/gender.dart b/example/lib/model/user/person/gender.dart index 687ae82..65bcadf 100644 --- a/example/lib/model/user/person/gender.dart +++ b/example/lib/model/user/person/gender.dart @@ -4,21 +4,45 @@ import 'package:json_annotation/json_annotation.dart'; enum Gender { @JsonValue('_mAl3') - MALE, + male( + value: '_mAl3', + ), @JsonValue('femAle') - FEMALE, + female( + value: 'femAle', + ), @JsonValue('X') - X, - @JsonValue('GENDER_X') - GENDER_X, - @JsonValue('null') - GENDER_Y, + x( + value: 'X', + ), + @JsonValue('gender_x') + genderX( + value: 'gender_x', + ), + @JsonValue('gender_y') + genderY( + value: 'gender_y', + ), @JsonValue('gender_z') - GENDER_Z, - @JsonValue('GENDER_abC') - GENDER_ABC, - @JsonValue('null') - GENDER_DEF, + genderZ( + value: 'gender_z', + ), + @JsonValue('gender_abc') + genderAbc( + value: 'gender_abc', + ), + @JsonValue('gender_def') + genderDef( + value: 'gender_def', + ), @JsonValue('GENDER_lap') - GENDER_LAP, + genderLap( + value: 'GENDER_lap', + ); + + final String value; + + const Gender({ + required this.value, + }); } diff --git a/example/lib/model/user/person/person.dart b/example/lib/model/user/person/person.dart index 1606912..ef92c5e 100644 --- a/example/lib/model/user/person/person.dart +++ b/example/lib/model/user/person/person.dart @@ -9,13 +9,9 @@ part 'person.g.dart'; @JsonSerializable(explicitToJson: true) @immutable class Person { - @JsonKey(name: 'firstName', required: true, includeIfNull: false) + @JsonKey(name: 'firstName', required: true) final String firstName; - @JsonKey( - name: 'gender', - required: true, - includeIfNull: false, - unknownEnumValue: Gender.X) + @JsonKey(name: 'gender', required: true, unknownEnumValue: Gender.x) final Gender gender; const Person({ diff --git a/example/lib/model/user/person/person.g.dart b/example/lib/model/user/person/person.g.dart index 970221b..ce41c07 100644 --- a/example/lib/model/user/person/person.g.dart +++ b/example/lib/model/user/person/person.g.dart @@ -14,7 +14,7 @@ Person _$PersonFromJson(Map json) { return Person( firstName: json['firstName'] as String, gender: - $enumDecode(_$GenderEnumMap, json['gender'], unknownValue: Gender.X), + $enumDecode(_$GenderEnumMap, json['gender'], unknownValue: Gender.x), ); } @@ -24,13 +24,13 @@ Map _$PersonToJson(Person instance) => { }; const _$GenderEnumMap = { - Gender.MALE: '_mAl3', - Gender.FEMALE: 'femAle', - Gender.X: 'X', - Gender.GENDER_X: 'GENDER_X', - Gender.GENDER_Y: 'null', - Gender.GENDER_Z: 'gender_z', - Gender.GENDER_ABC: 'GENDER_abC', - Gender.GENDER_DEF: 'null', - Gender.GENDER_LAP: 'GENDER_lap', + Gender.male: '_mAl3', + Gender.female: 'femAle', + Gender.x: 'X', + Gender.genderX: 'gender_x', + Gender.genderY: 'gender_y', + Gender.genderZ: 'gender_z', + Gender.genderAbc: 'gender_abc', + Gender.genderDef: 'gender_def', + Gender.genderLap: 'GENDER_lap', }; diff --git a/example/lib/model/user/profile/admin_profile_data.dart b/example/lib/model/user/profile/admin_profile_data.dart index ac5a8af..fbcb67c 100644 --- a/example/lib/model/user/profile/admin_profile_data.dart +++ b/example/lib/model/user/profile/admin_profile_data.dart @@ -12,7 +12,7 @@ part 'admin_profile_data.g.dart'; @JsonSerializable(explicitToJson: true) @immutable class AdminProfileData extends UserProfileDataExtended { - @JsonKey(name: 'privileges', required: true, includeIfNull: false) + @JsonKey(name: 'privileges', required: true) final String privileges; const AdminProfileData({ diff --git a/example/lib/model/user/profile/user_profile_data.dart b/example/lib/model/user/profile/user_profile_data.dart index 84b9c2d..ebd73d8 100644 --- a/example/lib/model/user/profile/user_profile_data.dart +++ b/example/lib/model/user/profile/user_profile_data.dart @@ -11,19 +11,19 @@ part 'user_profile_data.g.dart'; @JsonSerializable(explicitToJson: true) @immutable class UserProfileData { - @JsonKey(name: 'firstName', required: true, includeIfNull: false) + @JsonKey(name: 'firstName', required: true) final String firstName; - @JsonKey(name: 'lastName', required: true, includeIfNull: false) + @JsonKey(name: 'lastName', required: true) final String lastName; - @JsonKey(name: 'standardLanguage', required: true, includeIfNull: false) + @JsonKey(name: 'standardLanguage', required: true) final String standardLanguage; - @JsonKey(name: 'mainAccountNumber', required: true, includeIfNull: false) + @JsonKey(name: 'mainAccountNumber', required: true) final String mainAccountNumber; - @JsonKey(name: 'legalEmail', required: true, includeIfNull: false) + @JsonKey(name: 'legalEmail', required: true) final String legalEmail; - @JsonKey(name: 'phones', required: true, includeIfNull: false) + @JsonKey(name: 'phones', required: true) final Testing phones; - @JsonKey(name: 'legalAddress', required: true, includeIfNull: false) + @JsonKey(name: 'legalAddress', required: true) final OGM legalAddress; @JsonKey(name: 'offTrack', includeIfNull: false) final List? offTrack; diff --git a/example/lib/model/user/profile/user_profile_data_extended.dart b/example/lib/model/user/profile/user_profile_data_extended.dart index c0e77fb..a4c6f19 100644 --- a/example/lib/model/user/profile/user_profile_data_extended.dart +++ b/example/lib/model/user/profile/user_profile_data_extended.dart @@ -12,7 +12,7 @@ part 'user_profile_data_extended.g.dart'; @JsonSerializable(explicitToJson: true) @immutable class UserProfileDataExtended extends UserProfileData { - @JsonKey(name: 'additionalField', required: true, includeIfNull: false) + @JsonKey(name: 'additionalField', required: true) final String additionalField; const UserProfileDataExtended({ diff --git a/example/lib/model/user/project/project.dart b/example/lib/model/user/project/project.dart index b9afa89..0360843 100644 --- a/example/lib/model/user/project/project.dart +++ b/example/lib/model/user/project/project.dart @@ -9,16 +9,12 @@ part 'project.g.dart'; @JsonSerializable(explicitToJson: true) @immutable class Project { - @JsonKey( - name: 'name', - required: false, - disallowNullValue: false, - includeIfNull: false) + @JsonKey(name: 'name', required: false) final String name; @JsonKey(name: 'cost', includeIfNull: false) final double? cost; @JsonKey( - name: 'status', includeIfNull: false, unknownEnumValue: Status.STATUS_0) + name: 'status', includeIfNull: false, unknownEnumValue: Status.status0) final Status? status; const Project({ diff --git a/example/lib/model/user/project/project.g.dart b/example/lib/model/user/project/project.g.dart index bd63dd7..27c8f40 100644 --- a/example/lib/model/user/project/project.g.dart +++ b/example/lib/model/user/project/project.g.dart @@ -10,7 +10,7 @@ Project _$ProjectFromJson(Map json) => Project( name: json['name'] as String? ?? 'test', cost: (json['cost'] as num?)?.toDouble() ?? 0.2, status: $enumDecodeNullable(_$StatusEnumMap, json['status'], - unknownValue: Status.STATUS_0), + unknownValue: Status.status0), ); Map _$ProjectToJson(Project instance) { @@ -30,8 +30,8 @@ Map _$ProjectToJson(Project instance) { } const _$StatusEnumMap = { - Status.STATUS_0: 0, - Status.STATUS_1: 1, - Status.STATUS_2: 2, - Status.STATUS_3: 3, + Status.status0: 0, + Status.status1: 1, + Status.status2: 2, + Status.status3: 3, }; diff --git a/example/lib/model/user/project/project_wrapper.dart b/example/lib/model/user/project/project_wrapper.dart new file mode 100644 index 0000000..27b1859 --- /dev/null +++ b/example/lib/model/user/project/project_wrapper.dart @@ -0,0 +1,51 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +import 'package:flutter/foundation.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:model_generator_example/model/user/project/project.dart'; + +part 'project_wrapper.g.dart'; + +@JsonSerializable(explicitToJson: true) +@immutable +class ProjectWrapper { + @JsonKey(name: 'projectListById', required: true) + final Map> projectListById; + + const ProjectWrapper({ + required this.projectListById, + }); + + factory ProjectWrapper.fromJson(Map json) => + _$ProjectWrapperFromJson(json); + + Map toJson() => _$ProjectWrapperToJson(this); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is ProjectWrapper && + runtimeType == other.runtimeType && + projectListById == other.projectListById; + + @override + int get hashCode => projectListById.hashCode; + + @override + String toString() => 'ProjectWrapper{' + 'projectListById: $projectListById' + '}'; +} + +const deserializeProjectWrapper = ProjectWrapper.fromJson; + +Map serializeProjectWrapper(ProjectWrapper object) => + object.toJson(); + +List deserializeProjectWrapperList( + List> jsonList) => + jsonList.map(ProjectWrapper.fromJson).toList(); + +List> serializeProjectWrapperList( + List objects) => + objects.map((object) => object.toJson()).toList(); diff --git a/example/lib/model/user/project/project_wrapper.g.dart b/example/lib/model/user/project/project_wrapper.g.dart new file mode 100644 index 0000000..f9f5c92 --- /dev/null +++ b/example/lib/model/user/project/project_wrapper.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'project_wrapper.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +ProjectWrapper _$ProjectWrapperFromJson(Map json) { + $checkKeys( + json, + requiredKeys: const ['projectListById'], + ); + return ProjectWrapper( + projectListById: (json['projectListById'] as Map).map( + (k, e) => + MapEntry(k, (e as List).map(Project.fromJson).toList()), + ), + ); +} + +Map _$ProjectWrapperToJson(ProjectWrapper instance) => + { + 'projectListById': instance.projectListById + .map((k, e) => MapEntry(k, e.map((e) => e.toJson()).toList())), + }; diff --git a/example/lib/model/user/project/sub_project.dart b/example/lib/model/user/project/sub_project.dart new file mode 100644 index 0000000..d931c62 --- /dev/null +++ b/example/lib/model/user/project/sub_project.dart @@ -0,0 +1,46 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +import 'package:flutter/foundation.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:model_generator_example/model/status/status.dart'; +import 'package:model_generator_example/model/user/project/project.dart'; + +part 'sub_project.g.dart'; + +@JsonSerializable(explicitToJson: true) +@immutable +class SubProject extends Project { + const SubProject({ + String name = 'test', + double? cost = 0.2, + Status? status, + }) : super( + name: name, + cost: cost, + status: status, + ); + + factory SubProject.fromJson(Map json) => + _$SubProjectFromJson(json); + + @override + Map toJson() => _$SubProjectToJson(this); + + @override + String toString() => 'SubProject{' + 'name: $name, ' + 'cost: $cost, ' + 'status: $status' + '}'; +} + +const deserializeSubProject = SubProject.fromJson; + +Map serializeSubProject(SubProject object) => object.toJson(); + +List deserializeSubProjectList( + List> jsonList) => + jsonList.map(SubProject.fromJson).toList(); + +List> serializeSubProjectList(List objects) => + objects.map((object) => object.toJson()).toList(); diff --git a/example/lib/model/user/project/sub_project.g.dart b/example/lib/model/user/project/sub_project.g.dart new file mode 100644 index 0000000..50c8c68 --- /dev/null +++ b/example/lib/model/user/project/sub_project.g.dart @@ -0,0 +1,37 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'sub_project.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +SubProject _$SubProjectFromJson(Map json) => SubProject( + name: json['name'] as String? ?? 'test', + cost: (json['cost'] as num?)?.toDouble() ?? 0.2, + status: $enumDecodeNullable(_$StatusEnumMap, json['status'], + unknownValue: Status.status0), + ); + +Map _$SubProjectToJson(SubProject instance) { + final val = { + 'name': instance.name, + }; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('cost', instance.cost); + writeNotNull('status', _$StatusEnumMap[instance.status]); + return val; +} + +const _$StatusEnumMap = { + Status.status0: 0, + Status.status1: 1, + Status.status2: 2, + Status.status3: 3, +}; diff --git a/example/lib/model/user/testing.dart b/example/lib/model/user/testing.dart index d99ceed..e6f8e2b 100644 --- a/example/lib/model/user/testing.dart +++ b/example/lib/model/user/testing.dart @@ -8,7 +8,7 @@ part 'testing.g.dart'; @JsonSerializable(explicitToJson: true) class Testing { - @JsonKey(name: 'beneficiary', required: true, includeIfNull: false) + @JsonKey(name: 'beneficiary', required: true) final String beneficiary; @JsonKey( name: 'isFavourite', diff --git a/example/model_generator/config.yaml b/example/model_generator/config.yaml index 820ee21..955b9b5 100644 --- a/example/model_generator/config.yaml +++ b/example/model_generator/config.yaml @@ -30,6 +30,7 @@ OGM: type: List>? simpleFields: List simpleMap: Map? + listMap: Map> Testing: path: user @@ -106,7 +107,7 @@ Person: firstName: type: String gender: - unknown_enum_value: X + unknown_enum_value: x type: Gender Duration: @@ -148,4 +149,16 @@ Project: default_value: 0.2 status: type: Status? - unknown_enum_value: STATUS_0 + unknown_enum_value: status0 + +SubProject: + path: user/project/ + extends: Project + properties: + +ProjectWrapper: + path: user/project/ + type: object + properties: + projectListById: + type: Map> diff --git a/example/model_generator/enums.yaml b/example/model_generator/enums.yaml index c82c919..beed7ea 100644 --- a/example/model_generator/enums.yaml +++ b/example/model_generator/enums.yaml @@ -2,51 +2,76 @@ Gender: path: user/person/ type: enum properties: + value: + type: String + is_json_value: true + values: MALE: - value: _mAl3 + properties: + value: _mAl3 FEMALE: - value: femAle + properties: + value: femAle X: - value: X + properties: + value: X GENDER_X: + properties: + value: gender_x GENDER_Y: - value: - GENDER_z: - value: gender_z - GENDER_abC: - GENDER_def: - value: - GENDER_lap: - value: GENDER_lap + properties: + value: gender_y + GENDER_Z: + properties: + value: gender_z + GENDER_ABC: + properties: + value: gender_abc + GENDER_DEF: + properties: + value: gender_def + GENDER_LAP: + properties: + value: GENDER_lap Status: path: status type: enum - item_type: int - generate_map: true - generate_extensions: true properties: + value: + type: int + is_json_value: true + values: status_0: - value: 0 + properties: + value: 0 status_1: - value: 1 + properties: + value: 1 status_2: - value: 2 + properties: + value: 2 status_3: - value: 3 + properties: + value: 3 DoubleStatus: path: status type: enum - item_type: double - generate_map: true - generate_extensions: true properties: + value: + type: double + is_json_value: true + values: status_0: - value: 0.0 + properties: + value: 0.1 status_1: - value: 1.0 + properties: + value: 1.1 status_2: - value: 2.0 + properties: + value: 2.2 status_3: - value: 3.0 + properties: + value: 3.3 diff --git a/example/tool/model_generator.sh b/example/tool/model_generator.sh new file mode 100755 index 0000000..4b3f793 --- /dev/null +++ b/example/tool/model_generator.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +CURRENT=`pwd` +DIR_NAME=`basename "$CURRENT"` +if [ $DIR_NAME == 'tool' ] +then + cd .. +fi + +fvm flutter packages run model_generator \ No newline at end of file diff --git a/lib/config/pubspec_config.dart b/lib/config/pubspec_config.dart index f5c1990..f151cde 100644 --- a/lib/config/pubspec_config.dart +++ b/lib/config/pubspec_config.dart @@ -50,7 +50,7 @@ class PubspecConfig { explicitToJson = true; generateToString = false; staticCreate = false; - uppercaseEnums = true; + uppercaseEnums = false; retrofitMappers = false; disallowNullForDefaults = false; return; @@ -64,7 +64,7 @@ class PubspecConfig { explicitToJson = (config['explicit_to_json'] ?? true) == true; generateToString = (config['to_string'] ?? false) == true; staticCreate = (config['static_create'] ?? false) == true; - uppercaseEnums = (config['uppercase_enums'] ?? true) == true; + uppercaseEnums = (config['uppercase_enums'] ?? false) == true; retrofitMappers = (config['retrofit_compute'] ?? false) == true; disallowNullForDefaults = (config['disallow_null_for_defaults'] ?? false) == true; diff --git a/lib/config/yml_generator_config.dart b/lib/config/yml_generator_config.dart index c9646e4..0522ad9 100644 --- a/lib/config/yml_generator_config.dart +++ b/lib/config/yml_generator_config.dart @@ -16,6 +16,7 @@ import 'package:model_generator/model/model/enum_model.dart'; import 'package:model_generator/model/model/json_converter_model.dart'; import 'package:model_generator/model/model/model.dart'; import 'package:model_generator/model/model/object_model.dart'; +import 'package:model_generator/util/case_util.dart'; import 'package:model_generator/util/generic_type.dart'; import 'package:model_generator/util/list_extensions.dart'; import 'package:model_generator/util/type_checker.dart'; @@ -89,54 +90,162 @@ class YmlGeneratorConfig { )); return; } - if (properties == null) { - throw Exception('Properties can not be null. model: $key'); - } - if (properties is! YamlMap) { + if (properties is! YamlMap?) { throw Exception( 'Properties should be a map, right now you are using a ${properties.runtimeType}. model: $key'); } if (type == 'enum') { - final uppercaseEnums = - (value['uppercase_enums'] ?? pubspecConfig.uppercaseEnums) == true; - final itemType = value['item_type'] == null - ? const StringType() - : _parseSimpleType(value['item_type']); + final deprecatedItemType = value['item_type']; + if (deprecatedItemType != null) { + throw Exception( + 'item_type is removed, follow the migration to version 7.0.0'); + } - if (itemType is! StringType && - itemType is! IntegerType && - itemType is! DoubleType) { + final deprecatedGenerateMap = value['generate_map']; + if (deprecatedGenerateMap != null) { throw Exception( - 'item_type should be a string or integer. model: $key'); + 'generate_map is removed, follow the migration to version 7.0.0'); } + final uppercaseEnums = + (value['uppercase_enums'] ?? pubspecConfig.uppercaseEnums) == true; + final fields = []; - properties.forEach((propertyKey, propertyValue) { - if (propertyValue != null && propertyValue is! YamlMap) { - throw Exception('$propertyKey should be an object'); + final enumProperties = []; + properties?.forEach((propertyKey, propertyValue) { + final bool isJsonvalue; + final String type; + final String? defaultValue; + final ItemType itemType; + + final String name = propertyKey; + + if (propertyValue is YamlMap) { + final deprecatedValue = propertyValue['value']; + if (deprecatedValue != null) { + throw Exception( + '"value" in model $key on property $name is not longer supported, the way enums are defined has been updated in v7.0.0. Follow the migration'); + } + + if (propertyValue['type'] == null) { + throw Exception( + 'The required key "Type" is required in model $key on property $name'); + } + type = propertyValue['type']; + isJsonvalue = propertyValue['is_json_value'] == true; + defaultValue = propertyValue['default_value']?.toString(); + } else { + type = propertyValue; + isJsonvalue = false; + defaultValue = null; } + + final optional = type.endsWith('?'); + final typeString = + optional ? type.substring(0, type.length - 1) : type; + + itemType = _parseSimpleType(typeString); + + if (itemType is! StringType && + itemType is! DoubleType && + itemType is! IntegerType && + itemType is! BooleanType) { + throw Exception( + '$propertyKey should have a type of integer, boolean, double or string'); + } + + enumProperties.add(EnumProperty( + name: name, + type: itemType, + isJsonvalue: isJsonvalue, + isOptional: optional, + defaultValue: defaultValue, + )); + }); + + final values = value['values']; + if (values == null) { + throw Exception('Values can not be null. model: $key'); + } + + ItemType? simpleType; + + values.forEach((key, value) { + final enumValues = []; + String? description; + if (value is YamlMap) { + final properties = value['properties'] as YamlMap?; + description = value['description']; + + properties?.forEach((key, value) { + enumValues.add( + EnumValue( + value: value.toString(), + propertyName: key, + ), + ); + }); + } else if (value != null) { + final valueType = switch (value.runtimeType) { + int => IntegerType(), + double => DoubleType(), + _ => StringType(), + }; + if (simpleType == null) { + simpleType = valueType; + } else if (simpleType?.name != valueType.name) { + throw Exception( + 'All values in a simple enum declaration should have the same value type, value ${simpleType?.name} is not ${valueType.name}. enum value: $key'); + } + enumValues.add(EnumValue( + value: value.toString(), + propertyName: 'jsonValue', + )); + } + fields.add(EnumField( - name: uppercaseEnums ? propertyKey.toUpperCase() : propertyKey, - rawName: propertyKey, - value: propertyValue == null - ? null - : propertyValue['value'].toString(), - description: - propertyValue == null ? null : propertyValue['description'], + name: uppercaseEnums ? key.toUpperCase() : CaseUtil(key).camelCase, + rawName: key, + values: enumValues, + description: description, )); }); - models.add(EnumModel( + + if (simpleType != null) { + if (enumProperties.isNotEmpty) { + throw Exception( + 'Simple enum declaration only works if no properties are defined, model: $key'); + } + enumProperties.add( + EnumProperty( + name: 'jsonValue', + type: simpleType!, + isOptional: false, + isJsonvalue: true, + ), + ); + } + + final enumModel = EnumModel( + addJsonValueToProperties: value['use_default_json_value'] ?? true, + generateExtension: value['generate_extension'] == true, name: key, path: path, - generateMap: value['generate_map'] == true, - generateExtensions: value['generate_extensions'] == true, baseDirectory: baseDirectory, fields: fields, - itemType: itemType, + properties: enumProperties, extraImports: extraImports, extraAnnotations: extraAnnotations, description: description, - )); + ); + + final error = enumModel.validate(); + + if (error != null) { + throw Exception(error); + } + + models.add(enumModel); } else { final staticCreate = (value['static_create'] ?? false) == true; final disallowNullForDefaults = @@ -144,7 +253,7 @@ class YmlGeneratorConfig { ? (value['disallow_null_for_defaults'] == true) : pubspecConfig.disallowNullForDefaults; final fields = []; - properties.forEach((propertyKey, propertyValue) { + properties?.forEach((propertyKey, propertyValue) { if (propertyValue is YamlMap) { fields.add(getField(propertyKey, propertyValue, disallowNullForDefaults: disallowNullForDefaults)); @@ -227,7 +336,7 @@ class YmlGeneratorConfig { jsonKey: jsonKey, nonFinal: nonFinal, description: description, - includeIfNull: includeIfNull, + includeIfNull: optional ? includeIfNull : true, unknownEnumValue: unknownEnumValue, fromJson: fromJson, toJson: toJson, @@ -254,7 +363,7 @@ class YmlGeneratorConfig { ignore: false, includeToJson: true, includeFromJson: true, - includeIfNull: true, + includeIfNull: optional ? false : true, nonFinal: false, ignoreEquality: false, ); @@ -372,7 +481,7 @@ class YmlGeneratorConfig { ItemType _parseSimpleType(String type) { final listRegex = RegExp(r'^\s*[Ll]ist<\s*([a-zA-Z_0-9<>]*)\s*>\s*$'); final mapRegex = - RegExp(r'^\s*[Mm]ap<([a-zA-Z_0-9]*)\s*,\s*([a-zA-Z_0-9]*)\s*>\s*$'); + RegExp(r'^\s*[Mm]ap<([a-zA-Z_0-9<>]*)\s*,\s*([a-zA-Z_0-9<>]*)\s*>\s*$'); final lowerType = type.toLowerCase(); diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 0000000..6a69b47 --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,140 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:args/args.dart'; +import 'package:model_generator/config/pubspec_config.dart'; +import 'package:model_generator/config/yml_generator_config.dart'; +import 'package:model_generator/model/model/custom_model.dart'; +import 'package:model_generator/model/model/enum_model.dart'; +import 'package:model_generator/model/model/json_converter_model.dart'; +import 'package:model_generator/model/model/object_model.dart'; +import 'package:model_generator/run_process/run_process.dart'; +import 'package:model_generator/writer/enum_model_writer.dart'; +import 'package:model_generator/writer/object_model_writer.dart'; +import 'package:path/path.dart'; + +class ModelGenerator { + ModelGenerator._(); + + static void generate(ArgResults results) async { + final pubspecYaml = File(join(Directory.current.path, 'pubspec.yaml')); + if (!pubspecYaml.existsSync()) { + throw Exception( + 'This program should be run from the root of a flutter/dart project'); + } + final pubspecContent = pubspecYaml.readAsStringSync(); + final pubspecConfig = PubspecConfig(pubspecContent); + + final configPath = results['path'] ?? pubspecConfig.configPath; + String absolutePath; + if (isAbsolute(configPath)) { + absolutePath = configPath; + } else { + absolutePath = join(Directory.current.path, configPath); + } + final FileSystemEntity configEntity; + switch (FileSystemEntity.typeSync(absolutePath)) { + case FileSystemEntityType.directory: + configEntity = Directory(absolutePath); + break; + case FileSystemEntityType.file: + default: + configEntity = File(absolutePath); + break; + } + + if (!configEntity.existsSync()) { + throw Exception( + 'This program requires a config file/dir. `$configPath` does not exist'); + } + final YmlGeneratorConfig modelGeneratorConfig; + if (configEntity is Directory) { + modelGeneratorConfig = + _readConfigFilesInDirectory(pubspecConfig, configEntity, configPath); + } else { + final modelGeneratorContent = (configEntity as File).readAsStringSync(); + modelGeneratorConfig = + YmlGeneratorConfig(pubspecConfig, modelGeneratorContent, configPath); + } + modelGeneratorConfig.checkIfTypesAvailable(); + if (modelGeneratorConfig.models.isEmpty) { + print('No models defined in config files, skipping generation'); + } + + _writeToFiles(pubspecConfig, modelGeneratorConfig); + await _generateJsonGeneratedModels(useFvm: pubspecConfig.useFvm); + print('Done!!!'); + } + + static YmlGeneratorConfig _readConfigFilesInDirectory( + PubspecConfig config, Directory configEntity, String directoryPath) { + final configFiles = configEntity + .listSync(recursive: true) + .whereType() + .where((element) => + extension(element.path) == '.yaml' || + extension(element.path) == '.yml'); + final configs = configFiles.map((e) => + YmlGeneratorConfig(config, e.readAsStringSync(), relative(e.path))); + return YmlGeneratorConfig.merge(configs, directoryPath); + } + + static void _writeToFiles( + PubspecConfig pubspecConfig, YmlGeneratorConfig modelGeneratorConfig) { + for (final model in modelGeneratorConfig.models) { + final modelDirectory = Directory(join('lib', model.baseDirectory)); + if (!modelDirectory.existsSync()) { + modelDirectory.createSync(recursive: true); + } + String? content; + if (model is ObjectModel) { + content = ObjectModelWriter( + pubspecConfig, + model, + modelGeneratorConfig, + ).write(); + } else if (model is EnumModel) { + content = EnumModelWriter(model).write(); + } else if (model is JsonConverterModel) { + continue; + } else if (model is CustomModel) { + continue; + } + if (content == null) { + throw Exception( + 'content is null for ${model.name}. File a bug report on github. This is not normal. https://github.com/icapps/flutter-model-generator/issues'); + } + File file; + if (model.path == null) { + file = File(join('lib', model.baseDirectory, '${model.fileName}.dart')); + } else { + file = File(join( + 'lib', model.baseDirectory, model.path, '${model.fileName}.dart')); + } + if (!file.existsSync()) { + file.createSync(recursive: true); + } + file.writeAsStringSync(content); + } + } + + static Future _generateJsonGeneratedModels( + {required bool useFvm}) async { + final arguments = [ + if (useFvm) ...[ + 'fvm', + ], + 'flutter', + 'packages', + 'pub', + 'run', + 'build_runner', + 'build', + '--delete-conflicting-outputs', + ]; + await ProcessRunner.runProcessVerbose( + arguments.first, + arguments.skip(1).toList(), + ); + } +} diff --git a/lib/model/model/enum_model.dart b/lib/model/model/enum_model.dart index ebea3c1..0f6cecb 100644 --- a/lib/model/model/enum_model.dart +++ b/lib/model/model/enum_model.dart @@ -1,23 +1,26 @@ +import 'package:model_generator/model/item_type/boolean_type.dart'; +import 'package:model_generator/model/item_type/double_type.dart'; +import 'package:model_generator/model/item_type/integer_type.dart'; import 'package:model_generator/model/item_type/item_type.dart'; -import 'package:model_generator/model/item_type/string_type.dart'; import 'package:model_generator/model/model/model.dart'; +import 'package:model_generator/util/list_extensions.dart'; class EnumModel extends Model { - final List? fields; - final ItemType itemType; - final bool generateMap; - final bool generateExtensions; + final List fields; + final List properties; + final bool addJsonValueToProperties; + final bool generateExtension; EnumModel({ required String name, + required this.fields, + required this.properties, + required this.generateExtension, + this.addJsonValueToProperties = true, String? path, String? baseDirectory, - this.fields, - this.itemType = const StringType(), List? extraImports, List? extraAnnotations, - this.generateMap = false, - this.generateExtensions = false, String? description, }) : super( name: name, @@ -27,31 +30,126 @@ class EnumModel extends Model { extraAnnotations: extraAnnotations, description: description, ); + + String? validate() { + for (final property in properties) { + for (final field in fields) { + final value = field.values + .firstWhereOrNull((value) => value.propertyName == property.name) + ?.value; + if (value == null && + !property.isOptional && + property.defaultValue == null && + !property.isJsonvalue) { + return 'There is no value defined for property ${property.name} for the enum value ${field.name} in model $name. Either make this property optional or give it a value'; + } + final toParseValue = value ?? property.defaultValue; + + String? error; + if (toParseValue != null) { + if (property.type is DoubleType) { + error = testValueType( + parser: double.tryParse, + typeName: DoubleType().name, + toParseValue: toParseValue, + propertyName: property.name, + fieldName: field.name, + ); + } else if (property.type is IntegerType) { + error = testValueType( + parser: int.tryParse, + typeName: IntegerType().name, + toParseValue: toParseValue, + propertyName: property.name, + fieldName: field.name, + ); + } else if (property.type is BooleanType) { + error = testValueType( + parser: bool.tryParse, + typeName: BooleanType().name, + toParseValue: toParseValue, + propertyName: property.name, + fieldName: field.name, + ); + } + } + + if (error != null) return error; + } + } + final keyProperty = + properties.firstWhereOrNull((property) => property.isJsonvalue); + if ((!addJsonValueToProperties && keyProperty == null) && + generateExtension) { + return "Model: $name, a json value has to be defined when generating extensions for this model. Either enable a default json value by removing 'use_default_json_value: false' or define a property that is a json value by using 'is_json_value: true'"; + } + return null; + } + + String? testValueType({ + required T? Function(String toParseValue) parser, + required String typeName, + required String toParseValue, + required String propertyName, + required String fieldName, + }) { + final result = parser(toParseValue); + if (result == null) { + return 'Model: $name, Property $propertyName is of type $typeName but the corresponding value on enum value $fieldName is not, make sure they have the same type'; + } + return null; + } } class EnumField { final String name; final String serializedName; - final String? value; final String? description; + final List values; EnumField._({ required this.name, required this.serializedName, - required this.value, - required this.description, + required this.values, + this.description, }); factory EnumField({ required String name, required String rawName, - String? value, + required List values, String? description, }) => EnumField._( name: name, serializedName: rawName, - value: value, + values: values, description: description, ); } + +class EnumProperty { + final bool isJsonvalue; + final bool isOptional; + final String name; + String? defaultValue; + ItemType type; + + EnumProperty({ + required this.name, + required this.type, + required this.isOptional, + this.defaultValue, + this.isJsonvalue = false, + }); +} + +class EnumValue { + final String value; + final String propertyName; + + EnumValue({ + required this.value, + required this.propertyName, + }); +} diff --git a/lib/util/extension/list_extension.dart b/lib/util/extension/list_extension.dart new file mode 100644 index 0000000..2980bc5 --- /dev/null +++ b/lib/util/extension/list_extension.dart @@ -0,0 +1,8 @@ +extension ListExtension on List { + T? firstWhereOrNull(bool Function(T element) test) { + for (var element in this) { + if (test(element)) return element; + } + return null; + } +} diff --git a/lib/writer/enum_model_writer.dart b/lib/writer/enum_model_writer.dart index f90f2d2..1bf6068 100644 --- a/lib/writer/enum_model_writer.dart +++ b/lib/writer/enum_model_writer.dart @@ -2,6 +2,7 @@ import 'package:model_generator/model/item_type/double_type.dart'; import 'package:model_generator/model/item_type/string_type.dart'; import 'package:model_generator/model/model/enum_model.dart'; import 'package:model_generator/util/case_util.dart'; +import 'package:model_generator/util/list_extensions.dart'; import 'package:model_generator/writer/object_model_writer.dart'; class EnumModelWriter { @@ -13,8 +14,13 @@ class EnumModelWriter { final sb = StringBuffer() ..writeln(ObjectModelWriter.autoGeneratedWarning) ..writeln() - ..writeln("import 'package:json_annotation/json_annotation.dart';") - ..writeln(); + ..writeln("import 'package:json_annotation/json_annotation.dart';"); + if (jsonModel.generateExtension) { + sb.writeln( + "import 'package:model_generator/util/extension/list_extension.dart';"); + } + + sb.writeln(); final modelDescription = jsonModel.description?.trim(); if (modelDescription != null && modelDescription.isNotEmpty) { @@ -22,93 +28,116 @@ class EnumModelWriter { } final jsonModelName = CaseUtil(jsonModel.name); - final itemTypeName = CaseUtil(jsonModel.itemType.name); + final properties = jsonModel.properties; + final keyProperty = + properties.firstWhereOrNull((property) => property.isJsonvalue); + final addDefaultJsonKey = + keyProperty == null && jsonModel.addJsonValueToProperties; + final addProperties = properties.isNotEmpty || addDefaultJsonKey; sb.writeln('enum ${jsonModelName.pascalCase} {'); - jsonModel.fields?.forEach((key) { - final jsonValue = key.value == null || key.value?.isEmpty == null - ? key.serializedName - : key.value; - final description = key.description; - if (description != null) { - sb.writeln(' ///$description'); + for (var key in jsonModel.fields) { + final jsonValue = key.values + .firstWhereOrNull( + (value) => value.propertyName == keyProperty?.name) + ?.value ?? + key.serializedName; + final propertyType = keyProperty?.type; + final isLast = + jsonModel.fields.indexOf(key) == (jsonModel.fields.length - 1); + + if (key.description != null) { + sb.writeln(' ///${key.description}'); } - if (jsonModel.itemType is StringType) { + if (propertyType is StringType || propertyType == null) { sb.writeln(' @JsonValue(\'$jsonValue\')'); - } else if (jsonModel.itemType is DoubleType && jsonValue != null) { + } else if (propertyType is DoubleType) { final doubleValue = double.tryParse(jsonValue); sb.writeln(' @JsonValue($doubleValue)'); } else { sb.writeln(' @JsonValue($jsonValue)'); } - sb.writeln(' ${key.name},'); - }); - sb.writeln('}'); + sb.write(' ${key.name}'); - if (jsonModel.generateMap) { - sb - ..writeln() - ..writeln('const ${jsonModelName.camelCase}Mapping = {'); + if (addProperties) { + sb.writeln('('); + if (addDefaultJsonKey) { + sb.writeln(' jsonValue: \'$jsonValue\','); + } + for (var property in properties) { + final enumValue = valueForProperty(property.name, key.values); + var value = enumValue?.value ?? property.defaultValue; - jsonModel.fields?.forEach((key) { - final jsonValue = key.value == null || key.value?.isEmpty == null - ? key.serializedName - : key.value; - sb.write(' ${jsonModelName.pascalCase}.${key.name}: '); - if (jsonModel.itemType is StringType) { - sb.writeln('\'$jsonValue\','); - } else if (jsonModel.itemType is DoubleType && jsonValue != null) { - final doubleValue = double.tryParse(jsonValue); - sb.writeln('$doubleValue,'); + sb.write(' ${property.name}: '); + if (property.type is StringType && + (value != null || property.isJsonvalue)) { + if (value == null && property.isJsonvalue) value = jsonValue; + sb.writeln('\'$value\','); + } else { + sb.writeln('$value,'); + } + } + if (isLast) { + sb.writeln(' );'); } else { - sb.writeln('$jsonValue,'); + sb.writeln(' ),'); } - }); - - sb - ..writeln('};') - ..writeln(); - - if (jsonModel.itemType is DoubleType) { - sb.writeln('final reverse${jsonModelName.pascalCase}Mapping = {'); } else { - sb.writeln('const reverse${jsonModelName.pascalCase}Mapping = {'); + sb.writeln(','); } + } - jsonModel.fields?.forEach((key) { - final jsonValue = key.value == null || key.value?.isEmpty == null - ? key.serializedName - : key.value; - if (jsonModel.itemType is StringType) { - sb.write(' \'$jsonValue\': '); - } else if (jsonModel.itemType is DoubleType && jsonValue != null) { - final doubleValue = double.tryParse(jsonValue); - sb.write(' $doubleValue: '); - } else { - sb.write(' $jsonValue: '); - } - sb.writeln('${jsonModelName.pascalCase}.${key.name},'); - }); - - sb.writeln('};'); + if (addProperties) { + sb.writeln(); + } - if (jsonModel.generateExtensions) { - sb - ..writeln() - ..writeln( - 'extension ${jsonModelName.pascalCase}Extension on ${jsonModelName.pascalCase} {') - ..writeln( - ' ${itemTypeName.originalText} get ${itemTypeName.camelCase}Value => ${jsonModelName.camelCase}Mapping[this]!;') - ..writeln('}') - ..writeln() - ..writeln( - 'extension ${jsonModelName.pascalCase}${itemTypeName.pascalCase}Extension on ${itemTypeName.originalText} {') - ..writeln( - ' ${jsonModelName.pascalCase}? get as${jsonModelName.pascalCase} => reverse${jsonModelName.pascalCase}Mapping[this];') - ..writeln('}'); + if (addDefaultJsonKey) { + sb.writeln(' final String jsonValue;'); + } + for (var property in properties) { + sb.write(' final ${property.type.name}'); + if (property.isOptional) { + sb.write('?'); } + sb.writeln(' ${property.name};'); + } + if (addProperties) { + sb.writeln(); + sb.writeln(' const ${jsonModelName.pascalCase}({'); + if (addDefaultJsonKey) { + sb.writeln(' required this.jsonValue,'); + } + for (var property in properties) { + sb.write(' '); + if (!property.isOptional) { + sb.write('required '); + } + sb.writeln('this.${property.name},'); + } + sb.writeln(' });'); + } + sb.writeln('}'); + + if (jsonModel.generateExtension) { + final jsonValueName = addDefaultJsonKey ? 'jsonValue' : keyProperty?.name; + final jsonValueType = + addDefaultJsonKey ? StringType() : keyProperty?.type; + final name = jsonModelName.pascalCase; + sb.writeln(); + sb.writeln('extension ${name}Extension on $name {'); + sb.writeln( + ' static $name? fromJsonValue(${jsonValueType?.name} value) => $name.values.firstWhereOrNull((enumValue) => enumValue.$jsonValueName == value);'); + sb.writeln(''); + sb.writeln(' ${jsonValueType?.name} toJsonValue() => $jsonValueName;'); + sb.writeln('}'); } return sb.toString(); } + + EnumValue? valueForProperty( + String propertyName, + List values, + ) => + values.firstWhereOrNull((value) => value.propertyName == propertyName); } diff --git a/lib/writer/object_model_writer.dart b/lib/writer/object_model_writer.dart index 41748e3..37d86e2 100644 --- a/lib/writer/object_model_writer.dart +++ b/lib/writer/object_model_writer.dart @@ -104,7 +104,10 @@ class ObjectModelWriter { sb.write(" @JsonKey(name: '${key.serializedName}'"); if (key.isRequired) { if (key.hasDefaultValue) { - sb.write(', required: false, disallowNullValue: ${key.disallowNull}'); + sb.write(', required: false'); + if (key.disallowNull) { + sb.write(', disallowNullValue: ${key.disallowNull}'); + } } else { sb.write(', required: true'); } @@ -152,9 +155,13 @@ class ObjectModelWriter { final anyNonFinal = jsonModel.fields.any((element) => element.nonFinal) || extendsFields.any((element) => element.nonFinal); + sb ..writeln() - ..writeln(' ${anyNonFinal ? '' : 'const '}${jsonModel.name}({'); + ..write(' ${anyNonFinal ? '' : 'const '}${jsonModel.name}('); + if (jsonModel.fields.isNotEmpty || extendsModel != null) { + sb.writeln('{'); + } for (final key in jsonModel.fields .where((key) => (key.isRequired && !key.hasDefaultValue))) { @@ -174,8 +181,11 @@ class ObjectModelWriter { sb.writeln( ' ${_getKeyType(key)} ${key.name}${_fillDefaulValue(key)},'); } + if (jsonModel.fields.isNotEmpty || extendsModel != null) { + sb.write(' }'); + } if (extendsModel != null) { - sb.writeln(' }) : super('); + sb.writeln(') : super('); for (final key in extendsFields) { sb.writeln(' ${key.name}: ${key.name},'); } @@ -184,9 +194,10 @@ class ObjectModelWriter { ..writeln(); } else { sb - ..writeln(' });') + ..writeln(');') ..writeln(); } + if (jsonModel.generateForGenerics) { sb.writeln( ' factory ${jsonModel.name}.fromJson(Object? json) => _\$${jsonModel.name}FromJson(json as Map); // ignore: avoid_as'); @@ -209,7 +220,8 @@ class ObjectModelWriter { ' static ${jsonModel.name} create(${jsonModel.generateForGenerics ? 'Object? json' : 'Map json'}) => ${jsonModel.name}.fromJson(json);'); } - if (jsonModel.equalsAndHashCode ?? pubspecConfig.equalsHashCode) { + if ((jsonModel.equalsAndHashCode ?? pubspecConfig.equalsHashCode) && + jsonModel.fields.isNotEmpty) { sb ..writeln() ..writeln(' @override') @@ -241,7 +253,9 @@ class ObjectModelWriter { if (c == 0) sb.write(' 0'); sb.writeln(';'); } - if (jsonModel.generateToString ?? pubspecConfig.generateToString) { + if (jsonModel.generateToString ?? + pubspecConfig.generateToString && + (jsonModel.fields.isNotEmpty || extendsModel != null)) { sb ..writeln() ..writeln(' @override') diff --git a/test/config/yml_generator_config/enum-normal.txt b/test/config/yml_generator_config/enum-normal.txt index d453524..c7a063d 100644 --- a/test/config/yml_generator_config/enum-normal.txt +++ b/test/config/yml_generator_config/enum-normal.txt @@ -7,18 +7,40 @@ User: Gender: type: enum properties: + value: + type: String + is_json_value: true + values: MALE: + properties: + value: male FEMALE: - value: femAle + properties: + value: femAle other: + properties: + value: other X: + properties: + value: x Vehicles: type: enum - uppercase_enums: false + uppercase_enums: true properties: + value: + type: String + is_json_value: true + values: male: + properties: + value: male female: - value: femAle + properties: + value: femAle other: - X: + properties: + value: other + x: + properties: + value: x diff --git a/test/config/yml_generator_config_test.dart b/test/config/yml_generator_config_test.dart index fca5c3b..1fa51d8 100644 --- a/test/config/yml_generator_config_test.dart +++ b/test/config/yml_generator_config_test.dart @@ -129,11 +129,10 @@ void main() { expect(errorMessage, 'Exception: firstName has no defined type'); }); - test('Error no properties', () { + test('no Error no properties', () { final pubspecConfig = PubspecConfig(ConfigTestHelper.getPubspecConfig('normal')); var hasError = false; - var errorMessage = ''; try { YmlGeneratorConfig( pubspecConfig, @@ -143,11 +142,8 @@ void main() { .checkIfTypesAvailable(); } catch (e) { hasError = true; - errorMessage = e.toString(); } - expect(hasError, true); - expect(errorMessage, - 'Exception: Properties can not be null. model: Person'); + expect(hasError, false); }); }); group('Custom', () { @@ -173,58 +169,46 @@ void main() { expect(ymlConfig.models.first is ObjectModel, true); expect(ymlConfig.models[1] is EnumModel, true); expect(ymlConfig.models.last is EnumModel, true); - expect(pubspecConfig.uppercaseEnums, true); + expect(pubspecConfig.uppercaseEnums, false); final enumModel = ymlConfig.models[1] as EnumModel; // ignore: avoid_as final enumModel2 = ymlConfig.models[2] as EnumModel; // ignore: avoid_as expect(enumModel.fields, isNotNull); - expect(enumModel.fields!.length, 4); - expect(enumModel.fields![0].name, 'MALE'); - expect(enumModel.fields![0].serializedName, 'MALE'); - expect(enumModel.fields![0].value, null); - expect(enumModel.fields![1].name, 'FEMALE'); - expect(enumModel.fields![1].serializedName, 'FEMALE'); - expect(enumModel.fields![1].value, 'femAle'); - expect(enumModel.fields![2].name, 'OTHER'); - expect(enumModel.fields![2].serializedName, 'other'); - expect(enumModel.fields![2].value, null); - expect(enumModel.fields![3].name, 'X'); - expect(enumModel.fields![3].serializedName, 'X'); - expect(enumModel.fields![3].value, null); + expect(enumModel.fields.length, 4); + expect(enumModel.fields[0].name, 'male'); + expect(enumModel.fields[0].serializedName, 'MALE'); + expect(enumModel.fields[0].values[0].value, 'male'); + expect(enumModel.fields[0].values[0].propertyName, 'value'); + expect(enumModel.fields[1].name, 'female'); + expect(enumModel.fields[1].serializedName, 'FEMALE'); + expect(enumModel.fields[1].values[0].value, 'femAle'); + expect(enumModel.fields[1].values[0].propertyName, 'value'); + expect(enumModel.fields[2].name, 'other'); + expect(enumModel.fields[2].serializedName, 'other'); + expect(enumModel.fields[2].values[0].value, 'other'); + expect(enumModel.fields[2].values[0].propertyName, 'value'); + expect(enumModel.fields[3].name, 'x'); + expect(enumModel.fields[3].serializedName, 'X'); + expect(enumModel.fields[3].values[0].value, 'x'); + expect(enumModel.fields[3].values[0].propertyName, 'value'); expect(enumModel2.fields, isNotNull); - expect(enumModel2.fields!.length, 4); - expect(enumModel2.fields![0].name, 'male'); - expect(enumModel2.fields![0].serializedName, 'male'); - expect(enumModel2.fields![0].value, null); - expect(enumModel2.fields![1].name, 'female'); - expect(enumModel2.fields![1].serializedName, 'female'); - expect(enumModel2.fields![1].value, 'femAle'); - expect(enumModel2.fields![2].name, 'other'); - expect(enumModel2.fields![2].serializedName, 'other'); - expect(enumModel2.fields![2].value, null); - expect(enumModel2.fields![3].name, 'X'); - expect(enumModel2.fields![3].serializedName, 'X'); - expect(enumModel2.fields![3].value, null); - }); - - test('Error Enum no properties map', () { - final pubspecConfig = - PubspecConfig(ConfigTestHelper.getPubspecConfig('normal')); - var hasError = false; - var errorMessage = ''; - try { - YmlGeneratorConfig( - pubspecConfig, - ConfigTestHelper.getYmlGeneratorConfig( - 'enum-error-no-object'), - '') - .checkIfTypesAvailable(); - } catch (e) { - hasError = true; - errorMessage = e.toString(); - } - expect(hasError, true); - expect(errorMessage, 'Exception: MALE should be an object'); + expect(enumModel2.fields.length, 4); + expect(enumModel2.fields[0].name, 'MALE'); + expect(enumModel2.fields[0].serializedName, 'male'); + expect(enumModel.fields[0].values[0].value, 'male'); + expect(enumModel.fields[0].values[0].propertyName, 'value'); + expect(enumModel2.fields[1].name, 'FEMALE'); + expect(enumModel2.fields[1].serializedName, 'female'); + expect(enumModel.fields[1].values[0].value, 'femAle'); + expect(enumModel.fields[1].values[0].propertyName, 'value'); + expect(enumModel2.fields[2].name, 'OTHER'); + expect(enumModel2.fields[2].serializedName, 'other'); + expect(enumModel.fields[2].values[0].value, 'other'); + expect(enumModel.fields[2].values[0].propertyName, 'value'); + expect(enumModel2.fields[3].name, 'X'); + expect(enumModel2.fields[3].serializedName, 'x'); + expect(enumModel.fields[3].values[0].value, 'x'); + expect(enumModel.fields[3].values[0].propertyName, 'value'); }); test('Error Enum', () { diff --git a/test/model/model/enum_model_test.dart b/test/model/model/enum_model_test.dart index 0ec0709..9a85801 100644 --- a/test/model/model/enum_model_test.dart +++ b/test/model/model/enum_model_test.dart @@ -6,9 +6,12 @@ void main() { group('Default', () { test('Normal EnumModel', () { final model = EnumModel( + generateExtension: true, name: 'MyEnumModel', path: 'path_to_my_model', baseDirectory: 'base_dir', + fields: [], + properties: [], ); expect(model.name, 'MyEnumModel'); expect(model.path, 'path_to_my_model'); @@ -18,9 +21,12 @@ void main() { group('Custom Path', () { test('Normal Custom Path', () { final model = EnumModel( + generateExtension: true, name: 'MyEnumModel', path: 'path_to_my_model/', baseDirectory: 'base_dir', + fields: [], + properties: [], ); expect(model.name, 'MyEnumModel'); expect(model.path, 'path_to_my_model'); @@ -29,9 +35,12 @@ void main() { test('Normal Custom Base Dir', () { final model = EnumModel( + generateExtension: true, name: 'MyEnumModel', path: 'path_to_my_model', baseDirectory: 'base_dir/', + fields: [], + properties: [], ); expect(model.name, 'MyEnumModel'); expect(model.path, 'path_to_my_model'); @@ -44,32 +53,53 @@ void main() { group('Default', () { test('Normal EnumField', () { final field = EnumField( - name: 'MY_ENUM_VALUE', - rawName: 'MY_ENUM_VALUE', - value: 'MY_ENUM_VALUE'); + name: 'MY_ENUM_VALUE', + rawName: 'MY_ENUM_VALUE', + values: [ + EnumValue( + value: 'MY_ENUM_VALUE', + propertyName: 'MY_PROPERTY_NAME', + ), + ], + ); expect(field.name, 'MY_ENUM_VALUE'); expect(field.serializedName, 'MY_ENUM_VALUE'); - expect(field.value, 'MY_ENUM_VALUE'); + expect(field.values.first.value, 'MY_ENUM_VALUE'); + expect(field.values.first.propertyName, 'MY_PROPERTY_NAME'); }); test('Normal EnumField, lowercased', () { final field = EnumField( - name: 'my_enum_value', - rawName: 'MY_ENUM_VALUE', - value: 'MY_ENUM_VALUE'); + name: 'my_enum_value', + rawName: 'MY_ENUM_VALUE', + values: [ + EnumValue( + value: 'MY_ENUM_VALUE', + propertyName: 'MY_PROPERTY_NAME', + ), + ], + ); expect(field.name, 'my_enum_value'); expect(field.serializedName, 'MY_ENUM_VALUE'); - expect(field.value, 'MY_ENUM_VALUE'); + expect(field.values.first.value, 'MY_ENUM_VALUE'); + expect(field.values.first.propertyName, 'MY_PROPERTY_NAME'); }); }); group('Custom serializedName', () { test('Normal Custom Base Dir', () { final field = EnumField( - name: 'MY_ENUM_VALUE', - rawName: 'my_enum_value', - value: 'my_enum_value'); + name: 'MY_ENUM_VALUE', + rawName: 'my_enum_value', + values: [ + EnumValue( + value: 'MY_ENUM_VALUE', + propertyName: 'MY_PROPERTY_NAME', + ), + ], + ); expect(field.name, 'MY_ENUM_VALUE'); expect(field.serializedName, 'my_enum_value'); - expect(field.value, 'my_enum_value'); + expect(field.values.first.value, 'MY_ENUM_VALUE'); + expect(field.values.first.propertyName, 'MY_PROPERTY_NAME'); }); }); }); diff --git a/test/writer/enum_model_reader_test.dart b/test/writer/enum_model_reader_test.dart new file mode 100644 index 0000000..3bbb867 --- /dev/null +++ b/test/writer/enum_model_reader_test.dart @@ -0,0 +1,394 @@ +import 'package:model_generator/config/pubspec_config.dart'; +import 'package:model_generator/config/yml_generator_config.dart'; +import 'package:model_generator/model/item_type/boolean_type.dart'; +import 'package:model_generator/model/item_type/double_type.dart'; +import 'package:model_generator/model/item_type/integer_type.dart'; +import 'package:model_generator/model/item_type/item_type.dart'; +import 'package:model_generator/model/item_type/string_type.dart'; +import 'package:model_generator/model/model/enum_model.dart'; +import 'package:test/test.dart'; + +void main() { + group('EnumModel reader test', () { + test('Test simple enum', () { + final models = YmlGeneratorConfig( + PubspecConfig("name: test"), + """ +Gender: + path: user/person/ + type: enum + description: this is an enum + values: + MALE: + description: this is a enum of male + FEMALE: + description: this is a enum of female +""", + '') + .models; + + expect(models.length, 1); + final model = models.first; + expect(model is EnumModel, true); + model as EnumModel; + + expect(model.properties, isEmpty); + expect(model.fields.length, 2); + expect(model.addJsonValueToProperties, true); + expect(model.description, 'this is an enum'); + + expect(model.fields[0].description, 'this is a enum of male'); + + expect(model.fields[1].description, 'this is a enum of female'); + }); + + test('Test complex enum', () { + final models = YmlGeneratorConfig( + PubspecConfig("name: test"), + """ +Gender: + path: user/person/ + type: enum + description: this is an enum + use_default_json_value: false + properties: + abbreviation: String + isMale: + type: bool + default_value: true + name: + type: String? + jsonKey: + type: int + is_json_value: true + values: + MALE: + description: this is a enum of male + properties: + abbreviation: m + jsonKey: 1 + FEMALE: + description: this is a enum of female + properties: + abbreviation: f + isMale: false + jsonKey: 2 +""", + '') + .models; + + expect(models.length, 1); + final model = models.first; + expect(model is EnumModel, true); + model as EnumModel; + + expect(model.properties.length, 4); + expect(model.fields.length, 2); + expect(model.addJsonValueToProperties, false); + expect(model.description, 'this is an enum'); + + expect(model.properties[0].type, isA()); + expect(model.properties[3].isOptional, false); + + expect(model.properties[1].defaultValue, 'true'); + expect(model.properties[1].type, isA()); + expect(model.properties[3].isOptional, false); + + expect(model.properties[2].type, isA()); + expect(model.properties[2].isOptional, true); + + expect(model.properties[3].isOptional, false); + expect(model.properties[3].isJsonvalue, true); + expect(model.properties[3].type, isA()); + + expect(model.fields[0].description, 'this is a enum of male'); + expect(model.fields[0].values[0].value, 'm'); + expect(model.fields[0].values[1].value, '1'); + + expect(model.fields[1].description, 'this is a enum of female'); + expect(model.fields[1].values[0].value, 'f'); + expect(model.fields[1].values[1].value, 'false'); + expect(model.fields[1].values[2].value, '2'); + }); + + void testEnumError({ + required String enumYml, + required String expectedError, + }) { + dynamic error; + try { + YmlGeneratorConfig(PubspecConfig("name: test"), enumYml, '').models; + } catch (e) { + error = e; + } + expect(error, isNotNull); + expect(error, isException); + if (error is Exception) { + expect(error.toString(), expectedError); + } + } + + test( + 'Test enum without values', + () => testEnumError( + expectedError: 'Exception: Values can not be null. model: Gender', + enumYml: """ +Gender: + path: user/person/ + type: enum + description: this is an enum +""", + )); + + test( + 'Test enum with unsupported type', + () => testEnumError( + expectedError: + 'Exception: list should have a type of integer, boolean, double or string', + enumYml: """ +Gender: + path: user/person/ + type: enum + description: this is an enum + properties: + list: List + values: + MALE: + description: this is a enum of male + properties: + list: [] + FEMALE: + description: this is a enum of female + properties: + list: [] +""", + )); + void testEnumMissingValueForType({ + required ItemType type, + required String value, + }) { + testEnumError( + expectedError: + 'Exception: There is no value defined for property name for the enum value female in model Gender. Either make this property optional or give it a value', + enumYml: """ +Gender: + path: user/person/ + type: enum + description: this is an enum + properties: + name: ${type.name} + values: + MALE: + description: this is a enum of male + properties: + name: $value + FEMALE: + description: this is a enum of female + properties: +""", + ); + } + + test('Test enum with missing values', () { + testEnumMissingValueForType(type: StringType(), value: "'name'"); + testEnumMissingValueForType(type: IntegerType(), value: '1'); + testEnumMissingValueForType(type: DoubleType(), value: '1.1'); + testEnumMissingValueForType(type: BooleanType(), value: 'true'); + }); + + test( + 'Test enum with incorrect type bool', + () => testEnumError( + expectedError: + 'Exception: Model: Gender, Property isMale is of type bool but the corresponding value on enum value male is not, make sure they have the same type', + enumYml: """ +Gender: + path: user/person/ + type: enum + description: this is an enum + properties: + isMale: bool + values: + MALE: + description: this is a enum of male + properties: + isMale: 1 + FEMALE: + description: this is a enum of female + properties: + isMale: 1 +""", + )); + + test( + 'Test enum with incorrect type integer', + () => testEnumError( + expectedError: + 'Exception: Model: Gender, Property isMale is of type int but the corresponding value on enum value male is not, make sure they have the same type', + enumYml: """ +Gender: + path: user/person/ + type: enum + description: this is an enum + properties: + isMale: int + values: + MALE: + description: this is a enum of male + properties: + isMale: hello + FEMALE: + description: this is a enum of female + properties: + isMale: hello +""", + )); + + test( + 'Test enum with incorrect type double', + () => testEnumError( + expectedError: + 'Exception: Model: Gender, Property isMale is of type double but the corresponding value on enum value male is not, make sure they have the same type', + enumYml: """ +Gender: + path: user/person/ + type: enum + description: this is an enum + properties: + isMale: double + values: + MALE: + description: this is a enum of male + properties: + isMale: hello + FEMALE: + description: this is a enum of female + properties: + isMale: hello +""", + )); + + test( + 'item_type not supported anymore', + () => testEnumError( + expectedError: + 'Exception: item_type is removed, follow the migration to version 7.0.0', + enumYml: """ +Gender: + path: user/person/ + type: enum + item_type: double + values: + MALE: + FEMALE: +""", + )); + test( + 'generate_map not supported anymore', + () => testEnumError( + expectedError: + 'Exception: generate_map is removed, follow the migration to version 7.0.0', + enumYml: """ +Gender: + path: user/person/ + type: enum + generate_map: true + values: + MALE: + FEMALE: +""", + )); + + test( + 'generate extension required jsonValue', + () => testEnumError( + expectedError: + "Exception: Model: DoubleStatus, a json value has to be defined when generating extensions for this model. Either enable a default json value by removing 'use_default_json_value: false' or define a property that is a json value by using 'is_json_value: true'", + enumYml: """ +DoubleStatus: + path: status + type: enum + generate_extension: true + use_default_json_value: false + values: + status_0: + status_1: + status_2: + status_3: +""", + )); + test( + 'Value no longer supported', + () => testEnumError( + expectedError: + 'Exception: "value" in model DoubleStatus on property status_0 is not longer supported, the way enums are defined has been updated in v7.0.0. Follow the migration', + enumYml: """ +DoubleStatus: + path: status + type: enum + properties: + status_0: + value: true + status_1: + value: true + status_2: + value: true + status_3: + value: true +""", + )); + test( + 'type required', + () => testEnumError( + expectedError: + 'Exception: The required key "Type" is required in model Gender on property isMale', + enumYml: """ +Gender: + path: user/person/ + type: enum + properties: + isMale: + is_json_key: true + values: + MALE: + properties: + isMale: hello + FEMALE: + properties: + isMale: hello +""", + )); + test( + 'Simple enum declaration only works with empty properties', + () => testEnumError( + expectedError: + 'Exception: Simple enum declaration only works if no properties are defined, model: Gender', + enumYml: """ +Gender: + path: user/person/ + type: enum + properties: + isMale: + is_json_key: true + type: String + values: + MALE: hello + FEMALE: hello +""", + )); + test( + 'Simple enum declaration only works with String, int and double', + () => testEnumError( + expectedError: + 'Exception: All values in a simple enum declaration should have the same value type, value int is not String. enum value: FEMALE', + enumYml: """ +Gender: + path: user/person/ + type: enum + values: + MALE: 1 + FEMALE: female +""", + )); + }); +} diff --git a/test/writer/enum_model_writer/custom-value-map-extension.txt b/test/writer/enum_model_writer/custom-value-map-extension.txt deleted file mode 100644 index adfc467..0000000 --- a/test/writer/enum_model_writer/custom-value-map-extension.txt +++ /dev/null @@ -1,28 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -import 'package:json_annotation/json_annotation.dart'; - -enum MyEnumModel { - @JsonValue('customValue') - MY_VALUE_1, - @JsonValue('MY_VALUE_2') - MY_VALUE_2, -} - -const myEnumModelMapping = { - MyEnumModel.MY_VALUE_1: 'customValue', - MyEnumModel.MY_VALUE_2: 'MY_VALUE_2', -}; - -const reverseMyEnumModelMapping = { - 'customValue': MyEnumModel.MY_VALUE_1, - 'MY_VALUE_2': MyEnumModel.MY_VALUE_2, -}; - -extension MyEnumModelExtension on MyEnumModel { - String get stringValue => myEnumModelMapping[this]!; -} - -extension MyEnumModelStringExtension on String { - MyEnumModel? get asMyEnumModel => reverseMyEnumModelMapping[this]; -} diff --git a/test/writer/enum_model_writer/custom-value-map.txt b/test/writer/enum_model_writer/custom-value-map.txt deleted file mode 100644 index dfffeb1..0000000 --- a/test/writer/enum_model_writer/custom-value-map.txt +++ /dev/null @@ -1,20 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -import 'package:json_annotation/json_annotation.dart'; - -enum MyEnumModel { - @JsonValue('customValue') - MY_VALUE_1, - @JsonValue('MY_VALUE_2') - MY_VALUE_2, -} - -const myEnumModelMapping = { - MyEnumModel.MY_VALUE_1: 'customValue', - MyEnumModel.MY_VALUE_2: 'MY_VALUE_2', -}; - -const reverseMyEnumModelMapping = { - 'customValue': MyEnumModel.MY_VALUE_1, - 'MY_VALUE_2': MyEnumModel.MY_VALUE_2, -}; diff --git a/test/writer/enum_model_writer/custom-value.txt b/test/writer/enum_model_writer/custom-value.txt deleted file mode 100644 index 102c3f4..0000000 --- a/test/writer/enum_model_writer/custom-value.txt +++ /dev/null @@ -1,10 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -import 'package:json_annotation/json_annotation.dart'; - -enum MyEnumModel { - @JsonValue('MY_VALUE_1') - MY_VALUE_1, - @JsonValue('custom_value_2') - MY_VALUE_2, -} diff --git a/test/writer/enum_model_writer/custom-value/config.txt b/test/writer/enum_model_writer/custom-value/config.txt new file mode 100644 index 0000000..b53f275 --- /dev/null +++ b/test/writer/enum_model_writer/custom-value/config.txt @@ -0,0 +1,20 @@ +Person: + path: test/enum/ + type: enum + properties: + jsonKey: + is_json_value: true + type: int + firstName: String + lastName: String + values: + MAN: + properties: + jsonKey: 1 + firstName: firstName1 + lastName: lastName1 + WOMAN: + properties: + jsonKey: 2 + firstName: firstName2 + lastName: lastName2 diff --git a/test/writer/enum_model_writer/custom-value/output.txt b/test/writer/enum_model_writer/custom-value/output.txt new file mode 100644 index 0000000..5a59d1a --- /dev/null +++ b/test/writer/enum_model_writer/custom-value/output.txt @@ -0,0 +1,28 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +import 'package:json_annotation/json_annotation.dart'; + +enum Person { + @JsonValue(1) + man( + jsonKey: 1, + firstName: 'firstName1', + lastName: 'lastName1', + ), + @JsonValue(2) + woman( + jsonKey: 2, + firstName: 'firstName2', + lastName: 'lastName2', + ); + + final int jsonKey; + final String firstName; + final String lastName; + + const Person({ + required this.jsonKey, + required this.firstName, + required this.lastName, + }); +} diff --git a/test/writer/enum_model_writer/default_values/config.txt b/test/writer/enum_model_writer/default_values/config.txt new file mode 100644 index 0000000..d9b8af8 --- /dev/null +++ b/test/writer/enum_model_writer/default_values/config.txt @@ -0,0 +1,19 @@ +MyEnumModel: + path: test/enum/ + type: enum + properties: + firstName: String + lastName: + type: String + default_value: lastName + values: + MY_VALUE_1: + properties: + firstName: firstName + MY_VALUE_2: + properties: + firstName: firstName + MY_VALUE_3: + properties: + firstName: firstName + lastName: specifiedLastName \ No newline at end of file diff --git a/test/writer/enum_model_writer/default_values/output.txt b/test/writer/enum_model_writer/default_values/output.txt new file mode 100644 index 0000000..a0229f2 --- /dev/null +++ b/test/writer/enum_model_writer/default_values/output.txt @@ -0,0 +1,34 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +import 'package:json_annotation/json_annotation.dart'; + +enum MyEnumModel { + @JsonValue('MY_VALUE_1') + myValue1( + jsonValue: 'MY_VALUE_1', + firstName: 'firstName', + lastName: 'lastName', + ), + @JsonValue('MY_VALUE_2') + myValue2( + jsonValue: 'MY_VALUE_2', + firstName: 'firstName', + lastName: 'lastName', + ), + @JsonValue('MY_VALUE_3') + myValue3( + jsonValue: 'MY_VALUE_3', + firstName: 'firstName', + lastName: 'specifiedLastName', + ); + + final String jsonValue; + final String firstName; + final String lastName; + + const MyEnumModel({ + required this.jsonValue, + required this.firstName, + required this.lastName, + }); +} diff --git a/test/writer/enum_model_writer/description/config.txt b/test/writer/enum_model_writer/description/config.txt new file mode 100644 index 0000000..a3eab7c --- /dev/null +++ b/test/writer/enum_model_writer/description/config.txt @@ -0,0 +1,7 @@ +MyEnumModel: + path: test/enum/ + type: enum + description: A good description of this enum + values: + MY_VALUE_1: + MY_VALUE_2: diff --git a/test/writer/enum_model_writer/normal-description.txt b/test/writer/enum_model_writer/description/output.txt similarity index 54% rename from test/writer/enum_model_writer/normal-description.txt rename to test/writer/enum_model_writer/description/output.txt index f6f33bb..5c96261 100644 --- a/test/writer/enum_model_writer/normal-description.txt +++ b/test/writer/enum_model_writer/description/output.txt @@ -4,9 +4,18 @@ import 'package:json_annotation/json_annotation.dart'; ///A good description of this enum enum MyEnumModel { - ///A good description of this field @JsonValue('MY_VALUE_1') - MY_VALUE_1, + myValue1( + jsonValue: 'MY_VALUE_1', + ), @JsonValue('MY_VALUE_2') - MY_VALUE_2, + myValue2( + jsonValue: 'MY_VALUE_2', + ); + + final String jsonValue; + + const MyEnumModel({ + required this.jsonValue, + }); } diff --git a/test/writer/enum_model_writer/double_type/config.txt b/test/writer/enum_model_writer/double_type/config.txt new file mode 100644 index 0000000..04132a3 --- /dev/null +++ b/test/writer/enum_model_writer/double_type/config.txt @@ -0,0 +1,14 @@ +MyEnumModel: + path: test/enum/ + type: enum + properties: + value: + type: double + is_json_value: true + values: + MY_VALUE_1: + properties: + value: 1.2 + MY_VALUE_2: + properties: + value: 2.2 \ No newline at end of file diff --git a/test/writer/enum_model_writer/double_type/output.txt b/test/writer/enum_model_writer/double_type/output.txt new file mode 100644 index 0000000..89c5beb --- /dev/null +++ b/test/writer/enum_model_writer/double_type/output.txt @@ -0,0 +1,20 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +import 'package:json_annotation/json_annotation.dart'; + +enum MyEnumModel { + @JsonValue(1.2) + myValue1( + value: 1.2, + ), + @JsonValue(2.2) + myValue2( + value: 2.2, + ); + + final double value; + + const MyEnumModel({ + required this.value, + }); +} diff --git a/test/writer/enum_model_writer/field_description/config.txt b/test/writer/enum_model_writer/field_description/config.txt new file mode 100644 index 0000000..df272d2 --- /dev/null +++ b/test/writer/enum_model_writer/field_description/config.txt @@ -0,0 +1,8 @@ +MyEnumModel: + path: test/enum/ + type: enum + values: + MY_VALUE_1: + description: This is value 1 + MY_VALUE_2: + description: This is value 2 \ No newline at end of file diff --git a/test/writer/enum_model_writer/field_description/output.txt b/test/writer/enum_model_writer/field_description/output.txt new file mode 100644 index 0000000..a1082ce --- /dev/null +++ b/test/writer/enum_model_writer/field_description/output.txt @@ -0,0 +1,22 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +import 'package:json_annotation/json_annotation.dart'; + +enum MyEnumModel { + ///This is value 1 + @JsonValue('MY_VALUE_1') + myValue1( + jsonValue: 'MY_VALUE_1', + ), + ///This is value 2 + @JsonValue('MY_VALUE_2') + myValue2( + jsonValue: 'MY_VALUE_2', + ); + + final String jsonValue; + + const MyEnumModel({ + required this.jsonValue, + }); +} diff --git a/test/writer/enum_model_writer/full_enum/config.txt b/test/writer/enum_model_writer/full_enum/config.txt new file mode 100644 index 0000000..b716cb3 --- /dev/null +++ b/test/writer/enum_model_writer/full_enum/config.txt @@ -0,0 +1,35 @@ +Person: + path: test/enum/ + type: enum + description: This is a enum of a person + properties: + jsonKey: + is_json_value: true + type: int + firstName: String + lastName: + type: String + default_value: lastName + age: int? + height: double? + values: + MAN: + description: enum of a man + properties: + jsonKey: 1 + firstName: firstName1 + age: 12 + height: 12.4 + WOMAN: + description: enum of a woman + properties: + jsonKey: 2 + firstName: firstName2 + age: 16 + height: 22 + OTHER: + description: enum of a other + properties: + jsonKey: 3 + firstName: firstName3 + lastName: SpecifiedLastName \ No newline at end of file diff --git a/test/writer/enum_model_writer/full_enum/output.txt b/test/writer/enum_model_writer/full_enum/output.txt new file mode 100644 index 0000000..20ae616 --- /dev/null +++ b/test/writer/enum_model_writer/full_enum/output.txt @@ -0,0 +1,48 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +import 'package:json_annotation/json_annotation.dart'; + +///This is a enum of a person +enum Person { + ///enum of a man + @JsonValue(1) + man( + jsonKey: 1, + firstName: 'firstName1', + lastName: 'lastName', + age: 12, + height: 12.4, + ), + ///enum of a woman + @JsonValue(2) + woman( + jsonKey: 2, + firstName: 'firstName2', + lastName: 'lastName', + age: 16, + height: 22, + ), + ///enum of a other + @JsonValue(3) + other( + jsonKey: 3, + firstName: 'firstName3', + lastName: 'SpecifiedLastName', + age: null, + height: null, + ); + + final int jsonKey; + final String firstName; + final String lastName; + final int? age; + final double? height; + + const Person({ + required this.jsonKey, + required this.firstName, + required this.lastName, + this.age, + this.height, + }); +} diff --git a/test/writer/enum_model_writer/generate_extension_custom_property/config.txt b/test/writer/enum_model_writer/generate_extension_custom_property/config.txt new file mode 100644 index 0000000..6708548 --- /dev/null +++ b/test/writer/enum_model_writer/generate_extension_custom_property/config.txt @@ -0,0 +1,21 @@ +Person: + path: test/enum/ + type: enum + generate_extension: true + properties: + jsonKey: + is_json_value: true + type: int + firstName: String + lastName: String + values: + MAN: + properties: + jsonKey: 1 + firstName: firstName1 + lastName: lastName1 + WOMAN: + properties: + jsonKey: 2 + firstName: firstName2 + lastName: lastName2 diff --git a/test/writer/enum_model_writer/generate_extension_custom_property/output.txt b/test/writer/enum_model_writer/generate_extension_custom_property/output.txt new file mode 100644 index 0000000..19ffd54 --- /dev/null +++ b/test/writer/enum_model_writer/generate_extension_custom_property/output.txt @@ -0,0 +1,35 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +import 'package:json_annotation/json_annotation.dart'; +import 'package:model_generator/util/extension/list_extension.dart'; + +enum Person { + @JsonValue(1) + man( + jsonKey: 1, + firstName: 'firstName1', + lastName: 'lastName1', + ), + @JsonValue(2) + woman( + jsonKey: 2, + firstName: 'firstName2', + lastName: 'lastName2', + ); + + final int jsonKey; + final String firstName; + final String lastName; + + const Person({ + required this.jsonKey, + required this.firstName, + required this.lastName, + }); +} + +extension PersonExtension on Person { + static Person? fromJsonValue(int value) => Person.values.firstWhereOrNull((enumValue) => enumValue.jsonKey == value); + + int toJsonValue() => jsonKey; +} diff --git a/test/writer/enum_model_writer/generate_extension_normal/config.txt b/test/writer/enum_model_writer/generate_extension_normal/config.txt new file mode 100644 index 0000000..385f180 --- /dev/null +++ b/test/writer/enum_model_writer/generate_extension_normal/config.txt @@ -0,0 +1,7 @@ +MyEnumModel: + path: test/enum/ + type: enum + generate_extension: true + values: + MY_VALUE_1: + MY_VALUE_2: \ No newline at end of file diff --git a/test/writer/enum_model_writer/generate_extension_normal/output.txt b/test/writer/enum_model_writer/generate_extension_normal/output.txt new file mode 100644 index 0000000..f21bf4c --- /dev/null +++ b/test/writer/enum_model_writer/generate_extension_normal/output.txt @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +import 'package:json_annotation/json_annotation.dart'; +import 'package:model_generator/util/extension/list_extension.dart'; + +enum MyEnumModel { + @JsonValue('MY_VALUE_1') + myValue1( + jsonValue: 'MY_VALUE_1', + ), + @JsonValue('MY_VALUE_2') + myValue2( + jsonValue: 'MY_VALUE_2', + ); + + final String jsonValue; + + const MyEnumModel({ + required this.jsonValue, + }); +} + +extension MyEnumModelExtension on MyEnumModel { + static MyEnumModel? fromJsonValue(String value) => MyEnumModel.values.firstWhereOrNull((enumValue) => enumValue.jsonValue == value); + + String toJsonValue() => jsonValue; +} diff --git a/test/writer/enum_model_writer/int_type/config.txt b/test/writer/enum_model_writer/int_type/config.txt new file mode 100644 index 0000000..e6412f6 --- /dev/null +++ b/test/writer/enum_model_writer/int_type/config.txt @@ -0,0 +1,14 @@ +MyEnumModel: + path: test/enum/ + type: enum + properties: + value: + type: int + is_json_value: true + values: + MY_VALUE_1: + properties: + value: 1 + MY_VALUE_2: + properties: + value: 2 \ No newline at end of file diff --git a/test/writer/enum_model_writer/normal_with_int_type.txt b/test/writer/enum_model_writer/int_type/output.txt similarity index 52% rename from test/writer/enum_model_writer/normal_with_int_type.txt rename to test/writer/enum_model_writer/int_type/output.txt index e69141c..149346f 100644 --- a/test/writer/enum_model_writer/normal_with_int_type.txt +++ b/test/writer/enum_model_writer/int_type/output.txt @@ -3,9 +3,18 @@ import 'package:json_annotation/json_annotation.dart'; enum MyEnumModel { - ///A good description of this field @JsonValue(1) - MY_VALUE_1, + myValue1( + value: 1, + ), @JsonValue(2) - MY_VALUE_2, + myValue2( + value: 2, + ); + + final int value; + + const MyEnumModel({ + required this.value, + }); } diff --git a/test/writer/enum_model_writer/no-fields.txt b/test/writer/enum_model_writer/no-fields.txt deleted file mode 100644 index 005c481..0000000 --- a/test/writer/enum_model_writer/no-fields.txt +++ /dev/null @@ -1,6 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -import 'package:json_annotation/json_annotation.dart'; - -enum MyEnumModel { -} diff --git a/test/writer/enum_model_writer/normal.txt b/test/writer/enum_model_writer/normal.txt deleted file mode 100644 index 526caef..0000000 --- a/test/writer/enum_model_writer/normal.txt +++ /dev/null @@ -1,11 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -import 'package:json_annotation/json_annotation.dart'; - -enum MyEnumModel { - ///A good description of this field - @JsonValue('MY_VALUE_1') - MY_VALUE_1, - @JsonValue('MY_VALUE_2') - MY_VALUE_2, -} diff --git a/test/writer/enum_model_writer/normal/config.txt b/test/writer/enum_model_writer/normal/config.txt new file mode 100644 index 0000000..428ebce --- /dev/null +++ b/test/writer/enum_model_writer/normal/config.txt @@ -0,0 +1,6 @@ +MyEnumModel: + path: test/enum/ + type: enum + values: + MY_VALUE_1: + MY_VALUE_2: \ No newline at end of file diff --git a/test/writer/enum_model_writer/normal/output.txt b/test/writer/enum_model_writer/normal/output.txt new file mode 100644 index 0000000..6ea8685 --- /dev/null +++ b/test/writer/enum_model_writer/normal/output.txt @@ -0,0 +1,20 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +import 'package:json_annotation/json_annotation.dart'; + +enum MyEnumModel { + @JsonValue('MY_VALUE_1') + myValue1( + jsonValue: 'MY_VALUE_1', + ), + @JsonValue('MY_VALUE_2') + myValue2( + jsonValue: 'MY_VALUE_2', + ); + + final String jsonValue; + + const MyEnumModel({ + required this.jsonValue, + }); +} diff --git a/test/writer/enum_model_writer/normal_no_default_json_key/config.txt b/test/writer/enum_model_writer/normal_no_default_json_key/config.txt new file mode 100644 index 0000000..de6bba7 --- /dev/null +++ b/test/writer/enum_model_writer/normal_no_default_json_key/config.txt @@ -0,0 +1,7 @@ +MyEnumModel: + path: test/enum/ + use_default_json_value: false + type: enum + values: + MY_VALUE_1: + MY_VALUE_2: \ No newline at end of file diff --git a/test/writer/enum_model_writer/null-value.txt b/test/writer/enum_model_writer/normal_no_default_json_key/output.txt similarity index 86% rename from test/writer/enum_model_writer/null-value.txt rename to test/writer/enum_model_writer/normal_no_default_json_key/output.txt index 0b50f98..9ba7c83 100644 --- a/test/writer/enum_model_writer/null-value.txt +++ b/test/writer/enum_model_writer/normal_no_default_json_key/output.txt @@ -4,7 +4,7 @@ import 'package:json_annotation/json_annotation.dart'; enum MyEnumModel { @JsonValue('MY_VALUE_1') - MY_VALUE_1, + myValue1, @JsonValue('MY_VALUE_2') - MY_VALUE_2, + myValue2, } diff --git a/test/writer/enum_model_writer/normal_with_double_type.txt b/test/writer/enum_model_writer/normal_with_double_type.txt deleted file mode 100644 index b4eb47d..0000000 --- a/test/writer/enum_model_writer/normal_with_double_type.txt +++ /dev/null @@ -1,11 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -import 'package:json_annotation/json_annotation.dart'; - -enum MyEnumModel { - ///A good description of this field - @JsonValue(1.0) - MY_VALUE_1, - @JsonValue(2.2) - MY_VALUE_2, -} diff --git a/test/writer/enum_model_writer/normal_with_double_type_map.txt b/test/writer/enum_model_writer/normal_with_double_type_map.txt deleted file mode 100644 index 24687ad..0000000 --- a/test/writer/enum_model_writer/normal_with_double_type_map.txt +++ /dev/null @@ -1,21 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -import 'package:json_annotation/json_annotation.dart'; - -enum MyEnumModel { - ///A good description of this field - @JsonValue(1.0) - MY_VALUE_1, - @JsonValue(2.2) - MY_VALUE_2, -} - -const myEnumModelMapping = { - MyEnumModel.MY_VALUE_1: 1.0, - MyEnumModel.MY_VALUE_2: 2.2, -}; - -final reverseMyEnumModelMapping = { - 1.0: MyEnumModel.MY_VALUE_1, - 2.2: MyEnumModel.MY_VALUE_2, -}; diff --git a/test/writer/enum_model_writer/normal_with_double_type_map_extension.txt b/test/writer/enum_model_writer/normal_with_double_type_map_extension.txt deleted file mode 100644 index 209e34f..0000000 --- a/test/writer/enum_model_writer/normal_with_double_type_map_extension.txt +++ /dev/null @@ -1,29 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -import 'package:json_annotation/json_annotation.dart'; - -enum MyEnumModel { - ///A good description of this field - @JsonValue(1.0) - MY_VALUE_1, - @JsonValue(2.2) - MY_VALUE_2, -} - -const myEnumModelMapping = { - MyEnumModel.MY_VALUE_1: 1.0, - MyEnumModel.MY_VALUE_2: 2.2, -}; - -final reverseMyEnumModelMapping = { - 1.0: MyEnumModel.MY_VALUE_1, - 2.2: MyEnumModel.MY_VALUE_2, -}; - -extension MyEnumModelExtension on MyEnumModel { - double get doubleValue => myEnumModelMapping[this]!; -} - -extension MyEnumModelDoubleExtension on double { - MyEnumModel? get asMyEnumModel => reverseMyEnumModelMapping[this]; -} diff --git a/test/writer/enum_model_writer/normal_with_int_type_map.txt b/test/writer/enum_model_writer/normal_with_int_type_map.txt deleted file mode 100644 index 4f96235..0000000 --- a/test/writer/enum_model_writer/normal_with_int_type_map.txt +++ /dev/null @@ -1,21 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -import 'package:json_annotation/json_annotation.dart'; - -enum MyEnumModel { - ///A good description of this field - @JsonValue(1) - MY_VALUE_1, - @JsonValue(2) - MY_VALUE_2, -} - -const myEnumModelMapping = { - MyEnumModel.MY_VALUE_1: 1, - MyEnumModel.MY_VALUE_2: 2, -}; - -const reverseMyEnumModelMapping = { - 1: MyEnumModel.MY_VALUE_1, - 2: MyEnumModel.MY_VALUE_2, -}; diff --git a/test/writer/enum_model_writer/normal_with_int_type_map_extension.txt b/test/writer/enum_model_writer/normal_with_int_type_map_extension.txt deleted file mode 100644 index 1afd4d5..0000000 --- a/test/writer/enum_model_writer/normal_with_int_type_map_extension.txt +++ /dev/null @@ -1,29 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -import 'package:json_annotation/json_annotation.dart'; - -enum MyEnumModel { - ///A good description of this field - @JsonValue(1) - MY_VALUE_1, - @JsonValue(2) - MY_VALUE_2, -} - -const myEnumModelMapping = { - MyEnumModel.MY_VALUE_1: 1, - MyEnumModel.MY_VALUE_2: 2, -}; - -const reverseMyEnumModelMapping = { - 1: MyEnumModel.MY_VALUE_1, - 2: MyEnumModel.MY_VALUE_2, -}; - -extension MyEnumModelExtension on MyEnumModel { - int get intValue => myEnumModelMapping[this]!; -} - -extension MyEnumModelIntExtension on int { - MyEnumModel? get asMyEnumModel => reverseMyEnumModelMapping[this]; -} diff --git a/test/writer/enum_model_writer/optional_values/config.txt b/test/writer/enum_model_writer/optional_values/config.txt new file mode 100644 index 0000000..ef2f75e --- /dev/null +++ b/test/writer/enum_model_writer/optional_values/config.txt @@ -0,0 +1,15 @@ +MyEnumModel: + path: test/enum/ + type: enum + properties: + firstName: String + lastName: String? + values: + MY_VALUE_1: + properties: + firstName: firstName + lastName: lastName + MY_VALUE_2: + properties: + firstName: firstName + lastName: lastName \ No newline at end of file diff --git a/test/writer/enum_model_writer/optional_values/output.txt b/test/writer/enum_model_writer/optional_values/output.txt new file mode 100644 index 0000000..e6d8401 --- /dev/null +++ b/test/writer/enum_model_writer/optional_values/output.txt @@ -0,0 +1,28 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +import 'package:json_annotation/json_annotation.dart'; + +enum MyEnumModel { + @JsonValue('MY_VALUE_1') + myValue1( + jsonValue: 'MY_VALUE_1', + firstName: 'firstName', + lastName: 'lastName', + ), + @JsonValue('MY_VALUE_2') + myValue2( + jsonValue: 'MY_VALUE_2', + firstName: 'firstName', + lastName: 'lastName', + ); + + final String jsonValue; + final String firstName; + final String? lastName; + + const MyEnumModel({ + required this.jsonValue, + required this.firstName, + this.lastName, + }); +} diff --git a/test/writer/enum_model_writer/optional_values_default/config.txt b/test/writer/enum_model_writer/optional_values_default/config.txt new file mode 100644 index 0000000..94606db --- /dev/null +++ b/test/writer/enum_model_writer/optional_values_default/config.txt @@ -0,0 +1,13 @@ +MyEnumModel: + path: test/enum/ + type: enum + properties: + firstName: String + lastName: String? + values: + MY_VALUE_1: + properties: + firstName: firstName + MY_VALUE_2: + properties: + firstName: firstName \ No newline at end of file diff --git a/test/writer/enum_model_writer/optional_values_default/output.txt b/test/writer/enum_model_writer/optional_values_default/output.txt new file mode 100644 index 0000000..9dd18fe --- /dev/null +++ b/test/writer/enum_model_writer/optional_values_default/output.txt @@ -0,0 +1,28 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +import 'package:json_annotation/json_annotation.dart'; + +enum MyEnumModel { + @JsonValue('MY_VALUE_1') + myValue1( + jsonValue: 'MY_VALUE_1', + firstName: 'firstName', + lastName: null, + ), + @JsonValue('MY_VALUE_2') + myValue2( + jsonValue: 'MY_VALUE_2', + firstName: 'firstName', + lastName: null, + ); + + final String jsonValue; + final String firstName; + final String? lastName; + + const MyEnumModel({ + required this.jsonValue, + required this.firstName, + this.lastName, + }); +} diff --git a/test/writer/enum_model_writer/pubspec.txt b/test/writer/enum_model_writer/pubspec.txt new file mode 100644 index 0000000..95f3114 --- /dev/null +++ b/test/writer/enum_model_writer/pubspec.txt @@ -0,0 +1,4 @@ +name: model_generator_example + +model_generator: + config_path: model_generator/config.yaml diff --git a/test/writer/enum_model_writer/simple_enum_double/config.txt b/test/writer/enum_model_writer/simple_enum_double/config.txt new file mode 100644 index 0000000..801c6dd --- /dev/null +++ b/test/writer/enum_model_writer/simple_enum_double/config.txt @@ -0,0 +1,6 @@ +MyEnumModel: + path: test/enum/ + type: enum + values: + MY_VALUE_1: 1.2 + MY_VALUE_2: 2.2 \ No newline at end of file diff --git a/test/writer/enum_model_writer/simple_enum_double/output.txt b/test/writer/enum_model_writer/simple_enum_double/output.txt new file mode 100644 index 0000000..4d570e4 --- /dev/null +++ b/test/writer/enum_model_writer/simple_enum_double/output.txt @@ -0,0 +1,20 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +import 'package:json_annotation/json_annotation.dart'; + +enum MyEnumModel { + @JsonValue(1.2) + myValue1( + jsonValue: 1.2, + ), + @JsonValue(2.2) + myValue2( + jsonValue: 2.2, + ); + + final double jsonValue; + + const MyEnumModel({ + required this.jsonValue, + }); +} diff --git a/test/writer/enum_model_writer/simple_enum_int/config.txt b/test/writer/enum_model_writer/simple_enum_int/config.txt new file mode 100644 index 0000000..e799631 --- /dev/null +++ b/test/writer/enum_model_writer/simple_enum_int/config.txt @@ -0,0 +1,6 @@ +MyEnumModel: + path: test/enum/ + type: enum + values: + MY_VALUE_1: 1 + MY_VALUE_2: 2 \ No newline at end of file diff --git a/test/writer/enum_model_writer/simple_enum_int/output.txt b/test/writer/enum_model_writer/simple_enum_int/output.txt new file mode 100644 index 0000000..4b294ae --- /dev/null +++ b/test/writer/enum_model_writer/simple_enum_int/output.txt @@ -0,0 +1,20 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +import 'package:json_annotation/json_annotation.dart'; + +enum MyEnumModel { + @JsonValue(1) + myValue1( + jsonValue: 1, + ), + @JsonValue(2) + myValue2( + jsonValue: 2, + ); + + final int jsonValue; + + const MyEnumModel({ + required this.jsonValue, + }); +} diff --git a/test/writer/enum_model_writer/simple_enum_string/config.txt b/test/writer/enum_model_writer/simple_enum_string/config.txt new file mode 100644 index 0000000..c9aa768 --- /dev/null +++ b/test/writer/enum_model_writer/simple_enum_string/config.txt @@ -0,0 +1,6 @@ +MyEnumModel: + path: test/enum/ + type: enum + values: + MY_VALUE_1: my_value1 + MY_VALUE_2: my_value2 \ No newline at end of file diff --git a/test/writer/enum_model_writer/simple_enum_string/output.txt b/test/writer/enum_model_writer/simple_enum_string/output.txt new file mode 100644 index 0000000..c22898a --- /dev/null +++ b/test/writer/enum_model_writer/simple_enum_string/output.txt @@ -0,0 +1,20 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +import 'package:json_annotation/json_annotation.dart'; + +enum MyEnumModel { + @JsonValue('my_value1') + myValue1( + jsonValue: 'my_value1', + ), + @JsonValue('my_value2') + myValue2( + jsonValue: 'my_value2', + ); + + final String jsonValue; + + const MyEnumModel({ + required this.jsonValue, + }); +} diff --git a/test/writer/enum_model_writer/string_type/config.txt b/test/writer/enum_model_writer/string_type/config.txt new file mode 100644 index 0000000..9705a77 --- /dev/null +++ b/test/writer/enum_model_writer/string_type/config.txt @@ -0,0 +1,14 @@ +MyEnumModel: + path: test/enum/ + type: enum + properties: + value: + type: String + is_json_value: true + values: + MY_VALUE_1: + properties: + value: my_value1 + MY_VALUE_2: + properties: + value: my_value2 \ No newline at end of file diff --git a/test/writer/enum_model_writer/string_type/output.txt b/test/writer/enum_model_writer/string_type/output.txt new file mode 100644 index 0000000..da684a6 --- /dev/null +++ b/test/writer/enum_model_writer/string_type/output.txt @@ -0,0 +1,20 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +import 'package:json_annotation/json_annotation.dart'; + +enum MyEnumModel { + @JsonValue('my_value1') + myValue1( + value: 'my_value1', + ), + @JsonValue('my_value2') + myValue2( + value: 'my_value2', + ); + + final String value; + + const MyEnumModel({ + required this.value, + }); +} diff --git a/test/writer/enum_model_writer/use_default_json_value/config.txt b/test/writer/enum_model_writer/use_default_json_value/config.txt new file mode 100644 index 0000000..9d2eee4 --- /dev/null +++ b/test/writer/enum_model_writer/use_default_json_value/config.txt @@ -0,0 +1,14 @@ +DoubleStatus: + path: status + type: enum + properties: + value: + type: String + is_json_value: true + values: + status_0: + properties: + value: customValue + status_1: + status_2: + status_3: diff --git a/test/writer/enum_model_writer/use_default_json_value/output.txt b/test/writer/enum_model_writer/use_default_json_value/output.txt new file mode 100644 index 0000000..1f9a12e --- /dev/null +++ b/test/writer/enum_model_writer/use_default_json_value/output.txt @@ -0,0 +1,28 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +import 'package:json_annotation/json_annotation.dart'; + +enum DoubleStatus { + @JsonValue('customValue') + status0( + value: 'customValue', + ), + @JsonValue('status_1') + status1( + value: 'status_1', + ), + @JsonValue('status_2') + status2( + value: 'status_2', + ), + @JsonValue('status_3') + status3( + value: 'status_3', + ); + + final String value; + + const DoubleStatus({ + required this.value, + }); +} diff --git a/test/writer/enum_model_writer_test.dart b/test/writer/enum_model_writer_test.dart index 98044bc..670863c 100644 --- a/test/writer/enum_model_writer_test.dart +++ b/test/writer/enum_model_writer_test.dart @@ -1,296 +1,41 @@ -import 'package:model_generator/model/item_type/double_type.dart'; -import 'package:model_generator/model/item_type/integer_type.dart'; -import 'package:test/test.dart'; +import 'dart:io'; + import 'package:model_generator/model/model/enum_model.dart'; +import 'package:model_generator/writer/enum_model_writer.dart'; +import 'package:test/test.dart'; -import 'writer_test_helper.dart'; +import 'writer_helper.dart'; void main() { - group('EnumModel', () { - test('Normal EnumModel', () { - final model = EnumModel( - name: 'MyEnumModel', - path: 'path_to_my_model', - baseDirectory: 'base_dir', - fields: [ - EnumField( - name: 'MY_VALUE_1', - rawName: 'MY_VALUE_1', - value: 'MY_VALUE_1', - description: 'A good description of this field', - ), - EnumField( - name: 'MY_VALUE_2', - rawName: 'MY_VALUE_2', - value: 'MY_VALUE_2', - ), - ], - ); - WriterTestHelper.testEnumModelWriter(model, 'normal'); - }); - - test('Normal EnumModel with int type', () { - final model = EnumModel( - name: 'MyEnumModel', - path: 'path_to_my_model', - baseDirectory: 'base_dir', - itemType: IntegerType(), - fields: [ - EnumField( - name: 'MY_VALUE_1', - rawName: 'MY_VALUE_1', - value: '1', - description: 'A good description of this field', - ), - EnumField( - name: 'MY_VALUE_2', - rawName: 'MY_VALUE_2', - value: '2', - ), - ], - ); - WriterTestHelper.testEnumModelWriter(model, 'normal_with_int_type'); - }); - - test('Normal EnumModel with int type generate map', () { - final model = EnumModel( - name: 'MyEnumModel', - path: 'path_to_my_model', - baseDirectory: 'base_dir', - itemType: IntegerType(), - generateMap: true, - fields: [ - EnumField( - name: 'MY_VALUE_1', - rawName: 'MY_VALUE_1', - value: '1', - description: 'A good description of this field', - ), - EnumField( - name: 'MY_VALUE_2', - rawName: 'MY_VALUE_2', - value: '2', - ), - ], - ); - WriterTestHelper.testEnumModelWriter(model, 'normal_with_int_type_map'); - }); - - test('Normal EnumModel with int type generate map extension', () { - final model = EnumModel( - name: 'MyEnumModel', - path: 'path_to_my_model', - baseDirectory: 'base_dir', - itemType: IntegerType(), - generateMap: true, - generateExtensions: true, - fields: [ - EnumField( - name: 'MY_VALUE_1', - rawName: 'MY_VALUE_1', - value: '1', - description: 'A good description of this field', - ), - EnumField( - name: 'MY_VALUE_2', - rawName: 'MY_VALUE_2', - value: '2', - ), - ], - ); - WriterTestHelper.testEnumModelWriter( - model, 'normal_with_int_type_map_extension'); - }); - - test('Normal EnumModel with double type', () { - final model = EnumModel( - name: 'MyEnumModel', - path: 'path_to_my_model', - baseDirectory: 'base_dir', - itemType: DoubleType(), - fields: [ - EnumField( - name: 'MY_VALUE_1', - rawName: 'MY_VALUE_1', - value: '1.0', - description: 'A good description of this field', - ), - EnumField( - name: 'MY_VALUE_2', - rawName: 'MY_VALUE_2', - value: '2.2', - ), - ], - ); - WriterTestHelper.testEnumModelWriter(model, 'normal_with_double_type'); - }); - - test('Normal EnumModel with double type generate map', () { - final model = EnumModel( - name: 'MyEnumModel', - path: 'path_to_my_model', - baseDirectory: 'base_dir', - itemType: DoubleType(), - generateMap: true, - fields: [ - EnumField( - name: 'MY_VALUE_1', - rawName: 'MY_VALUE_1', - value: '1.0', - description: 'A good description of this field', - ), - EnumField( - name: 'MY_VALUE_2', - rawName: 'MY_VALUE_2', - value: '2.2', - ), - ], - ); - WriterTestHelper.testEnumModelWriter( - model, 'normal_with_double_type_map'); - }); - - test('Normal EnumModel with double type generate map extension', () { - final model = EnumModel( - name: 'MyEnumModel', - path: 'path_to_my_model', - baseDirectory: 'base_dir', - itemType: DoubleType(), - generateMap: true, - generateExtensions: true, - fields: [ - EnumField( - name: 'MY_VALUE_1', - rawName: 'MY_VALUE_1', - value: '1.0', - description: 'A good description of this field', - ), - EnumField( - name: 'MY_VALUE_2', - rawName: 'MY_VALUE_2', - value: '2.2', - ), - ], - ); - WriterTestHelper.testEnumModelWriter( - model, 'normal_with_double_type_map_extension'); - }); - - test('Normal EnumModel custom value', () { - final model = EnumModel( - name: 'MyEnumModel', - path: 'path_to_my_model', - baseDirectory: 'base_dir', - fields: [ - EnumField( - name: 'MY_VALUE_1', - rawName: 'MY_VALUE_1', - value: 'MY_VALUE_1', - ), - EnumField( - name: 'MY_VALUE_2', - rawName: 'MY_VALUE_2', - value: 'custom_value_2', - ), - ], - ); - WriterTestHelper.testEnumModelWriter(model, 'custom-value'); - }); - - test('Enum model with null enumfield.value ', () { - final model = EnumModel( - name: 'MyEnumModel', - path: 'path_to_my_model', - baseDirectory: 'base_dir', - fields: [ - EnumField( - name: 'MY_VALUE_1', - rawName: 'MY_VALUE_1', - value: 'MY_VALUE_1', - ), - EnumField( - name: 'MY_VALUE_2', - rawName: 'MY_VALUE_2', - ), - ], - ); - WriterTestHelper.testEnumModelWriter(model, 'null-value'); - }); - - test('EnumModel with no fields ', () { - final model = EnumModel( - name: 'MyEnumModel', - path: 'path_to_my_model', - baseDirectory: 'base_dir', - fields: [], - ); - WriterTestHelper.testEnumModelWriter(model, 'no-fields'); - }); - - test('Custom EnumModel generate map', () { - final model = EnumModel( - name: 'MyEnumModel', - path: 'path_to_my_model', - baseDirectory: 'base_dir', - generateMap: true, - fields: [ - EnumField( - name: 'MY_VALUE_1', - rawName: 'MY_VALUE_1', - value: 'customValue', - ), - EnumField( - name: 'MY_VALUE_2', - rawName: 'MY_VALUE_2', - ), - ], - ); - WriterTestHelper.testEnumModelWriter(model, 'custom-value-map'); - }); - - test('Custom EnumModel generate map extension', () { - final model = EnumModel( - name: 'MyEnumModel', - path: 'path_to_my_model', - baseDirectory: 'base_dir', - generateMap: true, - generateExtensions: true, - fields: [ - EnumField( - name: 'MY_VALUE_1', - rawName: 'MY_VALUE_1', - value: 'customValue', - ), - EnumField( - name: 'MY_VALUE_2', - rawName: 'MY_VALUE_2', - ), - ], - ); - WriterTestHelper.testEnumModelWriter(model, 'custom-value-map-extension'); - }); - - test('Normal EnumModel with description', () { - final model = EnumModel( - name: 'MyEnumModel', - path: 'path_to_my_model', - baseDirectory: 'base_dir', - fields: [ - EnumField( - name: 'MY_VALUE_1', - rawName: 'MY_VALUE_1', - value: 'MY_VALUE_1', - description: 'A good description of this field', - ), - EnumField( - name: 'MY_VALUE_2', - rawName: 'MY_VALUE_2', - value: 'MY_VALUE_2', - ), - ], - description: 'A good description of this enum', - ); - WriterTestHelper.testEnumModelWriter(model, 'normal-description'); - }); + void testEnumModelWriter(String path) { + final result = WriterHelper.prepareWriterTest( + path: path, + pubspecPath: 'test/writer/enum_model_writer', + ); + final jsonModel = result.config.models.first; + if (jsonModel is! EnumModel) { + throw Exception( + 'The first model in the config file must be an object model and will be validated. The model is ${jsonModel.runtimeType}'); + } + + final generateActual = EnumModelWriter(jsonModel).write; + if (result.expected.startsWith('Exception')) { + expect(generateActual, throwsA(isA())); + } else { + expect(generateActual(), result.expected); + } + } + + group('EnumModelWriter test', () { + final directory = Directory('test/writer/enum_model_writer'); + final folders = directory.listSync(); + for (final folder in folders) { + if (folder is Directory) { + test('Folder ${folder.path}', () { + print('Testing folder ${folder.path}'); + testEnumModelWriter(folder.path); + }); + } + } }); } diff --git a/test/writer/model_reader_test.dart b/test/writer/model_reader_test.dart index 7ab06de..d85897a 100644 --- a/test/writer/model_reader_test.dart +++ b/test/writer/model_reader_test.dart @@ -164,6 +164,7 @@ TestModel: simpleStringList: List nullableStringList: List? simpleMap: Map + nestedMap: Map> """, ''); final models = config.models; @@ -181,6 +182,7 @@ TestModel: final simpleStringList = model.fields.getByName("simpleStringList"); final nullableStringList = model.fields.getByName("nullableStringList"); final simpleMap = model.fields.getByName("simpleMap"); + final nestedMap = model.fields.getByName("nestedMap"); expect(simpleStringList.type, isA()); expect(simpleStringList.isRequired, true); @@ -191,6 +193,13 @@ TestModel: expect(simpleMap.type, isA()); expect(simpleMap.isRequired, true); + expect(nestedMap.type, isA()); + if (nestedMap.type is MapType) { + final type = nestedMap.type as MapType; + expect(type.valueName, 'List'); + } + expect(nestedMap.isRequired, true); + expect(error, isNull); }); test('Test simple object reference fields', () { @@ -227,35 +236,6 @@ TestModel2: expect(nullableRef.type, isA()); expect(nullableRef.isRequired, false); }); - - test('Test enum item_type should be string or integer', () { - dynamic error; - try { - YmlGeneratorConfig( - PubspecConfig("name: test"), - """ -Gender: - path: user/person/ - type: enum - item_type: List - properties: - MALE: - value: male - FEMALE: - value: female -""", - '') - .models; - } catch (e) { - error = e; - } - expect(error, isNotNull); - expect(error, isException); - if (error is Exception) { - expect(error.toString(), - 'Exception: item_type should be a string or integer. model: Gender'); - } - }); }); } diff --git a/test/writer/object_model_writer/array/output.txt b/test/writer/object_model_writer/array/output.txt index 604ce9c..2b9ef7f 100644 --- a/test/writer/object_model_writer/array/output.txt +++ b/test/writer/object_model_writer/array/output.txt @@ -6,7 +6,7 @@ part 'person.g.dart'; @JsonSerializable(explicitToJson: true) class Person { - @JsonKey(name: 'info', required: true, includeIfNull: false) + @JsonKey(name: 'info', required: true) final List info; const Person({ diff --git a/test/writer/object_model_writer/custom-from-to/output.txt b/test/writer/object_model_writer/custom-from-to/output.txt index cfed304..f66ea6a 100644 --- a/test/writer/object_model_writer/custom-from-to/output.txt +++ b/test/writer/object_model_writer/custom-from-to/output.txt @@ -7,7 +7,7 @@ part 'person.g.dart'; @JsonSerializable(explicitToJson: true) class Person { - @JsonKey(name: 'time', fromJson: handleTimeFromJson, toJson: handleTimeToJson) + @JsonKey(name: 'time', includeIfNull: false, fromJson: handleTimeFromJson, toJson: handleTimeToJson) final Time? time; const Person({ diff --git a/test/writer/object_model_writer/default-field-required-null-disallowed/output.txt b/test/writer/object_model_writer/default-field-required-null-disallowed/output.txt index e4ba12c..ab50d35 100644 --- a/test/writer/object_model_writer/default-field-required-null-disallowed/output.txt +++ b/test/writer/object_model_writer/default-field-required-null-disallowed/output.txt @@ -7,7 +7,7 @@ part 'person.g.dart'; @JsonSerializable(explicitToJson: true) class Person { ///A good description - @JsonKey(name: 'firstName', required: false, disallowNullValue: true, includeIfNull: false) + @JsonKey(name: 'firstName', required: false, disallowNullValue: true) final String firstName; const Person({ diff --git a/test/writer/object_model_writer/default-field-required/output.txt b/test/writer/object_model_writer/default-field-required/output.txt index c3c1300..4c11921 100644 --- a/test/writer/object_model_writer/default-field-required/output.txt +++ b/test/writer/object_model_writer/default-field-required/output.txt @@ -7,7 +7,7 @@ part 'person.g.dart'; @JsonSerializable(explicitToJson: true) class Person { ///A good description - @JsonKey(name: 'firstName', required: false, disallowNullValue: false, includeIfNull: false) + @JsonKey(name: 'firstName', required: false) final String firstName; const Person({ diff --git a/test/writer/object_model_writer/explicit-to-json-false-pubspec/output.txt b/test/writer/object_model_writer/explicit-to-json-false-pubspec/output.txt index 6471da8..a74903e 100644 --- a/test/writer/object_model_writer/explicit-to-json-false-pubspec/output.txt +++ b/test/writer/object_model_writer/explicit-to-json-false-pubspec/output.txt @@ -6,7 +6,7 @@ part 'person.g.dart'; @JsonSerializable() class Person { - @JsonKey(name: 'firstName') + @JsonKey(name: 'firstName', includeIfNull: false) final String? firstName; const Person({ diff --git a/test/writer/object_model_writer/explicit-to-json-false/output.txt b/test/writer/object_model_writer/explicit-to-json-false/output.txt index 6471da8..a74903e 100644 --- a/test/writer/object_model_writer/explicit-to-json-false/output.txt +++ b/test/writer/object_model_writer/explicit-to-json-false/output.txt @@ -6,7 +6,7 @@ part 'person.g.dart'; @JsonSerializable() class Person { - @JsonKey(name: 'firstName') + @JsonKey(name: 'firstName', includeIfNull: false) final String? firstName; const Person({ diff --git a/test/writer/object_model_writer/extra-imports-on-model/output.txt b/test/writer/object_model_writer/extra-imports-on-model/output.txt index 353f17b..93396fc 100644 --- a/test/writer/object_model_writer/extra-imports-on-model/output.txt +++ b/test/writer/object_model_writer/extra-imports-on-model/output.txt @@ -8,7 +8,7 @@ part 'person.g.dart'; @JsonSerializable(explicitToJson: true) @veryGood class Person { - @JsonKey(name: 'firstName') + @JsonKey(name: 'firstName', includeIfNull: false) final String? firstName; const Person({ diff --git a/test/writer/object_model_writer/extra-imports/output.txt b/test/writer/object_model_writer/extra-imports/output.txt index d20518d..a4daf3b 100644 --- a/test/writer/object_model_writer/extra-imports/output.txt +++ b/test/writer/object_model_writer/extra-imports/output.txt @@ -8,7 +8,7 @@ part 'person.g.dart'; @JsonSerializable(explicitToJson: true) @immutable class Person { - @JsonKey(name: 'firstName') + @JsonKey(name: 'firstName', includeIfNull: false) final String? firstName; const Person({ diff --git a/test/writer/object_model_writer/import_sorting/output.txt b/test/writer/object_model_writer/import_sorting/output.txt index 761a1b6..494bf07 100644 --- a/test/writer/object_model_writer/import_sorting/output.txt +++ b/test/writer/object_model_writer/import_sorting/output.txt @@ -7,7 +7,7 @@ part 'b_model.g.dart'; @JsonSerializable(explicitToJson: true) class BModel { - @JsonKey(name: 'aModel') + @JsonKey(name: 'aModel', includeIfNull: false) final AModel? aModel; const BModel({ diff --git a/test/writer/object_model_writer/list_in_map/config.txt b/test/writer/object_model_writer/list_in_map/config.txt new file mode 100644 index 0000000..5c46021 --- /dev/null +++ b/test/writer/object_model_writer/list_in_map/config.txt @@ -0,0 +1,32 @@ +ProjectWrapper: + path: user/person/ + type: object + properties: + projectByInt: Map> + projectByString: Map> + projectByEnum: Map> + StringsByString: Map> + intsByString: Map> + intsByDateTime: Map> + intsBydynamic: Map> + +Project: + path: user/project/ + type: object + generate_for_generics: true + static_create: true + properties: + name: + type: String + default_value: "'test'" + cost: + type: double? + default_value: 0.2 + +ProjectEnum: + path: user/project + type: enum + values: + smallProject: + mediumProject: + bigProject: \ No newline at end of file diff --git a/test/writer/object_model_writer/list_in_map/output.txt b/test/writer/object_model_writer/list_in_map/output.txt new file mode 100644 index 0000000..5e81d69 --- /dev/null +++ b/test/writer/object_model_writer/list_in_map/output.txt @@ -0,0 +1,40 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +import 'package:json_annotation/json_annotation.dart'; +import 'package:model_generator_example/model/user/project/project.dart'; +import 'package:model_generator_example/model/user/project/project_enum.dart'; + +part 'project_wrapper.g.dart'; + +@JsonSerializable(explicitToJson: true) +class ProjectWrapper { + @JsonKey(name: 'projectByInt', required: true) + final Map> projectByInt; + @JsonKey(name: 'projectByString', required: true) + final Map> projectByString; + @JsonKey(name: 'projectByEnum', required: true) + final Map> projectByEnum; + @JsonKey(name: 'StringsByString', required: true) + final Map> stringsByString; + @JsonKey(name: 'intsByString', required: true) + final Map> intsByString; + @JsonKey(name: 'intsByDateTime', required: true) + final Map> intsByDateTime; + @JsonKey(name: 'intsBydynamic', required: true) + final Map> intsBydynamic; + + const ProjectWrapper({ + required this.projectByInt, + required this.projectByString, + required this.projectByEnum, + required this.stringsByString, + required this.intsByString, + required this.intsByDateTime, + required this.intsBydynamic, + }); + + factory ProjectWrapper.fromJson(Map json) => _$ProjectWrapperFromJson(json); + + Map toJson() => _$ProjectWrapperToJson(this); + +} diff --git a/test/writer/object_model_writer/list_in_map/pubspec.txt b/test/writer/object_model_writer/list_in_map/pubspec.txt new file mode 100644 index 0000000..95f3114 --- /dev/null +++ b/test/writer/object_model_writer/list_in_map/pubspec.txt @@ -0,0 +1,4 @@ +name: model_generator_example + +model_generator: + config_path: model_generator/config.yaml diff --git a/test/writer/object_model_writer/no_fields/config.txt b/test/writer/object_model_writer/no_fields/config.txt new file mode 100644 index 0000000..0c48afa --- /dev/null +++ b/test/writer/object_model_writer/no_fields/config.txt @@ -0,0 +1,4 @@ +Person: + path: user/person/ + type: object + properties: \ No newline at end of file diff --git a/test/writer/object_model_writer/no_fields/output.txt b/test/writer/object_model_writer/no_fields/output.txt new file mode 100644 index 0000000..ad9c207 --- /dev/null +++ b/test/writer/object_model_writer/no_fields/output.txt @@ -0,0 +1,16 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +import 'package:json_annotation/json_annotation.dart'; + +part 'person.g.dart'; + +@JsonSerializable(explicitToJson: true) +class Person { + + const Person(); + + factory Person.fromJson(Map json) => _$PersonFromJson(json); + + Map toJson() => _$PersonToJson(this); + +} diff --git a/test/writer/object_model_writer/no_fields/pubspec.txt b/test/writer/object_model_writer/no_fields/pubspec.txt new file mode 100644 index 0000000..95f3114 --- /dev/null +++ b/test/writer/object_model_writer/no_fields/pubspec.txt @@ -0,0 +1,4 @@ +name: model_generator_example + +model_generator: + config_path: model_generator/config.yaml diff --git a/test/writer/object_model_writer/no_fields_inheritance/config.txt b/test/writer/object_model_writer/no_fields_inheritance/config.txt new file mode 100644 index 0000000..a908982 --- /dev/null +++ b/test/writer/object_model_writer/no_fields_inheritance/config.txt @@ -0,0 +1,14 @@ +Person: + path: user/person/ + type: object + extends: BasePerson + properties: + +BasePerson: + path: user/person/ + type: object + properties: + firstName: + description: A good description + type: String? + default_value: "'test'" diff --git a/test/writer/object_model_writer/no_fields_inheritance/output.txt b/test/writer/object_model_writer/no_fields_inheritance/output.txt new file mode 100644 index 0000000..40c6f84 --- /dev/null +++ b/test/writer/object_model_writer/no_fields_inheritance/output.txt @@ -0,0 +1,22 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +import 'package:json_annotation/json_annotation.dart'; +import 'package:model_generator_example/model/user/person/base_person.dart'; + +part 'person.g.dart'; + +@JsonSerializable(explicitToJson: true) +class Person extends BasePerson { + + const Person({ + String? firstName = 'test', + }) : super( + firstName: firstName, + ); + + factory Person.fromJson(Map json) => _$PersonFromJson(json); + + @override + Map toJson() => _$PersonToJson(this); + +} diff --git a/test/writer/object_model_writer/no_fields_inheritance/pubspec.txt b/test/writer/object_model_writer/no_fields_inheritance/pubspec.txt new file mode 100644 index 0000000..95f3114 --- /dev/null +++ b/test/writer/object_model_writer/no_fields_inheritance/pubspec.txt @@ -0,0 +1,4 @@ +name: model_generator_example + +model_generator: + config_path: model_generator/config.yaml diff --git a/test/writer/object_model_writer/normal-equals-hashcode-some-ignored/output.txt b/test/writer/object_model_writer/normal-equals-hashcode-some-ignored/output.txt index 270cc47..241a599 100644 --- a/test/writer/object_model_writer/normal-equals-hashcode-some-ignored/output.txt +++ b/test/writer/object_model_writer/normal-equals-hashcode-some-ignored/output.txt @@ -8,7 +8,7 @@ part 'person.g.dart'; class Person { @JsonKey(name: 'firstName', includeIfNull: false) final String? firstName; - @JsonKey(name: 'lastName') + @JsonKey(name: 'lastName', includeIfNull: false) final String? lastName; const Person({ diff --git a/test/writer/object_model_writer/normal-to-string/output.txt b/test/writer/object_model_writer/normal-to-string/output.txt index 6b85833..ec98bd9 100644 --- a/test/writer/object_model_writer/normal-to-string/output.txt +++ b/test/writer/object_model_writer/normal-to-string/output.txt @@ -6,7 +6,7 @@ part 'person.g.dart'; @JsonSerializable(explicitToJson: true) class Person { - @JsonKey(name: 'firstName') + @JsonKey(name: 'firstName', includeIfNull: false) final String? firstName; const Person({ diff --git a/test/writer/object_model_writer/nullsafety/output.txt b/test/writer/object_model_writer/nullsafety/output.txt index daea5a5..2bd57d7 100644 --- a/test/writer/object_model_writer/nullsafety/output.txt +++ b/test/writer/object_model_writer/nullsafety/output.txt @@ -8,7 +8,7 @@ part 'person.g.dart'; class Person { @JsonKey(name: 'firstName', required: true) final String firstName; - @JsonKey(name: 'lastName') + @JsonKey(name: 'lastName', includeIfNull: false) final String? lastName; const Person({ diff --git a/test/writer/object_model_writer/sort/output.txt b/test/writer/object_model_writer/sort/output.txt index 02a8458..b96ff33 100644 --- a/test/writer/object_model_writer/sort/output.txt +++ b/test/writer/object_model_writer/sort/output.txt @@ -10,7 +10,7 @@ class Person { final String x; @JsonKey(name: 'a', required: true) final String a; - @JsonKey(name: 'b') + @JsonKey(name: 'b', includeIfNull: false) final String? b; const Person({ diff --git a/test/writer/object_model_writer/to-json-from-json-handler/output.txt b/test/writer/object_model_writer/to-json-from-json-handler/output.txt index 9c31fe6..96843e6 100644 --- a/test/writer/object_model_writer/to-json-from-json-handler/output.txt +++ b/test/writer/object_model_writer/to-json-from-json-handler/output.txt @@ -8,7 +8,7 @@ part 'person.g.dart'; @JsonSerializable(explicitToJson: true) class Person { - @JsonKey(name: 'address', required: true, includeIfNull: false, fromJson: Handler.handleFromJson, toJson: Handler.handleToJson) + @JsonKey(name: 'address', required: true, fromJson: Handler.handleFromJson, toJson: Handler.handleToJson) final Address address; const Person({ diff --git a/test/writer/object_model_writer/to-json-from-json/output.txt b/test/writer/object_model_writer/to-json-from-json/output.txt index 8ae9c03..cf0af9d 100644 --- a/test/writer/object_model_writer/to-json-from-json/output.txt +++ b/test/writer/object_model_writer/to-json-from-json/output.txt @@ -7,7 +7,7 @@ part 'person.g.dart'; @JsonSerializable(explicitToJson: true) class Person { - @JsonKey(name: 'address', required: true, includeIfNull: false, fromJson: handleFromJson, toJson: handleToJson) + @JsonKey(name: 'address', required: true, fromJson: handleFromJson, toJson: handleToJson) final Address address; const Person({ diff --git a/test/writer/object_model_writer/unknown-enum-value/config.txt b/test/writer/object_model_writer/unknown-enum-value/config.txt index 16c3d01..3541051 100644 --- a/test/writer/object_model_writer/unknown-enum-value/config.txt +++ b/test/writer/object_model_writer/unknown-enum-value/config.txt @@ -9,7 +9,7 @@ Person: Gender: path: user/person/ type: enum - properties: + values: MALE: FEMALE: X: \ No newline at end of file diff --git a/test/writer/object_model_writer/unknown-enum-value/output.txt b/test/writer/object_model_writer/unknown-enum-value/output.txt index 3174953..b6260ac 100644 --- a/test/writer/object_model_writer/unknown-enum-value/output.txt +++ b/test/writer/object_model_writer/unknown-enum-value/output.txt @@ -7,7 +7,7 @@ part 'person.g.dart'; @JsonSerializable(explicitToJson: true) class Person { - @JsonKey(name: 'gender', required: true, includeIfNull: false, unknownEnumValue: Gender.X) + @JsonKey(name: 'gender', required: true, unknownEnumValue: Gender.X) final Gender gender; const Person({ diff --git a/test/writer/object_model_writer_test.dart b/test/writer/object_model_writer_test.dart index 33f4a16..60b5e28 100644 --- a/test/writer/object_model_writer_test.dart +++ b/test/writer/object_model_writer_test.dart @@ -1,33 +1,26 @@ import 'dart:io'; -import 'package:model_generator/config/pubspec_config.dart'; -import 'package:model_generator/config/yml_generator_config.dart'; import 'package:model_generator/model/model/object_model.dart'; import 'package:model_generator/writer/object_model_writer.dart'; import 'package:test/test.dart'; +import 'writer_helper.dart'; + void main() { void testObjectModelWriter(String path) { - final file = File('$path/output.txt'); - final pubspecFile = File('$path/pubspec.txt'); - final configFile = File('$path/config.txt'); - final expected = file.readAsStringSync(); - final pubspecContent = pubspecFile.readAsStringSync(); - final configContent = configFile.readAsStringSync(); - final pubspecConfig = PubspecConfig(pubspecContent); - final ymlConfig = YmlGeneratorConfig(pubspecConfig, configContent, ''); - final jsonModel = ymlConfig.models.first; + final result = WriterHelper.prepareWriterTest(path: path); + final jsonModel = result.config.models.first; if (jsonModel is! ObjectModel) { throw Exception( - 'The first model in the config file must be an object model and will be validated. The model is ${ymlConfig.models.first.runtimeType}'); + 'The first model in the config file must be an object model and will be validated. The model is ${jsonModel.runtimeType}'); } final generateActual = - ObjectModelWriter(pubspecConfig, jsonModel, ymlConfig).write; - if (expected.startsWith('Exception')) { + ObjectModelWriter(result.pubspecConfig, jsonModel, result.config).write; + if (result.expected.startsWith('Exception')) { expect(generateActual, throwsA(isA())); } else { - expect(generateActual(), expected); + expect(generateActual(), result.expected); } } diff --git a/test/writer/writer_helper.dart b/test/writer/writer_helper.dart new file mode 100644 index 0000000..a50abb0 --- /dev/null +++ b/test/writer/writer_helper.dart @@ -0,0 +1,32 @@ +import 'dart:io'; + +import 'package:model_generator/config/pubspec_config.dart'; +import 'package:model_generator/config/yml_generator_config.dart'; + +class WriterHelper { + WriterHelper._(); + + static ({ + String expected, + YmlGeneratorConfig config, + PubspecConfig pubspecConfig, + }) prepareWriterTest({ + required String path, + String? pubspecPath, + }) { + final file = File('$path/output.txt'); + final pubspecFile = File('${pubspecPath ?? path}/pubspec.txt'); + final configFile = File('$path/config.txt'); + final expected = file.readAsStringSync(); + final pubspecContent = pubspecFile.readAsStringSync(); + final configContent = configFile.readAsStringSync(); + final pubspecConfig = PubspecConfig(pubspecContent); + final ymlConfig = YmlGeneratorConfig(pubspecConfig, configContent, ''); + + return ( + expected: expected, + config: ymlConfig, + pubspecConfig: pubspecConfig, + ); + } +} diff --git a/test/writer/writer_test_helper.dart b/test/writer/writer_test_helper.dart deleted file mode 100644 index 55fb148..0000000 --- a/test/writer/writer_test_helper.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'dart:io'; - -import 'package:test/test.dart'; -import 'package:model_generator/model/model/enum_model.dart'; -import 'package:model_generator/writer/enum_model_writer.dart'; - -class WriterTestHelper { - static testEnumModelWriter(EnumModel model, String resultFileName) { - print(Directory.current); - final file = File('test/writer/enum_model_writer/$resultFileName.txt'); - final output = file.readAsStringSync(); - final actual = EnumModelWriter(model).write(); - // print(actual); - expect(actual, output); - } -} diff --git a/tool/gen_coverage_report.sh b/tool/gen_coverage_report.sh index be60493..4dfe20f 100755 --- a/tool/gen_coverage_report.sh +++ b/tool/gen_coverage_report.sh @@ -1,6 +1,13 @@ #!/bin/bash -dart run ./tool/travis/test_coverage_helper.dart || exit -1; +CURRENT=`pwd` +DIR_NAME=`basename "$CURRENT"` +if [ $DIR_NAME == 'tool' ] +then + cd .. +fi + +dart run tool/test_coverage_helper.dart || exit -1; flutter test --coverage || exit -1; genhtml coverage/lcov.info -o coverage/html || exit -1; open coverage/html/index.html || exit -1; diff --git a/tool/testLocal.sh b/tool/testLocal.sh index 6b8c407..b3c5c58 100755 --- a/tool/testLocal.sh +++ b/tool/testLocal.sh @@ -1,6 +1,13 @@ #!/bin/bash -dart run ./tool/travis/test_coverage_helper.dart || exit -1; +CURRENT=`pwd` +DIR_NAME=`basename "$CURRENT"` +if [ $DIR_NAME == 'tool' ] +then + cd .. +fi + +dart run ./tool/test_coverage_helper.dart || exit -1; flutter test --coverage || exit -1; genhtml coverage/lcov.info -o coverage/html rm test/coverage_helper_test.dart \ No newline at end of file