artemis icon indicating copy to clipboard operation
artemis copied to clipboard

How to handle generic Error interface in Union result

Open danilofuchs opened this issue 5 years ago • 1 comments

Our schema looks like this:

type Query {
  courier(code: String!): CourierResult!
}

union CourierResult = Courier | CourierNotFoundError | InternalServerError

type Courier {
  name: String!
  code: String!
  picture: String!
}

interface Error {
  message: String!
}

type InternalServerError implements Error {
  message: String!
}

type CourierNotFoundError implements Error {
  message: String!
}

This schema was inspired by this blog post: https://blog.logrocket.com/handling-graphql-errors-like-a-champ-with-unions-and-interfaces/

When consuming this schema, we use the following query:

query courier($code: String!) {
  courier(code: $code) {
    __typename
    ... on Courier {
      name
      code
      picture
    }
    ... on CourierNotFoundError {
      message
    }
    ... on Error {
      message
    }
  }
}

This way, ... on Error is a catch-all for errors. If we add a new error in the future, backend will return it and even an outdated frontend should be able to handle it as a generic Error until we implement a specific logic for it. For instance, InternalServerError should be treated as Error as the query does not know it in advance, but the API returned it.

What Artemis currently does is the following:

class Courier$Query$CourierResult with EquatableMixin {
  Courier$Query$CourierResult();

  factory Courier$Query$CourierResult.fromJson(Map<String, dynamic> json) {
    switch (json['__typename'].toString()) {
      case r'Courier':
        return Courier$Query$CourierResult$Courier.fromJson(json);
      case r'CourierNotFoundError':
        return Courier$Query$CourierResult$CourierNotFoundError.fromJson(json);
      case r'Error':
        return Courier$Query$CourierResult$Error.fromJson(json);
      default:
    }
    return _$Courier$Query$CourierResultFromJson(json);
  }
  // ...
}

When receiving a __typename: InternalServerError, no case will be matched and it will return a generic Courier$Query$CourierResult which is not convertible to Courier$Query$CourierResult$Error and does not have the message property. This way, we may lose valuable information

What I propose is to add some kind of logic for detecting types which extend an interface, and queries which use interfaces.

I suppose I can manually create a custom parser for this.

Specs Artemis version: 6.15.1-beta.1

build.yaml:
targets:
  $default:
    builders:
      artemis:
        options:
          schema_mapping:
            - output: lib/modules/graphql/api.graphql.dart
              schema: lib/modules/graphql/schema.graphql
              queries_glob: lib/modules/graphql/queries/**.graphql
Artemis output:
# Please paste the output of
$ flutter pub run build_runner build --verbose
#or
$ pub run build_runner build --verbose
GraphQL schema:
type Query {
  courier(code: String!): CourierResult!
}

union CourierResult = Courier | CourierNotFoundError | InternalServerError

type Courier {
  name: String!
  code: String!
  picture: String!
}

interface Error {
  message: String!
}

type InternalServerError implements Error {
  message: String!
}

type CourierNotFoundError implements Error {
  message: String!
}
GraphQL query:
query courier($code: String!) {
  courier(code: $code) {
    __typename
    ... on Courier {
      name
      code
      picture
    }
    ... on CourierNotFoundError {
      message
    }
    ... on Error {
      message
    }
  }
}

danilofuchs avatar Oct 30 '20 14:10 danilofuchs

Hi. Interesting case. May be this could be handled by utilizing default: case... I'll try to create reproduction and think about the fix

vasilich6107 avatar Nov 02 '20 19:11 vasilich6107