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

(feature): Add Protobuf mapper types #4210

Merged
merged 10 commits into from
Aug 6, 2024
Merged

(feature): Add Protobuf mapper types #4210

merged 10 commits into from
Aug 6, 2024

Conversation

amckinney
Copy link
Contributor

@amckinney amckinney commented Aug 5, 2024

Overview

This adds support for gRPC/Protobuf types in IRv53. With this, generators can support mapping Fern-generated types to/from their Protobuf equivalent representation defined in the user's .proto file. This includes updating the IR, YAML definition schema, generator configuration, and OpenAPI extensions.

Example

Consider the following Protobuf service definition:

// proto/user/v1/user.proto

syntax = "proto3";

package user.v1;

import "google/api/annotations.proto";
import "google/api/field_behavior.proto";
import "google/protobuf/struct.proto";

option csharp_namespace = "User.V1";

message User {
    string username = 1; 
    string email = 2;
    uint age = 3;
    float weight = 4;
    google.protobuf.Struct metadata = 5;
}

message CreateUserRequest {
    string username = 1;
    string email = 2;
    uint age = 3;
    float weight = 4;
    google.protobuf.Struct metadata = 5;
}

message CreateUserResponse {
    User user = 1;
}

service UserService {
  rpc CreateUser(CreateUserRequest) returns (CreateResponse) {
    option (google.api.http) = {
      post: "/users"
      body: "*"
    };
  }
}

We now expose a few new configuration options to annotate the Fern definition with the context necessary to map to and from the message types defined in proto/user/v1/user.proto. In combination, we have a Fern definition that looks like the following:

# generators.yml
api:
  - proto:
       root: proto
       target: proto/user/v1/user.proto

Behind the scenes, this is equivalent to a Fern definition that looks like the following:

types:
  Metadata:
    type: map<string, optional<MetadataValue>>
    encoding:
      proto:
        type: google.protobuf.Struct

  MetadataValue:
    discriminated: false
    union:
      - double
      - string
      - boolean
      - list<MetadataValue>
    encoding:
      proto:
        type: google.protobuf.Value

  User:
    properties:
      id: string
      username: string
      email: optional<string>
      age: optional<uint>
      weight: optional<float>
      metadata: optional<Metadata>

  CreateUserResponse:
    properties:
      user: User

service:
  auth: false
  base-path: /
  transport:
    grpc:
      service-name: UserService
  endpoints:
    createUser:
      method: POST
      path: /users
      request:
        name: CreateUserRequest
        body:
          properties:
            username: string
            email: optional<string>
            age: optional<uint>
            weight: optional<float>
      response: CreateUserResponse

@@ -55,6 +56,7 @@ types:
variables: list<variables.VariableDeclaration>
serviceTypeReferenceInfo: ServiceTypeReferenceInfo
readmeConfig: optional<ReadmeConfig>
sourceConfig: optional<SourceConfig>
Copy link
Member

Choose a reason for hiding this comment

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

I think the reality is that the source can be multiple OpenAPI specs, FernDefinitions or Proto files. Thoughts on making this:

sources: 
  type: list<ApiDefinitionSource>
  docs: The raw API definitions that produced the IR 
  
ApiDefinitionSource: 
  union: 
    proto: ProtoSource
    openapi: OpenAPISource # can skip for now

Copy link
Member

Choose a reason for hiding this comment

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

The C# generator can just handle processing a single (the first) ProtoSource for now

Copy link
Member

@dsinghvi dsinghvi Aug 6, 2024

Choose a reason for hiding this comment

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

Also would be great if each source had a unique ApiDefinitionSourceId (so that in the future we can mark each type / endpoint with a particular source)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nice call - the ProtoSourceConfig as-is handles the multiple .proto files (via the root directory URL reference), but it doesn't include mixing and matching between Fern / OpenAPI / Proto.

I wonder if it's worth still wrapping sources in an object so it's easy to extend later. Thoughts on this mild adjustment?

SourceConfig:
  properties: 
    sources:
      docs: The raw API definitions that produced the IR.
      type: list<ApiDefinitionSource>
  
ApiDefinitionSource:
  union:
    proto: ProtoSource
    openapi: {}

* grpc:
* service-name: UserService
*/
TRANSPORT: "x-fern-transport"
Copy link
Member

Choose a reason for hiding this comment

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

Don't see any logic that actually pushes thes extensions into the fern definition. is that intentional?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, nothing implemented just yet - just spec'ing out the interface for now. I'll incorporate this when they're used in a follow-up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

2 participants