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

Commit

Permalink
feat: allow nullable return and voidable params (#331)
Browse files Browse the repository at this point in the history
closes #278
  • Loading branch information
kinolaev authored and Jason Kuhrt committed Jan 5, 2019
1 parent a15d3f8 commit 1fddfec
Show file tree
Hide file tree
Showing 9 changed files with 211 additions and 122 deletions.
5 changes: 5 additions & 0 deletions packages/graphqlgen/src/generators/common.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ it('getDistinctInputTypes', () => {
isUnion: false,
isRequired: false,
isArray: false,
isArrayRequired: false,
},
},
{
Expand All @@ -56,6 +57,7 @@ it('getDistinctInputTypes', () => {
isUnion: false,
isRequired: false,
isArray: false,
isArrayRequired: false,
},
},
],
Expand Down Expand Up @@ -85,6 +87,7 @@ it('getDistinctInputTypes', () => {
isUnion: false,
isRequired: false,
isArray: false,
isArrayRequired: false,
},
},
],
Expand Down Expand Up @@ -114,6 +117,7 @@ it('getDistinctInputTypes', () => {
isUnion: false,
isRequired: false,
isArray: false,
isArrayRequired: false,
},
},
],
Expand Down Expand Up @@ -143,6 +147,7 @@ it('getDistinctInputTypes', () => {
isUnion: false,
isRequired: false,
isArray: false,
isArrayRequired: false,
},
},
],
Expand Down
99 changes: 79 additions & 20 deletions packages/graphqlgen/src/generators/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,29 +188,88 @@ export function shouldScaffoldFieldResolver(
return !shouldRenderDefaultResolver(graphQLField, modelField, args)
}

