Skip to content

Commit 346b9f6

Browse files
committed
Polish JSON ingestion extension APIs and docs
1 parent 39eec10 commit 346b9f6

37 files changed

Lines changed: 532 additions & 57 deletions

File tree

AGENTS.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,10 @@ All gems follow the pattern: `elasticgraph-[name]/` containing:
8585
- `elasticgraph-elasticsearch`: Elasticsearch client wrapper
8686
- `elasticgraph-opensearch`: OpenSearch client wrapper
8787

88-
**Extensions** (5 gems): Optional functionality
88+
**Extensions** (6 gems): Optional functionality plus the default JSON Schema ingestion serializer
8989
- `elasticgraph-apollo`: Apollo Federation support
9090
- `elasticgraph-health_check`: Health checks
91+
- `elasticgraph-json_ingestion`: JSON Schema ingestion serializer
9192
- `elasticgraph-query_interceptor`: Query interception
9293
- `elasticgraph-query_registry`: Source-controlled query registry
9394
- `elasticgraph-warehouse`: Data warehouse ingestion

elasticgraph-apollo/lib/elastic_graph/apollo/schema_definition/api_extension.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ module ElasticGraph
3535
# local_config_yaml: "config/settings/local.yaml",
3636
# path_to_schema: "config/schema.rb"
3737
# ) do |tasks|
38-
# tasks.schema_definition_extension_modules = [ElasticGraph::Apollo::SchemaDefinition::APIExtension]
38+
# tasks.schema_definition_extension_modules += [ElasticGraph::Apollo::SchemaDefinition::APIExtension]
3939
# end
4040
module Apollo
4141
# Namespace for all Apollo schema definition support.
@@ -55,7 +55,7 @@ module SchemaDefinition
5555
# local_config_yaml: "config/settings/local.yaml",
5656
# path_to_schema: "config/schema.rb"
5757
# ) do |tasks|
58-
# tasks.schema_definition_extension_modules = [ElasticGraph::Apollo::SchemaDefinition::APIExtension]
58+
# tasks.schema_definition_extension_modules += [ElasticGraph::Apollo::SchemaDefinition::APIExtension]
5959
# end
6060
module APIExtension
6161
# Applies an apollo tag to built-in types so that they are included in the Apollo contract schema.

elasticgraph-json_ingestion/README.md

Lines changed: 91 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,98 @@
11
# ElasticGraph::JSONIngestion
22

3-
JSON Schema ingestion support for ElasticGraph.
3+
Default JSON Schema ingestion support for ElasticGraph.
44

55
This gem provides the schema-definition extension that generates JSON Schema artifacts for indexing
6-
events and validates JSON-ingestion-specific schema options.
6+
events and validates JSON-ingestion-specific schema options. Generated ElasticGraph projects install
7+
and enable it by default. Applications that wire schema-definition tasks manually enable it by adding
8+
`ElasticGraph::JSONIngestion::SchemaDefinition::APIExtension` to their schema-definition extension modules.
9+
10+
## Schema Definition APIs
11+
12+
Use `schema.json_schema_version` to identify the current JSON schema artifact. Every change that affects
13+
the JSON schema should increment this version so publishers and indexers can safely evolve independently.
14+
15+
```diff
16+
diff --git a/config/schema.rb b/config/schema.rb
17+
index 015c5fa..b8eeaef 100644
18+
--- a/config/schema.rb
19+
+++ b/config/schema.rb
20+
@@ -1,5 +1,5 @@
21+
ElasticGraph.define_schema do |schema|
22+
# ElasticGraph will tell you when you need to bump this.
23+
- schema.json_schema_version 1
24+
+ schema.json_schema_version 2
25+
-
26+
+
27+
# Set this to true once you're beyond the prototyping stage.
28+
```
29+
30+
Use `schema.json_schema_strictness` to configure whether indexing events may omit nullable fields or include
31+
extra fields. We recommend enabling at most one of these options, because enabling both can hide misspelled
32+
event fields.
33+
34+
```ruby
35+
# in config/schema/json_schema_strictness.rb
36+
37+
ElasticGraph.define_schema do |schema|
38+
schema.json_schema_strictness allow_omitted_fields: true, allow_extra_fields: false
39+
end
40+
```
41+
42+
Custom scalar types must declare how they are represented in JSON Schema:
43+
44+
```ruby
45+
# in config/schema/url.rb
46+
47+
ElasticGraph.define_schema do |schema|
48+
schema.scalar_type "URL" do |t|
49+
t.mapping type: "keyword"
50+
t.json_schema type: "string", format: "uri"
51+
end
52+
end
53+
```
54+
55+
Fields and object/interface types can add JSON Schema validations. Use field-level validations sparingly:
56+
they run while indexing events, so violations can send otherwise valid source-system data to the dead letter
57+
queue. They are best reserved for constraints that ElasticGraph needs in order to index correctly.
58+
59+
```ruby
60+
# in config/schema/card.rb
61+
62+
ElasticGraph.define_schema do |schema|
63+
schema.object_type "Card" do |t|
64+
t.field "id", "ID!"
65+
66+
t.field "expYear", "Int" do |f|
67+
f.json_schema minimum: 2000, maximum: 2099
68+
end
69+
70+
t.field "expMonth", "Int" do |f|
71+
f.json_schema minimum: 1, maximum: 12
72+
end
73+
74+
t.index "cards"
75+
end
76+
end
77+
```
78+
79+
On fields, `nullable: false` disallows `null` in indexing events while keeping the GraphQL field nullable:
80+
81+
```ruby
82+
# in config/schema/widget.rb
83+
84+
ElasticGraph.define_schema do |schema|
85+
schema.object_type "Widget" do |t|
86+
t.field "id", "ID!"
87+
88+
t.field "name", "String" do |f|
89+
f.json_schema nullable: false
90+
end
91+
92+
t.index "widgets"
93+
end
94+
end
95+
```
796

