-
Notifications
You must be signed in to change notification settings - Fork 68
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
proto2 required
support
#414
Comments
We agree that this would be nice for type-safety, but it means we have to make properties mandatory in the constructor, something we would prefer to avoid. We haven't found the best way forward yet for this conundrum, so for the time being, proto2 required properties are typed as optional, but will raise an error at runtime when serializing, following the pattern of other implementations. Thank you for raising this issue though. It is very possible that we'll be making some changes for better support in of proto2 required in the not-so-distant future, so we're happy to keep this open. |
What are the cons of mandatory properties in the constructor or using a builder pattern? On the other hand, there are clear benefits of not having to check fields for "undefined" that are supposed to be required in the schema. |
The behavior has changed since this issue was filed. The generated classes for proto2 messages seem to reflect the required/optional fields. See below. That being said, some imported messages do not seem to generate correctly (e.g. Timestamp is required in the message but optional in the class). Proto Message (sample) syntax = "proto2";
package event_logs.v1;
import "google/protobuf/timestamp.proto";
enum FlowStateChange {
FLOW_STATE_CHANGE_UNSPECIFIED = 0;
FLOW_STATE_CHANGE_STARTED = 1;
FLOW_STATE_CHANGE_STOPPED = 2;
FLOW_STATE_CHANGE_FINISHED = 3;
FLOW_STATE_CHANGE_RESET = 4;
}
message FlowStateChangeEvent {
required uint32 workspace = 1;
required uint32 environment = 2;
optional uint32 group = 3;
required uint32 user = 4;
required uint32 flow = 5;
required uint32 version = 6;
required FlowStateChange state = 7;
required bool value = 8;
required google.protobuf.Timestamp timestamp = 9;
}
Generated Class /**
* @generated from message event_logs.v1.FlowStateChangeEvent
*/
export declare class FlowStateChangeEvent extends Message<FlowStateChangeEvent> {
/**
* @generated from field: required uint32 workspace = 1;
*/
workspace: number;
/**
* @generated from field: required uint32 environment = 2;
*/
environment: number;
/**
* @generated from field: optional uint32 group = 3;
*/
group?: number;
/**
* @generated from field: required uint32 user = 4;
*/
user: number;
/**
* @generated from field: required uint32 flow = 5;
*/
flow: number;
/**
* @generated from field: required uint32 version = 6;
*/
version: number;
/**
* @generated from field: required event_logs.v1.FlowStateChange state = 7;
*/
state: FlowStateChange;
/**
* @generated from field: required bool value = 8;
*/
value: boolean;
/**
* @generated from field: required google.protobuf.Timestamp timestamp = 9;
*/
timestamp?: Timestamp;
constructor(data?: PartialMessage<FlowStateChangeEvent>);
static readonly runtime: typeof proto2;
static readonly typeName = "event_logs.v1.FlowStateChangeEvent";
static readonly fields: FieldList;
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): FlowStateChangeEvent;
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): FlowStateChangeEvent;
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): FlowStateChangeEvent;
static equals(a: FlowStateChangeEvent | PlainMessage<FlowStateChangeEvent> | undefined, b: FlowStateChangeEvent | PlainMessage<FlowStateChangeEvent> | undefined): boolean;
} |
@joemckenney, this was actually a bug 😱, fixed in v1.7.2. To support editions, we're working on better support for field presence, and plan to update the logic for proto2 first. I'll keep this issue updated. |
@timostamm @joemckenney I cannot reproduce the generated class Here are the commands I use : SED := `which gsed 2>/dev/null || which sed`
TS_SRC_DIR := "src"
TS_DIST_DIR := "dist"
default:
just --list
build: _init_dist _build-es-pb _build-es-connect
tsc -b
clean:
rm -rf dist
_init_dist:
mkdir -p "{{TS_DIST_DIR}}"
_build-es-pb target="showhost.proto":
protoc -I . \
--es_out "{{TS_DIST_DIR}}" \
--es_opt target=js+dts \
--es_opt import_extension=none \
"{{target}}"
_build-es-connect target="showhost.proto":
protoc -I . \
--connect-es_out "{{TS_DIST_DIR}}" \
--connect-es_opt target=js+dts \
--connect-es_opt import_extension=none \
"{{target}}" Here is my {
"private": true,
"name": "@showhost/transport",
"main": "src/index.ts",
"types": "src/index.ts",
"version": "0.1.0",
"scripts": {
"build": "just build",
"clean": "just clean"
},
"files": [
"dist"
],
"exports": {
".": "./dist/index.js",
"./*": "./dist/*.js"
},
"devDependencies": {
"@bufbuild/protoc-gen-es": "^1.7.2",
"@connectrpc/protoc-gen-connect-es": "^1.3.0"
},
"dependencies": {
"@bufbuild/protobuf": "^1.7.2",
"@connectrpc/connect": "^1.3.0",
"@connectrpc/connect-web": "^1.3.0"
}
} And here is the (partial) output I get : /**
* @generated from message xyz.volgar1x.showhost.FlowStateChangeEvent
*/
export declare class FlowStateChangeEvent extends Message<FlowStateChangeEvent> {
/**
* @generated from field: required uint32 workspace = 1;
*/
workspace?: number;
/**
* @generated from field: required uint32 environment = 2;
*/
environment?: number;
/**
* @generated from field: optional uint32 group = 3;
*/
group?: number;
/**
* @generated from field: required uint32 user = 4;
*/
user?: number;
/**
* @generated from field: required uint32 flow = 5;
*/
flow?: number;
/**
* @generated from field: required uint32 version = 6;
*/
version?: number;
/**
* @generated from field: required xyz.volgar1x.showhost.FlowStateChange state = 7;
*/
state?: FlowStateChange;
/**
* @generated from field: required bool value = 8;
*/
value?: boolean;
/**
* @generated from field: required google.protobuf.Timestamp timestamp = 9;
*/
timestamp?: Timestamp;
constructor(data?: PartialMessage<FlowStateChangeEvent>);
static readonly runtime: typeof proto2;
static readonly typeName = "xyz.volgar1x.showhost.FlowStateChangeEvent";
static readonly fields: FieldList;
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): FlowStateChangeEvent;
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): FlowStateChangeEvent;
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): FlowStateChangeEvent;
static equals(a: FlowStateChangeEvent | PlainMessage<FlowStateChangeEvent> | undefined, b: FlowStateChangeEvent | PlainMessage<FlowStateChangeEvent> | undefined): boolean;
} Do you have the right command invocation notation? Thanks for your work anyway! |
Hello, We would like to generate types from protobuf definition that would look like this:
We have tried this:
and based on description of v2 alpha release #801 expected to get something like
but in generated type Is this in the works? |
Apologies for the confusion. I've updated the v2 release notes to clarify that it's an improvement over v1. Required scalar and enum fields are no longer optional in generated code, but this does not apply to message fields. This would need changes that impact the entire API, see #532 (comment). I've been working with JSON and TypeScript for a long time, and I understand the desire to have non-optional properties. Realistically though, the From my understanding, the important bit is avoiding the nullish-coalescing operator and checking for |
The Google's recomendation is relevant for cases where the developer doesn't fully control both sides the use the same proto message, for example, one side is a phone app that can't be forcibly updated. |
Certainly. But would you want to be stuck with a no longer maintained proto2 down the road? Do you think that switching to getters or immutable is the right call for general use? Protobuf-ES v2 is just descriptors and functions. For specific use cases like private IPC, you can easily wrap fromBinary and return a custom generated or even just a mapped type, based on a simple custom option. |
I imagine for the private IPC use-case people (who like protobufs) are evaluting different libraries for various criteria, including the amount of extra work and maintenance they have to do. From this perspective having to do a separate codegen themselves seems to be a big drawback, it would be simpler to pick another library. on the proto2 vs. proto3 topic: In my understanding various Typescript proto2 libraries are still well maintained, including protobuf-es. The proto2 spec has been battle-tested for years, not sure if it requires any maintenance. I personally think that proto2 schema is better than proto3, in particular around "required" and "has" semantics. For #532 (comment), I don't have enough data to have a strong opinion. Naively I'd say that enforcing immutability would be too much of a burden for the JS/TS users and they would switch to a different library. As a data point, in the official Java codegen, if I remember correctly, (for builders, which are essentially mutable messages) they create an mutable object and cache it inside. This mostly worked but created unpredictable memory and performance patterns, which were noticeable in some rare cases. |
would love to see better
Google opted to support proto2 "pretty much forever" due to their own dependency internally on proto2 specific features. |
And yet, Google also has plans to "deprecate/remove required field presence": protocolbuffers/protobuf@0b6e768 |
sure, in editions, right? |
I'm not sure. We plan to add It should be possible to use these types for construction and RPC in a flexible way, maybe also with a no-op validator if you trust the source. And I don't see why we couldn't also honor proto2 required and the LEGACY_REQUIRED feature. |
cool! that sounds like it would do the trick :) |
The required syntax of proto2 is now optional in the generated type, is there any way to make it required?
The text was updated successfully, but these errors were encountered: