Mongoose make Array required - validation

I've got a mongoose model that looks something like this:
var ProjectSchema = new Schema({
name: { type: String, required: true },
tags: [{ type: String, required: true }]
});
I want it to be required for a project to have at least one tag. However when I save a new project without a tags array, mongoose does not throw an error:
var project = new Project({'name': 'Some name'});
project.save(function(err, result) {
// No error here...
});
What am I missing here? How can I specify an array to be required?

Mongoose 5.x
https://mongoosejs.com/docs/migrating_to_5.html#array-required
tags: {
type: [String],
validate: v => Array.isArray(v) && v.length > 0,
}
Mongoose 4.x
One-liner would be:
tags: {type: [String], required: true}
SchemaTypes

AFAIK, you need to set the type to Array and add a custom validator to make sure that each entry is a String:
tags : {
type : Array,
required : true,
validate : {
validator : function(array) {
return array.every((v) => typeof v === 'string');
}
}
}

OK I tried a new method and it seems to work just fine for mongoose ^5.11.15 I'm not really sure if it's a proper answer in terms of clean code but as far as the functionality which is to make an array of Numbers/Strings required (meaning it won't accept an empty array) it works OK so it's as follows
size: [{
type: Number,
required: true
}],
instead of
size: {
type: [Number],
required: true
},
Instead of defining the type as an array of numbers/strings, I defined the size to be an array of numbers and then the required attribute works as it should it doesn't accept an empty array and raises an error to illustrate that.
again I'm not really sure if this is the best way to define a required array but as far as the functionality it works just fine

Related

GraphQL: how to have it return a flexible, dynamic array, depending on what the marketeer filled in? [duplicate]

We are in the situation that the response of our GraphQL Query has to return some dynamic properties of an object. In our case we are not able to predefine all possible properties - so it has to be dynamic.
As we think there are two options to solve it.
const MyType = new GraphQLObjectType({
name: 'SomeType',
fields: {
name: {
type: GraphQLString,
},
elements: {
/*
THIS is our special field which needs to return a dynamic object
*/
},
// ...
},
});
As you can see in the example code is element the property which has to return an object. A response when resolve this could be:
{
name: 'some name',
elements: {
an_unkonwn_key: {
some_nested_field: {
some_other: true,
},
},
another_unknown_prop: 'foo',
},
}
1) Return a "Any-Object"
We could just return any object - so GraphQL do not need to know which fields the Object has. When we tell GraphQL that the field is the type GraphQlObjectType it needs to define fields. Because of this it seems not to be possible to tell GraphQL that someone is just an Object.
Fo this we have changed it like this:
elements: {
type: new GraphQLObjectType({ name: 'elements' });
},
2) We could define dynamic field properties because its in an function
When we define fields as an function we could define our object dynamically. But the field function would need some information (in our case information which would be passed to elements) and we would need to access them to build the field object.
Example:
const MyType = new GraphQLObjectType({
name: 'SomeType',
fields: {
name: {
type: GraphQLString,
},
elements: {
type: new GraphQLObjectType({
name: 'elements',
fields: (argsFromElements) => {
// here we can now access keys from "args"
const fields = {};
argsFromElements.keys.forEach((key) => {
// some logic here ..
fields[someGeneratedProperty] = someGeneratedGraphQLType;
});
return fields;
},
}),
args: {
keys: {
type: new GraphQLList(GraphQLString),
},
},
},
// ...
},
});
This could work but the question would be if there is a way to pass the args and/or resolve object to the fields.
Question
So our question is now: Which way would be recommended in our case in GraphQL and is solution 1 or 2 possible ? Maybe there is another solution ?
Edit
Solution 1 would work when using the ScalarType. Example:
type: new GraphQLScalarType({
name: 'elements',
serialize(value) {
return value;
},
}),
I am not sure if this is a recommended way to solve our situation.
Neither option is really viable:
GraphQL is strongly typed. GraphQL.js doesn't support some kind of any field, and all types defined in your schema must have fields defined. If you look in the docs, fields is a required -- if you try to leave it out, you'll hit an error.
Args are used to resolve queries on a per-request basis. There's no way you can pass them back to your schema. You schema is supposed to be static.
As you suggest, it's possible to accomplish what you're trying to do by rolling your own customer Scalar. I think a simpler solution would be to just use JSON -- you can import a custom scalar for it like this one. Then just have your elements field resolve to a JSON object or array containing the dynamic fields. You could also manipulate the JSON object inside the resolver based on arguments if necessary (if you wanted to limit the fields returned to a subset as defined in the args, for example).
Word of warning: The issue with utilizing JSON, or any custom scalar that includes nested data, is that you're limiting the client's flexibility in requesting what it actually needs. It also results in less helpful errors on the client side -- I'd much rather be told that the field I requested doesn't exist or returned null when I make the request than to find out later down the line the JSON blob I got didn't include a field I expected it to.
One more possible solution could be to declare any such dynamic object as a string. And then pass a stringified version of the object as value to that object from your resolver functions. And then eventually you can parse that string to JSON again to make it again an object on the client side.
I'm not sure if its recommended way or not but I tried to make it work with this approach and it did work smoothly, so I'm sharing it here.

