Can apollo server return a partial success if one element of an array is invalid? - graphql

Say I have a query that returns an array of Customer objects, each one having an id, name, and email, all of which are non-nullable. My resolver loads the data from some source, maybe a database or maybe a downstream system. Most of the data that comes back is fine, but maybe for some reason we're missing the email address for a single customer.
Currently if my resolver just returns the array of all known customers, including the dodgy one, then the entire operation will fail with something like:
{
"data": {
"customers": null
},
"errors": [{
"message": "Cannot return null for non-nullable field Customer.email"
// More error stuff here
}]
}
I get that the validation is supposed to be strict, but now on my UI it fails to load the entire list of customers just because one of them is invalid. It makes it too easy for a single record to poison the entire database.
What I would like is a way to still return the error, but to also have the data field be populated with all of the other, valid Customer records. Is this possible to do with apollo server? Or do I need to manually validate all of the data before trying to return it from my resolver? That would be quite arduous as my real data structure is much more complex.

From this discussion, it seems there's no way to do exactly what I was asking (filter out the invalid records on the server side).
The right approach to this is to either:
Make the email field nullable, so the server can return every Customer, some of which may have incomplete data; or
Make the elements of the top-level Customer array nullable, so the server can return e.g. [customer1, null, customer3]
Option 1 means that the client gets access to more data on an error, so it could still show the rest of the customer's details even if the email is missing. However it makes the data structure less reliable as now anywhere that uses the email might need to do a null check.
Option 2 is a more aggressive failure, nulling out the entire customer because of a single bad field. But it makes it simpler for the client to filter out bad customer records at the top level, allowing the rest of the frontend code to be confident that every customer has an email address.
On that basis I think I prefer option 2.

You might be interested in this article: https://blog.logrocket.com/handling-graphql-errors-like-a-champ-with-unions-and-interfaces/
It talks about using union types to report result status rather than traditional GQL error reporting. Here's an example of how it might look in your case:
type Customer {
id: ID!
name: String!
email: String!
}
interface CustomerErrorInterface {
message: String!
}
type MissingCustomerEmailError implements CustomerErrorInterface {
id: ID!
}
union CustomerResult = Customer | CustomerErrorInterface
type Query {
getCustomers: [CustomerResult!]!
}
Then, on the client side, your query would look something like this:
query getCustomers() {
__typename
... on Customer {
id
name
email
}
... on CustomerErrorInterface {
message
}
... on MissingCustomerEmailError {
id
}
}
This allows you to maintain type safety and avoid nulling things that shouldn't be nullable, while still reporting meaningful results (both successes and error types) that are associated with the data you do have. Using an error interface rather than a concrete error type in the union allows you to leave the option for reporting new types of errors in the future in a backward-compatible way.
The difference between this and the "default" GQL way of error handling (with the top-level errors output) is that reporting the error in an interface allows still giving meaningful reference data (like the id field) in easy locations rather than needing to parse error messages, and different types of errors can provide different reference data as appropriate. Error interface type handling also better fits with regular GraphQL output type patterns (in my opinion) rather than needing to switch on an enum to know how to handle different types of errors.
Allowing null in the output list may accomplish something similar in this particular case, but it makes it impossible to know which piece of data has an error, especially if the data in question does not correspond directly to a user-supplied input that can be found through the path provided by traditional error handling.

Related

Is it possible to map a subscription parameter to an array at the mutation output?

I have a theoretical question. As I know subscription parameters must exist as a field in the returning type of the mutation. This means that the type of parameter must also match the type of the field in the returning object of the mutation. Am I right? Suppose I get an array with channels ids in the mutation response. I only send one channel id as a parameter in the subscription. Is it possible to map a subscription parameter to an array at the mutation output? If the channel id exists in the array (field channelsIds), the subscription must work. Is it possible to write this logic in the scheme itself, or is it technically impossible?
GraphQL schema:
schema {
mutation: Mutation
subscription: Subscription
}
type Mutation {
testMutation(input: TestMutationInput): TestMutationOutput
}
type TestMutationOutput {
channelsIds: [String!]!
userId: String!
userEmail: String
userPhoneNumber: String
}
type Subscription {
watchTestMutation(channelId: String!): TestMutationOutput
#aws_subscribe(mutations: ["testMutation"])
}
If I understand you correctly you want to filter based on if the mutation's returned value is in an array that is passed as an argument to the subscription. Sorry to say that is not possible at this time. Subscription filters only evaluate to true or false and cannot accommodate any logic other than that.
At the end of October 2020, I contacted AWS support for advice on this issue. I think this answer may be useful to someone, so I post their answer.
Please allow me to inform you that the use-case that you have
mentioned in the case is currently not possible via AppSync. I
understand that the lack of the feature may be causing inconvenience.
There is an internal feature request already with the AppSync team to
incorporate this feature and I have added a +1 on your behalf. It is
worth noting, that once this feature request is with the team, it will
be up to the team as to if/when this potential infrastructure feature
is implemented, and because of the limited visibility into the
progress of internal development processes, I won’t be able to provide
an ETA regarding its release. I would request you to keep an eye on
the what's new page or the AWS Blogs as all new feature requests and
enhancements are posted there[1-3].
However we can suggest a couple of workarounds in this case:
Filter the required fields on client side itself after receiving the values on the client-side from AppSync.
If the values to be filtered are very limited we can use a fake mutation made with the help of a resolver mapped to “None” Data
source. In this flow, we would create a lambda function that uses a
DynamoDB stream as the trigger. The Lambda function is triggered
whenever there's an update to the DynamoDB table.

