elasticgraph-schema_definition 0.18.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|