Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add gRPC reflection and file descriptor set support #1086

Merged
merged 10 commits into from
Jan 20, 2021

Conversation

tatemz
Copy link
Contributor

@tatemz tatemz commented Oct 21, 2020

Overview

Note: This is not yet complete. See "Discussion Notes" below.

This change adds two new features to the gRPC handler.

  • Reflection support
  • File descriptor set support

Both of these features make it easier for graphql-mesh to automatically create a schema for gRPC.

useReflection: boolean

This config option enables graphql-mesh to generate a schema by querying the gRPC reflection endpoints. This feature is enabled by the grpc-reflection-js package.

descriptorSetFilePath: object | string

This config option enabled graphql-mesh to generate a schema by importing either a binary-encoded file descriptor set file or a JSON file descriptor set file. This config works just like protoFilePath and can be a string or an object containing the file and proto loader options.

Binary-encoded file descriptor sets can be created by using protoc with the --descriptor_set_out option. Example:

protoc -I . --descriptor_set_out=./my-descriptor-set.bin ./my-rpc.proto

JSON file descriptor sets can be created using protobufjs/protobuf.js.

Todo

Discussion Notes

@grpc/grpc-js vs grpc

This issue has been resolved by redhoyasa/grpc-reflection-js#3

There is a conflict when using grpc-reflection-js. The CredentialsChannel type used by it is different than the CredentialsChannel type used by @grpc/grpc-js. This is why I swapped in grpc. However, on the grpc package README, it states that this package is deprecated (presumably in favor of @grpc/grpc-node? 🤔 🤷‍♂️)
I find this odd, because when using the JS protoc plugin, all of the generated gRPC server/client code uses the grpc package.
I suppose until protoc updates it's output dependency, then using grpc should be ok. Alternatively, we can submit a PR to grpc-reflection-js to update that dependency to use @grpc/grpc-js. Whew 😅

createPackageDefinition

The use of createPackageDefinition is not recommended as discussed in grpc/grpc-node#1627. Instead grpc/grpc-node#1635 was raised.

This change uses createPackageDefinition from @grpc/proto-loader. There's only one problem... that function isn't exported.
Follow these two issues to read more:

To get this feature to work, edit the following files:

$ echo 'export declare function createPackageDefinition(root: Protobuf.Root, options?: Options): PackageDefinition;' >> ./node_modules/@grpc/proto-loader/build/src/index.d.ts
$ echo 'exports.createPackageDefinition = createPackageDefinition;' >> node_modules/@grpc/proto-loader/build/src/index.js

What do you suggest we do to make this shippable?

New Config

This change adds two new config properties to the grpc handler. To enable this feature, either set useReflection or descriptorSetFilePath

Sample reflection .meshrc.yaml

sources:
  - name: MyGrpcApi
    handler:
      grpc:
        endpoint: localhost:50051
        useReflection: true

Sample binary-encoded file descriptor set .meshrc.yaml

sources:
  - name: MyGrpcApi
    handler:
      grpc:
        endpoint: localhost:50051
        descriptorSetFilePath: path/to/descriptor.bin

Sample JSON file descriptor set .meshrc.yaml

sources:
  - name: MyGrpcApi
    handler:
      grpc:
        endpoint: localhost:50051
        descriptorSetFilePath: path/to/descriptor.json

@changeset-bot
Copy link

changeset-bot bot commented Oct 21, 2020

🦋 Changeset detected

Latest commit: 557923a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 61 packages
Name Type
@graphql-mesh/grpc Minor
@graphql-mesh/types Minor
grpc-example Patch
@graphql-mesh/config Patch
@graphql-mesh/runtime Patch
@graphql-mesh/cache-file Patch
@graphql-mesh/cache-inmemory-lru Patch
@graphql-mesh/cache-localforage Patch
@graphql-mesh/cache-redis Patch
@graphql-mesh/fhir Patch
@graphql-mesh/graphql Patch
@graphql-mesh/json-schema Patch
@graphql-mesh/mongoose Patch
@graphql-mesh/mysql Patch
@graphql-mesh/neo4j Patch
@graphql-mesh/odata Patch
@graphql-mesh/openapi Patch
@graphql-mesh/postgraphile Patch
@graphql-mesh/soap Patch
@graphql-mesh/thrift Patch
@graphql-mesh/tuql Patch
@graphql-mesh/transform-cache Patch
@graphql-mesh/transform-encapsulate Patch
@graphql-mesh/transform-extend Patch
@graphql-mesh/transform-federation Patch
@graphql-mesh/transform-filter-schema Patch
@graphql-mesh/transform-mock Patch
@graphql-mesh/transform-naming-convention Patch
@graphql-mesh/transform-prefix Patch
@graphql-mesh/transform-rename Patch
@graphql-mesh/transform-resolvers-composition Patch
@graphql-mesh/transform-snapshot Patch
@graphql-mesh/merger-bare Patch
@graphql-mesh/merger-federation Patch
@graphql-mesh/merger-stitching Patch
@graphql-mesh/cli Patch
typescript-location-weather-example Patch
postgres-geodb-example Patch
openapi-react-weatherbit Patch
neo4j-example Patch
json-schema-fhir Patch
federation-handler-example Patch
graphql-file-upload-example Patch
covid-mesh Patch
hasura-openbrewery-geodb Patch
json-schema-example Patch
json-schema-subscriptions Patch
subscriptions-example Patch
mongoose-example Patch
mysql-employees Patch
mysql-rfam Patch
odata-microsoft-graph-example Patch
odata-trippin-example Patch
javascript-wiki Patch
openapi-stackexchange Patch
openapi-stripe Patch
openapi-subscriptions Patch
openapi-youtrack Patch
country-info-example Patch
thrift-calculator Patch
chinook Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Contributor Author