Creating a GraphQLObjectType with an indexable field signature?

I'm currently in the process of transforming a REST API into GraphQL, but I've hit a bit of a snag in one of the endpoints.
Currently, this endpoint returns an object who's keys can be an unlimited set of strings, and whos values all match a certain shape.
So, as a rudimentary example, I have this situation...
// response
{
foo: { id: 'foo', count: 3 },
bar: { id: 'bar', count: 6 },
baz: { id: 'baz', count: 1 },
}
Again, the keys are not known at runtime and can be an unlimited set of strings.
In TypeScript, for example, this sort of situation is handled by creating an interface using an indexable field signature, like so...
interface Data {
id: string;
count: number;
}
interface Response {
[key: string]: Data;
}
So, my question is: Is this sort of thing possible with graphql? How would I go about creating a type/schema for this?
Thanks in advance!
I think that one solution can be usage of JSON.stringify() method
exampleQuery: {
type: GraphQLString,
resolve: (root, args, context) => {
let obj = {
foo: { id: 'foo', count: 3 },
bar: { id: 'bar', count: 6 },
baz: { id: 'baz', count: 1 }
};
return JSON.stringify(obj);
}
}
Then, after retrieving the result of GraphQL query you could use JSON.parse(result) (in case the part performing the query is also written in JavaScript - otherwise you would have to use equivalent method of other language to parse the incoming JSON response).
Disadvantage of such a solution is that you do not have the possibility to choose what fields of obj you want to retrieve from the query, but, as you said, the returning object can have unlimited set of strings that probably are not known on the front end of the application, so there is no need to choose it's keys, am I right?

In GraphQL schema, how can a field be GraphQLString as well as GraphQLInt?

Here's my code from the express application for GraphQL schema:-
let data = new GraphQLObjectType({
name:"Data",
fields: {
id: {type: GraphQLID},
value: {type: GraphQLString} // TODO: allow for string as well as int
}
});
How can I make 'value' field accept a string value as well as int value so that it can be stored using the correct type?
According to your comment saying that you use NoSQL database which can place both strings and ints in the same field, you should go with GraphQLString. When creating new instance of above object, you can create a resolve method for value field, which would check if passed value is string or int (of course it would always be string because of GraphQLString type, however it can be a string like "123" which can be parsed to int) - according to this you can perform some parsing before saving in the database.
On the other hand, when you will retrieve the data from database, it will always occur as a string in the graphql representation - if this is not a case I think that this could be a simple solution.
However, if you are not satisfied with this proposition, I am afraid that you can't trick GraphQL as you want to. Every field can obtain only single type definition.
EDIT:
This solution is not valid for the question. It works only for object types and not scalars
You should look at GraphQLUnionType: http://graphql.org/graphql-js/type/#graphqluniontype
I'm not used to define types like this, but I expect it to be something like the below:
var ValueType = new GraphQLUnionType({
name: 'Value',
types: [ GraphQLString, GraphQLInt ],
resolveType(value) {
if (value instanceof string) {
return GraphQLString;
}
if (value instanceof number) {
return GraphQLInt;
}
}
});
let data = new GraphQLObjectType({
name:"Data",
fields: {
id: {type: GraphQLID},
value: {type: ValueType}
}
});

Can you make a graphql type both an input and output type?

