Skip to content

Commit d400650

Browse files
author
yoavkarako
authored
1.5.1 (#32)
* 1.5.1
1 parent b6e53af commit d400650

File tree

6 files changed

+116
-55
lines changed

6 files changed

+116
-55
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
# Change Log
2+
##### 1.5.1
3+
- Bug fix, non-null validation should only apply on upserts or list items
4+
---
25
### 1.5.0
36
- Add server-side validation for update args instead of preserving non-nullability from the origin type.
47
If a field is non-nullable it must be set in either the update operators (e.g. `setOnInsert`, `set`, `inc`, etc...)

README.md

Lines changed: 26 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,29 @@ new GraphQLObjectType({
2828
})
2929
})
3030
```
31-
**We'll define the peron query in our GraphQL scheme like so:**
31+
#### An example GraphQL query supported by the package:
32+
33+
Queries the first 50 persons, oldest first, over the age of 18, and whose first name is John
34+
35+
```
36+
{
37+
person (
38+
filter: {
39+
age: { GT: 18 },
40+
name: {
41+
firstName: { EQ: "John" }
42+
}
43+
},
44+
sort: { age: DESC },
45+
pagination: { limit: 50 }
46+
) {
47+
fullName
48+
age
49+
}
50+
}
51+
```
52+
53+
**To implement, we'll define the peron query field in our GraphQL scheme like so:**
3254
3355
3456
```js
@@ -37,7 +59,7 @@ person: {
3759
args: getGraphQLQueryArgs(PersonType),
3860
resolve: getMongoDbQueryResolver(PersonType,
3961
async (filter, projection, options, obj, args, context) => {
40-
return await context.db.collection('persons').find(filter, projection, options).toArray();
62+
return await context.db.collection('people').find(filter, projection, options).toArray();
4163
})
4264
}
4365
```
@@ -55,7 +77,7 @@ You'll notice that integrating the package takes little more than adding some fa
5577
* As of `mongodb` package version 3.0, you should implement the resolve callback as:
5678
```js
5779
options.projection = projection;
58-
return await context.db.collection('persons').find(filter, options).toArray();
80+
return await context.db.collection('people').find(filter, options).toArray();
5981
```
6082
6183
### That's it!
@@ -109,41 +131,5 @@ age: SortType
109131
limit: Int
110132
skip: Int
111133
```
112-
#### Example GraphQL Query:
113-
114-
Queries the first 50 persons, oldest first, over the age of 18, and whose first name is John
115-
116-
```
117-
{
118-
person (
119-
filter: {
120-
age: { GT: 18 },
121-
name: {
122-
firstName: { EQ: "John" }
123-
}
124-
},
125-
sort: { age: DESC },
126-
pagination: { limit: 50 }
127-
) {
128-
fullName
129-
age
130-
}
131-
}
132-
```
133134
134-
### Aside from the mentioned above, the package comes with functionality galore!
135-
136-
* ```getGraphQLFilterType```
137-
* ```getGraphQLSortType```
138-
* ```getGraphQLUpdateType```
139-
* ```getGraphQLInsertType```
140-
* ```getGraphQLQueryArgs```
141-
* ```getGraphQLUpdateArgs```
142-
* ```GraphQLPaginationType```
143-
* ```getMongoDbFilter```
144-
* ```getMongoDbProjection```
145-
* ```getMongoDbUpdate```
146-
* ```getMongoDbSort```
147-
* ```getMongoDbQueryResolver```
148-
* ```getMongoDbUpdateResolver```
149-
* ```setLogger```
135+
### Functionality galore! Update, insert, and extensiable custom fields.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "graphql-to-mongodb",
3-
"version": "1.5.0",
3+
"version": "1.5.1",
44
"description": "Allows for generic run-time generation of filter types for existing graphql types and parsing client requests to mongodb find queries",
55
"main": "lib/index.js",
66
"types": "lib/index.d.ts",

