absinthe-graphql / absinthe_relay

Absinthe support for the Relay framework
MIT License
182 stars 88 forks source link

Usage of ParseIDs Middleware in Subscription #122

Open maennchen opened 6 years ago

maennchen commented 6 years ago

When trying to use the Absinthe.Relay.Node.ParseIDs middleware for arguments in subscriptions, the following error is thrown.

12:50:25.251 [error] Should have halted or suspended middleware
     Started with: #Absinthe.Resolution<[acc: %{Absinthe.Middleware.Async => false, Absinthe.Middleware.Batch => %{input: [], output: %{}}}, adapter: Absinthe.Adapter.LanguageConventions, arguments: %{}, context: %{pubsub: MetisApi.Endpoint}, definition: %Absinthe.Blueprint.Document.Field{alias: nil, argument_data: %{}, arguments: [%Absinthe.Blueprint.Input.Argument{errors: [], flags: %{}, input_value: %Absinthe.Blueprint.Input.Value{data: nil, normalized: nil, raw: %Absinthe.Blueprint.Input.RawValue{content: %Absinthe.Blueprint.Input.Variable{errors: [], flags: %{}, name: "incident", source_location: %Absinthe.Blueprint.Document.SourceLocation{column: nil, line: 2}}}, schema_node: %Absinthe.Type.Scalar{__private__: [], __reference__: %{identifier: :id, location: %{file: "/Users/maennchen/Development/data-quest/metis/api/deps/absinthe/lib/absinthe/type/built_ins/scalars.ex", line: 44}, module: Absinthe.Type.BuiltIns.Scalars}, description: "The `ID` scalar type represents a unique identifier, often used to\nrefetch an object or as key for a cache. The ID type appears in a JSON\nresponse as a String; however, it is not intended to be human-readable.\nWhen expected as an input type, any string (such as `\"4\"`) or integer\n(such as `4`) input value will be accepted as an ID.", identifier: :id, name: "ID", parse: #Function<10.74440055/1 in Absinthe.Type.BuiltIns.Scalars.parse_with/2>, serialize: #Function<7.74440055/1 in Absinthe.Type.BuiltIns.Scalars.__absinthe_type__/1>}}, name: "incident", schema_node: %Absinthe.Type.Argument{__reference__: %{identifier: :incident, location: %{file: "/Users/maennchen/Development/data-quest/metis/api/apps/metis_api/lib/metis_api/schema/incident.ex", line: 66}, module: MetisApi.Schema.Incident}, default_value: nil, deprecation: nil, description: nil, name: "incident", type: :id}, source_location: %Absinthe.Blueprint.Document.SourceLocation{column: nil, line: 2}, value: nil}], complexity: nil, directives: [], errors: [], flags: %{}, name: "incidentUpdated", schema_node: %Absinthe.Type.Field{__private__: [], __reference__: %{identifier: :incident_updated, location: %{file: "/Users/maennchen/Development/data-quest/metis/api/apps/metis_api/lib/metis_api/schema/incident.ex", line: 65}, module: MetisApi.Schema.Incident}, args: %{incident: %Absinthe.Type.Argument{__reference__: %{identifier: :incident, location: %{file: "/Users/maennchen/Development/data-quest/metis/api/apps/metis_api/lib/metis_api/schema/incident.ex", line: 66}, module: MetisApi.Schema.Incident}, default_value: nil, deprecation: nil, description: nil, name: "incident", type: :id}}, complexity: nil, config: &MetisApi.Schema.Incident.SubscriptionConfig.updated/2, default_value: nil, deprecation: nil, description: nil, identifier: :incident_updated, middleware: [{{Absinthe.Relay.Node.ParseIDs, :call}, [incident: :incident]}], name: "incident_updated", triggers: [], type: :incident_edge}, selections: [%Absinthe.Blueprint.Document.Field{alias: nil, argument_data: %{}, arguments: [], complexity: nil, directives: [], errors: [], flags: %{}, name: "cursor", schema_node: %Absinthe.Type.Field{__private__: [], __reference__: %{identifier: :cursor, location: %{file: "/Users/maennchen/Development/data-quest/metis/api/apps/metis_api/lib/metis_api/schema/incident.ex", line: 0}, module: MetisApi.Schema.Incident}, args: %{}, complexity: nil, config: nil, default_value: nil, deprecation: nil, description: "A cursor for use in pagination", identifier: :cursor, middleware: [{Absinthe.Middleware.MapGet, :cursor}], name: "cursor", triggers: [], type: %Absinthe.Type.NonNull{of_type: :string}}, selections: [], source_location: %Absinthe.Blueprint.Document.SourceLocation{column: nil, line: 3}, type_conditions: []}, %Absinthe.Blueprint.Document.Field{alias: nil, argument_data: %{}, arguments: [], complexity: nil, directives: [], errors: [], flags: %{}, name: "node", schema_node: %Absinthe.Type.Field{__private__: [], __reference__: %{identifier: :node, location: %{file: "/Users/maennchen/Development/data-quest/metis/api/apps/metis_api/lib/metis_api/schema/incident.ex", line: 0}, module: MetisApi.Schema.Incident}, args: %{}, complexity: nil, config: nil, default_value: nil, deprecation: nil, description: "The item at the end of the edge", identifier: :node, middleware: [{Absinthe.Middleware.MapGet, :node}], name: "node", triggers: [], type: :incident}, selections: [%Absinthe.Blueprint.Document.Field{alias: nil, argument_data: %{}, arguments: [], complexity: nil, directives: [], errors: [], flags: %{}, name: "id", schema_node: %Absinthe.Type.Field{__private__: [], __reference__: %{identifier: :id, location: %{file: "/Users/maennchen/Development/data-quest/metis/api/apps/metis_api/lib/metis_api/schema/incident.ex", line: 0}, module: MetisApi.Schema.Incident}, args: %{}, complexity: nil, config: nil, default_value: nil, deprecation: nil, description: "The ID of an object", identifier: :id, middleware: [{{Absinthe.Resolution, ...}, #Function<2.20694165/2 in Absinthe.Relay.Node.global_id_resolver/2>}], name: "id", triggers: [], type: %Absinthe.Type.NonNull{...}}, selections: [], source_location: %Absinthe.Blueprint.Document.SourceLocation{column: nil, line: 5}, type_conditions: []}], source_location: %Absinthe.Blueprint.Document.SourceLocation{column: nil, line: 4}, type_conditions: []}], source_location: %Absinthe.Blueprint.Document.SourceLocation{column: nil, line: 2}, type_conditions: []}, errors: [], extensions: %{}, fields_cache: "#fieldscache<...>", fragments: %{}, middleware: [{{Absinthe.Relay.Node.ParseIDs, :call}, [incident: :incident]}], parent_type: %Absinthe.Type.Object{__private__: [], __reference__: %{identifier: :subscription, location: %{file: "/Users/maennchen/Development/data-quest/metis/api/apps/metis_api/lib/metis_api/schema.ex", line: 56}, module: MetisApi.Schema}, description: nil, field_imports: [activity_subscriptions: [], message_subscriptions: [], incident_subscriptions: [], status_group_subscriptions: []], fields: %{activity_created: %Absinthe.Type.Field{__private__: [], __reference__: %{identifier: :activity_created, location: %{file: "/Users/maennchen/Development/data-quest/metis/api/apps/metis_api/lib/metis_api/schema/activity.ex", line: 78}, module: MetisApi.Schema.Activity}, args: %{incident: %Absinthe.Type.Argument{__reference__: %{identifier: :incident, location: %{file: "/Users/maennchen/Development/data-quest/metis/api/apps/metis_api/lib/metis_api/schema/activity.ex", line: 79}, module: MetisApi.Schema.Activity}, default_value: nil, deprecation: nil, description: nil, name: "incident", type: :id}}, complexity: nil, config: &MetisApi.Schema.Activity.SubscriptionConfig.created/2, default_value: nil, deprecation: nil, description: nil, identifier: :activity_created, middleware: [Absinthe.Middleware.PassParent], name: "activity_created", triggers: [], type: :activity_edge}, incident_created: %Absinthe.Type.Field{__private__: [], __reference__: %{identifier: :incident_created, location: %{file: "/Users/maennchen/Development/data-quest/metis/api/apps/metis_api/lib/metis_api/schema/incident.ex", line: 59}, module: MetisApi.Schema.Incident}, args: %{status_group: %Absinthe.Type.Argument{__reference__: %{identifier: :status_group, location: %{file: "/Users/maennchen/Development/data-quest/metis/api/apps/metis_api/lib/metis_api/schema/incident.ex", line: 60}, module: MetisApi.Schema.Incident}, default_value: nil, deprecation: nil, description: nil, name: "status_group", type: :id}}, complexity: nil, config: &MetisApi.Schema.Incident.SubscriptionConfig.created/2, default_value: nil, deprecation: nil, description: nil, identifier: :incident_created, middleware: [Absinthe.Middleware.PassParent], name: "incident_created", triggers: [], type: :incident_edge}, incident_updated: %Absinthe.Type.Field{__private__: [], __reference__: %{identifier: :incident_updated, location: %{file: "/Users/maennchen/Development/data-quest/metis/api/apps/metis_api/lib/metis_api/schema/incident.ex", line: 65}, module: MetisApi.Schema.Incident}, ar (truncated)

If you're interested I could build a PR to solve the issue.

benwilson512 commented 6 years ago

Right, this is a good question.

Here's the issue generally: Right now middleware doesn't work with subscription config at all. The reason is simple: Middleware runs during the resolution phase, but subscription documents don't run resolution on setup, they run resolution on publication.

Your specific error message is a slightly different problem, this happens because you've added middleware to the field but haven't added a resolver. If middleware is added to a field via the middleware macro, then no default resolver is applied. ParseIDs doesn't resolve the field (not its job to) so when the doc is published you get an error.

Possible solutions

Honestly I'm not entirely sure what the best approach here is. Perhaps the best option is to create a middleware like mechanic that operates directly on the AST for stuff like argument transformation, and move ParseIDs to using that.

jcelliott commented 4 years ago

I just ran into this issue as well. It's easy enough to parse the global ID in the config function (or convert to a global ID in the trigger I guess), but it took me a few minutes to understand that the middleware wasn't working like I assumed it would. Perhaps adding a note in the moduledoc for ParseID would be worthwhile?

benwilson512 commented 4 years ago

@jcelliott PR welcome for that note!