Usage of ParseIDs Middleware in Subscription
maennchen opened this issue · 3 comments
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.
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.
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?
@jcelliott PR welcome for that note!