@tatemz tatemz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some notes showing my thought process.

@@ -21,6 +21,7 @@
"fs-extra": "9.0.1",
"graphql-scalars": "1.3.0",
"graphql-compose": "7.21.4",
"grpc-reflection-js": "^0.0.5",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See "Discussion Notes" in the PR regarding why this requires swapping in grpc

import { load } from '@grpc/proto-loader';
PackageDefinition,
} from 'grpc';
import { createPackageDefinition, load } from '@grpc/proto-loader';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

createPackageDefinition isn't exported by @grpc/proto-loader.
What can we do to get this?

services.map((service: string) => reflectionClient.fileContainingSymbol(service))
);
serviceRoots.forEach((serviceRoot: Root) => {
serviceRoot.name = this.config.serviceName || '';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By default, the first nested object will have a name of "". We add the config.serviceName to properly nest the objects for naming.

if (this.config.useReflection) {
const grpcReflectionServer = this.config.endpoint;
const reflectionClient = new grpcReflection.Client(grpcReflectionServer, creds);
const services = (await reflectionClient.listServices()) as string[];
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

services will include the strings of all of the services exposed by gRPC reflection... including the grpc.reflection.v1alpha ServerReflection service. It's quite meta... GQL can then be used to forward the reflection.

responseType = camelCase(
currentPath.split('.').join('_') + '_' + responseType.replace(reflectionPath, '')
);
requestType = camelCase(currentPath.split('.').join('_') + '_' + requestType.replace(reflectionPath, ''));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This stuff was tricky. The requestType and responseType properties under methods are in different format when we use reflection.

@tatemz tatemz force-pushed the feature/grpc-reflection-support branch from 586742b to 0c34937 Compare October 21, 2020 17:39
}
const protoDefinition = await root.load(fileName as string, options);
protoDefinition.resolveAll();
packageDefinition = await load(fileName as string, options);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of the original protoFilePath logic should still work

@tatemz tatemz force-pushed the feature/grpc-reflection-support branch from 0c34937 to c63eea0 Compare October 21, 2020 17:44
@tatemz tatemz changed the title Add gRPC reflection support Add gRPC reflection and file descriptor set support Nov 24, 2020
@tatemz tatemz force-pushed the feature/grpc-reflection-support branch from 251f0a5 to 967618d Compare November 24, 2020 04:28
@ardatan
Copy link
Owner

ardatan commented Nov 24, 2020

@tatemz That is really COOL! Thank you!
This is not ready to merge yet, right?

@tatemz
Copy link
Contributor Author

tatemz commented Nov 24, 2020

Correct. We are awaiting progress on grpc/grpc-node#1627. You can test it out by building the ./packages/proto-loader package in that PR and including the build/src in graphql-mesh.

This change adds a new feature to the gRPC handler that allows
graphql-mesh to automatically create a schema by querying
the reflection endpoints on a gRPC service that exposes its
reflection service. This feature is enabled by adding the
grpc-reflection-js package.

Additionally, this change replaces dependencies from @grpc/grpc-js with
grpc as that is the package that is used with generated proto code.
The grpc package works in conjunction with the grpc-reflection-js
package.
This change reverts back to using @grpc/grpc-js instead of grpc since
the grpc-reflection-js package no longer has conflicting implementation.

See redhoyasa/grpc-reflection-js#3
This change syncs @grpc/grpc-js versions for compatibility with
grpc-reflection-js.
This change adds a new grpc handler configuration property to specify
either a binary-encoded or JSON file descriptor set.
This change fixes an issue where the gRPC reflection feature was not
correctly adding gathered gRPC service root objects.
This change updates the grpc handler feature supporting reflection and
file descriptor sets using the latest proto-loader api.
@ardatan ardatan force-pushed the feature/grpc-reflection-support branch from 8aa53d7 to a2365f2 Compare January 20, 2021 21:20
@ardatan ardatan merged commit 183cfa9 into ardatan:master Jan 20, 2021
@tatemz
Copy link
Contributor Author

tatemz commented Jan 20, 2021

Sorry I didn't keep track of this. Thanks for carrying the torch @ardatan ! 💯

@alexisvisco
Copy link

Didn't work at all in the version ^0.14.9.
The buffer reader transform the .json into object that is then passed to Buffer.from.

Buffer.from does not accept json...

klippx pushed a commit to klippx/graphql-mesh that referenced this pull request Oct 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants