mongo 2.18.2 → 2.18.3

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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/README.md +1 -1
  4. data/lib/mongo/collection/view/iterable.rb +15 -0
  5. data/lib/mongo/collection/view.rb +1 -0
  6. data/lib/mongo/crypt/auto_encrypter.rb +1 -0
  7. data/lib/mongo/crypt/explicit_encrypter.rb +1 -0
  8. data/lib/mongo/crypt.rb +11 -0
  9. data/lib/mongo/grid/file/chunk.rb +2 -1
  10. data/lib/mongo/grid/file/info.rb +2 -1
  11. data/lib/mongo/protocol/bit_vector.rb +3 -1
  12. data/lib/mongo/protocol/caching_hash.rb +3 -20
  13. data/lib/mongo/protocol/message.rb +4 -8
  14. data/lib/mongo/protocol/msg.rb +1 -0
  15. data/lib/mongo/protocol/serializers.rb +24 -17
  16. data/lib/mongo/server/app_metadata/environment.rb +255 -0
  17. data/lib/mongo/server/app_metadata/truncator.rb +142 -0
  18. data/lib/mongo/server/app_metadata.rb +29 -42
  19. data/lib/mongo/version.rb +1 -1
  20. data/spec/integration/connection/faas_env_spec.rb +63 -0
  21. data/spec/integration/find_options_spec.rb +227 -0
  22. data/spec/integration/ocsp_verifier_spec.rb +1 -1
  23. data/spec/lite_spec_helper.rb +9 -0
  24. data/spec/mongo/address_spec.rb +1 -1
  25. data/spec/mongo/client_construction_spec.rb +3 -3
  26. data/spec/mongo/client_spec.rb +1 -9
  27. data/spec/mongo/cluster_spec.rb +2 -2
  28. data/spec/mongo/crypt_spec.rb +21 -0
  29. data/spec/mongo/index/view_spec.rb +1 -0
  30. data/spec/mongo/protocol/caching_hash_spec.rb +0 -45
  31. data/spec/mongo/protocol/msg_spec.rb +2 -4
  32. data/spec/mongo/server/app_metadata/environment_spec.rb +193 -0
  33. data/spec/mongo/server/app_metadata/truncator_spec.rb +158 -0
  34. data/spec/mongo/server/app_metadata_spec.rb +33 -47
  35. data/spec/mongo/socket/ssl_spec.rb +2 -8
  36. data/spec/runners/crud/requirement.rb +2 -2
  37. data/spec/shared/lib/mrss/docker_runner.rb +4 -0
  38. data/spec/shared/lib/mrss/lite_constraints.rb +8 -0
  39. data/spec/shared/lib/mrss/server_version_registry.rb +16 -23
  40. data/spec/shared/share/Dockerfile.erb +24 -19
  41. data/spec/shared/shlib/server.sh +31 -7
  42. data/spec/shared/shlib/set_env.sh +4 -4
  43. data/spec/solo/clean_exit_spec.rb +3 -10
  44. data/spec/spec_tests/data/change_streams_unified/change-streams-showExpandedEvents.yml +15 -6
  45. data/spec/spec_tests/data/command_monitoring_unified/redacted-commands.yml +8 -0
  46. data/spec/support/aws_utils.rb +3 -3
  47. data/spec/support/certificates/atlas-ocsp-ca.crt +67 -67
  48. data/spec/support/certificates/atlas-ocsp.crt +103 -103
  49. data/spec/support/shared/app_metadata.rb +14 -2
  50. data.tar.gz.sig +0 -0
  51. metadata +1155 -1138
  52. metadata.gz.sig +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 44bd3e615b9e0e11f3803eae60a44b56fe5be326b7dd8f78b3c5aa42fbf7e621
4
- data.tar.gz: 124bfd17f02e5b929e8b40dc797146770096270a83f7ea2389890efe83af1fbe
3
+ metadata.gz: 0c36a5a34e34ad90dc310f1151d98257f36c6c2bbea07282963f631f06a450eb
4
+ data.tar.gz: 220b2abd528a4f540d08cf08e78e256be845ae5b9bcf41f52f92ce9c98e554b5
5
5
  SHA512:
6
- metadata.gz: 675df13e076a6a8b94e187e493cefb8fe4e1697cafdb4241898519f55b5abf3cceff7db1e9424bc2defdcba5a218e1bd8b149572ab891319c7c8a42f9699706a
7
- data.tar.gz: 7593ef22c16038dc53e7e4e04c615dbc39c083bdffb6ef7d99f583d64613909079c7edb2b5e367117e0b3dca19a0a57ba3eb0ac92701bde48d750557a17cf567
6
+ metadata.gz: 8fd8cdeee661a65f74b3825db234492357bb82f3b4ec595f0df9477e0b03f62ac093c33d31a0ff7b766868556662b32ee74c3ff87be379cda4bc5363e2e37b0e
7
+ data.tar.gz: 2d202097305e6b8fa1fba5541fe8d6d630f1beb96cef753294d16ccfbaa1d114547208868d16fc3b48a9e5b897144edff580bcb8edab28123a089f32ded1340e
checksums.yaml.gz.sig CHANGED
Binary file
data/README.md CHANGED
@@ -5,7 +5,7 @@ MongoDB Ruby Driver
5
5
 
6
6
  The officially supported Ruby driver for [MongoDB](https://www.mongodb.org/).
7
7
 
8
- The Ruby driver supports Ruby 2.5-3.0 and JRuby 9.2.
8
+ The Ruby driver supports Ruby 2.5-3.2 and JRuby 9.2-9.3.
9
9
 
10
10
  ## Documentation
11
11
 
@@ -185,6 +185,8 @@ module Mongo
185
185
  collection.client.log_warn("The :oplog_replay option is deprecated and ignored by MongoDB 4.4 and later")
186
186
  end
187
187
 
188
+ maybe_set_tailable_options(spec)
189
+
188
190
  if explained?
189
191
  spec[:explain] = options[:explain]
190
192
  Operation::Explain.new(spec)
@@ -200,6 +202,19 @@ module Mongo
200
202
  def use_query_cache?
201
203
  QueryCache.enabled? && !collection.system_collection?
202
204
  end
205
+
206
+ # Add tailable cusror options to the command specifiction if needed.
207
+ #
208
+ # @param [ Hash ] spec The command specification.
209
+ def maybe_set_tailable_options(spec)
210
+ case cursor_type
211
+ when :tailable
212
+ spec[:tailable] = true
213
+ when :tailable_await
214
+ spec[:tailable] = true
215
+ spec[:await_data] = true
216
+ end
217
+ end
203
218
  end
204
219
  end
205
220
  end
@@ -127,6 +127,7 @@ module Mongo
127
127
  # return in each response from MongoDB.
128
128
  # @option options [ Hash ] :collation The collation to use.
129
129
  # @option options [ String ] :comment Associate a comment with the query.
130
+ # @option options [ :tailable, :tailable_await ] :cursor_type The type of cursor to use.
130
131
  # @option options [ Hash ] :explain Execute an explain with the provided
131
132
  # explain options (known options are :verbose and :verbosity) rather
132
133
  # than a find.
@@ -91,6 +91,7 @@ module Mongo
91
91
  # @raise [ ArgumentError ] If required options are missing or incorrectly
92
92
  # formatted.
93
93
  def initialize(options)
94
+ Crypt.validate_ffi!
94
95
  # Note that this call may eventually, via other method invocations,
95
96
  # create additional clients which have to be cleaned up.
96
97
  @options = set_default_options(options).freeze
@@ -36,6 +36,7 @@ module Mongo
36
36
  # should be hashes of TLS connection options. The options are equivalent
37
37
  # to TLS connection options of Mongo::Client.
38
38
  def initialize(key_vault_client, key_vault_namespace, kms_providers, kms_tls_options)
39
+ Crypt.validate_ffi!
39
40
  @crypt_handle = Handle.new(
40
41
  kms_providers,
41
42
  kms_tls_options,
data/lib/mongo/crypt.rb CHANGED
@@ -35,5 +35,16 @@ module Mongo
35
35
  autoload(:ExplicitEncrypter, 'mongo/crypt/explicit_encrypter')
36
36
  autoload(:AutoEncrypter, 'mongo/crypt/auto_encrypter')
37
37
  autoload(:KMS, 'mongo/crypt/kms')
38
+
39
+ def validate_ffi!
40
+ return if defined?(FFI)
41
+
42
+ require 'ffi'
43
+ rescue LoadError => e
44
+ raise Error::UnmetDependency, 'Cannot enable encryption because the ffi gem ' \
45
+ "has not been installed. Add \"gem 'ffi'\" to your Gemfile and run " \
46
+ "\"bundle install\" to install the gem. (#{e.class}: #{e})"
47
+ end
48
+ module_function :validate_ffi!
38
49
  end
39
50
  end
@@ -133,11 +133,12 @@ module Mongo
133
133
  #
134
134
  # @param [ BSON::ByteBuffer ] buffer The encoded BSON buffer to append to.
135
135
  # @param [ true, false ] validating_keys Whether keys should be validated when serializing.
136
+ # This option is deprecated and will not be used. It will removed in version 3.0.
136
137
  #
137
138
  # @return [ String ] The raw BSON data.
138
139
  #
139
140
  # @since 2.0.0
140
- def to_bson(buffer = BSON::ByteBuffer.new, validating_keys = BSON::Config.validating_keys?)
141
+ def to_bson(buffer = BSON::ByteBuffer.new, validating_keys = nil)
141
142
  document.to_bson(buffer)
142
143
  end
143
144
 
@@ -228,11 +228,12 @@ module Mongo
228
228
  #
229
229
  # @param [ BSON::ByteBuffer ] buffer The encoded BSON buffer to append to.
230
230
  # @param [ true, false ] validating_keys Whether keys should be validated when serializing.
231
+ # This option is deprecated and will not be used. It will removed in version 3.0.
231
232
  #
232
233
  # @return [ String ] The raw BSON data.
233
234
  #
234
235
  # @since 2.0.0
235
- def to_bson(buffer = BSON::ByteBuffer.new, validating_keys = BSON::Config.validating_keys?)
236
+ def to_bson(buffer = BSON::ByteBuffer.new, validating_keys = nil)
236
237
  if @client_md5 && !document[:md5]
237
238
  document[:md5] = @client_md5.hexdigest
238
239
  end
@@ -39,9 +39,11 @@ module Mongo
39
39
  #
40
40
  # @param buffer [ String ] Buffer to receive the serialized vector
41
41
  # @param value [ Array<Symbol> ] Array of flags to encode
42
+ # @param [ true, false ] validating_keys Whether keys should be validated when serializing.
43
+ # This option is deprecated and will not be used. It will removed in version 3.0.
42
44
  #
43
45
  # @return [ String ] Buffer that received the serialized vector
44
- def serialize(buffer, value, validating_keys = BSON::Config.validating_keys?)
46
+ def serialize(buffer, value, validating_keys = nil)
45
47
  bits = 0
46
48
  value.each { |flag| bits |= (@masks[flag] || 0) }
47
49
  buffer.put_int32(bits)
@@ -38,32 +38,15 @@ module Mongo
38
38
  #
39
39
  # @param [ BSON::ByteBuffer ] buffer The encoded BSON buffer to append to.
40
40
  # @param [ true, false ] validating_keys Whether keys should be validated when serializing.
41
+ # This option is deprecated and will not be used. It will removed in version 3.0.
41
42
  #
42
43
  # @return [ BSON::ByteBuffer ] The buffer with the encoded object.
43
- def to_bson(buffer = BSON::ByteBuffer.new, validating_keys = BSON::Config.validating_keys?)
44
+ def to_bson(buffer = BSON::ByteBuffer.new, validating_keys = nil)
44
45
  if !@bytes
45
- @bytes = @hash.to_bson(BSON::ByteBuffer.new, validating_keys).to_s
46
- elsif needs_validation?(validating_keys)
47
- @validated = true
48
- return @hash.to_bson(buffer, validating_keys)
46
+ @bytes = @hash.to_bson(BSON::ByteBuffer.new).to_s
49
47
  end
50
- @validated ||= validating_keys
51
48
  buffer.put_bytes(@bytes)
52
49
  end
53
-
54
- private
55
-
56
- # Checks the current value for validating keys, and whether or not this
57
- # bson has been validated in the past, and decides if we need to recalculate
58
- # the to_bson to check the validations.
59
- #
60
- # @param [ true, false ] validating_keys Whether keys should be validated when serializing.
61
- #
62
- # @return [ true, false ] Whether or not the bson needs to be recalculated
63
- # with validation.
64
- def needs_validation?(validating_keys)
65
- !@validated && validating_keys
66
- end
67
50
  end
68
51
  end
69
52
  end
@@ -354,16 +354,16 @@ module Mongo
354
354
  if field[:multi]
355
355
  value.each do |item|
356
356
  if field[:type].respond_to?(:size_limited?)
357
- field[:type].serialize(buffer, item, max_bson_size, validating_keys?)
357
+ field[:type].serialize(buffer, item, max_bson_size)
358
358
  else
359
- field[:type].serialize(buffer, item, validating_keys?)
359
+ field[:type].serialize(buffer, item)
360
360
  end
361
361
  end
362
362
  else
363
363
  if field[:type].respond_to?(:size_limited?)
364
- field[:type].serialize(buffer, value, max_bson_size, validating_keys?)
364
+ field[:type].serialize(buffer, value, max_bson_size)
365
365
  else
366
- field[:type].serialize(buffer, value, validating_keys?)
366
+ field[:type].serialize(buffer, value)
367
367
  end
368
368
  end
369
369
  end
@@ -456,10 +456,6 @@ module Mongo
456
456
  field[:type].deserialize(io, options)
457
457
  )
458
458
  end
459
-
460
- def validating_keys?
461
- @options[:validating_keys] if @options
462
- end
463
459
  end
464
460
  end
465
461
  end
@@ -58,6 +58,7 @@ module Mongo
58
58
  # @option options [ true, false ] validating_keys Whether keys should be
59
59
  # validated for being valid document keys (i.e. not begin with $ and
60
60
  # not contain dots).
61
+ # This option is deprecated and will not be used. It will removed in version 3.0.
61
62
  #
62
63
  # @api private
63
64
  #
@@ -51,9 +51,11 @@ module Mongo
51
51
  #
52
52
  # @param buffer [ String ] Buffer to receive the serialized value.
53
53
  # @param value [ String ] Header value to be serialized.
54
+ # @param [ true, false ] validating_keys Whether keys should be validated when serializing.
55
+ # This option is deprecated and will not be used. It will removed in version 3.0.
54
56
  #
55
57
  # @return [ String ] Buffer with serialized value.
56
- def self.serialize(buffer, value, validating_keys = BSON::Config.validating_keys?)
58
+ def self.serialize(buffer, value, validating_keys = nil)
57
59
  buffer.put_bytes(value.pack(HEADER_PACK))
58
60
  end
59
61
 
@@ -80,7 +82,7 @@ module Mongo
80
82
  # @param value [ String ] The string to be serialized.
81
83
  #
82
84
  # @return [ String ] Buffer with serialized value.
83
- def self.serialize(buffer, value, validating_keys = BSON::Config.validating_keys?)
85
+ def self.serialize(buffer, value, validating_keys = nil)
84
86
  buffer.put_cstring(value)
85
87
  end
86
88
  end
@@ -96,7 +98,7 @@ module Mongo
96
98
  # @param value [ Fixnum ] Ignored value.
97
99
  #
98
100
  # @return [ String ] Buffer with serialized value.
99
- def self.serialize(buffer, value, validating_keys = BSON::Config.validating_keys?)
101
+ def self.serialize(buffer, value, validating_keys = nil)
100
102
  buffer.put_int32(ZERO)
101
103
  end
102
104
  end
@@ -112,7 +114,7 @@ module Mongo
112
114
  # @param value [ Integer | BSON::Int32 ] 32-bit integer to be serialized.
113
115
  #
114
116
  # @return [String] Buffer with serialized value.
115
- def self.serialize(buffer, value, validating_keys = BSON::Config.validating_keys?)
117
+ def self.serialize(buffer, value, validating_keys = nil)
116
118
  if value.is_a?(BSON::Int32)
117
119
  if value.respond_to?(:value)
118
120
  # bson-ruby >= 4.6.0
@@ -146,7 +148,7 @@ module Mongo
146
148
  # @param value [ Integer | BSON::Int64 ] 64-bit integer to be serialized.
147
149
  #
148
150
  # @return [ String ] Buffer with serialized value.
149
- def self.serialize(buffer, value, validating_keys = BSON::Config.validating_keys?)
151
+ def self.serialize(buffer, value, validating_keys = nil)
150
152
  if value.is_a?(BSON::Int64)
151
153
  if value.respond_to?(:value)
152
154
  # bson-ruby >= 4.6.0
@@ -182,19 +184,20 @@ module Mongo
182
184
  # @param [ Array<Hash, BSON::Document> ] value The sections to be serialized.
183
185
  # @param [ Fixnum ] max_bson_size The max bson size of documents in the sections.
184
186
  # @param [ true, false ] validating_keys Whether to validate document keys.
187
+ # This option is deprecated and will not be used. It will removed in version 3.0.
185
188
  #
186
189
  # @return [ BSON::ByteBuffer ] Buffer with serialized value.
187
190
  #
188
191
  # @since 2.5.0
189
- def self.serialize(buffer, value, max_bson_size = nil, validating_keys = BSON::Config.validating_keys?)
192
+ def self.serialize(buffer, value, max_bson_size = nil, validating_keys = nil)
190
193
  value.each do |section|
191
194
  case section[:type]
192
195
  when PayloadZero::TYPE
193
- PayloadZero.serialize(buffer, section[:payload], max_bson_size, false)
196
+ PayloadZero.serialize(buffer, section[:payload], max_bson_size)
194
197
  when nil
195
- PayloadZero.serialize(buffer, section[:payload], max_bson_size, false)
198
+ PayloadZero.serialize(buffer, section[:payload], max_bson_size)
196
199
  when PayloadOne::TYPE
197
- PayloadOne.serialize(buffer, section[:payload], max_bson_size, validating_keys)
200
+ PayloadOne.serialize(buffer, section[:payload], max_bson_size)
198
201
  else
199
202
  raise Error::UnknownPayloadType.new(section[:type])
200
203
  end
@@ -259,13 +262,14 @@ module Mongo
259
262
  # @param [ BSON::Document, Hash ] value The object to serialize.
260
263
  # @param [ Fixnum ] max_bson_size The max bson size of documents in the section.
261
264
  # @param [ true, false ] validating_keys Whether to validate document keys.
265
+ # This option is deprecated and will not be used. It will removed in version 3.0.
262
266
  #
263
267
  # @return [ BSON::ByteBuffer ] Buffer with serialized value.
264
268
  #
265
269
  # @since 2.5.0
266
- def self.serialize(buffer, value, max_bson_size = nil, validating_keys = BSON::Config.validating_keys?)
270
+ def self.serialize(buffer, value, max_bson_size = nil, validating_keys = nil)
267
271
  buffer.put_byte(TYPE_BYTE)
268
- Serializers::Document.serialize(buffer, value, max_bson_size, validating_keys)
272
+ Serializers::Document.serialize(buffer, value, max_bson_size)
269
273
  end
270
274
 
271
275
  # Deserializes a section of payload type 0 of an OP_MSG from the IO stream.
@@ -307,17 +311,18 @@ module Mongo
307
311
  # @param [ BSON::Document, Hash ] value The object to serialize.
308
312
  # @param [ Fixnum ] max_bson_size The max bson size of documents in the section.
309
313
  # @param [ true, false ] validating_keys Whether to validate document keys.
314
+ # This option is deprecated and will not be used. It will removed in version 3.0.
310
315
  #
311
316
  # @return [ BSON::ByteBuffer ] Buffer with serialized value.
312
317
  #
313
318
  # @since 2.5.0
314
- def self.serialize(buffer, value, max_bson_size = nil, validating_keys = BSON::Config.validating_keys?)
319
+ def self.serialize(buffer, value, max_bson_size = nil, validating_keys = nil)
315
320
  buffer.put_byte(TYPE_BYTE)
316
321
  start = buffer.length
317
322
  buffer.put_int32(0) # hold for size
318
323
  buffer.put_cstring(value[:identifier])
319
324
  value[:sequence].each do |document|
320
- Document.serialize(buffer, document, max_bson_size, validating_keys)
325
+ Document.serialize(buffer, document, max_bson_size)
321
326
  end
322
327
  buffer.replace_int32(start, buffer.length - start)
323
328
  end
@@ -356,9 +361,9 @@ module Mongo
356
361
  # @param value [ Hash ] Document to serialize as BSON.
357
362
  #
358
363
  # @return [ String ] Buffer with serialized value.
359
- def self.serialize(buffer, value, max_bson_size = nil, validating_keys = BSON::Config.validating_keys?)
364
+ def self.serialize(buffer, value, max_bson_size = nil, validating_keys = nil)
360
365
  start_size = buffer.length
361
- value.to_bson(buffer, validating_keys)
366
+ value.to_bson(buffer)
362
367
  serialized_size = buffer.length - start_size
363
368
  if max_bson_size && serialized_size > max_bson_size
364
369
  raise Error::MaxBSONSize,
@@ -401,11 +406,12 @@ module Mongo
401
406
  # @param [ BSON::ByteBuffer ] buffer Buffer to receive the single byte.
402
407
  # @param [ String ] value The byte to write to the buffer.
403
408
  # @param [ true, false ] validating_keys Whether to validate keys.
409
+ # This option is deprecated and will not be used. It will removed in version 3.0.
404
410
  #
405
411
  # @return [ BSON::ByteBuffer ] Buffer with serialized value.
406
412
  #
407
413
  # @since 2.5.0
408
- def self.serialize(buffer, value, validating_keys = BSON::Config.validating_keys?)
414
+ def self.serialize(buffer, value, validating_keys = nil)
409
415
  buffer.put_byte(value)
410
416
  end
411
417
 
@@ -432,11 +438,12 @@ module Mongo
432
438
  # @param [ BSON::ByteBuffer ] buffer Buffer to receive the bytes.
433
439
  # @param [ String ] value The bytes to write to the buffer.
434
440
  # @param [ true, false ] validating_keys Whether to validate keys.
441
+ # This option is deprecated and will not be used. It will removed in version 3.0.
435
442
  #
436
443
  # @return [ BSON::ByteBuffer ] Buffer with serialized value.
437
444
  #
438
445
  # @since 2.5.0
439
- def self.serialize(buffer, value, validating_keys = BSON::Config.validating_keys?)
446
+ def self.serialize(buffer, value, validating_keys = nil)
440
447
  buffer.put_bytes(value)
441
448
  end
442
449
 
@@ -0,0 +1,255 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (C) 2016-2023 MongoDB Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ module Mongo
18
+ class Server
19
+ class AppMetadata
20
+ # Implements the logic from the handshake spec, for deducing and
21
+ # reporting the current FaaS environment in which the program is
22
+ # executing.
23
+ #
24
+ # @api private
25
+ class Environment
26
+ # Error class for reporting that too many discriminators were found
27
+ # in the environment. (E.g. if the environment reports that it is
28
+ # running under both AWS and Azure.)
29
+ class TooManyEnvironments < Mongo::Error; end
30
+
31
+ # Error class for reporting that a required environment variable is
32
+ # missing.
33
+ class MissingVariable < Mongo::Error; end
34
+
35
+ # Error class for reporting that the wrong type was given for a
36
+ # field.
37
+ class TypeMismatch < Mongo::Error; end
38
+
39
+ # Error class for reporting that the value for a field is too long.
40
+ class ValueTooLong < Mongo::Error; end
41
+
42
+ # This value is not explicitly specified in the spec, only implied to be
43
+ # less than 512.
44
+ MAXIMUM_VALUE_LENGTH = 500
45
+
46
+ # The mapping that determines which FaaS environment is active, based
47
+ # on which environment variable(s) are present.
48
+ DISCRIMINATORS = {
49
+ 'AWS_EXECUTION_ENV' => { pattern: /^AWS_Lambda_/, name: 'aws.lambda' },
50
+ 'AWS_LAMBDA_RUNTIME_API' => { name: 'aws.lambda' },
51
+ 'FUNCTIONS_WORKER_RUNTIME' => { name: 'azure.func' },
52
+ 'K_SERVICE' => { name: 'gcp.func' },
53
+ 'FUNCTION_NAME' => { name: 'gcp.func' },
54
+ 'VERCEL' => { name: 'vercel' },
55
+ }.freeze
56
+
57
+ # Describes how to coerce values of the specified type.
58
+ COERCIONS = {
59
+ string: ->(v) { String(v) },
60
+ integer: ->(v) { Integer(v) }
61
+ }.freeze
62
+
63
+ # Describes which fields are required for each FaaS environment,
64
+ # along with their expected types, and how they should be named in
65
+ # the handshake document.
66
+ FIELDS = {
67
+ 'aws.lambda' => {
68
+ 'AWS_REGION' => { field: :region, type: :string },
69
+ 'AWS_LAMBDA_FUNCTION_MEMORY_SIZE' => { field: :memory_mb, type: :integer },
70
+ },
71
+
72
+ 'azure.func' => {},
73
+
74
+ 'gcp.func' => {
75
+ 'FUNCTION_MEMORY_MB' => { field: :memory_mb, type: :integer },
76
+ 'FUNCTION_TIMEOUT_SEC' => { field: :timeout_sec, type: :integer },
77
+ 'FUNCTION_REGION' => { field: :region, type: :string },
78
+ },
79
+
80
+ 'vercel' => {
81
+ 'VERCEL_URL' => { field: :url, type: :string },
82
+ 'VERCEL_REGION' => { field: :region, type: :string },
83
+ },
84
+ }.freeze
85
+
86
+ # @return [ String | nil ] the name of the FaaS environment that was
87
+ # detected, or nil if no valid FaaS environment was detected.
88
+ attr_reader :name
89
+
90
+ # @return [ Hash | nil ] the fields describing the detected FaaS
91
+ # environment.
92
+ attr_reader :fields
93
+
94
+ # @return [ String | nil ] the error message explaining why a valid
95
+ # FaaS environment was not detected, or nil if no error occurred.
96
+ #
97
+ # @note These error messagess are not to be propogated to the
98
+ # user; they are intended only for troubleshooting and debugging.)
99
+ attr_reader :error
100
+
101
+ # Create a new AppMetadata::Environment object, initializing it from
102
+ # the current ENV variables. If no FaaS environment is detected, or
103
+ # if the environment contains invalid or contradictory state, it will
104
+ # be initialized with {{name}} set to {{nil}}.
105
+ def initialize
106
+ @error = nil
107
+ @name = detect_environment
108
+ populate_fields
109
+ rescue TooManyEnvironments => e
110
+ self.error = "too many environments detected: #{e.message}"
111
+ rescue MissingVariable => e
112
+ self.error = "missing environment variable: #{e.message}"
113
+ rescue TypeMismatch => e
114
+ self.error = e.message
115
+ rescue ValueTooLong => e
116
+ self.error = "value for #{e.message} is too long"
117
+ end
118
+
119
+ # Queries whether the current environment is a valid FaaS environment.
120
+ #
121
+ # @return [ true | false ] whether the environment is a FaaS
122
+ # environment or not.
123
+ def faas?
124
+ @name != nil
125
+ end
126
+
127
+ # Queries whether the current environment is a valid AWS Lambda
128
+ # environment.
129
+ #
130
+ # @return [ true | false ] whether the environment is a AWS Lambda
131
+ # environment or not.
132
+ def aws?
133
+ @name == 'aws.lambda'
134
+ end
135
+
136
+ # Queries whether the current environment is a valid Azure
137
+ # environment.
138
+ #
139
+ # @return [ true | false ] whether the environment is a Azure
140
+ # environment or not.
141
+ def azure?
142
+ @name == 'azure.func'
143
+ end
144
+
145
+ # Queries whether the current environment is a valid GCP
146
+ # environment.
147
+ #
148
+ # @return [ true | false ] whether the environment is a GCP
149
+ # environment or not.
150
+ def gcp?
151
+ @name == 'gcp.func'
152
+ end
153
+
154
+ # Queries whether the current environment is a valid Vercel
155
+ # environment.
156
+ #
157
+ # @return [ true | false ] whether the environment is a Vercel
158
+ # environment or not.
159
+ def vercel?
160
+ @name == 'vercel'
161
+ end
162
+
163
+ # Compiles the detected environment information into a Hash. It will
164
+ # always include a {{name}} key, but may include other keys as well,
165
+ # depending on the detected FaaS environment. (See the handshake
166
+ # spec for details.)
167
+ #
168
+ # @return [ Hash ] the detected environment information.
169
+ def to_h
170
+ fields.merge(name: name)
171
+ end
172
+
173
+ private
174
+
175
+ # Searches the DESCRIMINATORS list to see which (if any) apply to
176
+ # the current environment.
177
+ #
178
+ # @return [ String | nil ] the name of the detected FaaS provider.
179
+ #
180
+ # @raise [ TooManyEnvironments ] if the environment contains
181
+ # discriminating variables for more than one FaaS provider.
182
+ def detect_environment
183
+ matches = DISCRIMINATORS.keys.select { |k| discriminator_matches?(k) }
184
+ names = matches.map { |m| DISCRIMINATORS[m][:name] }.uniq
185
+
186
+ raise TooManyEnvironments, names.join(', ') if names.length > 1
187
+
188
+ names.first
189
+ end
190
+
191
+ # Determines whether the named environment variable exists, and (if
192
+ # a pattern has been declared for that descriminator) whether the
193
+ # pattern matches the value of the variable.
194
+ #
195
+ # @param [ String ] var the name of the environment variable
196
+ #
197
+ # @return [ true | false ] if the variable describes the current
198
+ # environment or not.
199
+ def discriminator_matches?(var)
200
+ return false unless ENV[var]
201
+
202
+ disc = DISCRIMINATORS[var]
203
+ return true unless disc[:pattern]
204
+
205
+ disc[:pattern].match?(ENV[var])
206
+ end
207
+
208
+ # Extracts environment information from the current environment
209
+ # variables, based on the detected FaaS environment. Populates the
210
+ # {{@fields}} instance variable.
211
+ def populate_fields
212
+ return unless name
213
+
214
+ @fields = FIELDS[name].each_with_object({}) do |(var, defn), fields|
215
+ fields[defn[:field]] = extract_field(var, defn)
216
+ end
217
+ end
218
+
219
+ # Extracts the named variable from the environment and validates it
220
+ # against its declared definition.
221
+ #
222
+ # @param [ String ] var The name of the environment variable to look
223
+ # for.
224
+ # @param [ Hash ] definition The definition of the field that applies
225
+ # to the named variable.
226
+ #
227
+ # @return [ Integer | String ] the validated and coerced value of the
228
+ # given environment variable.
229
+ #
230
+ # @raise [ MissingVariable ] if the environment does not include a
231
+ # variable required by the current FaaS provider.
232
+ # @raise [ ValueTooLong ] if a required variable is too long.
233
+ # @raise [ TypeMismatch ] if a required variable cannot be coerced to
234
+ # the expected type.
235
+ def extract_field(var, definition)
236
+ raise MissingVariable, var unless ENV[var]
237
+ raise ValueTooLong, var if ENV[var].length > MAXIMUM_VALUE_LENGTH
238
+
239
+ COERCIONS[definition[:type]].call(ENV[var])
240
+ rescue ArgumentError
241
+ raise TypeMismatch,
242
+ "#{var} must be #{definition[:type]} (got #{ENV[var].inspect})"
243
+ end
244
+
245
+ # Sets the error message to the given value and sets the name to nil.
246
+ #
247
+ # @param [ String ] msg The error message to store.
248
+ def error=(msg)
249
+ @name = nil
250
+ @error = msg
251
+ end
252
+ end
253
+ end
254
+ end
255
+ end