hoodoo 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (216) hide show
  1. checksums.yaml +7 -0
  2. data/bin/hoodoo +5 -0
  3. data/lib/hoodoo.rb +27 -0
  4. data/lib/hoodoo/active.rb +32 -0
  5. data/lib/hoodoo/active/active_model/uuid_validator.rb +45 -0
  6. data/lib/hoodoo/active/active_record/base.rb +81 -0
  7. data/lib/hoodoo/active/active_record/creator.rb +134 -0
  8. data/lib/hoodoo/active/active_record/dated.rb +343 -0
  9. data/lib/hoodoo/active/active_record/error_mapping.rb +351 -0
  10. data/lib/hoodoo/active/active_record/finder.rb +606 -0
  11. data/lib/hoodoo/active/active_record/search_helper.rb +189 -0
  12. data/lib/hoodoo/active/active_record/secure.rb +431 -0
  13. data/lib/hoodoo/active/active_record/support.rb +106 -0
  14. data/lib/hoodoo/active/active_record/translated.rb +87 -0
  15. data/lib/hoodoo/active/active_record/uuid.rb +80 -0
  16. data/lib/hoodoo/active/active_record/writer.rb +321 -0
  17. data/lib/hoodoo/client.rb +23 -0
  18. data/lib/hoodoo/client/augmented_array.rb +29 -0
  19. data/lib/hoodoo/client/augmented_base.rb +168 -0
  20. data/lib/hoodoo/client/augmented_hash.rb +23 -0
  21. data/lib/hoodoo/client/client.rb +354 -0
  22. data/lib/hoodoo/client/endpoint/endpoint.rb +427 -0
  23. data/lib/hoodoo/client/endpoint/endpoints/amqp.rb +180 -0
  24. data/lib/hoodoo/client/endpoint/endpoints/auto_session.rb +194 -0
  25. data/lib/hoodoo/client/endpoint/endpoints/http.rb +203 -0
  26. data/lib/hoodoo/client/endpoint/endpoints/http_based.rb +367 -0
  27. data/lib/hoodoo/client/endpoint/endpoints/not_found.rb +59 -0
  28. data/lib/hoodoo/client/headers.rb +269 -0
  29. data/lib/hoodoo/communicators.rb +23 -0
  30. data/lib/hoodoo/communicators/fast.rb +44 -0
  31. data/lib/hoodoo/communicators/pool.rb +601 -0
  32. data/lib/hoodoo/communicators/slow.rb +84 -0
  33. data/lib/hoodoo/data.rb +51 -0
  34. data/lib/hoodoo/data/resources/caller.rb +39 -0
  35. data/lib/hoodoo/data/resources/errors.rb +28 -0
  36. data/lib/hoodoo/data/resources/log.rb +31 -0
  37. data/lib/hoodoo/data/resources/session.rb +26 -0
  38. data/lib/hoodoo/data/types/error_primitive.rb +27 -0
  39. data/lib/hoodoo/data/types/permissions.rb +40 -0
  40. data/lib/hoodoo/data/types/permissions_defaults.rb +32 -0
  41. data/lib/hoodoo/data/types/permissions_full.rb +28 -0
  42. data/lib/hoodoo/data/types/permissions_resources.rb +31 -0
  43. data/lib/hoodoo/discovery.rb +20 -0
  44. data/lib/hoodoo/errors.rb +19 -0
  45. data/lib/hoodoo/errors/error_descriptions.rb +229 -0
  46. data/lib/hoodoo/errors/errors.rb +322 -0
  47. data/lib/hoodoo/generator.rb +139 -0
  48. data/lib/hoodoo/logger.rb +23 -0
  49. data/lib/hoodoo/logger/fast_writer.rb +27 -0
  50. data/lib/hoodoo/logger/flattener_mixin.rb +36 -0
  51. data/lib/hoodoo/logger/logger.rb +387 -0
  52. data/lib/hoodoo/logger/slow_writer.rb +49 -0
  53. data/lib/hoodoo/logger/writer_mixin.rb +52 -0
  54. data/lib/hoodoo/logger/writers/file_writer.rb +45 -0
  55. data/lib/hoodoo/logger/writers/log_entries_dot_com_writer.rb +64 -0
  56. data/lib/hoodoo/logger/writers/stream_writer.rb +43 -0
  57. data/lib/hoodoo/middleware.rb +33 -0
  58. data/lib/hoodoo/presenters.rb +45 -0
  59. data/lib/hoodoo/presenters/base.rb +281 -0
  60. data/lib/hoodoo/presenters/base_dsl.rb +519 -0
  61. data/lib/hoodoo/presenters/common_resource_fields.rb +31 -0
  62. data/lib/hoodoo/presenters/embedding.rb +232 -0
  63. data/lib/hoodoo/presenters/types/array.rb +118 -0
  64. data/lib/hoodoo/presenters/types/boolean.rb +26 -0
  65. data/lib/hoodoo/presenters/types/date.rb +26 -0
  66. data/lib/hoodoo/presenters/types/date_time.rb +26 -0
  67. data/lib/hoodoo/presenters/types/decimal.rb +47 -0
  68. data/lib/hoodoo/presenters/types/enum.rb +55 -0
  69. data/lib/hoodoo/presenters/types/field.rb +158 -0
  70. data/lib/hoodoo/presenters/types/float.rb +26 -0
  71. data/lib/hoodoo/presenters/types/hash.rb +361 -0
  72. data/lib/hoodoo/presenters/types/integer.rb +26 -0
  73. data/lib/hoodoo/presenters/types/object.rb +117 -0
  74. data/lib/hoodoo/presenters/types/string.rb +53 -0
  75. data/lib/hoodoo/presenters/types/tags.rb +24 -0
  76. data/lib/hoodoo/presenters/types/text.rb +26 -0
  77. data/lib/hoodoo/presenters/types/uuid.rb +54 -0
  78. data/lib/hoodoo/services.rb +34 -0
  79. data/lib/hoodoo/services/discovery/discoverers/by_consul.rb +66 -0
  80. data/lib/hoodoo/services/discovery/discoverers/by_convention.rb +173 -0
  81. data/lib/hoodoo/services/discovery/discoverers/by_drb/by_drb.rb +195 -0
  82. data/lib/hoodoo/services/discovery/discoverers/by_drb/drb_server.rb +166 -0
  83. data/lib/hoodoo/services/discovery/discoverers/by_drb/drb_server_start.rb +37 -0
  84. data/lib/hoodoo/services/discovery/discovery.rb +186 -0
  85. data/lib/hoodoo/services/discovery/results/for_amqp.rb +58 -0
  86. data/lib/hoodoo/services/discovery/results/for_http.rb +85 -0
  87. data/lib/hoodoo/services/discovery/results/for_local.rb +85 -0
  88. data/lib/hoodoo/services/discovery/results/for_remote.rb +57 -0
  89. data/lib/hoodoo/services/middleware/amqp_log_message.rb +186 -0
  90. data/lib/hoodoo/services/middleware/amqp_log_writer.rb +119 -0
  91. data/lib/hoodoo/services/middleware/endpoints/inter_resource_local.rb +130 -0
  92. data/lib/hoodoo/services/middleware/endpoints/inter_resource_remote.rb +202 -0
  93. data/lib/hoodoo/services/middleware/exception_reporting/base_reporter.rb +105 -0
  94. data/lib/hoodoo/services/middleware/exception_reporting/exception_reporting.rb +115 -0
  95. data/lib/hoodoo/services/middleware/exception_reporting/reporters/airbrake_reporter.rb +64 -0
  96. data/lib/hoodoo/services/middleware/exception_reporting/reporters/raygun_reporter.rb +63 -0
  97. data/lib/hoodoo/services/middleware/interaction.rb +127 -0
  98. data/lib/hoodoo/services/middleware/middleware.rb +2705 -0
  99. data/lib/hoodoo/services/middleware/rack_monkey_patch.rb +73 -0
  100. data/lib/hoodoo/services/services/context.rb +153 -0
  101. data/lib/hoodoo/services/services/implementation.rb +132 -0
  102. data/lib/hoodoo/services/services/interface.rb +934 -0
  103. data/lib/hoodoo/services/services/permissions.rb +250 -0
  104. data/lib/hoodoo/services/services/request.rb +189 -0
  105. data/lib/hoodoo/services/services/response.rb +316 -0
  106. data/lib/hoodoo/services/services/service.rb +141 -0
  107. data/lib/hoodoo/services/services/session.rb +729 -0
  108. data/lib/hoodoo/utilities.rb +12 -0
  109. data/lib/hoodoo/utilities/string_inquirer.rb +54 -0
  110. data/lib/hoodoo/utilities/utilities.rb +380 -0
  111. data/lib/hoodoo/utilities/uuid.rb +44 -0
  112. data/lib/hoodoo/version.rb +17 -0
  113. data/spec/active/active_record/base_spec.rb +57 -0
  114. data/spec/active/active_record/creator_spec.rb +88 -0
  115. data/spec/active/active_record/dated_spec.rb +248 -0
  116. data/spec/active/active_record/error_mapping_spec.rb +360 -0
  117. data/spec/active/active_record/finder_spec.rb +744 -0
  118. data/spec/active/active_record/search_helper_spec.rb +384 -0
  119. data/spec/active/active_record/secure_spec.rb +435 -0
  120. data/spec/active/active_record/support_spec.rb +225 -0
  121. data/spec/active/active_record/translated_spec.rb +19 -0
  122. data/spec/active/active_record/uuid_spec.rb +72 -0
  123. data/spec/active/active_record/writer_spec.rb +272 -0
  124. data/spec/alchemy/alchemy-amq.rb +33 -0
  125. data/spec/client/augmented_array_spec.rb +15 -0
  126. data/spec/client/augmented_base_spec.rb +50 -0
  127. data/spec/client/augmented_hash_spec.rb +15 -0
  128. data/spec/client/client_spec.rb +955 -0
  129. data/spec/client/endpoint/endpoint_spec.rb +70 -0
  130. data/spec/client/endpoint/endpoints/amqp_spec.rb +16 -0
  131. data/spec/client/endpoint/endpoints/auto_session_spec.rb +9 -0
  132. data/spec/client/endpoint/endpoints/http_based_spec.rb +9 -0
  133. data/spec/client/endpoint/endpoints/http_spec.rb +103 -0
  134. data/spec/client/endpoint/endpoints/not_found_spec.rb +35 -0
  135. data/spec/client/headers_spec.rb +172 -0
  136. data/spec/communicators/fast_spec.rb +9 -0
  137. data/spec/communicators/pool_spec.rb +339 -0
  138. data/spec/communicators/slow_spec.rb +15 -0
  139. data/spec/data/resources/caller_spec.rb +156 -0
  140. data/spec/data/resources/errors_spec.rb +22 -0
  141. data/spec/data/resources/log_spec.rb +20 -0
  142. data/spec/data/resources/session_spec.rb +15 -0
  143. data/spec/data/types/error_primitive_spec.rb +15 -0
  144. data/spec/data/types/permissions_defaults_spec.rb +25 -0
  145. data/spec/data/types/permissions_full_spec.rb +44 -0
  146. data/spec/data/types/permissions_resources_spec.rb +34 -0
  147. data/spec/data/types/permissions_spec.rb +37 -0
  148. data/spec/errors/error_descriptions_spec.rb +98 -0
  149. data/spec/errors/errors_spec.rb +346 -0
  150. data/spec/integration/service_actions_spec.rb +112 -0
  151. data/spec/logger/fast_writer_spec.rb +18 -0
  152. data/spec/logger/logger_spec.rb +259 -0
  153. data/spec/logger/slow_writer_spec.rb +144 -0
  154. data/spec/logger/writers/file_writer_spec.rb +37 -0
  155. data/spec/logger/writers/log_entries_dot_com_writer_spec.rb +29 -0
  156. data/spec/logger/writers/stream_writer_spec.rb +38 -0
  157. data/spec/presenters/base_dsl_spec.rb +111 -0
  158. data/spec/presenters/base_spec.rb +871 -0
  159. data/spec/presenters/common_resource_fields_spec.rb +30 -0
  160. data/spec/presenters/embedding_spec.rb +87 -0
  161. data/spec/presenters/types/array_spec.rb +249 -0
  162. data/spec/presenters/types/boolean_spec.rb +51 -0
  163. data/spec/presenters/types/date_spec.rb +57 -0
  164. data/spec/presenters/types/date_time_spec.rb +59 -0
  165. data/spec/presenters/types/decimal_spec.rb +58 -0
  166. data/spec/presenters/types/enum_spec.rb +71 -0
  167. data/spec/presenters/types/field_spec.rb +77 -0
  168. data/spec/presenters/types/float_spec.rb +50 -0
  169. data/spec/presenters/types/hash_spec.rb +1069 -0
  170. data/spec/presenters/types/integer_spec.rb +50 -0
  171. data/spec/presenters/types/object_spec.rb +177 -0
  172. data/spec/presenters/types/string_spec.rb +65 -0
  173. data/spec/presenters/types/tags_spec.rb +56 -0
  174. data/spec/presenters/types/text_spec.rb +50 -0
  175. data/spec/presenters/types/uuid_spec.rb +46 -0
  176. data/spec/presenters/walk_spec.rb +198 -0
  177. data/spec/services/discovery/discoverers/by_consul_spec.rb +29 -0
  178. data/spec/services/discovery/discoverers/by_convention_spec.rb +67 -0
  179. data/spec/services/discovery/discoverers/by_drb/by_drb_spec.rb +80 -0
  180. data/spec/services/discovery/discoverers/by_drb/drb_server_spec.rb +205 -0
  181. data/spec/services/discovery/discovery_spec.rb +73 -0
  182. data/spec/services/discovery/results/for_amqp_spec.rb +17 -0
  183. data/spec/services/discovery/results/for_http_spec.rb +37 -0
  184. data/spec/services/discovery/results/for_local_spec.rb +21 -0
  185. data/spec/services/discovery/results/for_remote_spec.rb +15 -0
  186. data/spec/services/middleware/amqp_log_message_spec.rb +60 -0
  187. data/spec/services/middleware/amqp_log_writer_spec.rb +95 -0
  188. data/spec/services/middleware/endpoints/inter_resource_local_spec.rb +9 -0
  189. data/spec/services/middleware/endpoints/inter_resource_remote_spec.rb +9 -0
  190. data/spec/services/middleware/exception_reporting/base_reporter_spec.rb +16 -0
  191. data/spec/services/middleware/exception_reporting/exception_reporting_spec.rb +92 -0
  192. data/spec/services/middleware/exception_reporting/reporters/airbrake_reporter_spec.rb +24 -0
  193. data/spec/services/middleware/exception_reporting/reporters/raygun_reporter_spec.rb +23 -0
  194. data/spec/services/middleware/middleware_cors_spec.rb +93 -0
  195. data/spec/services/middleware/middleware_create_update_spec.rb +489 -0
  196. data/spec/services/middleware/middleware_dated_at_spec.rb +186 -0
  197. data/spec/services/middleware/middleware_exotic_communication_spec.rb +560 -0
  198. data/spec/services/middleware/middleware_logging_spec.rb +356 -0
  199. data/spec/services/middleware/middleware_multi_local_spec.rb +1094 -0
  200. data/spec/services/middleware/middleware_multi_remote_spec.rb +1440 -0
  201. data/spec/services/middleware/middleware_permissions_spec.rb +1014 -0
  202. data/spec/services/middleware/middleware_public_spec.rb +238 -0
  203. data/spec/services/middleware/middleware_spec.rb +1569 -0
  204. data/spec/services/middleware/string_inquirer_spec.rb +30 -0
  205. data/spec/services/services/application_spec.rb +74 -0
  206. data/spec/services/services/context_spec.rb +48 -0
  207. data/spec/services/services/implementation_spec.rb +45 -0
  208. data/spec/services/services/interface_spec.rb +262 -0
  209. data/spec/services/services/permissions_spec.rb +249 -0
  210. data/spec/services/services/request_spec.rb +95 -0
  211. data/spec/services/services/response_spec.rb +250 -0
  212. data/spec/services/services/session_spec.rb +432 -0
  213. data/spec/spec_helper.rb +298 -0
  214. data/spec/utilities/utilities_spec.rb +537 -0
  215. data/spec/utilities/uuid_spec.rb +20 -0
  216. metadata +615 -0
