Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

delegateToSchema and subschemaErrors #1481

Closed
tlivings opened this issue May 17, 2020 · 16 comments
Closed

delegateToSchema and subschemaErrors #1481

tlivings opened this issue May 17, 2020 · 16 comments

Comments

@tlivings
Copy link
Contributor

delegateToSchema errors do not fall under errors but instead on result[Symbol(subschemaErrors)] which is not iterable and can't be accessed.

What is the correct way to access these errors to ensure they get returned to the caller?

@yaacovCR
Copy link
Collaborator

You should be using a merged resolver like defaultMergedResolver that extracts errors where appropriate.

In v6 you can write your own, as getErrors is exported

@yaacovCR yaacovCR added the waiting-for-release Fixed/resolved, and waiting for the next stable release label May 17, 2020
@tlivings
Copy link
Contributor Author

Is there an example of using defaultMergedResolver?

Doesn't seem like delegateToSchema is currently general purpose enough to arbitrarily delegate to another schema without pulling up / merging the sub-schema's fields as well and adding these merged resolvers?

@yaacovCR
Copy link
Collaborator

yaacovCR commented May 17, 2020 via email

@tlivings
Copy link
Contributor Author

Essentially composition between two types where they may have independent schemas.

For example, one schema imports types from a sub-schema, and creates proxy root type resolvers for the sub-schema. These proxy resolvers delegate to the sub-schema.

This has the effect of appearing as a merged schema, without actually merging all of the schema. A parallel to this would be something like apollo federation, which delegates to schema over the wire — whereas what I am describing is local federation.

I was previously constructing the sub-query and executing agains the sub-schema manually, but was looking to use delegateToSchema to handle the work when I discovered I couldn't access the errors.

@yaacovCR
Copy link
Collaborator

So.... you could use the lower level tools like delegateToSchema, in the next version coming very soon getErrors will be exposed. That seems to be what you want.

You could right now add a transform with a transformResult function that gets the original response prior to errors being stripped...

I am just wondering how you plan on handling aliases, seems like you will need something like defaultMergedResolver... Seems like you are doing stitching more than local federation, although I may be missing something.

Simplest options seems to be to use stitchSchemas/mergeSchemas (name change coming in v6) to automatically import only the relevant types and root fields that you need. That uses defaultMergedResolver under the hood where necessary.

stitchSchemas can also add fields to subschema types and merge types from different subschemas...

Happy to take a look at a code sample...

@yaacovCR
Copy link
Collaborator

It makes sense to note in this context to why the code is structured this way, The result is annotated in such a way such that data can be read simply and errors can be passed down to child fields so that they are thrown in the correct places for all the relevant sub fields.

@yaacovCR
Copy link
Collaborator

I think this is the example you want of using defaultMergedResolver:

test('preserves errors from underlying fields', async () => {

There are several ways to change the default resolver, that example uses one, but you could be more selective to use that resolver only when necessary if you desire...

@tlivings
Copy link
Contributor Author

To clarify use case, if possible, imagine a schema that is composed in part of another schema (it may expose parts of it only, and may utilize the secondary schema's API via graphql without exposing its resolvers).

For C to be a composition of A and B, the resolver for C may execute some part of each.

To avoid executing type resolvers twice (one in invoking A or B via graphql, and once again from merging types and resolvers into C), the secondary schema's type resolvers will not be merged into C at all and root resolvers, if imported, will be proxies a-la federation for the underlying schema.

If this was only schema stitching, this would be less complicated. But my particular use case is stitching + composition capabilities.

So, the schema might look like this at the C level:

type A { ... } <-- imported from schema 2
type B { ... } <-- imported from schema 2
type C { ... } <-- local to schema 1

type Query {
  a: A --> delegate to schema 2
  b: B --> delegate to schema 2
  c: C --> delegate to A + add some additional logic
}

At any rate, it sounds like what I do need is getErrors — so I will just continue building my own sub-query until it is available.

Thanks!

@yaacovCR
Copy link
Collaborator

This sounds really interesting, although I am not 100% on whether I followed the whole deal. It sounds like you are trying to avoid use of defaultMergedResolver. Which I would love, but what about aliases?

I would love a minimal example to hack around with if you have time...

getErrors is already available at @graphql-tools/utils @ canary....

@tlivings
Copy link
Contributor Author

As far as aliases, because i am creating the proxy resolvers based on known root type fields from the other schema, I know the field name already and when I create a resolver, I can introspect info for path and fieldNodes that match name and alias such that I strip away everything else.

A new operations with different selections is created from this to run a execute call on.

When errors come back, they are merged into data at the appropriate path for graphql-js to handle.

@yaacovCR
Copy link
Collaborator

This sounds like the stuff delegateToSchema and defaultMergedResolver are doing already. E why do you need getErrors, why can't you use defaultMergedResolver?

A code sample might help my poor brain...

@tlivings
Copy link
Contributor Author

Not sure where you'd use defaultMergedResolver ... the example you sent was utilizing it in the graphql call but not really sure where it would get used in a proxy resolver that needs to invoke delegateToSchema

@yaacovCR
Copy link
Collaborator

Could be! If you have a code sample of the composition you are going for, your expected result, I could try to take a look. I have a feeling you should be able to accomplish with existing tools, but I could be wrong...

@tlivings
Copy link
Contributor Author

Basically the code is something like this:

const createDelegateResolver = function (subSchema, root, field) {
  return function (_, args, context, info) {
    return /*delegate-to-subSchema-here*/;
  }
}

createDelegateResolver gets called to create the root field resolvers for imported subSchema types.

@yaacovCR
Copy link
Collaborator

I think I mean a sample schema with which fields you want to come from which subschema. Sorry I wasn't clear. I basically didn't understand your high level example above. :(

@yaacovCR
Copy link
Collaborator

Closed by #1419.

@yaacovCR yaacovCR removed the waiting-for-release Fixed/resolved, and waiting for the next stable release label May 21, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants