Skip to content

Generate a protobuf schema artifact from ElasticGraph schemas - #1080

Merged
jwils merged 4 commits into
mainfrom
joshuaw/protobuf-schema-generation
Jul 20, 2026
Merged

Generate a protobuf schema artifact from ElasticGraph schemas#1080
jwils merged 4 commits 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

Update — 2026-07-10


Update — 2026-07-15

  • Interface and union messages now wrap concrete subtype messages in a oneof, matching the JSON Schema oneOf representation.
  • Concrete subtype messages omit the redundant __typename discriminator.
  • Proto type rendering is now stateless: the generator selects the reachable type graph up front, then each extended type renders itself without mutating shared traversal state.
  • No-block extension coverage now completes the definitions so the fixture remains valid under CI GraphQL-schema validation.

Update — 2026-07-19

  • Replaced keyword suffixing with fully qualified local message and enum references, preserving source type and field names while disambiguating contextual protobuf words and built-in scalar names.
  • Removed the now-unnecessary keyword collision tracking and verified a generated schema containing contextual names with protoc 35.1.

@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
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
Comment thread elasticgraph-proto_ingestion/spec/spec_helper.rb
Comment thread elasticgraph-proto_ingestion/README.md
Comment thread elasticgraph-proto_ingestion/README.md
Comment thread elasticgraph-proto_ingestion/README.md Outdated

@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.

Still not done with my review but want to submit what I have so far.

One thing I haven't seen is something which verifies that the proto schema we produce is a valid proto schema--e.g. a test which parses a proto.schema file we generate using the google-protobuf gem, demonstrating that we generate valid protos. (For now, it just looks like we do but I can't say for sure that it's valid!).

One idea--maybe in a followup PR you can wire this up with the test schema (similar to the warehouse artifact) and then you can add a test to the test suite that verifies that the generated proto schema from our main test schema is valid? I'm open to other ideas here...

Comment thread elasticgraph-proto_ingestion/README.md Outdated
Comment thread elasticgraph-proto_ingestion/README.md Outdated
Comment thread elasticgraph-proto_ingestion/README.md Outdated
Comment thread elasticgraph-proto_ingestion/README.md Outdated
Comment thread elasticgraph-proto_ingestion/spec/support/proto_schema_support.rb
Fill in elasticgraph-proto_ingestion with proto schema generation for indexed types. Map built-in and custom scalars, render concrete and abstract types, preserve public field names, and generate enum defaults. Reject unrepresentable lists and unstable identifier collisions with actionable schema errors.

@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.

Finally made it through the entire PR! I'm hoping this is the last round of feedback.

@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.

One other thing I just noticed: the tests generate a few warnings which dirty up the output. Can we change the tests that generate these warnings (so they no longer generate them) without impacting what they cover?

$ be rspec elasticgraph-proto_ingestion/spec

Randomized with seed 37739
.........WARNING: Since a `Account.count` field exists, ElasticGraph is not able to
define its typical `AccountFieldsListFilterInput.count` field, which allows clients to filter on the count
of values for a `[Account]` field. Clients will still be able to filter on the `count`
at a leaf field path. However, there are a couple ways this naming conflict can be avoided if desired:

1. Pick a different name for the `Account.count` field.
2. Change the name used by ElasticGraph for this field. To do that, pass a
   `schema_element_name_overrides: {"count" => "alt_name"}` option alongside
   `schema_element_name_form: ...` when defining `ElasticGraph::SchemaDefinition::RakeTasks`
   (typically in the `Rakefile`).
...............Object types must have fields, but Query doesn't have any. Define a field for this type, remove it from your schema, or add `has_no_fields(true)` to its definition.

This will raise an error in a future GraphQL-Ruby version.
.......

@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.

Well done! Using the dot-prefixed FQN form to avoid naming collisions is a really nice approach. I'm really happy with how we landed on this.

Left some non-blocking feedback. Use your judgement about which things to address and feel free to address them in a followup PR. (Frankly, I'd prefer that as it would be a smaller diff to go through!).

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