I have some object types that I'd like to use as both input and output - for instance a currency type or a reservation type.
How do I define my schema to have a type that supports both input and output - I don't want to duplicate code if I don't have to. I'd also prefer not to create duplicate input types of things like currency and status enums.
export const ReservationInputType = new InputObjectType({
name: 'Reservation',
fields: {
hotelId: { type: IntType },
rooms: { type: new List(RoomType) },
totalCost: { type: new NonNull(CurrencyType) },
status: { type: new NonNull(ReservationStatusType) },
},
});
export const ReservationType = new ObjectType({
name: 'Reservation',
fields: {
hotelId: { type: IntType },
rooms: { type: new List(RoomType) },
totalCost: { type: new NonNull(CurrencyType) },
status: { type: new NonNull(ReservationStatusType) },
},
});
In the GraphQL spec, objects and input objects are distinct things. Quoting the spec for input objects:
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... 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.
An Input Object defines a set of input fields; the input fields are either scalars, enums, or other input objects. This allows arguments to accept arbitrarily complex structs.
While an implementation might provide convenience code to create an object and a corresponding input object from a single definition, under the covers, the spec indicates that they'll have to be separate things (with separate names, such as Reservation and ReservationInput).
While working on a project I had a similar problem with code duplication between input and type objects. I did not find the extend keyword very helpful as it only extended the fields of that specific type. So the fields in type objects cannot not be inherited in input objects.
In the end I found this pattern using literal expressions helpful:
const UserType = `
name: String!,
surname: String!
`;
const schema = graphql.buildSchema(`
type User {
${UserType}
}
input InputUser {
${UserType}
}
`)
You can do something like this:
export const createTypes = ({name, fields}) => {
return {
inputType: new InputObjectType({name: `${name}InputType`, fields}),
objectType: new ObjectType({name: `${name}ObjectType`, fields})
};
};
const reservation = createTypes({
name: "Reservation",
fields: () => ({
hotelId: { type: IntType },
rooms: { type: new List(RoomType) },
totalCost: { type: new NonNull(CurrencyType) },
status: { type: new NonNull(ReservationStatusType) }
})
});
// now you can use:
// reservation.inputType
// reservation.objectType
this is something that i did for my project (works good):
const RelativeTemplate = name => {
return {
name: name,
fields: () => ({
name: { type: GraphQLString },
reference: { type: GraphQLString }
})
};
};
const RelativeType = {
input: new GraphQLInputObjectType(RelativeTemplate("RelativeInput")),
output: new GraphQLObjectType(RelativeTemplate("RelativeOutput"))
};

Translating JSON into custom dijit objects

I am looking for an example where JSON constructed from the server side is used to represent objects that are then translated into customized widgets in dojo. The JSON would have to be very specific in its structure, so it would not be a very general solution. Could someone point me to an example of this. It would essentially be the reverse of this
http://docs.dojocampus.org/dojo/formToJson
First of all let me point out that JSON produced by dojo.formToJson() is not enough to recreate the original widgets:
{"field1": "value1", "field2": "value2"}
field1 can be literally anything: a checkbox, a radio button, a select, a text area, a text box, or anything else. You have to be more specific what widgets to use to represent fields. And I am not even touching the whole UI presentation layer: placement, styling, and so on.
But it is possible to a certain degree.
If we want to use Dojo widgets (Dijits), we can leverage the fact that they all are created uniformly:
var myDijit = new dijit.form.DijitName(props, node);
In this line:
dijit.form.DijitName is a dijit's class.
props is a dijit-specific properties.
node is an anchor node where to place this dijit. It is optional, and you don't need to specify it, but at some point you have to insert your dijit manually.
So let's encode this information as a JSON string taking this dijit snippet as an example:
var myDijit = new dijit.form.DropDownSelect({
options: [
{ label: 'foo', value: 'foo', selected: true },
{ label: 'bar', value: 'bar' }
]
}, "myNode");
The corresponding JSON can be something like that:
{
type: "DropDownSelect",
props: {
options: [
{ label: 'foo', value: 'foo', selected: true },
{ label: 'bar', value: 'bar' }
]
},
node: "myNode"
}
And the code to parse it:
function createDijit(json){
if(!json.type){
throw new Error("type is missing!");
}
var cls = dojo.getObject(json.type, false, dijit.form);
if(!cls){
// we couldn't find the type in dijit.form
// dojox widget? custom widget? let's try the global scope
cls = dojo.getObject(json.type, false);
}
if(!cls){
throw new Error("cannot find your widget type!");
}
var myDijit = new cls(json.props, json.node);
return myDijit;
}
That's it. This snippet correctly handles the dot notation in types, and it is smart enough to check the global scope too, so you can use JSON like that for your custom dijits:
{
type: "my.form.Box",
props: {
label: "The answer is:",
value: 42
},
node: "answer"
}
You can treat DOM elements the same way by wrapping dojo.create() function, which unifies the creation of DOM elements:
var myWidget = dojo.create("input", {
type: "text",
value: "42"
}, "myNode", "replace");
Obviously you can specify any placement option, or no placement at all.
Now let's repeat the familiar procedure and create our JSON sample:
{
tag: "input",
props: {
type: "text",
value: 42
},
node: "myNode",
pos: "replace"
}
And the code to parse it is straightforward:
function createNode(json){
if(!json.tag){
throw new Error("tag is missing!");
}
var myNode = dojo.create(json.tag, json.props, json.node, json.pos);
return myNode;
}
You can even categorize JSON items dynamically:
function create(json){
if("tag" in json){
// this is a node definition
return createNode(json);
}
// otherwise it is a dijit definition
return createDijit(json);
}
You can represent your form as an array of JSON snippets we defined earlier and go over it creating your widgets:
function createForm(array){
dojo.forEach(array, create);
}
All functions are trivial and essentially one-liners — just how I like it ;-)
I hope it'll give you something to build on your own custom solution.

Resources