Skip to content
This repository has been archived by the owner on Dec 8, 2021. It is now read-only.

[WIP] Support enums #244

Merged
merged 22 commits into from
Nov 7, 2018
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
// Code generated by github.com/prisma/graphqlgen, DO NOT EDIT.

import { GraphQLResolveInfo } from 'graphql'
import { Context } from '../types'
import { Post } from '../types'
import { User } from '../types'

import { Context } from '../types'
Weakky marked this conversation as resolved.
Show resolved Hide resolved

export namespace QueryResolvers {
export const defaultResolvers = {}

Expand Down Expand Up @@ -141,8 +142,7 @@ export namespace PostResolvers {
export namespace UserResolvers {
export const defaultResolvers = {
id: (parent: User) => parent.id,
name: (parent: User) =>
parent.name === undefined || parent.name === null ? null : parent.name,
name: (parent: User) => (parent.name === undefined ? null : parent.name),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason why () is needed here?

Copy link
Contributor Author

@Weakky Weakky Nov 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parenthesis aren't in the code generator. They must be added by prettier somehow 😕

}

export type IdResolver = (
Expand Down
128 changes: 100 additions & 28 deletions packages/graphqlgen/src/generators/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,21 @@ import {
GraphQLTypeObject,
GraphQLType,
GraphQLTypeField,
getGraphQLEnumValues,
} from '../source-helper'
import { Model, ModelMap, ContextDefinition } from '../types'
import { ModelField } from '../introspection/ts-ast'
import { ModelMap, ContextDefinition, GenerateArgs } from '../types'
import { flatten, uniq } from '../utils'
import {
TypeDefinition,
FieldDefinition,
InterfaceDefinition,
TypeAliasDefinition,
AnonymousInterfaceAnnotation,
} from '../introspection/types'
import {
isFieldDefinitionEnumOrLiteral,
getEnumValues,
} from '../introspection/utils'

type SpecificGraphQLScalarType = 'boolean' | 'number' | 'string'

Expand All @@ -19,28 +30,56 @@ export interface TypeToInputTypeAssociation {
[objectTypeName: string]: string[]
}

export function fieldsFromModelDefinition(
modelDef: TypeDefinition,
): FieldDefinition[] {
// If model is of type `interface InterfaceName { ... }`
if (modelDef.kind === 'InterfaceDefinition') {
const interfaceDef = modelDef as InterfaceDefinition

return interfaceDef.fields
}
// If model is of type `type TypeName = { ... }`
if (
modelDef.kind === 'TypeAliasDefinition' &&
(modelDef as TypeAliasDefinition).getType().kind ===
'AnonymousInterfaceAnnotation'
) {
const interfaceDef = (modelDef as TypeAliasDefinition).getType() as AnonymousInterfaceAnnotation

return interfaceDef.fields
}

return []
}

export function renderDefaultResolvers(
type: GraphQLTypeObject,
modelMap: ModelMap,
extractFieldsFromModel: (model: Model) => ModelField[],
graphQLTypeObject: GraphQLTypeObject,
args: GenerateArgs,
variableName: string,
): string {
const model = modelMap[type.name]
const model = args.modelMap[graphQLTypeObject.name]

if (model === undefined) {
return `export const ${variableName} = {}`
}

const modelFields = extractFieldsFromModel(model)
const modelDef = model.definition

return `export const ${variableName} = {
${modelFields
.filter(modelField => shouldRenderDefaultResolver(type, modelField))
${fieldsFromModelDefinition(modelDef)
.filter(modelField => {
const graphQLField = graphQLTypeObject.fields.find(
field => field.name === modelField.name,
)

return shouldRenderDefaultResolver(graphQLField, modelField, args)
})
.map(modelField =>
renderDefaultResolver(
modelField.fieldName,
modelField.fieldOptional,
model.modelTypeName,
modelField.name,
modelField.optional,
model.definition.name,
),
)
.join(os.EOL)}
Expand Down Expand Up @@ -90,37 +129,60 @@ export function getModelName(type: GraphQLType, modelMap: ModelMap): string {
return '{}'
}

return model.modelTypeName
return model.definition.name
}

function shouldRenderDefaultResolver(
type: GraphQLTypeObject,
modelField: ModelField,
function isModelEnumSubsetOfGraphQLEnum(
graphQLEnumValues: string[],
modelEnumValues: string[],
) {
const graphQLField = type.fields.find(
field => field.name === modelField.fieldName,
return modelEnumValues.every(enumValue =>
graphQLEnumValues.includes(enumValue),
)
}

function shouldRenderDefaultResolver(
graphQLField: GraphQLTypeField | undefined,
modelField: FieldDefinition | undefined,
args: GenerateArgs,
) {
if (graphQLField === undefined) {
return false
}

if (!graphQLField) {
if (modelField == undefined) {
Weakky marked this conversation as resolved.
Show resolved Hide resolved
return false
}

return !(modelField.fieldOptional && graphQLField.type.isRequired)
const modelFieldType = modelField.getType()

// If both types are enums, and model definition enum is a subset of the graphql enum
// Then render as defaultResolver
// eg: given GraphQLEnum = 'A' | 'B' | 'C'
// render when FieldDefinition = ('A') | ('A' | 'B') | ('A | 'B' | 'C')
if (
graphQLField.type.isEnum &&
isFieldDefinitionEnumOrLiteral(modelFieldType)
) {
return isModelEnumSubsetOfGraphQLEnum(
getGraphQLEnumValues(graphQLField, args.enums),
getEnumValues(modelFieldType),
)
}

return !(modelField.optional && graphQLField.type.isRequired)
}

export function shouldScaffoldFieldResolver(
graphQLField: GraphQLTypeField,
modelFields: ModelField[],
modelFields: FieldDefinition[],
args: GenerateArgs,
): boolean {
const modelField = modelFields.find(
modelField => modelField.fieldName === graphQLField.name,
modelField => modelField.name === graphQLField.name,
)

if (!modelField) {
return true
}

return modelField.fieldOptional && graphQLField.type.isRequired
return !shouldRenderDefaultResolver(graphQLField, modelField, args)
}

export function printFieldLikeType(
Expand All @@ -133,7 +195,7 @@ export function printFieldLikeType(
}${!field.type.isRequired ? '| null' : ''}`
}

if (field.type.isInput) {
if (field.type.isInput || field.type.isEnum) {
return `${field.type.name}${field.type.isArray ? '[]' : ''}${
!field.type.isRequired ? '| null' : ''
}`
Expand Down Expand Up @@ -189,3 +251,13 @@ export function getDistinctInputTypes(
.reduce(flatten, [])
.filter(uniq)
}

export function renderEnums(args: GenerateArgs): string {
return args.enums
.map(enumObject => {
return `type ${enumObject.name} = ${enumObject.values
.map(value => `'${value}'`)
.join(' | ')}`
})
.join(os.EOL)
}
38 changes: 14 additions & 24 deletions packages/graphqlgen/src/generators/flow-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ import * as prettier from 'prettier'

import { GenerateArgs, ModelMap, ContextDefinition } from '../types'
import { GraphQLTypeField, GraphQLTypeObject } from '../source-helper'
import { extractFieldsFromFlowType } from '../introspection/flow-ast'
import { upperFirst } from '../utils'
import {
renderDefaultResolvers,
getContextName,
getDistinctInputTypes,
getModelName,
TypeToInputTypeAssociation,
InputTypesMap,
printFieldLikeType,
getDistinctInputTypes,
renderDefaultResolvers,
renderEnums,
TypeToInputTypeAssociation,
} from './common'

export function format(code: string, options: prettier.Options = {}) {
Expand Down Expand Up @@ -67,6 +67,8 @@ export function generate(args: GenerateArgs): string {
return `\
${renderHeader(args)}

${renderEnums(args)}

${renderNamespaces(args, typeToInputTypeAssociation, inputTypesMap)}

${renderResolvers(args)}
Expand All @@ -79,7 +81,7 @@ function renderHeader(args: GenerateArgs): string {
const modelImports = modelArray
.map(
m =>
`import type { ${m.modelTypeName} } from '${
`import type { ${m.definition.name} } from '${
m.importPathRelativeToOutput
}'`,
)
Expand Down Expand Up @@ -113,13 +115,7 @@ function renderNamespaces(
return args.types
.filter(type => type.type.isObject)
.map(type =>
renderNamespace(
type,
typeToInputTypeAssociation,
inputTypesMap,
args.modelMap,
args.context,
),
renderNamespace(type, typeToInputTypeAssociation, inputTypesMap, args),
)
.join(os.EOL)
}
Expand All @@ -128,32 +124,26 @@ function renderNamespace(
type: GraphQLTypeObject,
typeToInputTypeAssociation: TypeToInputTypeAssociation,
inputTypesMap: InputTypesMap,
modelMap: ModelMap,
context?: ContextDefinition,
args: GenerateArgs,
): string {
const typeName = upperFirst(type.name)

return `\
// Types for ${typeName}
${renderDefaultResolvers(
type,
modelMap,
extractFieldsFromFlowType,
`${typeName}_defaultResolvers`,
)}
${renderDefaultResolvers(type, args, `${typeName}_defaultResolvers`)}

${renderInputTypeInterfaces(
type,
modelMap,
args.modelMap,
typeToInputTypeAssociation,
inputTypesMap,
)}

${renderInputArgInterfaces(type, modelMap)}
${renderInputArgInterfaces(type, args.modelMap)}

${renderResolverFunctionInterfaces(type, modelMap, context)}
${renderResolverFunctionInterfaces(type, args.modelMap, args.context)}

${renderResolverTypeInterface(type, modelMap, context)}
${renderResolverTypeInterface(type, args.modelMap, args.context)}

${/* TODO renderResolverClass(type, modelMap) */ ''}
`
Expand Down
56 changes: 34 additions & 22 deletions packages/graphqlgen/src/generators/flow-scaffolder.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { GenerateArgs, CodeFileLike, ModelMap } from '../types'
import { GenerateArgs, CodeFileLike } from '../types'
import { upperFirst } from '../utils'
import { GraphQLTypeObject } from '../source-helper'
import { extractFieldsFromFlowType } from '../introspection/flow-ast'
import { shouldScaffoldFieldResolver } from './common'
import {
fieldsFromModelDefinition,
shouldScaffoldFieldResolver,
} from './common'

export { format } from './flow-generator'

Expand Down Expand Up @@ -31,12 +33,35 @@ function renderParentResolvers(type: GraphQLTypeObject): CodeFileLike {
code,
}
}
function renderExports(types: GraphQLTypeObject[]): string {
return `\
// @flow
// This resolver file was scaffolded by github.com/prisma/graphqlgen, DO NOT EDIT.
// Please do not import this file directly but copy & paste to your application code.

import type { Resolvers } from '[TEMPLATE-INTERFACES-PATH]'
${types
.filter(type => type.type.isObject)
.map(
type => `
import { ${type.name} } from './${type.name}'
`,
)
.join(';')}

export const resolvers: Resolvers = {
${types
.filter(type => type.type.isObject)
.map(type => `${type.name}`)
.join(',')}
}`
}
function renderResolvers(
type: GraphQLTypeObject,
modelMap: ModelMap,
args: GenerateArgs,
): CodeFileLike {
const model = modelMap[type.name]
const modelFields = extractFieldsFromFlowType(model)
const model = args.modelMap[type.name]
const modelFields = fieldsFromModelDefinition(model.definition)
const upperTypeName = upperFirst(type.name)
const code = `/* @flow */
import { ${upperTypeName}_defaultResolvers } from '[TEMPLATE-INTERFACES-PATH]'
Expand All @@ -46,7 +71,7 @@ export const ${type.name}: ${upperTypeName}_Resolvers = {
...${upperTypeName}_defaultResolvers,
${type.fields
.filter(graphQLField =>
shouldScaffoldFieldResolver(graphQLField, modelFields),
shouldScaffoldFieldResolver(graphQLField, modelFields, args),
)
.map(
field => `
Expand All @@ -68,7 +93,7 @@ export function generate(args: GenerateArgs): CodeFileLike[] {
let files: CodeFileLike[] = args.types
.filter(type => type.type.isObject)
.filter(type => !isParentType(type.name))
.map(type => renderResolvers(type, args.modelMap))
.map(type => renderResolvers(type, args))

files = files.concat(
args.types
Expand All @@ -79,20 +104,7 @@ export function generate(args: GenerateArgs): CodeFileLike[] {
files.push({
path: 'index.js',
force: false,
code: `/* @flow */
import type { Resolvers } from '[TEMPLATE-INTERFACES-PATH]'
${args.types
.map(
type => `
import { ${type.name} } from './${type.name}'
`,
)
.join(';')}

export const resolvers: Resolvers = {
${args.types.map(type => `${type.name}`).join(',')}
}
`,
code: renderExports(args.types),
})

return files
Expand Down