xsahil03x / super_enum

Create super-powered dart enums similar to sealed classes in Kotlin
https://pub.dev/packages/super_enum
MIT License
116 stars 13 forks source link

[Ready to merge] Added support for List and Map data types #33

Closed astralstriker closed 4 years ago

astralstriker commented 4 years ago

Solves #30

Usage :

@superEnum
enum _TodosResponse {
  @Data(fields: [
    DataField('count', int),
    DataList('todos', String),
  ])
  Success,

  @object
  Error
}

Generated File :

@immutable
abstract class TodosResponse extends Equatable {
  const TodosResponse(this._type);

  factory TodosResponse.success(
      {@required int count, @required List<String> todos}) = Success;

  factory TodosResponse.error() = Error;

  final _TodosResponse _type;

//ignore: missing_return
  FutureOr<R> when<R>(
      {@required FutureOr<R> Function(Success) success,
      @required FutureOr<R> Function(Error) error}) {
    assert(() {
      if (success == null || error == null)
        throw 'check for all possible cases';
      return true;
    }());
    switch (this._type) {
      case _TodosResponse.Success:
        return success(this as Success);
      case _TodosResponse.Error:
        return error(this as Error);
    }
  }

  FutureOr<R> whenOrElse<R>(
      {FutureOr<R> Function(Success) success,
      FutureOr<R> Function(Error) error,
      @required FutureOr<R> Function(TodosResponse) orElse}) {
    assert(() {
      if (orElse == null) throw 'Missing orElse case';
      return true;
    }());
    switch (this._type) {
      case _TodosResponse.Success:
        if (success == null) break;
        return success(this as Success);
      case _TodosResponse.Error:
        if (error == null) break;
        return error(this as Error);
    }
    return orElse(this);
  }

  FutureOr<void> whenPartial(
      {FutureOr<void> Function(Success) success,
      FutureOr<void> Function(Error) error}) {
    assert(() {
      if (success == null && error == null) throw 'provide at least one branch';
      return true;
    }());
    switch (this._type) {
      case _TodosResponse.Success:
        if (success == null) break;
        return success(this as Success);
      case _TodosResponse.Error:
        if (error == null) break;
        return error(this as Error);
    }
  }

  @override
  List get props => const [];
}

@immutable
class Success extends TodosResponse {
  const Success({@required this.count, @required this.todos})
      : super(_TodosResponse.Success);

  final int count;

  final List<String> todos;

  @override
  String toString() => 'Success(count:${this.count},todos:${this.todos})';
  @override
  List get props => [count, todos];
}

@immutable
class Error extends TodosResponse {
  const Error._() : super(_TodosResponse.Error);

  factory Error() {
    _instance ??= Error._();
    return _instance;
  }

  static Error _instance;
}
passsy commented 4 years ago

isn't DataField('todos', List<String>), working?

xsahil03x commented 4 years ago

isn't DataField('todos', List<String>), working?

Currently, the dart compiler takes <> as two separate parts. It complains that The operator '<' isn't defined for the class 'Type'. So we thought of creating two new field type for List and Map each.

astralstriker commented 4 years ago

Reworked the syntax for providing datafield type. Instead of doing, or rather trying to do in some cases,

DataField('foo1', int),
DataField('foo2', List<int>),
DataField('foo3', BuiltList<int>),
DataField('fooMap', Map<int, String>)

We shall now follow the much better and more comprehensive way -

DataField<int>('foo1'),
DataField<List<int>>('foo2'),
DataField<BuiltList<int>>('foo3'),
DataField<Map<int, String>>('fooMap')

PS: I am yet to push the changes to this PR.

astralstriker commented 4 years ago

The above mentioned syntax allows us to specify the generic types and saves us the effort for creating parsers for specific generic types viz lists, maps etc.

The absolute generics (T) still works the old way -

DataField<Generic>('genericTodo')

As there is no way to make the enum Generic.

astralstriker commented 4 years ago

Example:

@superEnum
enum _TodoResponse {
  @Data(fields: [
    DataField<int>('count'),
    DataField<List<String>>('todos'),
    DataField<Map<String, Object>>('objectMap'),
  ])
  TodoSuccess,

  @Data(fields: [
    DataField<Generic>('todo'),
    DataField<List<Generic>>('todoList'),
    DataField<Map<int, Generic>>('todoMap'),
  ])
  @generic
  GenericTodo,

  @object
  TodoError
}

Generated file:

@immutable
abstract class TodoResponse<T> extends Equatable {
  const TodoResponse(this._type);

  factory TodoResponse.todoSuccess(
      {@required int count,
      @required List<String> todos,
      @required Map<String, Object> objectMap}) = TodoSuccess<T>;

