How to access query path properties in a resolver? GraphQL - graphql

I have a database with the following structure.
I'm writing a GraphQL resolver for the bottom-most node (the "rows" node).
As the image shows, each "rows" node corresponds to a specific path. (Company)->(DB)->(Table)->(rows)
A Query would be of the form:
{
Company(name: "Google") {
Database(name: "accounts") {
Table(name: "users") {
rows
}
}
}
}
Question: How can I include/access Company.name, Database.name, Table.name information in the rows resolver so that I can determine which rows node to return?
In other words: I know I can access Table.name using parent.name, but is there a way to get parent.parent.name or parent.parent.parent.name?
If there isn't a way to access ancestor properties, should I use arguments or context to pass these properties manually into the rows resolver?
Note: I can't use the neo4j-graphql-js package.
Note: This is the first simple example I thought of and I understand there are structural problems with organizing data this way, but the question still stands.

You can extract the path from the GraphQLResolveInfo object passed to the resolver:
const { responsePathAsArray } = require('graphql')
function resolver (parent, args, context, info) {
responsePathAsArray(info.path)
}
This returns an array like ['google', 'accounts', 0, 'user']. However, you can also pass arbitrary data from parent resolver to child resolver.
function accountResolver (parent, args, context, info) {
// Assuming we already have some value at parent.account and want to return that
return {
...parent.account,
message: 'It\'s a secret!',
}
}
function userResolver (parent, args, context, info) {
console.log(parent.message) // prints "It's a secret!"
}
Unless message matches some field name, it won't ever actually appear in your response.

Related

How to find path to a field in a graphql query

I am very new to graphql. I have a following graphql query for an example:
query pets {
breed(some arguments)
{
name
items
{
owner(some arguments)
{
items
{
ID
ownerSnumber
country
address
school
nationality
gender
activity
}
}
name
phoneNumber
sin
}
}
}
Is it possible to parse a gql query and get the path of a field in the query?
For example I would like to get the path of 'ID'. For example from the above query, is it possible to get the path where the ID is: owner.items.ID
With https://graphql.org/graphql-js/ it exposes a fourth argument called resolve info. This field contains more information about the field.
Have a look at GraphQLObjectType config parameter type definition:
With a good start from the earlier answer, relying on the ResolveInfo you could do something like a recursive check going from child to parent:
export const getFieldPath = (path: Path): string => {
if (!path.prev) return `${path.key}`
return `${getFieldPath(path.prev)}.${path.key}`
}
And later in your resolver you could use it like:
const myFieldResolver = (parent, args, ctx, info) => {
const pathOfThisResolversField = getFieldPath(info.path)
// use your pathOfThisResolversField
return yourFieldResolvedData
};
Worth noting though, the solution above will include every node all the way to the query root, rather than just the ones you mentioned owner.items.ID

Pass Graphql input arguement to directive

I have a simple graphql query and a directive
directive #isOwner(postID: String!) on FIELD_DEFINITION
type Query {
post(postID: String!): Post! #isOwner(postID: postID)
}
The problem is that I'm using GQLGen to generate my boilerplate code for Go, and directives are treated differently from the input values.
This presents a unique challenge where authorization logic is almost isolated from the actual db reads, which makes the logic very inefficient, in that I have to eiither make a database read twice: during validation and the actual db read.
The data required for validation is also required for the db read, and I would have to edit my whole code to inject this data into context.
Is there a way of passing the input arguements dynamically to the directive and have the validation done dynamically and is it a good pracise in the first place?
Arguments passed to schema directives are evaluated when your schema is initially built, so they can't be dynamic. In this particular case, you don't need an argument at all -- you can just read the value of the field's arguments.
visitFieldDefinition(field) {
const { resolve = defaultFieldResolver } = field
field.resolve = async function (parent, args, context, info) {
console.log(args.postID)
return resolve.apply(this, [parent, args, context, info])
}
}
However, if the name of the argument varies by field, then you can pass that as an argument to your directive
directive #isOwner(argName: String!) on FIELD_DEFINITION
visitFieldDefinition(field) {
const { resolve = defaultFieldResolver } = field
const { argName } = this.args
field.resolve = async function (parent, args, context, info) {
console.log(args[argName])
return resolve.apply(this, [parent, args, context, info])
}
}

How to trigger visitInputObject method on custom directive?

