lukepighetti / mastodon_dart

Unofficial 🐘 client written in 🎯
https://pub.dev/packages/mastodon_dart
MIT License
28 stars 8 forks source link

Handle parsing exceptions #55

Open abraham opened 1 year ago

abraham commented 1 year ago

I expect API response formats to become more divergent and buggy as more servers and forks of Mastodon popup. Currently if you request a timeline and one posts triggers a parsing exception (like #51) the entire response is lost. I'd like to see a way to get all the successful statuses and a list of exceptions for the failed statuses. This way users can be shown most of the content and the option to report parse issues to developers.

I'm noodling something similar to the following. What do you think?

Pattern 1:

class StatusException {
  final Exception exception;
  final String unparsed;
  final String? id;
  final Uri? uri;
  final String? attribute;

  StatusException({
    required this.exception,
    required this.unparsed,
    this.id,
    this.uri,
    this.attribute,
  });
}

class Response {
  final List<Status> statuses;
  final List<StatusException> exceptions;

  Response({
    required this.statuses,
    this.exceptions = const [],
  });
}

client.partialReponse = true; // some way of enabling partial responses
final Response response = await client.timeline();
lukepighetti commented 1 year ago

That solution sounds good to me.

abraham commented 1 year ago

I've been thinking about an alternative pattern that would maybe be simpler to use. It would return a list of results that would still be in order and each result would contain either success or an exception. This would probably support other endpoints more easily.

Pattern 2:

class ModelException {
  final Exception exception;
  final String unparsed;

  ModelException({
    required this.exception,
    required this.unparsed,
  });
}

class Result<T> {
  final T? data;
  final ModelException? error;

  Result(this.data, this.error);
}

client.safeParse = true;
final List<Result<Status>> response = await client.timeline();

A third pattern I was thinking about was support a builder method where users could support custom parsing/models.

Pattern 3:

var response = await client.timeline(
    modelBuilder: (json) {
      try {
        return Status.fromJson(json);
      } catch (e) {
        return ModelException(
          exception: e as Exception,
          unparsed: json.toString(),
        );
      }
    },
  );

Currently I'm leaning towards pattern 2.

lukepighetti commented 1 year ago

I almost suggested pattern 2 because it would be nice to preserve the order, especially giving developers the option to show a failed to load widget if they so desire. That said, I don't really like any of Dart's ways to handle multiple cases for a single value, since we don't have tagged unions etc. I think pattern 2 is the cleanest approach that preserves order.