Skip to content

Commit

Permalink
Merge pull request #255 from proto-graphql/izumin5210/proto3-optional
Browse files Browse the repository at this point in the history
feat: support proto3 optional
  • Loading branch information
izumin5210 authored Dec 24, 2022
2 parents 1ee44db + 0d4a8a7 commit 3f531e4
Show file tree
Hide file tree
Showing 25 changed files with 640 additions and 2 deletions.
10 changes: 10 additions & 0 deletions .changeset/weak-apes-behave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@proto-graphql/e2e": patch
"@proto-graphql/codegen-core": patch
"@proto-graphql/proto-descriptors": patch
"@testapis/proto": patch
"protoc-gen-nexus": patch
"protoc-gen-pothos": patch
---

support proto3 optional
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ jobs:
node-version: 18.x
- run: yarn --frozen-lockfile
- run: yarn test
env:
NODE_OPTIONS: "--max_old_space_size=8192"
- name: Coveralls
uses: coverallsapp/github-action@v1.1.2
with:
Expand All @@ -38,3 +40,5 @@ jobs:
- uses: bufbuild/buf-setup-action@v1
- run: yarn --frozen-lockfile
- run: yarn test:e2e
env:
NODE_OPTIONS: "--max_old_space_size=8192"
3 changes: 3 additions & 0 deletions e2e/tests.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
{ "target": "nexus", "proto": { "package": "oneof", "lib": "protobufjs" } },
{ "target": "nexus", "proto": { "package": "primitives", "lib": "google-protobuf" } },
{ "target": "nexus", "proto": { "package": "primitives", "lib": "protobufjs" } },
{ "target": "nexus", "proto": { "package": "proto3_optional", "lib": "google-protobuf" } },
{ "target": "nexus", "proto": { "package": "proto3_optional", "lib": "protobufjs" } },
{ "target": "nexus", "proto": { "package": "wktypes", "lib": "google-protobuf" } },
{ "target": "nexus", "proto": { "package": "wktypes", "lib": "protobufjs" } },
{ "target": "pothos", "proto": { "package": "deprecation", "lib": "ts-proto" } },
Expand All @@ -36,6 +38,7 @@
{ "target": "pothos", "proto": { "package": "oneof", "lib": "ts-proto" } },
{ "target": "pothos", "proto": { "package": "primitives", "lib": "ts-proto" } },
{ "target": "pothos", "proto": { "package": "primitives", "lib": "ts-proto-with-forcelong-number" } },
{ "target": "pothos", "proto": { "package": "proto3_optional", "lib": "ts-proto" } },
{ "target": "pothos", "proto": { "package": "wktypes", "lib": "ts-proto" } },
{ "target": "pothos", "proto": { "package": "wktypes", "lib": "ts-proto-with-forcelong-number" } }
]
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions e2e/tests/nexus--proto3_optional--google-protobuf/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import "@proto-nexus/google-protobuf";
import { Message } from "@testapis/node-native/lib/testapis/proto3_optional/proto3_optional_pb";
import { queryField } from "nexus";

import { makeTestSchema } from "../../src/makeTestSchema";
import * as types1 from "../__generated__/nexus/google-protobuf/testapis/proto3_optional/proto3_optional_pb_nexus";

const testQuery = queryField("test1", {
type: "Message",
resolve() {
return new Message();
},
});

export const schema = makeTestSchema({
rootDir: __dirname,
types: [types1, testQuery],
});
17 changes: 17 additions & 0 deletions e2e/tests/nexus--proto3_optional--google-protobuf/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Code generated by setupTests.mjs. DO NOT EDIT.
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@testapp/*": [
"./*"
]
}
},
"include": [
"./**/*",
"../../src/**/*",
"../__generated__/nexus/google-protobuf/testapis/proto3_optional/**/*"
]
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions e2e/tests/nexus--proto3_optional--protobufjs/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import "@proto-nexus/google-protobuf";
import * as pb from "@testapis/node/lib/testapis/proto3_optional";
import { queryField } from "nexus";

import { makeTestSchema } from "../../src/makeTestSchema";
import * as types1 from "../__generated__/nexus/protobufjs/testapis/proto3_optional/proto3_optional_pb_nexus";

const testQuery = queryField("test1", {
type: "Message",
resolve() {
return new pb.testapis.proto3_optional.Message();
},
});

export const schema = makeTestSchema({
rootDir: __dirname,
types: [types1, testQuery],
});
17 changes: 17 additions & 0 deletions e2e/tests/nexus--proto3_optional--protobufjs/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Code generated by setupTests.mjs. DO NOT EDIT.
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@testapp/*": [
"./*"
]
}
},
"include": [
"./**/*",
"../../src/**/*",
"../__generated__/nexus/protobufjs/testapis/proto3_optional/**/*"
]
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions e2e/tests/pothos--proto3_optional--ts-proto/builder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import SchemaBuilder from "@pothos/core";

export const builder = new SchemaBuilder({});
builder.queryType({});
57 changes: 57 additions & 0 deletions e2e/tests/pothos--proto3_optional--ts-proto/schema.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { graphql } from "graphql";

import { schema } from "./schema";

it("processes a query successfully", async () => {
const resp = await graphql({
schema,
source: /* GraphQL */ `
query Test {
valuesArePresent {
...Message
}
}
fragment Message on Message {
requiredStringValue
optionalStringValue
}
`,
});
expect(resp).toMatchInlineSnapshot(`
{
"data": {
"valuesArePresent": {
"optionalStringValue": "optional field",
"requiredStringValue": "required field",
},
},
}
`);
});

it("processes a query successfully when values are blank", async () => {
const resp = await graphql({
schema,
source: /* GraphQL */ `
query Test {
valuesAreBlank {
...Message
}
}
fragment Message on Message {
requiredStringValue
optionalStringValue
}
`,
});
expect(resp).toMatchInlineSnapshot(`
{
"data": {
"valuesAreBlank": {
"optionalStringValue": null,
"requiredStringValue": "",
},
},
}
`);
});
30 changes: 30 additions & 0 deletions e2e/tests/pothos--proto3_optional--ts-proto/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Message } from "@testapis/ts-proto/lib/testapis/proto3_optional/proto3_optional";

import { printGraphqlSchema } from "../../src/printGraphqlSchema";
import { Message$Ref } from "../__generated__/pothos/ts-proto/testapis/proto3_optional/proto3_optional.pb.pothos";
import { builder } from "./builder";

builder.queryField("valuesArePresent", (f) =>
f.field({
type: Message$Ref,
resolve() {
return Message.fromPartial({
requiredStringValue: "required field",
optionalStringValue: "optional field",
});
},
})
);

builder.queryField("valuesAreBlank", (f) =>
f.field({
type: Message$Ref,
resolve() {
return Message.fromPartial({});
},
})
);

export const schema = builder.toSchema();

printGraphqlSchema({ schema, rootDir: __dirname });
17 changes: 17 additions & 0 deletions e2e/tests/pothos--proto3_optional--ts-proto/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Code generated by setupTests.mjs. DO NOT EDIT.
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@testapp/*": [
"./*"
]
}
},
"include": [
"./**/*",
"../../src/**/*",
"../__generated__/pothos/ts-proto/testapis/proto3_optional/**/*"
]
}
9 changes: 9 additions & 0 deletions e2e/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@
{
"path": "tests/nexus--primitives--protobufjs"
},
{
"path": "tests/nexus--proto3_optional--google-protobuf"
},
{
"path": "tests/nexus--proto3_optional--protobufjs"
},
{
"path": "tests/nexus--wktypes--google-protobuf"
},
Expand Down Expand Up @@ -110,6 +116,9 @@
{
"path": "tests/pothos--primitives--ts-proto-with-forcelong-number"
},
{
"path": "tests/pothos--proto3_optional--ts-proto"
},
{
"path": "tests/pothos--wktypes--ts-proto"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export class ObjectType extends TypeBase<ProtoMessage> {
get fields(): (ObjectField<any> | ObjectOneofField)[] {
return [
...this.proto.fields
.filter((f) => f.containingOneof == null)
.filter((f) => f.containingOneof == null || f.containingOneof.synthetic)
.filter((f) => !isInputOnlyField(f))
.filter((f) => !isIgnoredField(f))
.map(
Expand Down
6 changes: 5 additions & 1 deletion packages/@proto-graphql/codegen-core/src/types/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ export function isRequiredField(
}
}
if (fieldType === "partial_input") return false;
if (field.kind === "Field" && field.optional) return false;
return hasRequiredLabel(field);
}

Expand Down Expand Up @@ -239,7 +240,7 @@ export function isIgnoredField(
return true;
}
const oneof = field.containingOneof;
if (oneof && isIgnoredField(oneof)) {
if (oneof && !oneof.synthetic && isIgnoredField(oneof)) {
return true;
}
ext = getExtension(field, "field");
Expand All @@ -249,6 +250,9 @@ export function isIgnoredField(
if (isIgnoredType(field.parent)) {
return true;
}
if (field.synthetic) {
return true;
}
ext = getExtension(field, "oneof");
} else {
const _exhaustiveCheck: never = field;
Expand Down
10 changes: 10 additions & 0 deletions packages/@proto-graphql/proto-descriptors/src/impls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,11 @@ export class ProtoOneofImpl implements ProtoOneof {
get deprecated(): boolean {
return isDeprecated(this);
}

@memo()
get synthetic(): boolean {
return this.fields.length === 1 && this.fields[0].optional;
}
}

export class ProtoFieldImpl implements ProtoField {
Expand Down Expand Up @@ -385,6 +390,11 @@ export class ProtoFieldImpl implements ProtoField {
);
}

@memo()
get optional(): boolean {
return this.descriptor.getProto3Optional() ?? false;
}

@memo()
get comments(): CommentSet {
return getCommentSetByDescriptors(this);
Expand Down
6 changes: 6 additions & 0 deletions packages/@proto-graphql/proto-descriptors/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,11 @@ export interface ProtoOneof extends ProtoBase<"Oneof"> {
readonly fields: ProtoField[];
readonly comments: CommentSet;
readonly deprecated: boolean;
/**
* return true if this is synthetic oneof for backward compat for proto3 optional.
* @see https://github.com/protocolbuffers/protobuf/blob/v21.12/docs/implementing_proto3_presence.md#updating-a-code-generator
*/
readonly synthetic: boolean;
}

export interface ProtoField extends ProtoBase<"Field"> {
Expand All @@ -158,6 +163,7 @@ export interface ProtoField extends ProtoBase<"Field"> {
readonly type: ProtoMessage | ProtoEnum | ProtoScalar;
readonly containingOneof: ProtoOneof | null;
readonly list: boolean;
readonly optional: boolean;
readonly comments: CommentSet;
readonly deprecated: boolean;
}
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
syntax = "proto3";

package testapis.proto3_optional;

message Message {
string required_string_value = 1;
optional string optional_string_value = 2;
}
Loading

0 comments on commit 3f531e4

Please sign in to comment.