-
Notifications
You must be signed in to change notification settings - Fork 2k
Typed arguments in resolvers #1419
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
Comments
@terion-name Taking query from StackOverflow as an example: class Query {
document(args, ctx, info) {
const argDefs = info.parentType.getFields()[info.fiedName].args;
for (const argName of Object.keys(args)) {
const argValue = args[argName];
const argType = argDefs.find(arg => arg.name === argName).type;
if (isInputObjectType(argType) {
for (const argFieldName in Object.keys(argValue)) {
const argFieldType = argType.getFields()[argFieldName];
// ...
}
}
}
}
} Is it something close to what you are trying to do? |
@IvanGoncharov thank you, after some fixes — yes, it is close to desired. Just need to tweak it for recursion, I will dig in this. And my point that this is very, very useful data for input processing and it would be great to have something like this in core library. Sort of |
@IvanGoncharov thank you for help, I've written a rough draft of function I speak of: function visitArguments(args, info, visitor = {}) {
let path = [];
let result = {};
const argDefs = info.parentType.getFields()[info.fieldName].args;
for (const argName of Object.keys(args)) {
const argValue = args[argName];
const argType = argDefs.find(arg => arg.name === argName).type;
path.push(argName);
const apply = (argType, argValue, argName, path) => {
const cbs = [visitor.enter, visitor[argType.toString()]].filter(cb => !!cb);
cbs.forEach(cb => {
const process = cb(argType, argValue, argName, path);
argValue = process === undefined ? argValue : process;
});
return argValue;
};
const traverse = (argType, argValue, argName) => {
argValue = apply(argType, argValue, argName, path);
if (isInputObjectType(argType)) {
for (const argFieldName of Object.keys(argValue)) {
path.push(argFieldName);
const argFieldType = argType.getFields()[argFieldName];
if (isListType(argFieldType.type) || isListType(argFieldType.type.ofType)) {
const innerType = getNamedType(argFieldType.type);
return argValue[argFieldName].map(v => {
return traverse(innerType, v, argFieldName);
});
}
if (isInputObjectType(argFieldType.type)) {
return traverse(argFieldType.type, argValue[argFieldName], argFieldName)
}
argValue = apply(argFieldType.type, argValue[argFieldName], argFieldName, path);
path.pop();
return {[argFieldName]: argValue};
}
}
return argValue;
};
const processed = traverse(argType, argValue, argName);
switch (processed) {
case undefined:
result[argName] = argValue;
break;
case null:
break;
default:
result[argName] = processed;
}
path = [];
}
return result;
} And some examples: const resolver = function (parent, args, ctx, info) {
args = visitArguments(args, info, {
enter(type, value, name, path) {
// transform input
if (name === 'first') {
return argValue > 10 ? 10 : argValue;
}
},
DocumentWhereInput(type, value, name, path) {
// validate
validatorForDocumentWhere(value); // throw on error
}
});
} |
@terion-name Makes sense 👍 Validation and transformation of complex argument is definitely something that we need to improve. Moreover, I just realize that you can use something like this to generate SQL query based on your args similar to how So I think it would be extremely useful to have such function inside const resolver = function (parent, args, ctx, info) {
const argDefs = info.parentType.getFields()[info.fiedName].args;
args = visitArgumentsWithTypes(args, argDefs, {
enter(type, value, name, path) {
...
},
});
} For example, you want to analyze arguments as part of validation, in this case, you don't have access to Also, having {
enter(type, value, name, path) {
// ...
},
types: {
DocumentWhereInput(type, value, name, path) {
// ...
}
}
} Do you interested in working on such PR? |
seems reasonable. then it will be useful to add a function that will extract arg defs from info, to prevent writing
yes, I've thought about this but didn't find a bulletproof interface for this. Your variant looks interesting
Of course. I'm currently testing out the concept in a project I develop, after some proofing I'll start a PR |
@terion-name Great 🎉 Just open WIP PR after you stabilize API so I could provide an early feedback. |
@IvanGoncharov some thoughts during proofing. It became obvious that this function should be async, to allow async visitors that will perform remote calls for validation or other async actions (uploading files or interacting with S3). One thing I am confused of — is should it respect NotNull type. E.g. should the function trigger visitor for |
@terion-name Using Promises will absolutely kill performance for people who want to use this function for simple validation/transformation. Not sure about your use case but can you use external promise like that: const resolver = function (parent, args, ctx, info) {
const argDefs = info.parentType.getFields()[info.fiedName].args;
let argsActionPromise = Promise.resolve();
args = visitArgumentsWithTypes(args, argDefs, {
enter(type, value, name, path) {
argsActionPromise = argsActionPromise.then(someValidation);
},
leave(type, value, name, path) {
argsActionPromise = argsActionPromise.then(someTask);
},
});
return argsActionPromise.then(() => getData());
} If not can you please give some simplified example of your usecase?
Yeah, that's tricky 🤔. The alternative idea: can you bind callbacks to fields instead of types: {
enter(type, value, name, path) {
// ...
},
'SomeType::someField': (type, value, name, path) => {
// ...
}
} As a bonus, it works similar to |
Moving files for example. Two variants:
|
@terion-name Make sense. |
It's kinda feature request. I've posted a question how to do this using existing tools (btw I'l be very grateful if someone can help with this), but I think this really should be in a spec/ref implementation.
Resolver accepts arguments as plain javascript object, already combined from inline arguments and variables. But GraphQL is strongly typed and this benefit doesn't work here. I get arguments, but I don't know types of these arguments. Getting types of args in resolver is useful because it gives more possibilities to process input. To validate or transform it based on types. This is especially useful in scenarios when you have deeply nested input with relations, multiple named input types, repeated types (lists of objects), etc.
And I see two variants of improving this:
The text was updated successfully, but these errors were encountered: