How to trigger visitInputObject method on custom directive? - graphql

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]);
}
}

Related

NestJS GrapHQL ensure scalar type

I have custom scalars for my users ID, CustomerID and ProviderID, I would like to validate them when someone call a mutation to ensure that the given ID match a user and of correct type.
We cannot make CustomerScalar parseValue method asynchronous, so I'm looking for a nice way to deal with such things.
Maybe customerDecorator ? I don't know ? Any idea ?
I would like to access my repository using Dependencies Injection to ensure that the passed ID exists in the database and is really a Customer.
Field Middleware and Directive seems not to support Deps injection.
#InputType()
export class CreateBillInput {
#Field(() => CustomerIDScalar)
customerID: CustomerID;
#Field()
name: string;
#Field(() => Int)
amount: number;
}
What I wanted that cannot work :
#Injectable()
export class CustomerIDScalar implements CustomScalar<string, CustomerID> {
constructor(private userRepository: IUserRepository) {}
parseValue(value: string) {
return this.getCustomerID(value);
}
parseLiteral(ast: ValueNode) {
if (ast.kind !== Kind.STRING) {
throw new TypeError('Argument is not a string value.');
}
return this.getCustomerID(value);;
}
serialize(value: CustomerID) {
return value.value; // value sent to the client
}
// TODO: Not usable here
private async getCustomerID(userID: string): Promise<CustomerID> {
const user = await this.userRepository.getByID(userID);
if (!user || !user.isCustomer()) {
throw new BadRequestException('There is no such customer user for provided ID.');
}
return user.id as CustomerID;
}
}
Thanks
First of all. gql scalars validation are about technical check to validate all data are correct (schema check). It's not suppose to have any business rules validation stuff there.
To achieve desired result you can use the next things:
Nest validation pipes with #Args decorator:
#Mutation(returns => Group)
async createGroup(
#Args('group', new ValidationPipe())
input: CreateGroupInput,
)
class-validation https://github.com/typestack/class-validator
Simply put a regular ID/Int scalar for the input type & validate it later in service class that responsive for such operation (recommend to use this approach for such things in gql)

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 access query path properties in a resolver? 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.

Possible to genericize NGXS actions?

I'd like to write just one action to perform the same CRUD operations on state, just on different slices of it, while preserving type safety.
For example, I'd like to use the following action to apply a set operation to any slice with a generic type T:
export class Set_Entity<T> {
static readonly type = '[Entity] Set';
constructor(public payload: <T>) {}
}
This is problematic because the type will always be the same. Is it possible to somehow decorate this class so a unique type property can be passed in whenever it is used as the #Action?
Something like:
/* action* /
class Set_Entity<T> {
constructor(public entity: string, public payload: <T>) {}
}
/* state */
#Action(Set_Entity('[Groups] Set Group'/* <-- Changes the `type` property */))
set_group(
context: StateContext<Model>,
action: SetEntity<{entity: string, payload: Group}>,
) {
const entity = action.entity;
const data = action.payload;
context.patchState({ [entity]: data });
}
/* facade or something */
this.store.dispatch([
new Set_Entity<GroupEntityType>(
'user', // <-- the state slice
aRecord,
),
]);
Even this solution leaves more to be desired. Generic Actions still must be written for each state slice, for each CRUD operation. It would be nice to be able to use the same generic action for each CRUD op on each state slice.
I managed to do it beautifully with NGRX via typescript-fsa and typescript-fsa-reducers. Only needed one single generic action plus one single generic reducer for the entire state, all typesafe.
The action looked like this:
function generic_set_action<T>(sliceName: string): ActionCreator<T> {
const creator = actionCreatorFactory(sliceName);
const action = creator<T>('set')
return action; // Produces type of `sliceName/set`
}
// Create the action
generic_set_action<User>('sliceName')(payload)
The reducer:
export function create_generic_reducer<T>(sliceName: string) {
const action_set = generic_set_action<T>(sliceName);
return reducerWithInitialState({} as T)
.case(action_set, (state, data) => (data))
.build();
}
And finally when creating the reducers:
export const Reducers: ActionReducerMap<State> = {
coolSlice: create_generic_reducer<MySliceModel>('coolSlice'),
// repeat for each slice..
};
It would be great to be able to reproduce this with NGXS.

Using Config.skip with a React-Apollo Query

I'm having some trouble making use of the Config.skip property inside of my graphql() wrapper.
The intent is for the query to be fired with an argument of currentGoalID, only after a user has selected an item from the drop-down (passing the associated currentGoalID) , and the (Redux) state has been updated with a value for currentGoalID.
Otherwise, I expect (as per Apollo documentation) that:
... your child component doesn’t get a data prop at all, and the options or props methods are not called.
In this case though, it seems that my skip property is being ignored based upon the absence of a value for currentGoalID, and the option is being called because the webpack compiler/linter throws on line 51, props is not defined...
I successfully console.log the value of currentGoalID without the graphql()
wrapper. Any idea why config.skip isn't working? Also wish to be advised on the proper use of this in graphql() function call. I've excluded it here, but am unsure of the context, thanks.
class CurrentGoal extends Component {
constructor(props) {
super(props)
}
render (){
console.log(this.props.currentGoalID);
return( <p>Current Goal: {null}</p>
)
}
}
const mapStateToProps = (state, props) => {
return {
currentGoal: state.goals.currentGoal,
currentGoalID: state.goals.currentGoalID,
currentGoalSteps: state.goals.currentGoalSteps
}
}
const FetchGoalDocByID = gql `
query root($varID:String) {
goalDocsByID(id:$varID) {
goal
}
}`;
const CurrentGoalWithState = connect(mapStateToProps)(CurrentGoal);
const CurrentGoalWithData = graphql(FetchGoalDocByID, {
skip: (props) => !props.currentGoalID,
options: {variables: {varID: props.currentGoalID}}
})(CurrentGoalWithState);
// export default CurrentGoalWithState
export default CurrentGoalWithData
See the answer here: https://stackoverflow.com/a/47943253/763231
connect must be the last decorator executed, after graphql, in order for graphql to include the props from Redux.

Resources