diff --git a/lib/SchemaBuilder.js b/lib/SchemaBuilder.js index 009b5d4..e87f089 100644 --- a/lib/SchemaBuilder.js +++ b/lib/SchemaBuilder.js @@ -187,9 +187,14 @@ class SchemaBuilder { if (relation instanceof objection.HasOneRelation || relation instanceof objection.BelongsToOneRelation || relation instanceof objection.HasOneThroughRelation) { + const isRequired = _.intersection(relation.ownerModelClass.jsonSchema.required, relation.ownerProp).length === relation.ownerProp.length; + let type = this._typeForModel(modelData); + if(isRequired) { + type = new graphqlRoot.GraphQLNonNull(type); + } return { - type: this._typeForModel(modelData), - args: _.omit(modelData.args, OMIT_FROM_RELATION_ARGS) + type: type, + args: {} }; } else if (relation instanceof objection.HasManyRelation || relation instanceof objection.ManyToManyRelation) { return { diff --git a/lib/argFactories.js b/lib/argFactories.js index 74314c3..dceed61 100644 --- a/lib/argFactories.js +++ b/lib/argFactories.js @@ -147,7 +147,11 @@ function reducePrimitiveFields(fields, modelClass, func) { for (var i = 0, l = propNames.length; i < l; ++i) { const propName = propNames[i]; - const field = fields[propName]; + let field = fields[propName]; + + if(field.type.constructor.name == "GraphQLNonNull") { + field = {type: field.type.ofType}; + } if (field.type instanceof GraphQLObjectType || field.type instanceof GraphQLList) { continue; diff --git a/lib/jsonSchema.js b/lib/jsonSchema.js index b60c835..6dcf3c6 100644 --- a/lib/jsonSchema.js +++ b/lib/jsonSchema.js @@ -5,6 +5,7 @@ const _ = require('lodash') , graphqlRoot = require('graphql') , GraphQLObjectType = graphqlRoot.GraphQLObjectType , GraphQLEnumType = graphqlRoot.GraphQLEnumType + , GraphQLNonNull = graphqlRoot.GraphQLNonNull , GraphQLBoolean = graphqlRoot.GraphQLBoolean , GraphQLString = graphqlRoot.GraphQLString , GraphQLFloat = graphqlRoot.GraphQLFloat @@ -20,6 +21,7 @@ function jsonSchemaToGraphQLFields(jsonSchema, opt) { typeCache: {} }); + const requiredFields = jsonSchema.required || []; const fields = {}; _.forOwn(jsonSchema.properties, (propSchema, propName) => { @@ -27,13 +29,13 @@ function jsonSchemaToGraphQLFields(jsonSchema, opt) { return; } - fields[propName] = toGraphQLField(propSchema, propName, ctx); + fields[propName] = toGraphQLField(propSchema, propName, ctx, requiredFields.indexOf(propName) !== -1); }); return fields; } -function toGraphQLField(jsonSchema, propName, ctx) { +function toGraphQLField(jsonSchema, propName, ctx, required) { let schemas; if (jsonSchema.anyOf || jsonSchema.oneOf) { @@ -48,26 +50,26 @@ function toGraphQLField(jsonSchema, propName, ctx) { const type = _.reject(jsonSchema.type, isNullType); if (type.length === 1) { - return typeToGraphQLField(type[0], jsonSchema, propName, ctx); + return typeToGraphQLField(type[0], jsonSchema, propName, ctx, required); } else { throw new Error('multiple values in json schema `type` property not supported. schema: ' + JSON.stringify(jsonSchema)); } } else { - return typeToGraphQLField(jsonSchema.type, jsonSchema, propName, ctx); + return typeToGraphQLField(jsonSchema.type, jsonSchema, propName, ctx, required); } } -function typeToGraphQLField(type, jsonSchema, propName, ctx) { +function typeToGraphQLField(type, jsonSchema, propName, ctx, required) { let graphQlField; if (_.isArray(jsonSchema.enum)) { - graphQlField = enumToGraphQLField(jsonSchema.enum, propName, ctx); + graphQlField = enumToGraphQLField(jsonSchema.enum, propName, ctx, required); } else if (type === 'object') { - graphQlField = objectToGraphQLField(jsonSchema, propName, ctx); + graphQlField = objectToGraphQLField(jsonSchema, propName, ctx, required); }else if (type === 'array') { - graphQlField = arrayToGraphQLField(jsonSchema, propName, ctx); + graphQlField = arrayToGraphQLField(jsonSchema, propName, ctx, required); } else { - graphQlField = primitiveToGraphQLField(type); + graphQlField = primitiveToGraphQLField(type, required); } if (jsonSchema.description) { @@ -77,23 +79,27 @@ function typeToGraphQLField(type, jsonSchema, propName, ctx) { return graphQlField; } -function enumToGraphQLField(enumeration, propName, ctx) { +function enumToGraphQLField(enumeration, propName, ctx, required) { var typeName = ctx.typeNamePrefix + _.upperFirst(_.camelCase(propName)) + 'Enum' + (ctx.typeIndex++); if (!ctx.typeCache[typeName]) { - ctx.typeCache[typeName] = new GraphQLEnumType({ + let type = new GraphQLEnumType({ name: typeName, values: _.reduce(enumeration, (values, enumValue) => { values[enumValue] = {value: enumValue}; return values; }, {}) }); + if(required) { + type = new GraphQLNonNull(type); + } + ctx.typeCache[typeName] = type; } return {type: ctx.typeCache[typeName]}; } -function objectToGraphQLField(jsonSchema, propName, ctx) { +function objectToGraphQLField(jsonSchema, propName, ctx, required) { const typeName = ctx.typeNamePrefix + _.upperFirst(_.camelCase(propName)) + 'JsonType' + (ctx.typeIndex++); if (!ctx.typeIndex[typeName]) { @@ -103,7 +109,7 @@ function objectToGraphQLField(jsonSchema, propName, ctx) { const fields = {}; _.forOwn(jsonSchema.properties, (propSchema, propName) => { - fields[propName] = toGraphQLField(propSchema, propName, ctx); + fields[propName] = toGraphQLField(propSchema, propName, ctx, required); }); return fields; @@ -114,23 +120,27 @@ function objectToGraphQLField(jsonSchema, propName, ctx) { return {type: ctx.typeCache[typeName]}; } -function arrayToGraphQLField(jsonSchema, propName, ctx) { +function arrayToGraphQLField(jsonSchema, propName, ctx, required) { if (_.isArray(jsonSchema.items)) { throw new Error('multiple values in `items` of array type is not supported. schema: ' + JSON.stringify(jsonSchema)); } return { - type: new GraphQLList(toGraphQLField(jsonSchema.items, propName, ctx).type) + type: new GraphQLList(toGraphQLField(jsonSchema.items, propName, ctx, required).type) }; } -function primitiveToGraphQLField(type) { - const graphQlType = primitiveToGraphQLType(type); +function primitiveToGraphQLField(type, required) { + let graphQlType = primitiveToGraphQLType(type); if (!graphQlType) { throw new Error('cannot convert json schema type "' + type + '" into GraphQL type'); } + if (required) { + graphQlType = new GraphQLNonNull(graphQlType); + } + return {type: graphQlType}; } diff --git a/tests/integration.js b/tests/integration.js index b98f3d4..c5aa304 100644 --- a/tests/integration.js +++ b/tests/integration.js @@ -5,6 +5,7 @@ const _ = require('lodash') , path = require('path') , expect = require('expect.js') , graphql = require('graphql').graphql + , printType = require('graphql').printType , GraphQLList = require('graphql').GraphQLList , GraphQLObjectType = require('graphql').GraphQLObjectType , mainModule = require('../') @@ -881,4 +882,31 @@ describe('integration tests', () => { }); }); + describe('Types', () => { + let schema; + + before(() => { + schema = mainModule + .builder() + .model(session.models.Person, {listFieldName: 'people'}) + .model(session.models.Movie) + .model(session.models.Review) + .build(); + }); + + it('should make required fields required', () => { + const expectedSchema = `type Review { + id: Int + title: String + stars: Int + text: String + reviewerId: Int! + movieId: Int! + reviewer: Person! + movie: Movie! +}`; + expect(expectedSchema).to.eql(printType(schema.getType("Review"))); + }); + }); + }); diff --git a/tests/setup/models/Review.js b/tests/setup/models/Review.js index 307d731..c55668d 100644 --- a/tests/setup/models/Review.js +++ b/tests/setup/models/Review.js @@ -10,6 +10,7 @@ class Review extends Model { static get jsonSchema() { return { type: 'object', + required: ['reviewerId', 'movieId'], properties: { id: {type: 'integer'},