Skip to content

feat(protobuf): generate nested enum for string fields with allowed values#502

Open
aki1770-del wants to merge 5 commits intoCOVESA:masterfrom
aki1770-del:feat/protobuf-enum-for-allowed-string-493
Open

feat(protobuf): generate nested enum for string fields with allowed values#502
aki1770-del wants to merge 5 commits intoCOVESA:masterfrom
aki1770-del:feat/protobuf-enum-for-allowed-string-493

Conversation

@aki1770-del
Copy link
Copy Markdown

Closes #493

Problem

When a VSS signal has datatype: string and an allowed attribute, the protobuf exporter emits a plain string field. This silently accepts any value at the protobuf layer — the constraint declared in the VSS spec is invisible to protobuf consumers.

# Before — allowed values ['DRY', 'WET', 'SNOW', 'ICE'] are lost
string Surface = 2;

Fix

print_messages() now uses a two-pass approach in src/vss_tools/exporters/protobuf.py:

  • Pass 1: for each string + allowed field, emit a nested enum <FieldName>Enum with allowed values assigned indices starting at 0 (satisfying the proto3 zero-value requirement).
  • Pass 2: emit the field using the enum type instead of string.

repeated string[] fields with allowed become repeated <EnumType>. String fields without allowed and all non-string fields are unchanged.

# After
message A {
  enum SurfaceEnum {
    DRY = 0;
    WET = 1;
    SNOW = 2;
    ICE = 3;
  }
  repeated SurfaceEnum Surface = 2;
}

Note on proto3 zero-value semantics: proto3 uses the first enum entry as the default for unset fields. This implementation assigns index 0 to the first entry in the VSS allowed list. For signals that follow the convention of listing UNKNOWN first (e.g., Vehicle.Exterior.RoadSurfaceCondition), this aligns naturally. For signals without a natural "unset" first value, the first allowed entry becomes the proto default. Happy to discuss if a different index assignment strategy is preferred.

Changes

File Change
src/vss_tools/exporters/protobuf.py +33 lines — _enum_type_name(), _write_nested_enum(), two-pass logic in print_messages()
tests/vspec/test_protobuf_enum_allowed/ New test: scalar, repeated, and unchanged-float cases
tests/vspec/test_protobuf_comments/expected_*.proto Updated expected output to reflect enum generation for StringWithAllowed fixture

Tests

$ python3 -m pytest tests/ -k "proto" -v
13 passed in 5.36s

All 13 protobuf-related tests pass, including the two existing test_protobuf_comments parametrized cases (no-comments / with-comments) which already contained a string + allowed field in their fixture.

AI-assisted — authored with Claude, reviewed by Komada.

nodes: tuple[VSSNode], fd: TextIOWrapper, static_uid: bool, add_optional: bool, include_comments: bool
):
# Pass 1: write nested enum definitions for every string field with allowed values.
for node in nodes:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why do we need to iterate twice?

for node in nodes:
if isinstance(node.data, VSSDataDatatype):
base = node.data.datatype.strip("[]")
if base == "string" and node.data.allowed:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

"string" literal will probably never change. Still use the the one from datatypes.Datatypes

…alues

When a VSS signal has `datatype: string` and an `allowed` attribute,
the protobuf exporter previously emitted a plain `string` field,
allowing any value without enforcement. This silently accepted values
outside the declared allowed set at the protobuf layer.

Fix: `print_messages()` now uses a two-pass approach:
- Pass 1: emit a nested `enum <FieldName>Enum` for each string+allowed
  field, with allowed values assigned indices starting at 0 (satisfying
  the proto3 zero-value requirement).
- Pass 2: emit fields using the enum type instead of `string`.

`repeated string[]` fields with `allowed` become `repeated <EnumType>`.
Non-string fields and string fields without `allowed` are unchanged.

Closes COVESA#493

Co-Authored-By: Claude and aki1770-del <aki1770@gmail.com>
Signed-off-by: Akihiko Komada <aki1770@gmail.com>
Co-Authored-By: Claude and aki1770-del <aki1770@gmail.com>
Signed-off-by: Akihiko Komada <aki1770@gmail.com>
ruff-format reformatted the multi-line assert to put the condition
arguments on their own line instead of wrapping in parentheses.
Keeps pre-commit CI green.

Co-Authored-By: Claude and aki1770-del <aki1770@gmail.com>
Signed-off-by: Akihiko Komada <aki1770@gmail.com>
- Collapse two-pass loop into one: _write_nested_enum is called
  immediately before the field declaration, removing the redundant
  pre-iteration.
- Replace "string" literal with Datatypes.STRING[0] from datatypes module.
- Update expected.proto: enum definitions now appear inline before their
  respective fields rather than grouped at the top of the message.

Co-Authored-By: Claude and aki1770-del <aki1770@gmail.com>
Signed-off-by: Akihiko Komada <aki1770@gmail.com>
Move `from vss_tools.datatypes import Datatypes` before
`from vss_tools.main import get_trees` — alphabetical within
the vss_tools.* group as required by ruff/isort.

Co-Authored-By: Claude and aki1770-del <aki1770@gmail.com>
Signed-off-by: Akihiko Komada <aki1770@gmail.com>
@aki1770-del aki1770-del force-pushed the feat/protobuf-enum-for-allowed-string-493 branch from 3e69703 to c151e10 Compare April 11, 2026 10:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Generate protobuf enums for datatype string with allowed option

2 participants