Use a GQLObject as arg for a mutation? - node.js

I have following mutation on serverside (nodeJS) (RequiredDataType is imported):
mutationA: {
type: MutationResponseType,
args: {
id: {
type: new GraphQLNonNull(GraphQLString)
},
name: {
type: new GraphQLNonNull(GraphQLString)
},
requiredData: {
type: new GraphQLNonNull(new GraphQLList(RequiredDataType))
}
},
async resolve(parentValue, {
id,
name,
requiredData
}, req) {
// Some Magic Code
}
},
The RequiredDataType is coded as follow (All GraphQL things are imported :)):
const RequiredDataType = new GraphQLObjectType({
name: 'RequiredDataType',
fields: {
name: {
type: GraphQLString
},
value: {
type: GraphQLString
},
required: {
type: GraphQLBoolean
}
}
});
module.exports = RequiredDataType;
When I use this code I get the following error: "module initialization error: Error"
If I change the RequiredDataType in the mutation to GraphQLString it works without any error but I can't use the object which I need :)
At the end I will send and process following data structure:
{
"name": "Hallo"
"id": "a54de3d0-a0a6-11e7-bf70-7b64ae72d2b6",
"requiredData": [
{
"name": "givenName",
"value": null,
"required": true
},
{
"name": "familyName",
"value": null,
"required": false
}
]
}
On the client (reactJS with apollo-client) I use the following gql-tag code:
export default gql`
mutation MutationA($id: String!, $name: String!, $requiredData: [RequiredDataType]!){
mutationA(id: $id, name: $name, requiredData: $requiredData) {
id,
somethingElse
}
}
`;
But in the first place it crashes on the mutation declaration on the server. So is it not possible to use and GQLObject as an argument at an mutation or where is my error in the code?
Thank you for your help!
Best,
Fabian

Unfortunately, a type cannot be used in place of an input, and an input cannot be used in place of a type. This is by design. From the official specification:
Fields can define arguments that the client passes up with the query,
to configure their behavior. These inputs can be Strings or Enums, but
they sometimes need to be more complex than this.
The Object type defined above is inappropriate for re‐use here,
because Objects can contain fields that express circular references or
references to interfaces and unions, neither of which is appropriate
for use as an input argument. For this reason, input objects have a
separate type in the system.
You can check this answer for more details as to the why
You'll need to define RequiredDataType as a GraphQLInputObjectType, not a GraphQLObjectType, to get your mutation working. If you need it as a GraphQLObjectType too, you'll need to declare them as two separate types -- something like RequiredDataType and RequiredDataInput.

Related

How can I cast a string to number in an response using axios?

