elasticgraph-schema_artifacts 0.19.1.1 → 0.19.2.1
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 +4 -4
- data/LICENSE.txt +1 -1
- data/lib/elastic_graph/schema_artifacts/artifacts_helper_methods.rb +1 -1
- data/lib/elastic_graph/schema_artifacts/from_disk.rb +5 -8
- data/lib/elastic_graph/schema_artifacts/runtime_metadata/computation_detail.rb +1 -1
- data/lib/elastic_graph/schema_artifacts/runtime_metadata/enum.rb +1 -1
- data/lib/elastic_graph/schema_artifacts/runtime_metadata/extension.rb +19 -12
- data/lib/elastic_graph/schema_artifacts/runtime_metadata/extension_loader.rb +6 -77
- data/lib/elastic_graph/schema_artifacts/runtime_metadata/graphql_extension.rb +40 -0
- data/lib/elastic_graph/schema_artifacts/runtime_metadata/graphql_field.rb +13 -6
- data/lib/elastic_graph/schema_artifacts/runtime_metadata/graphql_resolver.rb +66 -0
- data/lib/elastic_graph/schema_artifacts/runtime_metadata/hash_dumper.rb +1 -1
- data/lib/elastic_graph/schema_artifacts/runtime_metadata/index_definition.rb +1 -1
- data/lib/elastic_graph/schema_artifacts/runtime_metadata/index_field.rb +1 -1
- data/lib/elastic_graph/schema_artifacts/runtime_metadata/interface_verifier.rb +109 -0
- data/lib/elastic_graph/schema_artifacts/runtime_metadata/object_type.rb +11 -4
- data/lib/elastic_graph/schema_artifacts/runtime_metadata/params.rb +3 -3
- data/lib/elastic_graph/schema_artifacts/runtime_metadata/relation.rb +1 -1
- data/lib/elastic_graph/schema_artifacts/runtime_metadata/scalar_type.rb +3 -3
- data/lib/elastic_graph/schema_artifacts/runtime_metadata/schema.rb +15 -12
- data/lib/elastic_graph/schema_artifacts/runtime_metadata/schema_element_names.rb +1 -1
- data/lib/elastic_graph/schema_artifacts/runtime_metadata/sort_field.rb +1 -1
- data/lib/elastic_graph/schema_artifacts/runtime_metadata/update_target.rb +1 -1
- metadata +15 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1cc728697f7cfe8e926a44626cce30567a2ef7638b2e5f134ae4aedd34d02947
|
4
|
+
data.tar.gz: 9c8a6cd5ffbdcadfd8f6d5edebebf2067b64c9de454736d85b85f9aea8994912
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fe7be2fba3754752129f904541afd03c40aa856868d8eab0d08fd769289b0cef4e4064c516e6c8d117cd66251223f25cdbc261f81cb5e6d8d2f7e538c6a39d2a
|
7
|
+
data.tar.gz: 4f5cec991b458051b50870a9fe14dc7464b8d86d7c5cc2c1d71b71fec3f8b754bf661f0f9dabc7d357ebb76ded57c922172ca2b3e01ed5fe752e34f73762d46a
|
data/LICENSE.txt
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright 2024 Block, Inc.
|
1
|
+
# Copyright 2024 - 2025 Block, Inc.
|
2
2
|
#
|
3
3
|
# Use of this source code is governed by an MIT-style
|
4
4
|
# license that can be found in the LICENSE file or at
|
@@ -17,7 +17,7 @@ require "yaml"
|
|
17
17
|
module ElasticGraph
|
18
18
|
module SchemaArtifacts
|
19
19
|
# Builds a `SchemaArtifacts::FromDisk` instance using the provided YAML settings.
|
20
|
-
def self.from_parsed_yaml(parsed_yaml
|
20
|
+
def self.from_parsed_yaml(parsed_yaml)
|
21
21
|
schema_artifacts = parsed_yaml.fetch("schema_artifacts") do
|
22
22
|
raise Errors::ConfigError, "Config is missing required key `schema_artifacts`."
|
23
23
|
end
|
@@ -30,11 +30,11 @@ module ElasticGraph
|
|
30
30
|
raise Errors::ConfigError, "Config is missing required key `schema_artifacts.directory`."
|
31
31
|
end
|
32
32
|
|
33
|
-
FromDisk.new(directory
|
33
|
+
FromDisk.new(directory)
|
34
34
|
end
|
35
35
|
|
36
36
|
# Responsible for loading schema artifacts from disk.
|
37
|
-
class FromDisk < Support::MemoizableData.define(:artifacts_dir
|
37
|
+
class FromDisk < Support::MemoizableData.define(:artifacts_dir)
|
38
38
|
include ArtifactsHelperMethods
|
39
39
|
|
40
40
|
def graphql_schema_string
|
@@ -74,10 +74,7 @@ module ElasticGraph
|
|
74
74
|
end
|
75
75
|
|
76
76
|
def runtime_metadata
|
77
|
-
@runtime_metadata ||= RuntimeMetadata::Schema.from_hash(
|
78
|
-
parsed_yaml_from(RUNTIME_METADATA_FILE),
|
79
|
-
for_context: context
|
80
|
-
)
|
77
|
+
@runtime_metadata ||= RuntimeMetadata::Schema.from_hash(parsed_yaml_from(RUNTIME_METADATA_FILE))
|
81
78
|
end
|
82
79
|
|
83
80
|
private
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright 2024 Block, Inc.
|
1
|
+
# Copyright 2024 - 2025 Block, Inc.
|
2
2
|
#
|
3
3
|
# Use of this source code is governed by an MIT-style
|
4
4
|
# license that can be found in the LICENSE file or at
|
@@ -6,6 +6,7 @@
|
|
6
6
|
#
|
7
7
|
# frozen_string_literal: true
|
8
8
|
|
9
|
+
require "elastic_graph/schema_artifacts/runtime_metadata/interface_verifier"
|
9
10
|
require "elastic_graph/support/hash_util"
|
10
11
|
|
11
12
|
module ElasticGraph
|
@@ -15,36 +16,42 @@ module ElasticGraph
|
|
15
16
|
# code base) that implements a standard interface to plug in custom functionality.
|
16
17
|
#
|
17
18
|
# Extensions are serialized using two fields:
|
18
|
-
# - `
|
19
|
+
# - `name`: the Ruby constant of the extension
|
19
20
|
# - `require_path`: file path to `require` to load the extension
|
20
21
|
#
|
21
22
|
# However, an `Extension` instance represents a loaded, resolved extension.
|
22
23
|
# We eagerly load extensions (and validate them in the `ExtensionLoader`) in
|
23
24
|
# order to surface any issues with the extension as soon as possible. We don't
|
24
25
|
# want to defer errors if we can detect any issues with the extension at boot time.
|
25
|
-
Extension = ::Data.define(:extension_class, :require_path, :
|
26
|
+
Extension = ::Data.define(:extension_class, :require_path, :config, :name) do
|
26
27
|
# @implements Extension
|
28
|
+
def initialize(extension_class:, require_path:, config:, name: extension_class.name.to_s)
|
29
|
+
super(extension_class:, require_path:, config:, name:)
|
30
|
+
end
|
27
31
|
|
28
32
|
# Loads an extension using a serialized hash, via the provided `ExtensionLoader`.
|
29
33
|
def self.load_from_hash(hash, via:)
|
30
|
-
config = Support::HashUtil.symbolize_keys(hash["
|
31
|
-
via.load(hash.fetch("
|
32
|
-
end
|
33
|
-
|
34
|
-
# The name of the extension (based on the name of the extension class).
|
35
|
-
def extension_name
|
36
|
-
extension_class.name.to_s
|
34
|
+
config = Support::HashUtil.symbolize_keys(hash["config"] || {}) # : ::Hash[::Symbol, untyped]
|
35
|
+
via.load(hash.fetch("name"), from: hash.fetch("require_path"), config: config)
|
37
36
|
end
|
38
37
|
|
39
38
|
# The serialized form of an extension.
|
40
39
|
def to_dumpable_hash
|
41
40
|
# Keys here are ordered alphabetically; please keep them that way.
|
42
41
|
{
|
43
|
-
"
|
44
|
-
"
|
42
|
+
"config" => Support::HashUtil.stringify_keys(config),
|
43
|
+
"name" => name,
|
45
44
|
"require_path" => require_path
|
46
45
|
}.reject { |_, v| v.empty? }
|
47
46
|
end
|
47
|
+
|
48
|
+
def verify_against!(interface_def)
|
49
|
+
InterfaceVerifier.verify!(extension_class, against: interface_def, constant_name: name)
|
50
|
+
end
|
51
|
+
|
52
|
+
def verify_against(interface_def)
|
53
|
+
InterfaceVerifier.verify(extension_class, against: interface_def, constant_name: name)
|
54
|
+
end
|
48
55
|
end
|
49
56
|
end
|
50
57
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright 2024 Block, Inc.
|
1
|
+
# Copyright 2024 - 2025 Block, Inc.
|
2
2
|
#
|
3
3
|
# Use of this source code is governed by an MIT-style
|
4
4
|
# license that can be found in the LICENSE file or at
|
@@ -8,6 +8,7 @@
|
|
8
8
|
|
9
9
|
require "elastic_graph/errors"
|
10
10
|
require "elastic_graph/schema_artifacts/runtime_metadata/extension"
|
11
|
+
require "elastic_graph/schema_artifacts/runtime_metadata/interface_verifier"
|
11
12
|
|
12
13
|
module ElasticGraph
|
13
14
|
module SchemaArtifacts
|
@@ -35,89 +36,17 @@ module ElasticGraph
|
|
35
36
|
raise Errors::InvalidExtensionError, "Extension `#{constant_name}` cannot be loaded from `#{from}`, " \
|
36
37
|
"since it has already been loaded from `#{extension.require_path}`."
|
37
38
|
end
|
38
|
-
end.with(
|
39
|
+
end.with(config: config)
|
39
40
|
end
|
40
41
|
|
41
42
|
private
|
42
43
|
|
43
44
|
def load_extension(constant_name, require_path)
|
44
45
|
require require_path
|
45
|
-
extension_class = ::Object.const_get(constant_name)
|
46
|
-
Extension.new(extension_class, require_path, {})
|
47
|
-
|
48
|
-
|
49
|
-
def verify_interface(constant_name, extension)
|
50
|
-
# @type var problems: ::Array[::String]
|
51
|
-
problems = []
|
52
|
-
problems.concat(verify_methods("class", extension.singleton_class, @interface_def.singleton_class))
|
53
|
-
|
54
|
-
if extension.is_a?(::Module)
|
55
|
-
problems.concat(verify_methods("instance", extension, @interface_def))
|
56
|
-
|
57
|
-
# We care about the name exactly matching so that we can dump the extension name in a schema
|
58
|
-
# artifact w/o having to pass around the original constant name.
|
59
|
-
if extension.name != constant_name.delete_prefix("::")
|
60
|
-
problems << "- Exposes a name (`#{extension.name}`) that differs from the provided extension name (`#{constant_name}`)"
|
61
|
-
end
|
62
|
-
else
|
63
|
-
problems << "- Is not a class or module as expected"
|
64
|
-
end
|
65
|
-
|
66
|
-
if problems.any?
|
67
|
-
raise Errors::InvalidExtensionError,
|
68
|
-
"Extension `#{constant_name}` does not implement the expected interface correctly. Problems:\n\n" \
|
69
|
-
"#{problems.join("\n")}"
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
def verify_methods(type, extension, interface)
|
74
|
-
interface_methods = list_instance_interface_methods(interface)
|
75
|
-
extension_methods = list_instance_interface_methods(extension)
|
76
|
-
|
77
|
-
# @type var problems: ::Array[::String]
|
78
|
-
problems = []
|
79
|
-
|
80
|
-
if (missing_methods = interface_methods - extension_methods).any?
|
81
|
-
problems << "- Missing #{type} methods: #{missing_methods.map { |m| "`#{m}`" }.join(", ")}"
|
46
|
+
extension_class = ::Object.const_get(constant_name)
|
47
|
+
Extension.new(extension_class, require_path, {}, constant_name.delete_prefix("::")).tap do |ext|
|
48
|
+
ext.verify_against!(@interface_def)
|
82
49
|
end
|
83
|
-
|
84
|
-
interface_methods.intersection(extension_methods).each do |method_name|
|
85
|
-
unless parameters_match?(extension, interface, method_name)
|
86
|
-
interface_signature = signature_code_for(interface, method_name)
|
87
|
-
extension_signature = signature_code_for(extension, method_name)
|
88
|
-
|
89
|
-
problems << "- Method signature for #{type} method `#{method_name}` (`#{extension_signature}`) does not match interface (`#{interface_signature}`)"
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
problems
|
94
|
-
end
|
95
|
-
|
96
|
-
def list_instance_interface_methods(klass)
|
97
|
-
# Here we look at more than just the public methods. This is necessary for `initialize`.
|
98
|
-
# If it's defined on the interface definition, we want to verify it on the extension,
|
99
|
-
# but Ruby makes `initialize` private by default.
|
100
|
-
klass.instance_methods(false) +
|
101
|
-
klass.protected_instance_methods(false) +
|
102
|
-
klass.private_instance_methods(false)
|
103
|
-
end
|
104
|
-
|
105
|
-
def parameters_match?(extension, interface, method_name)
|
106
|
-
interface_parameters = interface.instance_method(method_name).parameters
|
107
|
-
extension_parameters = extension.instance_method(method_name).parameters
|
108
|
-
|
109
|
-
# Here we compare the parameters for exact equality. This is stricter than we need it
|
110
|
-
# to be (it doesn't allow the parameters to have different names, for example) but it's
|
111
|
-
# considerably simpler than us trying to determine what is truly required. For example,
|
112
|
-
# the name doesn't matter on a positional arg, but would matter on a keyword arg.
|
113
|
-
interface_parameters == extension_parameters
|
114
|
-
end
|
115
|
-
|
116
|
-
def signature_code_for(object, method_name)
|
117
|
-
# @type var file_name: ::String?
|
118
|
-
# @type var line_number: ::Integer?
|
119
|
-
file_name, line_number = object.instance_method(method_name).source_location
|
120
|
-
::File.read(file_name.to_s).split("\n").fetch(line_number.to_i - 1).strip
|
121
50
|
end
|
122
51
|
end
|
123
52
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# Copyright 2024 - 2025 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/schema_artifacts/runtime_metadata/extension_loader"
|
10
|
+
|
11
|
+
module ElasticGraph
|
12
|
+
module SchemaArtifacts
|
13
|
+
module RuntimeMetadata
|
14
|
+
class GraphQLExtension < ::Data.define(:extension_ref)
|
15
|
+
def self.loader
|
16
|
+
@loader ||= ExtensionLoader.new(Module.new)
|
17
|
+
end
|
18
|
+
|
19
|
+
EXTENSION_REF = "extension_ref"
|
20
|
+
|
21
|
+
def load_extension
|
22
|
+
Extension.load_from_hash(extension_ref, via: GraphQLExtension.loader)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.from_hash(hash)
|
26
|
+
new(
|
27
|
+
extension_ref: hash.fetch(EXTENSION_REF)
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_dumpable_hash
|
32
|
+
{
|
33
|
+
# Keys here are ordered alphabetically; please keep them that way.
|
34
|
+
EXTENSION_REF => extension_ref
|
35
|
+
}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright 2024 Block, Inc.
|
1
|
+
# Copyright 2024 - 2025 Block, Inc.
|
2
2
|
#
|
3
3
|
# Use of this source code is governed by an MIT-style
|
4
4
|
# license that can be found in the LICENSE file or at
|
@@ -12,17 +12,19 @@ require "elastic_graph/schema_artifacts/runtime_metadata/relation"
|
|
12
12
|
module ElasticGraph
|
13
13
|
module SchemaArtifacts
|
14
14
|
module RuntimeMetadata
|
15
|
-
class GraphQLField < ::Data.define(:name_in_index, :relation, :computation_detail)
|
16
|
-
EMPTY = new(nil, nil, nil)
|
15
|
+
class GraphQLField < ::Data.define(:name_in_index, :relation, :computation_detail, :resolver)
|
16
|
+
EMPTY = new(nil, nil, nil, nil)
|
17
17
|
NAME_IN_INDEX = "name_in_index"
|
18
18
|
RELATION = "relation"
|
19
19
|
AGGREGATION_DETAIL = "computation_detail"
|
20
|
+
RESOLVER = "resolver"
|
20
21
|
|
21
22
|
def self.from_hash(hash)
|
22
23
|
new(
|
23
24
|
name_in_index: hash[NAME_IN_INDEX],
|
24
25
|
relation: hash[RELATION]&.then { |rel_hash| Relation.from_hash(rel_hash) },
|
25
|
-
computation_detail: hash[AGGREGATION_DETAIL]&.then { |agg_hash| ComputationDetail.from_hash(agg_hash) }
|
26
|
+
computation_detail: hash[AGGREGATION_DETAIL]&.then { |agg_hash| ComputationDetail.from_hash(agg_hash) },
|
27
|
+
resolver: hash[RESOLVER]&.to_sym
|
26
28
|
)
|
27
29
|
end
|
28
30
|
|
@@ -31,7 +33,8 @@ module ElasticGraph
|
|
31
33
|
# Keys here are ordered alphabetically; please keep them that way.
|
32
34
|
AGGREGATION_DETAIL => computation_detail&.to_dumpable_hash,
|
33
35
|
NAME_IN_INDEX => name_in_index,
|
34
|
-
RELATION => relation&.to_dumpable_hash
|
36
|
+
RELATION => relation&.to_dumpable_hash,
|
37
|
+
RESOLVER => resolver&.to_s
|
35
38
|
}
|
36
39
|
end
|
37
40
|
|
@@ -39,7 +42,11 @@ module ElasticGraph
|
|
39
42
|
# `name_in_graphql`. Fields that have not been customized in some way do not need to be
|
40
43
|
# included in the dumped runtime metadata.
|
41
44
|
def needed?(name_in_graphql)
|
42
|
-
!!relation ||
|
45
|
+
!!relation ||
|
46
|
+
!!computation_detail ||
|
47
|
+
name_in_index&.!=(name_in_graphql) ||
|
48
|
+
!resolver.nil? ||
|
49
|
+
false
|
43
50
|
end
|
44
51
|
|
45
52
|
def with_computation_detail(empty_bucket_value:, function:)
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# Copyright 2024 - 2025 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/schema_artifacts/runtime_metadata/extension_loader"
|
10
|
+
|
11
|
+
module ElasticGraph
|
12
|
+
module SchemaArtifacts
|
13
|
+
module RuntimeMetadata
|
14
|
+
class GraphQLResolver < ::Data.define(:needs_lookahead, :resolver_ref)
|
15
|
+
def self.with_lookahead_loader
|
16
|
+
@with_lookahead_loader ||= ExtensionLoader.new(InterfaceWithLookahead)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.without_lookahead_loader
|
20
|
+
@without_lookahead_loader ||= ExtensionLoader.new(InterfaceWithoutLookahead)
|
21
|
+
end
|
22
|
+
|
23
|
+
NEEDS_LOOKAHEAD = "needs_lookahead"
|
24
|
+
RESOLVER_REF = "resolver_ref"
|
25
|
+
|
26
|
+
def load_resolver
|
27
|
+
loader = needs_lookahead ? GraphQLResolver.with_lookahead_loader : GraphQLResolver.without_lookahead_loader
|
28
|
+
Extension.load_from_hash(resolver_ref, via: loader)
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.from_hash(hash)
|
32
|
+
new(
|
33
|
+
needs_lookahead: hash.fetch(NEEDS_LOOKAHEAD),
|
34
|
+
resolver_ref: hash.fetch(RESOLVER_REF)
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_dumpable_hash
|
39
|
+
{
|
40
|
+
# Keys here are ordered alphabetically; please keep them that way.
|
41
|
+
NEEDS_LOOKAHEAD => needs_lookahead,
|
42
|
+
RESOLVER_REF => resolver_ref
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
class InterfaceWithLookahead
|
47
|
+
def initialize(elasticgraph_graphql:, config:)
|
48
|
+
# must be defined, but nothing to do
|
49
|
+
end
|
50
|
+
|
51
|
+
def resolve(field:, object:, args:, context:, lookahead:)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class InterfaceWithoutLookahead
|
56
|
+
def initialize(elasticgraph_graphql:, config:)
|
57
|
+
# must be defined, but nothing to do
|
58
|
+
end
|
59
|
+
|
60
|
+
def resolve(field:, object:, args:, context:)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# Copyright 2024 - 2025 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/errors"
|
10
|
+
|
11
|
+
module ElasticGraph
|
12
|
+
module SchemaArtifacts
|
13
|
+
module RuntimeMetadata
|
14
|
+
# Responsible for verifying extensions. This requires an interface definition
|
15
|
+
# (a class or module with empty method definitions that just serves to define what
|
16
|
+
# loaded extensions must implement). That allows us to verify the extension implements
|
17
|
+
# the interface correctly ahead of time, rather than deferring exceptions to when the
|
18
|
+
# extension is later used.
|
19
|
+
#
|
20
|
+
# Note, however, that this does not guarantee no runtime exceptions from the use of the
|
21
|
+
# extension: the extension may return invalid return values, or throw exceptions when
|
22
|
+
# called. But this verifies the interface to the extent that we can.
|
23
|
+
module InterfaceVerifier
|
24
|
+
class << self
|
25
|
+
def verify!(extension, against:, constant_name:)
|
26
|
+
problems = verify(extension, against:, constant_name:)
|
27
|
+
|
28
|
+
if problems.any?
|
29
|
+
raise Errors::InvalidExtensionError,
|
30
|
+
"Extension `#{constant_name}` does not implement the expected interface correctly. Problems:\n\n" \
|
31
|
+
"#{problems.join("\n")}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def verify(extension, against:, constant_name:)
|
36
|
+
problems = [] # : ::Array[::String]
|
37
|
+
problems.concat(verify_methods("class", extension.singleton_class, against.singleton_class))
|
38
|
+
|
39
|
+
if extension.is_a?(::Module)
|
40
|
+
problems.concat(verify_methods("instance", extension, against))
|
41
|
+
|
42
|
+
# We care about the name exactly matching so that we can dump the extension name in a schema
|
43
|
+
# artifact w/o having to pass around the original constant name.
|
44
|
+
if extension.name != constant_name.delete_prefix("::")
|
45
|
+
problems << "- Exposes a name (`#{extension.name}`) that differs from the provided extension name (`#{constant_name}`)"
|
46
|
+
end
|
47
|
+
else
|
48
|
+
problems << "- Is not a class or module as expected"
|
49
|
+
end
|
50
|
+
|
51
|
+
problems
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def verify_methods(type, extension, interface)
|
57
|
+
interface_methods = list_instance_interface_methods(interface)
|
58
|
+
extension_methods = list_instance_interface_methods(extension)
|
59
|
+
|
60
|
+
# @type var problems: ::Array[::String]
|
61
|
+
problems = []
|
62
|
+
|
63
|
+
if (missing_methods = interface_methods - extension_methods).any?
|
64
|
+
problems << "- Missing #{type} methods: #{missing_methods.map { |m| "`#{m}`" }.join(", ")}"
|
65
|
+
end
|
66
|
+
|
67
|
+
interface_methods.intersection(extension_methods).each do |method_name|
|
68
|
+
unless parameters_match?(extension, interface, method_name)
|
69
|
+
interface_signature = signature_code_for(interface, method_name)
|
70
|
+
extension_signature = signature_code_for(extension, method_name)
|
71
|
+
|
72
|
+
problems << "- Method signature for #{type} method `#{method_name}` (`#{extension_signature}`) does not match interface (`#{interface_signature}`)"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
problems
|
77
|
+
end
|
78
|
+
|
79
|
+
def list_instance_interface_methods(klass)
|
80
|
+
# Here we look at more than just the public methods. This is necessary for `initialize`.
|
81
|
+
# If it's defined on the interface definition, we want to verify it on the extension,
|
82
|
+
# but Ruby makes `initialize` private by default.
|
83
|
+
klass.instance_methods(false) +
|
84
|
+
klass.protected_instance_methods(false) +
|
85
|
+
klass.private_instance_methods(false)
|
86
|
+
end
|
87
|
+
|
88
|
+
def parameters_match?(extension, interface, method_name)
|
89
|
+
interface_parameters = interface.instance_method(method_name).parameters
|
90
|
+
extension_parameters = extension.instance_method(method_name).parameters
|
91
|
+
|
92
|
+
# Here we compare the parameters for exact equality. This is stricter than we need it
|
93
|
+
# to be (it doesn't allow the parameters to have different names, for example) but it's
|
94
|
+
# considerably simpler than us trying to determine what is truly required. For example,
|
95
|
+
# the name doesn't matter on a positional arg, but would matter on a keyword arg.
|
96
|
+
interface_parameters == extension_parameters
|
97
|
+
end
|
98
|
+
|
99
|
+
def signature_code_for(object, method_name)
|
100
|
+
# @type var file_name: ::String?
|
101
|
+
# @type var line_number: ::Integer?
|
102
|
+
file_name, line_number = object.instance_method(method_name).source_location
|
103
|
+
::File.read(file_name.to_s).split("\n").fetch(line_number.to_i - 1).strip
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright 2024 Block, Inc.
|
1
|
+
# Copyright 2024 - 2025 Block, Inc.
|
2
2
|
#
|
3
3
|
# Use of this source code is governed by an MIT-style
|
4
4
|
# license that can be found in the LICENSE file or at
|
@@ -50,8 +50,9 @@ module ElasticGraph
|
|
50
50
|
UpdateTarget.from_hash(update_target_hash)
|
51
51
|
end || []
|
52
52
|
|
53
|
-
graphql_fields_by_name = hash[GRAPHQL_FIELDS_BY_NAME]&.
|
54
|
-
|
53
|
+
graphql_fields_by_name = hash[GRAPHQL_FIELDS_BY_NAME]&.to_h do |name, field_hash|
|
54
|
+
field_hash = field_hash.merge(GraphQLField::NAME_IN_INDEX => name) unless field_hash[GraphQLField::NAME_IN_INDEX]
|
55
|
+
[name, GraphQLField.from_hash(field_hash)]
|
55
56
|
end || {}
|
56
57
|
|
57
58
|
new(
|
@@ -65,10 +66,16 @@ module ElasticGraph
|
|
65
66
|
end
|
66
67
|
|
67
68
|
def to_dumpable_hash
|
69
|
+
dumped_graphql_fields_by_name =
|
70
|
+
HashDumper.dump_hash(graphql_fields_by_name.to_h do |name, field|
|
71
|
+
field = field.with(name_in_index: nil) if field.name_in_index == name
|
72
|
+
[name, field]
|
73
|
+
end, &:to_dumpable_hash)
|
74
|
+
|
68
75
|
{
|
69
76
|
# Keys here are ordered alphabetically; please keep them that way.
|
70
77
|
ELASTICGRAPH_CATEGORY => elasticgraph_category&.to_s,
|
71
|
-
GRAPHQL_FIELDS_BY_NAME =>
|
78
|
+
GRAPHQL_FIELDS_BY_NAME => dumped_graphql_fields_by_name,
|
72
79
|
GRAPHQL_ONLY_RETURN_TYPE => graphql_only_return_type ? true : nil,
|
73
80
|
INDEX_DEFINITION_NAMES => index_definition_names,
|
74
81
|
SOURCE_TYPE => source_type,
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright 2024 Block, Inc.
|
1
|
+
# Copyright 2024 - 2025 Block, Inc.
|
2
2
|
#
|
3
3
|
# Use of this source code is governed by an MIT-style
|
4
4
|
# license that can be found in the LICENSE file or at
|
@@ -52,8 +52,8 @@ module ElasticGraph
|
|
52
52
|
|
53
53
|
def value_for(event_or_prepared_record)
|
54
54
|
case cardinality
|
55
|
-
when :many then Support::HashUtil.fetch_leaf_values_at_path(event_or_prepared_record, source_path) { [] }
|
56
|
-
when :one then Support::HashUtil.fetch_value_at_path(event_or_prepared_record, source_path) { nil }
|
55
|
+
when :many then Support::HashUtil.fetch_leaf_values_at_path(event_or_prepared_record, source_path.split(".")) { [] }
|
56
|
+
when :one then Support::HashUtil.fetch_value_at_path(event_or_prepared_record, source_path.split(".")) { nil }
|
57
57
|
end
|
58
58
|
end
|
59
59
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright 2024 Block, Inc.
|
1
|
+
# Copyright 2024 - 2025 Block, Inc.
|
2
2
|
#
|
3
3
|
# Use of this source code is governed by an MIT-style
|
4
4
|
# license that can be found in the LICENSE file or at
|
@@ -22,12 +22,12 @@ module ElasticGraph
|
|
22
22
|
end
|
23
23
|
|
24
24
|
DEFAULT_COERCION_ADAPTER_REF = {
|
25
|
-
"
|
25
|
+
"name" => "ElasticGraph::GraphQL::ScalarCoercionAdapters::NoOp",
|
26
26
|
"require_path" => "elastic_graph/graphql/scalar_coercion_adapters/no_op"
|
27
27
|
}
|
28
28
|
|
29
29
|
DEFAULT_INDEXING_PREPARER_REF = {
|
30
|
-
"
|
30
|
+
"name" => "ElasticGraph::Indexer::IndexingPreparers::NoOp",
|
31
31
|
"require_path" => "elastic_graph/indexer/indexing_preparers/no_op"
|
32
32
|
}
|
33
33
|
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright 2024 Block, Inc.
|
1
|
+
# Copyright 2024 - 2025 Block, Inc.
|
2
2
|
#
|
3
3
|
# Use of this source code is governed by an MIT-style
|
4
4
|
# license that can be found in the LICENSE file or at
|
@@ -9,6 +9,8 @@
|
|
9
9
|
require "elastic_graph/schema_artifacts/runtime_metadata/enum"
|
10
10
|
require "elastic_graph/schema_artifacts/runtime_metadata/extension"
|
11
11
|
require "elastic_graph/schema_artifacts/runtime_metadata/extension_loader"
|
12
|
+
require "elastic_graph/schema_artifacts/runtime_metadata/graphql_extension"
|
13
|
+
require "elastic_graph/schema_artifacts/runtime_metadata/graphql_resolver"
|
12
14
|
require "elastic_graph/schema_artifacts/runtime_metadata/hash_dumper"
|
13
15
|
require "elastic_graph/schema_artifacts/runtime_metadata/index_definition"
|
14
16
|
require "elastic_graph/schema_artifacts/runtime_metadata/object_type"
|
@@ -27,6 +29,7 @@ module ElasticGraph
|
|
27
29
|
:index_definitions_by_name,
|
28
30
|
:schema_element_names,
|
29
31
|
:graphql_extension_modules,
|
32
|
+
:graphql_resolvers_by_name,
|
30
33
|
:static_script_ids_by_scoped_name
|
31
34
|
)
|
32
35
|
OBJECT_TYPES_BY_NAME = "object_types_by_name"
|
@@ -35,9 +38,10 @@ module ElasticGraph
|
|
35
38
|
INDEX_DEFINITIONS_BY_NAME = "index_definitions_by_name"
|
36
39
|
SCHEMA_ELEMENT_NAMES = "schema_element_names"
|
37
40
|
GRAPHQL_EXTENSION_MODULES = "graphql_extension_modules"
|
41
|
+
GRAPHQL_RESOLVERS_BY_NAME = "graphql_resolvers_by_name"
|
38
42
|
STATIC_SCRIPT_IDS_BY_NAME = "static_script_ids_by_scoped_name"
|
39
43
|
|
40
|
-
def self.from_hash(hash
|
44
|
+
def self.from_hash(hash)
|
41
45
|
object_types_by_name = hash[OBJECT_TYPES_BY_NAME]&.transform_values do |type_hash|
|
42
46
|
ObjectType.from_hash(type_hash)
|
43
47
|
end || {}
|
@@ -56,17 +60,14 @@ module ElasticGraph
|
|
56
60
|
|
57
61
|
schema_element_names = SchemaElementNames.from_hash(hash.fetch(SCHEMA_ELEMENT_NAMES))
|
58
62
|
|
59
|
-
loader = ExtensionLoader.new(Module.new)
|
60
63
|
graphql_extension_modules =
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
[] # : ::Array[Extension]
|
69
|
-
end
|
64
|
+
hash[GRAPHQL_EXTENSION_MODULES]&.map do |ext_mod_hash|
|
65
|
+
GraphQLExtension.from_hash(ext_mod_hash)
|
66
|
+
end || []
|
67
|
+
|
68
|
+
graphql_resolvers_by_name = hash[GRAPHQL_RESOLVERS_BY_NAME]&.to_h do |name, resolver_hash|
|
69
|
+
[name.to_sym, GraphQLResolver.from_hash(resolver_hash)]
|
70
|
+
end || {}
|
70
71
|
|
71
72
|
static_script_ids_by_scoped_name = hash[STATIC_SCRIPT_IDS_BY_NAME] || {}
|
72
73
|
|
@@ -77,6 +78,7 @@ module ElasticGraph
|
|
77
78
|
index_definitions_by_name: index_definitions_by_name,
|
78
79
|
schema_element_names: schema_element_names,
|
79
80
|
graphql_extension_modules: graphql_extension_modules,
|
81
|
+
graphql_resolvers_by_name: graphql_resolvers_by_name,
|
80
82
|
static_script_ids_by_scoped_name: static_script_ids_by_scoped_name
|
81
83
|
)
|
82
84
|
end
|
@@ -86,6 +88,7 @@ module ElasticGraph
|
|
86
88
|
# Keys here are ordered alphabetically; please keep them that way.
|
87
89
|
ENUM_TYPES_BY_NAME => HashDumper.dump_hash(enum_types_by_name, &:to_dumpable_hash),
|
88
90
|
GRAPHQL_EXTENSION_MODULES => graphql_extension_modules.map(&:to_dumpable_hash),
|
91
|
+
GRAPHQL_RESOLVERS_BY_NAME => HashDumper.dump_hash(graphql_resolvers_by_name.transform_keys(&:to_s), &:to_dumpable_hash),
|
89
92
|
INDEX_DEFINITIONS_BY_NAME => HashDumper.dump_hash(index_definitions_by_name, &:to_dumpable_hash),
|
90
93
|
OBJECT_TYPES_BY_NAME => HashDumper.dump_hash(object_types_by_name, &:to_dumpable_hash),
|
91
94
|
SCALAR_TYPES_BY_NAME => HashDumper.dump_hash(scalar_types_by_name, &:to_dumpable_hash),
|
metadata
CHANGED
@@ -1,16 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: elasticgraph-schema_artifacts
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.19.
|
4
|
+
version: 0.19.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Myron Marston
|
8
8
|
- Ben VandenBos
|
9
9
|
- Block Engineering
|
10
|
-
autorequire:
|
11
10
|
bindir: bin
|
12
11
|
cert_chain: []
|
13
|
-
date: 2025-
|
12
|
+
date: 2025-04-24 00:00:00.000000000 Z
|
14
13
|
dependencies:
|
15
14
|
- !ruby/object:Gem::Dependency
|
16
15
|
name: elasticgraph-support
|
@@ -18,43 +17,42 @@ dependencies:
|
|
18
17
|
requirements:
|
19
18
|
- - '='
|
20
19
|
- !ruby/object:Gem::Version
|
21
|
-
version: 0.19.
|
20
|
+
version: 0.19.2.1
|
22
21
|
type: :runtime
|
23
22
|
prerelease: false
|
24
23
|
version_requirements: !ruby/object:Gem::Requirement
|
25
24
|
requirements:
|
26
25
|
- - '='
|
27
26
|
- !ruby/object:Gem::Version
|
28
|
-
version: 0.19.
|
27
|
+
version: 0.19.2.1
|
29
28
|
- !ruby/object:Gem::Dependency
|
30
29
|
name: elasticgraph-graphql
|
31
30
|
requirement: !ruby/object:Gem::Requirement
|
32
31
|
requirements:
|
33
32
|
- - '='
|
34
33
|
- !ruby/object:Gem::Version
|
35
|
-
version: 0.19.
|
34
|
+
version: 0.19.2.1
|
36
35
|
type: :development
|
37
36
|
prerelease: false
|
38
37
|
version_requirements: !ruby/object:Gem::Requirement
|
39
38
|
requirements:
|
40
39
|
- - '='
|
41
40
|
- !ruby/object:Gem::Version
|
42
|
-
version: 0.19.
|
41
|
+
version: 0.19.2.1
|
43
42
|
- !ruby/object:Gem::Dependency
|
44
43
|
name: elasticgraph-indexer
|
45
44
|
requirement: !ruby/object:Gem::Requirement
|
46
45
|
requirements:
|
47
46
|
- - '='
|
48
47
|
- !ruby/object:Gem::Version
|
49
|
-
version: 0.19.
|
48
|
+
version: 0.19.2.1
|
50
49
|
type: :development
|
51
50
|
prerelease: false
|
52
51
|
version_requirements: !ruby/object:Gem::Requirement
|
53
52
|
requirements:
|
54
53
|
- - '='
|
55
54
|
- !ruby/object:Gem::Version
|
56
|
-
version: 0.19.
|
57
|
-
description:
|
55
|
+
version: 0.19.2.1
|
58
56
|
email:
|
59
57
|
- myron@squareup.com
|
60
58
|
executables: []
|
@@ -69,10 +67,13 @@ files:
|
|
69
67
|
- lib/elastic_graph/schema_artifacts/runtime_metadata/enum.rb
|
70
68
|
- lib/elastic_graph/schema_artifacts/runtime_metadata/extension.rb
|
71
69
|
- lib/elastic_graph/schema_artifacts/runtime_metadata/extension_loader.rb
|
70
|
+
- lib/elastic_graph/schema_artifacts/runtime_metadata/graphql_extension.rb
|
72
71
|
- lib/elastic_graph/schema_artifacts/runtime_metadata/graphql_field.rb
|
72
|
+
- lib/elastic_graph/schema_artifacts/runtime_metadata/graphql_resolver.rb
|
73
73
|
- lib/elastic_graph/schema_artifacts/runtime_metadata/hash_dumper.rb
|
74
74
|
- lib/elastic_graph/schema_artifacts/runtime_metadata/index_definition.rb
|
75
75
|
- lib/elastic_graph/schema_artifacts/runtime_metadata/index_field.rb
|
76
|
+
- lib/elastic_graph/schema_artifacts/runtime_metadata/interface_verifier.rb
|
76
77
|
- lib/elastic_graph/schema_artifacts/runtime_metadata/object_type.rb
|
77
78
|
- lib/elastic_graph/schema_artifacts/runtime_metadata/params.rb
|
78
79
|
- lib/elastic_graph/schema_artifacts/runtime_metadata/relation.rb
|
@@ -86,12 +87,11 @@ licenses:
|
|
86
87
|
- MIT
|
87
88
|
metadata:
|
88
89
|
bug_tracker_uri: https://github.com/block/elasticgraph/issues
|
89
|
-
changelog_uri: https://github.com/block/elasticgraph/releases/tag/v0.19.
|
90
|
-
documentation_uri: https://block.github.io/elasticgraph/docs/
|
90
|
+
changelog_uri: https://github.com/block/elasticgraph/releases/tag/v0.19.2.1
|
91
|
+
documentation_uri: https://block.github.io/elasticgraph/api-docs/v0.19.2.1/
|
91
92
|
homepage_uri: https://block.github.io/elasticgraph/
|
92
|
-
source_code_uri: https://github.com/block/elasticgraph/tree/v0.19.
|
93
|
+
source_code_uri: https://github.com/block/elasticgraph/tree/v0.19.2.1/elasticgraph-schema_artifacts
|
93
94
|
gem_category: core
|
94
|
-
post_install_message:
|
95
95
|
rdoc_options: []
|
96
96
|
require_paths:
|
97
97
|
- lib
|
@@ -109,8 +109,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '0'
|
111
111
|
requirements: []
|
112
|
-
rubygems_version: 3.
|
113
|
-
signing_key:
|
112
|
+
rubygems_version: 3.6.2
|
114
113
|
specification_version: 4
|
115
114
|
summary: ElasticGraph gem containing code related to generated schema artifacts.
|
116
115
|
test_files: []
|