elasticgraph-support 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 +6 -0
- data/elasticgraph-support.gemspec +16 -0
- data/lib/elastic_graph/constants.rb +220 -0
- data/lib/elastic_graph/error.rb +99 -0
- data/lib/elastic_graph/support/faraday_middleware/msearch_using_get_instead_of_post.rb +31 -0
- data/lib/elastic_graph/support/faraday_middleware/support_timeouts.rb +36 -0
- data/lib/elastic_graph/support/from_yaml_file.rb +53 -0
- data/lib/elastic_graph/support/graphql_formatter.rb +66 -0
- data/lib/elastic_graph/support/hash_util.rb +191 -0
- data/lib/elastic_graph/support/logger.rb +82 -0
- data/lib/elastic_graph/support/memoizable_data.rb +147 -0
- data/lib/elastic_graph/support/monotonic_clock.rb +20 -0
- data/lib/elastic_graph/support/threading.rb +42 -0
- data/lib/elastic_graph/support/time_set.rb +293 -0
- data/lib/elastic_graph/support/time_util.rb +108 -0
- data/lib/elastic_graph/support/untyped_encoder.rb +67 -0
- data/lib/elastic_graph/version.rb +15 -0
- metadata +256 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 0a6c3aca3ffb3ca4835a6e051afe7dee323348ef1d819a30dca05b1f6d06d452
|
4
|
+
data.tar.gz: a52ecb10ebd1065e9780aa3dabf71848200e96409b110122f00bd5569e485fa7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6ab3fb42baad12fcab9414a262f0b2ddfddb20d90cbd0913f65fb0a1dfecbb1e5c9b17231ea337d0a1a3ea20854ce310b8ecf905fb528a975e14406f6e46905d
|
7
|
+
data.tar.gz: 391e5007037fbdfd29134d2dd6cbaa5313fa8b031b2a4366c58d8c036940ef318d1571d16aee18aeab520eca2c8275f1d79b5842b07943cebed143797d069ac5
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2024 Block, Inc.
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,16 @@
|
|
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_relative "../gemspec_helper"
|
10
|
+
|
11
|
+
ElasticGraphGemspecHelper.define_elasticgraph_gem(gemspec_file: __FILE__, category: :core) do |spec, eg_version|
|
12
|
+
spec.summary = "ElasticGraph gem providing support utilities to the other ElasticGraph gems."
|
13
|
+
|
14
|
+
spec.add_development_dependency "faraday", "~> 2.10"
|
15
|
+
spec.add_development_dependency "rake", "~> 13.2"
|
16
|
+
end
|
@@ -0,0 +1,220 @@
|
|
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
|
+
# Enumerates constants that are used from multiple places in the code.
|
10
|
+
module ElasticGraph
|
11
|
+
# The datastore date format used by ElasticGraph. Matches ISO-8601/RFC-3339.
|
12
|
+
# See https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html#built-in-date-formats
|
13
|
+
DATASTORE_DATE_FORMAT = "strict_date"
|
14
|
+
|
15
|
+
# The datastore date time format used by ElasticGraph. Matches ISO-8601/RFC-3339.
|
16
|
+
# See https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html#built-in-date-formats
|
17
|
+
DATASTORE_DATE_TIME_FORMAT = "strict_date_time"
|
18
|
+
|
19
|
+
# HTTP header that ElasticGraph HTTP implementations (e.g. elasticgraph-rack, elasticgraph-lambda)
|
20
|
+
# look at to determine a client-specified request timeout.
|
21
|
+
TIMEOUT_MS_HEADER = "ElasticGraph-Request-Timeout-Ms"
|
22
|
+
|
23
|
+
# Min/max values for the `Int` type.
|
24
|
+
# Based on the GraphQL spec:
|
25
|
+
#
|
26
|
+
# > If the integer internal value represents a value less than -2^31 or greater
|
27
|
+
# > than or equal to 2^31, a field error should be raised.
|
28
|
+
#
|
29
|
+
# (from http://spec.graphql.org/June2018/#sec-Int)
|
30
|
+
INT_MIN = -(2**31).to_int
|
31
|
+
INT_MAX = -INT_MIN - 1
|
32
|
+
|
33
|
+
# Min/max values for our `JsonSafeLong` type.
|
34
|
+
# Based on https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER
|
35
|
+
JSON_SAFE_LONG_MIN = -((2**53) - 1).to_int
|
36
|
+
JSON_SAFE_LONG_MAX = -JSON_SAFE_LONG_MIN
|
37
|
+
|
38
|
+
# Min/max values for our `LongString` type.
|
39
|
+
# This range is derived from the Elasticsearch docs on its longs:
|
40
|
+
# > A signed 64-bit integer with a minimum value of -2^63 and a maximum value of 2^63 - 1.
|
41
|
+
# (from https://www.elastic.co/guide/en/elasticsearch/reference/current/number.html)
|
42
|
+
LONG_STRING_MIN = -(2**63).to_int
|
43
|
+
LONG_STRING_MAX = -LONG_STRING_MIN - 1
|
44
|
+
|
45
|
+
# When indexing large string values into the datastore, we've observed errors like:
|
46
|
+
#
|
47
|
+
# > bytes can be at most 32766 in length
|
48
|
+
#
|
49
|
+
# This is also documented on the Elasticsearch docs site, under "Choosing a keyword family field type":
|
50
|
+
# https://www.elastic.co/guide/en/elasticsearch/reference/8.2/keyword.html#wildcard-field-type
|
51
|
+
#
|
52
|
+
# Note that it's a byte limit, but JSON schema's maxLength is a limit on the number of characters.
|
53
|
+
# UTF8 uses up to 4 bytes per character so to guard against a maliciously crafted payload, we limit
|
54
|
+
# the length to a quarter of 32766.
|
55
|
+
DEFAULT_MAX_KEYWORD_LENGTH = 32766 / 4
|
56
|
+
|
57
|
+
# Strings indexed as `text` can be much larger than `keyword` fields. In fact, there's no limitation
|
58
|
+
# on the `text` length, except for the overall size of the HTTP request body when we attempt to index
|
59
|
+
# a `text` field. By default it's limited to 100MB via the `http.max_content_length` setting:
|
60
|
+
#
|
61
|
+
# https://www.elastic.co/guide/en/elasticsearch/reference/8.11/modules-network.html#http-settings
|
62
|
+
#
|
63
|
+
# Note: there's no guarantee that `text` values shorter than this will succeed when indexing them--it
|
64
|
+
# depends on how many other fields and documents are included in the indexing payload, since the limit
|
65
|
+
# is on the overall payload size, and not on the size of one field. Given that, there's not really a
|
66
|
+
# discrete value we can use for the max length that guarantees successful indexing. But we know that
|
67
|
+
# values larger than this will fail, so this is the limit we use.
|
68
|
+
DEFAULT_MAX_TEXT_LENGTH = 100 * (2**20).to_int
|
69
|
+
|
70
|
+
# The name of the JSON schema definition for the ElasticGraph event envelope.
|
71
|
+
EVENT_ENVELOPE_JSON_SCHEMA_NAME = "ElasticGraphEventEnvelope"
|
72
|
+
|
73
|
+
# For some queries, we wind up needing a pagination cursor for a collection
|
74
|
+
# that will only ever contain a single value (and has no "key" to speak of
|
75
|
+
# to encode into a cursor). In those contexts, we'll use this as the cursor value.
|
76
|
+
# Ideally, we want this to be a value that could never be produced by our normal
|
77
|
+
# cursor encoding logic. This cursor is encoded from data that includes a UUID,
|
78
|
+
# which we can trust is unique.
|
79
|
+
SINGLETON_CURSOR = "eyJ1dWlkIjoiZGNhMDJkMjAtYmFlZS00ZWU5LWEwMjctZmVlY2UwYTZkZTNhIn0="
|
80
|
+
|
81
|
+
# Schema artifact file names.
|
82
|
+
GRAPHQL_SCHEMA_FILE = "schema.graphql"
|
83
|
+
JSON_SCHEMAS_FILE = "json_schemas.yaml"
|
84
|
+
DATASTORE_CONFIG_FILE = "datastore_config.yaml"
|
85
|
+
RUNTIME_METADATA_FILE = "runtime_metadata.yaml"
|
86
|
+
|
87
|
+
# Name for directory that contains versioned json_schemas files.
|
88
|
+
JSON_SCHEMAS_BY_VERSION_DIRECTORY = "json_schemas_by_version"
|
89
|
+
# Name for field in json schemas files that represents schema "version".
|
90
|
+
JSON_SCHEMA_VERSION_KEY = "json_schema_version"
|
91
|
+
|
92
|
+
# String that goes in the middle of a rollover index name, used to mark it as a rollover
|
93
|
+
# index (and split on to parse a rollover index name).
|
94
|
+
ROLLOVER_INDEX_INFIX_MARKER = "_rollover__"
|
95
|
+
|
96
|
+
DERIVED_INDEX_FAILURE_MESSAGE_PREAMBLE = "Derived index update failed due to bad input data"
|
97
|
+
|
98
|
+
# The current id of our static `index_data` update script. Verified by a test so you can count
|
99
|
+
# on it being accurate. We expose this as a constant so that we can detect this specific script
|
100
|
+
# in environments where we can't count on `elasticgraph-schema_definition` (where the script is
|
101
|
+
# defined) being available, since that gem is usually only used in development.
|
102
|
+
#
|
103
|
+
# Note: this constant is automatically kept up-to-date by our `schema_artifacts:dump` rake task.
|
104
|
+
INDEX_DATA_UPDATE_SCRIPT_ID = "update_index_data_d577eb4b07ee3c53b59f2f6d6c7b2413"
|
105
|
+
|
106
|
+
# The id of the old version of the update data script before ElasticGraph v0.9. For now, we are maintaining
|
107
|
+
# backwards compatibility with how it recorded event versions, and we have test coverage for that which relies
|
108
|
+
# upon this id.
|
109
|
+
#
|
110
|
+
# TODO: Drop this when we no longer need to maintain backwards-compatibility.
|
111
|
+
OLD_INDEX_DATA_UPDATE_SCRIPT_ID = "update_index_data_9b97090d5c97c4adc82dc7f4c2b89bc5"
|
112
|
+
|
113
|
+
# When an update script has a no-op result we often want to communicate more information about
|
114
|
+
# why it was a no-op back to ElatsicGraph from the script. The only way to do that is to throw
|
115
|
+
# an exception with an error message, but, as far as I can tell, painless doesn't let you define
|
116
|
+
# custom exception classes. To allow elasticgraph-indexer to detect that the script "failed" due
|
117
|
+
# to a no-op (rather than a true failure) we include this common preamble in the exception message
|
118
|
+
# thrown from our update scripts for the no-op case.
|
119
|
+
UPDATE_WAS_NOOP_MESSAGE_PREAMBLE = "ElasticGraph update was a no-op: "
|
120
|
+
|
121
|
+
# The name used to refer to a document's own/primary source event (that is, the event that has a `type`
|
122
|
+
# matching the document's type). The name here was chosen to avoid naming collisions with relationships
|
123
|
+
# defined via the `relates_to_one`/`relates_to_many` APIs. The GraphQL spec reserves the double-underscore
|
124
|
+
# prefix on field names, which means that users cannot define a relationship named `__self` via the
|
125
|
+
# `relates_to_one`/`relates_to_many` APIs.
|
126
|
+
SELF_RELATIONSHIP_NAME = "__self"
|
127
|
+
|
128
|
+
# This regex aligns with the datastore format of HH:mm:ss || HH:mm:ss.S || HH:mm:ss.SS || HH:mm:ss.SSS
|
129
|
+
# See https://rubular.com/r/NHjBWrpZvzOTJO for examples.
|
130
|
+
VALID_LOCAL_TIME_REGEX = /\A(([0-1][0-9])|(2[0-3])):[0-5][0-9]:[0-5][0-9](\.[0-9]{1,3})?\z/
|
131
|
+
|
132
|
+
# `VALID_LOCAL_TIME_REGEX`, expressed as a JSON schema pattern. JSON schema supports a subset of
|
133
|
+
# Ruby Regexp features and is expressed as a String object. Here we convert from the Ruby Regexp
|
134
|
+
# start-and-end-of-string anchors (\A and \z) and convert them to the JSON schema ones (^ and $).
|
135
|
+
#
|
136
|
+
# For more info, see:
|
137
|
+
# https://json-schema.org/understanding-json-schema/reference/regular_expressions.html
|
138
|
+
# http://www.rexegg.com/regex-anchors.html
|
139
|
+
VALID_LOCAL_TIME_JSON_SCHEMA_PATTERN = VALID_LOCAL_TIME_REGEX.source.sub(/\A\\A/, "^").sub(/\\z\z/, "$")
|
140
|
+
|
141
|
+
# Special hidden field defined in an index where we store the count of elements in each list field.
|
142
|
+
# We index the list counts so that we can offer a `count` filter operator on list fields, allowing
|
143
|
+
# clients to query on the count of list elements.
|
144
|
+
#
|
145
|
+
# The field name has a leading `__` because the GraphQL spec reserves that prefix for its own use,
|
146
|
+
# and we can therefore assume that no GraphQL fields have this name.
|
147
|
+
LIST_COUNTS_FIELD = "__counts"
|
148
|
+
|
149
|
+
# Character used to separate parts of a field path for the keys in the special `__counts`
|
150
|
+
# field which contains the counts of the various list fields. We were going to use a dot
|
151
|
+
# (as you'd expect) but ran into errors like this from the datastore:
|
152
|
+
#
|
153
|
+
# > can't merge a non object mapping [seasons.players.__counts.seasons] with an object mapping
|
154
|
+
#
|
155
|
+
# When we have a list of `object`, and then a list field on that object type, we want to
|
156
|
+
# store the count of both the parent list and the child list, but if we use dots then the datastore
|
157
|
+
# treats it like a nested JSON object, and the JSON entry at the parent path can't both be an integer
|
158
|
+
# (for the parent list count) and an object containing counts of its child lists.
|
159
|
+
#
|
160
|
+
# By using `|` instead of `.`, we avoid this problem.
|
161
|
+
LIST_COUNTS_FIELD_PATH_KEY_SEPARATOR = "|"
|
162
|
+
|
163
|
+
# The set of datastore field types which have no `properties` in the mapping, but which
|
164
|
+
# can be represented as a JSON object at indexing time.
|
165
|
+
#
|
166
|
+
# I built this list by auditing the full list of index field mapping types:
|
167
|
+
# https://www.elastic.co/guide/en/elasticsearch/reference/8.9/mapping-types.html
|
168
|
+
DATASTORE_PROPERTYLESS_OBJECT_TYPES = [
|
169
|
+
"aggregate_metric_double", # https://www.elastic.co/guide/en/elasticsearch/reference/8.9/aggregate-metric-double.html
|
170
|
+
"completion", # https://www.elastic.co/guide/en/elasticsearch/reference/8.9/search-suggesters.html#completion-suggester
|
171
|
+
"flattened", # https://www.elastic.co/guide/en/elasticsearch/reference/8.9/flattened.html
|
172
|
+
"geo_point", # https://www.elastic.co/guide/en/elasticsearch/reference/8.9/geo-point.html
|
173
|
+
"geo_shape", # https://www.elastic.co/guide/en/elasticsearch/reference/8.9/geo-shape.html
|
174
|
+
"histogram", # https://www.elastic.co/guide/en/elasticsearch/reference/8.9/histogram.html
|
175
|
+
"join", # https://www.elastic.co/guide/en/elasticsearch/reference/8.9/parent-join.html
|
176
|
+
"percolator", # https://www.elastic.co/guide/en/elasticsearch/reference/8.9/percolator.html
|
177
|
+
"point", # https://www.elastic.co/guide/en/elasticsearch/reference/8.9/point.html
|
178
|
+
"range", # https://www.elastic.co/guide/en/elasticsearch/reference/8.9/range.html
|
179
|
+
"rank_features", # https://www.elastic.co/guide/en/elasticsearch/reference/8.9/rank-features.html
|
180
|
+
"shape" # https://www.elastic.co/guide/en/elasticsearch/reference/8.9/shape.html
|
181
|
+
].to_set
|
182
|
+
|
183
|
+
# This pattern matches the spec for a valid GraphQL name:
|
184
|
+
# http://spec.graphql.org/June2018/#sec-Names
|
185
|
+
#
|
186
|
+
# ...however, it allows additional non-valid characters before and after it.
|
187
|
+
GRAPHQL_NAME_WITHIN_LARGER_STRING_PATTERN = /[_A-Za-z][_0-9A-Za-z]*/
|
188
|
+
|
189
|
+
# This pattern exactly matches a valid GraphQL name, with no extra characters allowed before or after.
|
190
|
+
GRAPHQL_NAME_PATTERN = /\A#{GRAPHQL_NAME_WITHIN_LARGER_STRING_PATTERN}\z/
|
191
|
+
|
192
|
+
# Description in English of the requirements for GraphQL names. (Used in multiple error messages).
|
193
|
+
GRAPHQL_NAME_VALIDITY_DESCRIPTION = "Names are limited to ASCII alphanumeric characters (plus underscore), and cannot start with a number."
|
194
|
+
|
195
|
+
# The standard set of scalars that are defined by the GraphQL spec:
|
196
|
+
# https://spec.graphql.org/October2021/#sec-Scalars
|
197
|
+
STOCK_GRAPHQL_SCALARS = %w[Boolean Float ID Int String].to_set.freeze
|
198
|
+
|
199
|
+
# The current variant of JSON schema that we use.
|
200
|
+
JSON_META_SCHEMA = "http://json-schema.org/draft-07/schema#"
|
201
|
+
|
202
|
+
# Filter the bulk response payload with a comma separated list using dot notation.
|
203
|
+
# https://www.elastic.co/guide/en/elasticsearch/reference/7.10/common-options.html#common-options-response-filtering
|
204
|
+
#
|
205
|
+
# Note: anytime you change this constant, be sure to check all the comments in the unit specs that mention this constant.
|
206
|
+
# When stubbing a datastore client test double, it doesn't respect this filtering obviously, so it's up to us
|
207
|
+
# to accurately mimic the filtering in our stubbed responses.
|
208
|
+
DATASTORE_BULK_FILTER_PATH = [
|
209
|
+
# The key under `items` names the type of operation (e.g. `index` or `update`) and
|
210
|
+
# we use a `*` for it since we always use that key, regardless of which operation it is.
|
211
|
+
"items.*.status", "items.*.result", "items.*.error"
|
212
|
+
].join(",")
|
213
|
+
|
214
|
+
# HTTP header set by `elasticgraph-graphql_lambda` to indicate the AWS ARN of the caller.
|
215
|
+
GRAPHQL_LAMBDA_AWS_ARN_HEADER = "X-AWS-LAMBDA-CALLER-ARN"
|
216
|
+
|
217
|
+
# TODO(steep): it complains about `define_schema` not being defined but it is defined
|
218
|
+
# in another file; I shouldn't have to say it's dynamic here. For now this works though.
|
219
|
+
# @dynamic self.define_schema
|
220
|
+
end
|
@@ -0,0 +1,99 @@
|
|
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
|
+
module ElasticGraph
|
10
|
+
class Error < StandardError
|
11
|
+
end
|
12
|
+
|
13
|
+
class CursorEncoderError < Error
|
14
|
+
end
|
15
|
+
|
16
|
+
class InvalidSortFieldsError < CursorEncoderError
|
17
|
+
end
|
18
|
+
|
19
|
+
class InvalidCursorError < CursorEncoderError
|
20
|
+
end
|
21
|
+
|
22
|
+
class CursorEncodingError < CursorEncoderError
|
23
|
+
end
|
24
|
+
|
25
|
+
class CountUnavailableError < Error
|
26
|
+
end
|
27
|
+
|
28
|
+
class InvalidArgumentValueError < Error
|
29
|
+
end
|
30
|
+
|
31
|
+
class InvalidAggregationKeyError < Error
|
32
|
+
end
|
33
|
+
|
34
|
+
class InvalidMergeError < Error
|
35
|
+
end
|
36
|
+
|
37
|
+
class QueryMergeError < Error
|
38
|
+
end
|
39
|
+
|
40
|
+
class SchemaError < Error
|
41
|
+
end
|
42
|
+
|
43
|
+
class InvalidGraphQLNameError < SchemaError
|
44
|
+
end
|
45
|
+
|
46
|
+
class NotFoundError < Error
|
47
|
+
end
|
48
|
+
|
49
|
+
class SearchFailedError < Error
|
50
|
+
end
|
51
|
+
|
52
|
+
class RequestExceededDeadlineError < SearchFailedError
|
53
|
+
end
|
54
|
+
|
55
|
+
class IdentifyDocumentVersionsFailedError < Error
|
56
|
+
end
|
57
|
+
|
58
|
+
class IndexOperationError < Error
|
59
|
+
end
|
60
|
+
|
61
|
+
class ClusterOperationError < Error
|
62
|
+
end
|
63
|
+
|
64
|
+
class InvalidEventIDError < Error
|
65
|
+
end
|
66
|
+
|
67
|
+
class UnsupportedOperationError < Error
|
68
|
+
end
|
69
|
+
|
70
|
+
class InvalidExtensionError < Error
|
71
|
+
end
|
72
|
+
|
73
|
+
class ConfigError < Error
|
74
|
+
end
|
75
|
+
|
76
|
+
class ConfigSettingNotSetError < ConfigError
|
77
|
+
end
|
78
|
+
|
79
|
+
class ConfigCannotBeMutatedError < ConfigError
|
80
|
+
end
|
81
|
+
|
82
|
+
class UnknownYAMLSettingError < ConfigError
|
83
|
+
end
|
84
|
+
|
85
|
+
class InvalidScriptDirectoryError < Error
|
86
|
+
end
|
87
|
+
|
88
|
+
class MissingSchemaArtifactError < Error
|
89
|
+
end
|
90
|
+
|
91
|
+
class S3OperationFailedError < Error
|
92
|
+
end
|
93
|
+
|
94
|
+
class MessageIdsMissingError < Error
|
95
|
+
end
|
96
|
+
|
97
|
+
class BadDatastoreRequest < Error
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,31 @@
|
|
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
|
+
module ElasticGraph
|
10
|
+
module Support
|
11
|
+
module FaradayMiddleware
|
12
|
+
# Custom Faraday middleware that forces `msearch` calls to use an HTTP GET instead of an HTTP POST. While not
|
13
|
+
# necessary, it preserves a useful property: all "read" calls made by ElasticGraph use an HTTP GET, and HTTP POST
|
14
|
+
# requests are "write" calls. This allows the access policy to only grant HTTP GET access from the GraphQL endpoint,
|
15
|
+
# which leads to a more secure setup (as the GraphQL endpoint can be blocked from performing any writes).
|
16
|
+
#
|
17
|
+
# Note: before elasticsearch-ruby 7.9.0, `msearch` used an HTTP GET request, so this simply restores that behavior.
|
18
|
+
# This results in an HTTP GET with a request body, but it works just fine and its what the Ruby Elasticsearch client
|
19
|
+
# did for years.
|
20
|
+
#
|
21
|
+
# For more info, see: https://github.com/elastic/elasticsearch-ruby/issues/1005
|
22
|
+
MSearchUsingGetInsteadOfPost = ::Data.define(:app) do
|
23
|
+
# @implements MSearchUsingGetInsteadOfPost
|
24
|
+
def call(env)
|
25
|
+
env.method = :get if env.url.path.to_s.end_with?("/_msearch")
|
26
|
+
app.call(env)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,36 @@
|
|
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
|
+
|
12
|
+
module ElasticGraph
|
13
|
+
module Support
|
14
|
+
module FaradayMiddleware
|
15
|
+
# Faraday supports specifying a timeout at both the client level (when building the Faraday connection) or on a
|
16
|
+
# per-request basis. We want to specify it on a per-request basis, but unfortunately, the Elasticsearch/OpenSearch
|
17
|
+
# clients don't provide any per-request API to specify the timeout (it only supports it when instantiating your
|
18
|
+
# client).
|
19
|
+
#
|
20
|
+
# This middleware helps us work around this deficiency by looking for the TIMEOUT_MS_HEADER. If present, it deletes
|
21
|
+
# it from the headers and instead sets it as the request timeout.
|
22
|
+
SupportTimeouts = ::Data.define(:app) do
|
23
|
+
# @implements SupportTimeouts
|
24
|
+
def call(env)
|
25
|
+
if (timeout_ms = env.request_headers.delete(TIMEOUT_MS_HEADER))
|
26
|
+
env.request.timeout = timeout_ms / 1000.0
|
27
|
+
end
|
28
|
+
|
29
|
+
app.call(env)
|
30
|
+
rescue ::Faraday::TimeoutError
|
31
|
+
raise RequestExceededDeadlineError, "Datastore request exceeded timeout of #{timeout_ms} ms."
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,53 @@
|
|
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 "yaml"
|
10
|
+
|
11
|
+
module ElasticGraph
|
12
|
+
module Support
|
13
|
+
module FromYamlFile
|
14
|
+
# Factory method that will build an instance from the provided `yaml_file`.
|
15
|
+
# `datastore_client_customization_block:` can be passed to customize the datastore clients.
|
16
|
+
# In addition, a block is accepted that can prepare the settings before the object is built
|
17
|
+
# (e.g. to override specific settings).
|
18
|
+
def from_yaml_file(yaml_file, datastore_client_customization_block: nil)
|
19
|
+
parsed_yaml = ::YAML.safe_load_file(yaml_file, aliases: true)
|
20
|
+
parsed_yaml = yield(parsed_yaml) if block_given?
|
21
|
+
from_parsed_yaml(parsed_yaml, &datastore_client_customization_block)
|
22
|
+
end
|
23
|
+
|
24
|
+
# An extension module that provides a `from_yaml_file` factory method on a `RakeTasks` class.
|
25
|
+
#
|
26
|
+
# This is designed for a `RakeTasks` class that needs an ElasticGraph component (e.g. an
|
27
|
+
# `ElasticGraph::GraphQL`, `ElasticGraph::Admin`, or `ElasticGraph::Indexer` instance).
|
28
|
+
# When the schema artifacts are out of date, loading those components can fail. This gracefully
|
29
|
+
# handles that for you, giving you clear instructions of what to do when this happens.
|
30
|
+
#
|
31
|
+
# This requires the `RakeTasks` class to accept the ElasticGraph component instance via a block
|
32
|
+
# so that it happens lazily.
|
33
|
+
class ForRakeTasks < ::Module
|
34
|
+
# @dynamic from_yaml_file
|
35
|
+
|
36
|
+
def initialize(component_class)
|
37
|
+
define_method :from_yaml_file do |yaml_file, *args, **options|
|
38
|
+
__skip__ = new(*args, **options) do
|
39
|
+
component_class.from_yaml_file(yaml_file)
|
40
|
+
rescue => e
|
41
|
+
raise <<~EOS
|
42
|
+
Failed to load `#{component_class}` with `#{yaml_file}`. This can happen if the schema artifacts are out of date.
|
43
|
+
Run `rake schema_artifacts:dump` and try again.
|
44
|
+
|
45
|
+
#{e.class}: #{e.message}
|
46
|
+
EOS
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,66 @@
|
|
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 "json"
|
10
|
+
|
11
|
+
module ElasticGraph
|
12
|
+
module Support
|
13
|
+
# Utility module that provides helper methods for generating well-formatted GraphQL syntax.
|
14
|
+
module GraphQLFormatter
|
15
|
+
# Formats the given hash as an argument list. If `args` is empty, returns an empty string.
|
16
|
+
# Otherwise, wraps the args list in parens. This allows the returned string to be appended
|
17
|
+
# to a field or directive, and it'll correctly use parens (or not) based on if there are args
|
18
|
+
# or not.
|
19
|
+
def self.format_args(**args)
|
20
|
+
return "" if args.empty?
|
21
|
+
"(#{serialize(args, wrap_hash_with_braces: false)})"
|
22
|
+
end
|
23
|
+
|
24
|
+
# Formats the given value in GraphQL syntax. This method was derived
|
25
|
+
# from a similar method from the graphql-ruby gem:
|
26
|
+
#
|
27
|
+
# https://github.com/rmosolgo/graphql-ruby/blob/v1.11.4/lib/graphql/language.rb#L17-L33
|
28
|
+
#
|
29
|
+
# We don't want to use that method because it is marked as `@api private`, indicating
|
30
|
+
# it could be removed in any release of the graphql gem. If we used it, it could hinder
|
31
|
+
# future upgrades.
|
32
|
+
#
|
33
|
+
# Our implementation here differs in a few ways:
|
34
|
+
#
|
35
|
+
# - case statement instead of multiple `if value.is_a?` checks (a bit cleaner)
|
36
|
+
# - `wrap_hash_with_braces` since we do not want to wrap an args hash with braces.
|
37
|
+
# - Readable spacing has been added so we get `foo: [1, 2], bar: 3` instead of `foo:[1,2],bar:3`.
|
38
|
+
# - Symbol support has been added. Symbols are converted to strings (with no quotes), allowing
|
39
|
+
# callers to pass them for GraphQL enums.
|
40
|
+
# - We've removed the `quirks_mode: true` flag passed to `JSON.generate` since it has been
|
41
|
+
# deprecated for a while: https://github.com/flori/json/issues/309
|
42
|
+
def self.serialize(value, wrap_hash_with_braces: true)
|
43
|
+
case value
|
44
|
+
when ::Hash
|
45
|
+
serialized_hash = value.map do |k, v|
|
46
|
+
"#{k}: #{serialize v}"
|
47
|
+
end.join(", ")
|
48
|
+
|
49
|
+
return serialized_hash unless wrap_hash_with_braces
|
50
|
+
|
51
|
+
"{#{serialized_hash}}"
|
52
|
+
when ::Array
|
53
|
+
serialized_array = value.map do |v|
|
54
|
+
serialize v
|
55
|
+
end.join(", ")
|
56
|
+
|
57
|
+
"[#{serialized_array}]"
|
58
|
+
when ::Symbol
|
59
|
+
value.to_s
|
60
|
+
else
|
61
|
+
::JSON.generate(value)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|