hoodoo 1.12.4 → 1.13.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 612e215b33df39e4942acbf763c27af9c817f5a2
4
- data.tar.gz: 9159951c875326ec7bcdd5d696365563028c7d2d
3
+ metadata.gz: 7afd9823b45dd59e8031b83a8eafe26118d38240
4
+ data.tar.gz: 3b23639744e3b536f5f5192538e88d07cf7cccef
5
5
  SHA512:
6
- metadata.gz: c2c0eeb88c1f8950a3dda6725f1110b8e32b9767c30dfbe4a560c4dbacaaad5d459ccfe71d46de5b850031d459708853aa818cfcc06f241f3176ee4319874403
7
- data.tar.gz: b747857c2b8469936e728e81872b80d33016df0a68a2e15a6001794593d216a6c86ec6d30cae97d0c48e6f1d9fd22bb947307a8b29cea398a8b3829d886f6a38
6
+ metadata.gz: 79d4a1cc3b9217baecf9129b245e16fea79d80f0fd8cf1accc54724e43ec9d9ea0fc4988241ba5418d52c8c5f33b401fad4157db204e2adfcf8bac72ba69a9e6
7
+ data.tar.gz: 0bd7649d997eec9b349119464bcd6ea801ffd099bf0df21136e77a6401449cf045f8cd931d0a9846afcc5beb9be02f063643115bde2054de46ddbb5e8acd6766
@@ -22,7 +22,7 @@ module Hoodoo
22
22
  # Define a JSON object with the supplied name and options.
23
23
  #
24
24
  # +name+:: The JSON key.
25
- # +options+:: Optional +Hash+ of options, e.g. :required => true
25
+ # +options+:: Optional +Hash+ of options, e.g. <tt>:required => true</tt>
26
26
  # &block:: Block declaring the fields making up the nested object
27
27
  #
28
28
  # Example - mandatory JSON field "currencies" would lead to an object
@@ -51,19 +51,69 @@ module Hoodoo
51
51
  # Define a JSON array with the supplied name and options. If there is
52
52
  # a block provided, then more DSL calls inside the block define how each
53
53
  # array entry must look; otherwise array entries are not validated /
54
- # are undefined.
54
+ # are undefined unless the +:type+ option is specified (see below).
55
55
  #
56
- # When an array field uses +:required => true+, this only says that at
56
+ # When an array uses <tt>:required => true</tt>, this only says that at
57
57
  # least an empty array must be present, nothing more. If the array uses
58
58
  # a block with fields that themselves are required, then this is only
59
59
  # checked for if the array contains one or more entries (and is checked
60
60
  # for each of those entries).
61
61
  #
62
62
  # +name+:: The JSON key
63
- # +options+:: A +Hash+ of options, e.g. :required => true
63
+ # +options+:: A +Hash+ of options, e.g. <tt>:required => true</tt>
64
64
  # &block:: Optional block declaring the fields of each array item
65
65
  #
66
- # Example - mandatory JSON field "currencies" would lead to an array
66
+ # Array entries are normally either unvalidated, or describe complex
67
+ # types via a block. For simple fields, pass a :type option to declare
68
+ # that array entries must be of supported types as follows:
69
+ #
70
+ # [:array]
71
+ # Hoodoo::Presenters::Array (see #array)
72
+ # [:boolean]
73
+ # Hoodoo::Presenters::Boolean (see #boolean)
74
+ # [:date]
75
+ # Hoodoo::Presenters::Date (see #date)
76
+ # [:date_time]
77
+ # Hoodoo::Presenters::DateTime (see #datetime)
78
+ # [:decimal]
79
+ # Hoodoo::Presenters::Decimal (see #decimal)
80
+ # [:enum]
81
+ # Hoodoo::Presenters::Enum (see #enum)
82
+ # [:float]
83
+ # Hoodoo::Presenters::Float (see #float)
84
+ # [:integer]
85
+ # Hoodoo::Presenters::Integer (see #integer)
86
+ # [:string]
87
+ # Hoodoo::Presenters::String (see #string)
88
+ # [:tags]
89
+ # Hoodoo::Presenters::Tags (see #tags)
90
+ # [:text]
91
+ # Hoodoo::Presenters::Text (see #text)
92
+ # [:uuid]
93
+ # Hoodoo::Presenters::UUID (see #uuid)
94
+ #
95
+ # Some of these types require additional parameters, such as
96
+ # +:precision+ for Hoodoo::Presenters::Decimal or +:from+ for
97
+ # Hoodoo::Presenters::Enum. For _any_ options that are to apply to the
98
+ # the new Array simple type fields, prefix the option with the string
99
+ # +field_+ - for example, <tt>:field_precision => 2</tt>.
100
+ #
101
+ # It does not make sense to attempt to apply field defaults to simple
102
+ # type array entries via +:field_default+; don't do this.
103
+ #
104
+ # In the case of <tt>:type => :array</tt>, the declaring Array is
105
+ # saying that its entries are themselves individually Arrays. This means
106
+ # that validation will ensure and rendering will assume that each of the
107
+ # parent Array entries are themselves Arrays, but will not validte the
108
+ # child Array contents any further. It is not possible to declare an
109
+ # Array with a child Array that has further children, or has child-level
110
+ # validation; instead you would need to use the block syntax, so that
111
+ # the child Array was associated to some named key in the arising
112
+ # Object/Hash making up each of the parent entries.
113
+ #
114
+ # == Block syntax example
115
+ #
116
+ # Mandatory JSON field "currencies" would lead to an array
67
117
  # where each array entry contains the fields defined by
68
118
  # Hoodoo::Data::Types::Currency along with an up-to-32 character string
69
119
  # with field name "notes", that field also being required. Whether or not
@@ -79,6 +129,41 @@ module Hoodoo
79
129
  # end
80
130
  # end
81
131
  #
132
+ # == Simple type syntax without field options
133
+ #
134
+ # An optional Array which consists of simple UUIDs as its entries:
135
+ #
136
+ # class UUIDCollection < Hoodoo::Presenters::Base
137
+ # schema do
138
+ # array :uuids, :type => :uuid
139
+ # end
140
+ # end
141
+ #
142
+ # # E.g.:
143
+ # #
144
+ # # {
145
+ # # "uuids" => [ "...uuid...", "...uuid...", ... ]
146
+ # # }
147
+ #
148
+ # Validation of data intended to be rendered through such a schema
149
+ # declaration would make sure that each array entry was UUID-like.
150
+ #
151
+ # == Simple type syntax with field options
152
+ #
153
+ # An optional Array which consists of Decimals with precision 2:
154
+ #
155
+ # class DecimalCollection < Hoodoo::Presenters::Base
156
+ # schema do
157
+ # array :numbers, :type => :decimal, :field_precision => 2
158
+ # end
159
+ # end
160
+ #
161
+ # # E.g.:
162
+ # #
163
+ # # {
164
+ # # "numbers" => [ BigDecimal.new( '2.2511' ) ]
165
+ # # }
166
+ #
82
167
  def array( name, options = {}, &block )
83
168
  ary = property( name, Hoodoo::Presenters::Array, options, &block )
84
169
  internationalised() if ary.is_internationalised?()
@@ -89,11 +174,13 @@ module Hoodoo
89
174
  # that the object may contain, in abstract terms.
90
175
  #
91
176
  # +name+:: The JSON key
92
- # +options+:: A +Hash+ of options, e.g. :required => true
177
+ # +options+:: A +Hash+ of options, e.g. <tt>:required => true</tt>
93
178
  # &block:: Optional block declaring the fields making up the nested
94
179
  # hash
95
180
  #
96
- # == Example 1
181
+ # == Block-based complex type examples
182
+ #
183
+ # === Example 1
97
184
  #
98
185
  # A Hash where keys must be <= 16 characters long and values must match
99
186
  # a <tt>Hoodoo::Data::Types::Currency</tt> type (with the default
@@ -112,7 +199,7 @@ module Hoodoo
112
199
  #
113
200
  # See Hoodoo::Presenters::Hash#keys for more information and examples.
114
201
  #
115
- # == Example 2
202
+ # === Example 2
116
203
  #
117
204
  # A Hash where keys must be 'one' or 'two', each with a value matching
118
205
  # the given schema. Here, the example assumes that a subclass of
@@ -137,68 +224,104 @@ module Hoodoo
137
224
  #
138
225
  # See Hoodoo::Presenters::Hash#key for more information and examples.
139
226
  #
140
- # == Limitations
227
+ # == Simple types
228
+ #
229
+ # As with #array, simple types can be declared for Hash key values by
230
+ # passing a +:type+ option to Hoodoo::Presenters::Hash#key or
231
+ # Hoodoo::Presenters::Hash#keys. See the #array documentation for a list
232
+ # of permitted types.
141
233
  #
142
- # The syntax cannot express simple value types. It always describes a
143
- # nested object. So, the following describes a Hash called +payload+
144
- # which has arbitrary keys each leading to a nested _object_ with
145
- # key/value pairs where the key is called +some_value+ and the value
146
- # is an arbitrary length String:
234
+ # For individual specific keys in Hoodoo::Presenters::Hash#key, it
235
+ # _does_ make sense sometimes to specify field defaults using either a
236
+ # +:default+ or +:field_default+ key (they are synonyms). For arbitrary
237
+ # keys via Hoodoo::Presenters::Hash#keys the situation is the same as
238
+ # with array entries and it does _not_ make sense to specify field
239
+ # defaults.
147
240
  #
148
- # class NotSoSimpleHash < Hoodoo::Presenters::Base
241
+ # === Simple type example
242
+ #
243
+ # class Person < Hoodoo::Presenters::Base
149
244
  # schema do
150
- # hash :payload do
151
- # keys do
152
- # text :some_value
153
- # end
245
+ # hash :name do
246
+ # key :first, :type => :text
247
+ # key :last, :type => :text
248
+ # end
249
+ #
250
+ # hash :address do
251
+ # keys :type => :text
252
+ # end
253
+ #
254
+ # hash :identifiers, :required => true do
255
+ # keys :length => 8, :type => :string, :field_length => 32
154
256
  # end
155
257
  # end
156
258
  # end
157
259
  #
158
- # This is a valid piece of Ruby input data for the above which will
159
- # render without changes and validate successfully:
260
+ # The optional Hash called +name+ has two optional keys which must be
261
+ # called +first+ or +last+ and have values that conform to
262
+ # Hoodoo::Presenters::Text.
160
263
  #
161
- # data = {
162
- # "payload" => {
163
- # "any_key_name" => { "some_value" => "Any string" },
164
- # "another_key_name" => { "some_value" => "Another string" },
165
- # }
166
- # }
264
+ # The optional Hash called +address+ has arbitrarily named unbounded
265
+ # length keys which where present must conform to
266
+ # Hoodoo::Presenters::Text.
167
267
  #
168
- # NotSoSimpleHash.validate( data )
169
- # # => []
268
+ # The required Hash called +identifiers+ hash arbitrarily named keys
269
+ # with a maximum length of 8 characters which must have values that
270
+ # conform to Hoodoo::Presenters::String and are each no more than
271
+ # 32 characters long.
170
272
  #
171
- # This is invalid because one of the values is not a String:
273
+ # Therefore the following payload is valid:
172
274
  #
173
275
  # data = {
174
- # "payload" => {
175
- # "any_key_name" => { "some_value" => "Any string" },
176
- # "another_key_name" => { "some_value" => 22 },
276
+ # "name" => {
277
+ # "first" => "Test",
278
+ # "last" => "Testy"
279
+ # },
280
+ # "address" => {
281
+ # "road" => "1 Test Street",
282
+ # "city" => "Testville",
283
+ # "post_code" => "T01 C41"
284
+ # },
285
+ # "identifiers" => {
286
+ # "primary" => "9759c77d188f4bfe85959738dc6f8505",
287
+ # "postgres" => "1442"
177
288
  # }
178
289
  # }
179
290
  #
180
- # NotSoSimpleHash.validate( data )
181
- # # => [{"code"=>"generic.invalid_string",
182
- # # "message"=>"Field `payload.another_key_name.some_value` is an invalid string",
183
- # # "reference"=>"payload.another_key_name.some_value"}]
291
+ # Person.validate( data )
292
+ # # => []
184
293
  #
185
- # This is invalid because the DSL cannot express a simple String value
186
- # for the keys:
294
+ # The following example contains numerous mistakes:
187
295
  #
188
296
  # data = {
189
- # "payload" => {
190
- # "any_key_name" => "Any string",
191
- # "another_key_name" => "Another string",
297
+ # "name" => {
298
+ # "first" => "Test",
299
+ # "surname" => "Testy" # Invalid key name
300
+ # },
301
+ # "address" => {
302
+ # "road" => "1 Test Street",
303
+ # "city" => "Testville",
304
+ # "zip" => 90421 # Integer, not Text
305
+ # },
306
+ # "identifiers" => {
307
+ # "primary" => "9759c77d188f4bfe85959738dc6f8505_441", # Value too long
308
+ # "postgresql" => "1442" # Key name too long
192
309
  # }
193
310
  # }
194
311
  #
195
- # NotSoSimpleHash.validate( data )
196
- # # => [{"code"=>"generic.invalid_object",
197
- # # "message"=>"Field `payload.any_key_name` is an invalid object",
198
- # # "reference"=>"payload.any_key_name"},
199
- # # {"code"=>"generic.invalid_object",
200
- # # "message"=>"Field `payload.another_key_name` is an invalid object",
201
- # # "reference"=>"payload.another_key_name"}]
312
+ # Person.validate( data )
313
+ # # => [{"code"=>"generic.invalid_hash",
314
+ # # "message"=>"Field `name` is an invalid hash due to unrecognised keys `surname`",
315
+ # # "reference"=>"name"},
316
+ # # {"code"=>"generic.invalid_string",
317
+ # # "message"=>"Field `address.zip` is an invalid string",
318
+ # # "reference"=>"address.zip"},
319
+ # # {"code"=>"generic.invalid_string",
320
+ # # "message"=>"Field `identifiers.primary` is longer than maximum length `32`",
321
+ # # "reference"=>"identifiers.primary"},
322
+ # # {"code"=>"generic.invalid_string",
323
+ # # "message"=>"Field `identifiers.postgresql` is longer than maximum length `8`",
324
+ # # "reference"=>"identifiers.postgresql"}]
202
325
  #
203
326
  def hash( name, options = {}, &block )
204
327
  hash = property( name, Hoodoo::Presenters::Hash, options, &block )
@@ -207,8 +330,8 @@ module Hoodoo
207
330
 
208
331
  # Define a JSON integer with the supplied name and options.
209
332
  #
210
- # +name+:: The JSON key
211
- # +options+:: A +Hash+ of options, e.g. :required => true
333
+ # +name+:: The JSON key
334
+ # +options+:: A +Hash+ of options, e.g. <tt>:required => true</tt>
212
335
  #
213
336
  def integer( name, options = {} )
214
337
  property( name, Hoodoo::Presenters::Integer, options )
@@ -216,8 +339,9 @@ module Hoodoo
216
339
 
217
340
  # Define a JSON string with the supplied name and options.
218
341
  #
219
- # +name+:: The JSON key
220
- # +options+:: A +Hash+ of options, e.g. :required => true, :length => 10
342
+ # +name+:: The JSON key
343
+ # +options+:: A +Hash+ of options, e.g. <tt>:required => true</tt> and
344
+ # mandatory <tt>:length => [max-length-in-chars]</tt>
221
345
  #
222
346
  def string( name, options = {} )
223
347
  property( name, Hoodoo::Presenters::String, options )
@@ -225,8 +349,8 @@ module Hoodoo
225
349
 
226
350
  # Define a JSON float with the supplied name and options.
227
351
  #
228
- # +name+:: The JSON key
229
- # +options+:: A +Hash+ of options, e.g. :required => true
352
+ # +name+:: The JSON key
353
+ # +options+:: A +Hash+ of options, e.g. <tt>:required => true</tt>
230
354
  #
231
355
  def float( name, options = {} )
232
356
  property( name, Hoodoo::Presenters::Float, options )
@@ -234,8 +358,9 @@ module Hoodoo
234
358
 
235
359
  # Define a JSON decimal with the supplied name and options.
236
360
  #
237
- # +name+:: The JSON key
238
- # +options+:: A +Hash+ of options, e.g. :required => true, :precision => 10
361
+ # +name+:: The JSON key
362
+ # +options+:: A +Hash+ of options, e.g. <tt>:required => true</tt> and
363
+ # mandatory <tt>:precision => [decimal-precision-number]</tt>
239
364
  #
240
365
  def decimal( name, options = {} )
241
366
  property( name, Hoodoo::Presenters::Decimal, options )
@@ -243,8 +368,8 @@ module Hoodoo
243
368
 
244
369
  # Define a JSON boolean with the supplied name and options.
245
370
  #
246
- # +name+:: The JSON key
247
- # +options+:: A +Hash+ of options, e.g. :required => true
371
+ # +name+:: The JSON key
372
+ # +options+:: A +Hash+ of options, e.g. <tt>:required => true</tt>
248
373
  #
249
374
  def boolean( name, options = {} )
250
375
  property( name, Hoodoo::Presenters::Boolean, options )
@@ -252,8 +377,8 @@ module Hoodoo
252
377
 
253
378
  # Define a JSON date with the supplied name and options.
254
379
  #
255
- # +name+:: The JSON key
256
- # +options+:: A +Hash+ of options, e.g. :required => true
380
+ # +name+:: The JSON key
381
+ # +options+:: A +Hash+ of options, e.g. <tt>:required => true</tt>
257
382
  #
258
383
  def date( name, options = {} )
259
384
  property( name, Hoodoo::Presenters::Date, options )
@@ -261,8 +386,8 @@ module Hoodoo
261
386
 
262
387
  # Define a JSON datetime with the supplied name and options.
263
388
  #
264
- # +name+:: The JSON key
265
- # +options+:: A +Hash+ of options, e.g. :required => true
389
+ # +name+:: The JSON key
390
+ # +options+:: A +Hash+ of options, e.g. <tt>:required => true</tt>
266
391
  #
267
392
  def datetime( name, options = {} )
268
393
  property( name, Hoodoo::Presenters::DateTime, options )
@@ -271,8 +396,8 @@ module Hoodoo
271
396
  # Define a JSON string of unlimited length with the supplied name
272
397
  # and options.
273
398
  #
274
- # +name+:: The JSON key
275
- # +options+:: A +Hash+ of options, e.g. :required => true
399
+ # +name+:: The JSON key
400
+ # +options+:: A +Hash+ of options, e.g. <tt>:required => true</tt>
276
401
  #
277
402
  def text( name, options = {} )
278
403
  property( name, Hoodoo::Presenters::Text, options )
@@ -281,9 +406,10 @@ module Hoodoo
281
406
  # Define a JSON string which can only have a restricted set of exactly
282
407
  # matched values, with the supplied name and options.
283
408
  #
284
- # +name+:: The JSON key
285
- # +options+:: A +Hash+ of options, e.g. :required => true and mandatory
286
- # :from => [array-of-allowed-strings-or-symbols]
409
+ # +name+:: The JSON key
410
+ # +options+:: A +Hash+ of options, e.g. <tt>:required => true</tt> and
411
+ # mandatory
412
+ # <tt>:from => [array-of-allowed-strings-or-symbols]</tt>
287
413
  #
288
414
  def enum( name, options = {} )
289
415
  property( name, Hoodoo::Presenters::Enum, options )
@@ -293,7 +419,7 @@ module Hoodoo
293
419
  # length that contains comma-separated tag strings.
294
420
  #
295
421
  # +field_name+:: Name of the field that will hold the tags.
296
- # +options+:: Optional options hash. See Hoodoo::Presenters::BaseDSL.
422
+ # +options+:: Optional options hash. See Hoodoo::Presenters::BaseDSL.
297
423
  #
298
424
  # Example - a Product resource which supports product tagging:
299
425
  #