I'm building a custom directive in which I'm hoping to validate entire input objects. I'm using the INPUT_OBJECT type with the visitInputObject method on SchemaDirectiveVisitor extended class.
Every time I run a mutation using the input type then visitInputObject does not run.
I've used the other types/methods like visitObject and visitFieldDefinition and they work perfectly. But when trying to use input types and methods they will not trigger.
I've read all the available documentation I can find. Is this just not supported yet?
Some context code(Not actual):
directive #validateThis on INPUT_OBJECT
input MyInputType #validateThis {
id: ID
someField: String
}
type Mutation {
someMutation(myInput: MyInputType!): SomeType
}
class ValidateThisDirective extends SchemaDirectiveVisitor {
visitInputObject(type) {
console.log('Not triggering');
}
}
All the visit methods of a SchemaDirectiveVisitor are ran at the same time -- when the schema is built. That includes visitFieldDefinition and visitFieldDefinition. The difference is that when we use visitFieldDefinition, we often do it to modify the resolve function for the visited field. It's this function that's called during execution.
You use each visit methods to modify the respective schema element. You can use visitInputObject to modify an input object, for example to add or remove fields from it. You cannot use it to modify the resolution logic of an output object's field. You should use visitFieldDefinition for that.
visitFieldDefinition(field, details) {
const { resolve = defaultFieldResolver } = field
field.resolve = async function (parent, args, context, info) {
Object.keys(args).forEach(argName => {
const argDefinition = field.args.find(a => a.name === argName)
// Note: you may have to "unwrap" the type if it's a list or non-null
const argType = argDefinition.type
if (argType.name === 'InputTypeToValidate') {
const argValue = args[argName]
// validate here
}
})
return resolve.apply(this, [parent, args, context, info]);
}
}

How can I execute an instance query in graphql-fhir?

This issue is migrated from a question on our Github account because we want the answer to be available to others. Here is the original question:
Hello,
Following is the InstanceQuery I tried
http://localhost:3000/3_0_1/Questionnaire/jamana/$graphql?query={id}
I am receiving back response as Cannot query field \"id\" on type \"Questionnaire_Query\"
So what is the right format I should try ?
https://build.fhir.org/graphql.html has a sample as http://test.fhir.org/r3/Patient/example/$graphql?query={name{text,given,family}}.Its working in their server. I cannot get the response When I try similarly in our graphql-fhir.
Original answer from Github:
We are using named queries since we are using express-graphql. I do not believe that is valid syntax. Also, the url provided does not seem to work, I just get an OperationOutcome saying the patient does not exist, which is not a valid GraphQL response.
Can you try changing your query from:
http://localhost:3000/3_0_1/Questionnaire/jamana/$graphql?query={id}
to this:
http://localhost:3000/3_0_1/Questionnaire/jamana/$graphql?query={Questionnaire{id}}
When writing the query, you need to provide the return type as part of the instance query. You should get a response that looks like similar to this(if you have implemented your resolver you will have data and not null):
{
"data": {
"Questionnaire": {
"id": null
}
}
}
and from a later comment:
If you are getting null then you are doing it correctly, but you haven't wrote a query or connected it to a data source. You still need to return the questionnaire in the resolver.
Where you are seeing this:
instance: {
name: 'Questionnaire',
path: '/3_0_1/Questionnaire/:id',
query: QuestionnaireInstanceQuery,
},
You are seeing the endpoint being registered with an id parameter, which is different from a GraphQL argument. This is just an express argument. If you navigate to the questionnaire/query.js file, you can see that the QuestionnaireInstanceQuery query has a different resolver than the standard QuestionnaireQuery. So in your questionnaire/resolver.js file, if you want both query and instance query to work, you need to implement both resolvers.
e.g.
// This is for the standard query
module.exports.getQuestionnaire = function getQuestionnaire(
root,
args,
context = {},
info,
) {
let { server, version, req, res } = context;
// Do query and return questionnaire
return {};
};
// This one is for a questionnaire instance
module.exports.getQuestionnaireInstance = function getQuestionnaireInstance(
root,
args,
context = {},
info,
) {
let { server, version, req, res } = context;
// req.params.id is your questionnaire id, use that for your query here
// queryQuestionnaireById does not exist, it is pseudo code
// you need to query your database here with the id
let questionnaire = queryQuestionnaireById(req.params.id);
// return the correct questionnaire here, default returns {},
// which is why you see null, because no data is returned
return questionnaire;
};

How can I do a WpGraphQL query with a where clause?

This works fine
query QryTopics {
topics {
nodes {
name
topicId
count
}
}
}
But I want a filtered result. I'm new to graphql but I see a param on this collection called 'where', after 'first', 'last', 'after' etc... How can I use that? Its type is 'RootTopicsTermArgs' which is likely something autogenerated from my schema. It has fields, one of which is 'childless' of Boolean. What I'm trying to do, is return only topics (a custom taxonomy in Wordpress) which have posts tagged with them. Basically it prevents me from doing this on the client.
data.data.topics.nodes.filter(n => n.count !== null)
Can anyone direct me to a good example of using where args with a collection? I have tried every permutation of syntax I could think of. Inlcuding
topics(where:childless:true)
topics(where: childless: 'true')
topics(where: new RootTopicsTermArgs())
etc...
Obviously those are all wrong.
If a custom taxonomy, such as Topics, is registered to "show_in_graphql" and is part of your Schema you can query using arguments like so:
query Topics {
topics(where: {childless: true}) {
edges {
node {
id
name
}
}
}
}
Additionally, you could use a static query combined with variables, like so:
query Topics($where:RootTopicsTermArgs!) {
topics(where:$where) {
edges {
node {
id
name
}
}
}
}
$variables = {
"where": {
"childless": true
}
};
One thing I would recommend is using a GraphiQL IDE, such as https://github.com/skevy/graphiql-app, which will help with validating your queries by providing hints as you type, and visual indicators of invalid queries.
You can see an example of using arguments to query terms here: https://playground.wpgraphql.com/#/connections-and-arguments

Resources