Skip to content

Commit

Permalink
Fix abstract types not being handled when nested in object types (#10014
Browse files Browse the repository at this point in the history
)
  • Loading branch information
eddeee888 authored Jul 2, 2024
1 parent 673857c commit 79fee3c
Show file tree
Hide file tree
Showing 8 changed files with 615 additions and 268 deletions.
6 changes: 6 additions & 0 deletions .changeset/grumpy-clocks-retire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@graphql-codegen/visitor-plugin-common': patch
'@graphql-codegen/typescript-resolvers': patch
---

Fix object types with fields being abstract types not pointing to resolver types correctly
13 changes: 9 additions & 4 deletions dev-test/modules/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,10 +171,12 @@ export type ResolversUnionTypes<_RefType extends Record<string, unknown>> = {

/** Mapping between all available schema types and the resolvers types */
export type ResolversTypes = {
Article: ResolverTypeWrapper<Article>;
Article: ResolverTypeWrapper<Omit<Article, 'author'> & { author: ResolversTypes['User'] }>;
Boolean: ResolverTypeWrapper<Scalars['Boolean']['output']>;
CreditCard: ResolverTypeWrapper<CreditCard>;
Donation: ResolverTypeWrapper<Donation>;
Donation: ResolverTypeWrapper<
Omit<Donation, 'recipient' | 'sender'> & { recipient: ResolversTypes['User']; sender: ResolversTypes['User'] }
>;
DonationInput: DonationInput;
Float: ResolverTypeWrapper<Scalars['Float']['output']>;
ID: ResolverTypeWrapper<Scalars['ID']['output']>;
Expand All @@ -191,10 +193,13 @@ export type ResolversTypes = {

/** Mapping between all available schema types and the resolvers parents */
export type ResolversParentTypes = {
Article: Article;
Article: Omit<Article, 'author'> & { author: ResolversParentTypes['User'] };
Boolean: Scalars['Boolean']['output'];
CreditCard: CreditCard;
Donation: Donation;
Donation: Omit<Donation, 'recipient' | 'sender'> & {
recipient: ResolversParentTypes['User'];
sender: ResolversParentTypes['User'];
};
DonationInput: DonationInput;
Float: Scalars['Float']['output'];
ID: Scalars['ID']['output'];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,7 @@ export class BaseResolversVisitor<
protected _hasFederation = false;
protected _fieldContextTypeMap: FieldContextTypeMap;
protected _directiveContextTypesMap: FieldContextTypeMap;
protected _checkedTypesWithNestedAbstractTypes: Record<string, { checkStatus: 'yes' | 'no' | 'checking' }> = {};
private _directiveResolverMappings: Record<string, string>;
private _shouldMapType: { [typeName: string]: boolean } = {};

Expand Down Expand Up @@ -1788,8 +1789,23 @@ export class BaseResolversVisitor<
const baseType = getBaseType(field.type);
const isUnion = isUnionType(baseType);
const isInterface = isInterfaceType(baseType);
const isObject = isObjectType(baseType);
let isObjectWithAbstractType = false;

if (!this.config.mappers[baseType.name] && !isUnion && !isInterface && !this._shouldMapType[baseType.name]) {
if (isObject) {
isObjectWithAbstractType = checkIfObjectTypeHasAbstractTypesRecursively(baseType, {
isObjectWithAbstractType,
checkedTypesWithNestedAbstractTypes: this._checkedTypesWithNestedAbstractTypes,
});
}

if (
!this.config.mappers[baseType.name] &&
!isUnion &&
!isInterface &&
!this._shouldMapType[baseType.name] &&
!isObjectWithAbstractType
) {
return null;
}

Expand Down Expand Up @@ -1835,3 +1851,84 @@ function normalizeResolversNonOptionalTypename(
...input,
};
}

function checkIfObjectTypeHasAbstractTypesRecursively(
baseType: GraphQLObjectType,
result: {
isObjectWithAbstractType: boolean;
checkedTypesWithNestedAbstractTypes: Record<string, { checkStatus: 'yes' | 'no' | 'checking' }>;
}
): boolean {
if (
result.checkedTypesWithNestedAbstractTypes[baseType.name] &&
(result.checkedTypesWithNestedAbstractTypes[baseType.name].checkStatus === 'yes' ||
result.checkedTypesWithNestedAbstractTypes[baseType.name].checkStatus === 'no')
) {
return result.checkedTypesWithNestedAbstractTypes[baseType.name].checkStatus === 'yes';
}

result.checkedTypesWithNestedAbstractTypes[baseType.name] ||= { checkStatus: 'checking' };

let atLeastOneFieldWithAbstractType = false;

const fields = baseType.getFields();
for (const field of Object.values(fields)) {
const fieldBaseType = getBaseType(field.type);

// If the field is self-referencing, skip it. Otherwise, it's an infinite loop
if (baseType.name === fieldBaseType.name) {
continue;
}

// If the current field has been checked, and it has nested abstract types,
// mark the parent type as having nested abstract types
if (result.checkedTypesWithNestedAbstractTypes[fieldBaseType.name]) {
if (result.checkedTypesWithNestedAbstractTypes[fieldBaseType.name].checkStatus === 'yes') {
atLeastOneFieldWithAbstractType = true;
result.checkedTypesWithNestedAbstractTypes[baseType.name].checkStatus = 'yes';
}
continue;
} else {
result.checkedTypesWithNestedAbstractTypes[fieldBaseType.name] = { checkStatus: 'checking' };
}

// If the field is an abstract type, then both the field type and parent type are abstract types
if (isInterfaceType(fieldBaseType) || isUnionType(fieldBaseType)) {
atLeastOneFieldWithAbstractType = true;
result.checkedTypesWithNestedAbstractTypes[fieldBaseType.name].checkStatus = 'yes';
result.checkedTypesWithNestedAbstractTypes[baseType.name].checkStatus = 'yes';
continue;
}

// If the field is an object, check it recursively to see if it has abstract types
// If it does, both field type and parent type have abstract types
if (isObjectType(fieldBaseType)) {
// IMPORTANT: we are pointing the parent type to the field type here
// to make sure when the field type is updated to either 'yes' or 'no', it becomes the parent's type as well
if (result.checkedTypesWithNestedAbstractTypes[baseType.name].checkStatus === 'checking') {
result.checkedTypesWithNestedAbstractTypes[baseType.name] =
result.checkedTypesWithNestedAbstractTypes[fieldBaseType.name];
}

const foundAbstractType = checkIfObjectTypeHasAbstractTypesRecursively(fieldBaseType, result);
if (foundAbstractType) {
atLeastOneFieldWithAbstractType = true;
result.checkedTypesWithNestedAbstractTypes[fieldBaseType.name].checkStatus = 'yes';
result.checkedTypesWithNestedAbstractTypes[baseType.name].checkStatus = 'yes';
}
continue;
}

// Otherwise, the current field type is not abstract type
// This includes scalar types, enums, input types and objects without abstract types
result.checkedTypesWithNestedAbstractTypes[fieldBaseType.name].checkStatus = 'no';
}

if (atLeastOneFieldWithAbstractType) {
result.isObjectWithAbstractType = true;
} else {
result.checkedTypesWithNestedAbstractTypes[baseType.name].checkStatus = 'no';
}

return atLeastOneFieldWithAbstractType;
}
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ export type DirectiveResolverFn<TResult = {}, TParent = {}, TContext = {}, TArgs

/** Mapping of union types */
export type ResolversUnionTypes<_RefType extends Record<string, unknown>> = ResolversObject<{
ChildUnion: ( Child ) | ( MyOtherType );
ChildUnion: ( Omit<Child, 'parent'> & { parent?: Maybe<_RefType['MyType']> } ) | ( MyOtherType );
MyUnion: ( Omit<MyType, 'unionChild'> & { unionChild?: Maybe<_RefType['ChildUnion']> } ) | ( MyOtherType );
}>;

Expand All @@ -184,7 +184,7 @@ export type ResolversInterfaceTypes<_RefType extends Record<string, unknown>> =
export type ResolversTypes = ResolversObject<{
MyType: ResolverTypeWrapper<Omit<MyType, 'unionChild'> & { unionChild?: Maybe<ResolversTypes['ChildUnion']> }>;
String: ResolverTypeWrapper<Scalars['String']['output']>;
Child: ResolverTypeWrapper<Child>;
Child: ResolverTypeWrapper<Omit<Child, 'parent'> & { parent?: Maybe<ResolversTypes['MyType']> }>;
MyOtherType: ResolverTypeWrapper<MyOtherType>;
ChildUnion: ResolverTypeWrapper<ResolversUnionTypes<ResolversTypes>['ChildUnion']>;
Query: ResolverTypeWrapper<{}>;
Expand All @@ -207,7 +207,7 @@ export type ResolversTypes = ResolversObject<{
export type ResolversParentTypes = ResolversObject<{
MyType: Omit<MyType, 'unionChild'> & { unionChild?: Maybe<ResolversParentTypes['ChildUnion']> };
String: Scalars['String']['output'];
Child: Child;
Child: Omit<Child, 'parent'> & { parent?: Maybe<ResolversParentTypes['MyType']> };
MyOtherType: MyOtherType;
ChildUnion: ResolversUnionTypes<ResolversParentTypes>['ChildUnion'];
Query: {};
Expand Down Expand Up @@ -427,7 +427,7 @@ export type DirectiveResolverFn<TResult = {}, TParent = {}, TContext = {}, TArgs

/** Mapping of union types */
export type ResolversUnionTypes<_RefType extends Record<string, unknown>> = ResolversObject<{
ChildUnion: ( Types.Child ) | ( Types.MyOtherType );
ChildUnion: ( Omit<Types.Child, 'parent'> & { parent?: Types.Maybe<_RefType['MyType']> } ) | ( Types.MyOtherType );
MyUnion: ( Omit<Types.MyType, 'unionChild'> & { unionChild?: Types.Maybe<_RefType['ChildUnion']> } ) | ( Types.MyOtherType );
}>;

Expand All @@ -443,7 +443,7 @@ export type ResolversInterfaceTypes<_RefType extends Record<string, unknown>> =
export type ResolversTypes = ResolversObject<{
MyType: ResolverTypeWrapper<Omit<Types.MyType, 'unionChild'> & { unionChild?: Types.Maybe<ResolversTypes['ChildUnion']> }>;
String: ResolverTypeWrapper<Types.Scalars['String']['output']>;
Child: ResolverTypeWrapper<Types.Child>;
Child: ResolverTypeWrapper<Omit<Types.Child, 'parent'> & { parent?: Types.Maybe<ResolversTypes['MyType']> }>;
MyOtherType: ResolverTypeWrapper<Types.MyOtherType>;
ChildUnion: ResolverTypeWrapper<ResolversUnionTypes<ResolversTypes>['ChildUnion']>;
Query: ResolverTypeWrapper<{}>;
Expand All @@ -466,7 +466,7 @@ export type ResolversTypes = ResolversObject<{
export type ResolversParentTypes = ResolversObject<{
MyType: Omit<Types.MyType, 'unionChild'> & { unionChild?: Types.Maybe<ResolversParentTypes['ChildUnion']> };
String: Types.Scalars['String']['output'];
Child: Types.Child;
Child: Omit<Types.Child, 'parent'> & { parent?: Types.Maybe<ResolversParentTypes['MyType']> };
MyOtherType: Types.MyOtherType;
ChildUnion: ResolversUnionTypes<ResolversParentTypes>['ChildUnion'];
Query: {};
Expand Down Expand Up @@ -772,7 +772,7 @@ export type DirectiveResolverFn<TResult = {}, TParent = {}, TContext = {}, TArgs

/** Mapping of union types */
export type ResolversUnionTypes<_RefType extends Record<string, unknown>> = ResolversObject<{
ChildUnion: ( Child ) | ( MyOtherType );
ChildUnion: ( Omit<Child, 'parent'> & { parent?: Maybe<_RefType['MyType']> } ) | ( MyOtherType );
MyUnion: ( Omit<MyType, 'unionChild'> & { unionChild?: Maybe<_RefType['ChildUnion']> } ) | ( MyOtherType );
}>;

Expand All @@ -788,7 +788,7 @@ export type ResolversInterfaceTypes<_RefType extends Record<string, unknown>> =
export type ResolversTypes = ResolversObject<{
MyType: ResolverTypeWrapper<Omit<MyType, 'unionChild'> & { unionChild?: Maybe<ResolversTypes['ChildUnion']> }>;
String: ResolverTypeWrapper<Scalars['String']['output']>;
Child: ResolverTypeWrapper<Child>;
Child: ResolverTypeWrapper<Omit<Child, 'parent'> & { parent?: Maybe<ResolversTypes['MyType']> }>;
MyOtherType: ResolverTypeWrapper<MyOtherType>;
ChildUnion: ResolverTypeWrapper<ResolversUnionTypes<ResolversTypes>['ChildUnion']>;
Query: ResolverTypeWrapper<{}>;
Expand All @@ -811,7 +811,7 @@ export type ResolversTypes = ResolversObject<{
export type ResolversParentTypes = ResolversObject<{
MyType: Omit<MyType, 'unionChild'> & { unionChild?: Maybe<ResolversParentTypes['ChildUnion']> };
String: Scalars['String']['output'];
Child: Child;
Child: Omit<Child, 'parent'> & { parent?: Maybe<ResolversParentTypes['MyType']> };
MyOtherType: MyOtherType;
ChildUnion: ResolversUnionTypes<ResolversParentTypes>['ChildUnion'];
Query: {};
Expand Down
Loading

0 comments on commit 79fee3c

Please sign in to comment.