Skip to content

Add field selection to GQL directive #207

@patrickwall57

Description

@patrickwall57

This a continuation from slack that will hopefully be easier to read. Just for consistency sake, I will define the client as the end user, stitch as stitch, and downstream as a backend service that will be the target of a rest/gql directive.

Imagine the following schema

    extend type Agreement @key(fields: "Id") {
        Id: String @external
        Claims(ClientChannelId: String!): [Claim] @gql(url: "https://{{ schemaConfig.customerClaims.targetUrl }}/graphql",
            fieldName: "claimsByMigrationStatus",
            operationType: Query,
            arguments: {
                migrationStatus: "{exports.MigrationStatus}",
                clientChannelId: "{args.ClientChannelId}"
            }
        )
    }

    type Claim @policy(namespace: "customer", name: "customerclaimsapiget", args: { userRoles: "{jwt.scope.split(\" \")}" }) {
        Id: string
        StartDate: String
        ClientChannelId: String
        ClientAccountId: String
        ServiceRequestId: String

        ContactPoint(ClientChannelId: String!): [ContactPoint] @gql(url: "https://{{ schemaConfig.customerClaims.targetUrl }}/graphql",
            fieldName: "getContactPoints",
            operationType: Query,
            arguments: {
                contactPointSearch: {
                    ClientChannelId: "{args.ClientChannelId}",
                    ClientAccountId: "{source.ClientAccountId}",
                    SearchParameters: {
                        ServiceRequestId: "{source.ServiceRequestId}"
                    },
                    FilterParameters: {
                        ReferenceType: "SRVRQST",
                        ReferenceIdentifier: "{source.ServiceRequestId}"
                    }
                }
            }
        )
    }
    type ContactPoint {
        Id: String
        Type: String
    }

In this case, we will populate some claims objects from a GQL downstream, and then we will make an additional call downstream to populate the ContactPoint type. The ContactPoint query requires the ClientChannelId, ClientAccountId, and ServiceRequestId from the Claim type. The following query will work as expected

      Claims(ClientChannelId: "1E5CA959308202F08CB4005056A72010") {
        Id
        ServiceRequestId
        ClientAccountId
        ContactPoint(ClientChannelId: "1E5CA959308202F08CB4005056A72010") {
          Type
        }
      }

However, this query does not work and returns an error that the inputs to the Claims gql directive are null

      Claims(ClientChannelId: "1E5CA959308202F08CB4005056A72010") {
        Id
        ContactPoint(ClientChannelId: "1E5CA959308202F08CB4005056A72010") {
          Type
        }
      }

This puts a burden on our consumers to know which fields are required in the query in order to make downstream fields resolve correctly. As our schema grows this is becoming complicated for our users.

The way I understand the GQL directive, the selection of fields in the clients query will be passed through to the downstream gql server. In the first example,

client queries for Claim.ServiceRequestId and Claim.ClientAccountId and Claim.Id. Stitch receives the request and queries the downstream for Claim.ServiceRequestId, Claim.ClientAccountId, and Claim.Id. At this point, all three fields are available in stitch's source object and thusly available to child resolvers/directives

In the second example, since the client does not request the ServiceRequestId and ClientAccountId fields, they arent queried from the downstream gql server and thus arent available in stitch's source object. Since they arent in the source object, the ContactPoint resolver fails.

What i am wondering is this - Is there a way that I can hardcode some selection of fields in the GQL directive such that the fields I've hardcoded are merged with the fields from the client query. This would let me ensure that I always populate the fields required by any downstream resolver without the client having to understand that logic. To extend the example above would look something like this.

    extend type Agreement @key(fields: "Id") {
        Id: String @external
        Claims(ClientChannelId: String!): [Claim] @gql(url: "https://{{ schemaConfig.customerClaims.targetUrl }}/graphql",
            fieldName: "claimsByMigrationStatus",
            operationType: Query,
            arguments: {
                migrationStatus: "{exports.MigrationStatus}",
                clientChannelId: "{args.ClientChannelId}"
            },
            query: {
                  ServiceRequestId,
                  ClientAccountId
            }
        )
    }

    type Claim @policy(namespace: "customer", name: "customerclaimsapiget", args: { userRoles: "{jwt.scope.split(\" \")}" }) {
        Id: string
        StartDate: String
        ClientChannelId: String
        ClientAccountId: String
        ServiceRequestId: String

        ContactPoint(ClientChannelId: String!): [ContactPoint] @gql(url: "https://{{ schemaConfig.customerClaims.targetUrl }}/graphql",
            fieldName: "getContactPoints",
            operationType: Query,
            arguments: {
                contactPointSearch: {
                    ClientChannelId: "{args.ClientChannelId}",
                    ClientAccountId: "{source.ClientAccountId}",
                    SearchParameters: {
                        ServiceRequestId: "{source.ServiceRequestId}"
                    },
                    FilterParameters: {
                        ReferenceType: "SRVRQST",
                        ReferenceIdentifier: "{source.ServiceRequestId}"
                    }
                }
            }
        )
    }
    type ContactPoint {
        Id: String
        Type: String
    }

In this proposed example, the ClientAccountId and ServiceRequestId are always queried - regardless of whether or not the client queries them. Because they are queried from the downstream, they are availble in the source, and the ContactPoint resolver will work for both of the client queries i detailed above.

Hopefully this clears up what I was trying to describe.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions