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

@UseClass(ExternalClass) annotation to wrap classes as value in the enum #32

Closed passsy closed 4 years ago

passsy commented 4 years ago

fixes #29

By only using @UseClass it is relatively easy to create union types. Pretty cool :)

@superEnum
enum _Result2 {
  @UseClass(MySuccess)
  Success,

  @UseClass(MyError)
  Error,
}

class MySuccess {
  MySuccess(this.fieldA);

  final String fieldA;
}

class MyError {
  MyError(this.fieldA, this.fieldB);

  final String fieldA;
  final int fieldB;
}

Generates

@immutable
abstract class Result2 extends Equatable {
  const Result2(this._type);
  factory Result2.success(MySuccess mySuccess) = MySuccessWrapper;
  factory Result2.error(MyError myError) = MyErrorWrapper;
  final _Result2 _type;
//ignore: missing_return
  FutureOr<R> when<R>(
      {@required FutureOr<R> Function(MySuccess) success,
      @required FutureOr<R> Function(MyError) error}) {
    assert(() {
      if (success == null || error == null)
        throw 'check for all possible cases';
      return true;
    }());
    switch (this._type) {
      case _Result2.Success:
        return success((this as MySuccessWrapper).mySuccess);
      case _Result2.Error:
        return error((this as MyErrorWrapper).myError);
    }
  }
  FutureOr<R> whenOrElse<R>(
      {FutureOr<R> Function(MySuccess) success,
      FutureOr<R> Function(MyError) error,
      @required FutureOr<R> Function(Result2) orElse}) {
    assert(() {
      if (orElse == null) throw 'Missing orElse case';
      return true;
    }());
    switch (this._type) {
      case _Result2.Success:
        if (success == null) break;
        return success((this as MySuccessWrapper).mySuccess);
      case _Result2.Error:
        if (error == null) break;
        return error((this as MyErrorWrapper).myError);
    }
    return orElse(this);
  }
  FutureOr<void> whenPartial(
      {FutureOr<void> Function(MySuccess) success,
      FutureOr<void> Function(MyError) error}) {
    assert(() {
      if (success == null && error == null) throw 'provide at least one branch';
      return true;
    }());
    switch (this._type) {
      case _Result2.Success:
        if (success == null) break;
        return success((this as MySuccessWrapper).mySuccess);
      case _Result2.Error:
        if (error == null) break;
        return error((this as MyErrorWrapper).myError);
    }
  }
  @override
  List get props => const [];
}
@immutable
class MySuccessWrapper extends Result2 {
  const MySuccessWrapper(this.mySuccess) : super(_Result2.Success);
  final MySuccess mySuccess;
  @override
  String toString() => 'MySuccessWrapper($mySuccess)';
  @override
  List get props => [mySuccess];
}
@immutable
class MyErrorWrapper extends Result2 {
  const MyErrorWrapper(this.myError) : super(_Result2.Error);
  final MyError myError;
  @override
  String toString() => 'MyErrorWrapper($myError)';
  @override
  List get props => [myError];
}

Usage

main() {
  final ResultUnion union = ResultUnion.success(MySuccess("hello"));
  union.when(success: (it) {
    print(it.fieldA);
  }, error: (e) {
    print('${e.fieldA} ${e.fieldB}');
  });
  // prints "hello"
}
passsy commented 4 years ago

CI works now, except for codecov upload where I don't have the rights to upload.

xsahil03x commented 4 years ago

CI works now, except for codecov upload where I don't have the rights to upload.

Yeah, need to fix that CI script :smile: Btw do you mind becoming a collaborator for super_enum?

astralstriker commented 4 years ago

@passsy Have you checked "Revivable" objects? It may be possible to completely avoid wrappers and directly recreate the injected classes.

passsy commented 4 years ago

I thought about calling the constructor on my own but it gets hard to pick the right one when a class has multiple.

Also constructors might be private or instances may only be created by a factory or is a singleton. Only the user knows how to create the wrapped objects.

~Getting rid of the Wrapper might be possible with is checks rather than using a switch case on the _type. But it's an implementation detail which an be changed later. (assuming nobody uses if (result is MySuccessWrapper))~

Getting rid of the Wrappers is impossible. The types have to extend the base type. And we can't change what the @UseClass classes extend.

astralstriker commented 4 years ago

But it's an implementation detail which an be changed later. (assuming nobody uses if (result is MySuccessWrapper))

This was my concern. Let us explore a little more and see if we can improve the code and avoid the wrapper situation before merging.

passsy commented 4 years ago

I don't see how we could get rid of the wrapper. The types have to extend the base type. We can't change what the @UseClass classes extend. That's why we need a wrapper so that the returned types always extends the Result type.

xsahil03x commented 4 years ago

@passsy I think we can merge this PR for now. It's a go from my side too.