@@ -0,0 +1,47 @@
1
+ require 'bigdecimal'
2
+
3
+ module Hoodoo
4
+ module Presenters
5
+
6
+ # A JSON Decimal schema member.
7
+ #
8
+ class Decimal < Hoodoo::Presenters::Field
9
+
10
+ # The precision of the Decimal.
11
+ #
12
+ attr_accessor :precision
13
+
14
+ # Initialize a Decimal instance with the appropriate name and options.
15
+ #
16
+ # +name+:: The JSON key.
17
+ # +options+:: A +Hash+ of options, e.g. :required => true, :precision => 10.
18
+ #
19
+ def initialize( name, options = {} )
20
+ super( name, options )
21
+
22
+ unless options.has_key?( :precision )
23
+ raise ArgumentError.new( 'Hoodoo::Presenters::Decimal must have a :precision' )
24
+ end
25
+
26
+ @precision = options[ :precision ]
27
+ end
28
+
29
+ # Check if data is a valid Decimal and return a Hoodoo::Errors instance.
30
+ #
31
+ def validate( data, path = '' )
32
+ errors = super( data, path )
33
+ return errors if errors.has_errors? || ( ! @required && data.nil? )
34
+
35
+ unless data.is_a?( ::BigDecimal )
36
+ errors.add_error(
37
+ 'generic.invalid_decimal',
38
+ :message => "Field `#{ full_path( path ) }` is an invalid decimal",
39
+ :reference => { :field_name => full_path( path ) }
40
+ )
41
+ end
42
+
43
+ errors
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,55 @@
1
+ module Hoodoo
2
+ module Presenters
3
+
4
+ # A JSON String schema member. An enumeration (of sorts) - a list of
5
+ # discrete string values that are permitted for the value of a field of
6
+ # this type. Matches must be exact (case sensitive, no leading/trailing
7
+ # white space etc.). Allowed values are expressed as Ruby strings or
8
+ # symbols (converted to and matched as strings) via an array under key
9
+ # +:from+ in the options hash provided to the constructor.
10
+ #
11
+ class Enum < Hoodoo::Presenters::Field
12
+
13
+ # Array of permitted enumeration values. This may be written with
14
+ # non-String values but they will be converted to Strings when read
15
+ # back.
16
+ #
17
+ attr_accessor :from
18
+
19
+ # Initialize a String instance with the appropriate name and options.
20
+ #
21
+ # +name+:: The JSON key.
22
+ # +options+:: A +Hash+ of options, e.g. :required => true,
23
+ # :from => [ :array, :of, :allowed, :enum, :values ].
24
+ #
25
+ def initialize( name, options = {} )
26
+ super( name, options )
27
+
28
+ @from = options[ :from ]
29
+
30
+ if @from.is_a?( ::Array )
31
+ @from = @from.map { | entry | entry.to_s }
32
+ else
33
+ raise ArgumentError.new( 'Hoodoo::Presenters::Enum must have a :from array listing allowed values' )
34
+ end
35
+ end
36
+
37
+ # Check if data is a valid String and return a Hoodoo::Errors instance.
38
+ #
39
+ def validate( data, path = '' )
40
+ errors = super( data, path )
41
+ return errors if errors.has_errors? || ( ! @required && data.nil? )
42
+
43
+ unless @from.include?( data )
44
+ errors.add_error(
45
+ 'generic.invalid_enum',
46
+ :message => "Field `#{ full_path( path ) }` does not contain an allowed reference value from this list: `#{@from}`",
47
+ :reference => { :field_name => full_path( path ) }
48
+ )
49
+ end
50
+
51
+ errors
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,158 @@
1
+ module Hoodoo
2
+ module Presenters
3
+ # A JSON schema member
4
+ class Field
5
+
6
+ # The name of the field.
7
+ #
8
+ attr_accessor :name
9
+
10
+ # +true+ if the field is required.
11
+ #
12
+ attr_accessor :required
13
+
14
+ # Default value, if supplied.
15
+ #
16
+ attr_accessor :default
17
+
18
+ # Initialize a Field instance with the appropriate name and options.
19
+ #
20
+ # +name+:: The JSON key.
21
+ # +options+:: A +Hash+ of options, e.g. :required => true.
22
+ #
23
+ def initialize(name, options = {})
24
+ @name = name.to_s
25
+ @required = options.has_key?( :required ) ? options[ :required ] : false
26
+ @path = options.has_key?( :path ) ? options[ :path ] : []
27
+
28
+ if options.has_key?( :default )
29
+ @has_default = true
30
+ @default = Hoodoo::Utilities.stringify( options[ :default ] )
31
+ else
32
+ @has_default = false
33
+ @default = nil
34
+ end
35
+ end
36
+
37
+ # Does this property have a defined default (which may be defined as
38
+ # +nil+) rather than having no defined value (+nil+ or otherwise)?
39
+ # Returns +true+ if it has a default, +false+ if it has no default.
40
+ #
41
+ def has_default?
42
+ !! @has_default
43
+ end
44
+
45
+ # Check if data is required and return a Hoodoo::Errors instance.
46
+ #
47
+ def validate( data, path = '' )
48
+ errors = Hoodoo::Errors.new
49
+
50
+ if data.nil? && @required
51
+ errors.add_error(
52
+ 'generic.required_field_missing',
53
+ :message => "Field `#{ full_path( path ) }` is required",
54
+ :reference => { :field_name => full_path( path ) }
55
+ )
56
+ end
57
+
58
+ errors
59
+ end
60
+
61
+ # Dive down into a given hash along path array +@path+, building new hash
62
+ # entries if necessary at each path level until the last one. At that
63
+ # last level, assign the given object.
64
+ #
65
+ # +data+:: The object to build at the final path entry - usually an
66
+ # empty Array or Hash.
67
+ #
68
+ # +target+:: The Hash (may be initially empty) in which to build the
69
+ # path of keys from internal data +@path+.
70
+ #
71
+ # Returns the full path array that was used (a clone of +@path+).
72
+ #
73
+ def render( data, target )
74
+ return if @name.empty?
75
+
76
+ root = target
77
+ path = @path.clone
78
+ final = path.pop.to_s
79
+
80
+ path.each do | element |
81
+ element = element.to_s
82
+ root[ element ] = {} unless root.has_key?( element )
83
+ root = root[ element ]
84
+ end
85
+
86
+ root[ final ] = data
87
+ return path << final
88
+ end
89
+
90
+ # Invoke a given block, passing this item. See
91
+ # Hoodoo::Presenters::Base#walk for why.
92
+ #
93
+ # &block:: Mandatory block, which is passed 'self' when called.
94
+ #
95
+ def walk( &block )
96
+ block.call( self )
97
+ end
98
+
99
+ # Return the full path and name of this field
100
+ # +path+:: The JSON path or nil, e.g. 'one.two'
101
+ #
102
+ def full_path( path )
103
+ return @name.to_s if path.nil? or path.empty?
104
+ return path.to_s if @name.nil? or @name.empty?
105
+ path + '.' + @name.to_s
106
+ end
107
+
108
+ protected
109
+
110
+ # Dive down into a given target data hash using the given array of path
111
+ # keys, returning the result at the final key in the path. E.g. if the
112
+ # Hash is "{ :foo => { :bar => { :baz => "hello" } } }" then a path of
113
+ # "[ :foo, :bar ]" would yield "{ :baz => "hello" }".
114
+ #
115
+ def read_at_path( from_target, with_path )
116
+ with_path.each do | element |
117
+ element = element.to_s
118
+ from_target = from_target[ element ]
119
+ break if from_target.nil?
120
+ end
121
+
122
+ return from_target
123
+ end
124
+
125
+ # Rename a property to the given name. The internal name is changed and
126
+ # the last path entry set to the same name (if a path is present). Paths
127
+ # of sub-properties (if any) are updated with the parent's new name.
128
+ #
129
+ # This is a specialist interface which is intended for internal use
130
+ # under unusual circumstances.
131
+ #
132
+ # +name+:: New property name. Must be a String.
133
+ #
134
+ def rename( name )
135
+ depth = @path.count - 1
136
+ @name = name
137
+
138
+ rewrite_path( depth, name )
139
+ end
140
+
141
+ # Change the +@path+ array by writing a given value in at a given index.
142
+ # If this property has any sub-properties, then those are recursively
143
+ # updated to change the same depth item to the new name in all of them.
144
+ #
145
+ # +depth+:: Index into +@path+ to make modifications.
146
+ # +name+:: Value to write at that index.
147
+ #
148
+ def rewrite_path( depth, name )
149
+ @path[ depth ] = name if depth >= 0
150
+ return if @properties.nil?
151
+
152
+ @properties.each do | property_name, property |
153
+ property.rewrite_path( depth, name )
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,26 @@
1
+ module Hoodoo
2
+ module Presenters
3
+
4
+ # A JSON Float schema member.
5
+ #
6
+ class Float < Hoodoo::Presenters::Field
7
+
8
+ # Check if data is a valid Float and return a Hoodoo::Errors instance.
9
+ #
10
+ def validate( data, path = '' )
11
+ errors = super( data, path )
12
+ return errors if errors.has_errors? || ( ! @required && data.nil? )
13
+
14
+ unless data.is_a?( ::Float )
15
+ errors.add_error(
16
+ 'generic.invalid_float',
17
+ :message => "Field `#{ full_path( path ) }` is an invalid float",
18
+ :reference => { :field_name => full_path( path ) }
19
+ )
20
+ end
21
+
22
+ errors
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,361 @@
1
+ module Hoodoo
2
+ module Presenters
3
+ # A JSON hash schema member
4
+ class Hash < Hoodoo::Presenters::Field
5
+
6
+ include Hoodoo::Presenters::BaseDSL
7
+
8
+ # Hash DSL: Define a specific named key that is allowed (or even required)
9
+ # in the hash. The optional block uses Hoodoo::Presenters::BaseDSL to
10
+ # describe the required form of the key's value. If the block is omitted,
11
+ # any value is permitted.
12
+ #
13
+ # The singular #key method is useful when you want to describe an object
14
+ # which has known permitted keys yielding to required value types. For
15
+ # example, you may have a Hash which defines configuration data for a
16
+ # variety of fixed, known types, with the Hash keys being the type name.
17
+ # See Hoodoo::Data::Types::CalculatorConfiguration for an example.
18
+ #
19
+ # Example:
20
+ #
21
+ # hash :nested_object do
22
+ # key :this_key_is_optional do
23
+ # text :text_value
24
+ # string :string_value, :length => 16
25
+ # end
26
+ #
27
+ # # ...multiple calls would be made to #key usually, to define
28
+ # # each allowed key.
29
+ #
30
+ # end
31
+ #
32
+ # ...defines something that this JSON would validate against:
33
+ #
34
+ # {
35
+ # "nested_object": {
36
+ # "this_key_is_optional": {
37
+ # "text_value": "some arbitrary length string",
38
+ # "string_value": "I'm <= 16 chars"
39
+ # }
40
+ # }
41
+ # }
42
+ #
43
+ # This JSON would not validate as it includes an unrecognised key:
44
+ #
45
+ # {
46
+ # "nested_object": {
47
+ # "this_key_is_unknown": '', // (...any value at all...)
48
+ # "this_key_is_optional": {
49
+ # "text_value": "Some arbitrary length string",
50
+ # "string_value": "I'm <= 16 chars"
51
+ # }
52
+ # }
53
+ # }
54
+ #
55
+ def key( name, options = {}, &block )
56
+ if @specific == false
57
+ raise "Can't use \#keys and then \#key in the same hash definition - use one or the other"
58
+ end
59
+
60
+ # If we're defining specific keys and some of those keys have fields
61
+ # with defaults, we need to merge those up to provide a whole-key
62
+ # equivalent default. If someone renders an empty hash they expect a
63
+ # specific key with some internal defaults to be rendered; doing this
64
+ # amalgamation up to key level is the easiest way to handle that.
65
+
66
+ if options.has_key?( :default )
67
+ @has_default = true
68
+
69
+ @default ||= {}
70
+ @default.merge!( Hoodoo::Utilities.stringify( options[ :default ] ) )
71
+ end
72
+
73
+ @specific = true
74
+
75
+ klass = block_given? ? Hoodoo::Presenters::Object : Hoodoo::Presenters::Field
76
+ prop = property( name, klass, options, &block )
77
+
78
+ if prop && prop.respond_to?( :is_internationalised? ) && prop.is_internationalised?
79
+ internationalised()
80
+ end
81
+ end
82
+
83
+ # Hash DSL: Define general parameters allowed for keys in a Hash and, if
84
+ # a block is given, use Hoodoo::Presenters::BaseDSL to describe how any
85
+ # of the values in the Hash must look.
86
+ #
87
+ # +options+:: A +Hash+ of options - currently only +:length => [n]+ is
88
+ # supported, which describes the maximum permitted length of
89
+ # the key. If this option is omitted, keys can be any length.
90
+ #
91
+ # Example:
92
+ #
93
+ # hash :nested_object do
94
+ # keys :length => 4 do
95
+ # text :text_value
96
+ # end
97
+ #
98
+ # # ...only one call is made to #keys, because it defines the
99
+ # # permitted form of all keys and values for the whole Hash.
100
+ #
101
+ # end
102
+ #
103
+ # ...defines a Hash with keys that have a maximum string length of 4
104
+ # characters (inclusive) and simple object values with just a single text
105
+ # field. This JSON would validate against the definition:
106
+ #
107
+ # {
108
+ # "nested_object": {
109
+ # "one": {
110
+ # "text_value": "Some arbitrary length string"
111
+ # },
112
+ # "two": {
113
+ # "text_value": "Another arbitrary length string"
114
+ # }
115
+ # }
116
+ # }
117
+ #
118
+ # This JSON would not validate as one of the keys is too long:
119
+ #
120
+ # {
121
+ # "nested_object": {
122
+ # "one": {
123
+ # "text_value": "Some arbitrary length string"
124
+ # },
125
+ # "a_very_long_key": {
126
+ # "text_value": "Another arbitrary length string"
127
+ # }
128
+ # }
129
+ # }
130
+ #
131
+ # This JSON would not validate as the value's object format is wrong:
132
+ #
133
+ # {
134
+ # "nested_object": {
135
+ # "one": {
136
+ # "text_value": 11
137
+ # }
138
+ # }
139
+ # }
140
+ #
141
+ def keys( options = {}, &block )
142
+ unless @specific.nil?
143
+ raise "Can't use \#key and then \#keys in the same hash definition, or use \#keys more than once"
144
+ end
145
+
146
+ if options.has_key?( :default )
147
+ raise "It doesn't make sense to specify a default for unknown source data keys, since every key that's present in the source data has an associated value by definition, even if that value is nil"
148
+ end
149
+
150
+ @specific = false
151
+
152
+ klass = options.has_key?( :length ) ? Hoodoo::Presenters::String : Hoodoo::Presenters::Text
153
+ property('keys', klass, options)
154
+
155
+ klass = block_given? ? Hoodoo::Presenters::Object : Hoodoo::Presenters::Field
156
+ prop = property( 'values', klass, {}, &block )
157
+
158
+ if prop && prop.respond_to?( :is_internationalised? ) && prop.is_internationalised?
159
+ internationalised()
160
+ end
161
+ end
162
+
163
+ # The properties of this object, a +hash+ of +Field+ instances.
164
+ attr_accessor :properties
165
+
166
+ # Check if data is a valid Hash and return a Hoodoo::Errors instance.
167
+ #
168
+ def validate( data, path = '' )
169
+ errors = super( data, path )
170
+ return errors if errors.has_errors? || ( ! @required && data.nil? )
171
+
172
+ if data.is_a?( ::Hash )
173
+
174
+ # No hash entry schema? No hash entry validation, then.
175
+ #
176
+ unless @properties.nil?
177
+ if @specific == true
178
+
179
+ allowed_keys = @properties.keys
180
+ unrecognised_keys = data.keys - allowed_keys
181
+
182
+ unless unrecognised_keys.empty?
183
+ errors.add_error(
184
+ 'generic.invalid_hash',
185
+ :message => "Field `#{ full_path( path ) }` is an invalid hash due to unrecognised keys `#{ unrecognised_keys.join( ', ' ) }`",
186
+ :reference => { :field_name => full_path( path ) }
187
+ )
188
+ end
189
+
190
+ data.each do | key, value |
191
+ property = @properties[ key ]
192
+ errors.merge!( property.validate( value, full_path( path ) ) ) unless property.nil?
193
+ end
194
+
195
+ @properties.each do | name, property |
196
+ if property.required && ! data.has_key?( name )
197
+ local_path = full_path(path) + '.' + name
198
+
199
+ errors.add_error(
200
+ 'generic.required_field_missing',
201
+ :message => "Field `#{local_path}` is required",
202
+ :reference => { :field_name => local_path }
203
+ )
204
+ end
205
+ end
206
+
207
+ else
208
+
209
+ keys_property = @properties[ 'keys' ]
210
+ values_property = @properties[ 'values' ]
211
+
212
+ if keys_property.required && data.empty?
213
+
214
+ errors.add_error(
215
+ 'generic.required_field_missing',
216
+ :message => "Field `#{ full_path( path ) }` is required (Hash, if present, must contain at least one key)",
217
+ :reference => { :field_name => full_path( path ) }
218
+ )
219
+
220
+ else
221
+
222
+ # Need to adjust the above property names for each of the
223
+ # unknown-named keys coming into this generic key hash. That
224
+ # way, errors are reported at the correct "path", including the
225
+ # 'dynamic' incoming hash key name.
226
+
227
+ data.each do | key, value |
228
+ local_path = full_path( path )
229
+
230
+ # So use the "keys property" as a validator for the format
231
+ # (i.e. just length, in practice) of the current key we're
232
+ # examining in the data from the caller. Use the "values
233
+ # property" to validate the value in the data hash. Both are
234
+ # temporarily renamed to match the key in the client data so
235
+ # that field paths shown in errors will be correct.
236
+
237
+ keys_property.rename( key )
238
+ values_property.rename( key )
239
+
240
+ errors.merge!( keys_property.validate( key, local_path ) )
241
+ errors.merge!( values_property.validate( value, local_path ) )
242
+ end
243
+
244
+ keys_property.rename( 'keys' )
245
+ values_property.rename( 'values' )
246
+
247
+ end
248
+ end
249
+ end # 'unless @properties.nil?'
250
+
251
+ else # 'if data.is_a?( ::Hash )'
252
+ errors.add_error(
253
+ 'generic.invalid_hash',
254
+ :message => "Field `#{ full_path( path ) }` is an invalid hash",
255
+ :reference => { :field_name => full_path( path ) }
256
+ )
257
+ end
258
+
259
+ errors
260
+ end
261
+
262
+ # Render a hash into the target hash based on the internal state that
263
+ # describes this instance's current path (position in the heirarchy of
264
+ # nested schema entities).
265
+ #
266
+ # +data+:: The Hash to render.
267
+ # +target+:: The Hash that we render into. A "path" of keys leading to
268
+ # nested Hashes is built via +super()+, with the final
269
+ # key entry yielding the rendered hash.
270
+ #
271
+ def render( data, target )
272
+
273
+ # Data provided is explicitly nil or not a hash? Don't need to render
274
+ # anything beyond 'nil' at the field (the not-hash case covers nil and
275
+ # covers invalid input, which is treated as nil).
276
+ #
277
+ return super( nil, target ) unless data.is_a?( ::Hash )
278
+
279
+ # This relies on pass-by-reference; we'll update 'hash' later.
280
+
281
+ hash = {}
282
+ path = super( hash, target )
283
+
284
+ # No defined schema for the hash contents? Just use the data as-is;
285
+ # we can do no validation. Have to hope the caller has given us data
286
+ # that would be valid as JSON. Otherwise, use the key definitions.
287
+
288
+ if @properties.nil?
289
+ hash.merge!( data )
290
+
291
+ else
292
+ subtarget = {}
293
+
294
+ if @specific == true
295
+
296
+ @properties.each do | name, property |
297
+ name = name.to_s
298
+ has_key = data.has_key?( name )
299
+
300
+ next unless has_key || property.has_default?()
301
+
302
+ property.render( has_key ? data[ name ] : property.default, subtarget )
303
+ end
304
+
305
+ else
306
+
307
+ # The "keys :default => ..." part of the DSL is theoretically
308
+ # possible but meaningless. The principle everywhere else is that
309
+ # if the input data has an explicit "nil" then the output data has
310
+ # the same. In that case, the input data hash either has non-nil
311
+ # or nil *explicit* values, so there are no conditions under which
312
+ # we would apply a default.
313
+
314
+ values_property = @properties[ 'values' ]
315
+
316
+ # As with validation, have to temporarily rename the above property
317
+ # (and update its path) so that we render under the correct key
318
+ # name, those names coming from the caller-supplied hash and thus
319
+ # not known at any time other than right now.
320
+
321
+ data.each do | key, value |
322
+ values_property.rename( key )
323
+ values_property.render( value, subtarget )
324
+ end
325
+
326
+ values_property.rename( 'values' )
327
+ end
328
+
329
+ rendered = read_at_path( subtarget, path )
330
+ hash.merge!( rendered ) unless rendered.nil?
331
+ end
332
+ end
333
+
334
+ # Invoke a given block, passing this item; call recursively for any
335
+ # defined sub-fields too. See Hoodoo::Presenters::Base#walk for why.
336
+ #
337
+ # &block:: Mandatory block, which is passed 'self' when called.
338
+ #
339
+ def walk( &block )
340
+ block.call( self )
341
+
342
+ unless @properties.nil?
343
+ if @specific == true
344
+
345
+ @properties.each do | name, property |
346
+ property.walk( &block )
347
+ end
348
+
349
+ else
350
+
351
+ values_property = @properties[ 'values' ]
352
+ values_property.properties.each do | name, property |
353
+ property.walk( &block )
354
+ end unless values_property.respond_to?( :properties ) == false || values_property.properties.nil?
355
+
356
+ end
357
+ end
358
+ end
359
+ end
360
+ end
361
+ end