elasticgraph-schema_definition 0.18.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +7 -0
- data/elasticgraph-schema_definition.gemspec +26 -0
- data/lib/elastic_graph/schema_definition/api.rb +359 -0
- data/lib/elastic_graph/schema_definition/factory.rb +506 -0
- data/lib/elastic_graph/schema_definition/indexing/derived_fields/append_only_set.rb +79 -0
- data/lib/elastic_graph/schema_definition/indexing/derived_fields/field_initializer_support.rb +59 -0
- data/lib/elastic_graph/schema_definition/indexing/derived_fields/immutable_value.rb +99 -0
- data/lib/elastic_graph/schema_definition/indexing/derived_fields/min_or_max_value.rb +62 -0
- data/lib/elastic_graph/schema_definition/indexing/derived_indexed_type.rb +346 -0
- data/lib/elastic_graph/schema_definition/indexing/event_envelope.rb +74 -0
- data/lib/elastic_graph/schema_definition/indexing/field.rb +181 -0
- data/lib/elastic_graph/schema_definition/indexing/field_reference.rb +51 -0
- data/lib/elastic_graph/schema_definition/indexing/field_type/enum.rb +65 -0
- data/lib/elastic_graph/schema_definition/indexing/field_type/object.rb +113 -0
- data/lib/elastic_graph/schema_definition/indexing/field_type/scalar.rb +51 -0
- data/lib/elastic_graph/schema_definition/indexing/field_type/union.rb +70 -0
- data/lib/elastic_graph/schema_definition/indexing/index.rb +318 -0
- data/lib/elastic_graph/schema_definition/indexing/json_schema_field_metadata.rb +34 -0
- data/lib/elastic_graph/schema_definition/indexing/json_schema_with_metadata.rb +234 -0
- data/lib/elastic_graph/schema_definition/indexing/list_counts_mapping.rb +53 -0
- data/lib/elastic_graph/schema_definition/indexing/relationship_resolver.rb +96 -0
- data/lib/elastic_graph/schema_definition/indexing/rollover_config.rb +25 -0
- data/lib/elastic_graph/schema_definition/indexing/update_target_factory.rb +54 -0
- data/lib/elastic_graph/schema_definition/indexing/update_target_resolver.rb +195 -0
- data/lib/elastic_graph/schema_definition/json_schema_pruner.rb +61 -0
- data/lib/elastic_graph/schema_definition/mixins/can_be_graphql_only.rb +31 -0
- data/lib/elastic_graph/schema_definition/mixins/has_derived_graphql_type_customizations.rb +119 -0
- data/lib/elastic_graph/schema_definition/mixins/has_directives.rb +65 -0
- data/lib/elastic_graph/schema_definition/mixins/has_documentation.rb +74 -0
- data/lib/elastic_graph/schema_definition/mixins/has_indices.rb +281 -0
- data/lib/elastic_graph/schema_definition/mixins/has_readable_to_s_and_inspect.rb +46 -0
- data/lib/elastic_graph/schema_definition/mixins/has_subtypes.rb +116 -0
- data/lib/elastic_graph/schema_definition/mixins/has_type_info.rb +181 -0
- data/lib/elastic_graph/schema_definition/mixins/implements_interfaces.rb +122 -0
- data/lib/elastic_graph/schema_definition/mixins/supports_default_value.rb +47 -0
- data/lib/elastic_graph/schema_definition/mixins/supports_filtering_and_aggregation.rb +267 -0
- data/lib/elastic_graph/schema_definition/mixins/verifies_graphql_name.rb +38 -0
- data/lib/elastic_graph/schema_definition/rake_tasks.rb +190 -0
- data/lib/elastic_graph/schema_definition/results.rb +404 -0
- data/lib/elastic_graph/schema_definition/schema_artifact_manager.rb +482 -0
- data/lib/elastic_graph/schema_definition/schema_elements/argument.rb +56 -0
- data/lib/elastic_graph/schema_definition/schema_elements/built_in_types.rb +1541 -0
- data/lib/elastic_graph/schema_definition/schema_elements/deprecated_element.rb +21 -0
- data/lib/elastic_graph/schema_definition/schema_elements/directive.rb +40 -0
- data/lib/elastic_graph/schema_definition/schema_elements/enum_type.rb +189 -0
- data/lib/elastic_graph/schema_definition/schema_elements/enum_value.rb +73 -0
- data/lib/elastic_graph/schema_definition/schema_elements/enum_value_namer.rb +89 -0
- data/lib/elastic_graph/schema_definition/schema_elements/enums_for_indexed_types.rb +82 -0
- data/lib/elastic_graph/schema_definition/schema_elements/field.rb +1085 -0
- data/lib/elastic_graph/schema_definition/schema_elements/field_path.rb +112 -0
- data/lib/elastic_graph/schema_definition/schema_elements/field_source.rb +16 -0
- data/lib/elastic_graph/schema_definition/schema_elements/graphql_sdl_enumerator.rb +113 -0
- data/lib/elastic_graph/schema_definition/schema_elements/input_field.rb +31 -0
- data/lib/elastic_graph/schema_definition/schema_elements/input_type.rb +60 -0
- data/lib/elastic_graph/schema_definition/schema_elements/interface_type.rb +72 -0
- data/lib/elastic_graph/schema_definition/schema_elements/list_counts_state.rb +40 -0
- data/lib/elastic_graph/schema_definition/schema_elements/object_type.rb +53 -0
- data/lib/elastic_graph/schema_definition/schema_elements/relationship.rb +218 -0
- data/lib/elastic_graph/schema_definition/schema_elements/scalar_type.rb +310 -0
- data/lib/elastic_graph/schema_definition/schema_elements/sort_order_enum_value.rb +36 -0
- data/lib/elastic_graph/schema_definition/schema_elements/sub_aggregation_path.rb +66 -0
- data/lib/elastic_graph/schema_definition/schema_elements/type_namer.rb +237 -0
- data/lib/elastic_graph/schema_definition/schema_elements/type_reference.rb +353 -0
- data/lib/elastic_graph/schema_definition/schema_elements/type_with_subfields.rb +579 -0
- data/lib/elastic_graph/schema_definition/schema_elements/union_type.rb +157 -0
- data/lib/elastic_graph/schema_definition/scripting/file_system_repository.rb +77 -0
- data/lib/elastic_graph/schema_definition/scripting/script.rb +48 -0
- data/lib/elastic_graph/schema_definition/scripting/scripts/field/as_day_of_week.painless +24 -0
- data/lib/elastic_graph/schema_definition/scripting/scripts/field/as_time_of_day.painless +41 -0
- data/lib/elastic_graph/schema_definition/scripting/scripts/filter/by_time_of_day.painless +22 -0
- data/lib/elastic_graph/schema_definition/scripting/scripts/update/index_data.painless +93 -0
- data/lib/elastic_graph/schema_definition/state.rb +212 -0
- data/lib/elastic_graph/schema_definition/test_support.rb +113 -0
- metadata +513 -0
@@ -0,0 +1,190 @@
|
|
1
|
+
# Copyright 2024 Block, Inc.
|
2
|
+
#
|
3
|
+
# Use of this source code is governed by an MIT-style
|
4
|
+
# license that can be found in the LICENSE file or at
|
5
|
+
# https://opensource.org/licenses/MIT.
|
6
|
+
#
|
7
|
+
# frozen_string_literal: true
|
8
|
+
|
9
|
+
require "rake/tasklib"
|
10
|
+
require "elastic_graph/schema_artifacts/runtime_metadata/schema_element_names"
|
11
|
+
|
12
|
+
module ElasticGraph
|
13
|
+
module SchemaDefinition
|
14
|
+
# Defines rake tasks for managing artifacts generated from a schema definition.
|
15
|
+
#
|
16
|
+
# @note {ElasticGraph::Local::RakeTasks} wraps this and provides additional functionality. Most users will not need to interact with
|
17
|
+
# this class directly.
|
18
|
+
class RakeTasks < ::Rake::TaskLib
|
19
|
+
# @private
|
20
|
+
attr_reader :output
|
21
|
+
|
22
|
+
# @param index_document_sizes [Boolean] When enabled, ElasticGraph will configure the index mappings so that the datastore indexes a
|
23
|
+
# `_size` field in each document. ElasticGraph itself does not do anything with this field, but it will be available for your use
|
24
|
+
# in any direct queries (e.g. via Kibana). Important note: this requires the [mapper-size
|
25
|
+
# plugin](https://www.elastic.co/guide/en/elasticsearch/plugins/8.15/mapper-size.html) to be installed on your datastore cluster.
|
26
|
+
# You are responsible for ensuring that is installed if you enable this feature. If you enable this and the plugin is not
|
27
|
+
# installed, you will get errors!
|
28
|
+
# @param path_to_schema [String, Pathname] path to the main (or only) schema definition file
|
29
|
+
# @param schema_artifacts_directory [String, Pathname] Directory to dump the schema artifacts in
|
30
|
+
# @param schema_element_name_form [:camelCase, :snake_case] the form of names for schema elements (fields, arguments, directives)
|
31
|
+
# generated by ElasticGraph.
|
32
|
+
# @param schema_element_name_overrides [Hash<Symbol, String>] overrides for specific names of schema elements (fields, arguments,
|
33
|
+
# directives) generated by ElasticGraph. For example, to rename the `gt` filter field to `greaterThan`, pass `{gt: "greaterThan"}`.
|
34
|
+
# @param derived_type_name_formats [Hash<Symbol, String>] overrides for the naming formats used by ElasticGraph for derived GraphQL
|
35
|
+
# type names. For example, to use `Metrics` instead of `AggregatedValues` as the suffix for the generated types supporting
|
36
|
+
# getting aggregated metrid values, pass `{AggregatedValues: "%{base}Metrics"}`. See {SchemaElements::TypeNamer::DEFAULT_FORMATS}
|
37
|
+
# for the available formats.
|
38
|
+
# @param type_name_overrides [Hash<Symbol, String>] overrides for the names of specific GraphQL types. For example, to rename the
|
39
|
+
# `DateTime` scalar to `Timestamp`, pass `{DateTime: "Timestamp}`.
|
40
|
+
# @param enum_value_overrides_by_type [Hash<Symbol, Hash<Symbol, String>>] overrides for the names of specific enum values for
|
41
|
+
# specific enum types. For example, to rename the `DayOfWeek.MONDAY` enum to `DayOfWeek.MON`, pass `{DayOfWeek: {MONDAY: "MON"}}`.
|
42
|
+
# @param extension_modules [Array<Module>] List of Ruby modules to extend onto the `SchemaDefinition::API` instance. Designed to
|
43
|
+
# support ElasticGraph extension gems (such as `elasticgraph-apollo`).
|
44
|
+
# @param enforce_json_schema_version [Boolean] Whether or not to enforce the requirement that the JSON schema version is incremented
|
45
|
+
# every time dumping the JSON schemas results in a changed artifact. Generally speaking, you will want this to be `true` for any
|
46
|
+
# ElasticGraph application that is in production as the versioning of JSON schemas is what supports safe schema evolution as it
|
47
|
+
# allows ElasticGraph to identify which version of the JSON schema the publishing system was operating on when it published an
|
48
|
+
# event. It can be useful to set it to `false` before your application is in production, as you do not want to be forced to bump
|
49
|
+
# the version after every single schema change while you are building an initial prototype.
|
50
|
+
# @param output [IO] used for printing task output
|
51
|
+
#
|
52
|
+
# @example Minimal setup with defaults
|
53
|
+
# ElasticGraph::SchemaDefinition::RakeTasks.new(
|
54
|
+
# index_document_sizes: false,
|
55
|
+
# path_to_schema: "config/schema.rb",
|
56
|
+
# schema_artifacts_directory: "config/schema/artifacts",
|
57
|
+
# schema_element_name_form: :camelCase
|
58
|
+
# )
|
59
|
+
#
|
60
|
+
# @example Spell out the full names of the `gt`/`gte`/`lt`/`lte` filter operators
|
61
|
+
# ElasticGraph::SchemaDefinition::RakeTasks.new(
|
62
|
+
# index_document_sizes: false,
|
63
|
+
# path_to_schema: "config/schema.rb",
|
64
|
+
# schema_artifacts_directory: "config/schema/artifacts",
|
65
|
+
# schema_element_name_form: :camelCase,
|
66
|
+
# schema_element_name_overrides: {
|
67
|
+
# gt: "greaterThan",
|
68
|
+
# gte: "greaterThanOrEqualTo",
|
69
|
+
# lt: "lessThan",
|
70
|
+
# lte: "lessThanOrEqualTo"
|
71
|
+
# }
|
72
|
+
# )
|
73
|
+
#
|
74
|
+
# @example Change the `AggregatedValues` type suffix to `Metrics`
|
75
|
+
# ElasticGraph::SchemaDefinition::RakeTasks.new(
|
76
|
+
# index_document_sizes: false,
|
77
|
+
# path_to_schema: "config/schema.rb",
|
78
|
+
# schema_artifacts_directory: "config/schema/artifacts",
|
79
|
+
# schema_element_name_form: :camelCase,
|
80
|
+
# derived_type_name_formats: {AggregatedValues: "Metrics"}
|
81
|
+
# )
|
82
|
+
#
|
83
|
+
# @example Rename `JsonSafeLong` to `BigInt`
|
84
|
+
# ElasticGraph::SchemaDefinition::RakeTasks.new(
|
85
|
+
# index_document_sizes: false,
|
86
|
+
# path_to_schema: "config/schema.rb",
|
87
|
+
# schema_artifacts_directory: "config/schema/artifacts",
|
88
|
+
# schema_element_name_form: :camelCase,
|
89
|
+
# type_name_overrides: {JsonSafeLong: "BigInt"}
|
90
|
+
# )
|
91
|
+
#
|
92
|
+
# @example Shorten the names of the `DayOfWeek` enum values
|
93
|
+
# ElasticGraph::SchemaDefinition::RakeTasks.new(
|
94
|
+
# index_document_sizes: false,
|
95
|
+
# path_to_schema: "config/schema.rb",
|
96
|
+
# schema_artifacts_directory: "config/schema/artifacts",
|
97
|
+
# schema_element_name_form: :camelCase,
|
98
|
+
# enum_value_overrides_by_type: {
|
99
|
+
# DayOfWeek: {
|
100
|
+
# MONDAY: "MON",
|
101
|
+
# TUESDAY: "TUE",
|
102
|
+
# WEDNESDAY: "WED",
|
103
|
+
# THURSDAY: "THU",
|
104
|
+
# FRIDAY: "FRI",
|
105
|
+
# SATURDAY: "SAT",
|
106
|
+
# SUNDAY: "SUN"
|
107
|
+
# }
|
108
|
+
# }
|
109
|
+
# )
|
110
|
+
def initialize(
|
111
|
+
index_document_sizes:,
|
112
|
+
path_to_schema:,
|
113
|
+
schema_artifacts_directory:,
|
114
|
+
schema_element_name_form:,
|
115
|
+
schema_element_name_overrides: {},
|
116
|
+
derived_type_name_formats: {},
|
117
|
+
type_name_overrides: {},
|
118
|
+
enum_value_overrides_by_type: {},
|
119
|
+
extension_modules: [],
|
120
|
+
enforce_json_schema_version: true,
|
121
|
+
output: $stdout
|
122
|
+
)
|
123
|
+
@schema_element_names = SchemaArtifacts::RuntimeMetadata::SchemaElementNames.new(
|
124
|
+
form: schema_element_name_form,
|
125
|
+
overrides: schema_element_name_overrides
|
126
|
+
)
|
127
|
+
|
128
|
+
@derived_type_name_formats = derived_type_name_formats
|
129
|
+
@type_name_overrides = type_name_overrides
|
130
|
+
@enum_value_overrides_by_type = enum_value_overrides_by_type
|
131
|
+
@index_document_sizes = index_document_sizes
|
132
|
+
@path_to_schema = path_to_schema
|
133
|
+
@schema_artifacts_directory = schema_artifacts_directory
|
134
|
+
@enforce_json_schema_version = enforce_json_schema_version
|
135
|
+
@extension_modules = extension_modules
|
136
|
+
@output = output
|
137
|
+
|
138
|
+
define_tasks
|
139
|
+
end
|
140
|
+
|
141
|
+
private
|
142
|
+
|
143
|
+
def define_tasks
|
144
|
+
namespace :schema_artifacts do
|
145
|
+
desc "Dumps all schema artifacts based on the current ElasticGraph schema definition"
|
146
|
+
task :dump do
|
147
|
+
schema_artifact_manager.dump_artifacts
|
148
|
+
end
|
149
|
+
|
150
|
+
desc "Checks the artifacts to make sure they are up-to-date, raising an exception if not"
|
151
|
+
task :check do
|
152
|
+
schema_artifact_manager.check_artifacts
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def schema_artifact_manager
|
158
|
+
require "elastic_graph/schema_definition/schema_artifact_manager"
|
159
|
+
|
160
|
+
# :nocov: -- tests don't cover the `VERBOSE` side
|
161
|
+
max_diff_lines = ENV["VERBOSE"] ? 999999999 : 50
|
162
|
+
# :nocov:
|
163
|
+
|
164
|
+
SchemaArtifactManager.new(
|
165
|
+
schema_definition_results: schema_definition_results,
|
166
|
+
schema_artifacts_directory: @schema_artifacts_directory.to_s,
|
167
|
+
enforce_json_schema_version: @enforce_json_schema_version,
|
168
|
+
output: @output,
|
169
|
+
max_diff_lines: max_diff_lines
|
170
|
+
)
|
171
|
+
end
|
172
|
+
|
173
|
+
def schema_definition_results
|
174
|
+
require "elastic_graph/schema_definition/api"
|
175
|
+
|
176
|
+
API.new(
|
177
|
+
@schema_element_names,
|
178
|
+
@index_document_sizes,
|
179
|
+
extension_modules: @extension_modules,
|
180
|
+
derived_type_name_formats: @derived_type_name_formats,
|
181
|
+
type_name_overrides: @type_name_overrides,
|
182
|
+
enum_value_overrides_by_type: @enum_value_overrides_by_type,
|
183
|
+
output: @output
|
184
|
+
).tap do |api|
|
185
|
+
api.as_active_instance { load @path_to_schema.to_s }
|
186
|
+
end.results
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
@@ -0,0 +1,404 @@
|
|
1
|
+
# Copyright 2024 Block, Inc.
|
2
|
+
#
|
3
|
+
# Use of this source code is governed by an MIT-style
|
4
|
+
# license that can be found in the LICENSE file or at
|
5
|
+
# https://opensource.org/licenses/MIT.
|
6
|
+
#
|
7
|
+
# frozen_string_literal: true
|
8
|
+
|
9
|
+
require "elastic_graph/constants"
|
10
|
+
require "elastic_graph/error"
|
11
|
+
require "elastic_graph/schema_artifacts/runtime_metadata/schema"
|
12
|
+
require "elastic_graph/schema_artifacts/artifacts_helper_methods"
|
13
|
+
require "elastic_graph/schema_definition/indexing/event_envelope"
|
14
|
+
require "elastic_graph/schema_definition/indexing/json_schema_with_metadata"
|
15
|
+
require "elastic_graph/schema_definition/indexing/relationship_resolver"
|
16
|
+
require "elastic_graph/schema_definition/indexing/update_target_resolver"
|
17
|
+
require "elastic_graph/schema_definition/mixins/has_readable_to_s_and_inspect"
|
18
|
+
require "elastic_graph/schema_definition/schema_elements/field_path"
|
19
|
+
require "elastic_graph/schema_definition/scripting/file_system_repository"
|
20
|
+
require "elastic_graph/support/memoizable_data"
|
21
|
+
|
22
|
+
module ElasticGraph
|
23
|
+
module SchemaDefinition
|
24
|
+
# Provides the results of defining a schema.
|
25
|
+
#
|
26
|
+
# @note This class is designed to implement the same interface as `ElasticGraph::SchemaArtifacts::FromDisk`, so that it can be used
|
27
|
+
# interchangeably with schema artifacts loaded from disk. This allows the artifacts to be used in tests without having to dump them or
|
28
|
+
# reload them.
|
29
|
+
class Results < Support::MemoizableData.define(:state)
|
30
|
+
include Mixins::HasReadableToSAndInspect.new
|
31
|
+
include SchemaArtifacts::ArtifactsHelperMethods
|
32
|
+
|
33
|
+
# @return [String] the generated GraphQL SDL schema string dumped as `schema.graphql`
|
34
|
+
def graphql_schema_string
|
35
|
+
@graphql_schema_string ||= generate_sdl
|
36
|
+
end
|
37
|
+
|
38
|
+
# @return [Hash<String, Object>] the Elasticsearch/OpenSearch configuration dumped as `datastore_config.yaml`
|
39
|
+
def datastore_config
|
40
|
+
@datastore_config ||= generate_datastore_config
|
41
|
+
end
|
42
|
+
|
43
|
+
# @return [Hash<String, Object>] runtime metadata used by other parts of ElasticGraph and dumped as `runtime_metadata.yaml`
|
44
|
+
def runtime_metadata
|
45
|
+
@runtime_metadata ||= build_runtime_metadata
|
46
|
+
end
|
47
|
+
|
48
|
+
# @param version [Integer] desired JSON schema version
|
49
|
+
# @return [Hash<String, Object>] the JSON schema for the requested version, if available
|
50
|
+
# @raise [NotFoundError] if the requested JSON schema version is not available
|
51
|
+
def json_schemas_for(version)
|
52
|
+
unless available_json_schema_versions.include?(version)
|
53
|
+
raise NotFoundError, "The requested json schema version (#{version}) is not available. Available versions: #{available_json_schema_versions.to_a.join(", ")}."
|
54
|
+
end
|
55
|
+
|
56
|
+
@latest_versioned_json_schema ||= merge_field_metadata_into_json_schema(current_public_json_schema).json_schema
|
57
|
+
end
|
58
|
+
|
59
|
+
# @return [Set<Integer>] set of available JSON schema versions
|
60
|
+
def available_json_schema_versions
|
61
|
+
@available_json_schema_versions ||= Set[latest_json_schema_version]
|
62
|
+
end
|
63
|
+
|
64
|
+
# @return [Hash<String, Object>] the newly generated JSON schema
|
65
|
+
def latest_json_schema_version
|
66
|
+
current_public_json_schema[JSON_SCHEMA_VERSION_KEY]
|
67
|
+
end
|
68
|
+
|
69
|
+
# @private
|
70
|
+
def json_schema_version_setter_location
|
71
|
+
state.json_schema_version_setter_location
|
72
|
+
end
|
73
|
+
|
74
|
+
# @private
|
75
|
+
def json_schema_field_metadata_by_type_and_field_name
|
76
|
+
@json_schema_field_metadata_by_type_and_field_name ||= json_schema_indexing_field_types_by_name
|
77
|
+
.transform_values(&:json_schema_field_metadata_by_field_name)
|
78
|
+
end
|
79
|
+
|
80
|
+
# @private
|
81
|
+
def current_public_json_schema
|
82
|
+
@current_public_json_schema ||= build_public_json_schema
|
83
|
+
end
|
84
|
+
|
85
|
+
# @private
|
86
|
+
def merge_field_metadata_into_json_schema(json_schema)
|
87
|
+
json_schema_with_metadata_merger.merge_metadata_into(json_schema)
|
88
|
+
end
|
89
|
+
|
90
|
+
# @private
|
91
|
+
def unused_deprecated_elements
|
92
|
+
json_schema_with_metadata_merger.unused_deprecated_elements
|
93
|
+
end
|
94
|
+
|
95
|
+
# @private
|
96
|
+
STATIC_SCRIPT_REPO = Scripting::FileSystemRepository.new(::File.join(__dir__.to_s, "scripting", "scripts"))
|
97
|
+
|
98
|
+
# @private
|
99
|
+
def derived_indexing_type_names
|
100
|
+
@derived_indexing_type_names ||= state
|
101
|
+
.object_types_by_name
|
102
|
+
.values
|
103
|
+
.flat_map { |type| type.derived_indexed_types.map { |dit| dit.destination_type_ref.name } }
|
104
|
+
.to_set
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def after_initialize
|
110
|
+
# Record that we are now generating results so that caching can kick in.
|
111
|
+
state.user_definition_complete = true
|
112
|
+
end
|
113
|
+
|
114
|
+
def json_schema_with_metadata_merger
|
115
|
+
@json_schema_with_metadata_merger ||= Indexing::JSONSchemaWithMetadata::Merger.new(self)
|
116
|
+
end
|
117
|
+
|
118
|
+
def generate_datastore_config
|
119
|
+
# We need to check this before generating our datastore configuration.
|
120
|
+
# We can't generate a mapping from a recursively defined schema type.
|
121
|
+
check_for_circular_dependencies!
|
122
|
+
|
123
|
+
index_templates, indices = state.object_types_by_name.values
|
124
|
+
.flat_map(&:indices)
|
125
|
+
.sort_by(&:name)
|
126
|
+
.partition(&:rollover_config)
|
127
|
+
|
128
|
+
datastore_scripts = (build_dynamic_scripts + STATIC_SCRIPT_REPO.scripts)
|
129
|
+
|
130
|
+
{
|
131
|
+
"index_templates" => index_templates.to_h { |i| [i.name, i.to_index_template_config] },
|
132
|
+
"indices" => indices.to_h { |i| [i.name, i.to_index_config] },
|
133
|
+
"scripts" => datastore_scripts.to_h { |s| [s.id, s.to_artifact_payload] }
|
134
|
+
}
|
135
|
+
end
|
136
|
+
|
137
|
+
def build_dynamic_scripts
|
138
|
+
state.object_types_by_name.values
|
139
|
+
.flat_map(&:derived_indexed_types)
|
140
|
+
.map(&:painless_script)
|
141
|
+
end
|
142
|
+
|
143
|
+
def build_runtime_metadata
|
144
|
+
extra_update_targets_by_object_type_name = identify_extra_update_targets_by_object_type_name
|
145
|
+
|
146
|
+
object_types_by_name = all_types_except_root_query_type
|
147
|
+
.select { |t| t.respond_to?(:graphql_fields_by_name) }
|
148
|
+
.to_h { |type| [type.name, (_ = type).runtime_metadata(extra_update_targets_by_object_type_name.fetch(type.name) { [] })] }
|
149
|
+
|
150
|
+
scalar_types_by_name = state.scalar_types_by_name.transform_values(&:runtime_metadata)
|
151
|
+
|
152
|
+
enum_generator = state.factory.new_enums_for_indexed_types
|
153
|
+
|
154
|
+
indexed_enum_types_by_name = state.object_types_by_name.values
|
155
|
+
.select(&:indexed?)
|
156
|
+
.filter_map { |type| enum_generator.sort_order_enum_for(_ = type) }
|
157
|
+
.to_h { |enum_type| [(_ = enum_type).name, (_ = enum_type).runtime_metadata] }
|
158
|
+
|
159
|
+
enum_types_by_name = all_types_except_root_query_type
|
160
|
+
.grep(SchemaElements::EnumType) # : ::Array[SchemaElements::EnumType]
|
161
|
+
.to_h { |t| [t.name, t.runtime_metadata] }
|
162
|
+
.merge(indexed_enum_types_by_name)
|
163
|
+
|
164
|
+
index_definitions_by_name = state.object_types_by_name.values.flat_map(&:indices).to_h do |index|
|
165
|
+
[index.name, index.runtime_metadata]
|
166
|
+
end
|
167
|
+
|
168
|
+
SchemaArtifacts::RuntimeMetadata::Schema.new(
|
169
|
+
object_types_by_name: object_types_by_name,
|
170
|
+
scalar_types_by_name: scalar_types_by_name,
|
171
|
+
enum_types_by_name: enum_types_by_name,
|
172
|
+
index_definitions_by_name: index_definitions_by_name,
|
173
|
+
schema_element_names: state.schema_elements,
|
174
|
+
graphql_extension_modules: state.graphql_extension_modules,
|
175
|
+
static_script_ids_by_scoped_name: STATIC_SCRIPT_REPO.script_ids_by_scoped_name
|
176
|
+
)
|
177
|
+
end
|
178
|
+
|
179
|
+
# Builds a map, keyed by object type name, of extra `update_targets` that have been generated
|
180
|
+
# from any fields that use `sourced_from` on other types.
|
181
|
+
def identify_extra_update_targets_by_object_type_name
|
182
|
+
# The field_path_resolver memoizes some calculations, and we want the same instance to be
|
183
|
+
# used by all UpdateTargetBuilders to maximize its effectiveness.
|
184
|
+
field_path_resolver = SchemaElements::FieldPath::Resolver.new
|
185
|
+
sourced_field_errors = [] # : ::Array[::String]
|
186
|
+
relationship_errors = [] # : ::Array[::String]
|
187
|
+
|
188
|
+
state.object_types_by_name.values.each_with_object(::Hash.new { |h, k| h[k] = [] }) do |object_type, accum|
|
189
|
+
fields_with_sources_by_relationship_name =
|
190
|
+
if object_type.indices.empty?
|
191
|
+
# only indexed types can have `sourced_from` fields, and resolving `fields_with_sources` on an unindexed union type
|
192
|
+
# such as `_Entity` when we are using apollo can lead to exceptions when multiple entity types have the same field name
|
193
|
+
# that use different mapping types.
|
194
|
+
{} # : ::Hash[::String, ::Array[SchemaElements::Field]]
|
195
|
+
else
|
196
|
+
object_type
|
197
|
+
.fields_with_sources
|
198
|
+
.group_by { |f| (_ = f.source).relationship_name }
|
199
|
+
end
|
200
|
+
|
201
|
+
defined_relationships = object_type
|
202
|
+
.graphql_fields_by_name.values
|
203
|
+
.select(&:relationship)
|
204
|
+
.map(&:name)
|
205
|
+
|
206
|
+
(defined_relationships | fields_with_sources_by_relationship_name.keys).each do |relationship_name|
|
207
|
+
sourced_fields = fields_with_sources_by_relationship_name.fetch(relationship_name) { [] }
|
208
|
+
relationship_resolver = Indexing::RelationshipResolver.new(
|
209
|
+
schema_def_state: state,
|
210
|
+
object_type: object_type,
|
211
|
+
relationship_name: relationship_name,
|
212
|
+
sourced_fields: sourced_fields,
|
213
|
+
field_path_resolver: field_path_resolver
|
214
|
+
)
|
215
|
+
|
216
|
+
resolved_relationship, relationship_error = relationship_resolver.resolve
|
217
|
+
relationship_errors << relationship_error if relationship_error
|
218
|
+
|
219
|
+
if object_type.indices.any? && resolved_relationship && sourced_fields.any?
|
220
|
+
update_target_resolver = Indexing::UpdateTargetResolver.new(
|
221
|
+
object_type: object_type,
|
222
|
+
resolved_relationship: resolved_relationship,
|
223
|
+
sourced_fields: sourced_fields,
|
224
|
+
field_path_resolver: field_path_resolver
|
225
|
+
)
|
226
|
+
|
227
|
+
update_target, errors = update_target_resolver.resolve
|
228
|
+
accum[resolved_relationship.related_type.name] << update_target if update_target
|
229
|
+
sourced_field_errors.concat(errors)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end.tap do
|
233
|
+
full_errors = [] # : ::Array[::String]
|
234
|
+
|
235
|
+
if sourced_field_errors.any?
|
236
|
+
full_errors << "Schema had #{sourced_field_errors.size} error(s) related to `sourced_from` fields:\n\n#{sourced_field_errors.map.with_index(1) { |e, i| "#{i}. #{e}" }.join("\n\n")}"
|
237
|
+
end
|
238
|
+
|
239
|
+
if relationship_errors.any?
|
240
|
+
full_errors << "Schema had #{relationship_errors.size} error(s) related to relationship fields:\n\n#{relationship_errors.map.with_index(1) { |e, i| "#{i}. #{e}" }.join("\n\n")}"
|
241
|
+
end
|
242
|
+
|
243
|
+
unless full_errors.empty?
|
244
|
+
raise SchemaError, full_errors.join("\n\n")
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
# Generates the SDL defined by your schema. Intended to be called only once
|
250
|
+
# at the very end (after evaluating the "main" template). `Evaluator` calls this
|
251
|
+
# automatically at the end.
|
252
|
+
def generate_sdl
|
253
|
+
check_for_circular_dependencies!
|
254
|
+
state.object_types_by_name.values.each(&:verify_graphql_correctness!)
|
255
|
+
|
256
|
+
type_defs = state.factory
|
257
|
+
.new_graphql_sdl_enumerator(all_types_except_root_query_type)
|
258
|
+
.map { |sdl| strip_trailing_whitespace(sdl) }
|
259
|
+
|
260
|
+
[type_defs + state.sdl_parts].join("\n\n")
|
261
|
+
end
|
262
|
+
|
263
|
+
def build_public_json_schema
|
264
|
+
json_schema_version = state.json_schema_version
|
265
|
+
if json_schema_version.nil?
|
266
|
+
raise SchemaError, "`json_schema_version` must be specified in the schema. To resolve, add `schema.json_schema_version 1` in a schema definition block."
|
267
|
+
end
|
268
|
+
|
269
|
+
indexed_type_names = state.object_types_by_name.values
|
270
|
+
.select { |type| type.indexed? && !type.abstract? }
|
271
|
+
.reject { |type| derived_indexing_type_names.include?(type.name) }
|
272
|
+
.map(&:name)
|
273
|
+
|
274
|
+
definitions_by_name = json_schema_indexing_field_types_by_name
|
275
|
+
.transform_values(&:to_json_schema)
|
276
|
+
.compact
|
277
|
+
|
278
|
+
{
|
279
|
+
"$schema" => JSON_META_SCHEMA,
|
280
|
+
JSON_SCHEMA_VERSION_KEY => json_schema_version,
|
281
|
+
"$defs" => {
|
282
|
+
"ElasticGraphEventEnvelope" => Indexing::EventEnvelope.json_schema(indexed_type_names, json_schema_version)
|
283
|
+
}.merge(definitions_by_name)
|
284
|
+
}
|
285
|
+
end
|
286
|
+
|
287
|
+
def json_schema_indexing_field_types_by_name
|
288
|
+
@json_schema_indexing_field_types_by_name ||= state
|
289
|
+
.types_by_name.values
|
290
|
+
.reject do |t|
|
291
|
+
derived_indexing_type_names.include?(t.name) ||
|
292
|
+
# Skip graphql framework types
|
293
|
+
t.graphql_only?
|
294
|
+
end
|
295
|
+
.sort_by(&:name)
|
296
|
+
.to_h { |type| [type.name, type.to_indexing_field_type] }
|
297
|
+
end
|
298
|
+
|
299
|
+
def strip_trailing_whitespace(string)
|
300
|
+
string.gsub(/ +$/, "")
|
301
|
+
end
|
302
|
+
|
303
|
+
def check_for_circular_dependencies!
|
304
|
+
return if @no_circular_dependencies
|
305
|
+
|
306
|
+
referenced_types_by_source_type = state.types_by_name
|
307
|
+
.reject { |_, type| type.graphql_only? }
|
308
|
+
.each_with_object(::Hash.new { |h, k| h[k] = ::Set.new }) do |(type_name, _), cache|
|
309
|
+
recursively_add_referenced_types_to(state.type_ref(type_name), cache)
|
310
|
+
end
|
311
|
+
|
312
|
+
circular_reference_sets = referenced_types_by_source_type
|
313
|
+
.select { |source_type, referenced_types| referenced_types.include?(source_type) }
|
314
|
+
.values
|
315
|
+
.uniq
|
316
|
+
|
317
|
+
if circular_reference_sets.any?
|
318
|
+
descriptions = circular_reference_sets.map do |set|
|
319
|
+
"- The set of #{set.to_a} forms a circular reference chain."
|
320
|
+
end
|
321
|
+
|
322
|
+
raise SchemaError, "Your schema has self-referential types, which are not allowed, since " \
|
323
|
+
"it prevents the datastore mapping and GraphQL schema generation from terminating:\n" \
|
324
|
+
"#{descriptions.join("\n")}"
|
325
|
+
end
|
326
|
+
|
327
|
+
@no_circular_dependencies = true
|
328
|
+
end
|
329
|
+
|
330
|
+
def recursively_add_referenced_types_to(source_type_ref, references_cache)
|
331
|
+
return unless (source_type = source_type_ref.as_object_type)
|
332
|
+
references_set = references_cache[source_type_ref.name]
|
333
|
+
|
334
|
+
# Recursive references are allowed only when its a relation, so skip that case.
|
335
|
+
source_type.graphql_fields_by_name.values.reject { |f| f.relationship }.each do |field|
|
336
|
+
field_type = field.type.fully_unwrapped
|
337
|
+
|
338
|
+
if field_type.object? && references_set.add?(field_type.name)
|
339
|
+
recursively_add_referenced_types_to(field_type, references_cache)
|
340
|
+
end
|
341
|
+
|
342
|
+
references_set.merge(references_cache[field_type.name])
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
def all_types_except_root_query_type
|
347
|
+
@all_types_except_root_query_type ||= state.types_by_name.values.flat_map do |registered_type|
|
348
|
+
related_types = [registered_type] + registered_type.derived_graphql_types
|
349
|
+
apply_customizations_to(related_types, registered_type)
|
350
|
+
related_types
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
def apply_customizations_to(types, registered_type)
|
355
|
+
built_in_customizers = state.built_in_types_customization_blocks
|
356
|
+
if built_in_customizers.any? && state.initially_registered_built_in_types.include?(registered_type.name)
|
357
|
+
types.each do |type|
|
358
|
+
built_in_customizers.each do |customization_block|
|
359
|
+
customization_block.call(type)
|
360
|
+
end
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
unless (unknown_type_names = registered_type.derived_type_customizations_by_name.keys - types.map(&:name)).empty?
|
365
|
+
raise SchemaError,
|
366
|
+
"`customize_derived_types` was called on `#{registered_type.name}` with some unrecognized type names " \
|
367
|
+
"(#{unknown_type_names.join(", ")}). Maybe some of the derived GraphQL types are misspelled?"
|
368
|
+
end
|
369
|
+
|
370
|
+
unless (unknown_type_names = registered_type.derived_field_customizations_by_type_and_field_name.keys - types.map(&:name)).empty?
|
371
|
+
raise SchemaError,
|
372
|
+
"`customize_derived_type_fields` was called on `#{registered_type.name}` with some unrecognized type names " \
|
373
|
+
"(#{unknown_type_names.join(", ")}). Maybe some of the derived GraphQL types are misspelled?"
|
374
|
+
end
|
375
|
+
|
376
|
+
unknown_field_names = (types - [registered_type]).flat_map do |type|
|
377
|
+
registered_type.derived_type_customizations_for_type(type).each { |b| b.call(type) }
|
378
|
+
field_customizations_by_name = registered_type.derived_field_customizations_by_name_for_type(type)
|
379
|
+
|
380
|
+
if field_customizations_by_name.any? && !type.respond_to?(:graphql_fields_by_name)
|
381
|
+
raise SchemaError,
|
382
|
+
"`customize_derived_type_fields` was called on `#{registered_type.name}` with a type that can " \
|
383
|
+
"never have fields: `#{type.name}`."
|
384
|
+
end
|
385
|
+
|
386
|
+
field_customizations_by_name.filter_map do |field_name, customization_blocks|
|
387
|
+
if (field = (_ = type).graphql_fields_by_name[field_name])
|
388
|
+
customization_blocks.each { |b| b.call(field) }
|
389
|
+
nil
|
390
|
+
else
|
391
|
+
"#{type.name}.#{field_name}"
|
392
|
+
end
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
unless unknown_field_names.empty?
|
397
|
+
raise SchemaError,
|
398
|
+
"`customize_derived_type_fields` was called on `#{registered_type.name}` with some unrecognized field names " \
|
399
|
+
"(#{unknown_field_names.join(", ")}). Maybe one of the field names was misspelled?"
|
400
|
+
end
|
401
|
+
end
|
402
|
+
end
|
403
|
+
end
|
404
|
+
end
|