Skip to content

Generate a protobuf schema artifact from ElasticGraph schemas#1080

Open
jwils wants to merge 1 commit into
mainfrom
joshuaw/protobuf-schema-generation
Open

Generate a protobuf schema artifact from ElasticGraph schemas#1080
jwils wants to merge 1 commit into
mainfrom
joshuaw/protobuf-schema-generation

Conversation

@jwils

@jwils jwils commented Mar 22, 2026

Copy link
Copy Markdown
Collaborator

Note

First of a four-PR stack. Supporting changes merged earlier (#1296 artifact comment prefixes, #1297 gem scaffold), so this diff is contained entirely to the elasticgraph-proto_ingestion directory. Per review feedback the original PR has been split: wire-stable field/enum-value numbering, proto2 + custom headers, and enum sourcing/external references are now stacked follow-ups.

Why

What

  • fill in the elasticgraph-proto_ingestion extension gem with core generation: schema_artifacts:dump emits a proto3 schema.proto covering the schema's indexed types
  • map built-in ElasticGraph scalars to proto types, with t.protobuf type: for custom scalars (resolved via type_ref.with_reverted_override so built-ins renamed with type_name_overrides keep working)
  • generate messages for object/interface/union types and enums (with a zero-valued *_UNSPECIFIED entry), escaping proto reserved words and wrapping lists of lists so the output stays valid
  • keep schema.proto on public GraphQL field names; validate proto package names
  • hold extension state on a ProtoIngestionState container behind a single proto_ingestion_state reader (matching Group JSON ingestion schema state #1281)

Field and enum value numbers are assigned sequentially in definition order in this PR; the stacked follow-up adds the proto_field_numbers.yaml sidecar that keeps them wire-stable across schema evolution.

Stacked follow-ups

  1. this PR — core schema.proto generation
  2. wire-stable field/enum value numbers via a proto_field_numbers.yaml sidecar
  3. syntax: :proto2 support and custom file-level headers:
  4. Source enum values from external proto enums and reference external enum types #1286 — enum value sourcing from existing proto enums + external proto type references

Verification

  • script/run_gem_specs elasticgraph-proto_ingestion (100% line + branch coverage at this commit)
  • script/type_check, script/lint, script/spellcheck
  • script/quick_build green at the stack head (whose tree is identical to the previously reviewed single-PR revision)

References

@jwils jwils force-pushed the joshuaw/protobuf-schema-generation branch from 2e0e4a1 to 53ea492 Compare March 22, 2026 22:54
@jwils jwils force-pushed the joshauw/pluggable-ingestion-serializers branch from a3cfa5c to 9e6f39a Compare March 22, 2026 22:57
@jwils jwils force-pushed the joshuaw/protobuf-schema-generation branch 3 times, most recently from 4325d51 to fd8dd47 Compare March 23, 2026 04:10
@jwils jwils force-pushed the joshauw/pluggable-ingestion-serializers branch from 9e6f39a to c408a50 Compare March 23, 2026 04:10
@jwils jwils force-pushed the joshuaw/protobuf-schema-generation branch from fd8dd47 to bdce7fb Compare March 26, 2026 14:32
@jwils jwils force-pushed the joshauw/pluggable-ingestion-serializers branch from c408a50 to 5717334 Compare March 26, 2026 14:32
@jwils jwils force-pushed the joshauw/pluggable-ingestion-serializers branch 12 times, most recently from 0607029 to 40815e1 Compare April 4, 2026 21:53
@jwils jwils force-pushed the joshuaw/protobuf-schema-generation branch 6 times, most recently from 6591c29 to 751db75 Compare April 5, 2026 15:31
@jwils jwils force-pushed the joshauw/pluggable-ingestion-serializers branch 4 times, most recently from b2e8d34 to 6969f84 Compare May 6, 2026 15:42
@jwils jwils force-pushed the joshuaw/protobuf-schema-generation branch from 070849e to ce625bc Compare June 16, 2026 23:27
@jwils jwils force-pushed the joshuaw/protobuf-schema-generation branch 2 times, most recently from e9902f1 to f069179 Compare June 29, 2026 19:47
@jwils jwils marked this pull request as ready for review June 29, 2026 20:20
@jwils jwils force-pushed the joshuaw/protobuf-schema-generation branch 2 times, most recently from 5efeb79 to 7d428d5 Compare July 1, 2026 20:55
Comment thread elasticgraph-protobuf/elasticgraph-protobuf.gemspec Outdated
@jwils jwils force-pushed the joshuaw/protobuf-schema-generation branch from 7d428d5 to 9acfed5 Compare July 4, 2026 01:38
@jwils jwils changed the title Add elasticgraph-protobuf schema artifacts Add elasticgraph-proto_ingestion schema artifacts Jul 4, 2026
@jwils jwils force-pushed the joshuaw/protobuf-schema-generation branch from 9acfed5 to 4fe8938 Compare July 4, 2026 12:59
@jwils jwils force-pushed the joshuaw/protobuf-schema-generation branch from 4fe8938 to 474788d Compare July 5, 2026 03:49
@jwils jwils changed the base branch from main to joshuaw/schema-evolution-declarations July 5, 2026 03:49
@jwils jwils force-pushed the joshuaw/protobuf-schema-generation branch from 474788d to bcc30c1 Compare July 5, 2026 15:39
jwils added a commit that referenced this pull request Jul 7, 2026
Moves `field.renamed_from`, `type.renamed_from`, `type.deleted_field`,
and `schema.deleted_type` — along with the state registries and
`DeprecatedElement` record they populate — from
`elasticgraph-json_ingestion` back into the core schema definition gem,
partially reverting the placement chosen in #1259.

These declarations record serializer-neutral facts about how a schema
has evolved, and we now have a second consumer:
`elasticgraph-proto_ingestion` (#1080) needs rename metadata to keep
protobuf field numbers stable across renames (with `reserved` field
numbers for deletions as a natural follow-up). Keeping the markers
inside one serializer forced other consumers to duck-type against state
that may or may not be present; with the declarations in core, every
consumer reads typed `State` members directly.

This also addresses the concern raised in review of #1259 about
`elasticgraph-json_ingestion` bolting 9 fields onto
`::ElasticGraph::SchemaDefinition::State`: its `StateExtension` now
adds only the 5 genuinely JSON-specific fields, and the four
deprecation registries are ordinary typed `State` members that need no
`State & StateExtension` casts.

All JSON-specific behavior — schema version enforcement, merge
reporting, pruning, and the warnings that flag no-longer-needed
declarations — stays in `elasticgraph-json_ingestion`.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jwils jwils force-pushed the joshuaw/schema-evolution-declarations branch from 98f82ce to 683be9a Compare July 7, 2026 14:45

@myronmarston myronmarston left a comment

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.

Not done reviewing but wanted to submit what I have so far.

Comment thread elasticgraph-proto_ingestion/README.md Outdated
Comment thread elasticgraph-proto_ingestion/README.md Outdated
Comment thread elasticgraph-proto_ingestion/README.md Outdated
```

When a mapping exists for an enum, `elasticgraph-proto_ingestion` uses the mapped proto enum(s)
as the source of enum values (respecting `exclusions`, `expected_extras`, and `name_transform`).

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.

EG today doesn't have any concept of proto enum mappings, and doesn't have any logic to support exclusions, expected_extras or name_transform. Instead of importing an existing data structure designed for tests within Block internal code, can we invert this?

  • Design and implement whatever features make sense for the elasticgraph-proto_ingestion gem as a stand-alone, open source library.
  • Update the internal testing support inside Block to leverage what elasticgraph-proto_ingestion provides.

For example, instead of it being provided as a global data structure, maybe we can do something like:

schema.enum_type "Currency" do |t|
  t.external_proto_enum "v1.money.Currency", # ... plus other options as needed like `exclusions`, etc.
end

With that said, I don't yet understand how this gem uses this mapping...I haven't gotten that far.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Makes sense. Going to move enums out of this pr so we can review this independently.

RSpec.configure do |config|
config.when_first_matching_example_defined(:proto_schema) do
require "support/proto_schema_support"
end

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.

You can also think about applying this tag automatically to specs under elasticgraph-proto_ingestion/spec, using this technique:

config.define_derived_metadata(absolute_file_path: %r{/elasticgraph-json_ingestion/}) do |meta|
meta[:json_ingestion_schema_definition] = true
end

| `Boolean` | `bool` |
| `Cursor` | `string` |
| `Date` | `string` |
| `DateTime` | `string` |

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.

|-------------------|------------|
| `Boolean` | `bool` |
| `Cursor` | `string` |
| `Date` | `string` |

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.

One potential concern is that string is a much wider type than Date. Something like 0.0001% of valid strings are valid ISO8601 Date strings.

Have you given thought to how invalid values will get handled? With JSON schema we could specify validations for this kind of thing.

Comment thread elasticgraph-proto_ingestion/README.md Outdated

Additionally:
- List types become `repeated` fields.
- Nested list types generate wrapper messages so the output remains valid `proto3`.

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.

I'm not following this.

schema.object_type "Team" do |t|
  t.field "players", "[Player!]!" do |f|
    f.mapping type: "nested"
  end

  t.field "seasons", "[Season!]!" do |f|
    f.mapping type: "object"
  end
end

Is this saying that a wrapper message type will be generated for the players field but not the seasons field? Why would that be? And what would the proto look like?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

nested as in the list of lists not the mapping type, so the issue exists for both player and seasons. you can't do repeated repeated for a field. Updating test.

Fills in the `elasticgraph-proto_ingestion` gem with the core Protocol
Buffers schema generation logic. Running `schema_artifacts:dump` emits a
`proto3` `schema.proto` artifact covering the schema's indexed types.

Capabilities:

- Maps built-in ElasticGraph scalars to proto types, with `protobuf` to
  configure custom scalars (resolved via `type_ref.with_reverted_override`
  so built-in scalars renamed with `type_name_overrides` keep working).
- Generates messages for object/interface/union types and enums (with a
  zero-valued `*_UNSPECIFIED` entry), escaping proto reserved words and
  wrapping lists of lists so the output stays valid.
- `schema.proto` uses the public GraphQL field names, and validates proto
  package names (each dot-separated segment must be a valid identifier).

Field and enum value numbers are assigned sequentially in definition order;
keeping them wire-stable across schema evolution comes in a stacked
follow-up that adds a `proto_field_numbers.yaml` sidecar artifact.

The extension's schema definition state lives on a ProtoIngestionState
container exposed via a single `proto_ingestion_state` reader, mirroring
the JSONIngestionState approach from #1281.
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.

2 participants