export function printFieldLikeType(
const nullable = (type: string): string => {
return `${type} | null`
}

const kv = (
key: string,
value: string,
isOptional: boolean = false,
): string => {
return `${key}${isOptional ? '?' : ''}: ${value}`
}

const array = (innerType: string, config: { innerUnion?: boolean } = {}) => {
return config.innerUnion ? `${innerType}[]` : `Array<${innerType}>`
}

type FieldPrintOptions = {
isReturn?: boolean
}

export const printFieldLikeType = (
field: GraphQLTypeField,
modelMap: ModelMap,
) {
const isNullable =
field.defaultValue === null ||
(!field.type.isRequired && field.defaultValue === undefined)

if (field.type.isScalar) {
return `${getTypeFromGraphQLType(field.type.name)}${
field.type.isArray ? '[]' : ''
}${isNullable ? '| null' : ''}`
options: FieldPrintOptions = {
isReturn: false,
},
): string => {
const name = field.type.isScalar
? getTypeFromGraphQLType(field.type.name)
: field.type.isInput || field.type.isEnum
? field.type.name
: getModelName(field.type, modelMap)

/**
* Considerable difference between types in array versus not, such as what
* default value means, isRequired, ..., lead to forking the rendering paths.
*
* Regarding voidable, note how it can only show up in the k:v rendering e.g.:
*
* foo?: null | string
*
* but not for return style e.g.:
*
* undefined | null | string
*
* given footnote 1 below.
*
* 1. Return type doesn't permit void return since that would allow
* resolvers to e.g. forget to return anything and that be considered OK.
*/

if (field.type.isArray) {
const innerUnion = field.type.isRequired

// - Not voidable here because a void array member is not possible
// - For arrays default value does not apply to inner value
const valueInnerType = field.type.isRequired ? name : nullable(name)

const isArrayNullable =
!field.type.isArrayRequired &&
(field.defaultValue === undefined || field.defaultValue === null)

const isArrayVoidable = isArrayNullable && field.defaultValue === undefined

const valueType = isArrayNullable
? nullable(array(valueInnerType, { innerUnion })) // [1]
: array(valueInnerType, { innerUnion })

return options.isReturn
? valueType
: kv(field.name, valueType, isArrayVoidable)
} else {
const isNullable =
!field.type.isRequired &&
(field.defaultValue === undefined || field.defaultValue === null)

const isVoidable = isNullable && field.defaultValue === undefined

const valueType = isNullable ? nullable(name) : name // [1]

return options.isReturn ? valueType : kv(field.name, valueType, isVoidable)
}

if (field.type.isInput || field.type.isEnum) {
return `${field.type.name}${field.type.isArray ? '[]' : ''}${
isNullable ? '| null' : ''
}`
}

return `${getModelName(field.type, modelMap)}${
field.type.isArray ? '[]' : ''
}${isNullable ? '| null' : ''}`
}

export function getTypeFromGraphQLType(
Expand Down
24 changes: 14 additions & 10 deletions packages/graphqlgen/src/generators/flow-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,8 @@ function renderInputTypeInterfaces(
return `export interface ${upperFirst(type.name)}_${upperFirst(
inputTypesMap[typeAssociation].name,
)} {
${inputTypesMap[typeAssociation].fields.map(
field => `${field.name}: ${printFieldLikeType(field, modelMap)}`,
${inputTypesMap[typeAssociation].fields.map(field =>
printFieldLikeType(field, modelMap),
)}
}`
})
Expand Down Expand Up @@ -199,12 +199,11 @@ function renderInputArgInterface(
return `
export interface ${getInputArgName(type, field)} {
${field.arguments
.map(
arg =>
`${arg.name}: ${getArgTypePrefix(type, arg)}${printFieldLikeType(
arg as GraphQLTypeField,
modelMap,
)}`,
.map(arg =>
printFieldLikeType(arg as GraphQLTypeField, modelMap).replace(
': ',
`: ${getArgTypePrefix(type, arg)}`,
),
)
.join(',' + os.EOL)}
}
Expand Down Expand Up @@ -254,7 +253,10 @@ function renderResolverFunctionInterface(
info: GraphQLResolveInfo,
)
`
const returnType = printFieldLikeType(field, modelMap)

const returnType = printFieldLikeType(field, modelMap, {
isReturn: true,
})

if (type.name === 'Subscription') {
return `
Expand Down Expand Up @@ -299,7 +301,9 @@ function renderResolverTypeInterfaceFunction(
ctx: ${getContextName(context)},
info: GraphQLResolveInfo,
)`
const returnType = printFieldLikeType(field, modelMap)
const returnType = printFieldLikeType(field, modelMap, {
isReturn: true,
})

if (type.name === 'Subscription') {
return `
Expand Down
17 changes: 6 additions & 11 deletions packages/graphqlgen/src/generators/ts-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,8 @@ function renderInputTypeInterfaces(
return getDistinctInputTypes(type, typeToInputTypeAssociation, inputTypesMap)
.map(typeAssociation => {
return `export interface ${inputTypesMap[typeAssociation].name} {
${inputTypesMap[typeAssociation].fields.map(
field => `${field.name}: ${printFieldLikeType(field, modelMap)}`,
${inputTypesMap[typeAssociation].fields.map(field =>
printFieldLikeType(field, modelMap),
)}
}`
})
Expand All @@ -222,13 +222,7 @@ function renderInputArgInterface(
return `
export interface Args${upperFirst(field.name)} {
${field.arguments
.map(
arg =>
`${arg.name}: ${printFieldLikeType(
arg as GraphQLTypeField,
modelMap,
)}`,
)
.map(arg => printFieldLikeType(arg as GraphQLTypeField, modelMap))
.join(os.EOL)}
}
`
Expand Down Expand Up @@ -263,7 +257,8 @@ function renderResolverFunctionInterface(
info: GraphQLResolveInfo,
)
`
const returnType = printFieldLikeType(field, modelMap)

const returnType = printFieldLikeType(field, modelMap, { isReturn: true })

if (type.name === 'Subscription') {
return `
Expand Down Expand Up @@ -311,7 +306,7 @@ function renderResolverTypeInterfaceFunction(
info: GraphQLResolveInfo,
)
`
const returnType = printFieldLikeType(field, modelMap)
const returnType = printFieldLikeType(field, modelMap, { isReturn: true })

if (type.name === 'Subscription') {
return `
Expand Down
21 changes: 19 additions & 2 deletions packages/graphqlgen/src/source-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ type GraphQLTypeDefinition = {

export type GraphQLType = GraphQLTypeDefinition & {
isArray: boolean
isArrayRequired: boolean
isRequired: boolean
}

Expand Down Expand Up @@ -83,6 +84,7 @@ export type GraphQLUnionObject = {
interface FinalType {
isRequired: boolean
isArray: boolean
isArrayRequired: boolean
type: GraphQLInputType | GraphQLOutputType
}

Expand Down Expand Up @@ -130,13 +132,20 @@ function extractTypeDefinition(

const getFinalType = (
type: GraphQLInputType | GraphQLOutputType,
acc: FinalType = { isArray: false, isRequired: false, type },
acc: FinalType = {
isArray: false,
isArrayRequired: false,
isRequired: false,
type,
},
): FinalType => {
if (type instanceof GraphQLNonNull) {
acc.isRequired = true
}
if (type instanceof GraphQLList) {
acc.isArray = true
acc.isArrayRequired = acc.isRequired
acc.isRequired = false
}

if (type instanceof GraphQLNonNull || type instanceof GraphQLList) {
Expand All @@ -157,13 +166,21 @@ function extractTypeLike(
type: GraphQLInputType | GraphQLOutputType,
): GraphQLType {
const typeLike: GraphQLType = {} as GraphQLType
const { isArray, isRequired, type: finalType } = getFinalType(type)
const {
isArray,
isArrayRequired,
isRequired,
type: finalType,
} = getFinalType(type)
if (isRequired) {
typeLike.isRequired = true
}
if (isArray) {
typeLike.isArray = true
}
if (isArrayRequired) {
typeLike.isArrayRequired = true
}
if (
finalType instanceof GraphQLObjectType ||
finalType instanceof GraphQLInterfaceType ||
Expand Down
Loading

0 comments on commit 1fddfec

Please sign in to comment.