897
## Dependency Diagram
998

elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/api_extension.rb

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,7 @@
99
require "elastic_graph/constants"
1010
require "elastic_graph/graphql/scalar_coercion_adapters/valid_time_zones"
1111
require "elastic_graph/json_ingestion/schema_definition/factory_extension"
12-
require "elastic_graph/json_ingestion/schema_definition/indexing/field_type/enum_extension"
13-
require "elastic_graph/json_ingestion/schema_definition/indexing/field_type/object_extension"
14-
require "elastic_graph/json_ingestion/schema_definition/indexing/field_type/scalar_extension"
15-
require "elastic_graph/json_ingestion/schema_definition/indexing/field_type/union_extension"
1612
require "elastic_graph/json_ingestion/schema_definition/state_extension"
17-
require "elastic_graph/schema_definition/indexing/field_type/enum"
18-
require "elastic_graph/schema_definition/indexing/field_type/object"
19-
require "elastic_graph/schema_definition/indexing/field_type/scalar"
20-
require "elastic_graph/schema_definition/indexing/field_type/union"
2113

2214
module ElasticGraph
2315
module JSONIngestion
@@ -47,20 +39,12 @@ module APIExtension
4739
"LongString" => {type: "integer", minimum: LONG_STRING_MIN, maximum: LONG_STRING_MAX}
4840
}.freeze
4941

50-
# Wires up the factory extension when this module is extended onto an API instance.
42+
# Wires up the JSON ingestion extensions when this module is extended onto an API instance.
5143
#
5244
# @param api [ElasticGraph::SchemaDefinition::API] the API instance to extend
5345
# @return [void]
5446
# @api private
5547
def self.extended(api)
56-
# Prepend our indexing-field-type extensions onto the core classes so they participate in
57-
# `to_json_schema` / `format_field_json_schema_customizations` / `json_schema_field_metadata_by_field_name`.
58-
# Guarded so re-extending an already-extended API instance is a no-op.
59-
ElasticGraph::SchemaDefinition::Indexing::FieldType::Enum.prepend(Indexing::FieldType::EnumExtension) unless ElasticGraph::SchemaDefinition::Indexing::FieldType::Enum < Indexing::FieldType::EnumExtension
60-
ElasticGraph::SchemaDefinition::Indexing::FieldType::Object.prepend(Indexing::FieldType::ObjectExtension) unless ElasticGraph::SchemaDefinition::Indexing::FieldType::Object < Indexing::FieldType::ObjectExtension
61-
ElasticGraph::SchemaDefinition::Indexing::FieldType::Scalar.prepend(Indexing::FieldType::ScalarExtension) unless ElasticGraph::SchemaDefinition::Indexing::FieldType::Scalar < Indexing::FieldType::ScalarExtension
62-
ElasticGraph::SchemaDefinition::Indexing::FieldType::Union.prepend(Indexing::FieldType::UnionExtension) unless ElasticGraph::SchemaDefinition::Indexing::FieldType::Union < Indexing::FieldType::UnionExtension
63-
6448
state = api.state.extend(StateExtension) # : ElasticGraph::SchemaDefinition::State & StateExtension
6549
state.reserved_type_names << EVENT_ENVELOPE_JSON_SCHEMA_NAME
6650
api.factory.extend FactoryExtension

elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/factory_extension.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
require "elastic_graph/json_ingestion/schema_definition/schema_elements/object_interface_extension"
1515
require "elastic_graph/json_ingestion/schema_definition/schema_elements/scalar_type_extension"
1616
require "elastic_graph/json_ingestion/schema_definition/schema_elements/type_reference_extension"
17+
require "elastic_graph/json_ingestion/schema_definition/schema_elements/union_type_extension"
1718

1819
module ElasticGraph
1920
module JSONIngestion
@@ -78,6 +79,14 @@ def new_type_reference(name)
7879
super(name).extend(SchemaElements::TypeReferenceExtension)
7980
end
8081