We can then include logic in the Lambda function to filter the
required fields and perform a mutation to AppSync. In AppSync, the
mutation which was called by lambda would configured using a resolver
mapped to a “None” Data source. The None data source type passes the
request mapping template directly to the response mapping template.
And when we subscribe to this mutation, we will directly get the
filtered data from Lambda that was used to call this mutation. Please
refer to [4] for a step-by-step description of this process.
But please note that this workaround is cumbersome and would require a lot of changes if the required field values keep changing. Workaround 1(handling it on the client-side) is usually the preferred way to handle this use-case.
Resources:
[1] https://blogs.amazon.com/
[2] https://aws.amazon.com/new/
[3] https://aws.amazon.com/releasenotes/
[4] https://aws.amazon.com/premiumsupport/knowledge-center/appsync-notify-subscribers-real-time/

GraphQL Skip directive - can this be used to exclude items? [duplicate]

Given the following GQL
query getMembers {
repository(owner: "nasa", name: "cumulus") {
mentionableUsers(first: 100) {
nodes {
login
organization(login: "nasa") {
login
}
}
}
}
}
(Query against GitHub v4 GraphQL)
the value for login under organization is either "nasa" or null
I am trying to figure out if it's possible to use #skip against the login/organization so that only contributors to the repo, who are members of the nasa org are shown. I believe for this particular query you can do it another way, but this is just an example.
How would you use #skip/#include with a non boolean. There is minimal documentation on this. While I could filter the response JSON in my client side app, it would be more efficient to receive less data sent over the network and then to parse in my app.
Playing in GraphQLi I received errors trying this various ways - maybe its only possible if the field returns a boolean itself?
e.g., I couldn't do login #skip(if login==null). I also tried setting a value to null in the variables section and the referencing it in the query, but none of the variations I tried work.
What I would really like to do is not include the parent if the child field is some value. e.g., if login=null then don't include that mentionable user. There is no search field option on mentionableUser. From my reading, I am guessing that the only way to do this would be if the API was modified to put a search or filter field on the mentionalbeUsers, otherwise I would need to do this with my client?
Couple of points.
Both the #skip and #include directives provide the same functionality -- allowing the client to arbitrarily chose whether a field should be included in the request.
Let's say we have a query like:
query ($skipBar: Boolean!) {
foo
bar #skip(if: $skipBar)
}
If I set skipBar to true, I am effectively just sending this query:
query {
foo
}
If I set it to false, I am effectively just sending this query:
query {
foo
bar
}
As a client, my logic has to determine the value to assign to skipBar, but I could just as easily use that same logic to decide between sending one of those two queries. In other words, like variables and fragments, #skip and #include are simply a convenient way to keep things DRY on the client-side. They cannot be used to filter the result returned by the server.
GraphQL syntax does not support expressions (or for that matter, any sort of references to parts of the response). Additionally, #skip and #include only take a single argument (if) and that argument must be passed a Boolean -- either as a variable or as a literal value. Even if you could somehow pass an expression to the if argument, though, the directives determine whether the field is included in the request, period. So if the skipped field is part of a returned List (like a List of nodes), it will be absent from every node when it's skipped.
So, is there a workaround?
Not really :( As you've already guessed, if the GitHub API doesn't provide a way to filter a field, there's not much you can do as a client -- you'll have to apply the filtering logic client-side.

Apollo Client strips away additional results from response object

We have implemented our graphql api response like this.
{
data:  {...},
skip: 0,
limit: 10,
total: 100,
hasMore: true
}
If I query our api via graphiql the response looks like expected.
But unfortunately the apollo client in our application strips away all properties from the return object except data.
Is this expected behaviour?
And if so, how can I change it or solve this problem differently.
I need to get the total amount of data to implement pagination accordingly.
I know there is a method with fetchMore but it won't tell me the whole amount of entries in the list.
According to the spec only three top-level keys are expected -- data, errors and extensions. If you include additional keys you're going off-spec -- I would not expect any client to attempt to read them.
At the end of the day, this information should be included in your schema and returned as part of the data in the response. Returning it anywhere else (as additional keys in the response, as response headers, etc.) is a bad idea, if for no other reason than the fact that you could have multiple query fields at the root level, in which case you'd only be able to convey pagination information about one of the fields and it'd be unclear which field the information applied to. The same could be said if you have nested fields that can also be paginated.

How to support patch rest request with protobuf 3

We often have use cases where we only want to update a subset fields on a resource. So if we have a resource Person:
type Person struct {
Age int
Name string
Otherfield string
}
Say the calling client only wants to update the Age field. How would an endpoint be normally set up to handle this?
I believe this should be done with a PATCH request, with only the fields being set as part of the payload, ie:
{
Age: 21
}
However, this won't work with proto3 because as far as I know there are no null fields, only default values. This won't work in many cases where the default value is valid.
Looking at Google own protobuf files (e.g. here), they use FieldMask for partial update.
FieldMask object is passed along with the request, and has the form (in JSON):
{
mask: "Person.Age"
}
This allows the client to tell the server which fields they wish to update, without counting on the partial message itself to figure this out.
I think this adds unnecessary complexity on (each!) client, but we couldn't find any other way to achieve partial updates with proto3.
You can see full documentation of FieldMask here.
Note that it can also be used to filter out responses if the client doesn't need the entire object.

Mixing of schema-level and app-level errors in GraphQL

While building a new application on top of a graphql API we have run into the following problem:
We have a mutation with an input field whose type is a custom scalar with its own validation rules (in this case that the input is a well-formed email address).
On the client, the user of the app fills in a bunch of fields and hits submit. Currently, validation of the email address is handled by the GraphQL layer and aborts the mutation if it fails with a top-level error. Validation of all other fields is handled by the mutation, returning app-level errors in the mutation payload. The other validations in this case cannot be represented directly in the schema since they involve inter-dependent fields.
This behaviour is really unhelpful for the client: it now has to know about errors in two possible locations (top-level graphql errors, and the application errors in the mutation payload) and in two possible formats. It also means that other malformed fields whose malformed-ness is not represented in the GraphQL schema will not be reported until all the schema-level issues have been fixed, forcing the user to go through multiple rounds of "fix the error, hit submit, get another error".
What is the standard solution to this problem? Putting validations (quite complex in this case) on the client? Weakening the schema in order to group all relevant validations at the application layer?
The problem with error categorization
top-level graphql errors, and the application errors in the mutation payload
The distinction that you made between schema-level and application level errors is based on GraphQL type and mutation implementation. A client-side application usually expects a higher abstraction level of errors, i.e., it needs to distinguish user errors and system errors. That way it can mask the system errors as "internal error" and present the user errors as necessary. The developer also can inspect the set of system errors.
See a nice and concise article by Konstantin Tarkus on this: Validation and User Errors in GraphQL Mutations, whose approach I have followed in this answer.
A Not-so-standard-yet-makes-sense solution
To the best of my knowledge, there is no particular standard approach. However, you can try out the following approach.
First, having system-level errors in the top-level field errors of mutation response:
{
"data": {
"viewer": {
"articles": {
"edges": [
{
"node": {
"title": "Sample article title",
"tags": null
}
}
]
}
}
},
"errors": [
{
"message": "Cannot read property 'bar' of undefined",
"locations": [
{
"line": 7,
"column": 11
}
]
}
]
}
Second, putting user-level errors as a separate field errors in mutation payload. Example from the mentioned article:
{
data: {
user: null,
errors: [
'',
'Failed to create a new user account.',
'email',
'User with this email address was already registered.',
]
}
}
// The errors field is just an array of strings that follows this pattern —
// [argumentName1, errorMessage1, argumentName2, errorMessage2, … ]
The above approach enables the client to look for user errors in a defined format in a single place - the errors field of mutation payload. It also allows the client to receive all errors together.
This approach loses automatic validation of the mutation's input type. However, validation is not compromised as the input type's validation logic can be put in a separate function. This function will return validation errors as necessary, which will eventually be put in mutation payload's errors field.
By the way, thanks for the well-written question!
If you are using Apollo, you can easily multiplex the errors array in the graphql response for both graphql errors AND custom errors that are machine readable using this package:
https://github.com/thebigredgeek/apollo-errors

Resources