rom-ldap 0.2.2

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 (104) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +251 -0
  3. data/CONTRIBUTING.md +18 -0
  4. data/README.md +172 -0
  5. data/TODO.md +33 -0
  6. data/config/responses.yml +328 -0
  7. data/lib/dry/monitor/ldap/colorizers/default.rb +17 -0
  8. data/lib/dry/monitor/ldap/colorizers/rouge.rb +31 -0
  9. data/lib/dry/monitor/ldap/logger.rb +58 -0
  10. data/lib/rom-ldap.rb +1 -0
  11. data/lib/rom/ldap.rb +22 -0
  12. data/lib/rom/ldap/alias.rb +30 -0
  13. data/lib/rom/ldap/associations.rb +6 -0
  14. data/lib/rom/ldap/associations/core.rb +23 -0
  15. data/lib/rom/ldap/associations/many_to_many.rb +18 -0
  16. data/lib/rom/ldap/associations/many_to_one.rb +22 -0
  17. data/lib/rom/ldap/associations/one_to_many.rb +32 -0
  18. data/lib/rom/ldap/associations/one_to_one.rb +19 -0
  19. data/lib/rom/ldap/associations/self_ref.rb +35 -0
  20. data/lib/rom/ldap/attribute.rb +327 -0
  21. data/lib/rom/ldap/client.rb +185 -0
  22. data/lib/rom/ldap/client/authentication.rb +118 -0
  23. data/lib/rom/ldap/client/operations.rb +233 -0
  24. data/lib/rom/ldap/commands.rb +6 -0
  25. data/lib/rom/ldap/commands/create.rb +41 -0
  26. data/lib/rom/ldap/commands/delete.rb +17 -0
  27. data/lib/rom/ldap/commands/update.rb +35 -0
  28. data/lib/rom/ldap/constants.rb +193 -0
  29. data/lib/rom/ldap/dataset.rb +286 -0
  30. data/lib/rom/ldap/dataset/conversion.rb +62 -0
  31. data/lib/rom/ldap/dataset/dsl.rb +299 -0
  32. data/lib/rom/ldap/dataset/persistence.rb +44 -0
  33. data/lib/rom/ldap/directory.rb +126 -0
  34. data/lib/rom/ldap/directory/capabilities.rb +71 -0
  35. data/lib/rom/ldap/directory/entry.rb +200 -0
  36. data/lib/rom/ldap/directory/env.rb +155 -0
  37. data/lib/rom/ldap/directory/operations.rb +282 -0
  38. data/lib/rom/ldap/directory/password.rb +122 -0
  39. data/lib/rom/ldap/directory/root.rb +187 -0
  40. data/lib/rom/ldap/directory/tokenization.rb +66 -0
  41. data/lib/rom/ldap/directory/transactions.rb +31 -0
  42. data/lib/rom/ldap/directory/vendors/active_directory.rb +129 -0
  43. data/lib/rom/ldap/directory/vendors/apache_ds.rb +27 -0
  44. data/lib/rom/ldap/directory/vendors/e_directory.rb +16 -0
  45. data/lib/rom/ldap/directory/vendors/open_directory.rb +12 -0
  46. data/lib/rom/ldap/directory/vendors/open_dj.rb +25 -0
  47. data/lib/rom/ldap/directory/vendors/open_ldap.rb +35 -0
  48. data/lib/rom/ldap/directory/vendors/three_eight_nine.rb +16 -0
  49. data/lib/rom/ldap/directory/vendors/unknown.rb +22 -0
  50. data/lib/rom/ldap/dsl.rb +76 -0
  51. data/lib/rom/ldap/errors.rb +47 -0
  52. data/lib/rom/ldap/expression.rb +77 -0
  53. data/lib/rom/ldap/expression_encoder.rb +174 -0
  54. data/lib/rom/ldap/extensions.rb +50 -0
  55. data/lib/rom/ldap/extensions/active_support_notifications.rb +26 -0
  56. data/lib/rom/ldap/extensions/compatibility.rb +11 -0
  57. data/lib/rom/ldap/extensions/dsml.rb +165 -0
  58. data/lib/rom/ldap/extensions/msgpack.rb +23 -0
  59. data/lib/rom/ldap/extensions/optimised_json.rb +25 -0
  60. data/lib/rom/ldap/extensions/rails_log_subscriber.rb +38 -0
  61. data/lib/rom/ldap/formatter.rb +26 -0
  62. data/lib/rom/ldap/functions.rb +207 -0
  63. data/lib/rom/ldap/gateway.rb +145 -0
  64. data/lib/rom/ldap/ldif.rb +74 -0
  65. data/lib/rom/ldap/ldif/exporter.rb +77 -0
  66. data/lib/rom/ldap/ldif/importer.rb +95 -0
  67. data/lib/rom/ldap/mapper_compiler.rb +19 -0
  68. data/lib/rom/ldap/matchers.rb +69 -0
  69. data/lib/rom/ldap/message_queue.rb +7 -0
  70. data/lib/rom/ldap/oid.rb +101 -0
  71. data/lib/rom/ldap/parsers/abstract_syntax.rb +91 -0
  72. data/lib/rom/ldap/parsers/attribute.rb +290 -0
  73. data/lib/rom/ldap/parsers/filter_syntax.rb +133 -0
  74. data/lib/rom/ldap/pdu.rb +285 -0
  75. data/lib/rom/ldap/plugin/pagination.rb +145 -0
  76. data/lib/rom/ldap/plugins.rb +7 -0
  77. data/lib/rom/ldap/projection_dsl.rb +38 -0
  78. data/lib/rom/ldap/relation.rb +135 -0
  79. data/lib/rom/ldap/relation/exporting.rb +72 -0
  80. data/lib/rom/ldap/relation/reading.rb +461 -0
  81. data/lib/rom/ldap/relation/writing.rb +64 -0
  82. data/lib/rom/ldap/responses.rb +17 -0
  83. data/lib/rom/ldap/restriction_dsl.rb +45 -0
  84. data/lib/rom/ldap/schema.rb +123 -0
  85. data/lib/rom/ldap/schema/attributes_inferrer.rb +59 -0
  86. data/lib/rom/ldap/schema/dsl.rb +13 -0
  87. data/lib/rom/ldap/schema/inferrer.rb +50 -0
  88. data/lib/rom/ldap/schema/type_builder.rb +133 -0
  89. data/lib/rom/ldap/scope.rb +19 -0
  90. data/lib/rom/ldap/search_request.rb +249 -0
  91. data/lib/rom/ldap/socket.rb +210 -0
  92. data/lib/rom/ldap/tasks/ldap.rake +103 -0
  93. data/lib/rom/ldap/tasks/ldif.rake +80 -0
  94. data/lib/rom/ldap/transaction.rb +29 -0
  95. data/lib/rom/ldap/type_map.rb +88 -0
  96. data/lib/rom/ldap/types.rb +158 -0
  97. data/lib/rom/ldap/version.rb +17 -0
  98. data/lib/rom/plugins/relation/ldap/active_directory.rb +182 -0
  99. data/lib/rom/plugins/relation/ldap/auto_restrictions.rb +69 -0
  100. data/lib/rom/plugins/relation/ldap/e_directory.rb +27 -0
  101. data/lib/rom/plugins/relation/ldap/instrumentation.rb +35 -0
  102. data/lib/rouge/lexers/ldap.rb +72 -0
  103. data/lib/rouge/themes/ldap.rb +49 -0
  104. metadata +231 -0
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/core/extensions'
4
+
5
+ module ROM
6
+ module LDAP
7
+ extend Dry::Core::Extensions
8
+
9
+ # Make entry attributes suitable method names.
10
+ #
11
+ register_extension(:compatibility) do
12
+ require 'rom/ldap/extensions/compatibility'
13
+ end
14
+
15
+ #=======================================
16
+ # Exporters
17
+ #=======================================
18
+
19
+ # Add #to_dsml method to relation instance.
20
+ #
21
+ register_extension(:dsml_export) do
22
+ require 'rom/ldap/extensions/dsml'
23
+ end
24
+
25
+ # Add #to_msgpack method to relation instance.
26
+ #
27
+ register_extension(:msgpack_export) do
28
+ require 'rom/ldap/extensions/msgpack'
29
+ end
30
+
31
+ # Patch #to_json method in relation instance.
32
+ #
33
+ register_extension(:oj_export) do
34
+ require 'rom/ldap/extensions/optimised_json'
35
+ end
36
+
37
+
38
+ #=======================================
39
+ # Autoloaded for Rails
40
+ #=======================================
41
+
42
+ register_extension(:active_support_notifications) do
43
+ require 'rom/ldap/extensions/active_support_notifications'
44
+ end
45
+
46
+ register_extension(:rails_log_subscriber) do
47
+ require 'rom/ldap/extensions/rails_log_subscriber'
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/notifications'
4
+
5
+ module ROM
6
+ module LDAP
7
+ module ActiveSupportInstrumentation
8
+ def call(filter)
9
+ ActiveSupport::Notifications.instrument(
10
+ 'ldap.rom',
11
+ ldap: 'foobar',
12
+ name: instrumentation_name,
13
+ binds: filter
14
+ ) { super }
15
+ end
16
+
17
+ private
18
+
19
+ def instrumentation_name
20
+ "ROM[#{directory.type}]"
21
+ end
22
+ end
23
+
24
+ Dataset.prepend ActiveSupportInstrumentation
25
+ end
26
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rom/ldap/functions'
4
+
5
+ module ROM
6
+ module LDAP
7
+ # Assign the formatting proc used to rename the Directory::Entry attributes.
8
+ #
9
+ use_formatter Functions[:to_method_name]
10
+ end
11
+ end
@@ -0,0 +1,165 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rom/initializer'
4
+ require 'libxml'
5
+
6
+ module ROM
7
+ module LDAP
8
+ #
9
+ # Directory Service Markup Language (DSML)
10
+ #
11
+ # Refines Array and Hash with #to_dsml method.
12
+ #
13
+ # @see https://publib.boulder.ibm.com/tividd/td/ITIM/SC32-1149-01/en_US/HTML/Policy_Org_Admin395.htm
14
+ # @see Directory::Entry
15
+ # @see Relation::Exporting
16
+ #
17
+ module DSML
18
+ # Export Entry objects as DSML files.
19
+ #
20
+ # @param tuple [Entry]
21
+ #
22
+ # @api private
23
+ class Exporter
24
+
25
+ extend Initializer
26
+
27
+ include LibXML
28
+
29
+ # Dataset
30
+ #
31
+ param :tuples, type: Types::Strict::Array.of(Types::Strict::Hash)
32
+
33
+ # <?xml version="1.0" encoding="UTF-8"?>
34
+ # <dsml>
35
+ # <directory-entries>
36
+ # <entry dn="dn">
37
+ #
38
+ # @return [String]
39
+ #
40
+ # @api private
41
+ def to_dsml
42
+ doc = XML::Document.new
43
+ doc.encoding = XML::Encoding::UTF_8
44
+ doc.root = root = create_node('dsml')
45
+ root << (entries = create_node('directory-entries'))
46
+ map_tuples { |entry| entries << entry }
47
+ doc.to_s
48
+ end
49
+
50
+ private
51
+
52
+ # <entry dn="dn">
53
+ #
54
+ # @yield [LibXML::XML::Node]
55
+ #
56
+ def map_tuples
57
+ tuples.each do |tuple|
58
+ next if tuple.empty?
59
+
60
+ dn = tuple.delete('dn')
61
+ objc = tuple.delete('objectClass')
62
+
63
+ entry_node = create_node('entry', dn: dn&.first)
64
+
65
+ classes(objc) { |c| entry_node << c }
66
+
67
+ attributes(tuple) { |a| entry_node << a }
68
+
69
+ yield(entry_node)
70
+ end
71
+ end
72
+
73
+ # Returns "<objectclass/>" if param is nil
74
+ #
75
+ # <oc-value>inetOrgPerson</oc-value>
76
+ #
77
+ # @param [Array, Nil] values
78
+ #
79
+ # @yield [LibXML::XML::Node]
80
+ #
81
+ def classes(values)
82
+ class_node = create_node('objectclass')
83
+ values.to_a.each do |value|
84
+ value_node = create_node('oc-value')
85
+ value_node.content = value
86
+ class_node << value_node
87
+ end
88
+ yield(class_node)
89
+ end
90
+
91
+ # @example
92
+ #
93
+ # {'cn'=>['Peter']}
94
+ # => <attr name="cn"><value>Peter</value></attr>
95
+ #
96
+ # @yield [LibXML::XML::Node]
97
+ #
98
+ def attributes(attrs)
99
+ attrs.each do |attr_name, attr_values|
100
+ attr_node = create_node('attr', name: attr_name)
101
+ attr_values.each do |value|
102
+ value_node = create_node('value')
103
+ value_node.content = value
104
+ attr_node << value_node
105
+ end
106
+ yield(attr_node)
107
+ end
108
+ end
109
+
110
+ #
111
+ # @return [LibXML::XML::Node]
112
+ #
113
+ def create_node(type, params = EMPTY_OPTS)
114
+ node = XML::Node.new(type)
115
+ unless params.empty?
116
+ params.each do |key, value|
117
+ XML::Attr.new(node, key.to_s, value.to_s)
118
+ end
119
+ end
120
+ node
121
+ end
122
+
123
+ end
124
+
125
+ # Extend functionality of Hash class.
126
+ #
127
+ refine ::Hash do
128
+ # Convert hash to DSML format
129
+ #
130
+ # @return [String]
131
+ #
132
+ # @api public
133
+ def to_dsml
134
+ Exporter.new([self]).to_dsml
135
+ end
136
+ end
137
+
138
+ # Extend functionality of Array class.
139
+ #
140
+ refine ::Array do
141
+ # Convert array to DSML format
142
+ #
143
+ # @return [String]
144
+ #
145
+ # @api public
146
+ def to_dsml
147
+ Exporter.new(self).to_dsml
148
+ end
149
+ end
150
+ end
151
+
152
+ module DSMLExport
153
+ using DSML
154
+ #
155
+ # @return [String]
156
+ #
157
+ # @api public
158
+ def to_dsml
159
+ export.to_dsml
160
+ end
161
+ end
162
+
163
+ Relation.include DSMLExport
164
+ end
165
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'msgpack'
4
+
5
+ module ROM
6
+ module LDAP
7
+ module MsgPackExport
8
+ # Export the relation as MessagePack Binary
9
+ #
10
+ # @return [String]
11
+ #
12
+ # @example
13
+ # relation.to_msgpack
14
+ #
15
+ # @api public
16
+ def to_msgpack
17
+ export.to_msgpack
18
+ end
19
+ end
20
+
21
+ Relation.include MsgPackExport
22
+ end
23
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'oj'
4
+
5
+ module ROM
6
+ module LDAP
7
+ module OptimisedJSON
8
+ # Replace #to_json
9
+ #
10
+ # @param _opts [Mixed] compatibility with JSON.generate
11
+ #
12
+ # @return [String]
13
+ #
14
+ # @example
15
+ # relation.to_json
16
+ #
17
+ # @api public
18
+ def to_json(_opts = nil)
19
+ Oj.generate(export)
20
+ end
21
+ end
22
+
23
+ Relation.include OptimisedJSON
24
+ end
25
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/log_subscriber'
4
+
5
+ module ROM
6
+ module LDAP
7
+ class RailsLogSubscriber < ActiveSupport::LogSubscriber
8
+
9
+ def ldap(event)
10
+ return unless logger.debug?
11
+
12
+ payload = event.payload
13
+
14
+ name = format('%s (%.1fms)', payload[:name], event.duration)
15
+ ldap = payload[:ldap].squeeze(' ')
16
+ binds = payload[:binds].to_a.inspect if payload[:binds]
17
+
18
+ if odd?
19
+ name = color(name, :cyan, true)
20
+ ldap = color(ldap, nil, true)
21
+ else
22
+ name = color(name, :magenta, true)
23
+ end
24
+
25
+ debug " #{name} #{ldap} #{binds}"
26
+ end
27
+
28
+ attr_reader :odd_or_even
29
+ private :odd_or_even
30
+ def odd?
31
+ @odd_or_even = !odd_or_even
32
+ end
33
+
34
+ end
35
+
36
+ RailsLogSubscriber.attach_to(:rom)
37
+ end
38
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ROM
4
+ module LDAP
5
+ # Proc that returns input value.
6
+ #
7
+ DEFAULT_FORMATTER = ->(v) { v }
8
+
9
+ # Set/Reset the formatting proc
10
+ #
11
+ # @param func [Proc] Callable object
12
+ #
13
+ def self.use_formatter(func = nil)
14
+ @formatter = func
15
+ end
16
+
17
+ # @see 'rom/ldap/extensions/compatibility'
18
+ #
19
+ # @example
20
+ # ROM::LDAP.load_extensions :compatibility
21
+ #
22
+ def self.formatter
23
+ @formatter || DEFAULT_FORMATTER
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,207 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/transformer/all'
4
+ require 'base64'
5
+ require 'rom/support/inflector'
6
+
7
+ module ROM
8
+ module LDAP
9
+ # @api private
10
+ module Functions
11
+ extend Dry::Transformer::Registry
12
+
13
+ import Dry::Transformer::Coercions
14
+ import Dry::Transformer::ArrayTransformations
15
+ import Dry::Transformer::HashTransformations
16
+
17
+ # Build tuple from arguments.
18
+ # Translates keys into original schema names and stringify values.
19
+ #
20
+ # @param tuple [Hash] input arguments for directory #add and #modify
21
+ #
22
+ # @return [Hash]
23
+ #
24
+ # @note Directory#add will receive a hash with key :dn
25
+ #
26
+ # @api private
27
+ def self.tuplify(tuple, matrix)
28
+ fn = t(:rename_keys, matrix) >>
29
+ t(:map_values, t(:identify_value)) >>
30
+ t(:map_values, t(:stringify)) >> t(:reject_blank)
31
+
32
+ fn.call(tuple)
33
+ end
34
+
35
+ # remove keys with blank values
36
+ # nil values allow attribute to be deleted
37
+ #
38
+ def self.reject_blank(tuple)
39
+ tuple.reject { |_k, v| v&.empty? }
40
+ end
41
+
42
+ # Map from
43
+ #
44
+ # @todo Finish documentation
45
+ #
46
+ # @param val [Symbol,String]
47
+ #
48
+ # @example
49
+ # id_value(true) => 'TRUE'
50
+ # id_value('TRUE') => true
51
+ # id_value('peter hamilton') => 'peter hamilton'
52
+ #
53
+ # @return [Mixed]
54
+ #
55
+ # @api private
56
+ def self.identify_value(val)
57
+ case val
58
+ when ::Symbol, ::TrueClass, ::FalseClass, ::NilClass
59
+ VALUES_MAP.fetch(val, val)
60
+ else
61
+ VALUES_MAP.invert.fetch(val, val)
62
+ end
63
+ end
64
+
65
+ # Ensure tuple values are strings or nil
66
+ #
67
+ # @param value [Mixed]
68
+ #
69
+ # @return [String, NilClass]
70
+ #
71
+ # @api private
72
+ def self.stringify(value)
73
+ case value
74
+ when ::Numeric then value.to_s
75
+ when ::Enumerable then value.map(&:to_s)
76
+ when ::Hash then value.to_json
77
+ when ::String then value
78
+ else
79
+ value
80
+ end
81
+ end
82
+
83
+ # Compare Magic Bytes
84
+ #
85
+ # @see https://en.wikipedia.org/wiki/List_of_file_signatures
86
+ # @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Complete_list_of_MIME_types
87
+ # @see https://github.com/sdsykes/fastimage/blob/master/lib/fastimage.rb
88
+ #
89
+ # @param value [String] UTF-8 encoded
90
+ #
91
+ def self.mime_type(value)
92
+ mime =
93
+ case value[0, 2]
94
+ when "\xFF\xD8".b
95
+ 'image/jpeg'
96
+ when "\x89P".b
97
+ 'image/png'
98
+ when 'BM'
99
+ 'image/bitmap'
100
+ when 'II', 'MM'
101
+ 'image/tiff'
102
+ when "\xFF\xFBID".b
103
+ 'audio/mpeg'
104
+ when 'WA'
105
+ 'audio/x-wav'
106
+ else
107
+ 'application/octet-stream'
108
+ end
109
+ to_base64(value).prepend("data:#{mime};base64,")
110
+ end
111
+
112
+ # Base64 encoded string, with optional new line characters.
113
+ #
114
+ # @return [String]
115
+ #
116
+ def self.to_base64(value, strict: true)
117
+ if strict
118
+ Base64.strict_encode64(value).chomp
119
+ else
120
+ # [value].pack('m').chomp
121
+ Base64.encode64(value).chomp
122
+ end
123
+ end
124
+
125
+ #
126
+ #
127
+ # @return [Boolean]
128
+ #
129
+ def self.to_boolean(value)
130
+ Dry::Transformer::Coercions::BOOLEAN_MAP.fetch(value.to_s.downcase)
131
+ end
132
+
133
+ # The 18-digit Active Directory timestamps, also named
134
+ # 'Windows NT time format','Win32 FILETIME or SYSTEMTIME' or 'NTFS file time'.
135
+ #
136
+ # These are used in Microsoft Active Directory for
137
+ # pwdLastSet, accountExpires, LastLogon, LastLogonTimestamp and LastPwdSet.
138
+ #
139
+ # The number of 100-nanoseconds intervals since 12:00 A.M. January 1st, 1601 (UTC).
140
+ # NB:
141
+ # 1 nanosecond = a billionth of a second
142
+ # Accurate to the nearest millisecond (7 digits)
143
+ #
144
+ # @see ROM::LDAP::Types::Time
145
+ # @see https://ldapwiki.com/wiki/Microsoft%20TIME
146
+ #
147
+ # @param value [String] time or integer
148
+ #
149
+ # @return [Time] UTC formatted
150
+ #
151
+ def self.to_time(value)
152
+ unix_epoch_time = (Integer(value) / TEN_MILLION) - SINCE_1601
153
+ ::Time.at(unix_epoch_time).utc
154
+ rescue ArgumentError
155
+ ::Time.parse(value).utc
156
+ end
157
+
158
+ # @return [Array<String>]
159
+ #
160
+ def self.map_to_base64(values)
161
+ t(:map_array, t(:to_base64)).call(values)
162
+ end
163
+
164
+ # @return [Array<Integer>]
165
+ #
166
+ def self.map_to_integers(tuples)
167
+ t(:map_array, t(:to_integer)).call(tuples)
168
+ end
169
+
170
+ # @return [Array<Symbol>]
171
+ #
172
+ def self.map_to_symbols(tuples)
173
+ t(:map_array, t(:to_symbol)).call(tuples)
174
+ end
175
+
176
+ # @return [Array<TrueClass, FalseClass>]
177
+ #
178
+ def self.map_to_booleans(values)
179
+ t(:map_array, t(:to_boolean)).call(values)
180
+ end
181
+
182
+ # @return [Array<Time>]
183
+ #
184
+ def self.map_to_times(values)
185
+ t(:map_array, t(:to_time)).call(values)
186
+ end
187
+
188
+ # Convert string to snake case.
189
+ #
190
+ # @param value [String]
191
+ #
192
+ # @return [String]
193
+ #
194
+ def self.to_underscore(value)
195
+ Inflector.underscore(value.delete('= '))
196
+ end
197
+
198
+ # Function applied to Directory::Entry to format incoming attribute names.
199
+ #
200
+ # @api public
201
+ def self.to_method_name(value)
202
+ fn = t(:to_string) >> t(:to_underscore) >> t(:to_symbol)
203
+ fn.call(value)
204
+ end
205
+ end
206
+ end
207
+ end