PiperOrigin-RevId: 564482175pull/14039/head
parent
437ec356fb
commit
e46855432e
2 changed files with 627 additions and 0 deletions
@ -0,0 +1,626 @@ |
||||
# Edition Zero Features |
||||
|
||||
**Authors:** [@mcy](https://github.com/mcy), |
||||
[@zhangskz](https://github.com/zhangskz), |
||||
[@mkruskal-google](https://github.com/mkruskal-google) |
||||
|
||||
**Approved:** 2022-07-22 |
||||
|
||||
Feature flags, and their defaults, that we will introduce to define the |
||||
converged semantics of Edition Zero. |
||||
|
||||
**NOTE:** This document is largely replaced by the topic, |
||||
[Feature Settings for Editions](https://protobuf.dev/editions/features) (to be |
||||
released soon). |
||||
|
||||
## Overview |
||||
|
||||
*Edition Zero Features* defines the "first edition" of the brave new world of |
||||
no-`syntax` Protobuf. This document defines the actual mechanics of the features |
||||
(in the narrow sense of editions) we need to implement in protoc, as well as the |
||||
chosen defaults. |
||||
|
||||
This document will require careful review from various stakeholders, because it |
||||
is essentially defining a new Protobuf `syntax`, even if it isn't spelled that |
||||
way. In particular, we need to ensure that there is a way to rewrite existing |
||||
`proto2` and `proto3` files as `editions` files, and the behavior of "mixed |
||||
syntax" messages, without any nasty surprises. |
||||
|
||||
Note that it is an explicit goal that it be possible to take an arbitrary |
||||
proto2/proto3 file and convert it to editions without semantic changes, via |
||||
appropriate application of features. |
||||
|
||||
## Existing Non-Conformance |
||||
|
||||
We must keep in mind that the status quo is messy. Many languages have some |
||||
areas where they currently diverge from the correct proto2/proto3 semantics. For |
||||
edition zero, we must preserve these idiosyncratic behaviors, because that is |
||||
the only way for a proto2/proto3 -> editions LSC to be a no-op. |
||||
|
||||
For example, in this document we define a feature `features.enum = |
||||
{CLOSED,OPEN}`. But currently Go does not implement closed enum semantics for |
||||
`syntax=proto2` as it should. This behavior is out of conformance, but we must |
||||
preserve this out-of-conformance behavior for edition zero. |
||||
|
||||
In other words, defining features and their semantics is in scope for edition |
||||
zero, but fixing code generators to perfectly match those semantics is |
||||
explicitly out-of-scope. |
||||
|
||||
## Glossary |
||||
|
||||
Because we need to speak of two proto syntaxes, `proto2` and `proto3`, that have |
||||
disagreeing terminology in some places, we'll define the following terms to aid |
||||
discussion. When a term appears in `code font`, it refers to the Protobuf |
||||
language keyword. |
||||
|
||||
* A **presence discipline** is a handling for the presence (or hasbit) of a |
||||
field. Every field notionally has a hasbit: whether it has been explicitly |
||||
set via the API or whether a record for it was present on deserialization. |
||||
See |
||||
[Application Note: Field Presence](https://protobuf.dev/programming-guides/field_presence) |
||||
for more on this topic. The discipline specifies how this bit is surfaced to |
||||
the user: |
||||
* **No presence** means that the API does not expose the hasbit. The |
||||
default value for the field behaves somewhat like a special sentinel |
||||
value, which is not serialized and not merged-from. The hasbit may still |
||||
exist in the implementation (C++ accidentally leaks this via HasField, |
||||
for example). Note that repeated fields sort-of behave like no presence |
||||
fields. |
||||
* **Explicit presence** means that the API exposes the hasbit through a |
||||
`has` method and a `Clear` method; default values are always serialized |
||||
if the hasbit is set. |
||||
* A **closed enum** is an enum where parsing requires validating that a parsed |
||||
`int32` representing a field of this type matches one of the known set of |
||||
valid values. |
||||
* An **open enum** does not have this restriction, and is just an `int32` |
||||
field with well-known values. |
||||
|
||||
For the purposes of this document, we will use the syntax described in *Features |
||||
as Custom Options*, since it is the prevailing consensus among those working on |
||||
editions, and allows us to have enum-typed features. The exact names for the |
||||
features are a matter of bikeshedding. |
||||
|
||||
## Proposed Converged Semantics |
||||
|
||||
There are two kinds of syntax behaviors we need to capture: those that are |
||||
turned on by a keyword, like `required`, and those that are implicit, like open |
||||
enums. The differences between proto2 and proto3 today are: |
||||
|
||||
* Required. Proto2 has `required` but not `defaulted`; Proto3 has `defaulted` |
||||
but not `required`. Proto3 also does not allow custom defaults on |
||||
`defaulted` fields, and on message-typed fields, `defaulted` is a synonym |
||||
for `optional`. |
||||
* Groups. Proto2 has groups, proto3 does not. |
||||
* Enums. In Proto2, enums are **closed**: messages that have an enum not in |
||||
the known set are stored in the unknown field set. In Proto3, enums are |
||||
**open**. |
||||
* String validation. Proto2 is a bit wobbly on whether strings must be UTF-8 |
||||
when serialized; Proto3 enforces this (sometimes). |
||||
* Extensions. Proto2 has extensions, while Proto3 does not (`Any` is the |
||||
canonical workaround). |
||||
|
||||
We propose defining the following features as part of edition zero: |
||||
|
||||
### features.field_presence |
||||
|
||||
This feature is enum-typed and controls the presence discipline of a singular |
||||
field: |
||||
|
||||
* `EXPLICIT` (default) - the field has *explicit presence* discipline. Any |
||||
explicitly set value will be serialized onto the wire (even if it is the |
||||
same as the default value). |
||||
* `IMPLICIT` - the field has *no presence* discipline. The default value is |
||||
not serialized onto the wire (even if it is explicitly set). |
||||
* `LEGACY_REQUIRED` - the field is wire-required and API-optional. Setting |
||||
this will require being in the `required` allowlist. Any explicitly set |
||||
value will be serialized onto the wire (even if it is the same as the |
||||
default value). |
||||
|
||||
The syntax for singular fields is a much debated question. After discussing the |
||||
tradeoffs, we have chosen to *eliminate both the `optional` and `required` |
||||
keywords, making them parse errors*. Singular fields are spelled as in proto3 |
||||
(no label), and will take on the presence discipline given by |
||||
`features.:presence`. Migration will require deleting every instance of |
||||
`optional` in proto files in google3, of which there are 385,236. |
||||
|
||||
It is important to observe that proto2 users are much likelier to care about |
||||
presence than proto3 users, since the design of proto3 discourages thinking |
||||
about presence as an interesting feature of protos, so arguably introducing |
||||
proto2-style presence will not register on most users' mental radars. This is |
||||
difficult to prove concretely. |
||||
|
||||
`IMPLICIT` fields behave much like proto3 implicit fields: they cannot have |
||||
custom defaults and are ignored on submessage fields. Also, if it is an |
||||
enum-typed field, that enum must be open (i.e., it is either defined in a |
||||
`syntax = proto3;` file or it specifies `option features.enum = OPEN;` |
||||
transitively). |
||||
|
||||
We also make some semantic changes: |
||||
|
||||
* ~~`IMPLICIT``fields may have a custom default value, unlike in`proto3`. |
||||
Whether an`IMPLICIT` field containing its default value is serialized |
||||
becomes an implementation choice (implementations are encouraged to try to |
||||
avoid serializing too much, though).~~ |
||||
* `has_optional_keyword()` and `has_presence()` now check for `EXPLICIT`, and |
||||
are effectively synonyms. |
||||
* `proto3_optional` is rejected as a parse error (use the feature instead). |
||||
|
||||
Migrating from proto2/3 involves deleting all `optional`/`required` labels and |
||||
adding `IMPLICT` and `LEGACY_REQUIURED` annotations where necessary. |
||||
|
||||
#### Alternatives |
||||
|
||||
* For syntax: |
||||
* Require `optional`. This may confuse proto3 users who are used to |
||||
`optional` not being a default they reach for. Will result in |
||||
significant (trivial, but noisy) churn in proto3 files. The keyword is |
||||
effectively line noise, since it does not indicate anything other than |
||||
"this is a singular field". |
||||
* Invent a new label, like `singular`. This results in more churn but |
||||
avoids breaking peoples' priors. |
||||
* Allow `optional` and no label to coexist in a file, which take on their |
||||
original meanings unless overridden by `features.field_presence`. The |
||||
fact that a top-level `features.field_presence = IMPLICIT` breaks the |
||||
proto3 expectation that `optional` means `EXPLICIT` may be a source of |
||||
confusion. |
||||
* `proto:allow_required`, which must be present for `required` to not be a |
||||
syntax error. |
||||
* Allow `required`/`optional` and introduce `defaulted` as a real keyword. We |
||||
will not have another easy chance to introduce such syntax (which we do, |
||||
because `edition = ...` is a breaking change). |
||||
* Reject custom defaults for `IMPLICIT` fields. This is technically not really |
||||
needed for converged semantics, but trying to remove the Proto3-ness from |
||||
`IMPLICIT` fields seems useful for consistency. |
||||
|
||||
#### Future Work |
||||
|
||||
In the future, we can introduce something like `features.always_serialize` or a |
||||
similar new enumerator (`ALWAYS_SERIALIZE`) to the `when_missing` enum, which |
||||
makes `EXPLICIT_PRESENCE` fields unconditionally serialized, allowing |
||||
`LEGACY_REQUIRED` fields to become `EXPLICIT_PRESENCE` in a future large-scale |
||||
change. The details of such a migration are out-of-scope for this document. |
||||
|
||||
#### Migration Examples |
||||
|
||||
Given the following files: |
||||
|
||||
``` |
||||
// foo.proto |
||||
syntax = "proto2" |
||||
|
||||
message Foo { |
||||
required int32 x = 1; |
||||
optional int32 y = 2; |
||||
repeated int32 z = 3; |
||||
} |
||||
|
||||
// bar.proto |
||||
syntax = "proto3" |
||||
|
||||
message Bar { |
||||
int32 x = 1; |
||||
optional int32 y = 2; |
||||
repeated int32 z = 3; |
||||
} |
||||
``` |
||||
|
||||
post-editions, they will look like this: |
||||
|
||||
``` |
||||
// foo.proto |
||||
edition = "tbd" |
||||
|
||||
message Foo { |
||||
int32 x = 1 [features.presence = LEGACY_REQUIRED]; |
||||
int32 y = 2; |
||||
repeated int32 z = 3; |
||||
} |
||||
|
||||
// bar.proto |
||||
edition = "tbd" |
||||
option features.presence = NO_PRESENCE; |
||||
|
||||
message Bar { |
||||
int32 x = 1; |
||||
int32 y = 2 [features.presence = EXPLICIT_PRESENCE]; |
||||
repeated int32 z = 3; |
||||
} |
||||
``` |
||||
|
||||
### features.enum_type |
||||
|
||||
Enum types come in two distinct flavors: *closed* and *open*. |
||||
|
||||
* *closed* enums will store enum values that are out of range in the unknown |
||||
field set. |
||||
* *open* enums will parse out of range values into their fields directly. |
||||
|
||||
**NOTE:** Closed enums can cause confusion for parallel arrays (two repeated |
||||
fields that expect to have index i refer to the same logical concept in both |
||||
fields) because an unknown enum value from a parallel array will be placed |
||||
in the unknown field set and the arrays will cease being parallel. Similarly |
||||
parsing and serializing can change the order of a repeated closed enum by |
||||
moving unknown values to the end. |
||||
|
||||
**NOTE:** Some runtimes (C++ and Java, in particular) currently do not use |
||||
the declaration site of enums to determine whether an enum field is treated |
||||
as open; rather, they use the syntax of the message the field is defined in, |
||||
instead. To preserve this proto2 quirk until we can migrate users off of it, |
||||
Java and C++ (and runtimes with the same quirk) will use the value of |
||||
`features.enum` as set at the file level of messages (so, if a file sets |
||||
`features.enum = CLOSED` at the file level, enum fields defined in it behave |
||||
as if the enum was closed, regardless of declaration). IMPLICIT singular |
||||
fields in Java and C++ ignore this and are always treated as open, because |
||||
they used to only be possible to define in proto3 files, which can't use |
||||
proto2 enums. |
||||
|
||||
In proto2, `enum` values are closed and no requirements are placed upon the |
||||
first `enum` value. The first enum value will be used as the default value. |
||||
|
||||
In proto3, `enum` values are open and the first `enum` value must be zero. The |
||||
first `enum` value is used as the default value, but that value is required to |
||||
be zero. |
||||
|
||||
In edition zero, We will add a feature `features.enum_type = {CLOSED,OPEN}`. The |
||||
default will be `OPEN`. Upgraded proto2 files will explicitly set |
||||
`features.enum_type = CLOSED`. The requirement of having the first enum value be |
||||
zero will be dropped. |
||||
|
||||
**NOTE:** Nominally this exposes a new state in the configuration space, OPEN |
||||
enums with a non-zero default. We decided that excluding this option simply |
||||
because it was previously inexpressible was a false economy. |
||||
|
||||
#### Alternatives |
||||
|
||||
* We could add a property for requiring a zero first value for an enum. This |
||||
feels needlessly complicated. |
||||
* We could drop the ability to have `CLOSED` enums, but that is a semantic |
||||
change. |
||||
|
||||
#### Migration Examples |
||||
|
||||
Given the following files: |
||||
|
||||
``` |
||||
// foo.proto |
||||
syntax = "proto2" |
||||
|
||||
enum Foo { |
||||
A = 2, B = 4, C = 6, |
||||
} |
||||
|
||||
// bar.proto |
||||
syntax = "proto3" |
||||
|
||||
enum Bar { |
||||
A = 0, B = 1, C = 5, |
||||
} |
||||
``` |
||||
|
||||
post-editions, they will look like this: |
||||
|
||||
``` |
||||
// foo.proto |
||||
edition = "tbd" |
||||
option features.enum_type = CLOSED; |
||||
|
||||
enum Foo { |
||||
A = 2, B = 4, C = 6, |
||||
} |
||||
|
||||
// bar.proto |
||||
edition = "tbd" |
||||
|
||||
enum Bar { |
||||
A = 0, B = 1, C = 5, |
||||
} |
||||
``` |
||||
|
||||
If we wanted to merge them into one file, it would look like this: |
||||
|
||||
``` |
||||
// foo.proto |
||||
edition = "tbd" |
||||
|
||||
enum Foo { |
||||
option features.enum_type = CLOSED; |
||||
A = 2, B = 4, C = 6, |
||||
} |
||||
|
||||
|
||||
enum Bar { |
||||
A = 0, B = 1, C = 5, |
||||
} |
||||
``` |
||||
|
||||
### features.repeated_field_encoding |
||||
|
||||
In proto3, the `repeated_field_encoding` attribute defaults to `PACKED`. In |
||||
proto2, the `repeated_field_encoding` attribute defaults to `EXPANDED`. Users |
||||
explicitly enabled packed fields 12.3k times, but only explicitly disable it 200 |
||||
times. Thus we can see a clear preference for `repeated_field_encoding = PACKED` |
||||
emerge. This data matches best practices. As such, the default value for |
||||
`features.repeated_field_encoding` will be `PACKED`. |
||||
|
||||
The existing `[packed = …]` syntax will be made an alias for setting the feature |
||||
in edition zero. This alias will eventually be removed. Whether that removal |
||||
happens during the initial large-scale change to enable edition zero or as a |
||||
follow on will be decided at the time. |
||||
|
||||
In the long term, we would like to remove explicit usages of |
||||
`features.repeated_field_encoding = EXPANDED`, but we would prefer to separate |
||||
that large-scale change from the landing of edition zero. So we will explicitly |
||||
set `features.repeated_field_encoding` to `EXPANDED` at the file level when we |
||||
migrate proto2 files to edition zero. |
||||
|
||||
#### Alternatives |
||||
|
||||
* Force everyone to use packed fields. This is a semantic change, which we're |
||||
trying to avoid in edition zero. |
||||
* Don’t add `features.repeated_field_encoding` and instead specify `[packed = |
||||
false]` when converting from proto2. This will be incredibly noisy, |
||||
syntax-wise and diff-wise. |
||||
|
||||
#### Migration Examples |
||||
|
||||
Given the following files: |
||||
|
||||
``` |
||||
// foo.proto |
||||
syntax = "proto2" |
||||
|
||||
message Foo { |
||||
repeated int32 x = 1; |
||||
repeated int32 y = 2 [packed = true]; |
||||
repeated int32 z = 3; |
||||
} |
||||
|
||||
// bar.proto |
||||
syntax = "proto3" |
||||
|
||||
message Foo { |
||||
repeated int32 x = 1; |
||||
repeated int32 y = 2 [packed = false]; |
||||
repeated int32 z = 3; |
||||
} |
||||
``` |
||||
|
||||
post-editions, they will look like this: |
||||
|
||||
``` |
||||
// foo.proto |
||||
edition = "tbd" |
||||
options features.repeated_field_encoding = EXPANDED; |
||||
|
||||
message Foo { |
||||
repeated int32 x = 1; |
||||
repeated int32 y = 2 [packed = true]; |
||||
repeated int32 z = 3; |
||||
} |
||||
|
||||
|
||||
// bar.proto |
||||
edition = "tbd" |
||||
|
||||
message Foo { |
||||
repeated int32 x = 1; |
||||
repeated int32 y = 2 [packed = false]; |
||||
repeated int32 z = 3; |
||||
} |
||||
``` |
||||
|
||||
Note that post migration, we have not changed `packed` to |
||||
`features.repeated_field_encoding = PACKED`, although we could choose to do so |
||||
if the diff cost is not monumental. We prefer to defer to an LSC after editions |
||||
are shipped, if possible. |
||||
|
||||
### features.string_field_validation |
||||
|
||||
**WARNING:** UTF8 validation is actually messier than originally believed. This |
||||
feature is being reconsidered in _Editions Zero Feature: utf8_validation_. |
||||
|
||||
This feature is a tristate: |
||||
|
||||
* `MANDATORY` - this means that a runtime MUST verify UTF-8. |
||||
* `HINT` - this means that a runtime may refuse to parse invalid UTF-8, but it |
||||
can also simply skip the check for performance in some build modes. |
||||
* `NONE` - this field behaves like a `bytes` field on the wire, but parsers |
||||
may mangle the string in an unspecified way (for example, Java may insert |
||||
spaces as replacement characters). |
||||
|
||||
The default will be `MANDATORY`. |
||||
|
||||
Long term, we would like to remove this feature and make all `string` fields |
||||
`MANDATORY`. |
||||
|
||||
#### Alternatives |
||||
|
||||
* Drop the UTF-8 requirements completely. This seems like it will create more |
||||
problems than it will solve (e.g., random things relying on validation need |
||||
to be fixed) and it will be a lot of work. This is also counter to the |
||||
vision of string being a UTF-8 type, and bytes being its unchecked sibling. |
||||
* Make opt-in verification a hard requirement instead of a hint, so that users |
||||
have a nice performance needle they can play with. |
||||
|
||||
#### Future Work |
||||
|
||||
In the infinite future, we would like to remove this feature and force all |
||||
`string` fields to be UTF-8 validated. To do this, we need to recognize that |
||||
what many callers want from their `string` fields is a `bytes` field with a |
||||
`string`-like API. To ease the transition, we would add per-codegen backend |
||||
features, like `java.bytes_as_string`, that give a `bytes` field a generated API |
||||
resembling that of a `string` field (with caveats about replacement characters |
||||
forced by the host language's string type). |
||||
|
||||
The migration would take `HINT` or `SKIP` `string` fields and convert them into |
||||
`bytes` fields with the appropriate API modifiers, depending on which languages |
||||
use that proto; C++-only protos, for example, are a no-op. |
||||
|
||||
There is an argument to be made for "I want a string type, and I explicitly want |
||||
replacement U+FFFD characters if I get something that isn't UTF-8." It is |
||||
unclear if this is something users want and we would need to investigate it |
||||
before making a decision. |
||||
|
||||
### features.json_format |
||||
|
||||
This feature is dual state in edition zero: |
||||
|
||||
* `ALLOW` - this means that a runtime must allow JSON parsing and |
||||
serialization. Checks will be applied at the proto level to make sure that |
||||
there is a well-defined mapping to JSON. |
||||
* `LEGACY_BEST_EFFORT` - this means that a runtime will do the best it can to |
||||
parse and serialize JSON. Certain protos will be allowed that can result in |
||||
undefined behavior at runtime (e.g. many:1 or 1:many mappings). |
||||
|
||||
The default will be `ALLOW`, which maps the to the current proto3 behavior. |
||||
`LEGACY_BEST_EFFORT` will be used for proto2 files that require it (e.g. they’ve |
||||
set `deprecated_legacy_json_field_conflicts`) |
||||
|
||||
#### Alternatives |
||||
|
||||
* Keep the proto2 behavior - this will regress proto3 files by removing |
||||
validation for JSON mappings, and lead to *more* undefined runtime behavior |
||||
* Only use `ALLOW` - there are ~30 cases internally where protos have invalid |
||||
JSON mappings and rely on unspecified (but luckily well defined) runtime |
||||
behavior. |
||||
|
||||
#### Future Work |
||||
|
||||
Long term, we would like to either remove this feature entirely or add a |
||||
`DISALLOW` option instead of `LEGACY_BEST_EFFORT`. This will more strictly |
||||
enforce that protos without a valid JSON mapping *can’t* be serialized or parsed |
||||
to JSON. `DISALLOW` will be enforced at the proto-language level, where no |
||||
message marked `ALLOW` can contain any message/enum marked `DISALLOW` (e.g. |
||||
through extensions or fields) |
||||
|
||||
#### Migration Examples |
||||
|
||||
### Extensions are Always Allowed |
||||
|
||||
Extensions may be used on all messages. This lifts a restriction from proto3. |
||||
|
||||
Extensions do not play nicely with `TypeResolver`. This is actually fixable, but |
||||
probably only worth it if someone complains. |
||||
|
||||
#### Alternatives |
||||
|
||||
* Add `features.allow_extensions`, default true. This feels unnecessary since |
||||
uttering `extend` and `extensions` is required to use extensions in the |
||||
first place. |
||||
|
||||
### features.message_encoding |
||||
|
||||
This feature defaults to `LENGTH_PREFIXED`. The `group` syntax does not exist |
||||
under editions. Instead, message-typed fields that have |
||||
`features.message_encoding = DELIMITED` set will be encoded as groups (wire type |
||||
3/4) rather than byte blobs (wire type 2). This reflects the existing API |
||||
(groups are funny message fields) and simplifies the parser. |
||||
|
||||
A `proto2` group field will be converted into a nested message type of the same |
||||
name, and a singular submessage field that is `features.message_encoding = |
||||
DELIMITED` with the message type's name in snake_case. |
||||
|
||||
This could be used in the future to switch new message fields to use group |
||||
encoding, which suggested previously as an efficiency direction. |
||||
|
||||
#### Alternatives |
||||
|
||||
* Allow groups in `editions` with no changes. `group` syntax is deprecated, so |
||||
we may as well take the opportunity to knock it out. |
||||
* Add a sidecar allowlist like we do for `required`. This is mostly |
||||
orthogonal. |
||||
|
||||
#### Migration Examples |
||||
|
||||
Given the following file |
||||
|
||||
``` |
||||
// foo.proto |
||||
syntax = "proto2" |
||||
|
||||
message Foo { |
||||
group Bar = 1 { |
||||
optional int32 x = 1; |
||||
repeated int32 y = 2; |
||||
} |
||||
} |
||||
``` |
||||
|
||||
post-editions, it will look like this: |
||||
|
||||
``` |
||||
// foo.proto |
||||
edition = "tbd" |
||||
|
||||
message Foo { |
||||
message Bar { |
||||
optional int32 x = 1; |
||||
repeated int32 y = 2; |
||||
} |
||||
Bar bar = 1 [features.message_encoding = DELIMITED]; |
||||
} |
||||
``` |
||||
|
||||
## Proposed Features Message |
||||
|
||||
Putting together all of the above, we propose the following `Features` message, |
||||
including retention and target rules associated with fields. |
||||
|
||||
``` |
||||
message Features { |
||||
enum FieldPresence { |
||||
EXPLICIT = 0; |
||||
IMPLICIT = 1; |
||||
LEGACY_REQUIRED = 2; |
||||
} |
||||
optional FieldPresence field_presence = 1 [ |
||||
retention = RUNTIME, |
||||
target = FILE, |
||||
target = FIELD |
||||
]; |
||||
|
||||
enum EnumType { |
||||
OPEN = 0; |
||||
CLOSED = 1; |
||||
} |
||||
optional EnumType enum = 2 [ |
||||
retention = RUNTIME, |
||||
target = FILE, |
||||
target = ENUM |
||||
]; |
||||
|
||||
enum RepeatedFieldEncoding { |
||||
PACKED = 0; |
||||
UNPACKED = 1; |
||||
} |
||||
optional RepeatedFieldEncoding repeated_field_encoding = 3 [ |
||||
retention = RUNTIME, |
||||
target = FILE, |
||||
target = FIELD |
||||
]; |
||||
|
||||
enum StringFieldValidation { |
||||
MANDATORY = 0; |
||||
HINT = 1; |
||||
NONE = 2; |
||||
} |
||||
optional StringFieldValidation string_field_validation = 4 [ |
||||
retention = RUNTIME, |
||||
target = FILE, |
||||
target = FIELD |
||||
]; |
||||
|
||||
enum MessageEncoding { |
||||
LENGTH_PREFIXED = 0; |
||||
DELIMITED = 1; |
||||
} |
||||
optional MessageEncoding message_encoding = 5 [ |
||||
retention = RUNTIME, |
||||
target = FILE, |
||||
target = FIELD |
||||
]; |
||||
|
||||
extensions 1000; // for features_cpp.proto |
||||
extensions 1001; // for features_java.proto |
||||
} |
||||
``` |
Loading…
Reference in new issue