82+
# @private
83+
def new_union_type(name)
84+
super(name) do |type|
85+
type.extend SchemaElements::UnionTypeExtension
86+
yield type if block_given?
87+
end
88+
end
89+
8190
# Creates a new Results instance with JSON Schema extensions.
8291
#
8392
# @return [ElasticGraph::SchemaDefinition::Results] the created results instance
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Copyright 2024 - 2026 Block, Inc.
2+
#
3+
# Use of this source code is governed by an MIT-style
4+
# license that can be found in the LICENSE file or at
5+
# https://opensource.org/licenses/MIT.
6+
#
7+
# frozen_string_literal: true
8+
9+
require "elastic_graph/json_ingestion/schema_definition/indexing/field_type/enum_extension"
10+
require "elastic_graph/schema_definition/indexing/field_type/enum"
11+
12+
module ElasticGraph
13+
module JSONIngestion
14+
module SchemaDefinition
15+
module Indexing
16+
module FieldType
17+
# JSON-ingestion indexing field type wrapper for enums.
18+
#
19+
# @private
20+
class Enum
21+
include EnumExtension
22+
23+
def self.wrap(field_type)
24+
new(field_type)
25+
end
26+
27+
def initialize(field_type)
28+
@field_type = field_type
29+
end
30+
31+
def enum_value_names
32+
@field_type.enum_value_names
33+
end
34+
35+
def to_mapping
36+
@field_type.to_mapping
37+
end
38+
end
39+
end
40+
end
41+
end
42+
end
43+
end
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Copyright 2024 - 2026 Block, Inc.
2+
#
3+
# Use of this source code is governed by an MIT-style
4+
# license that can be found in the LICENSE file or at
5+
# https://opensource.org/licenses/MIT.
6+
#
7+
# frozen_string_literal: true
8+
9+
require "elastic_graph/json_ingestion/schema_definition/indexing/field_type/object_extension"
10+
require "elastic_graph/schema_definition/indexing/field_type/object"
11+
12+
module ElasticGraph
13+
module JSONIngestion
14+
module SchemaDefinition
15+
module Indexing
16+
module FieldType
17+
# JSON-ingestion indexing field type wrapper for objects.
18+
#
19+
# @private
20+
class Object
21+
include ObjectExtension
22+
23+
def self.wrap(field_type, json_schema_options:)
24+
new(field_type).with_json_schema_options(json_schema_options)
25+
end
26+
27+
def initialize(field_type)
28+
@field_type = field_type
29+
end
30+
31+
def schema_def_state
32+
@field_type.schema_def_state
33+
end
34+
35+
def type_name
36+
@field_type.type_name
37+
end
38+
39+
def subfields
40+
@field_type.subfields
41+
end
42+
43+
def mapping_options
44+
@field_type.mapping_options
45+
end
46+
47+
def doc_comment
48+
@field_type.doc_comment
49+
end
50+
51+
def to_mapping
52+
@field_type.to_mapping
53+
end
54+
end
55+
end
56+
end
57+
end
58+
end
59+
end
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Copyright 2024 - 2026 Block, Inc.
2+
#
3+
# Use of this source code is governed by an MIT-style
4+
# license that can be found in the LICENSE file or at
5+
# https://opensource.org/licenses/MIT.
6+
#
7+
# frozen_string_literal: true
8+
9+
require "elastic_graph/json_ingestion/schema_definition/indexing/field_type/scalar_extension"
10+
require "elastic_graph/schema_definition/indexing/field_type/scalar"
11+
12+
module ElasticGraph
13+
module JSONIngestion
14+
module SchemaDefinition
15+
module Indexing
16+
module FieldType
17+
# JSON-ingestion indexing field type wrapper for scalars.
18+
#
19+
# @private
20+
class Scalar
21+
include ScalarExtension
22+
23+
def self.wrap(field_type)
24+
new(field_type)
25+
end
26+
27+
def initialize(field_type)
28+
@field_type = field_type
29+
end
30+
31+
def scalar_type
32+
@field_type.scalar_type
33+
end
34+
35+
def to_mapping
36+
@field_type.to_mapping
37+
end
38+
end
39+
end
40+
end
41+
end
42+
end
43+
end
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Copyright 2024 - 2026 Block, Inc.
2+
#
3+
# Use of this source code is governed by an MIT-style
4+
# license that can be found in the LICENSE file or at
5+
# https://opensource.org/licenses/MIT.
6+
#
7+
# frozen_string_literal: true
8+
9+
require "elastic_graph/json_ingestion/schema_definition/indexing/field_type/union_extension"
10+
require "elastic_graph/schema_definition/indexing/field_type/union"
11+
12+
module ElasticGraph
13+
module JSONIngestion
14+
module SchemaDefinition
15+
module Indexing
16+
module FieldType
17+
# JSON-ingestion indexing field type wrapper for unions.
18+
#
19+
# @private
20+
class Union
21+
include UnionExtension
22+
23+
def self.wrap(field_type)
24+
new(field_type)
25+
end
26+
27+
def initialize(field_type)
28+
@field_type = field_type
29+
end
30+
31+
def subtypes_by_name
32+
@field_type.subtypes_by_name
33+
end
34+
35+
def to_mapping
36+
@field_type.to_mapping
37+
end
38+
end
39+
end
40+
end
41+
end
42+
end
43+
end

0 commit comments

Comments
 (0)