I am using Axios to execute a GET request to a public API. This API return a numeric value as string. I need to cast this value a numeric value. My application runs using tsc, so I expect the result of my object to be a numeric value, but it's not.
Axios Response
[
{
"name": "foo1",
"value": "8123.3000"
},
{
"name": "foo2",
"value": "5132.2003"
},
{
"name": "foo3",
"value": "622.0000"
}
]
Expected Output
[
{
"name": "foo1",
"value": 8123.3
},
{
"name": "foo2",
"value": 5132.2003
},
{
"name": "foo3",
"value": 622
}
]
My code is very simple,
interface MyObj {
myString: string;
myNumber: number;
}
(async () => {
let { data }: AxiosResponse<MyObj> = await axios.get<MyObj>("/public/data");
console.log(data);
})();
I try to use interface, class, the interface Number. Nothing worked.
I leave an example of code to try it.
How can I get the expected output without manually converting each value one by one?
Axios does not change the type of properties in the response. Please verify that the server does not send you the wrong types.
Edit
From your comment, it seems that the server sends you the vslue as string instead of as number. In this case I would suggest working with Ajv (https://github.com/ajv-validator/ajv) so you can create a schema that describes how the response looks like. Ajv cn also transform the value from string to number for you:
const Ajv = require('ajv')
const ajv = new Ajv({
// allow chaning the type of some values from type X to type Y. depends on the source and target type:
// https://ajv.js.org/guide/modifying-data.html#coercing-data-types
// X => Y rules: https://ajv.js.org/coercion.html
coerceTypes: true,
})
const schema = {
type: 'array',
items: {
type: 'object',
properties: {
name: { type: 'string' },
value: { type: 'number' },
},
required: ['name', 'value'],
additionalProperties: false,
},
}
const data = { name: 1, value: '1.1' }
console.log(typeof data.value === 'number') // false!
const valid = ajv.validate(schema, data)
if (!valid) {
console.log(ajv.errors)
process.exit(1)
}
console.log(typeof data.value === 'number') // true!
When declaring that your axios request returns a certain type; this will be checked at compile time and syntax checking. It does not however do this at runtime. If you know that the axios request is returning something different than your interface, you need to convert to that format first. You can do this using the second argument of the JSON.parse function like so:
interface Item {
name: string;
value: number;
}
let responseString = `
[
{
"name": "foo1",
"value": "8123.3000"
},
{
"name": "foo2",
"value": "5132.2003"
},
{
"name": "foo3",
"value": "622.0000"
}
]`
const items: Item[] = JSON.parse(responseString, (key, value) => {
const propertiesToCast = ["value"] // Which properties should be converted from string to number
if (propertiesToCast.includes(key)) {
return parseFloat(value)
}
return value
});
console.log(items)

How to use String properties in fastest-validator

Link: https://www.npmjs.com/package/fastest-validator
I'm using fastest-validation in my NodeJS application. I've been having great success with it. Unfortunately, I'm running into an issue that I can't seem to figure out.
If you take a look under String (located here: https://www.npmjs.com/package/fastest-validator#user-content-string)
I'm attempting to utilize the property numeric as I have a string that is a number that I would like to validate. I'm not able to find any examples, so I was left with the assumption I must set the property to true. This doesn't appear to work as I've tried to validate this theory by also setting another field that is a numeric string and set the property of alpha to true. I fully expected my 'label' to pass and my 'value' field to fail. But both passed.
How are you supposed to used these properties?
See below for my code:
buildSchema.catalogPages = {
type: "array", items: {
type: "object", props: {
label: { type: "string", empty: false, numeric: true },
value: { type: "string", empty: false, alpha: true }
}
}
}
const v = new Validator()
const check = v.compile(buildSchema)
check(valuesToCheck)
Here is my data:
const valuesToCheck = [
{
label: "9"
value: "9"
},
{
label: "12"
value: "12"
},
EDIT
I just figured out my issue. I have a handler function that checks my schemas and valuesToCheck. What I was doing was returning the check(valuesToCheck) with the assumption it simply returns true or false. But in fact, if the check fails, it returns an array of what failed (which is awesome). I'm going to accept tam.teixeira's answer as it helped me realized I need to update my handler to check if it's a Boolean true or an array (aka it failed).
I think that should not be something related to the schema itself, in the following example:
const Validator = require('fastest-validator');
const schema = {
myItems: {
type: 'array',
items: {
type: 'object',
props: {
label: { type: 'string', empty: false, numeric: true },
value: { type: 'string', empty: false, alpha: true },
},
},
},
};
const valuesToCheck = {
myItems: [
{
label: '9',
value: '9',
},
{
label: '12',
value: '12',
},
],
};
const v = new Validator();
const check = v.compile(schema);
console.log(JSON.stringify(check(valuesToCheck), null, 4));
Indeed it fails validation with errors:
$> node fastestValidatorTest.js
[
{
"type": "stringAlpha",
"message": "The 'myItems[0].value' field must be an alphabetic string.",
"field": "myItems[0].value",
"actual": "9"
},
{
"type": "stringAlpha",
"message": "The 'myItems[1].value' field must be an alphabetic string.",
"field": "myItems[1].value",
"actual": "12"
}
]
So i got your expected behaviour: "'label' to pass and my 'value' field to fail"
So my conclusion is that maybe something is not right in the buildSchema.catalogPages part, to me it feels a bit suspiscious and i also got the expected behaviour with your example, but i've changed the schema object to be simpler.
PS: Thanks for the question i didn't knew the library, so learned something new, which is cool

throw new TypeError(`Invalid schema configuration: \`${name}\` is not ` +

I wrote the below schema. But while running it gives me an error -- throw new TypeError(Invalid schema configuration: \${name}` is not ` + --- can someone help me why does this error comes?. Below shown is my schema, can someone figure out if my schema have some mistakes.
$
jsonSchema: {
bsonType: "object",
properties: {
name: {
bsonType: "string",
description: "must be a string"
},
teacherId: {
bsonType: "objectId",
description: "must be a Object ID"
}
}
}
You are using the MongoDB native syntax to define your schema. If you are using mongoose, mongoose has its own syntax. Please refer to mongoose documentation. For your schema it would be:
var studentSchema = new mongoose.Schema({
name: { type: String },
teacherId: { type: mongoose.Schema.Types.ObjectId }
})
Mmm how I resolved this was to use the Types defined within the Schema import. For some reason importing the Types module directly didn't work -- well for Map at least.
import { Document, Schema, model, Query, Model } from "mongoose";
{
...
metadata: { type: Schema.Types.Map, of: String, required: false },
}
"name" is not a valid attribute anymore in mongoose schemas. Replace it with "type" it will work.
Oh man this is related to wrong name of the schema fields in my case:
participants: [
{
typeof: Types.ObjectId,
ref: "User",
},
]
and I got same error as you. I was so struggled and this typeof - type f* VS code I'm using it at the moment cause this computer can't deal with jet brains, :(

How to manage GraphQL child objectType that can be nullable in an output type?

I'm setting up a nodeJS GraphQL API and I'm experimenting a blocking point regarding one of my resource output type.
The feature is a form that contain three different level :
Level 1- formTemplate
Level 2- formItems (templateId, type (video, image, question) - 1-N relation with formTemplate)
Level 3- formQuestions (0-1 relation with formItem if and only if formItems.type is 'question')
My GraphQL resource is returning all the templates in the database so it's an array that for each template is returning all his items and each item of type "question" needs to return an array containing the associated question.
My problem is : I really don't know how to return an empty object type for the formItems where type is different from "question" or if there is a better approach for this kind of situation
I've tried to look at GraphQL directives and inline fragments but I think it really needs to be manage by the backend side because it's transparent for the API consumer.
const formTemplate = new GraphQLObjectType({
name: 'FormTemplate',
fields: () => {
return {
id: {
type: new GraphQLNonNull(GraphQLInt)
},
authorId: {
type: new GraphQLNonNull(GraphQLInt)
},
name: {
type: new GraphQLNonNull(GraphQLString)
},
items: {
type: new GraphQLList(formItem),
resolve: parent => FormItem.findAllByTemplateId(parent.id)
}
}
}
})
const formItem = new GraphQLObjectType({
name: 'FormItem',
fields: () => {
return {
id: {
type: new GraphQLNonNull(GraphQLInt)
},
templateId: {
type: new GraphQLNonNull(GraphQLInt)
},
type: {
type: new GraphQLNonNull(GraphQLString)
},
question: {
type: formQuestion,
resolve: async parent => FormQuestion.findByItemId(parent.id)
}
}
}
})
const formQuestion= new GraphQLObjectType({
name: 'FormQuestion',
fields: () => {
return {
id: {
type: new GraphQLNonNull(GraphQLInt)
},
itemId: {
type: new GraphQLNonNull(GraphQLInt)
},
type: {
type: new GraphQLNonNull(GraphQLString)
},
label: {
type: new GraphQLNonNull(GraphQLString)
}
}
}
})
My GraphQL request :
query {
getFormTemplates {
name
items {
type
question {
label
type
}
}
}
}
What I'm expected is
{
"data": {
"getFormTemplates": [
{
"name": "Form 1",
"items": [
{
"type": "question",
"question": {
"label": "Question 1",
"type": "shortText"
},
{
"type": "rawContent"
"question": {}
}
]
}
]
}
}
I'd design your "level 2" items so that the "type" property corresponded to actual GraphQL types, implementing a common interface. Also, in general, I'd design the schema so that it had actual links to neighboring items and not their identifiers.
So if every form item possibly has an associated template, you can make that be a GraphQL interface:
interface FormItem {
id: ID!
template: FormTemplate
}
Then you can have three separate types for your three kinds of items
# Skipping VideoItem
type ImageItem implements FormItem {
id: ID!
template: FormTemplate
src: String!
}
type QuestionItem implements FormItem {
id: ID!
template: FormTemplate
questions: [FormQuestion!]!
}
The other types you describe would be:
type FormTemplate {
id: ID!
author: Author!
name: String!
items: [FormItem!]!
}
type FormQuestion {
id: ID!
question: Question
type: String!
label: String!
}
The other tricky thing is, since not all form items are questions, you have to specifically mention that you're interested in questions in your query to get the question-specific fields. Your query might look like
query {
getFormTemplates {
name
items {
__typename # a GraphQL builtin that gives the type of this object
... on Question {
label
type
}
}
}
}
The ... on Question syntax is an inline fragment, and you can similarly use it to pick out the fields specific to other kinds of form items.
Thank you David for your answer !
I've figured it out how to solve my problem using inline fragments and UnionTypes that seems to be the most adapted for this use case. Here is the code :
const formItemObjectType = new GraphQLUnionType({
name: 'FormItemObject',
types: [formItemContent, formItemQuestion],
resolveType(parent) {
switch (parent.type) {
case ('question'): return formItemQuestion
default: return formItemContent
}
}
})
and the GraphQL query using inline fragment:
query {
getFormTemplates {
name
items {
...on FormItemContent {
type,
meta
}
...on FormItemQuestion {
type,
meta,
question {
label
}
}
}
}
}

customize json response in graphQL

i use Express-js and express graphQL module to create my endpoint and web service ;
i am looking for way to create custom response in graphQL my endpoint is simple
select books from database my response is
{
"data": {
"books": [
{
"id": "5b5c02beab8dc1182b2e0a03",
"name": "dasta"
},
{
"id": "5b5c02c0ab8dc1182b2e0a04",
"name": "dasta"
}
]
}
}
but in need something like this
{
"result": "success",
"msg" : "list ...",
"data": [
{
"id": "5b5c02beab8dc1182b2e0a03",
"name": "dasta"
},
{
"id": "5b5c02c0ab8dc1182b2e0a04",
"name": "dasta"
}
]
}
here is my bookType
const BookType = new GraphQLObjectType({
name: 'Book',
fields: () => ({
id: {type: GraphQLID},
name: {type: GraphQLString},
genre: {type: GraphQLString},
author_id: {type: GraphQLString},
author: {
type: AuthorType,
resolve(parent, args) {
return Author.findById(parent.author_id);
}
}
})
});
That's not a legal GraphQL response. As per section 7.1 of the spec, after describing the data, errors, and extensions: top-level keys:
... the top level response map must not contain any entries other than the three described above.
You might put this data into extensions; or make it an explicit part of your GraphQL API; or simply let "success" be implied by the presence of a result and the lack of an error.

Resources