Skip to content

Commit

Permalink
fix(inputs): support overwriting fields of extended types
Browse files Browse the repository at this point in the history
  • Loading branch information
MichalLytek committed Nov 7, 2021
1 parent 178d742 commit 3ac1a27
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 3 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# Changelog and release notes

<!-- ## Unreleased -->
## Unreleased
<!-- here goes all the unreleased changes descriptions -->
### Fixes
- support overwriting fields of extended types (#1109)

## v1.2.0-rc.1
### Features
Expand Down
7 changes: 6 additions & 1 deletion src/resolvers/convert-args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,12 @@ function generateInstanceTransformationTree(target: TypeValue): TransformationTr
while (superClass.prototype !== undefined) {
const superInputType = getInputType(superClass);
if (superInputType) {
inputFields = [...inputFields, ...superInputType.fields!];
// support overwriting fields of extended types
const existingFieldNames = new Set(inputFields.map(field => field.name));
const superFields = superInputType.fields!.filter(
field => !existingFieldNames.has(field.name),
);
inputFields = [...inputFields, ...superFields];
}
superClass = Object.getPrototypeOf(superClass);
}
Expand Down
87 changes: 86 additions & 1 deletion tests/functional/interfaces-and-inheritance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
} from "graphql";

import { getSchemaInfo } from "../helpers/getSchemaInfo";
import { getInnerFieldType } from "../helpers/getInnerFieldType";
import { getInnerFieldType, getInnerInputFieldType } from "../helpers/getInnerFieldType";
import { getMetadataStorage } from "../../src/metadata/getMetadataStorage";
import { GeneratingSchemaError } from "../../src/errors";
import {
Expand Down Expand Up @@ -43,6 +43,7 @@ describe("Interfaces and inheritance", () => {
let sampleImplementingObject1Type: IntrospectionObjectType;
let sampleImplementingObject2Type: IntrospectionObjectType;
let sampleExtendingObject2Type: IntrospectionObjectType;
let sampleSecondExtendedInputType: IntrospectionInputObjectType;

beforeAll(async () => {
getMetadataStorage().clear();
Expand Down Expand Up @@ -133,6 +134,28 @@ describe("Interfaces and inheritance", () => {
extendingInputField: boolean;
}

// overwriting fields case
@InputType()
class SampleFirstBaseInput {
@Field()
baseField: string;
}
@InputType()
class SampleFirstExtendedInput extends SampleFirstBaseInput {
@Field()
extendedField: string;
}
@InputType()
class SampleSecondBaseInput {
@Field()
baseInputField: SampleFirstBaseInput;
}
@InputType()
class SampleSecondExtendedInput extends SampleSecondBaseInput {
@Field()
baseInputField: SampleFirstExtendedInput;
}

class SampleResolver {
@Query()
sampleQuery(): boolean {
Expand Down Expand Up @@ -162,6 +185,7 @@ describe("Interfaces and inheritance", () => {
SampleMultiImplementingObject,
SampleExtendingImplementingObject,
SampleExtendingObject2,
SampleSecondExtendedInput,
],
});
queryType = schemaInfo.queryType;
Expand Down Expand Up @@ -190,6 +214,9 @@ describe("Interfaces and inheritance", () => {
sampleExtendingObject2Type = schemaIntrospection.types.find(
type => type.name === "SampleExtendingObject2",
) as IntrospectionObjectType;
sampleSecondExtendedInputType = schemaIntrospection.types.find(
type => type.name === "SampleSecondExtendedInput",
) as IntrospectionInputObjectType;
});

// helpers
Expand Down Expand Up @@ -396,6 +423,15 @@ describe("Interfaces and inheritance", () => {
expect(extendingInputFieldType.name).toEqual("Boolean");
});

it("should properly overwrite input type field", () => {
const baseInputFieldType = getInnerInputFieldType(
sampleSecondExtendedInputType,
"baseInputField",
);

expect(baseInputFieldType.name).toEqual("SampleFirstExtendedInput");
});

it("shouldn't throw error when extending wrong class type", async () => {
getMetadataStorage().clear();

Expand Down Expand Up @@ -645,6 +681,28 @@ describe("Interfaces and inheritance", () => {
sampleField: string;
}

// overwriting fields case
@InputType()
class SampleFirstBaseInput {
@Field()
baseField: string;
}
@InputType()
class SampleFirstExtendedInput extends SampleFirstBaseInput {
@Field()
extendedField: string;
}
@InputType()
class SampleSecondBaseInput {
@Field()
baseInputField: SampleFirstBaseInput;
}
@InputType()
class SampleSecondExtendedInput extends SampleSecondBaseInput {
@Field()
baseInputField: SampleFirstExtendedInput;
}

@Resolver()
class InterfacesResolver {
@Query()
Expand Down Expand Up @@ -719,6 +777,12 @@ describe("Interfaces and inheritance", () => {
obj.interfaceFieldToBeRenamed = "interfaceFieldToBeRenamed";
return obj;
}

@Mutation()
overwritingInputFieldMutation(@Arg("input") input: SampleSecondExtendedInput): boolean {
mutationInput = input;
return true;
}
}

schema = await buildSchema({
Expand Down Expand Up @@ -923,6 +987,27 @@ describe("Interfaces and inheritance", () => {
});

it("should allow to return plain object when return type is a class that implements an interface", async () => {
const query = `mutation {
overwritingInputFieldMutation(input: {
baseInputField: {
baseField: "baseField",
extendedField: "extendedField",
}
})
}`;

const { errors } = await graphql(schema, query);

expect(errors).toBeUndefined();
expect(mutationInput).toEqual({
baseInputField: {
baseField: "baseField",
extendedField: "extendedField",
},
});
});

it("should correctly transform data of overwritten input field", async () => {
const query = `query {
secondImplementationPlainQuery {
baseInterfaceField
Expand Down

0 comments on commit 3ac1a27

Please sign in to comment.