From e4f6b9667ca110742fe1c36e248d480c28fbb135 Mon Sep 17 00:00:00 2001 From: Josh Wilson Date: Wed, 20 May 2026 14:50:22 -0500 Subject: [PATCH] Add JSON ingestion schema extension modules --- .../schema_definition/api_extension.rb | 163 +++++++++++ .../schema_definition/factory_extension.rb | 97 +++++++ .../schema_definition/indexing/field.rb | 136 +++++++++ .../indexing/field_reference.rb | 44 +++ .../indexing/field_type/enum_extension.rb | 52 ++++ .../indexing/field_type/object_extension.rb | 108 +++++++ .../indexing/field_type/scalar_extension.rb | 43 +++ .../indexing/field_type/union_extension.rb | 52 ++++ .../indexing/index_extension.rb | 53 ++++ .../json_schema_option_validator.rb | 35 +++ .../schema_definition/results_extension.rb | 132 +++++++++ .../schema_artifact_manager_extension.rb | 263 ++++++++++++++++++ .../schema_elements/enum_type_extension.rb | 24 ++ .../schema_elements/field_extension.rb | 71 +++++ .../object_interface_extension.rb | 44 +++ .../schema_elements/scalar_type_extension.rb | 71 +++++ .../type_reference_extension.rb | 49 ++++ .../schema_definition/state_extension.rb | 31 +++ .../schema_definition/api_extension.rbs | 18 ++ .../schema_definition/factory_extension.rbs | 17 ++ .../schema_definition/indexing/field.rbs | 32 +++ .../indexing/field_reference.rbs | 27 ++ .../schema_definition/indexing/field_type.rbs | 13 + .../indexing/field_type/enum_extension.rbs | 13 + .../indexing/field_type/object_extension.rbs | 24 ++ .../indexing/field_type/scalar_extension.rbs | 13 + .../indexing/field_type/union_extension.rbs | 13 + .../indexing/index_extension.rbs | 20 ++ .../json_schema_option_validator.rbs | 9 + .../schema_definition/results_extension.rbs | 30 ++ .../schema_artifact_manager_extension.rbs | 27 ++ .../schema_elements/enum_type_extension.rbs | 13 + .../schema_elements/field_extension.rbs | 17 ++ .../object_interface_extension.rbs | 15 + .../schema_elements/scalar_type_extension.rbs | 19 ++ .../type_reference_extension.rbs | 16 ++ .../schema_definition/state_extension.rbs | 14 + .../schema_elements/enum_type.rb | 8 +- .../elastic_graph/schema_definition/state.rb | 6 +- .../schema_definition/indexing/index.rbs | 6 + .../schema_elements/enum_type.rbs | 4 + .../elastic_graph/schema_definition/state.rbs | 2 + 42 files changed, 1841 insertions(+), 3 deletions(-) create mode 100644 elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/api_extension.rb create mode 100644 elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/factory_extension.rb create mode 100644 elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/indexing/field.rb create mode 100644 elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/indexing/field_reference.rb create mode 100644 elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/indexing/field_type/enum_extension.rb create mode 100644 elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/indexing/field_type/object_extension.rb create mode 100644 elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/indexing/field_type/scalar_extension.rb create mode 100644 elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/indexing/field_type/union_extension.rb create mode 100644 elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/indexing/index_extension.rb create mode 100644 elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/json_schema_option_validator.rb create mode 100644 elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/results_extension.rb create mode 100644 elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/schema_artifact_manager_extension.rb create mode 100644 elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/schema_elements/enum_type_extension.rb create mode 100644 elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/schema_elements/field_extension.rb create mode 100644 elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/schema_elements/object_interface_extension.rb create mode 100644 elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/schema_elements/scalar_type_extension.rb create mode 100644 elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/schema_elements/type_reference_extension.rb create mode 100644 elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/state_extension.rb create mode 100644 elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/api_extension.rbs create mode 100644 elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/factory_extension.rbs create mode 100644 elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/indexing/field.rbs create mode 100644 elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/indexing/field_reference.rbs create mode 100644 elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/indexing/field_type.rbs create mode 100644 elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/indexing/field_type/enum_extension.rbs create mode 100644 elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/indexing/field_type/object_extension.rbs create mode 100644 elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/indexing/field_type/scalar_extension.rbs create mode 100644 elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/indexing/field_type/union_extension.rbs create mode 100644 elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/indexing/index_extension.rbs create mode 100644 elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/json_schema_option_validator.rbs create mode 100644 elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/results_extension.rbs create mode 100644 elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/schema_artifact_manager_extension.rbs create mode 100644 elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/schema_elements/enum_type_extension.rbs create mode 100644 elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/schema_elements/field_extension.rbs create mode 100644 elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/schema_elements/object_interface_extension.rbs create mode 100644 elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/schema_elements/scalar_type_extension.rbs create mode 100644 elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/schema_elements/type_reference_extension.rbs create mode 100644 elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/state_extension.rbs diff --git a/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/api_extension.rb b/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/api_extension.rb new file mode 100644 index 000000000..f37cf6ade --- /dev/null +++ b/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/api_extension.rb @@ -0,0 +1,163 @@ +# Copyright 2024 - 2026 Block, Inc. +# +# Use of this source code is governed by an MIT-style +# license that can be found in the LICENSE file or at +# https://opensource.org/licenses/MIT. +# +# frozen_string_literal: true + +require "elastic_graph/constants" +require "elastic_graph/graphql/scalar_coercion_adapters/valid_time_zones" +require "elastic_graph/json_ingestion/schema_definition/factory_extension" +require "elastic_graph/json_ingestion/schema_definition/indexing/field_type/enum_extension" +require "elastic_graph/json_ingestion/schema_definition/indexing/field_type/object_extension" +require "elastic_graph/json_ingestion/schema_definition/indexing/field_type/scalar_extension" +require "elastic_graph/json_ingestion/schema_definition/indexing/field_type/union_extension" +require "elastic_graph/json_ingestion/schema_definition/state_extension" +require "elastic_graph/schema_definition/indexing/field_type/enum" +require "elastic_graph/schema_definition/indexing/field_type/object" +require "elastic_graph/schema_definition/indexing/field_type/scalar" +require "elastic_graph/schema_definition/indexing/field_type/union" + +module ElasticGraph + module JSONIngestion + # Namespace for all JSON Schema schema definition support. + # + # {SchemaDefinition::APIExtension} is the primary entry point and should be used as a schema definition extension module. + module SchemaDefinition + # Module designed to be extended onto an {ElasticGraph::SchemaDefinition::API} instance + # to add JSON Schema ingestion serializer capabilities. + module APIExtension + # Default JSON schema options applied to ElasticGraph's built-in scalar types when this extension + # is loaded. Keyed by the un-overridden type name; the lookup at runtime maps each key through + # `type_name_overrides` so renamed built-ins still receive the right options. + BUILT_IN_SCALAR_JSON_SCHEMA_OPTIONS_BY_NAME = { + "Boolean" => {type: "boolean"}, + "Float" => {type: "number"}, + "ID" => {type: "string"}, + "Int" => {type: "integer", minimum: INT_MIN, maximum: INT_MAX}, + "String" => {type: "string"}, + "Cursor" => {type: "string"}, + "Date" => {type: "string", format: "date"}, + "DateTime" => {type: "string", format: "date-time"}, + "LocalTime" => {type: "string", pattern: VALID_LOCAL_TIME_JSON_SCHEMA_PATTERN}, + "TimeZone" => {type: "string", enum: GraphQL::ScalarCoercionAdapters::VALID_TIME_ZONES.to_a.freeze}, + "Untyped" => {type: ["array", "boolean", "integer", "number", "object", "string"].freeze}, + "JsonSafeLong" => {type: "integer", minimum: JSON_SAFE_LONG_MIN, maximum: JSON_SAFE_LONG_MAX}, + "LongString" => {type: "integer", minimum: LONG_STRING_MIN, maximum: LONG_STRING_MAX} + }.freeze + + # Wires up the factory extension when this module is extended onto an API instance. + # + # @param api [ElasticGraph::SchemaDefinition::API] the API instance to extend + # @return [void] + # @api private + def self.extended(api) + # Prepend our indexing-field-type extensions onto the core classes so they participate in + # `to_json_schema` / `format_field_json_schema_customizations` / `json_schema_field_metadata_by_field_name`. + # Guarded so re-extending an already-extended API instance is a no-op. + ElasticGraph::SchemaDefinition::Indexing::FieldType::Enum.prepend(Indexing::FieldType::EnumExtension) unless ElasticGraph::SchemaDefinition::Indexing::FieldType::Enum < Indexing::FieldType::EnumExtension + ElasticGraph::SchemaDefinition::Indexing::FieldType::Object.prepend(Indexing::FieldType::ObjectExtension) unless ElasticGraph::SchemaDefinition::Indexing::FieldType::Object < Indexing::FieldType::ObjectExtension + ElasticGraph::SchemaDefinition::Indexing::FieldType::Scalar.prepend(Indexing::FieldType::ScalarExtension) unless ElasticGraph::SchemaDefinition::Indexing::FieldType::Scalar < Indexing::FieldType::ScalarExtension + ElasticGraph::SchemaDefinition::Indexing::FieldType::Union.prepend(Indexing::FieldType::UnionExtension) unless ElasticGraph::SchemaDefinition::Indexing::FieldType::Union < Indexing::FieldType::UnionExtension + + state = api.state.extend(StateExtension) # : ElasticGraph::SchemaDefinition::State & StateExtension + state.reserved_type_names << EVENT_ENVELOPE_JSON_SCHEMA_NAME + api.factory.extend FactoryExtension + + # Build a lookup from final (post-`type_name_overrides`) names to JSON schema options. We can't + # key directly on `type.name` because users may have overridden the names of built-in scalars + # (e.g. `Cursor` → `PreCursor`); the keys in `BUILT_IN_SCALAR_JSON_SCHEMA_OPTIONS_BY_NAME` are + # always the un-overridden names. + options_by_final_name = BUILT_IN_SCALAR_JSON_SCHEMA_OPTIONS_BY_NAME.to_h do |name, options| + [api.state.type_ref(name).to_final_form.name, options] + end + + api.on_built_in_types do |type| + if (options = options_by_final_name[type.name]) + scalar_type = type # : ElasticGraph::SchemaDefinition::SchemaElements::ScalarType & SchemaElements::ScalarTypeExtension + scalar_type.json_schema(**options) + elsif type.name == api.state.type_ref("GeoLocation").to_final_form.name + # @type var geo_location_type: ElasticGraph::SchemaDefinition::SchemaElements::TypeWithSubfields & SchemaElements::ObjectInterfaceExtension + geo_location_type = _ = type + names = api.state.schema_elements + + # We use `nullable: false` because `GeoLocation` is indexed as a single `geo_point` field, + # and therefore can't support a `latitude` without a `longitude` or vice-versa. + latitude = geo_location_type.graphql_fields_by_name.fetch(names.latitude) # : ElasticGraph::SchemaDefinition::SchemaElements::Field & SchemaElements::FieldExtension + longitude = geo_location_type.graphql_fields_by_name.fetch(names.longitude) # : ElasticGraph::SchemaDefinition::SchemaElements::Field & SchemaElements::FieldExtension + latitude.json_schema minimum: -90, maximum: 90, nullable: false + longitude.json_schema minimum: -180, maximum: 180, nullable: false + end + end + end + + # Defines the version number of the current JSON schema. Importantly, every time a change is made that impacts the JSON schema + # artifact, the version number must be incremented to ensure that each different version of the JSON schema is identified by a unique + # version number. The publisher will then include this version number in published events to identify the version of the schema it + # was using. This avoids the need to deploy the publisher and ElasticGraph indexer at the same time to keep them in sync. + # + # @note While this is an important part of how ElasticGraph is designed to support schema evolution, it can be annoying constantly + # have to increment this while rapidly changing the schema during prototyping. You can disable the requirement to increment this + # on every JSON schema change by setting `enforce_json_schema_version` to `false` in your `Rakefile`. + # + # @param version [Integer] current version number of the JSON schema artifact + # @return [void] + # @see Local::RakeTasks#enforce_json_schema_version + def json_schema_version(version) + state = json_ingestion_state + + if !version.is_a?(Integer) || version < 1 + raise Errors::SchemaError, "`json_schema_version` must be a positive integer. Specified version: #{version}" + end + + if state.json_schema_version + raise Errors::SchemaError, "`json_schema_version` can only be set once on a schema. Previously-set version: #{state.json_schema_version}" + end + + state.json_schema_version = version + state.json_schema_version_setter_location = caller_locations(1, 1).to_a.first + nil + end + + # Defines strictness of the JSON schema validation. By default, the JSON schema will require all fields to be provided by the + # publisher (but they can be nullable) and will ignore extra fields that are not defined in the schema. Use this method to + # configure this behavior. + # + # @param allow_omitted_fields [bool] Whether nullable fields can be omitted from indexing events. + # @param allow_extra_fields [bool] Whether extra fields (e.g. beyond fields defined in the schema) can be included in indexing events. + # @return [void] + # + # @note If you allow both omitted fields and extra fields, ElasticGraph's JSON schema validation will allow (and ignore) misspelled + # field names in indexing events. For example, if the ElasticGraph schema has a nullable field named `parentId` but the publisher + # accidentally provides it as `parent_id`, ElasticGraph would happily ignore the `parent_id` field entirely, because `parentId` + # is allowed to be omitted and `parent_id` would be treated as an extra field. Therefore, we recommend that you only set one of + # these to `true` (or none). + def json_schema_strictness(allow_omitted_fields: false, allow_extra_fields: true) + state = json_ingestion_state + + unless [true, false].include?(allow_omitted_fields) + raise Errors::SchemaError, "`allow_omitted_fields` must be true or false" + end + + unless [true, false].include?(allow_extra_fields) + raise Errors::SchemaError, "`allow_extra_fields` must be true or false" + end + + state.allow_omitted_json_schema_fields = allow_omitted_fields + state.allow_extra_json_schema_fields = allow_extra_fields + nil + end + + private + + # Returns the API's `state` narrowed to include this gem's `StateExtension`. Centralizes + # the Steep cast that's needed because Steep can't see the `extend(StateExtension)` applied + # at runtime in `extended`. + def json_ingestion_state + state # : ElasticGraph::SchemaDefinition::State & StateExtension + end + end + end + end +end diff --git a/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/factory_extension.rb b/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/factory_extension.rb new file mode 100644 index 000000000..0bae2715b --- /dev/null +++ b/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/factory_extension.rb @@ -0,0 +1,97 @@ +# Copyright 2024 - 2026 Block, Inc. +# +# Use of this source code is governed by an MIT-style +# license that can be found in the LICENSE file or at +# https://opensource.org/licenses/MIT. +# +# frozen_string_literal: true + +require "elastic_graph/json_ingestion/schema_definition/indexing/index_extension" +require "elastic_graph/json_ingestion/schema_definition/results_extension" +require "elastic_graph/json_ingestion/schema_definition/schema_artifact_manager_extension" +require "elastic_graph/json_ingestion/schema_definition/schema_elements/enum_type_extension" +require "elastic_graph/json_ingestion/schema_definition/schema_elements/field_extension" +require "elastic_graph/json_ingestion/schema_definition/schema_elements/object_interface_extension" +require "elastic_graph/json_ingestion/schema_definition/schema_elements/scalar_type_extension" +require "elastic_graph/json_ingestion/schema_definition/schema_elements/type_reference_extension" + +module ElasticGraph + module JSONIngestion + module SchemaDefinition + # Extension module applied to `ElasticGraph::SchemaDefinition::Factory` to wire up + # JSON Schema support on Results and SchemaArtifactManager instances. + # + # @api private + module FactoryExtension + # @private + def new_enum_type(name) + super(name) do |type| + type.extend SchemaElements::EnumTypeExtension + yield type if block_given? + end + end + + # @private + def new_field(**kwargs, &block) + super(**kwargs) do |field| + field.extend SchemaElements::FieldExtension + block&.call(field) + end + end + + # @private + def new_index(name, settings, type, &block) + super(name, settings, type) do |index| + index.extend Indexing::IndexExtension + index.require_id_in_json_schema + block&.call(index) + end + end + + # @private + def new_interface_type(name) + super(name) do |type| + type.extend SchemaElements::ObjectInterfaceExtension + yield type if block_given? + end + end + + # @private + def new_object_type(name) + super(name) do |type| + type.extend SchemaElements::ObjectInterfaceExtension + yield type if block_given? + end + end + + # @private + def new_scalar_type(name) + super(name) do |type| + type.extend SchemaElements::ScalarTypeExtension + yield type if block_given? + type.validate_json_schema_configuration! unless state.initially_registered_built_in_types.empty? + end + end + + # @private + def new_type_reference(name) + super(name).extend(SchemaElements::TypeReferenceExtension) + end + + # Creates a new Results instance with JSON Schema extensions. + # + # @return [ElasticGraph::SchemaDefinition::Results] the created results instance + def new_results + super.extend(ResultsExtension) + end + + # Creates a new SchemaArtifactManager instance with JSON Schema extensions. + # + # @return [ElasticGraph::SchemaDefinition::SchemaArtifactManager] the created artifact manager + def new_schema_artifact_manager(...) + super.extend(SchemaArtifactManagerExtension) + end + end + end + end +end diff --git a/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/indexing/field.rb b/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/indexing/field.rb new file mode 100644 index 000000000..7dd2b691f --- /dev/null +++ b/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/indexing/field.rb @@ -0,0 +1,136 @@ +# Copyright 2024 - 2026 Block, Inc. +# +# Use of this source code is governed by an MIT-style +# license that can be found in the LICENSE file or at +# https://opensource.org/licenses/MIT. +# +# frozen_string_literal: true + +require "elastic_graph/constants" +require "elastic_graph/json_ingestion/schema_definition/indexing/json_schema_field_metadata" +require "elastic_graph/support/hash_util" + +module ElasticGraph + module JSONIngestion + module SchemaDefinition + # Namespace for JSON-schema-aware indexing components. + module Indexing + # Extends indexing fields with JSON schema generation behavior. + # + # @api private + module FieldExtension + # JSON schema overrides that automatically apply to specific mapping types so that the JSON schema + # validation will reject values which cannot be indexed into fields of a specific mapping type. + # + # @see https://www.elastic.co/guide/en/elasticsearch/reference/current/number.html Elasticsearch numeric field type documentation + # @note We don't handle `integer` here because it's the default numeric type (handled by our definition of the `Int` scalar type). + # @note Likewise, we don't handle `long` here because a custom scalar type must be used for that since GraphQL's `Int` type can't handle long values. + JSON_SCHEMA_OVERRIDES_BY_MAPPING_TYPE = { + "byte" => {"minimum" => -(2**7), "maximum" => (2**7) - 1}, + "short" => {"minimum" => -(2**15), "maximum" => (2**15) - 1}, + "keyword" => {"maxLength" => DEFAULT_MAX_KEYWORD_LENGTH}, + "text" => {"maxLength" => DEFAULT_MAX_TEXT_LENGTH} + } + + # @return [Hash] user-specified JSON schema customizations for this field + def json_schema_customizations + @json_schema_customizations + end + + # @private + def with_json_schema(json_schema_layers:, json_schema_customizations:) + @json_schema_layers = json_schema_layers + @json_schema_customizations = json_schema_customizations + self + end + + # Returns the JSON schema definition for this field. + # + # @return [Hash] the JSON schema hash + def json_schema + @json_schema ||= + json_schema_layers + .reverse # resolve layers from innermost to outermost wrappings + .reduce(inner_json_schema) { |acc, layer| process_layer(layer, acc) } + .merge(outer_json_schema_customizations) + .merge({"description" => doc_comment}.compact) + .then { |hash| Support::HashUtil.stringify_keys(hash) } + end + + # @return [JSONSchemaFieldMetadata] metadata about this field for inclusion in the JSON schema + def json_schema_metadata + JSONSchemaFieldMetadata.new(type: type.name, name_in_index: name_in_index) + end + + def nullable? + json_schema_layers.include?(:nullable) + end + + private + + def json_schema_layers + @json_schema_layers + end + + def inner_json_schema + user_specified_customizations = + if user_specified_json_schema_customizations_go_on_outside? + {} # : ::Hash[::String, untyped] + else + Support::HashUtil.stringify_keys(json_schema_customizations) + end + + customizations_from_mapping = JSON_SCHEMA_OVERRIDES_BY_MAPPING_TYPE[mapping["type"]] || {} + customizations = customizations_from_mapping.merge(user_specified_customizations) + # @type var field_type: _JSONFieldType + field_type = _ = indexing_field_type + customizations = field_type.format_field_json_schema_customizations(customizations) + + ref = {"$ref" => "#/$defs/#{type.unwrapped_name}"} + return ref if customizations.empty? + + # Combine any customizations with the type ref under an "allOf" subschema: + # all of these properties must hold true for the type to be valid. + # + # Note that if we simply combine the customizations with the `$ref` + # at the same level, it will not work, because other subschema + # properties are ignored when they are in the same object as a `$ref`: + # https://github.com/json-schema-org/JSON-Schema-Test-Suite/blob/2.0.0/tests/draft7/ref.json#L165-L168 + {"allOf" => [ref, customizations]} + end + + def outer_json_schema_customizations + return {} unless user_specified_json_schema_customizations_go_on_outside? + Support::HashUtil.stringify_keys(json_schema_customizations) + end + + # Indicates if the user-specified JSON schema customizations should go on the inside + # (where they normally go) or on the outside. They only go on the outside when it's + # an array field, because then they apply to the array itself instead of the items in the + # array. + def user_specified_json_schema_customizations_go_on_outside? + json_schema_layers.include?(:array) + end + + def process_layer(layer, schema) + case layer + when :nullable + # Here we use "anyOf" to ensure that JSON can either match the schema OR null. + # + # (Using "oneOf" would mean that if we had a schema that also allowed null, + # null would never be allowed, since "oneOf" must match exactly one subschema). + { + "anyOf" => [ + schema, + {"type" => "null"} + ] + } + when :array + {"type" => "array", "items" => schema} + end + end + end + end + end + end +end diff --git a/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/indexing/field_reference.rb b/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/indexing/field_reference.rb new file mode 100644 index 000000000..e04f14995 --- /dev/null +++ b/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/indexing/field_reference.rb @@ -0,0 +1,44 @@ +# Copyright 2024 - 2026 Block, Inc. +# +# Use of this source code is governed by an MIT-style +# license that can be found in the LICENSE file or at +# https://opensource.org/licenses/MIT. +# +# frozen_string_literal: true + +require "elastic_graph/json_ingestion/schema_definition/indexing/field" + +module ElasticGraph + module JSONIngestion + module SchemaDefinition + module Indexing + # @!parse class FieldReference < ::Data; end + FieldReference = ::Data.define( + :field_reference, + :json_schema_layers, + :json_schema_customizations + ) + + # A JSON-schema-aware wrapper around the core indexing field reference. + # + # @api private + class FieldReference < ::Data + # Resolves this field reference into a JSON-schema-aware field, or `nil` if unresolvable. + # + # @return [ElasticGraph::SchemaDefinition::Indexing::Field, nil] + def resolve + return nil unless (resolved_field = field_reference.resolve) + + json_schema_field = resolved_field.extend(Indexing::FieldExtension) # : ElasticGraph::SchemaDefinition::Indexing::Field & FieldExtension + json_schema_field.with_json_schema( + json_schema_layers: json_schema_layers, + json_schema_customizations: json_schema_customizations + ) + end + + # @dynamic initialize, with, field_reference, json_schema_layers, json_schema_customizations + end + end + end + end +end diff --git a/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/indexing/field_type/enum_extension.rb b/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/indexing/field_type/enum_extension.rb new file mode 100644 index 000000000..1819f3ea1 --- /dev/null +++ b/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/indexing/field_type/enum_extension.rb @@ -0,0 +1,52 @@ +# Copyright 2024 - 2026 Block, Inc. +# +# Use of this source code is governed by an MIT-style +# license that can be found in the LICENSE file or at +# https://opensource.org/licenses/MIT. +# +# frozen_string_literal: true + +module ElasticGraph + module JSONIngestion + module SchemaDefinition + module Indexing + # Namespace for indexing-field-type extensions that contribute JSON schema generation behavior. + module FieldType + # Extends enum indexing field types with JSON schema serialization. + # + # @private + module EnumExtension + # @return [Hash] additional ElasticGraph metadata to put in the JSON schema for this enum type. + def json_schema_field_metadata_by_field_name + {} + end + + # @param customizations [Hash] JSON schema customizations + # @return [Hash] formatted customizations. + def format_field_json_schema_customizations(customizations) + # Since an enum type already restricts the values to a small set of allowed values, we do not need to keep + # other customizations (such as the `maxLength` field customization EG automatically applies to fields + # indexed as a `keyword`--we don't allow enum values to exceed that length, anyway). + # + # It's desirable to restrict what customizations are applied because when a publisher uses the JSON schema + # to generate code using a library such as https://github.com/pwall567/json-kotlin-schema-codegen, we found + # that the presence of extra field customizations inhibits the library's ability to generate code in the way + # we want (it causes the type of the enum to change since the JSON schema changes from a direct `$ref` to + # being wrapped in an `allOf`). + # + # However, we still want to apply `enum` customizations--this allows a user to "narrow" the set of allowed + # values for a field. For example, a `Currency` enum could contain every currency, and a user may want to + # restrict a specific `currency` field to a subset of currencies (e.g. to just USD, CAD, and EUR). + customizations.slice("enum") + end + + # @return [Hash] the JSON schema for this enum type. + def to_json_schema + {"type" => "string", "enum" => enum_value_names} + end + end + end + end + end + end +end diff --git a/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/indexing/field_type/object_extension.rb b/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/indexing/field_type/object_extension.rb new file mode 100644 index 000000000..ab436546f --- /dev/null +++ b/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/indexing/field_type/object_extension.rb @@ -0,0 +1,108 @@ +# Copyright 2024 - 2026 Block, Inc. +# +# Use of this source code is governed by an MIT-style +# license that can be found in the LICENSE file or at +# https://opensource.org/licenses/MIT. +# +# frozen_string_literal: true + +require "elastic_graph/support/hash_util" + +module ElasticGraph + module JSONIngestion + module SchemaDefinition + module Indexing + module FieldType + # Extends object/interface indexing field types with JSON schema serialization. + # + # @private + module ObjectExtension + def json_schema_options + @json_schema_options ||= {} + end + + def with_json_schema_options(json_schema_options) + @json_schema_options = json_schema_options + self + end + + # @return [Hash] field metadata keyed by field name + def json_schema_field_metadata_by_field_name + # @type var json_subfields: ::Array[ElasticGraph::SchemaDefinition::Indexing::Field & FieldExtension] + json_subfields = _ = subfields + json_subfields.to_h { |field| [field.name, field.json_schema_metadata] } + end + + # @param customizations [Hash] the customizations to format + # @return [Hash] the formatted customizations + def format_field_json_schema_customizations(customizations) + customizations + end + + # @return [Hash] the JSON schema definition for this object type + def to_json_schema + # @type var state: ElasticGraph::SchemaDefinition::State & StateExtension + state = _ = schema_def_state + # @type var json_subfields: ::Array[ElasticGraph::SchemaDefinition::Indexing::Field & FieldExtension] + json_subfields = _ = subfields + + @to_json_schema ||= + if json_schema_options.empty? + # Fields that are `sourced_from` an alternate type must not be included in this type's JSON schema, + # since events of this type won't include them. + other_source_subfields, json_schema_candidate_subfields = json_subfields.partition(&:source) + validate_sourced_fields_have_no_json_schema_overrides(other_source_subfields) + json_schema_subfields = json_schema_candidate_subfields.reject(&:runtime_field_script) + required_fields = json_schema_subfields + required_fields = required_fields.reject(&:nullable?) if state.allow_omitted_json_schema_fields + + { + "type" => "object", + "properties" => json_schema_subfields.to_h { |field| [field.name, field.json_schema] }.merge(json_schema_typename_field), + # Note: `__typename` is intentionally not included in the `required` list. If `__typename` is present + # we want it validated (as we do by merging in `json_schema_typename_field`) but we only want + # to require it in the context of a union type. The union's JSON schema requires the field. + "required" => required_fields.map(&:name).freeze, + "additionalProperties" => (false unless state.allow_extra_json_schema_fields), + "description" => doc_comment + }.compact.freeze + else + Support::HashUtil.stringify_keys(json_schema_options) + end + end + + private + + # Returns a `__typename` property which we use for union types. + # + # This must always be set to the name of the type (thus the const value). + # + # We also add a "default" value. This does not impact validation, but rather + # aids tools like our Kotlin codegen to save publishers from having to set the + # property explicitly when creating events. + def json_schema_typename_field + { + "__typename" => { + "type" => "string", + "const" => type_name, + "default" => type_name + } + } + end + + def validate_sourced_fields_have_no_json_schema_overrides(other_source_subfields) + problem_fields = other_source_subfields.reject { |field| field.json_schema_customizations.empty? } + return if problem_fields.empty? + + field_descriptions = problem_fields.map(&:name).sort.map { |field| "`#{field}`" }.join(", ") + raise Errors::SchemaError, + "`#{type_name}` has #{problem_fields.size} field(s) (#{field_descriptions}) that are `sourced_from` " \ + "another type and also have JSON schema customizations. Instead, put the JSON schema " \ + "customizations on the source type's field definitions." + end + end + end + end + end + end +end diff --git a/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/indexing/field_type/scalar_extension.rb b/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/indexing/field_type/scalar_extension.rb new file mode 100644 index 000000000..a4975778a --- /dev/null +++ b/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/indexing/field_type/scalar_extension.rb @@ -0,0 +1,43 @@ +# Copyright 2024 - 2026 Block, Inc. +# +# Use of this source code is governed by an MIT-style +# license that can be found in the LICENSE file or at +# https://opensource.org/licenses/MIT. +# +# frozen_string_literal: true + +require "elastic_graph/support/hash_util" + +module ElasticGraph + module JSONIngestion + module SchemaDefinition + module Indexing + module FieldType + # Extends scalar indexing field types with JSON schema serialization. + # + # @private + module ScalarExtension + # @return [Hash] empty hash, as scalar types have no subfields + def json_schema_field_metadata_by_field_name + {} + end + + # @param customizations [Hash] the customizations to format + # @return [Hash] the formatted customizations + def format_field_json_schema_customizations(customizations) + customizations + end + + # @return [Hash] the JSON schema definition for this scalar type + def to_json_schema + json_scalar_type = scalar_type # : ElasticGraph::SchemaDefinition::SchemaElements::ScalarType & SchemaElements::ScalarTypeExtension + json_scalar_type.validate_json_schema_configuration! + + Support::HashUtil.stringify_keys(json_scalar_type.json_schema_options) + end + end + end + end + end + end +end diff --git a/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/indexing/field_type/union_extension.rb b/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/indexing/field_type/union_extension.rb new file mode 100644 index 000000000..fbe73e39e --- /dev/null +++ b/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/indexing/field_type/union_extension.rb @@ -0,0 +1,52 @@ +# Copyright 2024 - 2026 Block, Inc. +# +# Use of this source code is governed by an MIT-style +# license that can be found in the LICENSE file or at +# https://opensource.org/licenses/MIT. +# +# frozen_string_literal: true + +module ElasticGraph + module JSONIngestion + module SchemaDefinition + module Indexing + module FieldType + # Extends union indexing field types with JSON schema serialization. + # + # @private + module UnionExtension + # @return [Hash] empty hash, as union types have no subfields + def json_schema_field_metadata_by_field_name + {} + end + + # @param customizations [Hash] the customizations to format + # @return [Hash] the formatted customizations + def format_field_json_schema_customizations(customizations) + customizations + end + + # @return [Hash] the JSON schema definition for this union type + def to_json_schema + subtype_json_schemas = subtypes_by_name.keys.map { |name| {"$ref" => "#/$defs/#{name}"} } + + # A union type can represent multiple subtypes, referenced by the "anyOf" clause below. + # We also add a requirement for the presence of __typename to indicate which type + # is being referenced (this property is pre-defined on the type itself as a constant). + # + # Note: Although both "oneOf" and "anyOf" keywords are valid for combining schemas + # to form a union, and validate equivalently when no object can satisfy multiple of the + # subschemas (which is the case here given the __typename requirements are mutually + # exclusive), we chose to use "oneOf" here because it works better with this library: + # https://github.com/pwall567/json-kotlin-schema-codegen + { + "required" => %w[__typename], + "oneOf" => subtype_json_schemas + } + end + end + end + end + end + end +end diff --git a/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/indexing/index_extension.rb b/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/indexing/index_extension.rb new file mode 100644 index 000000000..94a4b47e6 --- /dev/null +++ b/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/indexing/index_extension.rb @@ -0,0 +1,53 @@ +# Copyright 2024 - 2026 Block, Inc. +# +# Use of this source code is governed by an MIT-style +# license that can be found in the LICENSE file or at +# https://opensource.org/licenses/MIT. +# +# frozen_string_literal: true + +module ElasticGraph + module JSONIngestion + module SchemaDefinition + module Indexing + # Extends indices with JSON-schema-specific event requirements. + module IndexExtension + # @private + def require_id_in_json_schema + schema_def_state.after_user_definition_complete do + # @type var routing_field_path: _JSONFieldPath + routing_field_path = _ = self.routing_field_path + routing_field_path.last_part.json_schema nullable: false + end + end + + # @private + def rollover(frequency, timestamp_field_path_name) + super + + schema_def_state.after_user_definition_complete do + # @type var timestamp_field_path: _JSONFieldPath + timestamp_field_path = _ = public_field_path(timestamp_field_path_name, explanation: "it is referenced as an index `rollover` field") + timestamp_field_path + .path_parts + .each { |field| field.json_schema nullable: false } + end + end + + # @private + def route_with(routing_field_path_name) + super + + schema_def_state.after_user_definition_complete do + # @type var routing_field_path: _JSONFieldPath + routing_field_path = _ = public_field_path(routing_field_path_name, explanation: "it is referenced as an index `route_with` field") + + routing_field_path.path_parts.take(routing_field_path.path_parts.size - 1).each { |field| field.json_schema nullable: false } + routing_field_path.last_part.json_schema nullable: false, pattern: ElasticGraph::SchemaDefinition::Indexing::Index::HAS_NON_WHITE_SPACE_REGEX + end + end + end + end + end + end +end diff --git a/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/json_schema_option_validator.rb b/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/json_schema_option_validator.rb new file mode 100644 index 000000000..4112bde5e --- /dev/null +++ b/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/json_schema_option_validator.rb @@ -0,0 +1,35 @@ +# Copyright 2024 - 2026 Block, Inc. +# +# Use of this source code is governed by an MIT-style +# license that can be found in the LICENSE file or at +# https://opensource.org/licenses/MIT. +# +# frozen_string_literal: true + +require "elastic_graph/support/hash_util" +require "elastic_graph/support/json_schema/meta_schema_validator" + +module ElasticGraph + module JSONIngestion + module SchemaDefinition + # Validates JSON-schema-specific configuration supplied through schema definition APIs. + # + # @api private + module JSONSchemaOptionValidator + # Validates JSON schema options against the JSON meta-schema. + # + # @param schema_element [Object] the schema element being configured (used in error messages) + # @param options [Hash] the JSON schema options to validate + # @raise [Errors::SchemaError] if the options are invalid + # @return [void] + def self.validate!(schema_element, options) + validatable_json_schema = Support::HashUtil.stringify_keys(options) + + if (error_msg = Support::JSONSchema.strict_meta_schema_validator.validate_with_error_message(validatable_json_schema)) + raise Errors::SchemaError, "Invalid JSON schema options set on #{schema_element}:\n\n#{error_msg}" + end + end + end + end + end +end diff --git a/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/results_extension.rb b/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/results_extension.rb new file mode 100644 index 000000000..051aeb08a --- /dev/null +++ b/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/results_extension.rb @@ -0,0 +1,132 @@ +# Copyright 2024 - 2026 Block, Inc. +# +# Use of this source code is governed by an MIT-style +# license that can be found in the LICENSE file or at +# https://opensource.org/licenses/MIT. +# +# frozen_string_literal: true + +require "elastic_graph/constants" +require "elastic_graph/errors" +require "elastic_graph/json_ingestion/schema_definition/indexing/event_envelope" +require "elastic_graph/json_ingestion/schema_definition/indexing/json_schema_with_metadata" + +module ElasticGraph + module JSONIngestion + module SchemaDefinition + # Extension module for {ElasticGraph::SchemaDefinition::Results} that adds + # JSON Schema generation support. + # + # @private + module ResultsExtension + # @param version [Integer] desired JSON schema version + # @return [Hash] the JSON schema for the requested version, if available + # @raise [Errors::NotFoundError] if the requested JSON schema version is not available + def json_schemas_for(version) + unless available_json_schema_versions.include?(version) + raise Errors::NotFoundError, "The requested json schema version (#{version}) is not available. Available versions: #{available_json_schema_versions.to_a.join(", ")}." + end + + @latest_versioned_json_schema ||= merge_field_metadata_into_json_schema(current_public_json_schema).json_schema + end + + # @return [Set] set of available JSON schema versions + def available_json_schema_versions + @available_json_schema_versions ||= Set[latest_json_schema_version] + end + + # @return [Integer] the current JSON schema version + def latest_json_schema_version + current_public_json_schema[JSON_SCHEMA_VERSION_KEY] + end + + # @private + def json_schema_version_setter_location + json_ingestion_state.json_schema_version_setter_location + end + + # @private + def json_schema_field_metadata_by_type_and_field_name + @json_schema_field_metadata_by_type_and_field_name ||= json_schema_indexing_field_types_by_name + .transform_values(&:json_schema_field_metadata_by_field_name) + end + + # @private + def current_public_json_schema + @current_public_json_schema ||= build_public_json_schema + end + + # @private + def merge_field_metadata_into_json_schema(json_schema) + json_schema_with_metadata_merger.merge_metadata_into(json_schema) + end + + # @private + def unused_deprecated_elements + json_schema_with_metadata_merger.unused_deprecated_elements + end + + private + + # Returns the wrapped state narrowed to include this gem's `StateExtension`. Centralizes + # the Steep cast that's needed because Steep can't see the `extend(StateExtension)` applied + # at runtime in {APIExtension.extended}. + def json_ingestion_state + state # : ElasticGraph::SchemaDefinition::State & StateExtension + end + + def json_schema_with_metadata_merger + @json_schema_with_metadata_merger ||= Indexing::JSONSchemaWithMetadata::Merger.new(self) + end + + def build_public_json_schema + json_schema_version = json_ingestion_state.json_schema_version + if json_schema_version.nil? + raise Errors::SchemaError, "`json_schema_version` must be specified in the schema. To resolve, add `schema.json_schema_version 1` in a schema definition block." + end + + root_document_type_names = state.object_types_by_name.values + .select { |type| type.root_document_type? && !type.abstract? } + .reject { |type| derived_indexing_type_names.include?(type.name) } + .map(&:name) + + definitions_by_name = json_schema_indexing_field_types_by_name + .transform_values(&:to_json_schema) + .compact + + { + "$schema" => JSON_META_SCHEMA, + JSON_SCHEMA_VERSION_KEY => json_schema_version, + "$defs" => { + "ElasticGraphEventEnvelope" => Indexing::EventEnvelope.json_schema(root_document_type_names, json_schema_version) + }.merge(definitions_by_name) + } + end + + def json_schema_indexing_field_types_by_name + # Force `all_types` to materialize before iterating `state.types_by_name`. Reading `all_types` + # runs the `on_built_in_types` callbacks (registered by `APIExtension.extended`) that attach + # `json_schema_options` onto the built-in scalar types — those options must be set before we + # call `to_indexing_field_type` on any of them or the resulting JSON schema is incomplete. + all_types + + @json_schema_indexing_field_types_by_name ||= state + .types_by_name + .except("Query") + .values + .reject do |t| + derived_indexing_type_names.include?(t.name) || + # Skip graphql framework types + t.graphql_only? + end + .sort_by(&:name) + .to_h do |type| + # @type var indexing_field_type: ElasticGraph::SchemaDefinition::Indexing::_FieldType + indexing_field_type = _ = type.to_indexing_field_type + [type.name, indexing_field_type] + end + end + end + end + end +end diff --git a/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/schema_artifact_manager_extension.rb b/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/schema_artifact_manager_extension.rb new file mode 100644 index 000000000..f8b4f8f4c --- /dev/null +++ b/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/schema_artifact_manager_extension.rb @@ -0,0 +1,263 @@ +# Copyright 2024 - 2026 Block, Inc. +# +# Use of this source code is governed by an MIT-style +# license that can be found in the LICENSE file or at +# https://opensource.org/licenses/MIT. +# +# frozen_string_literal: true + +require "elastic_graph/constants" +require "elastic_graph/json_ingestion/schema_definition/json_schema_pruner" +require "yaml" + +module ElasticGraph + module JSONIngestion + module SchemaDefinition + # Extension module for {ElasticGraph::SchemaDefinition::SchemaArtifactManager} that adds + # JSON Schema artifact generation support. + # + # @private + module SchemaArtifactManagerExtension + # Overrides `dump_artifacts` to add JSON schema version bump checking before dumping. + def dump_artifacts + schema_results = json_ingestion_schema_definition_results + + check_if_needs_json_schema_version_bump do |recommended_json_schema_version| + if schema_results.state.enforce_json_schema_version + # @type var setter_location: ::Thread::Backtrace::Location + # We use `_ =` because while `json_schema_version_setter_location` can be nil, + # it'll never be nil if we get here and we want the type to be non-nilable. + setter_location = _ = schema_results.json_schema_version_setter_location + setter_location_path = ::Pathname.new(setter_location.absolute_path.to_s).relative_path_from(::Dir.pwd) + + abort "A change has been attempted to `json_schemas.yaml`, but the `json_schema_version` has not been correspondingly incremented. Please " \ + "increase the schema's version, and then run the `bundle exec rake schema_artifacts:dump` command again.\n\n" \ + "To update the schema version to the expected version, change line #{setter_location.lineno} at `#{setter_location_path}` to:\n" \ + " `schema.json_schema_version #{recommended_json_schema_version}`\n\n" \ + "Alternately, call `schema.enforce_json_schema_version false` in your schema definition to allow the JSON schemas file " \ + "to change without requiring a version bump, but that is only recommended for non-production applications during initial schema prototyping." + else + @output.puts <<~EOS + WARNING: the `json_schemas.yaml` artifact is being updated without the `json_schema_version` being correspondingly incremented. + This is not recommended for production applications, but is currently allowed because you have called `schema.enforce_json_schema_version false`. + EOS + end + end + + super + end + + private + + # Returns the wrapped {ElasticGraph::SchemaDefinition::Results} narrowed to include this + # gem's `ResultsExtension`. Centralizes the Steep cast that's needed because Steep can't + # see the `extend(ResultsExtension)` applied at runtime. + def json_ingestion_schema_definition_results + schema_definition_results # : ElasticGraph::SchemaDefinition::Results & ResultsExtension + end + + # Overrides the base `artifacts_from_schema_def` method to add JSON schema artifacts. + def artifacts_from_schema_def + base_artifacts = super + + versioned_artifacts = build_desired_versioned_json_schemas(json_schemas_artifact.desired_contents).values.map do |versioned_schema| + new_versioned_json_schema_artifact(versioned_schema) + end + + base_artifacts + [json_schemas_artifact] + versioned_artifacts + end + + def json_schemas_artifact + schema_results = json_ingestion_schema_definition_results + + @json_schemas_artifact ||= new_yaml_artifact( + JSON_SCHEMAS_FILE, + JSONSchemaPruner.prune(schema_results.current_public_json_schema), + extra_comment_lines: [ + "This is the \"public\" JSON schema file and is intended to be provided to publishers so that", + "they can perform code generation and event validation." + ] + ) + end + + def check_if_needs_json_schema_version_bump(&block) + if json_schemas_artifact.out_of_date? + existing_schema_version = json_schemas_artifact.existing_dumped_contents&.dig(JSON_SCHEMA_VERSION_KEY) || -1 + desired_schema_version = json_schemas_artifact.desired_contents[JSON_SCHEMA_VERSION_KEY] + + if existing_schema_version >= desired_schema_version + yield existing_schema_version + 1 + end + end + end + + def build_desired_versioned_json_schemas(current_public_json_schema) + schema_results = json_ingestion_schema_definition_results + versioned_parsed_yamls = ::Dir.glob(::File.join(@schema_artifacts_directory, JSON_SCHEMAS_BY_VERSION_DIRECTORY, "v*.yaml")).map do |file| + ::YAML.safe_load_file(file) + end + [current_public_json_schema] + + results_by_json_schema_version = versioned_parsed_yamls.to_h do |parsed_yaml| + merged_schema = schema_results.merge_field_metadata_into_json_schema(parsed_yaml) + [merged_schema.json_schema_version, merged_schema] + end + + report_json_schema_merge_errors(results_by_json_schema_version.values) + report_json_schema_merge_warnings + + results_by_json_schema_version.transform_values(&:json_schema) + end + + def report_json_schema_merge_errors(merged_results) + json_schema_versions_by_missing_field = ::Hash.new { |h, k| h[k] = [] } # : ::Hash[::String, ::Array[::Integer]] + json_schema_versions_by_missing_type = ::Hash.new { |h, k| h[k] = [] } # : ::Hash[::String, ::Array[::Integer]] + json_schema_versions_by_missing_necessary_field = ::Hash.new { |h, k| h[k] = [] } # : ::Hash[Indexing::JSONSchemaWithMetadata::MissingNecessaryField, ::Array[::Integer]] + + merged_results.each do |result| + result.missing_fields.each do |field| + json_schema_versions_by_missing_field[field] << result.json_schema_version + end + + result.missing_types.each do |type| + json_schema_versions_by_missing_type[type] << result.json_schema_version + end + + result.missing_necessary_fields.each do |missing_necessary_field| + json_schema_versions_by_missing_necessary_field[missing_necessary_field] << result.json_schema_version + end + end + + missing_field_errors = json_schema_versions_by_missing_field.map do |field, json_schema_versions| + missing_field_error_for(field, json_schema_versions) + end + + missing_type_errors = json_schema_versions_by_missing_type.map do |type, json_schema_versions| + missing_type_error_for(type, json_schema_versions) + end + + missing_necessary_field_errors = json_schema_versions_by_missing_necessary_field.map do |field, json_schema_versions| + missing_necessary_field_error_for(field, json_schema_versions) + end + + definition_conflict_errors = merged_results + .flat_map { |result| result.definition_conflicts.to_a } + .group_by(&:name) + .map do |name, deprecated_elements| + <<~EOS + The schema definition of `#{name}` has conflicts. To resolve the conflict, remove the unneeded definitions from the following: + + #{format_deprecated_elements(deprecated_elements)} + EOS + end + + errors = missing_field_errors + missing_type_errors + missing_necessary_field_errors + definition_conflict_errors + return if errors.empty? + + abort errors.join("\n\n") + end + + def report_json_schema_merge_warnings + schema_results = json_ingestion_schema_definition_results + unused_elements = schema_results.unused_deprecated_elements + return if unused_elements.empty? + + @output.puts <<~EOS + The schema definition has #{unused_elements.size} unneeded reference(s) to deprecated schema elements. These can all be safely deleted: + + #{format_deprecated_elements(unused_elements)} + + EOS + end + + def format_deprecated_elements(deprecated_elements) + descriptions = deprecated_elements + .sort_by { |e| [e.defined_at.path, e.defined_at.lineno] } + .map(&:description) + .uniq + + descriptions.each.with_index(1).map { |desc, idx| "#{idx}. #{desc}" }.join("\n") + end + + def missing_field_error_for(qualified_field, json_schema_versions) + type, field = qualified_field.split(".") + + <<~EOS + The `#{qualified_field}` field (which existed in #{describe_json_schema_versions(json_schema_versions, "and")}) no longer exists in the current schema definition. + ElasticGraph cannot guess what it should do with this field's data when ingesting events at #{old_versions(json_schema_versions)}. + To continue, do one of the following: + + 1. If the `#{qualified_field}` field has been renamed, indicate this by calling `field.renamed_from "#{field}"` on the renamed field. + 2. If the `#{qualified_field}` field has been dropped, indicate this by calling `type.deleted_field "#{field}"` on the `#{type}` type. + 3. Alternately, if no publishers or in-flight events use #{describe_json_schema_versions(json_schema_versions, "or")}, delete #{files_noun_phrase(json_schema_versions)} from `#{JSON_SCHEMAS_BY_VERSION_DIRECTORY}`, and no further changes are required. + EOS + end + + def missing_type_error_for(type, json_schema_versions) + <<~EOS + The `#{type}` type (which existed in #{describe_json_schema_versions(json_schema_versions, "and")}) no longer exists in the current schema definition. + ElasticGraph cannot guess what it should do with this type's data when ingesting events at #{old_versions(json_schema_versions)}. + To continue, do one of the following: + + 1. If the `#{type}` type has been renamed, indicate this by calling `type.renamed_from "#{type}"` on the renamed type. + 2. If the `#{type}` field has been dropped, indicate this by calling `schema.deleted_type "#{type}"` on the schema. + 3. Alternately, if no publishers or in-flight events use #{describe_json_schema_versions(json_schema_versions, "or")}, delete #{files_noun_phrase(json_schema_versions)} from `#{JSON_SCHEMAS_BY_VERSION_DIRECTORY}`, and no further changes are required. + EOS + end + + def missing_necessary_field_error_for(field, json_schema_versions) + path = field.fully_qualified_path.split(".").last + # :nocov: -- we only cover one side of this ternary. + has_or_have = (json_schema_versions.size == 1) ? "has" : "have" + # :nocov: + + <<~EOS + #{describe_json_schema_versions(json_schema_versions, "and")} #{has_or_have} no field that maps to the #{field.field_type} field path of `#{field.fully_qualified_path}`. + Since the field path is required for #{field.field_type}, ElasticGraph cannot ingest events that lack it. To continue, do one of the following: + + 1. If the `#{field.fully_qualified_path}` field has been renamed, indicate this by calling `field.renamed_from "#{path}"` on the renamed field rather than using `deleted_field`. + 2. Alternately, if no publishers or in-flight events use #{describe_json_schema_versions(json_schema_versions, "or")}, delete #{files_noun_phrase(json_schema_versions)} from `#{JSON_SCHEMAS_BY_VERSION_DIRECTORY}`, and no further changes are required. + EOS + end + + def describe_json_schema_versions(json_schema_versions, conjunction) + json_schema_versions = json_schema_versions.sort + + # Steep doesn't support pattern matching yet, so have to skip type checking here. + __skip__ = case json_schema_versions + in [single_version] + "JSON schema version #{single_version}" + in [version1, version2] + "JSON schema versions #{version1} #{conjunction} #{version2}" + else + *versions, last_version = json_schema_versions + "JSON schema versions #{versions.join(", ")}, #{conjunction} #{last_version}" + end + end + + def old_versions(json_schema_versions) + return "this old version" if json_schema_versions.size == 1 + "these old versions" + end + + def files_noun_phrase(json_schema_versions) + return "its file" if json_schema_versions.size == 1 + "their files" + end + + def new_versioned_json_schema_artifact(desired_contents) + # File name depends on the schema_version field in the json schema. + schema_version = desired_contents[JSON_SCHEMA_VERSION_KEY] + + new_yaml_artifact( + ::File.join(JSON_SCHEMAS_BY_VERSION_DIRECTORY, "v#{schema_version}.yaml"), + desired_contents, + extra_comment_lines: [ + "This JSON schema file contains internal ElasticGraph metadata and should be considered private.", + "The unversioned JSON schema file is public and intended to be provided to publishers." + ] + ) + end + end + end + end +end diff --git a/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/schema_elements/enum_type_extension.rb b/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/schema_elements/enum_type_extension.rb new file mode 100644 index 000000000..68eb12abd --- /dev/null +++ b/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/schema_elements/enum_type_extension.rb @@ -0,0 +1,24 @@ +# Copyright 2024 - 2026 Block, Inc. +# +# Use of this source code is governed by an MIT-style +# license that can be found in the LICENSE file or at +# https://opensource.org/licenses/MIT. +# +# frozen_string_literal: true + +module ElasticGraph + module JSONIngestion + module SchemaDefinition + module SchemaElements + # Extends enum types with JSON schema behavior. + module EnumTypeExtension + # @private + def configure_derived_scalar_type(scalar_type) + super + scalar_type.json_schema type: "string" + end + end + end + end + end +end diff --git a/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/schema_elements/field_extension.rb b/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/schema_elements/field_extension.rb new file mode 100644 index 000000000..6cef4b048 --- /dev/null +++ b/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/schema_elements/field_extension.rb @@ -0,0 +1,71 @@ +# Copyright 2024 - 2026 Block, Inc. +# +# Use of this source code is governed by an MIT-style +# license that can be found in the LICENSE file or at +# https://opensource.org/licenses/MIT. +# +# frozen_string_literal: true + +require "elastic_graph/json_ingestion/schema_definition/indexing/field_reference" +require "elastic_graph/json_ingestion/schema_definition/json_schema_option_validator" + +module ElasticGraph + module JSONIngestion + module SchemaDefinition + # Namespace for schema-element extensions that contribute JSON schema generation and validation. + module SchemaElements + # Extends schema-definition fields with JSON schema validation behavior. + module FieldExtension + # @return [Hash] JSON schema options for this field + def json_schema_options + @json_schema_options ||= {} + end + + # @return [Boolean] whether this field has been marked as non-nullable in the JSON schema + def non_nullable_in_json_schema + @non_nullable_in_json_schema || false + end + + # Sets whether this field is non-nullable in the JSON schema. + # @param value [Boolean] true to make the field non-nullable + attr_writer :non_nullable_in_json_schema + + # Configures JSON schema options for this field. + # + # @param nullable [Boolean, nil] set to `false` to make this field non-nullable in the JSON schema + # @param options [Hash] additional JSON schema options + # @return [void] + def json_schema(nullable: nil, **options) + if options.key?(:type) + raise Errors::SchemaError, "Cannot override JSON schema type of field `#{name}` with `#{options.fetch(:type)}`" + end + + case nullable + when true + raise Errors::SchemaError, "`nullable: true` is not allowed on a field--just declare the GraphQL field as being nullable (no `!` suffix) instead." + when false + @non_nullable_in_json_schema = true + end + + JSONSchemaOptionValidator.validate!(self, options) + json_schema_options.update(options) + end + + # @private + def to_indexing_field_reference + reference = super + return nil unless reference + + type_for_json_schema = (non_nullable_in_json_schema ? type.wrap_non_null : type) # : ElasticGraph::SchemaDefinition::SchemaElements::TypeReference & TypeReferenceExtension + + Indexing::FieldReference.new( + field_reference: reference.with(type: type_for_json_schema), + json_schema_layers: type_for_json_schema.json_schema_layers, + json_schema_customizations: json_schema_options + ) + end + end + end + end + end +end diff --git a/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/schema_elements/object_interface_extension.rb b/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/schema_elements/object_interface_extension.rb new file mode 100644 index 000000000..8a207e273 --- /dev/null +++ b/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/schema_elements/object_interface_extension.rb @@ -0,0 +1,44 @@ +# Copyright 2024 - 2026 Block, Inc. +# +# Use of this source code is governed by an MIT-style +# license that can be found in the LICENSE file or at +# https://opensource.org/licenses/MIT. +# +# frozen_string_literal: true + +require "elastic_graph/json_ingestion/schema_definition/json_schema_option_validator" + +module ElasticGraph + module JSONIngestion + module SchemaDefinition + module SchemaElements + # Extends object and interface types with JSON schema behavior. + module ObjectInterfaceExtension + # @return [Hash] JSON schema options for this type + def json_schema_options + @json_schema_options ||= {} + end + + # Configures JSON schema options for this object or interface type. + # + # @param options [Hash] JSON schema options + # @return [void] + def json_schema(**options) + JSONSchemaOptionValidator.validate!(self, options) + json_schema_options.update(options) + end + + # @private + def to_indexing_field_type + # @type var field_type: (ElasticGraph::SchemaDefinition::Indexing::FieldType::Object & Indexing::FieldType::ObjectExtension) | ElasticGraph::SchemaDefinition::Indexing::FieldType::Union + field_type = _ = super + + return field_type if field_type.is_a?(ElasticGraph::SchemaDefinition::Indexing::FieldType::Union) + + field_type.with_json_schema_options(json_schema_options) + end + end + end + end + end +end diff --git a/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/schema_elements/scalar_type_extension.rb b/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/schema_elements/scalar_type_extension.rb new file mode 100644 index 000000000..29d02c2b9 --- /dev/null +++ b/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/schema_elements/scalar_type_extension.rb @@ -0,0 +1,71 @@ +# Copyright 2024 - 2026 Block, Inc. +# +# Use of this source code is governed by an MIT-style +# license that can be found in the LICENSE file or at +# https://opensource.org/licenses/MIT. +# +# frozen_string_literal: true + +require "elastic_graph/json_ingestion/schema_definition/json_schema_option_validator" + +module ElasticGraph + module JSONIngestion + module SchemaDefinition + module SchemaElements + # Extends scalar types with JSON schema validation and serialization behavior. + module ScalarTypeExtension + # @return [Hash] JSON schema options for this scalar type + def json_schema_options + @json_schema_options ||= {} + end + + # Configures JSON schema options for this scalar type. + # + # @param options [Hash] JSON schema options + # @return [void] + def json_schema(**options) + JSONSchemaOptionValidator.validate!(self, options) + json_schema_options.update(options) + self.runtime_metadata = runtime_metadata.with(grouping_missing_value_placeholder: inferred_grouping_missing_value_placeholder) unless grouping_missing_value_placeholder_overridden + end + + # Validates that json_schema has been configured on this scalar type. + # + # @raise [Errors::SchemaError] if json_schema has not been configured + # @return [void] + def validate_json_schema_configuration! + return unless json_schema_options.empty? + + raise Errors::SchemaError, "Scalar types require `json_schema` to be configured, but `#{name}` lacks `json_schema`." + end + + private + + def inferred_grouping_missing_value_placeholder + case mapping_type + when "long" + # It is only safe to use NaN for a long when the long's range is safe to coerce to a float + # without loss of precision. This is because using NaN as the missing value will cause + # the datastore to coerce the other bucket keys to float. + # JSON schema min/max only constrains newly indexed values, not existing data that may fall + # outside the range before the constraints were added. In that edge case, users can set + # `grouping_missing_value_placeholder` to `nil`. + if (json_schema_options[:minimum] || LONG_STRING_MIN) >= JSON_SAFE_LONG_MIN && + (json_schema_options[:maximum] || LONG_STRING_MAX) <= JSON_SAFE_LONG_MAX + inferred_numeric_placeholder_for_integer_type + end + when "unsigned_long" + # Similar to the checks above for long except we only need to check the max + # (since the min is zero even if not specified). + if (json_schema_options[:maximum] || LONG_STRING_MAX) <= JSON_SAFE_LONG_MAX + inferred_numeric_placeholder_for_integer_type + end + else + super + end + end + end + end + end + end +end diff --git a/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/schema_elements/type_reference_extension.rb b/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/schema_elements/type_reference_extension.rb new file mode 100644 index 000000000..599a3df4d --- /dev/null +++ b/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/schema_elements/type_reference_extension.rb @@ -0,0 +1,49 @@ +# Copyright 2024 - 2026 Block, Inc. +# +# Use of this source code is governed by an MIT-style +# license that can be found in the LICENSE file or at +# https://opensource.org/licenses/MIT. +# +# frozen_string_literal: true + +module ElasticGraph + module JSONIngestion + module SchemaDefinition + module SchemaElements + # Extends schema-definition type references with JSON schema layer calculation. + # + # @private + module TypeReferenceExtension + # Returns all JSON schema array/nullable layers of a type, from outermost to innermost. + # For example, `[[Int]]` returns `[:nullable, :array, :nullable, :array, :nullable]`. + def json_schema_layers + @json_schema_layers ||= begin + layers, inner_type = peel_json_schema_layers_once + + if layers.empty? || inner_type == self + layers + else + layers + inner_type.json_schema_layers + end + end + end + + private + + def peel_json_schema_layers_once + if list? + inner_type = unwrap_list # : ElasticGraph::SchemaDefinition::SchemaElements::TypeReference & TypeReferenceExtension + return [[:array], inner_type] if non_null? + return [[:nullable, :array], inner_type] + end + + no_layers = [] # : ::Array[ElasticGraph::SchemaDefinition::jsonSchemaLayer] + inner_type = unwrap_non_null # : ElasticGraph::SchemaDefinition::SchemaElements::TypeReference & TypeReferenceExtension + return [no_layers, inner_type] if non_null? + [[:nullable], self] + end + end + end + end + end +end diff --git a/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/state_extension.rb b/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/state_extension.rb new file mode 100644 index 000000000..52427e0c8 --- /dev/null +++ b/elasticgraph-json_ingestion/lib/elastic_graph/json_ingestion/schema_definition/state_extension.rb @@ -0,0 +1,31 @@ +# Copyright 2024 - 2026 Block, Inc. +# +# Use of this source code is governed by an MIT-style +# license that can be found in the LICENSE file or at +# https://opensource.org/licenses/MIT. +# +# frozen_string_literal: true + +module ElasticGraph + module JSONIngestion + module SchemaDefinition + # Extension module applied to `ElasticGraph::SchemaDefinition::State` to support JSON ingestion state. + # + # @private + module StateExtension + # @dynamic json_schema_version, json_schema_version= + # @dynamic json_schema_version_setter_location, json_schema_version_setter_location= + # @dynamic allow_omitted_json_schema_fields, allow_omitted_json_schema_fields= + # @dynamic allow_extra_json_schema_fields, allow_extra_json_schema_fields= + attr_accessor :json_schema_version, :json_schema_version_setter_location, :allow_omitted_json_schema_fields, :allow_extra_json_schema_fields + + def self.extended(state) + state.json_schema_version = nil + state.json_schema_version_setter_location = nil + state.allow_omitted_json_schema_fields = false + state.allow_extra_json_schema_fields = true + end + end + end + end +end diff --git a/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/api_extension.rbs b/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/api_extension.rbs new file mode 100644 index 000000000..53d8c9070 --- /dev/null +++ b/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/api_extension.rbs @@ -0,0 +1,18 @@ +module ElasticGraph + module JSONIngestion + module SchemaDefinition + module APIExtension: ::ElasticGraph::SchemaDefinition::API + BUILT_IN_SCALAR_JSON_SCHEMA_OPTIONS_BY_NAME: ::Hash[::String, ::Hash[::Symbol, untyped]] + + def json_schema_version: (::Integer) -> void + def json_schema_strictness: (?allow_omitted_fields: bool, ?allow_extra_fields: bool) -> void + + def self.extended: (::ElasticGraph::SchemaDefinition::API & APIExtension) -> void + + private + + def json_ingestion_state: () -> (::ElasticGraph::SchemaDefinition::State & StateExtension) + end + end + end +end diff --git a/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/factory_extension.rbs b/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/factory_extension.rbs new file mode 100644 index 000000000..0eb4732df --- /dev/null +++ b/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/factory_extension.rbs @@ -0,0 +1,17 @@ +module ElasticGraph + module JSONIngestion + module SchemaDefinition + module FactoryExtension: ::ElasticGraph::SchemaDefinition::Factory + def new_enum_type: (::String) ?{ (::ElasticGraph::SchemaDefinition::SchemaElements::EnumType & SchemaElements::EnumTypeExtension) -> void } -> (::ElasticGraph::SchemaDefinition::SchemaElements::EnumType & SchemaElements::EnumTypeExtension) + def new_field: (**untyped) ?{ (::ElasticGraph::SchemaDefinition::SchemaElements::Field & SchemaElements::FieldExtension) -> void } -> (::ElasticGraph::SchemaDefinition::SchemaElements::Field & SchemaElements::FieldExtension) + def new_index: (::String, ::Hash[::String, untyped], ::ElasticGraph::SchemaDefinition::indexableType) ?{ (::ElasticGraph::SchemaDefinition::Indexing::Index & Indexing::IndexExtension) -> void } -> (::ElasticGraph::SchemaDefinition::Indexing::Index & Indexing::IndexExtension) + def new_interface_type: (::String) ?{ (::ElasticGraph::SchemaDefinition::SchemaElements::InterfaceType & SchemaElements::ObjectInterfaceExtension) -> void } -> (::ElasticGraph::SchemaDefinition::SchemaElements::InterfaceType & SchemaElements::ObjectInterfaceExtension) + def new_object_type: (::String) ?{ (::ElasticGraph::SchemaDefinition::SchemaElements::ObjectType & SchemaElements::ObjectInterfaceExtension) -> void } -> (::ElasticGraph::SchemaDefinition::SchemaElements::ObjectType & SchemaElements::ObjectInterfaceExtension) + def new_scalar_type: (::String) ?{ (::ElasticGraph::SchemaDefinition::SchemaElements::ScalarType & SchemaElements::ScalarTypeExtension) -> void } -> (::ElasticGraph::SchemaDefinition::SchemaElements::ScalarType & SchemaElements::ScalarTypeExtension) + def new_type_reference: (::String) -> ::ElasticGraph::SchemaDefinition::SchemaElements::TypeReference + def new_results: () -> ::ElasticGraph::SchemaDefinition::Results + def new_schema_artifact_manager: (**untyped) -> ::ElasticGraph::SchemaDefinition::SchemaArtifactManager + end + end + end +end diff --git a/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/indexing/field.rbs b/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/indexing/field.rbs new file mode 100644 index 000000000..5061987bf --- /dev/null +++ b/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/indexing/field.rbs @@ -0,0 +1,32 @@ +module ElasticGraph + module JSONIngestion + module SchemaDefinition + module Indexing + module FieldExtension: ::ElasticGraph::SchemaDefinition::Indexing::Field + JSON_SCHEMA_OVERRIDES_BY_MAPPING_TYPE: ::Hash[::String, ::Hash[::String, untyped]] + + @json_schema: ::Hash[::String, untyped]? + @json_schema_layers: ::ElasticGraph::SchemaDefinition::jsonSchemaLayersArray + @json_schema_customizations: ::Hash[::Symbol, untyped] + + def json_schema_customizations: () -> ::Hash[::Symbol, untyped] + def with_json_schema: ( + json_schema_layers: ::ElasticGraph::SchemaDefinition::jsonSchemaLayersArray, + json_schema_customizations: ::Hash[::Symbol, untyped] + ) -> (::ElasticGraph::SchemaDefinition::Indexing::Field & FieldExtension) + def json_schema: () -> ::Hash[::String, untyped] + def json_schema_metadata: () -> JSONSchemaFieldMetadata + def nullable?: () -> bool + + private + + def json_schema_layers: () -> ::ElasticGraph::SchemaDefinition::jsonSchemaLayersArray + def inner_json_schema: () -> ::Hash[::String, untyped] + def outer_json_schema_customizations: () -> ::Hash[::String, untyped] + def user_specified_json_schema_customizations_go_on_outside?: () -> bool + def process_layer: (::ElasticGraph::SchemaDefinition::jsonSchemaLayer, ::Hash[::String, untyped]) -> ::Hash[::String, untyped] + end + end + end + end +end diff --git a/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/indexing/field_reference.rbs b/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/indexing/field_reference.rbs new file mode 100644 index 000000000..cb6b212c5 --- /dev/null +++ b/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/indexing/field_reference.rbs @@ -0,0 +1,27 @@ +module ElasticGraph + module JSONIngestion + module SchemaDefinition + module Indexing + class FieldReference + attr_reader field_reference: ::ElasticGraph::SchemaDefinition::Indexing::FieldReference + attr_reader json_schema_layers: ::ElasticGraph::SchemaDefinition::jsonSchemaLayersArray + attr_reader json_schema_customizations: ::Hash[::Symbol, untyped] + + def initialize: ( + field_reference: ::ElasticGraph::SchemaDefinition::Indexing::FieldReference, + json_schema_layers: ::ElasticGraph::SchemaDefinition::jsonSchemaLayersArray, + json_schema_customizations: ::Hash[::Symbol, untyped] + ) -> void + + def with: ( + ?field_reference: ::ElasticGraph::SchemaDefinition::Indexing::FieldReference, + ?json_schema_layers: ::ElasticGraph::SchemaDefinition::jsonSchemaLayersArray, + ?json_schema_customizations: ::Hash[::Symbol, untyped] + ) -> FieldReference + + def resolve: () -> (::ElasticGraph::SchemaDefinition::Indexing::Field & FieldExtension)? + end + end + end + end +end diff --git a/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/indexing/field_type.rbs b/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/indexing/field_type.rbs new file mode 100644 index 000000000..96995e7fc --- /dev/null +++ b/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/indexing/field_type.rbs @@ -0,0 +1,13 @@ +module ElasticGraph + module JSONIngestion + module SchemaDefinition + module Indexing + interface _JSONFieldType + def json_schema_field_metadata_by_field_name: () -> ::Hash[::String, JSONSchemaFieldMetadata] + def format_field_json_schema_customizations: (::Hash[::String, untyped]) -> ::Hash[::String, untyped] + def to_json_schema: () -> ::Hash[::String, untyped] + end + end + end + end +end diff --git a/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/indexing/field_type/enum_extension.rbs b/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/indexing/field_type/enum_extension.rbs new file mode 100644 index 000000000..88147e1d0 --- /dev/null +++ b/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/indexing/field_type/enum_extension.rbs @@ -0,0 +1,13 @@ +module ElasticGraph + module JSONIngestion + module SchemaDefinition + module Indexing + module FieldType + module EnumExtension: ::ElasticGraph::SchemaDefinition::Indexing::FieldType::Enum + include _JSONFieldType + end + end + end + end + end +end diff --git a/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/indexing/field_type/object_extension.rbs b/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/indexing/field_type/object_extension.rbs new file mode 100644 index 000000000..ea21f0f48 --- /dev/null +++ b/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/indexing/field_type/object_extension.rbs @@ -0,0 +1,24 @@ +module ElasticGraph + module JSONIngestion + module SchemaDefinition + module Indexing + module FieldType + module ObjectExtension: ::ElasticGraph::SchemaDefinition::Indexing::FieldType::Object + include _JSONFieldType + + @json_schema_options: ::Hash[::Symbol, untyped] + @to_json_schema: ::Hash[::String, untyped]? + + def json_schema_options: () -> ::Hash[::Symbol, untyped] + def with_json_schema_options: (::Hash[::Symbol, untyped]) -> (::ElasticGraph::SchemaDefinition::Indexing::FieldType::Object & ObjectExtension) + + private + + def json_schema_typename_field: () -> ::Hash[::String, ::Hash[::String, untyped]] + def validate_sourced_fields_have_no_json_schema_overrides: (::Array[::ElasticGraph::SchemaDefinition::Indexing::Field & FieldExtension]) -> void + end + end + end + end + end +end diff --git a/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/indexing/field_type/scalar_extension.rbs b/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/indexing/field_type/scalar_extension.rbs new file mode 100644 index 000000000..139241fbb --- /dev/null +++ b/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/indexing/field_type/scalar_extension.rbs @@ -0,0 +1,13 @@ +module ElasticGraph + module JSONIngestion + module SchemaDefinition + module Indexing + module FieldType + module ScalarExtension: ::ElasticGraph::SchemaDefinition::Indexing::FieldType::Scalar + include _JSONFieldType + end + end + end + end + end +end diff --git a/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/indexing/field_type/union_extension.rbs b/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/indexing/field_type/union_extension.rbs new file mode 100644 index 000000000..9817d218b --- /dev/null +++ b/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/indexing/field_type/union_extension.rbs @@ -0,0 +1,13 @@ +module ElasticGraph + module JSONIngestion + module SchemaDefinition + module Indexing + module FieldType + module UnionExtension: ::ElasticGraph::SchemaDefinition::Indexing::FieldType::Union + include _JSONFieldType + end + end + end + end + end +end diff --git a/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/indexing/index_extension.rbs b/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/indexing/index_extension.rbs new file mode 100644 index 000000000..8084031eb --- /dev/null +++ b/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/indexing/index_extension.rbs @@ -0,0 +1,20 @@ +module ElasticGraph + module JSONIngestion + module SchemaDefinition + module Indexing + interface _JSONFieldPath + def last_part: () -> (::ElasticGraph::SchemaDefinition::SchemaElements::Field & SchemaElements::FieldExtension) + def path_parts: () -> ::Array[::ElasticGraph::SchemaDefinition::SchemaElements::Field & SchemaElements::FieldExtension] + def type: () -> ::ElasticGraph::SchemaDefinition::SchemaElements::TypeReference + def full_description: () -> ::String + end + + module IndexExtension: ::ElasticGraph::SchemaDefinition::Indexing::Index + def require_id_in_json_schema: () -> void + def rollover: (::Symbol, ::String) -> void + def route_with: (::String) -> void + end + end + end + end +end diff --git a/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/json_schema_option_validator.rbs b/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/json_schema_option_validator.rbs new file mode 100644 index 000000000..c4e10cb4d --- /dev/null +++ b/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/json_schema_option_validator.rbs @@ -0,0 +1,9 @@ +module ElasticGraph + module JSONIngestion + module SchemaDefinition + module JSONSchemaOptionValidator + def self.validate!: (untyped, ::Hash[::Symbol, untyped]) -> void + end + end + end +end diff --git a/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/results_extension.rbs b/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/results_extension.rbs new file mode 100644 index 000000000..e7eac97be --- /dev/null +++ b/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/results_extension.rbs @@ -0,0 +1,30 @@ +module ElasticGraph + module JSONIngestion + module SchemaDefinition + module ResultsExtension : ::ElasticGraph::SchemaDefinition::Results + def json_schemas_for: (::Integer) -> ::Hash[::String, untyped] + def available_json_schema_versions: () -> ::Set[::Integer] + def latest_json_schema_version: () -> ::Integer + def json_schema_version_setter_location: () -> ::Thread::Backtrace::Location? + def json_schema_field_metadata_by_type_and_field_name: () -> ::Hash[::String, ::Hash[::String, Indexing::JSONSchemaFieldMetadata]] + def current_public_json_schema: () -> ::Hash[::String, untyped] + def merge_field_metadata_into_json_schema: (::Hash[::String, untyped]) -> Indexing::JSONSchemaWithMetadata + def unused_deprecated_elements: () -> ::Set[::ElasticGraph::SchemaDefinition::SchemaElements::DeprecatedElement] + + private + + @latest_versioned_json_schema: ::Hash[::String, untyped]? + @available_json_schema_versions: ::Set[::Integer]? + @json_schema_field_metadata_by_type_and_field_name: ::Hash[::String, ::Hash[::String, Indexing::JSONSchemaFieldMetadata]]? + @current_public_json_schema: ::Hash[::String, untyped]? + @json_schema_with_metadata_merger: Indexing::JSONSchemaWithMetadata::Merger? + @json_schema_indexing_field_types_by_name: ::Hash[::String, ::ElasticGraph::SchemaDefinition::Indexing::_FieldType]? + + def json_ingestion_state: () -> (::ElasticGraph::SchemaDefinition::State & StateExtension) + def json_schema_with_metadata_merger: () -> Indexing::JSONSchemaWithMetadata::Merger + def build_public_json_schema: () -> ::Hash[::String, untyped] + def json_schema_indexing_field_types_by_name: () -> ::Hash[::String, ::ElasticGraph::SchemaDefinition::Indexing::_FieldType] + end + end + end +end diff --git a/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/schema_artifact_manager_extension.rbs b/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/schema_artifact_manager_extension.rbs new file mode 100644 index 000000000..e4626d8d7 --- /dev/null +++ b/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/schema_artifact_manager_extension.rbs @@ -0,0 +1,27 @@ +module ElasticGraph + module JSONIngestion + module SchemaDefinition + module SchemaArtifactManagerExtension : ::ElasticGraph::SchemaDefinition::SchemaArtifactManager + private + + @json_schemas_artifact: ::ElasticGraph::SchemaDefinition::SchemaArtifact[untyped] + + def json_ingestion_schema_definition_results: () -> (::ElasticGraph::SchemaDefinition::Results & ResultsExtension) + def artifacts_from_schema_def: () -> ::Array[::ElasticGraph::SchemaDefinition::SchemaArtifact[untyped]] + def json_schemas_artifact: () -> ::ElasticGraph::SchemaDefinition::SchemaArtifact[::Hash[::String, untyped]] + def check_if_needs_json_schema_version_bump: () { (::Integer) -> void } -> void + def build_desired_versioned_json_schemas: (::Hash[::String, untyped]) -> ::Hash[::Integer, ::Hash[::String, untyped]] + def report_json_schema_merge_errors: (::Array[Indexing::JSONSchemaWithMetadata]) -> void + def report_json_schema_merge_warnings: () -> void + def format_deprecated_elements: (::Enumerable[::ElasticGraph::SchemaDefinition::SchemaElements::DeprecatedElement]) -> ::String + def missing_field_error_for: (::String, ::Array[::Integer]) -> ::String + def missing_type_error_for: (::String, ::Array[::Integer]) -> ::String + def missing_necessary_field_error_for: (Indexing::JSONSchemaWithMetadata::MissingNecessaryField, ::Array[::Integer]) -> ::String + def describe_json_schema_versions: (::Array[::Integer], ::String) -> ::String + def old_versions: (::Array[::Integer]) -> ::String + def files_noun_phrase: (::Array[::Integer]) -> ::String + def new_versioned_json_schema_artifact: (::Hash[::String, untyped]) -> ::ElasticGraph::SchemaDefinition::SchemaArtifact[::Hash[::String, untyped]] + end + end + end +end diff --git a/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/schema_elements/enum_type_extension.rbs b/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/schema_elements/enum_type_extension.rbs new file mode 100644 index 000000000..12d81c14b --- /dev/null +++ b/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/schema_elements/enum_type_extension.rbs @@ -0,0 +1,13 @@ +module ElasticGraph + module JSONIngestion + module SchemaDefinition + module SchemaElements + module EnumTypeExtension: ::ElasticGraph::SchemaDefinition::SchemaElements::EnumType + private + + def configure_derived_scalar_type: (::ElasticGraph::SchemaDefinition::SchemaElements::ScalarType & ScalarTypeExtension) -> void + end + end + end + end +end diff --git a/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/schema_elements/field_extension.rbs b/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/schema_elements/field_extension.rbs new file mode 100644 index 000000000..0236ddb92 --- /dev/null +++ b/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/schema_elements/field_extension.rbs @@ -0,0 +1,17 @@ +module ElasticGraph + module JSONIngestion + module SchemaDefinition + module SchemaElements + module FieldExtension: ::ElasticGraph::SchemaDefinition::SchemaElements::Field + @json_schema_options: ::Hash[::Symbol, untyped] + @non_nullable_in_json_schema: bool + + def json_schema_options: () -> ::Hash[::Symbol, untyped] + def non_nullable_in_json_schema: () -> bool + def json_schema: (?nullable: bool?, **untyped) -> void + def to_indexing_field_reference: () -> Indexing::FieldReference? + end + end + end + end +end diff --git a/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/schema_elements/object_interface_extension.rbs b/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/schema_elements/object_interface_extension.rbs new file mode 100644 index 000000000..5fcf3774f --- /dev/null +++ b/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/schema_elements/object_interface_extension.rbs @@ -0,0 +1,15 @@ +module ElasticGraph + module JSONIngestion + module SchemaDefinition + module SchemaElements + module ObjectInterfaceExtension: ::ElasticGraph::SchemaDefinition::SchemaElements::TypeWithSubfields + @json_schema_options: ::Hash[::Symbol, untyped] + + def json_schema_options: () -> ::Hash[::Symbol, untyped] + def json_schema: (**untyped) -> void + def to_indexing_field_type: () -> ((::ElasticGraph::SchemaDefinition::Indexing::FieldType::Object & Indexing::FieldType::ObjectExtension) | ::ElasticGraph::SchemaDefinition::Indexing::FieldType::Union) + end + end + end + end +end diff --git a/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/schema_elements/scalar_type_extension.rbs b/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/schema_elements/scalar_type_extension.rbs new file mode 100644 index 000000000..c659b4ec8 --- /dev/null +++ b/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/schema_elements/scalar_type_extension.rbs @@ -0,0 +1,19 @@ +module ElasticGraph + module JSONIngestion + module SchemaDefinition + module SchemaElements + module ScalarTypeExtension: ::ElasticGraph::SchemaDefinition::SchemaElements::ScalarType + @json_schema_options: ::Hash[::Symbol, untyped] + + def json_schema_options: () -> ::Hash[::Symbol, untyped] + def json_schema: (**untyped) -> void + def validate_json_schema_configuration!: () -> void + + private + + def inferred_grouping_missing_value_placeholder: () -> ::String? + end + end + end + end +end diff --git a/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/schema_elements/type_reference_extension.rbs b/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/schema_elements/type_reference_extension.rbs new file mode 100644 index 000000000..4ff5b792b --- /dev/null +++ b/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/schema_elements/type_reference_extension.rbs @@ -0,0 +1,16 @@ +module ElasticGraph + module JSONIngestion + module SchemaDefinition + module SchemaElements + module TypeReferenceExtension: ::ElasticGraph::SchemaDefinition::SchemaElements::TypeReference + @json_schema_layers: ::ElasticGraph::SchemaDefinition::jsonSchemaLayersArray? + def json_schema_layers: () -> ::ElasticGraph::SchemaDefinition::jsonSchemaLayersArray + + private + + def peel_json_schema_layers_once: () -> [::ElasticGraph::SchemaDefinition::jsonSchemaLayersArray, (::ElasticGraph::SchemaDefinition::SchemaElements::TypeReference & TypeReferenceExtension)] + end + end + end + end +end diff --git a/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/state_extension.rbs b/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/state_extension.rbs new file mode 100644 index 000000000..a11f759cc --- /dev/null +++ b/elasticgraph-json_ingestion/sig/elastic_graph/json_ingestion/schema_definition/state_extension.rbs @@ -0,0 +1,14 @@ +module ElasticGraph + module JSONIngestion + module SchemaDefinition + module StateExtension: ::ElasticGraph::SchemaDefinition::State + attr_accessor json_schema_version: ::Integer? + attr_accessor json_schema_version_setter_location: ::Thread::Backtrace::Location? + attr_accessor allow_omitted_json_schema_fields: bool + attr_accessor allow_extra_json_schema_fields: bool + + def self.extended: (::ElasticGraph::SchemaDefinition::State & StateExtension) -> void + end + end + end +end diff --git a/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_elements/enum_type.rb b/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_elements/enum_type.rb index 0bdb27b60..9e51ed105 100644 --- a/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_elements/enum_type.rb +++ b/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_elements/enum_type.rb @@ -145,7 +145,7 @@ def derived_graphql_types derived_scalar_types = schema_def_state.factory.new_scalar_type(name) do |t| t.mapping type: "keyword" - t.json_schema type: "string" + configure_derived_scalar_type(t) t.graphql_only graphql_only? end.derived_graphql_types @@ -156,6 +156,12 @@ def derived_graphql_types end end + # Hook for extensions to customize the scalar type derived from an enum type. + # @api private + def configure_derived_scalar_type(scalar_type) + scalar_type.json_schema type: "string" + end + # @return [Indexing::FieldType::Enum] indexing representation of this enum type def to_indexing_field_type Indexing::FieldType::Enum.new(values_by_name.keys) diff --git a/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/state.rb b/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/state.rb index ccc4e76c5..2942671d8 100644 --- a/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/state.rb +++ b/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/state.rb @@ -12,8 +12,8 @@ require "elastic_graph/schema_definition/mixins/has_readable_to_s_and_inspect" require "elastic_graph/schema_definition/schema_elements/enum_value_namer" require "elastic_graph/schema_definition/schema_elements/field_path" -require "elastic_graph/schema_definition/schema_elements/type_namer" require "elastic_graph/schema_definition/schema_elements/sub_aggregation_path" +require "elastic_graph/schema_definition/schema_elements/type_namer" module ElasticGraph module SchemaDefinition @@ -41,6 +41,7 @@ class State < Struct.new( :deleted_types_by_old_name, :renamed_fields_by_type_name_and_old_field_name, :deleted_fields_by_type_name_and_old_field_name, + :reserved_type_names, :json_schema_version, :json_schema_version_setter_location, :enforce_json_schema_version, @@ -92,6 +93,7 @@ def self.with( deleted_types_by_old_name: {}, renamed_fields_by_type_name_and_old_field_name: ::Hash.new { |h, k| h[k] = {} }, deleted_fields_by_type_name_and_old_field_name: ::Hash.new { |h, k| h[k] = {} }, + reserved_type_names: RESERVED_TYPE_NAMES.dup, json_schema_version_setter_location: nil, json_schema_version: nil, enforce_json_schema_version: true, @@ -231,7 +233,7 @@ def field_path_resolver def register_type(type, additional_type_index = nil) name = (_ = type).name - if RESERVED_TYPE_NAMES.include?(name) + if reserved_type_names.include?(name) raise Errors::SchemaError, "`#{name}` cannot be used as a schema type because it is a reserved name." end diff --git a/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/indexing/index.rbs b/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/indexing/index.rbs index 0dc296381..4a8b4c1d3 100644 --- a/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/indexing/index.rbs +++ b/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/indexing/index.rbs @@ -8,7 +8,12 @@ module ElasticGraph attr_reader rollover_config: RolloverConfig? attr_reader has_had_multiple_sources_flag: bool attr_reader indexed_type: indexableType + attr_reader schema_def_state: State + HAS_NON_WHITE_SPACE_REGEX: ::String + + def rollover: (::Symbol, ::String) -> void + def route_with: (::String) -> void def uses_custom_routing?: () -> bool def to_index_config: () -> ::Hash[::String, untyped] def to_index_template_config: () -> ::Hash[::String, untyped] @@ -16,6 +21,7 @@ module ElasticGraph private + def public_field_path: (::String, explanation: ::String) -> SchemaElements::FieldPath def date_and_datetime_types: () -> ::Array[::String] end end diff --git a/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/schema_elements/enum_type.rbs b/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/schema_elements/enum_type.rbs index 518792bcb..43b6472ee 100644 --- a/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/schema_elements/enum_type.rbs +++ b/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/schema_elements/enum_type.rbs @@ -26,6 +26,10 @@ module ElasticGraph def values: (*::String) -> void def runtime_metadata: () -> SchemaArtifacts::RuntimeMetadata::Enum::Type def as_input: () -> EnumType + + private + + def configure_derived_scalar_type: (ScalarType) -> void end end end diff --git a/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/state.rbs b/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/state.rbs index bb9a73291..b7adcd58b 100644 --- a/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/state.rbs +++ b/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/state.rbs @@ -18,6 +18,7 @@ module ElasticGraph attr_reader deleted_types_by_old_name: ::Hash[::String, SchemaElements::DeprecatedElement] attr_reader renamed_fields_by_type_name_and_old_field_name: ::Hash[::String, ::Hash[::String, SchemaElements::DeprecatedElement]] attr_reader deleted_fields_by_type_name_and_old_field_name: ::Hash[::String, ::Hash[::String, SchemaElements::DeprecatedElement]] + attr_reader reserved_type_names: ::Set[::String] attr_accessor json_schema_version: ::Integer? attr_accessor json_schema_version_setter_location: ::Thread::Backtrace::Location? attr_accessor enforce_json_schema_version: bool @@ -55,6 +56,7 @@ module ElasticGraph deleted_types_by_old_name: ::Hash[::String, SchemaElements::DeprecatedElement], renamed_fields_by_type_name_and_old_field_name: ::Hash[::String, ::Hash[::String, SchemaElements::DeprecatedElement]], deleted_fields_by_type_name_and_old_field_name: ::Hash[::String, ::Hash[::String, SchemaElements::DeprecatedElement]], + reserved_type_names: ::Set[::String], json_schema_version: Integer?, json_schema_version_setter_location: ::Thread::Backtrace::Location?, enforce_json_schema_version: bool,