From 0184e352e10974ee92fcf2528a7168ab77306f4f Mon Sep 17 00:00:00 2001 From: Caster Date: Sun, 15 Oct 2023 02:06:57 +0800 Subject: [PATCH 1/3] feat: add generate options --- .sage/einrideeslint.go | 4 +- README.md | 54 +- examples/proto/buf.gen.yaml | 1 + .../example/freight/v1/freight_service.proto | 16 +- .../einride/example/freight/v1/shipment.proto | 22 +- .../einride/example/freight/v1/index.ts | 792 +++++++++++------- .../einride/example/syntax/v1/index.ts | 568 +++++++++---- .../einride/example/syntax/v2/index.ts | 514 ++++++++---- examples/proto/gen/typescript/index.ts | 6 + go.mod | 1 + go.sum | 2 + internal/plugin/commentgen.go | 26 +- internal/plugin/enumgen.go | 44 +- internal/plugin/generate.go | 36 +- internal/plugin/helpers.go | 12 + internal/plugin/messagegen.go | 33 +- internal/plugin/packagegen.go | 20 +- internal/plugin/servicegen.go | 69 +- internal/plugin/type.go | 18 +- internal/plugin/wellknown.go | 12 + main.go | 71 +- tsconfig.json | 33 + 22 files changed, 1673 insertions(+), 681 deletions(-) create mode 100644 examples/proto/gen/typescript/index.ts create mode 100644 tsconfig.json diff --git a/.sage/einrideeslint.go b/.sage/einrideeslint.go index ca4c423..154b22c 100644 --- a/.sage/einrideeslint.go +++ b/.sage/einrideeslint.go @@ -14,8 +14,8 @@ const ( name = "eslint" packageJSONContent = `{ "dependencies": { - "@einride/eslint-plugin": "4.2.0", - "eslint": "8.5.0" + "@einride/eslint-plugin": "7.4.0", + "eslint": "8.51.0" } }` ) diff --git a/README.md b/README.md index d6ad28c..0930342 100644 --- a/README.md +++ b/README.md @@ -23,9 +23,53 @@ Or download a prebuilt binary from [releases](./releases). ```bash protoc --typescript-http_out [OUTPUT DIR] \ + --typescript-http_opt use_enum_numbers=true,use_multi_line_comment=true [.proto files ...] ``` +#### Support options + +```ts +// UseProtoNames controls the casing of generated field names. +// If set to true, fields will use proto names (typically snake_case). +// If omitted or set to false, fields will use JSON names (typically camelCase). +use_proto_names: bool + +// UseEnumNumbers emits enum values as numbers. +use_enum_numbers: bool + +// The method names of service methods naming case. +// Only work when `UseEnumNumbers=true` +// opt: +// camelcase: convert name to lower camel case like `camelCase` +// pascalcase: convert name to pascalcase like `PascalCase` +// default is pascalcase +enum_field_naming: string + +// Generate comments as multiline comments. +// multiline comments: /** ... */ +// single line comments: // ... +use_multi_line_comment: bool + +// force add `undefined` to message field. +// default true +force_message_field_undefinable: bool + +// If set to true, body will be JSON.stringify before send +// default true +use_body_stringify: bool + +// The method names of service methods naming case. +// opt: +// camelcase: convert name to lower camel case like `camelCase` +// pascalcase: convert name to pascalcase like `PascalCase` +// default is pascalcase +service_method_naming: 'camelcase' | 'pascalcase' + +// If set to true, field int64 and uint64 will convert to string +force_long_as_string: bool +``` + --- The generated clients can be used with any HTTP client that returns a Promise containing JSON data. @@ -39,11 +83,17 @@ type Request = { body: string | null } -function fetchRequestHandler({path, method, body}: Request) { +// This is optional +type RequestOptions = { + useCache?: boolean; +} + +function fetchRequestHandler({path, method, body}: Request & RequestOptions) { return fetch(rootUrl + path, {method, body}).then(response => response.json()) } export function siteClient() { - return createShipperServiceClient(fetchRequestHandler); + // This Generics is optional + return createShipperServiceClient(fetchRequestHandler); } ``` diff --git a/examples/proto/buf.gen.yaml b/examples/proto/buf.gen.yaml index 649b478..0d3247f 100644 --- a/examples/proto/buf.gen.yaml +++ b/examples/proto/buf.gen.yaml @@ -3,3 +3,4 @@ version: v1 plugins: - name: typescript-http out: gen/typescript + opt: use_enum_numbers=true,force_message_field_undefined=false,use_body_stringify=false,service_method_naming=camelcase,force_long_as_string=true diff --git a/examples/proto/einride/example/freight/v1/freight_service.proto b/examples/proto/einride/example/freight/v1/freight_service.proto index 44804d2..1bf4afd 100644 --- a/examples/proto/einride/example/freight/v1/freight_service.proto +++ b/examples/proto/einride/example/freight/v1/freight_service.proto @@ -21,7 +21,8 @@ import "google/protobuf/field_mask.proto"; // - Each Shipper has a collection of [Site][einride.example.freight.v1.Site] // resources, named `shippers/*/sites/*` // -// - Each Shipper has a collection of [Shipment][einride.example.freight.v1.Shipment] +// - Each Shipper has a collection of +// [Shipment][einride.example.freight.v1.Shipment] // resources, named `shippers/*/shipments/*` service FreightService { option (google.api.default_host) = "freight-example.einride.tech"; @@ -29,7 +30,10 @@ service FreightService { // Get a shipper. // See: https://google.aip.dev/131 (Standard methods: Get). rpc GetShipper(GetShipperRequest) returns (Shipper) { - option (google.api.http) = {get: "/v1/{name=shippers/*}"}; + option (google.api.http) = { + get: "/v1/{name=shippers/*}", + additional_bindings {get: "/v1/{name=shippers-alias/*}"} + }; option (google.api.method_signature) = "name"; } @@ -261,8 +265,8 @@ message ListSitesResponse { // Request message for FreightService.CreateSite. message CreateSiteRequest { - // The resource name of the parent shipper for which this site will be created. - // Format: shippers/{shipper} + // The resource name of the parent shipper for which this site will be + // created. Format: shippers/{shipper} string parent = 1 [ (google.api.field_behavior) = REQUIRED, (google.api.resource_reference) = {child_type: "freight-example.einride.tech/Shipper"} @@ -339,8 +343,8 @@ message ListShipmentsResponse { // Request message for FreightService.CreateShipment. message CreateShipmentRequest { - // The resource name of the parent shipper for which this shipment will be created. - // Format: shippers/{shipper} + // The resource name of the parent shipper for which this shipment will be + // created. Format: shippers/{shipper} string parent = 1 [ (google.api.field_behavior) = REQUIRED, (google.api.resource_reference) = {child_type: "freight-example.einride.tech/Shipper"} diff --git a/examples/proto/einride/example/freight/v1/shipment.proto b/examples/proto/einride/example/freight/v1/shipment.proto index f2affa4..4d40287 100644 --- a/examples/proto/einride/example/freight/v1/shipment.proto +++ b/examples/proto/einride/example/freight/v1/shipment.proto @@ -5,6 +5,16 @@ package einride.example.freight.v1; import "google/api/field_behavior.proto"; import "google/api/resource.proto"; import "google/protobuf/timestamp.proto"; +import "google/protobuf/wrappers.proto"; + +enum ShipmentState { + // The shipment' state is unknown + UNKNOWN_UNSPECIFIED = 0; + // The shipment'state is active + ACTIVE = 1; + // The shipment'state is inactive + INACTIVE = 2; +} // A shipment represents transportation of goods between an origin // [site][einride.example.freight.v1.Site] and a destination @@ -62,6 +72,16 @@ message Shipment { // Annotations of the shipment. map annotations = 12; + + // The state of the shipment + ShipmentState state = 14; + + // wrapper string + optional google.protobuf.StringValue wrapper_string = 15; + // long number + int64 long_number = 16; + // wrapper long number + google.protobuf.Int64Value wrapper_long_number = 17; } // A shipment line item. @@ -73,5 +93,5 @@ message LineItem { // The weight of the line item in kilograms. float weight_kg = 3; // The volume of the line item in cubic meters. - float volume_m3 = 4; + optional float volume_m3 = 4; } diff --git a/examples/proto/gen/typescript/einride/example/freight/v1/index.ts b/examples/proto/gen/typescript/einride/example/freight/v1/index.ts index 058d98d..2de8aea 100644 --- a/examples/proto/gen/typescript/einride/example/freight/v1/index.ts +++ b/examples/proto/gen/typescript/einride/example/freight/v1/index.ts @@ -2,55 +2,111 @@ /* eslint-disable camelcase */ // @ts-nocheck -// A shipment represents transportation of goods between an origin -// [site][einride.example.freight.v1.Site] and a destination -// [site][einride.example.freight.v1.Site]. +export enum ShipmentState { + /** + * The shipment' state is unknown + */ + UnknownUnspecified = 0, + /** + * The shipment'state is active + */ + Active = 1, + /** + * The shipment'state is inactive + */ + Inactive = 2, +} +/** + * A shipment represents transportation of goods between an origin + * [site][einride.example.freight.v1.Site] and a destination + * [site][einride.example.freight.v1.Site]. + */ export type Shipment = { - // The resource name of the shipment. - name: string | undefined; - // The creation timestamp of the shipment. - // - // Behaviors: OUTPUT_ONLY + /** + * The resource name of the shipment. + */ + name: string; + /** + * The creation timestamp of the shipment. + * + * Behaviors: OUTPUT_ONLY + */ createTime: wellKnownTimestamp | undefined; - // The last update timestamp of the shipment. - // Updated when create/update/delete operation is shipment. - // - // Behaviors: OUTPUT_ONLY + /** + * The last update timestamp of the shipment. + * Updated when create/update/delete operation is shipment. + * + * Behaviors: OUTPUT_ONLY + */ updateTime: wellKnownTimestamp | undefined; - // The deletion timestamp of the shipment. - // - // Behaviors: OUTPUT_ONLY + /** + * The deletion timestamp of the shipment. + * + * Behaviors: OUTPUT_ONLY + */ deleteTime: wellKnownTimestamp | undefined; - // The resource name of the origin site of the shipment. - // Format: shippers/{shipper}/sites/{site} - // - // Behaviors: REQUIRED - originSite: string | undefined; - // The resource name of the destination site of the shipment. - // Format: shippers/{shipper}/sites/{site} - // - // Behaviors: REQUIRED - destinationSite: string | undefined; - // The earliest pickup time of the shipment at the origin site. - // - // Behaviors: REQUIRED + /** + * The resource name of the origin site of the shipment. + * Format: shippers/{shipper}/sites/{site} + * + * Behaviors: REQUIRED + */ + originSite: string; + /** + * The resource name of the destination site of the shipment. + * Format: shippers/{shipper}/sites/{site} + * + * Behaviors: REQUIRED + */ + destinationSite: string; + /** + * The earliest pickup time of the shipment at the origin site. + * + * Behaviors: REQUIRED + */ pickupEarliestTime: wellKnownTimestamp | undefined; - // The latest pickup time of the shipment at the origin site. - // - // Behaviors: REQUIRED + /** + * The latest pickup time of the shipment at the origin site. + * + * Behaviors: REQUIRED + */ pickupLatestTime: wellKnownTimestamp | undefined; - // The earliest delivery time of the shipment at the destination site. - // - // Behaviors: REQUIRED + /** + * The earliest delivery time of the shipment at the destination site. + * + * Behaviors: REQUIRED + */ deliveryEarliestTime: wellKnownTimestamp | undefined; - // The latest delivery time of the shipment at the destination site. - // - // Behaviors: REQUIRED + /** + * The latest delivery time of the shipment at the destination site. + * + * Behaviors: REQUIRED + */ deliveryLatestTime: wellKnownTimestamp | undefined; - // The line items of the shipment. - lineItems: LineItem[] | undefined; - // Annotations of the shipment. - annotations: { [key: string]: string } | undefined; + /** + * The line items of the shipment. + */ + lineItems: LineItem[]; + /** + * Annotations of the shipment. + */ + annotations: { [key: string]: string }; + /** + * The state of the shipment + */ + state: ShipmentState; + /** + * wrapper string + */ + wrapperString?: wellKnownStringValue | undefined; + /** + * long number + */ + longNumber: string; + /** + * wrapper long number + */ + wrapperLongNumber: wellKnownInt64Value | undefined; }; // Encoded using RFC 3339, where generated output will always be Z-normalized @@ -58,128 +114,200 @@ export type Shipment = { // Offsets other than "Z" are also accepted. type wellKnownTimestamp = string; -// A shipment line item. +/** + * A shipment line item. + */ export type LineItem = { - // The title of the line item. - title: string | undefined; - // The quantity of the line item. - quantity: number | undefined; - // The weight of the line item in kilograms. - weightKg: number | undefined; - // The volume of the line item in cubic meters. - volumeM3: number | undefined; + /** + * The title of the line item. + */ + title: string; + /** + * The quantity of the line item. + */ + quantity: number; + /** + * The weight of the line item in kilograms. + */ + weightKg: number; + /** + * The volume of the line item in cubic meters. + */ + volumeM3?: number; }; -// A shipper is a supplier or owner of goods to be transported. +type wellKnownStringValue = string | null; + +type wellKnownInt64Value = string | null; + +/** + * A shipper is a supplier or owner of goods to be transported. + */ export type Shipper = { - // The resource name of the shipper. - name: string | undefined; - // The creation timestamp of the shipper. - // - // Behaviors: OUTPUT_ONLY + /** + * The resource name of the shipper. + */ + name: string; + /** + * The creation timestamp of the shipper. + * + * Behaviors: OUTPUT_ONLY + */ createTime: wellKnownTimestamp | undefined; - // The last update timestamp of the shipper. - // Updated when create/update/delete operation is performed. - // - // Behaviors: OUTPUT_ONLY + /** + * The last update timestamp of the shipper. + * Updated when create/update/delete operation is performed. + * + * Behaviors: OUTPUT_ONLY + */ updateTime: wellKnownTimestamp | undefined; - // The deletion timestamp of the shipper. - // - // Behaviors: OUTPUT_ONLY + /** + * The deletion timestamp of the shipper. + * + * Behaviors: OUTPUT_ONLY + */ deleteTime: wellKnownTimestamp | undefined; - // The display name of the shipper. - // - // Behaviors: REQUIRED - displayName: string | undefined; + /** + * The display name of the shipper. + * + * Behaviors: REQUIRED + */ + displayName: string; }; -// A site is a node in a [shipper][einride.example.freight.v1.Shipper]'s -// transport network. +/** + * A site is a node in a [shipper][einride.example.freight.v1.Shipper]'s + * transport network. + */ export type Site = { - // The resource name of the site. - name: string | undefined; - // The creation timestamp of the site. - // - // Behaviors: OUTPUT_ONLY + /** + * The resource name of the site. + */ + name: string; + /** + * The creation timestamp of the site. + * + * Behaviors: OUTPUT_ONLY + */ createTime: wellKnownTimestamp | undefined; - // The last update timestamp of the site. - // Updated when create/update/delete operation is performed. - // - // Behaviors: OUTPUT_ONLY + /** + * The last update timestamp of the site. + * Updated when create/update/delete operation is performed. + * + * Behaviors: OUTPUT_ONLY + */ updateTime: wellKnownTimestamp | undefined; - // The deletion timestamp of the site. - // - // Behaviors: OUTPUT_ONLY + /** + * The deletion timestamp of the site. + * + * Behaviors: OUTPUT_ONLY + */ deleteTime: wellKnownTimestamp | undefined; - // The display name of the site. - // - // Behaviors: REQUIRED - displayName: string | undefined; - // The geographic location of the site. - latLng: googletype_LatLng | undefined; + /** + * The display name of the site. + * + * Behaviors: REQUIRED + */ + displayName: string; + /** + * The geographic location of the site. + */ + latLng: googletype_LatLng; }; -// An object that represents a latitude/longitude pair. This is expressed as a -// pair of doubles to represent degrees latitude and degrees longitude. Unless -// specified otherwise, this must conform to the -// WGS84 -// standard. Values must be within normalized ranges. +/** + * An object that represents a latitude/longitude pair. This is expressed as a + * pair of doubles to represent degrees latitude and degrees longitude. Unless + * specified otherwise, this must conform to the + * WGS84 + * standard. Values must be within normalized ranges. + */ export type googletype_LatLng = { - // The latitude in degrees. It must be in the range [-90.0, +90.0]. - latitude: number | undefined; - // The longitude in degrees. It must be in the range [-180.0, +180.0]. - longitude: number | undefined; + /** + * The latitude in degrees. It must be in the range [-90.0, +90.0]. + */ + latitude: number; + /** + * The longitude in degrees. It must be in the range [-180.0, +180.0]. + */ + longitude: number; }; -// Request message for FreightService.GetShipper. +/** + * Request message for FreightService.GetShipper. + */ export type GetShipperRequest = { - // The resource name of the shipper to retrieve. - // Format: shippers/{shipper} - // - // Behaviors: REQUIRED - name: string | undefined; + /** + * The resource name of the shipper to retrieve. + * Format: shippers/{shipper} + * + * Behaviors: REQUIRED + */ + name: string; }; -// Request message for FreightService.ListShippers. +/** + * Request message for FreightService.ListShippers. + */ export type ListShippersRequest = { - // Requested page size. Server may return fewer shippers than requested. - // If unspecified, server will pick an appropriate default. - pageSize: number | undefined; - // A token identifying a page of results the server should return. - // Typically, this is the value of - // [ListShippersResponse.next_page_token][einride.example.freight.v1.ListShippersResponse.next_page_token] - // returned from the previous call to `ListShippers` method. - pageToken: string | undefined; + /** + * Requested page size. Server may return fewer shippers than requested. + * If unspecified, server will pick an appropriate default. + */ + pageSize: number; + /** + * A token identifying a page of results the server should return. + * Typically, this is the value of + * [ListShippersResponse.next_page_token][einride.example.freight.v1.ListShippersResponse.next_page_token] + * returned from the previous call to `ListShippers` method. + */ + pageToken: string; }; -// Response message for FreightService.ListShippers. +/** + * Response message for FreightService.ListShippers. + */ export type ListShippersResponse = { - // The list of shippers. - shippers: Shipper[] | undefined; - // A token to retrieve next page of results. Pass this value in the - // [ListShippersRequest.page_token][einride.example.freight.v1.ListShippersRequest.page_token] - // field in the subsequent call to `ListShippers` method to retrieve the next - // page of results. - nextPageToken: string | undefined; + /** + * The list of shippers. + */ + shippers: Shipper[]; + /** + * A token to retrieve next page of results. Pass this value in the + * [ListShippersRequest.page_token][einride.example.freight.v1.ListShippersRequest.page_token] + * field in the subsequent call to `ListShippers` method to retrieve the next + * page of results. + */ + nextPageToken: string; }; -// Request message for FreightService.CreateShipper. +/** + * Request message for FreightService.CreateShipper. + */ export type CreateShipperRequest = { - // The shipper to create. - // - // Behaviors: REQUIRED - shipper: Shipper | undefined; + /** + * The shipper to create. + * + * Behaviors: REQUIRED + */ + shipper: Shipper; }; -// Request message for FreightService.UpdateShipper. +/** + * Request message for FreightService.UpdateShipper. + */ export type UpdateShipperRequest = { - // The shipper to update with. The name must match or be empty. - // The shipper's `name` field is used to identify the shipper to be updated. - // Format: shippers/{shipper} - // - // Behaviors: REQUIRED - shipper: Shipper | undefined; - // The list of fields to be updated. + /** + * The shipper to update with. The name must match or be empty. + * The shipper's `name` field is used to identify the shipper to be updated. + * Format: shippers/{shipper} + * + * Behaviors: REQUIRED + */ + shipper: Shipper; + /** + * The list of fields to be updated. + */ updateMask: wellKnownFieldMask | undefined; }; @@ -211,155 +339,227 @@ export type UpdateShipperRequest = { // } type wellKnownFieldMask = string; -// Request message for FreightService.DeleteShipper. +/** + * Request message for FreightService.DeleteShipper. + */ export type DeleteShipperRequest = { - // The resource name of the shipper to delete. - // Format: shippers/{shipper} - // - // Behaviors: REQUIRED - name: string | undefined; + /** + * The resource name of the shipper to delete. + * Format: shippers/{shipper} + * + * Behaviors: REQUIRED + */ + name: string; }; -// Request message for FreightService.GetSite. +/** + * Request message for FreightService.GetSite. + */ export type GetSiteRequest = { - // The resource name of the site to retrieve. - // Format: shippers/{shipper}/sites/{site} - // - // Behaviors: REQUIRED - name: string | undefined; + /** + * The resource name of the site to retrieve. + * Format: shippers/{shipper}/sites/{site} + * + * Behaviors: REQUIRED + */ + name: string; }; -// Request message for FreightService.ListSites. +/** + * Request message for FreightService.ListSites. + */ export type ListSitesRequest = { - // The resource name of the parent, which owns this collection of sites. - // Format: shippers/{shipper} - // - // Behaviors: REQUIRED - parent: string | undefined; - // Requested page size. Server may return fewer sites than requested. - // If unspecified, server will pick an appropriate default. - pageSize: number | undefined; - // A token identifying a page of results the server should return. - // Typically, this is the value of - // [ListSitesResponse.next_page_token][einride.example.freight.v1.ListSitesResponse.next_page_token] - // returned from the previous call to `ListSites` method. - pageToken: string | undefined; + /** + * The resource name of the parent, which owns this collection of sites. + * Format: shippers/{shipper} + * + * Behaviors: REQUIRED + */ + parent: string; + /** + * Requested page size. Server may return fewer sites than requested. + * If unspecified, server will pick an appropriate default. + */ + pageSize: number; + /** + * A token identifying a page of results the server should return. + * Typically, this is the value of + * [ListSitesResponse.next_page_token][einride.example.freight.v1.ListSitesResponse.next_page_token] + * returned from the previous call to `ListSites` method. + */ + pageToken: string; }; -// Response message for FreightService.ListSites. +/** + * Response message for FreightService.ListSites. + */ export type ListSitesResponse = { - // The list of sites. - sites: Site[] | undefined; - // A token to retrieve next page of results. Pass this value in the - // [ListSitesRequest.page_token][einride.example.freight.v1.ListSitesRequest.page_token] - // field in the subsequent call to `ListSites` method to retrieve the next - // page of results. - nextPageToken: string | undefined; + /** + * The list of sites. + */ + sites: Site[]; + /** + * A token to retrieve next page of results. Pass this value in the + * [ListSitesRequest.page_token][einride.example.freight.v1.ListSitesRequest.page_token] + * field in the subsequent call to `ListSites` method to retrieve the next + * page of results. + */ + nextPageToken: string; }; -// Request message for FreightService.CreateSite. +/** + * Request message for FreightService.CreateSite. + */ export type CreateSiteRequest = { - // The resource name of the parent shipper for which this site will be created. - // Format: shippers/{shipper} - // - // Behaviors: REQUIRED - parent: string | undefined; - // The site to create. - // - // Behaviors: REQUIRED - site: Site | undefined; + /** + * The resource name of the parent shipper for which this site will be + * created. Format: shippers/{shipper} + * + * Behaviors: REQUIRED + */ + parent: string; + /** + * The site to create. + * + * Behaviors: REQUIRED + */ + site: Site; }; -// Request message for FreightService.UpdateSite. +/** + * Request message for FreightService.UpdateSite. + */ export type UpdateSiteRequest = { - // The site to update with. The name must match or be empty. - // The site's `name` field is used to identify the site to be updated. - // Format: shippers/{shipper}/sites/{site} - // - // Behaviors: REQUIRED - site: Site | undefined; - // The list of fields to be updated. + /** + * The site to update with. The name must match or be empty. + * The site's `name` field is used to identify the site to be updated. + * Format: shippers/{shipper}/sites/{site} + * + * Behaviors: REQUIRED + */ + site: Site; + /** + * The list of fields to be updated. + */ updateMask: wellKnownFieldMask | undefined; }; -// Request message for FreightService.DeleteSite. +/** + * Request message for FreightService.DeleteSite. + */ export type DeleteSiteRequest = { - // The resource name of the site to delete. - // Format: shippers/{shipper}/sites/{site} - // - // Behaviors: REQUIRED - name: string | undefined; + /** + * The resource name of the site to delete. + * Format: shippers/{shipper}/sites/{site} + * + * Behaviors: REQUIRED + */ + name: string; }; -// Request message for FreightService.GetShipment. +/** + * Request message for FreightService.GetShipment. + */ export type GetShipmentRequest = { - // The resource name of the shipment to retrieve. - // Format: shippers/{shipper}/shipments/{shipment} - // - // Behaviors: REQUIRED - name: string | undefined; + /** + * The resource name of the shipment to retrieve. + * Format: shippers/{shipper}/shipments/{shipment} + * + * Behaviors: REQUIRED + */ + name: string; }; -// Request message for FreightService.ListShipments. +/** + * Request message for FreightService.ListShipments. + */ export type ListShipmentsRequest = { - // The resource name of the parent, which owns this collection of shipments. - // Format: shippers/{shipper} - // - // Behaviors: REQUIRED - parent: string | undefined; - // Requested page size. Server may return fewer shipments than requested. - // If unspecified, server will pick an appropriate default. - pageSize: number | undefined; - // A token identifying a page of results the server should return. - // Typically, this is the value of - // [ListShipmentsResponse.next_page_token][einride.example.freight.v1.ListShipmentsResponse.next_page_token] - // returned from the previous call to `ListShipments` method. - pageToken: string | undefined; + /** + * The resource name of the parent, which owns this collection of shipments. + * Format: shippers/{shipper} + * + * Behaviors: REQUIRED + */ + parent: string; + /** + * Requested page size. Server may return fewer shipments than requested. + * If unspecified, server will pick an appropriate default. + */ + pageSize: number; + /** + * A token identifying a page of results the server should return. + * Typically, this is the value of + * [ListShipmentsResponse.next_page_token][einride.example.freight.v1.ListShipmentsResponse.next_page_token] + * returned from the previous call to `ListShipments` method. + */ + pageToken: string; }; -// Response message for FreightService.ListShipments. +/** + * Response message for FreightService.ListShipments. + */ export type ListShipmentsResponse = { - // The list of shipments. - shipments: Shipment[] | undefined; - // A token to retrieve next page of results. Pass this value in the - // [ListShipmentsRequest.page_token][einride.example.freight.v1.ListShipmentsRequest.page_token] - // field in the subsequent call to `ListShipments` method to retrieve the next - // page of results. - nextPageToken: string | undefined; + /** + * The list of shipments. + */ + shipments: Shipment[]; + /** + * A token to retrieve next page of results. Pass this value in the + * [ListShipmentsRequest.page_token][einride.example.freight.v1.ListShipmentsRequest.page_token] + * field in the subsequent call to `ListShipments` method to retrieve the next + * page of results. + */ + nextPageToken: string; }; -// Request message for FreightService.CreateShipment. +/** + * Request message for FreightService.CreateShipment. + */ export type CreateShipmentRequest = { - // The resource name of the parent shipper for which this shipment will be created. - // Format: shippers/{shipper} - // - // Behaviors: REQUIRED - parent: string | undefined; - // The shipment to create. - // - // Behaviors: REQUIRED - shipment: Shipment | undefined; + /** + * The resource name of the parent shipper for which this shipment will be + * created. Format: shippers/{shipper} + * + * Behaviors: REQUIRED + */ + parent: string; + /** + * The shipment to create. + * + * Behaviors: REQUIRED + */ + shipment: Shipment; }; -// Request message for FreightService.UpdateShipment. +/** + * Request message for FreightService.UpdateShipment. + */ export type UpdateShipmentRequest = { - // The shipment to update with. The name must match or be empty. - // The shipment's `name` field is used to identify the shipment to be updated. - // Format: shippers/{shipper}/shipments/{shipment} - // - // Behaviors: REQUIRED - shipment: Shipment | undefined; - // The list of fields to be updated. + /** + * The shipment to update with. The name must match or be empty. + * The shipment's `name` field is used to identify the shipment to be updated. + * Format: shippers/{shipper}/shipments/{shipment} + * + * Behaviors: REQUIRED + */ + shipment: Shipment; + /** + * The list of fields to be updated. + */ updateMask: wellKnownFieldMask | undefined; }; -// Request message for FreightService.DeleteShipment. +/** + * Request message for FreightService.DeleteShipment. + */ export type DeleteShipmentRequest = { - // The resource name of the shipment to delete. - // Format: shippers/{shipper}/shipments/{shipment} - // - // Behaviors: REQUIRED - name: string | undefined; + /** + * The resource name of the shipment to delete. + * Format: shippers/{shipper}/shipments/{shipment} + * + * Behaviors: REQUIRED + */ + name: string; }; // This API represents a simple freight service. @@ -368,72 +568,77 @@ export type DeleteShipmentRequest = { // resources, named `shippers/*` // - Each Shipper has a collection of [Site][einride.example.freight.v1.Site] // resources, named `shippers/*/sites/*` -// - Each Shipper has a collection of [Shipment][einride.example.freight.v1.Shipment] +// - Each Shipper has a collection of +// [Shipment][einride.example.freight.v1.Shipment] // resources, named `shippers/*/shipments/*` -export interface FreightService { +export interface FreightService { // Get a shipper. // See: https://google.aip.dev/131 (Standard methods: Get). - GetShipper(request: GetShipperRequest): Promise; + getShipper(request: GetShipperRequest, options?: T): Promise; // List shippers. // See: https://google.aip.dev/132 (Standard methods: List). - ListShippers(request: ListShippersRequest): Promise; + listShippers(request: ListShippersRequest, options?: T): Promise; // Create a shipper. // See: https://google.aip.dev/133 (Standard methods: Create). - CreateShipper(request: CreateShipperRequest): Promise; + createShipper(request: CreateShipperRequest, options?: T): Promise; // Update a shipper. // See: https://google.aip.dev/134 (Standard methods: Update). - UpdateShipper(request: UpdateShipperRequest): Promise; + updateShipper(request: UpdateShipperRequest, options?: T): Promise; // Delete a shipper. // See: https://google.aip.dev/135 (Standard methods: Delete). // See: https://google.aip.dev/164 (Soft delete). - DeleteShipper(request: DeleteShipperRequest): Promise; + deleteShipper(request: DeleteShipperRequest, options?: T): Promise; // Get a site. // See: https://google.aip.dev/131 (Standard methods: Get). - GetSite(request: GetSiteRequest): Promise; + getSite(request: GetSiteRequest, options?: T): Promise; // List sites for a shipper. // See: https://google.aip.dev/132 (Standard methods: List). - ListSites(request: ListSitesRequest): Promise; + listSites(request: ListSitesRequest, options?: T): Promise; // Create a site. // See: https://google.aip.dev/133 (Standard methods: Create). - CreateSite(request: CreateSiteRequest): Promise; + createSite(request: CreateSiteRequest, options?: T): Promise; // Update a site. // See: https://google.aip.dev/134 (Standard methods: Update). - UpdateSite(request: UpdateSiteRequest): Promise; + updateSite(request: UpdateSiteRequest, options?: T): Promise; // Delete a site. // See: https://google.aip.dev/135 (Standard methods: Delete). // See: https://google.aip.dev/164 (Soft delete). - DeleteSite(request: DeleteSiteRequest): Promise; + deleteSite(request: DeleteSiteRequest, options?: T): Promise; // Get a shipment. // See: https://google.aip.dev/131 (Standard methods: Get). - GetShipment(request: GetShipmentRequest): Promise; + getShipment(request: GetShipmentRequest, options?: T): Promise; // List shipments for a shipper. // See: https://google.aip.dev/132 (Standard methods: List). - ListShipments(request: ListShipmentsRequest): Promise; + listShipments(request: ListShipmentsRequest, options?: T): Promise; // Create a shipment. // See: https://google.aip.dev/133 (Standard methods: Create). - CreateShipment(request: CreateShipmentRequest): Promise; + createShipment(request: CreateShipmentRequest, options?: T): Promise; // Update a shipment. // See: https://google.aip.dev/134 (Standard methods: Update). - UpdateShipment(request: UpdateShipmentRequest): Promise; + updateShipment(request: UpdateShipmentRequest, options?: T): Promise; // Delete a shipment. // See: https://google.aip.dev/135 (Standard methods: Delete). // See: https://google.aip.dev/164 (Soft delete). - DeleteShipment(request: DeleteShipmentRequest): Promise; + deleteShipment(request: DeleteShipmentRequest, options?: T): Promise; } -type RequestType = { +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type RequestType | string | null> = { path: string; method: string; - body: string | null; + body: T; }; -type RequestHandler = (request: RequestType, meta: { service: string, method: string }) => Promise; +type RequestHandler = ( + request: RequestType & T, + meta: { service: string, method: string }, +) => Promise; -export function createFreightServiceClient( - handler: RequestHandler -): FreightService { +export function createFreightServiceClient( + handler: RequestHandler +): FreightService { return { - GetShipper(request) { // eslint-disable-line @typescript-eslint/no-unused-vars + getShipper(request, options) { // eslint-disable-line @typescript-eslint/no-unused-vars if (!request.name) { throw new Error("missing required field request.name"); } @@ -448,12 +653,13 @@ export function createFreightServiceClient( path: uri, method: "GET", body, + ...(options as T), }, { service: "FreightService", method: "GetShipper", }) as Promise; }, - ListShippers(request) { // eslint-disable-line @typescript-eslint/no-unused-vars + listShippers(request, options) { // eslint-disable-line @typescript-eslint/no-unused-vars const path = `v1/shippers`; // eslint-disable-line quotes const body = null; const queryParams: string[] = []; @@ -471,14 +677,15 @@ export function createFreightServiceClient( path: uri, method: "GET", body, + ...(options as T), }, { service: "FreightService", method: "ListShippers", }) as Promise; }, - CreateShipper(request) { // eslint-disable-line @typescript-eslint/no-unused-vars + createShipper(request, options) { // eslint-disable-line @typescript-eslint/no-unused-vars const path = `v1/shippers`; // eslint-disable-line quotes - const body = JSON.stringify(request?.shipper ?? {}); + const body = request?.shipper ?? {}; const queryParams: string[] = []; let uri = path; if (queryParams.length > 0) { @@ -488,17 +695,18 @@ export function createFreightServiceClient( path: uri, method: "POST", body, + ...(options as T), }, { service: "FreightService", method: "CreateShipper", }) as Promise; }, - UpdateShipper(request) { // eslint-disable-line @typescript-eslint/no-unused-vars + updateShipper(request, options) { // eslint-disable-line @typescript-eslint/no-unused-vars if (!request.shipper?.name) { throw new Error("missing required field request.shipper.name"); } const path = `v1/${request.shipper.name}`; // eslint-disable-line quotes - const body = JSON.stringify(request?.shipper ?? {}); + const body = request?.shipper ?? {}; const queryParams: string[] = []; if (request.updateMask) { queryParams.push(`updateMask=${encodeURIComponent(request.updateMask.toString())}`) @@ -511,12 +719,13 @@ export function createFreightServiceClient( path: uri, method: "PATCH", body, + ...(options as T), }, { service: "FreightService", method: "UpdateShipper", }) as Promise; }, - DeleteShipper(request) { // eslint-disable-line @typescript-eslint/no-unused-vars + deleteShipper(request, options) { // eslint-disable-line @typescript-eslint/no-unused-vars if (!request.name) { throw new Error("missing required field request.name"); } @@ -531,12 +740,13 @@ export function createFreightServiceClient( path: uri, method: "DELETE", body, + ...(options as T), }, { service: "FreightService", method: "DeleteShipper", }) as Promise; }, - GetSite(request) { // eslint-disable-line @typescript-eslint/no-unused-vars + getSite(request, options) { // eslint-disable-line @typescript-eslint/no-unused-vars if (!request.name) { throw new Error("missing required field request.name"); } @@ -551,12 +761,13 @@ export function createFreightServiceClient( path: uri, method: "GET", body, + ...(options as T), }, { service: "FreightService", method: "GetSite", }) as Promise; }, - ListSites(request) { // eslint-disable-line @typescript-eslint/no-unused-vars + listSites(request, options) { // eslint-disable-line @typescript-eslint/no-unused-vars if (!request.parent) { throw new Error("missing required field request.parent"); } @@ -577,17 +788,18 @@ export function createFreightServiceClient( path: uri, method: "GET", body, + ...(options as T), }, { service: "FreightService", method: "ListSites", }) as Promise; }, - CreateSite(request) { // eslint-disable-line @typescript-eslint/no-unused-vars + createSite(request, options) { // eslint-disable-line @typescript-eslint/no-unused-vars if (!request.parent) { throw new Error("missing required field request.parent"); } const path = `v1/${request.parent}/sites`; // eslint-disable-line quotes - const body = JSON.stringify(request?.site ?? {}); + const body = request?.site ?? {}; const queryParams: string[] = []; let uri = path; if (queryParams.length > 0) { @@ -597,17 +809,18 @@ export function createFreightServiceClient( path: uri, method: "POST", body, + ...(options as T), }, { service: "FreightService", method: "CreateSite", }) as Promise; }, - UpdateSite(request) { // eslint-disable-line @typescript-eslint/no-unused-vars + updateSite(request, options) { // eslint-disable-line @typescript-eslint/no-unused-vars if (!request.site?.name) { throw new Error("missing required field request.site.name"); } const path = `v1/${request.site.name}`; // eslint-disable-line quotes - const body = JSON.stringify(request?.site ?? {}); + const body = request?.site ?? {}; const queryParams: string[] = []; if (request.updateMask) { queryParams.push(`updateMask=${encodeURIComponent(request.updateMask.toString())}`) @@ -620,12 +833,13 @@ export function createFreightServiceClient( path: uri, method: "PATCH", body, + ...(options as T), }, { service: "FreightService", method: "UpdateSite", }) as Promise; }, - DeleteSite(request) { // eslint-disable-line @typescript-eslint/no-unused-vars + deleteSite(request, options) { // eslint-disable-line @typescript-eslint/no-unused-vars if (!request.name) { throw new Error("missing required field request.name"); } @@ -640,12 +854,13 @@ export function createFreightServiceClient( path: uri, method: "DELETE", body, + ...(options as T), }, { service: "FreightService", method: "DeleteSite", }) as Promise; }, - GetShipment(request) { // eslint-disable-line @typescript-eslint/no-unused-vars + getShipment(request, options) { // eslint-disable-line @typescript-eslint/no-unused-vars if (!request.name) { throw new Error("missing required field request.name"); } @@ -660,12 +875,13 @@ export function createFreightServiceClient( path: uri, method: "GET", body, + ...(options as T), }, { service: "FreightService", method: "GetShipment", }) as Promise; }, - ListShipments(request) { // eslint-disable-line @typescript-eslint/no-unused-vars + listShipments(request, options) { // eslint-disable-line @typescript-eslint/no-unused-vars if (!request.parent) { throw new Error("missing required field request.parent"); } @@ -686,17 +902,18 @@ export function createFreightServiceClient( path: uri, method: "GET", body, + ...(options as T), }, { service: "FreightService", method: "ListShipments", }) as Promise; }, - CreateShipment(request) { // eslint-disable-line @typescript-eslint/no-unused-vars + createShipment(request, options) { // eslint-disable-line @typescript-eslint/no-unused-vars if (!request.parent) { throw new Error("missing required field request.parent"); } const path = `v1/${request.parent}/shipments`; // eslint-disable-line quotes - const body = JSON.stringify(request?.shipment ?? {}); + const body = request?.shipment ?? {}; const queryParams: string[] = []; let uri = path; if (queryParams.length > 0) { @@ -706,17 +923,18 @@ export function createFreightServiceClient( path: uri, method: "POST", body, + ...(options as T), }, { service: "FreightService", method: "CreateShipment", }) as Promise; }, - UpdateShipment(request) { // eslint-disable-line @typescript-eslint/no-unused-vars + updateShipment(request, options) { // eslint-disable-line @typescript-eslint/no-unused-vars if (!request.shipment?.name) { throw new Error("missing required field request.shipment.name"); } const path = `v1/${request.shipment.name}`; // eslint-disable-line quotes - const body = JSON.stringify(request?.shipment ?? {}); + const body = request?.shipment ?? {}; const queryParams: string[] = []; if (request.updateMask) { queryParams.push(`updateMask=${encodeURIComponent(request.updateMask.toString())}`) @@ -729,12 +947,13 @@ export function createFreightServiceClient( path: uri, method: "PATCH", body, + ...(options as T), }, { service: "FreightService", method: "UpdateShipment", }) as Promise; }, - DeleteShipment(request) { // eslint-disable-line @typescript-eslint/no-unused-vars + deleteShipment(request, options) { // eslint-disable-line @typescript-eslint/no-unused-vars if (!request.name) { throw new Error("missing required field request.name"); } @@ -749,6 +968,7 @@ export function createFreightServiceClient( path: uri, method: "DELETE", body, + ...(options as T), }, { service: "FreightService", method: "DeleteShipment", diff --git a/examples/proto/gen/typescript/einride/example/syntax/v1/index.ts b/examples/proto/gen/typescript/einride/example/syntax/v1/index.ts index bddca86..b3a9b5c 100644 --- a/examples/proto/gen/typescript/einride/example/syntax/v1/index.ts +++ b/examples/proto/gen/typescript/einride/example/syntax/v1/index.ts @@ -2,197 +2,390 @@ /* eslint-disable camelcase */ // @ts-nocheck -// Enum -export type Enum = - // ENUM_UNSPECIFIED - | "ENUM_UNSPECIFIED" - // ENUM_ONE - | "ENUM_ONE" - // ENUM_TWO - | "ENUM_TWO"; -// Message +/** + * Enum + */ +export enum Enum { + /** + * ENUM_UNSPECIFIED + */ + EnumUnspecified = 0, + /** + * ENUM_ONE + */ + EnumOne = 1, + /** + * ENUM_TWO + */ + EnumTwo = 2, +} +/** + * Message + */ export type Message = { - // double - double: number | undefined; - // float - float: number | undefined; - // int32 - int32: number | undefined; - // int64 - int64: number | undefined; - // uint32 - uint32: number | undefined; - // uint64 - uint64: number | undefined; - // sint32 - sint32: number | undefined; - // sint64 - sint64: number | undefined; - // fixed32 - fixed32: number | undefined; - // fixed64 - fixed64: number | undefined; - // sfixed32 - sfixed32: number | undefined; - // sfixed64 - sfixed64: number | undefined; - // bool - bool: boolean | undefined; - // string - string: string | undefined; - // bytes - bytes: string | undefined; - // enum - enum: Enum | undefined; - // message - message: Message | undefined; - // optional double + /** + * double + */ + double: number; + /** + * float + */ + float: number; + /** + * int32 + */ + int32: number; + /** + * int64 + */ + int64: string; + /** + * uint32 + */ + uint32: number; + /** + * uint64 + */ + uint64: string; + /** + * sint32 + */ + sint32: number; + /** + * sint64 + */ + sint64: string; + /** + * fixed32 + */ + fixed32: number; + /** + * fixed64 + */ + fixed64: string; + /** + * sfixed32 + */ + sfixed32: number; + /** + * sfixed64 + */ + sfixed64: string; + /** + * bool + */ + bool: boolean; + /** + * string + */ + string: string; + /** + * bytes + */ + bytes: string; + /** + * enum + */ + enum: Enum; + /** + * message + */ + message: Message; + /** + * optional double + */ optionalDouble?: number; - // optional float + /** + * optional float + */ optionalFloat?: number; - // optional int32 + /** + * optional int32 + */ optionalInt32?: number; - // optional int64 - optionalInt64?: number; - // optional uint32 + /** + * optional int64 + */ + optionalInt64?: string; + /** + * optional uint32 + */ optionalUint32?: number; - // optional uint64 - optionalUint64?: number; - // optional sint32 + /** + * optional uint64 + */ + optionalUint64?: string; + /** + * optional sint32 + */ optionalSint32?: number; - // optional sint64 - optionalSint64?: number; - // optional fixed32 + /** + * optional sint64 + */ + optionalSint64?: string; + /** + * optional fixed32 + */ optionalFixed32?: number; - // optional fixed64 - optionalFixed64?: number; - // optional sfixed32 + /** + * optional fixed64 + */ + optionalFixed64?: string; + /** + * optional sfixed32 + */ optionalSfixed32?: number; - // optional sfixed64 - optionalSfixed64?: number; - // optional bool + /** + * optional sfixed64 + */ + optionalSfixed64?: string; + /** + * optional bool + */ optionalBool?: boolean; - // optional string + /** + * optional string + */ optionalString?: string; - // optional bytes + /** + * optional bytes + */ optionalBytes?: string; - // optional enum + /** + * optional enum + */ optionalEnum?: Enum; - // optional message + /** + * optional message + */ optionalMessage?: Message; - // repeated_double - repeatedDouble: number[] | undefined; - // repeated_float - repeatedFloat: number[] | undefined; - // repeated_int32 - repeatedInt32: number[] | undefined; - // repeated_int64 - repeatedInt64: number[] | undefined; - // repeated_uint32 - repeatedUint32: number[] | undefined; - // repeated_uint64 - repeatedUint64: number[] | undefined; - // repeated_sint32 - repeatedSint32: number[] | undefined; - // repeated_sint64 - repeatedSint64: number[] | undefined; - // repeated_fixed32 - repeatedFixed32: number[] | undefined; - // repeated_fixed64 - repeatedFixed64: number[] | undefined; - // repeated_sfixed32 - repeatedSfixed32: number[] | undefined; - // repeated_sfixed64 - repeatedSfixed64: number[] | undefined; - // repeated_bool - repeatedBool: boolean[] | undefined; - // repeated_string - repeatedString: string[] | undefined; - // repeated_bytes - repeatedBytes: string[] | undefined; - // repeated_enum - repeatedEnum: Enum[] | undefined; - // repeated_message - repeatedMessage: Message[] | undefined; - // map_string_string - mapStringString: { [key: string]: string } | undefined; - // map_string_message - mapStringMessage: { [key: string]: Message } | undefined; - // oneof_string + /** + * repeated_double + */ + repeatedDouble: number[]; + /** + * repeated_float + */ + repeatedFloat: number[]; + /** + * repeated_int32 + */ + repeatedInt32: number[]; + /** + * repeated_int64 + */ + repeatedInt64: string[]; + /** + * repeated_uint32 + */ + repeatedUint32: number[]; + /** + * repeated_uint64 + */ + repeatedUint64: string[]; + /** + * repeated_sint32 + */ + repeatedSint32: number[]; + /** + * repeated_sint64 + */ + repeatedSint64: string[]; + /** + * repeated_fixed32 + */ + repeatedFixed32: number[]; + /** + * repeated_fixed64 + */ + repeatedFixed64: string[]; + /** + * repeated_sfixed32 + */ + repeatedSfixed32: number[]; + /** + * repeated_sfixed64 + */ + repeatedSfixed64: string[]; + /** + * repeated_bool + */ + repeatedBool: boolean[]; + /** + * repeated_string + */ + repeatedString: string[]; + /** + * repeated_bytes + */ + repeatedBytes: string[]; + /** + * repeated_enum + */ + repeatedEnum: Enum[]; + /** + * repeated_message + */ + repeatedMessage: Message[]; + /** + * map_string_string + */ + mapStringString: { [key: string]: string }; + /** + * map_string_message + */ + mapStringMessage: { [key: string]: Message }; + /** + * oneof_string + */ oneofString?: string; - // oneof_enum + /** + * oneof_enum + */ oneofEnum?: Enum; - // oneof_message1 + /** + * oneof_message1 + */ oneofMessage1?: Message; - // oneof_message2 + /** + * oneof_message2 + */ oneofMessage2?: Message; - // any + /** + * any + */ any: wellKnownAny | undefined; - // repeated_any + /** + * repeated_any + */ repeatedAny: wellKnownAny[] | undefined; - // duration + /** + * duration + */ duration: wellKnownDuration | undefined; - // repeated_duration + /** + * repeated_duration + */ repeatedDuration: wellKnownDuration[] | undefined; - // empty + /** + * empty + */ empty: wellKnownEmpty | undefined; - // repeated_empty + /** + * repeated_empty + */ repeatedEmpty: wellKnownEmpty[] | undefined; - // field_mask + /** + * field_mask + */ fieldMask: wellKnownFieldMask | undefined; - // repeated_field_mask + /** + * repeated_field_mask + */ repeatedFieldMask: wellKnownFieldMask[] | undefined; - // struct + /** + * struct + */ struct: wellKnownStruct | undefined; - // repeated_struct + /** + * repeated_struct + */ repeatedStruct: wellKnownStruct[] | undefined; - // value + /** + * value + */ value: wellKnownValue | undefined; - // repeated_value + /** + * repeated_value + */ repeatedValue: wellKnownValue[] | undefined; - // null_value - nullValue: wellKnownNullValue | undefined; - // repeated_null_value - repeatedNullValue: wellKnownNullValue[] | undefined; - // list_value + /** + * null_value + */ + nullValue: wellKnownNullValue; + /** + * repeated_null_value + */ + repeatedNullValue: wellKnownNullValue[]; + /** + * list_value + */ listValue: wellKnownListValue | undefined; - // repeated_list_value + /** + * repeated_list_value + */ repeatedListValue: wellKnownListValue[] | undefined; - // bool_value + /** + * bool_value + */ boolValue: wellKnownBoolValue | undefined; - // repeated_bool_value + /** + * repeated_bool_value + */ repeatedBoolValue: wellKnownBoolValue[] | undefined; - // bytes_value + /** + * bytes_value + */ bytesValue: wellKnownBytesValue | undefined; - // repeated_bytes_value + /** + * repeated_bytes_value + */ repeatedBytesValue: wellKnownBytesValue[] | undefined; - // double_value + /** + * double_value + */ doubleValue: wellKnownDoubleValue | undefined; - // repeated_double_value + /** + * repeated_double_value + */ repeatedDoubleValue: wellKnownDoubleValue[] | undefined; - // float_value + /** + * float_value + */ floatValue: wellKnownFloatValue | undefined; - // repeated_float_value + /** + * repeated_float_value + */ repeatedFloatValue: wellKnownFloatValue[] | undefined; - // int32_value + /** + * int32_value + */ int32Value: wellKnownInt32Value | undefined; - // repeated_int32_value + /** + * repeated_int32_value + */ repeatedInt32Value: wellKnownInt32Value[] | undefined; - // int64_value + /** + * int64_value + */ int64Value: wellKnownInt64Value | undefined; - // repeated_int64_value + /** + * repeated_int64_value + */ repeatedInt64Value: wellKnownInt64Value[] | undefined; - // uint32_value + /** + * uint32_value + */ uint32Value: wellKnownUInt32Value | undefined; - // repeated_uint32_value + /** + * repeated_uint32_value + */ repeatedUint32Value: wellKnownUInt32Value[] | undefined; - // uint64_value + /** + * uint64_value + */ uint64Value: wellKnownUInt64Value | undefined; - // repeated_uint64_value + /** + * repeated_uint64_value + */ repeatedUint64Value: wellKnownUInt64Value[] | undefined; - // string_value + /** + * string_value + */ stringValue: wellKnownUInt64Value | undefined; - // repeated_string_value + /** + * repeated_string_value + */ repeatedStringValue: wellKnownStringValue[] | undefined; }; @@ -262,56 +455,69 @@ type wellKnownFloatValue = number | null; type wellKnownInt32Value = number | null; -type wellKnownInt64Value = number | null; +type wellKnownInt64Value = string | null; type wellKnownUInt32Value = number | null; -type wellKnownUInt64Value = number | null; +type wellKnownUInt64Value = string | null; type wellKnownStringValue = string | null; -// NestedMessage +/** + * NestedMessage + */ export type Message_NestedMessage = { - // nested_message.string - string: string | undefined; + /** + * nested_message.string + */ + string: string; }; -// NestedEnum -export type Message_NestedEnum = - // NESTEDENUM_UNSPECIFIED - "NESTEDENUM_UNSPECIFIED"; +/** + * NestedEnum + */ +export enum Message_NestedEnum { + /** + * NESTEDENUM_UNSPECIFIED + */ + NestedenumUnspecified = 0, +} export type Request = { - string: string | undefined; - repeatedString: string[] | undefined; - nested: Request_Nested | undefined; + string: string; + repeatedString: string[]; + nested: Request_Nested; }; export type Request_Nested = { - string: string | undefined; + string: string; }; -export interface SyntaxService { - QueryOnly(request: Request): Promise; - EmptyVerb(request: wellKnownEmpty): Promise; - StarBody(request: Request): Promise; - Body(request: Request): Promise; - Path(request: Request): Promise; - PathBody(request: Request): Promise; +export interface SyntaxService { + queryOnly(request: Request, options?: T): Promise; + emptyVerb(request: wellKnownEmpty, options?: T): Promise; + starBody(request: Request, options?: T): Promise; + body(request: Request, options?: T): Promise; + path(request: Request, options?: T): Promise; + pathBody(request: Request, options?: T): Promise; } -type RequestType = { +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type RequestType | string | null> = { path: string; method: string; - body: string | null; + body: T; }; -type RequestHandler = (request: RequestType, meta: { service: string, method: string }) => Promise; +type RequestHandler = ( + request: RequestType & T, + meta: { service: string, method: string }, +) => Promise; -export function createSyntaxServiceClient( - handler: RequestHandler -): SyntaxService { +export function createSyntaxServiceClient( + handler: RequestHandler +): SyntaxService { return { - QueryOnly(request) { // eslint-disable-line @typescript-eslint/no-unused-vars + queryOnly(request, options) { // eslint-disable-line @typescript-eslint/no-unused-vars const path = `v1`; // eslint-disable-line quotes const body = null; const queryParams: string[] = []; @@ -334,12 +540,13 @@ export function createSyntaxServiceClient( path: uri, method: "GET", body, + ...(options as T), }, { service: "SyntaxService", method: "QueryOnly", }) as Promise; }, - EmptyVerb(request) { // eslint-disable-line @typescript-eslint/no-unused-vars + emptyVerb(request, options) { // eslint-disable-line @typescript-eslint/no-unused-vars const path = `v1:emptyVerb`; // eslint-disable-line quotes const body = null; const queryParams: string[] = []; @@ -351,14 +558,15 @@ export function createSyntaxServiceClient( path: uri, method: "GET", body, + ...(options as T), }, { service: "SyntaxService", method: "EmptyVerb", }) as Promise; }, - StarBody(request) { // eslint-disable-line @typescript-eslint/no-unused-vars + starBody(request, options) { // eslint-disable-line @typescript-eslint/no-unused-vars const path = `v1:starBody`; // eslint-disable-line quotes - const body = JSON.stringify(request); + const body = request; const queryParams: string[] = []; let uri = path; if (queryParams.length > 0) { @@ -368,14 +576,15 @@ export function createSyntaxServiceClient( path: uri, method: "POST", body, + ...(options as T), }, { service: "SyntaxService", method: "StarBody", }) as Promise; }, - Body(request) { // eslint-disable-line @typescript-eslint/no-unused-vars + body(request, options) { // eslint-disable-line @typescript-eslint/no-unused-vars const path = `v1:body`; // eslint-disable-line quotes - const body = JSON.stringify(request?.nested ?? {}); + const body = request?.nested ?? {}; const queryParams: string[] = []; if (request.string) { queryParams.push(`string=${encodeURIComponent(request.string.toString())}`) @@ -393,12 +602,13 @@ export function createSyntaxServiceClient( path: uri, method: "POST", body, + ...(options as T), }, { service: "SyntaxService", method: "Body", }) as Promise; }, - Path(request) { // eslint-disable-line @typescript-eslint/no-unused-vars + path(request, options) { // eslint-disable-line @typescript-eslint/no-unused-vars if (!request.string) { throw new Error("missing required field request.string"); } @@ -421,17 +631,18 @@ export function createSyntaxServiceClient( path: uri, method: "POST", body, + ...(options as T), }, { service: "SyntaxService", method: "Path", }) as Promise; }, - PathBody(request) { // eslint-disable-line @typescript-eslint/no-unused-vars + pathBody(request, options) { // eslint-disable-line @typescript-eslint/no-unused-vars if (!request.string) { throw new Error("missing required field request.string"); } const path = `v1/${request.string}:pathBody`; // eslint-disable-line quotes - const body = JSON.stringify(request?.nested ?? {}); + const body = request?.nested ?? {}; const queryParams: string[] = []; if (request.repeatedString) { request.repeatedString.forEach((x) => { @@ -446,6 +657,7 @@ export function createSyntaxServiceClient( path: uri, method: "POST", body, + ...(options as T), }, { service: "SyntaxService", method: "PathBody", diff --git a/examples/proto/gen/typescript/einride/example/syntax/v2/index.ts b/examples/proto/gen/typescript/einride/example/syntax/v2/index.ts index 8498ae6..c06a202 100644 --- a/examples/proto/gen/typescript/einride/example/syntax/v2/index.ts +++ b/examples/proto/gen/typescript/einride/example/syntax/v2/index.ts @@ -2,206 +2,401 @@ /* eslint-disable camelcase */ // @ts-nocheck -// Message +/** + * Message + */ export type Message = { - forwardedMessage: einrideexamplesyntaxv1_Message | undefined; - forwardedEnum: einrideexamplesyntaxv1_Enum | undefined; + forwardedMessage: einrideexamplesyntaxv1_Message; + forwardedEnum: einrideexamplesyntaxv1_Enum; }; -// Message +/** + * Message + */ export type einrideexamplesyntaxv1_Message = { - // double - double: number | undefined; - // float - float: number | undefined; - // int32 - int32: number | undefined; - // int64 - int64: number | undefined; - // uint32 - uint32: number | undefined; - // uint64 - uint64: number | undefined; - // sint32 - sint32: number | undefined; - // sint64 - sint64: number | undefined; - // fixed32 - fixed32: number | undefined; - // fixed64 - fixed64: number | undefined; - // sfixed32 - sfixed32: number | undefined; - // sfixed64 - sfixed64: number | undefined; - // bool - bool: boolean | undefined; - // string - string: string | undefined; - // bytes - bytes: string | undefined; - // enum - enum: einrideexamplesyntaxv1_Enum | undefined; - // message - message: einrideexamplesyntaxv1_Message | undefined; - // optional double + /** + * double + */ + double: number; + /** + * float + */ + float: number; + /** + * int32 + */ + int32: number; + /** + * int64 + */ + int64: string; + /** + * uint32 + */ + uint32: number; + /** + * uint64 + */ + uint64: string; + /** + * sint32 + */ + sint32: number; + /** + * sint64 + */ + sint64: string; + /** + * fixed32 + */ + fixed32: number; + /** + * fixed64 + */ + fixed64: string; + /** + * sfixed32 + */ + sfixed32: number; + /** + * sfixed64 + */ + sfixed64: string; + /** + * bool + */ + bool: boolean; + /** + * string + */ + string: string; + /** + * bytes + */ + bytes: string; + /** + * enum + */ + enum: einrideexamplesyntaxv1_Enum; + /** + * message + */ + message: einrideexamplesyntaxv1_Message; + /** + * optional double + */ optionalDouble?: number; - // optional float + /** + * optional float + */ optionalFloat?: number; - // optional int32 + /** + * optional int32 + */ optionalInt32?: number; - // optional int64 - optionalInt64?: number; - // optional uint32 + /** + * optional int64 + */ + optionalInt64?: string; + /** + * optional uint32 + */ optionalUint32?: number; - // optional uint64 - optionalUint64?: number; - // optional sint32 + /** + * optional uint64 + */ + optionalUint64?: string; + /** + * optional sint32 + */ optionalSint32?: number; - // optional sint64 - optionalSint64?: number; - // optional fixed32 + /** + * optional sint64 + */ + optionalSint64?: string; + /** + * optional fixed32 + */ optionalFixed32?: number; - // optional fixed64 - optionalFixed64?: number; - // optional sfixed32 + /** + * optional fixed64 + */ + optionalFixed64?: string; + /** + * optional sfixed32 + */ optionalSfixed32?: number; - // optional sfixed64 - optionalSfixed64?: number; - // optional bool + /** + * optional sfixed64 + */ + optionalSfixed64?: string; + /** + * optional bool + */ optionalBool?: boolean; - // optional string + /** + * optional string + */ optionalString?: string; - // optional bytes + /** + * optional bytes + */ optionalBytes?: string; - // optional enum + /** + * optional enum + */ optionalEnum?: einrideexamplesyntaxv1_Enum; - // optional message + /** + * optional message + */ optionalMessage?: einrideexamplesyntaxv1_Message; - // repeated_double - repeatedDouble: number[] | undefined; - // repeated_float - repeatedFloat: number[] | undefined; - // repeated_int32 - repeatedInt32: number[] | undefined; - // repeated_int64 - repeatedInt64: number[] | undefined; - // repeated_uint32 - repeatedUint32: number[] | undefined; - // repeated_uint64 - repeatedUint64: number[] | undefined; - // repeated_sint32 - repeatedSint32: number[] | undefined; - // repeated_sint64 - repeatedSint64: number[] | undefined; - // repeated_fixed32 - repeatedFixed32: number[] | undefined; - // repeated_fixed64 - repeatedFixed64: number[] | undefined; - // repeated_sfixed32 - repeatedSfixed32: number[] | undefined; - // repeated_sfixed64 - repeatedSfixed64: number[] | undefined; - // repeated_bool - repeatedBool: boolean[] | undefined; - // repeated_string - repeatedString: string[] | undefined; - // repeated_bytes - repeatedBytes: string[] | undefined; - // repeated_enum - repeatedEnum: einrideexamplesyntaxv1_Enum[] | undefined; - // repeated_message - repeatedMessage: einrideexamplesyntaxv1_Message[] | undefined; - // map_string_string - mapStringString: { [key: string]: string } | undefined; - // map_string_message - mapStringMessage: { [key: string]: einrideexamplesyntaxv1_Message } | undefined; - // oneof_string + /** + * repeated_double + */ + repeatedDouble: number[]; + /** + * repeated_float + */ + repeatedFloat: number[]; + /** + * repeated_int32 + */ + repeatedInt32: number[]; + /** + * repeated_int64 + */ + repeatedInt64: string[]; + /** + * repeated_uint32 + */ + repeatedUint32: number[]; + /** + * repeated_uint64 + */ + repeatedUint64: string[]; + /** + * repeated_sint32 + */ + repeatedSint32: number[]; + /** + * repeated_sint64 + */ + repeatedSint64: string[]; + /** + * repeated_fixed32 + */ + repeatedFixed32: number[]; + /** + * repeated_fixed64 + */ + repeatedFixed64: string[]; + /** + * repeated_sfixed32 + */ + repeatedSfixed32: number[]; + /** + * repeated_sfixed64 + */ + repeatedSfixed64: string[]; + /** + * repeated_bool + */ + repeatedBool: boolean[]; + /** + * repeated_string + */ + repeatedString: string[]; + /** + * repeated_bytes + */ + repeatedBytes: string[]; + /** + * repeated_enum + */ + repeatedEnum: einrideexamplesyntaxv1_Enum[]; + /** + * repeated_message + */ + repeatedMessage: einrideexamplesyntaxv1_Message[]; + /** + * map_string_string + */ + mapStringString: { [key: string]: string }; + /** + * map_string_message + */ + mapStringMessage: { [key: string]: einrideexamplesyntaxv1_Message }; + /** + * oneof_string + */ oneofString?: string; - // oneof_enum + /** + * oneof_enum + */ oneofEnum?: einrideexamplesyntaxv1_Enum; - // oneof_message1 + /** + * oneof_message1 + */ oneofMessage1?: einrideexamplesyntaxv1_Message; - // oneof_message2 + /** + * oneof_message2 + */ oneofMessage2?: einrideexamplesyntaxv1_Message; - // any + /** + * any + */ any: wellKnownAny | undefined; - // repeated_any + /** + * repeated_any + */ repeatedAny: wellKnownAny[] | undefined; - // duration + /** + * duration + */ duration: wellKnownDuration | undefined; - // repeated_duration + /** + * repeated_duration + */ repeatedDuration: wellKnownDuration[] | undefined; - // empty + /** + * empty + */ empty: wellKnownEmpty | undefined; - // repeated_empty + /** + * repeated_empty + */ repeatedEmpty: wellKnownEmpty[] | undefined; - // field_mask + /** + * field_mask + */ fieldMask: wellKnownFieldMask | undefined; - // repeated_field_mask + /** + * repeated_field_mask + */ repeatedFieldMask: wellKnownFieldMask[] | undefined; - // struct + /** + * struct + */ struct: wellKnownStruct | undefined; - // repeated_struct + /** + * repeated_struct + */ repeatedStruct: wellKnownStruct[] | undefined; - // value + /** + * value + */ value: wellKnownValue | undefined; - // repeated_value + /** + * repeated_value + */ repeatedValue: wellKnownValue[] | undefined; - // null_value - nullValue: wellKnownNullValue | undefined; - // repeated_null_value - repeatedNullValue: wellKnownNullValue[] | undefined; - // list_value + /** + * null_value + */ + nullValue: wellKnownNullValue; + /** + * repeated_null_value + */ + repeatedNullValue: wellKnownNullValue[]; + /** + * list_value + */ listValue: wellKnownListValue | undefined; - // repeated_list_value + /** + * repeated_list_value + */ repeatedListValue: wellKnownListValue[] | undefined; - // bool_value + /** + * bool_value + */ boolValue: wellKnownBoolValue | undefined; - // repeated_bool_value + /** + * repeated_bool_value + */ repeatedBoolValue: wellKnownBoolValue[] | undefined; - // bytes_value + /** + * bytes_value + */ bytesValue: wellKnownBytesValue | undefined; - // repeated_bytes_value + /** + * repeated_bytes_value + */ repeatedBytesValue: wellKnownBytesValue[] | undefined; - // double_value + /** + * double_value + */ doubleValue: wellKnownDoubleValue | undefined; - // repeated_double_value + /** + * repeated_double_value + */ repeatedDoubleValue: wellKnownDoubleValue[] | undefined; - // float_value + /** + * float_value + */ floatValue: wellKnownFloatValue | undefined; - // repeated_float_value + /** + * repeated_float_value + */ repeatedFloatValue: wellKnownFloatValue[] | undefined; - // int32_value + /** + * int32_value + */ int32Value: wellKnownInt32Value | undefined; - // repeated_int32_value + /** + * repeated_int32_value + */ repeatedInt32Value: wellKnownInt32Value[] | undefined; - // int64_value + /** + * int64_value + */ int64Value: wellKnownInt64Value | undefined; - // repeated_int64_value + /** + * repeated_int64_value + */ repeatedInt64Value: wellKnownInt64Value[] | undefined; - // uint32_value + /** + * uint32_value + */ uint32Value: wellKnownUInt32Value | undefined; - // repeated_uint32_value + /** + * repeated_uint32_value + */ repeatedUint32Value: wellKnownUInt32Value[] | undefined; - // uint64_value + /** + * uint64_value + */ uint64Value: wellKnownUInt64Value | undefined; - // repeated_uint64_value + /** + * repeated_uint64_value + */ repeatedUint64Value: wellKnownUInt64Value[] | undefined; - // string_value + /** + * string_value + */ stringValue: wellKnownUInt64Value | undefined; - // repeated_string_value + /** + * repeated_string_value + */ repeatedStringValue: wellKnownStringValue[] | undefined; }; -// Enum -export type einrideexamplesyntaxv1_Enum = - // ENUM_UNSPECIFIED - | "ENUM_UNSPECIFIED" - // ENUM_ONE - | "ENUM_ONE" - // ENUM_TWO - | "ENUM_TWO"; +/** + * Enum + */ +export enum einrideexamplesyntaxv1_Enum { + /** + * ENUM_UNSPECIFIED + */ + EnumUnspecified = 0, + /** + * ENUM_ONE + */ + EnumOne = 1, + /** + * ENUM_TWO + */ + EnumTwo = 2, +} // If the Any contains a value that has a special JSON mapping, // it will be converted as follows: // {"@type": xxx, "value": yyy}. @@ -268,23 +463,32 @@ type wellKnownFloatValue = number | null; type wellKnownInt32Value = number | null; -type wellKnownInt64Value = number | null; +type wellKnownInt64Value = string | null; type wellKnownUInt32Value = number | null; -type wellKnownUInt64Value = number | null; +type wellKnownUInt64Value = string | null; type wellKnownStringValue = string | null; -// NestedMessage +/** + * NestedMessage + */ export type einrideexamplesyntaxv1_Message_NestedMessage = { - // nested_message.string - string: string | undefined; + /** + * nested_message.string + */ + string: string; }; -// NestedEnum -export type einrideexamplesyntaxv1_Message_NestedEnum = - // NESTEDENUM_UNSPECIFIED - "NESTEDENUM_UNSPECIFIED"; +/** + * NestedEnum + */ +export enum einrideexamplesyntaxv1_Message_NestedEnum { + /** + * NESTEDENUM_UNSPECIFIED + */ + NestedenumUnspecified = 0, +} // @@protoc_insertion_point(typescript-http-eof) diff --git a/examples/proto/gen/typescript/index.ts b/examples/proto/gen/typescript/index.ts new file mode 100644 index 0000000..b076efc --- /dev/null +++ b/examples/proto/gen/typescript/index.ts @@ -0,0 +1,6 @@ +// Code generated by protoc-gen-typescript-http. DO NOT EDIT. +/* eslint-disable camelcase */ +// @ts-nocheck + + +// @@protoc_insertion_point(typescript-http-eof) diff --git a/go.mod b/go.mod index 58ebbf8..964aec5 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module go.einride.tech/protoc-gen-typescript-http go 1.17 require ( + github.com/iancoleman/strcase v0.3.0 google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d google.golang.org/protobuf v1.28.1 gotest.tools/v3 v3.4.0 diff --git a/go.sum b/go.sum index 0ead040..38e3650 100644 --- a/go.sum +++ b/go.sum @@ -22,6 +22,8 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= diff --git a/internal/plugin/commentgen.go b/internal/plugin/commentgen.go index 9bdae4c..4592157 100644 --- a/internal/plugin/commentgen.go +++ b/internal/plugin/commentgen.go @@ -10,24 +10,36 @@ import ( ) type commentGenerator struct { + opts Options descriptor protoreflect.Descriptor } func (c commentGenerator) generateLeading(f *codegen.File, indent int) { loc := c.descriptor.ParentFile().SourceLocations().ByDescriptor(c.descriptor) lines := strings.Split(loc.LeadingComments, "\n") + + commentPrefix := getCommentPrefix(c.opts.UseMultiLineComment) + + if c.opts.UseMultiLineComment && len(loc.LeadingComments) > 0 { + f.P(t(indent), "/**") + } + for _, line := range lines { if line == "" { continue } - f.P(t(indent), "// ", strings.TrimSpace(line)) + f.P(t(indent), commentPrefix, " ", strings.TrimSpace(line)) } if field, ok := c.descriptor.(protoreflect.FieldDescriptor); ok { if behaviorComment := fieldBehaviorComment(field); len(behaviorComment) > 0 { - f.P(t(indent), "//") - f.P(t(indent), "// ", behaviorComment) + f.P(t(indent), commentPrefix) + f.P(t(indent), commentPrefix, " ", behaviorComment) } } + + if c.opts.UseMultiLineComment && len(loc.LeadingComments) > 0 { + f.P(t(indent), " */") + } } func fieldBehaviorComment(field protoreflect.FieldDescriptor) string { @@ -51,3 +63,11 @@ func getFieldBehaviors(field protoreflect.FieldDescriptor) []annotations.FieldBe } return nil } + +func getCommentPrefix(multiline bool) string { + if multiline { + return " *" + } + + return "//" +} diff --git a/internal/plugin/enumgen.go b/internal/plugin/enumgen.go index e871713..d189d65 100644 --- a/internal/plugin/enumgen.go +++ b/internal/plugin/enumgen.go @@ -8,24 +8,40 @@ import ( ) type enumGenerator struct { + opts Options pkg protoreflect.FullName enum protoreflect.EnumDescriptor } func (e enumGenerator) Generate(f *codegen.File) { - commentGenerator{descriptor: e.enum}.generateLeading(f, 0) - f.P("export type ", scopedDescriptorTypeName(e.pkg, e.enum), " =") - if e.enum.Values().Len() == 1 { - commentGenerator{descriptor: e.enum.Values().Get(0)}.generateLeading(f, 1) - f.P(t(1), strconv.Quote(string(e.enum.Values().Get(0).Name())), ";") - return - } - rangeEnumValues(e.enum, func(value protoreflect.EnumValueDescriptor, last bool) { - commentGenerator{descriptor: value}.generateLeading(f, 1) - if last { - f.P(t(1), "| ", strconv.Quote(string(value.Name())), ";") - } else { - f.P(t(1), "| ", strconv.Quote(string(value.Name()))) + commentGenerator{opts: e.opts, descriptor: e.enum}.generateLeading(f, 0) + if e.opts.UseEnumNumbers { + f.P("export enum ", scopedDescriptorTypeName(e.pkg, e.enum), " {") + + rangeEnumValues(e.enum, func(value protoreflect.EnumValueDescriptor, last bool) { + commentGenerator{opts: e.opts, descriptor: value}.generateLeading(f, 1) + + name := string(value.Name()) + name = TextToCase(name, e.opts.EnumFieldNaming) + + f.P(t(1), name, " = ", value.Number(), ",") + }) + + f.P("}") + } else { + f.P("export type ", scopedDescriptorTypeName(e.pkg, e.enum), " =") + if e.enum.Values().Len() == 1 { + commentGenerator{opts: e.opts, descriptor: e.enum.Values().Get(0)}.generateLeading(f, 1) + f.P(t(1), strconv.Quote(string(e.enum.Values().Get(0).Name())), ";") + return } - }) + rangeEnumValues(e.enum, func(value protoreflect.EnumValueDescriptor, last bool) { + commentGenerator{opts: e.opts, descriptor: value}.generateLeading(f, 1) + if last { + f.P(t(1), "| ", strconv.Quote(string(value.Name())), ";") + } else { + f.P(t(1), "| ", strconv.Quote(string(value.Name()))) + } + }) + } } diff --git a/internal/plugin/generate.go b/internal/plugin/generate.go index e46f5ba..3c19323 100644 --- a/internal/plugin/generate.go +++ b/internal/plugin/generate.go @@ -13,7 +13,39 @@ import ( "google.golang.org/protobuf/types/pluginpb" ) -func Generate(request *pluginpb.CodeGeneratorRequest) (*pluginpb.CodeGeneratorResponse, error) { +type Options struct { + // UseProtoNames controls the casing of generated field names. + // If set to true, fields will use proto names (typically snake_case). + // If omitted or set to false, fields will use JSON names (typically camelCase). + UseProtoNames bool + // UseEnumNumbers emits enum values as numbers. + UseEnumNumbers bool + // The method names of service methods naming case. + // Only work when `UseEnumNumbers=true` + // opt: + // camelcase: convert name to lower camel case like `camelCase` + // pascalcase: convert name to pascalcase like `PascalCase` + // default is pascalcase + EnumFieldNaming string + // Generate comments as multiline comments. + UseMultiLineComment bool + // force add `undefined` to message field. + // default true + ForceMessageFieldUndefinable bool + // If set to true, body will be JSON.stringify before send + // default true + UseBodyStringify bool + // The method names of service methods naming case. + // opt: + // camelcase: convert name to lower camel case like `camelCase` + // pascalcase: convert name to pascalcase like `PascalCase` + // default is pascalcase + ServiceMethodNaming string + // If set to true, field int64 and uint64 will convert to string + ForceLongAsString bool +} + +func Generate(request *pluginpb.CodeGeneratorRequest, opts Options) (*pluginpb.CodeGeneratorResponse, error) { generate := make(map[string]struct{}) registry, err := protodesc.NewFiles(&descriptorpb.FileDescriptorSet{ File: request.ProtoFile, @@ -37,7 +69,7 @@ func Generate(request *pluginpb.CodeGeneratorRequest) (*pluginpb.CodeGeneratorRe for pkg, files := range packaged { var index codegen.File indexPathElems := append(strings.Split(string(pkg), "."), "index.ts") - if err := (packageGenerator{pkg: pkg, files: files}).Generate(&index); err != nil { + if err := (packageGenerator{opts: opts, pkg: pkg, files: files}).Generate(&index); err != nil { return nil, fmt.Errorf("generate package '%s': %w", pkg, err) } index.P() diff --git a/internal/plugin/helpers.go b/internal/plugin/helpers.go index 40cb040..b1e5988 100644 --- a/internal/plugin/helpers.go +++ b/internal/plugin/helpers.go @@ -3,6 +3,7 @@ package plugin import ( "strings" + "github.com/iancoleman/strcase" "google.golang.org/protobuf/reflect/protoreflect" ) @@ -56,3 +57,14 @@ func rangeEnumValues(enum protoreflect.EnumDescriptor, f func(value protoreflect func t(n int) string { return strings.Repeat(" ", n) } + +func TextToCase(text, textcase string) string { + switch textcase { + case "camelcase": + return strcase.ToLowerCamel(text) + case "pascalcase": + return strcase.ToCamel(text) + default: + return text + } +} diff --git a/internal/plugin/messagegen.go b/internal/plugin/messagegen.go index 3dd9256..4f6e9b1 100644 --- a/internal/plugin/messagegen.go +++ b/internal/plugin/messagegen.go @@ -1,25 +1,50 @@ package plugin import ( + "strings" + "go.einride.tech/protoc-gen-typescript-http/internal/codegen" "google.golang.org/protobuf/reflect/protoreflect" ) type messageGenerator struct { + opts Options pkg protoreflect.FullName message protoreflect.MessageDescriptor } func (m messageGenerator) Generate(f *codegen.File) { - commentGenerator{descriptor: m.message}.generateLeading(f, 0) + commentGenerator{opts: m.opts, descriptor: m.message}.generateLeading(f, 0) f.P("export type ", scopedDescriptorTypeName(m.pkg, m.message), " = {") rangeFields(m.message, func(field protoreflect.FieldDescriptor) { - commentGenerator{descriptor: field}.generateLeading(f, 1) + commentGenerator{opts: m.opts, descriptor: field}.generateLeading(f, 1) fieldType := typeFromField(m.pkg, field) + + name := field.JSONName() + if m.opts.UseProtoNames { + name = field.TextName() + } + + var types []string + + reference := fieldType.Reference() + + if m.opts.ForceLongAsString && IsTypeLong(field) { + reference = strings.ReplaceAll(reference, "number", "string") + } + + types = append(types, reference) + + if IsWellKnownType(field.Message()) || m.opts.ForceMessageFieldUndefinable { + types = append(types, "undefined") + } + + typesString := strings.Join(types, " | ") + if field.ContainingOneof() == nil && !field.HasOptionalKeyword() { - f.P(t(1), field.JSONName(), ": ", fieldType.Reference(), " | undefined;") + f.P(t(1), name, ": ", typesString, ";") } else { - f.P(t(1), field.JSONName(), "?: ", fieldType.Reference(), ";") + f.P(t(1), name, "?: ", typesString, ";") } }) diff --git a/internal/plugin/packagegen.go b/internal/plugin/packagegen.go index a1f5de1..bc6d062 100644 --- a/internal/plugin/packagegen.go +++ b/internal/plugin/packagegen.go @@ -1,12 +1,15 @@ package plugin import ( + "strings" + "go.einride.tech/protoc-gen-typescript-http/internal/codegen" "go.einride.tech/protoc-gen-typescript-http/internal/protowalk" "google.golang.org/protobuf/reflect/protoreflect" ) type packageGenerator struct { + opts Options pkg protoreflect.FullName files []protoreflect.FileDescriptor } @@ -17,7 +20,11 @@ func (p packageGenerator) Generate(f *codegen.File) error { var walkErr error protowalk.WalkFiles(p.files, func(desc protoreflect.Descriptor) bool { if wkt, ok := WellKnownType(desc); ok { - f.P(wkt.TypeDeclaration()) + d := wkt.TypeDeclaration() + if p.opts.ForceLongAsString && IsWellKnownTypeLong(wkt) { + d = strings.ReplaceAll(d, "number", "string") + } + f.P(d) return false } switch v := desc.(type) { @@ -25,11 +32,16 @@ func (p packageGenerator) Generate(f *codegen.File) error { if v.IsMapEntry() { return false } - messageGenerator{pkg: p.pkg, message: v}.Generate(f) + messageGenerator{opts: p.opts, pkg: p.pkg, message: v}.Generate(f) case protoreflect.EnumDescriptor: - enumGenerator{pkg: p.pkg, enum: v}.Generate(f) + enumGenerator{opts: p.opts, pkg: p.pkg, enum: v}.Generate(f) case protoreflect.ServiceDescriptor: - if err := (serviceGenerator{pkg: p.pkg, service: v, genHandler: !seenService}).Generate(f); err != nil { + if err := (serviceGenerator{ + opts: p.opts, + pkg: p.pkg, + service: v, + genHandler: !seenService, + }).Generate(f); err != nil { walkErr = err return false } diff --git a/internal/plugin/servicegen.go b/internal/plugin/servicegen.go index de883b7..1534392 100644 --- a/internal/plugin/servicegen.go +++ b/internal/plugin/servicegen.go @@ -11,6 +11,7 @@ import ( ) type serviceGenerator struct { + opts Options pkg protoreflect.FullName genHandler bool service protoreflect.ServiceDescriptor @@ -26,7 +27,7 @@ func (s serviceGenerator) Generate(f *codegen.File) error { func (s serviceGenerator) generateInterface(f *codegen.File) { commentGenerator{descriptor: s.service}.generateLeading(f, 0) - f.P("export interface ", descriptorTypeName(s.service), " {") + f.P("export interface ", descriptorTypeName(s.service), " {") rangeMethods(s.service.Methods(), func(method protoreflect.MethodDescriptor) { if !supportedMethod(method) { return @@ -34,20 +35,26 @@ func (s serviceGenerator) generateInterface(f *codegen.File) { commentGenerator{descriptor: method}.generateLeading(f, 1) input := typeFromMessage(s.pkg, method.Input()) output := typeFromMessage(s.pkg, method.Output()) - f.P(t(1), method.Name(), "(request: ", input.Reference(), "): Promise<", output.Reference(), ">;") + + name := methodName(string(method.Name()), s.opts.ServiceMethodNaming) + f.P(t(1), name, "(request: ", input.Reference(), ", options?: T): Promise<", output.Reference(), ">;") }) f.P("}") f.P() } func (s serviceGenerator) generateHandler(f *codegen.File) { - f.P("type RequestType = {") + f.P("// eslint-disable-next-line @typescript-eslint/no-explicit-any") + f.P("type RequestType | string | null> = {") f.P(t(1), "path: string;") f.P(t(1), "method: string;") - f.P(t(1), "body: string | null;") + f.P(t(1), "body: T;") f.P("};") f.P() - f.P("type RequestHandler = (request: RequestType, meta: { service: string, method: string }) => Promise;") + f.P("type RequestHandler = (") + f.P(t(1), "request: RequestType & T,") + f.P(t(1), "meta: { service: string, method: string },") + f.P(") => Promise;") f.P() } @@ -55,14 +62,14 @@ func (s serviceGenerator) generateClient(f *codegen.File) error { f.P( "export function create", descriptorTypeName(s.service), - "Client(", + "Client(", "\n", t(1), - "handler: RequestHandler", + "handler: RequestHandler", "\n", "): ", descriptorTypeName(s.service), - " {", + " {", ) f.P(t(1), "return {") var methodErr error @@ -89,7 +96,8 @@ func (s serviceGenerator) generateMethod(f *codegen.File, method protoreflect.Me if err != nil { return fmt.Errorf("parse http rule: %w", err) } - f.P(t(2), method.Name(), "(request) { // eslint-disable-line @typescript-eslint/no-unused-vars") + name := methodName(string(method.Name()), s.opts.ServiceMethodNaming) + f.P(t(2), name, "(request, options) { // eslint-disable-line @typescript-eslint/no-unused-vars") s.generateMethodPathValidation(f, method.Input(), rule) s.generateMethodPath(f, method.Input(), rule) s.generateMethodBody(f, method.Input(), rule) @@ -102,6 +110,7 @@ func (s serviceGenerator) generateMethod(f *codegen.File, method protoreflect.Me f.P(t(4), "path: uri,") f.P(t(4), "method: ", strconv.Quote(rule.Method), ",") f.P(t(4), "body,") + f.P(t(4), "...(options as T),") f.P(t(3), "}, {") f.P(t(4), "service: \"", method.Parent().Name(), "\",") f.P(t(4), "method: \"", method.Name(), "\",") @@ -120,7 +129,7 @@ func (s serviceGenerator) generateMethodPathValidation( continue } fp := seg.Variable.FieldPath - nullPath := nullPropagationPath(fp, input) + nullPath := s.nullPropagationPath(fp, input) protoPath := strings.Join(fp, ".") errMsg := "missing required field request." + protoPath f.P(t(3), "if (!request.", nullPath, ") {") @@ -138,7 +147,7 @@ func (s serviceGenerator) generateMethodPath( for _, seg := range rule.Template.Segments { switch seg.Kind { case httprule.SegmentKindVariable: - fieldPath := jsonPath(seg.Variable.FieldPath, input) + fieldPath := s.jsonPath(seg.Variable.FieldPath, input) pathParts = append(pathParts, "${request."+fieldPath+"}") case httprule.SegmentKindLiteral: pathParts = append(pathParts, seg.Literal) @@ -164,10 +173,18 @@ func (s serviceGenerator) generateMethodBody( case rule.Body == "": f.P(t(3), "const body = null;") case rule.Body == "*": - f.P(t(3), "const body = JSON.stringify(request);") + if s.opts.UseBodyStringify { + f.P(t(3), "const body = JSON.stringify(request);") + } else { + f.P(t(3), "const body = request;") + } default: - nullPath := nullPropagationPath(httprule.FieldPath{rule.Body}, input) - f.P(t(3), "const body = JSON.stringify(request?.", nullPath, " ?? {});") + nullPath := s.nullPropagationPath(httprule.FieldPath{rule.Body}, input) + if s.opts.UseBodyStringify { + f.P(t(3), "const body = JSON.stringify(request?.", nullPath, " ?? {});") + } else { + f.P(t(3), "const body = request?.", nullPath, " ?? {};") + } } } @@ -196,8 +213,8 @@ func (s serviceGenerator) generateMethodQuery( if rule.Body != "" && path[0] == rule.Body { return } - nullPath := nullPropagationPath(path, input) - jp := jsonPath(path, input) + nullPath := s.nullPropagationPath(path, input) + jp := s.jsonPath(path, input) f.P(t(3), "if (request.", nullPath, ") {") switch { case field.IsList(): @@ -216,22 +233,30 @@ func supportedMethod(method protoreflect.MethodDescriptor) bool { return ok && !method.IsStreamingClient() && !method.IsStreamingServer() } -func jsonPath(path httprule.FieldPath, message protoreflect.MessageDescriptor) string { - return strings.Join(jsonPathSegments(path, message), ".") +func (s serviceGenerator) jsonPath(path httprule.FieldPath, message protoreflect.MessageDescriptor) string { + return strings.Join(s.jsonPathSegments(path, message), ".") } -func nullPropagationPath(path httprule.FieldPath, message protoreflect.MessageDescriptor) string { - return strings.Join(jsonPathSegments(path, message), "?.") +func (s serviceGenerator) nullPropagationPath(path httprule.FieldPath, message protoreflect.MessageDescriptor) string { + return strings.Join(s.jsonPathSegments(path, message), "?.") } -func jsonPathSegments(path httprule.FieldPath, message protoreflect.MessageDescriptor) []string { +func (s serviceGenerator) jsonPathSegments(path httprule.FieldPath, message protoreflect.MessageDescriptor) []string { segs := make([]string, len(path)) for i, p := range path { field := message.Fields().ByName(protoreflect.Name(p)) - segs[i] = field.JSONName() + if s.opts.UseProtoNames { + segs[i] = field.TextName() + } else { + segs[i] = field.JSONName() + } if i < len(path) { message = field.Message() } } return segs } + +func methodName(name, textcase string) string { + return TextToCase(name, textcase) +} diff --git a/internal/plugin/type.go b/internal/plugin/type.go index 94ef602..88edc54 100644 --- a/internal/plugin/type.go +++ b/internal/plugin/type.go @@ -1,6 +1,8 @@ package plugin -import "google.golang.org/protobuf/reflect/protoreflect" +import ( + "google.golang.org/protobuf/reflect/protoreflect" +) type Type struct { IsNamed bool @@ -80,3 +82,17 @@ func typeFromMessage(pkg protoreflect.FullName, message protoreflect.MessageDesc } return Type{IsNamed: true, Name: scopedDescriptorTypeName(pkg, message)} } + +func IsTypeLong(field protoreflect.FieldDescriptor) bool { + switch field.Kind() { + case + protoreflect.Int64Kind, + protoreflect.Uint64Kind, + protoreflect.Fixed64Kind, + protoreflect.Sfixed64Kind, + protoreflect.Sint64Kind: + return true + default: + return false + } +} diff --git a/internal/plugin/wellknown.go b/internal/plugin/wellknown.go index 6a8cf00..1df8dbe 100644 --- a/internal/plugin/wellknown.go +++ b/internal/plugin/wellknown.go @@ -155,3 +155,15 @@ func (w *writer) P(ss ...string) { func (w *writer) String() string { return w.b.String() } + +// Check is WellKnownInt64Value or WellKnownUInt64Value type. +func IsWellKnownTypeLong(wkt WellKnown) bool { + switch wkt { + case + WellKnownInt64Value, + WellKnownUInt64Value: + return true + default: + return false + } +} diff --git a/main.go b/main.go index 2e0572f..81b28ae 100644 --- a/main.go +++ b/main.go @@ -5,6 +5,8 @@ import ( "io" "os" "path/filepath" + "strconv" + "strings" "go.einride.tech/protoc-gen-typescript-http/internal/plugin" "google.golang.org/protobuf/proto" @@ -18,6 +20,72 @@ func main() { } } +const TrueString = "true" + +func NewOptions(parameter string) plugin.Options { + opts := plugin.Options{ + UseMultiLineComment: true, + ForceMessageFieldUndefinable: true, + UseBodyStringify: true, + ServiceMethodNaming: "none", + EnumFieldNaming: "pascalcase", + } + + for _, param := range strings.Split(parameter, ",") { + var value string + if i := strings.Index(param, "="); i >= 0 { + value = param[i+1:] + param = param[0:i] + } + + switch param { + case "use_enum_numbers": + enable, err := strconv.ParseBool(value) + if err != nil { + enable = false + } + + opts.UseEnumNumbers = enable + case "use_proto_names": + enable, err := strconv.ParseBool(value) + if err != nil { + enable = false + } + opts.UseProtoNames = enable + case "use_multiline_comment": + enable, err := strconv.ParseBool(value) + if err != nil { + enable = false + } + opts.UseMultiLineComment = enable + case "force_message_field_undefined": + enable, err := strconv.ParseBool(value) + if err != nil { + enable = false + } + opts.ForceMessageFieldUndefinable = enable + case "force_long_as_string": + enable, err := strconv.ParseBool(value) + if err != nil { + enable = false + } + opts.ForceLongAsString = enable + case "use_body_stringify": + enable, err := strconv.ParseBool(value) + if err != nil { + enable = false + } + opts.UseBodyStringify = enable + case "service_method_naming": + opts.ServiceMethodNaming = value + case "enum_field_naming": + opts.EnumFieldNaming = value + } + } + + return opts +} + func run() error { in, err := io.ReadAll(os.Stdin) if err != nil { @@ -27,7 +95,8 @@ func run() error { if err := proto.Unmarshal(in, req); err != nil { return err } - resp, err := plugin.Generate(req) + opts := NewOptions(req.GetParameter()) + resp, err := plugin.Generate(req, opts) if err != nil { return err } diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..8ef2c7a --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,33 @@ +{ + "compilerOptions": { + // type checking + "exactOptionalPropertyTypes": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "strict": true, + + // modules + "module": "es2020", + "moduleResolution": "bundler", + "resolveJsonModule": true, + + // emit + "noEmit": true, + + // javascript support + "allowJs": false, + + // interop constraints + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + + // landguage and environment + "jsx": "react-jsx", + "lib": ["es2021", "dom", "dom.iterable"], + "target": "es2021", + + // completeness + "skipLibCheck": true + } +} From b365ff134b4601ab5f5ebc4febb0501d4439405b Mon Sep 17 00:00:00 2001 From: Caster Date: Sun, 15 Oct 2023 02:06:57 +0800 Subject: [PATCH 2/3] feat: add generate options --- .../example/freight/v1/freight_service.proto | 10 +++++ .../einride/example/freight/v1/index.ts | 42 ++++++++++++++----- .../einride/example/syntax/v1/index.ts | 22 ++++++---- internal/plugin/servicegen.go | 24 +++++++++-- 4 files changed, 76 insertions(+), 22 deletions(-) diff --git a/examples/proto/einride/example/freight/v1/freight_service.proto b/examples/proto/einride/example/freight/v1/freight_service.proto index 1bf4afd..1917283 100644 --- a/examples/proto/einride/example/freight/v1/freight_service.proto +++ b/examples/proto/einride/example/freight/v1/freight_service.proto @@ -327,6 +327,16 @@ message ListShipmentsRequest { // [ListShipmentsResponse.next_page_token][einride.example.freight.v1.ListShipmentsResponse.next_page_token] // returned from the previous call to `ListShipments` method. string page_token = 3; + + // query condition + map query = 4; +} + +message QueryMessage { + string key = 1; + string value = 2; + + optional QueryMessage nested_query = 3; } // Response message for FreightService.ListShipments. diff --git a/examples/proto/gen/typescript/einride/example/freight/v1/index.ts b/examples/proto/gen/typescript/einride/example/freight/v1/index.ts index 2de8aea..3a93fb7 100644 --- a/examples/proto/gen/typescript/einride/example/freight/v1/index.ts +++ b/examples/proto/gen/typescript/einride/example/freight/v1/index.ts @@ -493,6 +493,16 @@ export type ListShipmentsRequest = { * returned from the previous call to `ListShipments` method. */ pageToken: string; + /** + * query condition + */ + query: { [key: string]: QueryMessage }; +}; + +export type QueryMessage = { + key: string; + value: string; + nestedQuery?: QueryMessage; }; /** @@ -635,7 +645,11 @@ type RequestHandler = ( ) => Promise; export function createFreightServiceClient( - handler: RequestHandler + handler: RequestHandler, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + handlerOptions: { + mapStringify?: (map: Record) => string; + } = {}, ): FreightService { return { getShipper(request, options) { // eslint-disable-line @typescript-eslint/no-unused-vars @@ -664,10 +678,10 @@ export function createFreightServiceClient( const body = null; const queryParams: string[] = []; if (request.pageSize) { - queryParams.push(`pageSize=${encodeURIComponent(request.pageSize.toString())}`) + queryParams.push(`pageSize=${encodeURIComponent(request.pageSize.toString())}`); } if (request.pageToken) { - queryParams.push(`pageToken=${encodeURIComponent(request.pageToken.toString())}`) + queryParams.push(`pageToken=${encodeURIComponent(request.pageToken.toString())}`); } let uri = path; if (queryParams.length > 0) { @@ -709,7 +723,7 @@ export function createFreightServiceClient( const body = request?.shipper ?? {}; const queryParams: string[] = []; if (request.updateMask) { - queryParams.push(`updateMask=${encodeURIComponent(request.updateMask.toString())}`) + queryParams.push(`updateMask=${encodeURIComponent(request.updateMask.toString())}`); } let uri = path; if (queryParams.length > 0) { @@ -775,10 +789,10 @@ export function createFreightServiceClient( const body = null; const queryParams: string[] = []; if (request.pageSize) { - queryParams.push(`pageSize=${encodeURIComponent(request.pageSize.toString())}`) + queryParams.push(`pageSize=${encodeURIComponent(request.pageSize.toString())}`); } if (request.pageToken) { - queryParams.push(`pageToken=${encodeURIComponent(request.pageToken.toString())}`) + queryParams.push(`pageToken=${encodeURIComponent(request.pageToken.toString())}`); } let uri = path; if (queryParams.length > 0) { @@ -823,7 +837,7 @@ export function createFreightServiceClient( const body = request?.site ?? {}; const queryParams: string[] = []; if (request.updateMask) { - queryParams.push(`updateMask=${encodeURIComponent(request.updateMask.toString())}`) + queryParams.push(`updateMask=${encodeURIComponent(request.updateMask.toString())}`); } let uri = path; if (queryParams.length > 0) { @@ -889,10 +903,18 @@ export function createFreightServiceClient( const body = null; const queryParams: string[] = []; if (request.pageSize) { - queryParams.push(`pageSize=${encodeURIComponent(request.pageSize.toString())}`) + queryParams.push(`pageSize=${encodeURIComponent(request.pageSize.toString())}`); } if (request.pageToken) { - queryParams.push(`pageToken=${encodeURIComponent(request.pageToken.toString())}`) + queryParams.push(`pageToken=${encodeURIComponent(request.pageToken.toString())}`); + } + if (request.query) { + const query = handlerOptions?.mapStringify + ? handlerOptions.mapStringify(request.query) + : Object.entries(request.query).map((x) => ( + `${encodeURIComponent(`query[${x[0]}]`)}=${encodeURIComponent(x[1].toString())}` + )); + queryParams.push(query); } let uri = path; if (queryParams.length > 0) { @@ -937,7 +959,7 @@ export function createFreightServiceClient( const body = request?.shipment ?? {}; const queryParams: string[] = []; if (request.updateMask) { - queryParams.push(`updateMask=${encodeURIComponent(request.updateMask.toString())}`) + queryParams.push(`updateMask=${encodeURIComponent(request.updateMask.toString())}`); } let uri = path; if (queryParams.length > 0) { diff --git a/examples/proto/gen/typescript/einride/example/syntax/v1/index.ts b/examples/proto/gen/typescript/einride/example/syntax/v1/index.ts index b3a9b5c..d969d44 100644 --- a/examples/proto/gen/typescript/einride/example/syntax/v1/index.ts +++ b/examples/proto/gen/typescript/einride/example/syntax/v1/index.ts @@ -514,7 +514,11 @@ type RequestHandler = ( ) => Promise; export function createSyntaxServiceClient( - handler: RequestHandler + handler: RequestHandler, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + handlerOptions: { + mapStringify?: (map: Record) => string; + } = {}, ): SyntaxService { return { queryOnly(request, options) { // eslint-disable-line @typescript-eslint/no-unused-vars @@ -522,15 +526,15 @@ export function createSyntaxServiceClient( const body = null; const queryParams: string[] = []; if (request.string) { - queryParams.push(`string=${encodeURIComponent(request.string.toString())}`) + queryParams.push(`string=${encodeURIComponent(request.string.toString())}`); } if (request.repeatedString) { request.repeatedString.forEach((x) => { - queryParams.push(`repeatedString=${encodeURIComponent(x.toString())}`) + queryParams.push(`repeatedString=${encodeURIComponent(x.toString())}`); }) } if (request.nested?.string) { - queryParams.push(`nested.string=${encodeURIComponent(request.nested.string.toString())}`) + queryParams.push(`nested.string=${encodeURIComponent(request.nested.string.toString())}`); } let uri = path; if (queryParams.length > 0) { @@ -587,11 +591,11 @@ export function createSyntaxServiceClient( const body = request?.nested ?? {}; const queryParams: string[] = []; if (request.string) { - queryParams.push(`string=${encodeURIComponent(request.string.toString())}`) + queryParams.push(`string=${encodeURIComponent(request.string.toString())}`); } if (request.repeatedString) { request.repeatedString.forEach((x) => { - queryParams.push(`repeatedString=${encodeURIComponent(x.toString())}`) + queryParams.push(`repeatedString=${encodeURIComponent(x.toString())}`); }) } let uri = path; @@ -617,11 +621,11 @@ export function createSyntaxServiceClient( const queryParams: string[] = []; if (request.repeatedString) { request.repeatedString.forEach((x) => { - queryParams.push(`repeatedString=${encodeURIComponent(x.toString())}`) + queryParams.push(`repeatedString=${encodeURIComponent(x.toString())}`); }) } if (request.nested?.string) { - queryParams.push(`nested.string=${encodeURIComponent(request.nested.string.toString())}`) + queryParams.push(`nested.string=${encodeURIComponent(request.nested.string.toString())}`); } let uri = path; if (queryParams.length > 0) { @@ -646,7 +650,7 @@ export function createSyntaxServiceClient( const queryParams: string[] = []; if (request.repeatedString) { request.repeatedString.forEach((x) => { - queryParams.push(`repeatedString=${encodeURIComponent(x.toString())}`) + queryParams.push(`repeatedString=${encodeURIComponent(x.toString())}`); }) } let uri = path; diff --git a/internal/plugin/servicegen.go b/internal/plugin/servicegen.go index 1534392..7384989 100644 --- a/internal/plugin/servicegen.go +++ b/internal/plugin/servicegen.go @@ -65,7 +65,18 @@ func (s serviceGenerator) generateClient(f *codegen.File) error { "Client(", "\n", t(1), - "handler: RequestHandler", + "handler: RequestHandler,", + "\n", + t(1), + "// eslint-disable-next-line @typescript-eslint/no-unused-vars", + "\n", + t(1), + "handlerOptions: {", + "\n", + t(2), "mapStringify?: (map: Record) => string;", + "\n", + t(1), + "} = {},", "\n", "): ", descriptorTypeName(s.service), @@ -217,12 +228,19 @@ func (s serviceGenerator) generateMethodQuery( jp := s.jsonPath(path, input) f.P(t(3), "if (request.", nullPath, ") {") switch { + case field.IsMap(): + f.P(t(4), "const ", jp, " = handlerOptions?.mapStringify") + f.P(t(5), "? handlerOptions.mapStringify(request.", jp, ")") + f.P(t(5), ": Object.entries(request.", jp, ").map((x) => (") + f.P(t(6), "`${encodeURIComponent(`", jp, "[${x[0]}]`)}=${encodeURIComponent(x[1].toString())}`") + f.P(t(5), "));") + f.P(t(4), "queryParams.push(", jp, ");") case field.IsList(): f.P(t(4), "request.", jp, ".forEach((x) => {") - f.P(t(5), "queryParams.push(`", jp, "=${encodeURIComponent(x.toString())}`)") + f.P(t(5), "queryParams.push(`", jp, "=${encodeURIComponent(x.toString())}`);") f.P(t(4), "})") default: - f.P(t(4), "queryParams.push(`", jp, "=${encodeURIComponent(request.", jp, ".toString())}`)") + f.P(t(4), "queryParams.push(`", jp, "=${encodeURIComponent(request.", jp, ".toString())}`);") } f.P(t(3), "}") }) From 0531ecb8cefefd4152bbc05690e87de927774ba9 Mon Sep 17 00:00:00 2001 From: Caster Date: Tue, 17 Oct 2023 01:58:21 +0800 Subject: [PATCH 3/3] feat: generate field type obey behavior optional --- .../example/freight/v1/freight_service.proto | 2 +- .../einride/example/freight/v1/index.ts | 4 +++- internal/plugin/commentgen.go | 15 +++++++++++++++ internal/plugin/messagegen.go | 2 +- 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/examples/proto/einride/example/freight/v1/freight_service.proto b/examples/proto/einride/example/freight/v1/freight_service.proto index 1917283..0d8414f 100644 --- a/examples/proto/einride/example/freight/v1/freight_service.proto +++ b/examples/proto/einride/example/freight/v1/freight_service.proto @@ -329,7 +329,7 @@ message ListShipmentsRequest { string page_token = 3; // query condition - map query = 4; + map query = 4 [(google.api.field_behavior) = OPTIONAL]; } message QueryMessage { diff --git a/examples/proto/gen/typescript/einride/example/freight/v1/index.ts b/examples/proto/gen/typescript/einride/example/freight/v1/index.ts index 3a93fb7..e14efd7 100644 --- a/examples/proto/gen/typescript/einride/example/freight/v1/index.ts +++ b/examples/proto/gen/typescript/einride/example/freight/v1/index.ts @@ -495,8 +495,10 @@ export type ListShipmentsRequest = { pageToken: string; /** * query condition + * + * Behaviors: OPTIONAL */ - query: { [key: string]: QueryMessage }; + query?: { [key: string]: QueryMessage }; }; export type QueryMessage = { diff --git a/internal/plugin/commentgen.go b/internal/plugin/commentgen.go index 4592157..17548bf 100644 --- a/internal/plugin/commentgen.go +++ b/internal/plugin/commentgen.go @@ -71,3 +71,18 @@ func getCommentPrefix(multiline bool) string { return "//" } + +func isFieldBehaviorOptional(field protoreflect.FieldDescriptor) bool { + behaviors := getFieldBehaviors(field) + if len(behaviors) == 0 { + return false + } + + for _, b := range behaviors { + if b.String() == "OPTIONAL" { + return true + } + } + + return false +} diff --git a/internal/plugin/messagegen.go b/internal/plugin/messagegen.go index 4f6e9b1..2cf04f1 100644 --- a/internal/plugin/messagegen.go +++ b/internal/plugin/messagegen.go @@ -41,7 +41,7 @@ func (m messageGenerator) Generate(f *codegen.File) { typesString := strings.Join(types, " | ") - if field.ContainingOneof() == nil && !field.HasOptionalKeyword() { + if field.ContainingOneof() == nil && !field.HasOptionalKeyword() && !isFieldBehaviorOptional(field) { f.P(t(1), name, ": ", typesString, ";") } else { f.P(t(1), name, "?: ", typesString, ";")