Skip to content

Commit

Permalink
feat: required fields from mongoose schema became required in GraphQL
Browse files Browse the repository at this point in the history
closes #224
  • Loading branch information
nodkz committed Jul 2, 2020
1 parent df6afdb commit d3d28da
Show file tree
Hide file tree
Showing 18 changed files with 88 additions and 13 deletions.
7 changes: 6 additions & 1 deletion src/__mocks__/userModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,17 @@ const UserSchema: SchemaType<any> = new Schema(

name: {
type: String,
required: true,
description: 'Person name',
},

age: {
type: Number,
description: 'Full years',
required() {
// in graphql this field should be Nullable
return this.name === 'Something special';
},
},

gender: {
Expand Down Expand Up @@ -120,7 +125,7 @@ UserSchema.set('autoIndex', false);
UserSchema.index({ name: 1, age: -1 });

// eslint-disable-next-line
UserSchema.virtual('nameVirtual').get(function() {
UserSchema.virtual('nameVirtual').get(function () {
return `VirtualFieldValue${this._id}`;
});

Expand Down
9 changes: 7 additions & 2 deletions src/__tests__/composeWithMongoose-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -208,8 +208,13 @@ describe('composeWithMongoose ->', () => {
},
},
});
const filterArgInFindOne = typeComposer.getResolver('findOne').getArgITC('filter');
expect(filterArgInFindOne.isFieldNonNull('age')).toBe(true);

const ac = typeComposer.getResolver('createOne').getArgITC('record');
expect(ac.isFieldNonNull('age')).toBe(true);

// but should be nullable in filters
const ac2 = typeComposer.getResolver('findOne').getArgITC('filter');
expect(ac2.isFieldNonNull('age')).toBe(false);
});

it('should use cached type to avoid maximum call stack size exceeded', () => {
Expand Down
5 changes: 2 additions & 3 deletions src/__tests__/composeWithMongooseDiscriminators-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,8 @@ describe('composeWithMongooseDiscriminators ->', () => {
},
},
});
const filterArgInFindOne: any = typeComposer.getResolver('findOne').getArg('filter');
const inputComposer = schemaComposer.createInputTC(filterArgInFindOne.type);
expect(inputComposer.isFieldNonNull('kind')).toBe(true);
const ac: any = typeComposer.getResolver('createOne').getArgTC('record');
expect(ac.isFieldNonNull('kind')).toBe(true);
});

it('should proceed customizationOptions.inputType.fields.required', () => {
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/fieldConverter-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ describe('fieldConverter', () => {
const tc = convertModelToGraphQL(UserModel, 'User', sc);

it('should work with String', () => {
expect(tc.getFieldTypeName('name')).toBe('String');
expect(tc.getFieldTypeName('name')).toBe('String!');
expect(tc.getFieldTypeName('skills')).toBe('[String]');
});

Expand Down
10 changes: 10 additions & 0 deletions src/fieldsConverter.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,10 +140,19 @@ export function convertModelToGraphQL<TSource, TContext>(

const mongooseFields = getFieldsFromModel(model);
const graphqlFields = {};
const requiredFields = [];

Object.keys(mongooseFields).forEach((fieldName) => {
const mongooseField: MongooseFieldT = mongooseFields[fieldName];

if (
(mongooseField: any).isRequired &&
// conditional required field in mongoose cannot be NonNulable in GraphQL
typeof (mongooseField: any).originalRequiredValue !== 'function'
) {
requiredFields.push(fieldName);
}

graphqlFields[fieldName] = {
type: convertFieldToGraphQL(mongooseField, typeName, sc),
description: _getFieldDescription(mongooseField),
Expand All @@ -165,6 +174,7 @@ export function convertModelToGraphQL<TSource, TContext>(
});

typeComposer.addFields(graphqlFields);
typeComposer.makeFieldNonNull(requiredFields);
return typeComposer;
}

Expand Down
6 changes: 4 additions & 2 deletions src/resolvers/__tests__/createMany-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,8 +187,10 @@ describe('createMany() ->', () => {

describe('Resolver.getType()', () => {
it('should have correct output type name', () => {
const outputType: any = createMany(UserModel, UserTC).getType();
expect(outputType.name).toBe(`CreateMany${UserTC.getTypeName()}Payload`);
const resolver: any = createMany(UserModel, UserTC);
expect(resolver.getTypeName()).toBe(`CreateMany${UserTC.getTypeName()}Payload`);
expect(resolver.getArgITC('records').getFieldTypeName('name')).toBe('String!');
expect(resolver.getArgITC('records').getFieldTypeName('age')).toBe('Float');
});

it('should have recordIds field, NonNull List', () => {
Expand Down
6 changes: 4 additions & 2 deletions src/resolvers/__tests__/createOne-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,10 @@ describe('createOne() ->', () => {

describe('Resolver.getType()', () => {
it('should have correct output type name', () => {
const outputType: any = createOne(UserModel, UserTC).getType();
expect(outputType.name).toBe(`CreateOne${UserTC.getTypeName()}Payload`);
const resolver = createOne(UserModel, UserTC);
expect(resolver.getTypeName()).toBe(`CreateOne${UserTC.getTypeName()}Payload`);
expect(resolver.getArgITC('record').getFieldTypeName('name')).toBe('String!');
expect(resolver.getArgITC('record').getFieldTypeName('age')).toBe('Float');
});

it('should have recordId field', () => {
Expand Down
8 changes: 8 additions & 0 deletions src/resolvers/__tests__/findMany-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,12 @@ describe('findMany() ->', () => {
expect(result).toEqual([{ overridden: true }]);
});
});

describe('Resolver.getType()', () => {
it('should have all fields optional in filter', () => {
const resolver = findMany(UserModel, UserTC);
expect(resolver.getArgITC('filter').getFieldTypeName('name')).toBe('String');
expect(resolver.getArgITC('filter').getFieldTypeName('age')).toBe('Float');
});
});
});
6 changes: 6 additions & 0 deletions src/resolvers/__tests__/findOne-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,5 +138,11 @@ describe('findOne() ->', () => {
const outputType = findOne(UserModel, UserTC).getType();
expect(outputType).toBe(UserTC.getType());
});

it('should have all fields optional in filter', () => {
const resolver = findOne(UserModel, UserTC);
expect(resolver.getArgITC('filter').getFieldTypeName('name')).toBe('String');
expect(resolver.getArgITC('filter').getFieldTypeName('age')).toBe('Float');
});
});
});
6 changes: 6 additions & 0 deletions src/resolvers/__tests__/removeMany-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,5 +142,11 @@ describe('removeMany() ->', () => {
const outputType = removeMany(UserModel, UserTC).getType();
expect(outputType).toBe(existedType.getType());
});

it('should have all fields optional in filter', () => {
const resolver = removeMany(UserModel, UserTC);
expect(resolver.getArgITC('filter').getFieldTypeName('name')).toBe('String');
expect(resolver.getArgITC('filter').getFieldTypeName('age')).toBe('Float');
});
});
});
6 changes: 6 additions & 0 deletions src/resolvers/__tests__/removeOne-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -235,5 +235,11 @@ describe('removeOne() ->', () => {
const outputType = removeOne(UserModel, UserTC).getType();
expect(outputType).toBe(existedType.getType());
});

it('should have all fields optional in filter', () => {
const resolver = removeOne(UserModel, UserTC);
expect(resolver.getArgITC('filter').getFieldTypeName('name')).toBe('String');
expect(resolver.getArgITC('filter').getFieldTypeName('age')).toBe('Float');
});
});
});
8 changes: 8 additions & 0 deletions src/resolvers/__tests__/updateMany-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,5 +151,13 @@ describe('updateMany() ->', () => {
const outputType = updateMany(UserModel, UserTC).getType();
expect(outputType).toBe(existedType.getType());
});

it('should have all fields optional in filter', () => {
const resolver = updateMany(UserModel, UserTC);
expect(resolver.getArgITC('filter').getFieldTypeName('name')).toBe('String');
expect(resolver.getArgITC('filter').getFieldTypeName('age')).toBe('Float');
expect(resolver.getArgITC('record').getFieldTypeName('name')).toBe('String');
expect(resolver.getArgITC('record').getFieldTypeName('age')).toBe('Float');
});
});
});
8 changes: 8 additions & 0 deletions src/resolvers/__tests__/updateOne-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -257,5 +257,13 @@ describe('updateOne() ->', () => {
const outputType = updateOne(UserModel, UserTC).getType();
expect(outputType).toBe(existedType.getType());
});

it('should have all fields optional in filter', () => {
const resolver = updateOne(UserModel, UserTC);
expect(resolver.getArgITC('filter').getFieldTypeName('name')).toBe('String');
expect(resolver.getArgITC('filter').getFieldTypeName('age')).toBe('Float');
expect(resolver.getArgITC('record').getFieldTypeName('name')).toBe('String');
expect(resolver.getArgITC('record').getFieldTypeName('age')).toBe('Float');
});
});
});
2 changes: 1 addition & 1 deletion src/resolvers/helpers/filter.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export const filterHelperArgs = (
const filterTypeName: string = opts.filterTypeName;
const itc = typeComposer.getInputTypeComposer().clone(filterTypeName);

itc.makeFieldNullable('_id');
itc.makeFieldNullable(itc.getFieldNames());

itc.addFields({
_ids: [GraphQLMongoID],
Expand Down
4 changes: 3 additions & 1 deletion src/resolvers/helpers/record.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ export type RecordHelperArgsOpts = {
isRequired?: boolean;
removeFields?: string[];
requiredFields?: string[];
/** Make all fields nullable by default. May be overridden by `requiredFields` property */
allFieldsNullable?: boolean;
};

export function getRecordHelperArgsOptsMap(): Partial<
Expand All @@ -16,5 +18,5 @@ export type RecordsHelperArgs<TSource> = { records: TSource[] };

export function recordHelperArgs(
tc: ObjectTypeComposer<any>,
opts?: RecordHelperArgsOpts,
opts?: RecordHelperArgsOpts
): ObjectTypeComposerArgumentConfigMapDefinition;
6 changes: 6 additions & 0 deletions src/resolvers/helpers/record.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export type RecordHelperArgsOpts = {
isRequired?: boolean,
removeFields?: string[],
requiredFields?: string[],
/** Make all fields nullable by default. May be overridden by `requiredFields` property */
allFieldsNullable?: boolean,
};

// for merging, discriminators merge-able only
Expand Down Expand Up @@ -42,6 +44,10 @@ export const recordHelperArgs = (
recordITC = tc.getInputTypeComposer().clone(recordTypeName);
}

if (opts && opts.allFieldsNullable) {
recordITC.makeFieldNullable(recordITC.getFieldNames());
}

if (opts && opts.removeFields) {
recordITC.removeField(opts.removeFields);
}
Expand Down
1 change: 1 addition & 0 deletions src/resolvers/updateMany.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export default function updateMany<TSource: MongooseDocument, TContext>(
recordTypeName: `UpdateMany${tc.getTypeName()}Input`,
removeFields: ['id', '_id'],
isRequired: true,
allFieldsNullable: true,
...(opts && opts.record),
}),
...filterHelperArgs(tc, model, {
Expand Down
1 change: 1 addition & 0 deletions src/resolvers/updateOne.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export default function updateOne<TSource: MongooseDocument, TContext>(
recordTypeName: `UpdateOne${tc.getTypeName()}Input`,
removeFields: ['id', '_id'],
isRequired: true,
allFieldsNullable: true,
...(opts && opts.record),
}),
...filterHelperArgs(tc, model, {
Expand Down

0 comments on commit d3d28da

Please sign in to comment.