src/mongoDbUpdateValidation.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,20 @@ export interface UpdateField {
99
export function validateUpdateArgs(updateArgs: UpdateArgs, graphQLType: GraphQLObjectType): void {
1010
let errors: string[] = [];
1111

12-
errors = [...errors, ...validateNonNullableFields(Object.values(updateArgs), graphQLType)];
12+
errors = errors.concat(validateNonNullableFields(
13+
Object.keys(updateArgs).map(_ => updateArgs[_]), graphQLType, !!updateArgs.setOnInsert));
1314

1415
if (errors.length > 0) {
1516
throw errors.join("\n");
1617
}
1718
}
1819

19-
export function validateNonNullableFields(objects: object[], graphQLType: GraphQLObjectType, path: string[] = []): string[] {
20+
export function validateNonNullableFields(objects: object[], graphQLType: GraphQLObjectType, shouldAssert: boolean, path: string[] = []): string[] {
2021
const typeFields = graphQLType.getFields();
2122

22-
const errors = validateNonNullableFieldsAssert(objects, typeFields, path);
23+
const errors: string[] = shouldAssert ? validateNonNullableFieldsAssert(objects, typeFields, path) : [];
2324

24-
return [...errors, ...validateNonNullableFieldsTraverse(objects, typeFields, path)];
25+
return [...errors, ...validateNonNullableFieldsTraverse(objects, typeFields, shouldAssert, path)];
2526
}
2627

2728
export function validateNonNullableFieldsAssert(objects: object[], typeFields: GraphQLFieldMap<any, any>, path: string[] = []): string[] {
@@ -62,7 +63,7 @@ export function validateNonNullListField(fieldValues: object[], type: GraphQLTyp
6263
return true;
6364
}
6465

65-
export function validateNonNullableFieldsTraverse(objects: object[], typeFields: GraphQLFieldMap<any, any>, path: string[] = []): string[] {
66+
export function validateNonNullableFieldsTraverse(objects: object[], typeFields: GraphQLFieldMap<any, any>, shouldAssert: boolean, path: string[] = []): string[] {
6667
let keys: string[] = Array.from(new Set(flatten(objects.map(_ => Object.keys(_)))));
6768

6869
return keys.reduce((agg, key) => {
@@ -78,9 +79,9 @@ export function validateNonNullableFieldsTraverse(objects: object[], typeFields:
7879
const values = objects.map(_ => _[key]).filter(_ => _);
7980

8081
if (isListType(type)) {
81-
return [...agg, ...flatten(flattenListField(values, type).map(_ => validateNonNullableFields([_], innerType, newPath)))];
82+
return [...agg, ...flatten(flattenListField(values, type).map(_ => validateNonNullableFields([_], innerType, true, newPath)))];
8283
} else {
83-
return [...agg, ...validateNonNullableFields(values, innerType, newPath)];
84+
return [...agg, ...validateNonNullableFields(values, innerType, shouldAssert, newPath)];
8485
}
8586
}, []);
8687
}

tests/specs/mongoDbUpdateValidation.spec.ts

Lines changed: 77 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,82 @@
11
import { ObjectType } from "../utils/types";
2-
import { validateNonNullableFields, validateNonNullableFieldsAssert, validateNonNullListField, validateNonNullableFieldsTraverse, flattenListField } from "../../src/mongoDbUpdateValidation";
2+
import { validateUpdateArgs, validateNonNullableFields, validateNonNullableFieldsAssert, validateNonNullListField, validateNonNullableFieldsTraverse, flattenListField } from "../../src/mongoDbUpdateValidation";
33
import { expect } from "chai";
4-
import { GraphQLObjectType, GraphQLType, GraphQLList, GraphQLNonNull, GraphQLString, execute } from "graphql";
4+
import { GraphQLObjectType, GraphQLType, GraphQLList, GraphQLNonNull, GraphQLString } from "graphql";
55
import { UpdateArgs } from "../../src/mongoDbUpdate";
66

7-
describe("mongoDbProjection", () => {
7+
describe("mongoDbUpdateValidation", () => {
8+
describe("validateUpdateArgs", () => {
9+
const tests: { description: string, type: GraphQLObjectType, updateArgs: UpdateArgs, expectedErrors: string[] }[] = [{
10+
description: "Should invalidate non-null on upsert",
11+
type: ObjectType,
12+
updateArgs: {
13+
setOnInsert: {
14+
stringScalar: "x",
15+
},
16+
set: {
17+
nonNullScalar: null
18+
}
19+
},
20+
expectedErrors: ["Missing non-nullable field \"nonNullList\"", "Non-nullable field \"nonNullScalar\" is set to null"]
21+
}, {
22+
description: "Should ignore non-null on update",
23+
type: ObjectType,
24+
updateArgs: {
25+
set: {
26+
nonNullScalar: null
27+
}
28+
},
29+
expectedErrors: []
30+
}, {
31+
description: "Should invalidate non-null on update list item",
32+
type: ObjectType,
33+
updateArgs: {
34+
set: {
35+
nonNullScalar: null,
36+
nestedList: [{
37+
}]
38+
}
39+
},
40+
expectedErrors: ["Missing non-nullable field \"nestedList.nonNullList\"", "Missing non-nullable field \"nestedList.nonNullScalar\""]
41+
}, {
42+
description: "Should validate update correct non-null",
43+
type: ObjectType,
44+
updateArgs: {
45+
setOnInsert: {
46+
stringScalar: "x",
47+
},
48+
set: {
49+
nonNullScalar: "x",
50+
nonNullList: []
51+
}
52+
},
53+
expectedErrors: []
54+
}];
55+
56+
tests.forEach(test => it(test.description, () => {
57+
// Arrange
58+
let error;
59+
60+
// Act
61+
try {
62+
validateUpdateArgs(test.updateArgs, test.type);
63+
} catch (err) {
64+
error = err;
65+
}
66+
67+
// Assert
68+
if (test.expectedErrors.length > 0) {
69+
expect(error, "error object expected").to.not.be.undefined;
70+
71+
const errorString: string = typeof error == "string" ? error : (error as Error).message;
72+
const errors = errorString.split("\n");
73+
expect(errors).to.have.members(test.expectedErrors, "Should detect correct errors");
74+
} else {
75+
if (error) throw error
76+
}
77+
}));
78+
});
79+
880
describe("validateNonNullableFields", () => {
981
const tests: { description: string, type: GraphQLObjectType, updateArgs: UpdateArgs, expectedErrors: string[] }[] = [{
1082
description: "Should invalidate root fields",
@@ -91,7 +163,7 @@ describe("mongoDbProjection", () => {
91163
const objects = Object.keys(test.updateArgs).map(_ => test.updateArgs[_]);
92164

93165
// Act
94-
const errors = validateNonNullableFields(objects, test.type);
166+
const errors = validateNonNullableFields(objects, test.type, true);
95167

96168
// Assert
97169
expect(errors).to.have.members(test.expectedErrors, "Should detect correct errors");
@@ -217,7 +289,7 @@ describe("mongoDbProjection", () => {
217289

218290
tests.forEach(test => it(test.description, () => {
219291
// act
220-
const errors = validateNonNullableFieldsTraverse(test.objects, test.type.getFields(), test.path);
292+
const errors = validateNonNullableFieldsTraverse(test.objects, test.type.getFields(), true, test.path);
221293

222294
// Assert
223295
expect(errors).to.have.members(test.expectedErrors, "Should detect correct errors");

tsconfig.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@
66
"lib": [
77
"es7",
88
"dom",
9-
"esnext.asynciterable",
10-
"es2017.object"
9+
"esnext.asynciterable"
1110
],
1211
"allowSyntheticDefaultImports": true,
1312
"declaration": true,

0 commit comments

Comments
 (0)