  factory TodoResponse.genericTodo(
      {@required T todo,
      @required List<T> todoList,
      @required Map<int, T> todoMap}) = GenericTodo<T>;

  factory TodoResponse.todoError() = TodoError<T>;

  final _TodoResponse _type;

//ignore: missing_return
  FutureOr<R> when<R>(
      {@required FutureOr<R> Function(TodoSuccess) todoSuccess,
      @required FutureOr<R> Function(GenericTodo) genericTodo,
      @required FutureOr<R> Function(TodoError) todoError}) {
    assert(() {
      if (todoSuccess == null || genericTodo == null || todoError == null) {
        throw 'check for all possible cases';
      }
      return true;
    }());
    switch (this._type) {
      case _TodoResponse.TodoSuccess:
        return todoSuccess(this as TodoSuccess);
      case _TodoResponse.GenericTodo:
        return genericTodo(this as GenericTodo);
      case _TodoResponse.TodoError:
        return todoError(this as TodoError);
    }
  }

  FutureOr<R> whenOrElse<R>(
      {FutureOr<R> Function(TodoSuccess) todoSuccess,
      FutureOr<R> Function(GenericTodo) genericTodo,
      FutureOr<R> Function(TodoError) todoError,
      @required FutureOr<R> Function(TodoResponse) orElse}) {
    assert(() {
      if (orElse == null) {
        throw 'Missing orElse case';
      }
      return true;
    }());
    switch (this._type) {
      case _TodoResponse.TodoSuccess:
        if (todoSuccess == null) break;
        return todoSuccess(this as TodoSuccess);
      case _TodoResponse.GenericTodo:
        if (genericTodo == null) break;
        return genericTodo(this as GenericTodo);
      case _TodoResponse.TodoError:
        if (todoError == null) break;
        return todoError(this as TodoError);
    }
    return orElse(this);
  }

  FutureOr<void> whenPartial(
      {FutureOr<void> Function(TodoSuccess) todoSuccess,
      FutureOr<void> Function(GenericTodo) genericTodo,
      FutureOr<void> Function(TodoError) todoError}) {
    assert(() {
      if (todoSuccess == null && genericTodo == null && todoError == null) {
        throw 'provide at least one branch';
      }
      return true;
    }());
    switch (this._type) {
      case _TodoResponse.TodoSuccess:
        if (todoSuccess == null) break;
        return todoSuccess(this as TodoSuccess);
      case _TodoResponse.GenericTodo:
        if (genericTodo == null) break;
        return genericTodo(this as GenericTodo);
      case _TodoResponse.TodoError:
        if (todoError == null) break;
        return todoError(this as TodoError);
    }
  }

  @override
  List get props => const [];
}

@immutable
class TodoSuccess<T> extends TodoResponse<T> {
  const TodoSuccess(
      {@required this.count, @required this.todos, @required this.objectMap})
      : super(_TodoResponse.TodoSuccess);

  final int count;

  final List<String> todos;

  final Map<String, Object> objectMap;

  @override
  String toString() =>
      'TodoSuccess(count:${this.count},todos:${this.todos},objectMap:${this.objectMap})';
  @override
  List get props => [count, todos, objectMap];
}

@immutable
class GenericTodo<T> extends TodoResponse<T> {
  const GenericTodo(
      {@required this.todo, @required this.todoList, @required this.todoMap})
      : super(_TodoResponse.GenericTodo);

  final T todo;

  final List<T> todoList;

  final Map<int, T> todoMap;

  @override
  String toString() =>
      'GenericTodo(todo:${this.todo},todoList:${this.todoList},todoMap:${this.todoMap})';
  @override
  List get props => [todo, todoList, todoMap];
}

@immutable
class TodoError<T> extends TodoResponse<T> {
  const TodoError._() : super(_TodoResponse.TodoError);

  factory TodoError() {
    _instance ??= TodoError._();
    return _instance;
  }

  static TodoError _instance;
}
astralstriker commented 4 years ago

Commit pushed!

xsahil03x commented 4 years ago

Reworked the syntax for providing datafield type. Instead of doing, or rather trying to do in some cases,

DataField('foo1', int),
DataField('foo2', List<int>),
DataField('foo3', BuiltList<int>),
DataField('fooMap', Map<int, String>)

We shall now follow the much better and more comprehensive way -

DataField<int>('foo1'),
DataField<List<int>>('foo2'),
DataField<BuiltList<int>>('foo3'),
DataField<Map<int, String>>('fooMap')

PS: I am yet to push the changes to this PR.

Loved the syntax :100:

xsahil03x commented 4 years ago

@astralstriker resolve conflicts and push again :+1:

astralstriker commented 4 years ago

Got it

xsahil03x commented 4 years ago

@astralstriker fix the CI checks please

xsahil03x commented 4 years ago

@passsy please review