Skip to content

Commit

Permalink
BREAKING CHANGE: generate marker interfaces for union types by default (
Browse files Browse the repository at this point in the history
  • Loading branch information
danadajian committed Apr 24, 2024
1 parent 52f6b49 commit b917916
Show file tree
Hide file tree
Showing 44 changed files with 408 additions and 329 deletions.
6 changes: 5 additions & 1 deletion .releaserc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@ plugins:
- type: breaking
release: major
- type: docs
release: patch
release: false
- type: refactor
release: patch
- scope: no-release
release: false
parserOpts:
noteKeywords:
- BREAKING CHANGE
- BREAKING CHANGES
- "@semantic-release/github"
- "@semantic-release/npm"
- "@semantic-release/release-notes-generator"
Expand Down
Binary file modified bun.lockb
Binary file not shown.
2 changes: 1 addition & 1 deletion docs/docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ sidebar_position: 3
# Configuration

```ts reference title="Config Schema"
https://github.com/ExpediaGroup/graphql-kotlin-codegen/blob/main/src/config.ts#L17-L100000
https://github.com/ExpediaGroup/graphql-kotlin-codegen/blob/main/src/config.ts#L1-L100000
```
1 change: 1 addition & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export default [
},
},
rules: {
"no-console": "error",
"@typescript-eslint/no-non-null-assertion": "error",
"@typescript-eslint/no-unsafe-argument": "error",
"@typescript-eslint/no-unsafe-call": "error",
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"@graphql-codegen/cli": "5.0.2",
"@total-typescript/ts-reset": "0.5.1",
"bun-types": "1.1.4",
"eslint": "9.1.0",
"eslint": "9.1.1",
"husky": "9.0.11",
"prettier": "3.2.5",
"tsup": "8.0.2",
Expand All @@ -39,7 +39,7 @@
"build": "tsup src/plugin.ts --clean --dts --external graphql",
"format": "prettier --write .",
"format-check": "prettier --check .",
"integration": "bun run build && graphql-codegen && ./gradlew compileTestKotlin",
"integration": "bun run build && graphql-codegen && ./gradlew build",
"lint": "eslint .",
"prepack": "bun run build",
"prepare": "husky",
Expand Down
13 changes: 11 additions & 2 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
array,
boolean,
enum_,
literal,
object,
optional,
string,
Expand Down Expand Up @@ -44,11 +45,11 @@ export const configSchema = object({
dependentTypesInScope: optional(array(string())),
/**
* Denotes Kotlin classes representing union types to be treated as interfaces rather than annotation classes.
* This should be used for types outside `dependentTypesInScope` that are not generated by the plugin.
* @description This should be used for types outside `dependentTypesInScope` that are not generated by the plugin. Only use when unionGeneration is set to `ANNOTATION_CLASS`.
*/
externalUnionsAsInterfaces: optional(array(string())),
/**
* Additional imports to add to the generated file.
* Additional imports to add to the generated file. GraphQL Kotlin annotations are always imported.
* @example ["com.example.additional.import.*"]
*/
extraImports: optional(array(string())),
Expand Down Expand Up @@ -129,4 +130,12 @@ export const configSchema = object({
}),
),
),
/**
* Denotes the generation strategy for union types. Defaults to `MARKER_INTERFACE`.
* @description The `MARKER_INTERFACE` option is highly recommended, since it is more type-safe than using annotation classes.
* @link https://opensource.expediagroup.com/graphql-kotlin/docs/schema-generator/writing-schemas/unions/
*/
unionGeneration: optional(
union([literal("ANNOTATION_CLASS"), literal("MARKER_INTERFACE")]),
),
});
8 changes: 4 additions & 4 deletions src/definitions/enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ import { EnumTypeDefinitionNode, EnumValueDefinitionNode } from "graphql";
import { indentMultiline } from "@graphql-codegen/visitor-plugin-common";
import { buildAnnotations } from "../helpers/build-annotations";
import { shouldIncludeTypeDefinition } from "../helpers/should-include-type-definition";
import { CodegenConfig } from "../plugin";
import { CodegenConfigWithDefaults } from "../helpers/build-config-with-defaults";

export function buildEnumTypeDefinition(
node: EnumTypeDefinitionNode,
config: CodegenConfig,
config: CodegenConfigWithDefaults,
) {
if (!shouldIncludeTypeDefinition(node, config)) {
return "";
Expand All @@ -46,11 +46,11 @@ ${indentMultiline(enumValues.join(",\n") + ";", 2)}

function buildEnumValueDefinition(
node: EnumValueDefinitionNode,
config: CodegenConfig,
config: CodegenConfigWithDefaults,
) {
const annotations = buildAnnotations({
config,
definitionNode: node,
});
return `${annotations}${config.convert(node)}`;
return `${annotations}${config.convert?.(node)}`;
}
5 changes: 2 additions & 3 deletions src/definitions/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ import { shouldIncludeTypeDefinition } from "../helpers/should-include-type-defi
import { buildTypeMetadata } from "../helpers/build-type-metadata";
import { buildAnnotations } from "../helpers/build-annotations";
import { indent } from "@graphql-codegen/visitor-plugin-common";
import { CodegenConfig } from "../plugin";
import { CodegenConfigWithDefaults } from "../helpers/build-config-with-defaults";

export function buildInputObjectDefinition(
node: InputObjectTypeDefinitionNode,
schema: GraphQLSchema,
config: CodegenConfig,
config: CodegenConfigWithDefaults,
) {
if (!shouldIncludeTypeDefinition(node, config)) {
return "";
Expand All @@ -47,7 +47,6 @@ export function buildInputObjectDefinition(

const annotations = buildAnnotations({
config,
inputDescription: node.description?.value,
definitionNode: node,
});

Expand Down
4 changes: 2 additions & 2 deletions src/definitions/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ import { indent } from "@graphql-codegen/visitor-plugin-common";
import { buildTypeMetadata } from "../helpers/build-type-metadata";
import { shouldIncludeTypeDefinition } from "../helpers/should-include-type-definition";
import { buildFieldDefinition } from "../helpers/build-field-definition";
import { CodegenConfig } from "../plugin";
import { CodegenConfigWithDefaults } from "../helpers/build-config-with-defaults";

export function buildInterfaceDefinition(
node: InterfaceTypeDefinitionNode,
schema: GraphQLSchema,
config: CodegenConfig,
config: CodegenConfigWithDefaults,
) {
if (!shouldIncludeTypeDefinition(node, config)) {
return "";
Expand Down
26 changes: 17 additions & 9 deletions src/definitions/object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,19 @@ import { buildAnnotations } from "../helpers/build-annotations";
import { indent } from "@graphql-codegen/visitor-plugin-common";
import { buildTypeMetadata } from "../helpers/build-type-metadata";
import { shouldIncludeTypeDefinition } from "../helpers/should-include-type-definition";
import { getDependentInterfaceNames } from "../helpers/dependent-type-utils";
import {
getDependentInterfaceNames,
getDependentUnionsForType,
} from "../helpers/dependent-type-utils";
import { isResolverType } from "../helpers/is-resolver-type";
import { buildFieldDefinition } from "../helpers/build-field-definition";
import { isExternalField } from "../helpers/is-external-field";
import { CodegenConfig } from "../plugin";
import { CodegenConfigWithDefaults } from "../helpers/build-config-with-defaults";

export function buildObjectTypeDefinition(
node: ObjectTypeDefinitionNode,
schema: GraphQLSchema,
config: CodegenConfig,
config: CodegenConfigWithDefaults,
) {
if (!shouldIncludeTypeDefinition(node, config)) {
return "";
Expand All @@ -36,33 +39,38 @@ export function buildObjectTypeDefinition(
definitionNode: node,
});
const name = node.name.value;
const interfacesToInherit = getDependentInterfaceNames(node);
const dependentInterfaces = getDependentInterfaceNames(node);
const dependentUnions = getDependentUnionsForType(schema, node);
const interfacesToInherit =
config.unionGeneration === "MARKER_INTERFACE"
? dependentInterfaces.concat(dependentUnions)
: dependentInterfaces;
const interfaceInheritance = `${interfacesToInherit.length ? ` : ${interfacesToInherit.join(", ")}` : ""}`;

if (isResolverType(node, config)) {
return `${annotations}@GraphQLIgnore\ninterface ${name}${interfaceInheritance} {
${getClassMembers({ node, schema, config })}
${getDataClassMembers({ node, schema, config })}
}
${annotations}@GraphQLIgnore\ninterface ${name}CompletableFuture {
${getClassMembers({ node, schema, config, completableFuture: true })}
${getDataClassMembers({ node, schema, config, completableFuture: true })}
}`;
}

return `${annotations}data class ${name}(
${getClassMembers({ node, schema, config })}
${getDataClassMembers({ node, schema, config })}
)${interfaceInheritance}`;
}

function getClassMembers({
function getDataClassMembers({
node,
schema,
config,
completableFuture,
}: {
node: ObjectTypeDefinitionNode;
schema: GraphQLSchema;
config: CodegenConfig;
config: CodegenConfigWithDefaults;
completableFuture?: boolean;
}) {
const resolverType = isResolverType(node, config);
Expand Down
22 changes: 15 additions & 7 deletions src/definitions/union.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,33 @@ limitations under the License.

import { UnionTypeDefinitionNode } from "graphql";
import { shouldIncludeTypeDefinition } from "../helpers/should-include-type-definition";
import { buildDirectiveAnnotations } from "../helpers/build-directive-annotations";
import { CodegenConfig } from "../plugin";
import { trimDescription } from "../helpers/build-annotations";
import { CodegenConfigWithDefaults } from "../helpers/build-config-with-defaults";
import {
buildAnnotations,
trimDescription,
} from "../helpers/build-annotations";

export function buildUnionTypeDefinition(
node: UnionTypeDefinitionNode,
config: CodegenConfig,
config: CodegenConfigWithDefaults,
) {
if (!shouldIncludeTypeDefinition(node, config)) {
return "";
}
const annotations = buildAnnotations({
config,
definitionNode: node,
});
if (config.unionGeneration === "MARKER_INTERFACE") {
return `${annotations}interface ${node.name.value}`;
}

const directiveAnnotations = buildDirectiveAnnotations(node, config);
const possibleTypes =
node.types?.map((type) => `${type.name.value}::class`).join(", ") || "";
return `${directiveAnnotations}@GraphQLUnion(
return `${annotations}@GraphQLUnion(
name = "${node.name.value}",
possibleTypes = [${possibleTypes}],
description = "${node.description?.value ? trimDescription(node.description.value) : ""}"
description = "${trimDescription(node.description?.value)}"
)
annotation class ${node.name.value}`;
}
36 changes: 36 additions & 0 deletions src/helpers/add-dependent-types-to-only-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
Copyright 2024 Expedia, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import { CodegenConfigWithDefaults } from "./build-config-with-defaults";
import { getDependentTypeNames } from "./get-dependent-type-names";
import { GraphQLSchema } from "graphql";

export function addDependentTypesToOnlyTypes(
config: CodegenConfigWithDefaults,
schema: GraphQLSchema,
) {
if (!config.onlyTypes) {
throw new Error(`onlyTypes config is required to add dependent types`);
}
const onlyTypesNodes = config.onlyTypes
.map((typeName) => schema.getType(typeName)?.astNode)
.filter(Boolean);
const dependentTypeNames = onlyTypesNodes.flatMap((node) =>
getDependentTypeNames(schema, node, config),
);
const typesInScope = config.dependentTypesInScope;
const dependentTypesInScope = typesInScope
? dependentTypeNames.filter((typeName) => typesInScope.includes(typeName))
: dependentTypeNames;
config.onlyTypes.push(...dependentTypesInScope);
}
35 changes: 0 additions & 35 deletions src/helpers/add-dependent-types.ts

This file was deleted.

Loading

0 comments on commit b917916

Please sign in to comment.