@@ -573,7 +699,7 @@ module Hoodoo
573
699
  #
574
700
  # +name+:: The JSON key
575
701
  # +type+:: A +Class+ for validation
576
- # +options+:: A +Hash+ of options, e.g. :required => true
702
+ # +options+:: A +Hash+ of options, e.g. <tt>:required => true</tt>
577
703
  #
578
704
  def property( name, type, options = {}, &block )
579
705
  name = name.to_s
@@ -586,6 +712,94 @@ module Hoodoo
586
712
  return inst
587
713
  end
588
714
 
715
+ # Given a <tt>:type</tt> option key, take the value (which must be a
716
+ # Symbol) and convert it to a class variable for known cases, else
717
+ # raise an exception. Used as a back-end for methods such as
718
+ # Hoodoo::Presenters::Hash#key which support value type validation.
719
+ #
720
+ # +type+:: Required class type expressed as a Symbol (see below).
721
+ #
722
+ # Supported values for the +type+ parameter are:
723
+ #
724
+ # [nil]
725
+ # Hoodoo::Presenters::Field generic class
726
+ # [:array]
727
+ # Hoodoo::Presenters::Array
728
+ # [:boolean]
729
+ # Hoodoo::Presenters::Boolean
730
+ # [:date]
731
+ # Hoodoo::Presenters::Date
732
+ # [:date_time]
733
+ # Hoodoo::Presenters::DateTime
734
+ # [:decimal]
735
+ # Hoodoo::Presenters::Decimal
736
+ # [:enum]
737
+ # Hoodoo::Presenters::Enum
738
+ # [:float]
739
+ # Hoodoo::Presenters::Float
740
+ # [:integer]
741
+ # Hoodoo::Presenters::Integer
742
+ # [:string]
743
+ # Hoodoo::Presenters::String
744
+ # [:tags]
745
+ # Hoodoo::Presenters::Tags
746
+ # [:text]
747
+ # Hoodoo::Presenters::Text
748
+ # [:uuid]
749
+ # Hoodoo::Presenters::UUID
750
+ #
751
+ def type_option_to_class( type )
752
+ case type
753
+ when nil
754
+ Hoodoo::Presenters::Field
755
+ when :array
756
+ Hoodoo::Presenters::Array
757
+ when :boolean
758
+ Hoodoo::Presenters::Boolean
759
+ when :date
760
+ Hoodoo::Presenters::Date
761
+ when :date_time
762
+ Hoodoo::Presenters::DateTime
763
+ when :decimal
764
+ Hoodoo::Presenters::Decimal
765
+ when :enum
766
+ Hoodoo::Presenters::Enum
767
+ when :float
768
+ Hoodoo::Presenters::Float
769
+ when :integer
770
+ Hoodoo::Presenters::Integer
771
+ when :string
772
+ Hoodoo::Presenters::String
773
+ when :tags
774
+ Hoodoo::Presenters::Tags
775
+ when :text
776
+ Hoodoo::Presenters::Text
777
+ when :uuid
778
+ Hoodoo::Presenters::UUID
779
+ else
780
+ raise "Unsupported 'type' option value of '#{ type }' in Hoodoo::Presenters::BaseDSL"
781
+ end
782
+ end
783
+
784
+ # Given a Hash with keys as Symbols or Strings prefixed by the string
785
+ # "field_", return a Hash with those items kept but keys renamed without
786
+ # that "field_" prefix and with all other items removed. The keys in the
787
+ # returned Hash are all coerced to Symbols regardless of input class.
788
+ #
789
+ # +options+:: Hash from which to extract field-specific data.
790
+ #
791
+ def extract_field_prefix_options_from( options )
792
+ options.inject( {} ) do | hash, key_value_pair_array |
793
+ key = key_value_pair_array[ 0 ].to_s
794
+
795
+ if key.start_with?( 'field_' )
796
+ hash[ key.sub( /^field_/, '' ).to_sym ] = key_value_pair_array[ 1 ]
797
+ end
798
+
799
+ hash
800
+ end
801
+ end
802
+
589
803
  end
590
804
  end
591
805
  end