hoodoo 1.0.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 (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,250 @@
1
+ ########################################################################
2
+ # File:: permissions.rb
3
+ # (C):: Loyalty New Zealand 2014
4
+ #
5
+ # Purpose:: Allow/ask/deny support for resources and actions.
6
+ # ----------------------------------------------------------------------
7
+ # 26-Jan-2015 (ADH): Created.
8
+ ########################################################################
9
+
10
+ module Hoodoo; module Services
11
+
12
+ # The Permissions class provides a way to store and recall information on
13
+ # action behaviour for resources. It is just a way to store and query this
14
+ # information; actually enforcing the result is up to the caller.
15
+ #
16
+ # Permissions are based on the standard actions - +list+, +show+, +create+,
17
+ # +update+ and +delete+ - with defined permissions of constants DENY
18
+ # (prohibit access), ALLOW (allow access) and ASK. The intention of ASK is
19
+ # that some other component - usually a service application - should be
20
+ # passed details of the request and asked if it should be permitted.
21
+ #
22
+ # Callers must *ensure* they *only* use the DENY, ALLOW and ASK constants
23
+ # defined herein, without making assumptions about their assigned values.
24
+ #
25
+ # There is both a default set of permissions in addition to per-resource
26
+ # permissions and there is a fallback for cases where a permission for a
27
+ # particular action has not been defined. This lets you define the baseline
28
+ # behaviour in the fallback cases and only describe exceptions to that
29
+ # baseline through the Permissions interface, minimising caller workload.
30
+ #
31
+ # Hoodoo::Services::Middleware uses an instance of this class to determine
32
+ # whether or not it should pass on inbound requests to service applications.
33
+ #
34
+ # Example:
35
+ #
36
+ # Here, an object is created with a default fallback of DENY, then has the
37
+ # action "list" allowed for all resources and says that resource "Member"
38
+ # must ask someone for permission if its "show" action is requested.
39
+ # Another resource "Ping" allows any action unconditionally.
40
+ #
41
+ # p = Hoodoo::Services::Permissions.new
42
+ # p.set_default( :list, Hoodoo::Services::Permissions::ALLOW )
43
+ # p.set_resource( :Member, :show, Hoodoo::Services::Permissions::ASK )
44
+ # p.set_resource_fallback( :Ping, Hoodoo::Services::Permissions::ALLOW )
45
+ #
46
+ # puts JSON.pretty_generate( p.to_h() )
47
+ #
48
+ # # Yields...
49
+ # #
50
+ # # {
51
+ # # "default": {
52
+ # # "else": "deny",
53
+ # # "actions": {
54
+ # # "list": "allow"
55
+ # # }
56
+ # # },
57
+ # # "resources": {
58
+ # # "Member": {
59
+ # # "actions": {
60
+ # # "show": "ask"
61
+ # # }
62
+ # # },
63
+ # # "Ping": {
64
+ # # "else": "allow"
65
+ # # }
66
+ # # }
67
+ # # }
68
+ #
69
+ class Permissions
70
+
71
+ # Permission is denied; the action should not be permitted.
72
+ #
73
+ DENY = 'deny'
74
+
75
+ # Permission is granted; the action should be permitted.
76
+ #
77
+ ALLOW = 'allow'
78
+
79
+ # Something else (e.g. a service application) needs to be asked to see if
80
+ # it permits the action.
81
+ #
82
+ ASK = 'ask'
83
+
84
+ # All currently known (allowed/supported) permission policies.
85
+ #
86
+ ALLOWED_POLICIES = [
87
+ DENY,
88
+ ALLOW,
89
+ ASK
90
+ ]
91
+
92
+ # Create a new Permissions instance, optionally from a Hash of the format
93
+ # returned by #to_h.
94
+ #
95
+ # By default the object is initialised with a default fallback which
96
+ # denies all actions for all resources.
97
+ #
98
+ def initialize( hash = nil )
99
+ if hash.nil?
100
+ @permissions = {}
101
+ set_default_fallback( DENY )
102
+ else
103
+ from_h!( hash )
104
+ end
105
+ end
106
+
107
+ # Set the default fallback for actions. If a resource does not have a
108
+ # specific entry for it in the Permissions object and if the action does
109
+ # not have a default permission, then this permission used.
110
+ #
111
+ # +permission+:: DENY, ALLOW or ASK.
112
+ #
113
+ def set_default_fallback( permission )
114
+ action_name = action_name.to_s
115
+
116
+ @permissions[ 'default' ] ||= {}
117
+ @permissions[ 'default' ][ 'else' ] = permission
118
+ end
119
+
120
+ # Set the default permission for the given action. If a resource does not
121
+ # have a specific entry for it in the Permissions object but the action
122
+ # matches the given name, then this permission is used.
123
+ #
124
+ # +action_name+:: Action as a String or Symbol, from: +list+, +show+,
125
+ # +create+, +update+ or +delete+.
126
+ #
127
+ # +permission+:: DENY, ALLOW or ASK.
128
+ #
129
+ def set_default( action_name, permission )
130
+ action_name = action_name.to_s
131
+
132
+ @permissions[ 'default' ] ||= {}
133
+ @permissions[ 'default' ][ 'actions' ] ||= {}
134
+ @permissions[ 'default' ][ 'actions' ][ action_name ] = permission
135
+ end
136
+
137
+ # Set the default fallback for a resource. If the resource is asked to
138
+ # perform an action that's not otherwise listed in the resource's entry
139
+ # in the Permissions object, then this permission is used.
140
+ #
141
+ # +resource_name+:: Resource name as a Symbol or String, e.g. "+Purchase+"
142
+ # or +:Member+.
143
+ #
144
+ # +permission+:: DENY, ALLOW or ASK.
145
+ #
146
+ def set_resource_fallback( resource_name, permission )
147
+ resource_name = resource_name.to_s
148
+
149
+ @permissions[ 'resources' ] ||= {}
150
+ @permissions[ 'resources' ][ resource_name ] ||= {}
151
+ @permissions[ 'resources' ][ resource_name ][ 'else' ] = permission
152
+ end
153
+
154
+ # Set the permissions an action on a resource.
155
+ #
156
+ # +resource_name+:: Resource name as a Symbol or String, e.g. "+Purchase+"
157
+ # or +:Member+.
158
+ #
159
+ # +action_name+:: Action as a String or Symbol, from: +list+, +show+,
160
+ # +create+, +update+ or +delete+.
161
+ #
162
+ # +permission+:: DENY, ALLOW or ASK.
163
+ #
164
+ def set_resource( resource_name, action_name, permission )
165
+ resource_name = resource_name.to_s
166
+ action_name = action_name.to_s
167
+
168
+ @permissions[ 'resources' ] ||= {}
169
+ @permissions[ 'resources' ][ resource_name ] ||= {}
170
+ @permissions[ 'resources' ][ resource_name ][ 'actions' ] ||= {}
171
+ @permissions[ 'resources' ][ resource_name ][ 'actions' ][ action_name ] = permission
172
+ end
173
+
174
+ # For the given resource, is the given action permitted? Returns one of the
175
+ # ALLOW, DENY or ASK constant values.
176
+ #
177
+ # +resource_name+:: Resource name as a Symbol or String, e.g. "+Purchase+"
178
+ # or +:Member+.
179
+ #
180
+ # +action_name+:: Action as a String or Symbol, from: +list+, +show+,
181
+ # +create+, +update+ or +delete+.
182
+ #
183
+ def permitted?( resource_name, action_name )
184
+ resource_name = resource_name.to_s
185
+ action_name = action_name.to_s
186
+
187
+ tree = if @permissions.has_key?( 'resources' )
188
+ @permissions[ 'resources' ][ resource_name ]
189
+ end || {}
190
+
191
+ result = permitted_in?( tree, action_name )
192
+
193
+ if result.nil?
194
+ tree = @permissions[ 'default' ] || {}
195
+ result = permitted_in?( tree, action_name )
196
+ end
197
+
198
+ return result || DENY
199
+ end
200
+
201
+ # Return a Hash representative of this permissions object, which can be
202
+ # stored elsewhere, used to initialise another instance or written to an
203
+ # existing instance with #from_h!.
204
+ #
205
+ def to_h
206
+ @permissions
207
+ end
208
+
209
+ # Overwrite this instances's permissions with those from the given Hash.
210
+ #
211
+ # +hash+:: Permissions hash, which must come (directly or indirectly) from
212
+ # a #to_h call.
213
+ #
214
+ def from_h!( hash )
215
+ @permissions = Hoodoo::Utilities.stringify( hash )
216
+ end
217
+
218
+ # Merge the permissions described by the given Hash with those inside this
219
+ # instance. This will add to, or overwrite permissions with those from the
220
+ # given input Hash.
221
+ #
222
+ # +hash+:: Permissions hash, which must come (directly or indirectly) from
223
+ # a #to_h call.
224
+ #
225
+ def merge!( hash )
226
+ @permissions = Hoodoo::Utilities.deep_merge_into(
227
+ @permissions,
228
+ Hoodoo::Utilities.stringify( hash )
229
+ )
230
+ end
231
+
232
+ private
233
+
234
+ # For a given permissions sub-tree at the level of +actions+ / +else+,
235
+ # return an entry for the given action name. May return +nil+ if the
236
+ # tree has neither a matching action nor an +else+ case.
237
+ #
238
+ # +tree+:: Permissions sub-tree at the +actions+ / +else+ level.
239
+ # +action_name+:: As for #permitted?
240
+ #
241
+ def permitted_in?( tree, action_name )
242
+ result = if tree.has_key?( 'actions' )
243
+ tree[ 'actions' ][ action_name ]
244
+ end || tree[ 'else' ]
245
+
246
+ return result
247
+ end
248
+ end
249
+
250
+ end; end
@@ -0,0 +1,189 @@
1
+ ########################################################################
2
+ # File:: request.rb
3
+ # (C):: Loyalty New Zealand 2014
4
+ #
5
+ # Purpose:: A high level description of a client's request, with all of
6
+ # the "raw" Rack request data parsed, verified as far as
7
+ # possible and generally cleaned up. Instances of this class
8
+ # are given to Hoodoo::Services::Implementation methods for
9
+ # each new request.
10
+ # ----------------------------------------------------------------------
11
+ # 24-Sep-2014 (ADH): Created.
12
+ ########################################################################
13
+
14
+ module Hoodoo; module Services
15
+
16
+ # Instances of the Hoodoo::Services::Request class are passed to service
17
+ # interface implementations when requests come in via Rack, after basic
18
+ # checks have been passed and a particular interface implementation has
19
+ # been identified by endpoint.
20
+ #
21
+ # Descriptions of default values expected out of accessors herein refer
22
+ # to the use case when driven through Hoodoo::Services::Middleware. If the
23
+ # class is instantiated "bare" it gains no default values at all (all
24
+ # read accessors would report +nil+).
25
+ #
26
+ class Request
27
+
28
+ # Encapsulation of all parameters related only to modifying a
29
+ # list of results. Other parameters may modify lists too, but they
30
+ # also modify other representations (e.g. single-resource 'show').
31
+ #
32
+ class ListParameters
33
+
34
+ # List offset, for index views; an integer; always defined.
35
+ #
36
+ attr_accessor :offset
37
+
38
+ # List page size, for index views; an integer; always defined.
39
+ #
40
+ attr_accessor :limit
41
+
42
+ # A Hash of String keys and values, where each key is a field for
43
+ # sorting and each value is the direction to sort that field.
44
+ #
45
+ attr_accessor :sort_data
46
+
47
+ # List search key/value pairs as a hash, all keys/values strings; {}
48
+ # if there's no search data in the request URI query string.
49
+ #
50
+ attr_accessor :search_data
51
+
52
+ # List filter key/value pairs as a hash, all keys/values strings; {}
53
+ # if there's no filter data in the request URI query string.
54
+ #
55
+ attr_accessor :filter_data
56
+
57
+ # Set up defaults in this instance.
58
+ #
59
+ def initialize
60
+ self.offset = 0
61
+ self.limit = 50
62
+ self.sort_data = { 'created_at' => 'desc' }
63
+ self.search_data = {}
64
+ self.filter_data = {}
65
+ end
66
+ end
67
+
68
+ # Requested locale for internationalised operations; +"en-nz"+ by
69
+ # default.
70
+ #
71
+ attr_accessor :locale
72
+
73
+ # Define read/write accessors for properties related to "X-Foo"
74
+ # headers. See the Middleware for details.
75
+ #
76
+ Hoodoo::Client::Headers.define_accessors_for_header_equivalents( self )
77
+
78
+ # Hash of HTTP headers _in_ _Rack_ _format_ - e.g. +HTTP_X_INTERACTION_ID+
79
+ # for the "X-Interaction-ID" header, for read-only use. All keys are in
80
+ # upper case, are Strings, have "HTTP_" at the start and use underscores
81
+ # where the original request might've used an underscore or hyphen. The
82
+ # usual curious Rack exceptions of +CONTENT_TYPE+ and +CONTENT_LENGTH+ do
83
+ # apply, though. This is a superset of header values including those sent
84
+ # by the client in its request and anything Rack itself might have added.
85
+ #
86
+ attr_accessor :headers
87
+
88
+ # Parsed payload hash, for create and update actions only; else +nil+.
89
+ #
90
+ attr_accessor :body
91
+
92
+ # An array of zero or more path components making up the URI *after* the
93
+ # service endpoint has been accounted for. For example, with a service
94
+ # endpoint of "products", this URI:
95
+ #
96
+ # http://test.com/v1/products/1234/foo.json
97
+ #
98
+ # ...would lead to this path component array:
99
+ #
100
+ # [ '1234', 'foo' ]
101
+ #
102
+ # The first element of the path components array is exposed in the
103
+ # read-only #ident accessor.
104
+ #
105
+ attr_reader :uri_path_components
106
+
107
+ # Set the array returned by #uri_path_components and record the first
108
+ # element in the value returned by #ident.
109
+ #
110
+ # +ary+:: Path component array to record. If +nil+ or not an array,
111
+ # +nil+ is stored for uri_path_components and #ident.
112
+ #
113
+ def uri_path_components=( ary )
114
+ if ary.is_a?( ::Array )
115
+ @uri_path_components = ary
116
+ @ident = ary.first
117
+ else
118
+ @uri_path_components = nil
119
+ @ident = nil
120
+ end
121
+ end
122
+
123
+ # The first entry in the #uri_path_components array, or +nil+ if the
124
+ # array is empty. This supports a common case for inter-resource calls
125
+ # where a UUID or other unique identifier is provided through the first
126
+ # path element ("+.../v1/resource/uuid+").
127
+ #
128
+ attr_reader :ident
129
+
130
+ # A filename extension on the URI path component, if any, else an empty
131
+ # string. The _first_ dot in the _last_ path component is looked for (see
132
+ # also #uri_path_components), so for example this URI:
133
+ #
134
+ # http://test.com/v1/products/1.2.3.4/foo.my.tar.gz
135
+ #
136
+ # ...would lead to this URI path extension string:
137
+ #
138
+ # 'my.tar.gz'
139
+ #
140
+ attr_accessor :uri_path_extension
141
+
142
+ # The Hoodoo::Services::Request::ListParameters instance
143
+ # associated with this request.
144
+ #
145
+ attr_accessor :list
146
+
147
+ # Define a set of now-deprecated accessors that are basically
148
+ # just proxies through to the "list" instance. See #list.
149
+ #
150
+ %i{
151
+ offset
152
+ limit
153
+ sort_data
154
+ search_data
155
+ filter_data
156
+ }.each do | method |
157
+ define_method( "list_#{ method }" ) do
158
+ list.send( method )
159
+ end
160
+
161
+ define_method( "list_#{ method }=" ) do | value |
162
+ list.send( "#{ method }=", value )
163
+ end
164
+ end
165
+
166
+ # Array of strings giving requested embedded items; [] if there are
167
+ # none requested.
168
+ #
169
+ attr_accessor :embeds
170
+
171
+ # Array of strings giving requested referenced items; [] if there are
172
+ # none requested.
173
+ #
174
+ attr_accessor :references
175
+
176
+ # Set up defaults in this instance.
177
+ #
178
+ def initialize
179
+ self.locale = 'en-nz'
180
+ self.uri_path_components = []
181
+ self.uri_path_extension = ''
182
+ self.list = Hoodoo::Services::Request::ListParameters.new
183
+ self.embeds = []
184
+ self.references = []
185
+ self.headers = {}.freeze
186
+ end
187
+ end
188
+
189
+ end; end
@@ -0,0 +1,316 @@
1
+ ########################################################################
2
+ # File:: response.rb
3
+ # (C):: Loyalty New Zealand 2014
4
+ #
5
+ # Purpose:: A high level description of a service's response to a
6
+ # client's request. The middleware constructs instances and
7
+ # fills in some of the data for every client request, then
8
+ # passes it to Hoodoo::Services::Implementation methods so
9
+ # the service can fill in the rest of the data.
10
+ # ----------------------------------------------------------------------
11
+ # 24-Sep-2014 (ADH): Created.
12
+ ########################################################################
13
+
14
+ require 'json'
15
+
16
+ module Hoodoo; module Services
17
+
18
+ # The service middleware creates a Hoodoo::Services::Response instance for
19
+ # each request it handles, populating it with some data before and after the
20
+ # service implementation runs as part of standard pre- and post-processing.
21
+ # In the middle, the service implementation is given the instance and adds
22
+ # its own data to it.
23
+ #
24
+ # The instance carries data about both error conditions and successful work.
25
+ # In the successful case, #http_status_code and #body data is set by the
26
+ # service and used in the response. In the error case (see #errors), the
27
+ # HTTP status code is taken from the first error in the errors collection and
28
+ # the response body will be the JSON representation of that collection - any
29
+ # HTTP status code or response body data previously set by the service will
30
+ # be ignored.
31
+ #
32
+ class Response
33
+
34
+ # Obtain a reference to the Hoodoo::Errors instance for this response;
35
+ # use Hoodoo::Errors#add_error to add to the collection directly. For
36
+ # convenience, this class also provides the #add_error proxy instance
37
+ # method (syntactic sugar for most service implementations, but with a
38
+ # return value that helps keep the service middleware code clean).
39
+ #
40
+ # It's possible to change the errors object if you want to swap it for any
41
+ # reason, though this is generally discouraged - especially if the existing
42
+ # errors collection isn't empty. The middleware does this as part of
43
+ # request handling, but generally speaking nobody else should need to.
44
+ #
45
+ attr_accessor :errors
46
+
47
+ # HTTP status code that will be involved in the response. Default is 200.
48
+ # Integer, or something that can be converted to one with +to_i+. If errors
49
+ # are added to the response then the status code is derived from the first
50
+ # error in the collection, overriding any value set here. See #errors.
51
+ #
52
+ attr_accessor :http_status_code
53
+
54
+ # A service implementation can set (and read back, should it wish) the
55
+ # API call response body data using this #body / #body= accessor. This is
56
+ # converted to a client-facing representation automatically (e.g. to JSON).
57
+ #
58
+ # The response body *MUST* be either a Ruby +Array+ or a Ruby +Hash+.
59
+ # For *internal* *use* *only* a Ruby +String+ of pre-encoded response data
60
+ # is also accepted.
61
+ #
62
+ # This method is aliased as #set_resource, for semantic use when you want
63
+ # to set the response body to a representation (as a Hash) of a resource.
64
+ # When you want to set an Array of items for a list, it is strongly
65
+ # recommended that you call #set_resources and pass a total dataset size
66
+ # in addition to just the Array containing a page of list data.
67
+ #
68
+ # When reading response data, the body information is only valid if
69
+ # method #halt_processing? returns +false+.
70
+ #
71
+ attr_accessor :body
72
+ alias_method :set_resource, :body=
73
+
74
+ # Read back a the dataset size given by a prior call to #set_resources,
75
+ # or +nil+ if none has been provided (either the response contains no
76
+ # list yet/at all, or an Array was given but the dataset size was not
77
+ # supplied).
78
+ #
79
+ attr_reader :dataset_size
80
+
81
+ # Create a new instance, ready to take on a response. The service
82
+ # middleware is responsible for doing this.
83
+ #
84
+ # +interaction_id+:: The UUID of the interaction taking place for which a
85
+ # response is required.
86
+ #
87
+ def initialize( interaction_id )
88
+
89
+ unless Hoodoo::UUID.valid?( interaction_id )
90
+ raise "Hoodoo::Services::Response.new must be given a valid Interaction ID (got '#{ interaction_id.inspect }')"
91
+ end
92
+
93
+ @interaction_id = interaction_id
94
+ @errors = Hoodoo::Errors.new()
95
+ @headers = {}
96
+ @http_status_code = 200
97
+ @body = {}
98
+ @dataset_size = nil
99
+
100
+ end
101
+
102
+ # Returns +true+ if processing should halt, e.g. because errors have been
103
+ # added to the errors collection. Check here whenever you would consider an
104
+ # early exit due to errors arising in processing (otherwise they will just
105
+ # continue to accumulate).
106
+ #
107
+ def halt_processing?
108
+ @errors.has_errors?
109
+ end
110
+
111
+ # Similar to #body and #set_resource, but used when you are returning an
112
+ # array of items. Although you can just assign an array to either of
113
+ # #body or #set_resource, calling #set_resources is more semantically
114
+ # correct and provides an additional feature; you can specify the total
115
+ # number of items in the dataset.
116
+ #
117
+ # For example, if you were listing a page of 50 resource instances but
118
+ # the total matching dataset of that list included 344 instances, you
119
+ # would pass 344 in the +dataset_size+ input parameter. This is optional
120
+ # but highly recommended as it is often very useful for calling clients.
121
+ #
122
+ # +array+:: Array of resource representations (Ruby Array with
123
+ # Ruby Hash entries representing rendered resources,
124
+ # ideally through the Hoodoo::Presenters framework).
125
+ #
126
+ # +dataset_size+:: Optional _total_ number of items in the entire dataset
127
+ # of which +array+ is, most likely, just a subset due to
128
+ # paginated lists via offset and limit parameters.
129
+ #
130
+ def set_resources( array, dataset_size = nil )
131
+ self.body = array
132
+ @dataset_size = dataset_size
133
+ end
134
+
135
+ # Add an HTTP header to the internal collection that will be used for the
136
+ # response. Trying to set data for the same HTTP header name more than once
137
+ # will result in an exception being raised unless the +overwrite+ parameter
138
+ # is used (this is strongly discouraged in the general case).
139
+ #
140
+ # +name+:: Correct case and punctuation HTTP header name (e.g.
141
+ # "Content-Type").
142
+ #
143
+ # +value+:: Value for the header, as a string or something that behaves
144
+ # sensibly when +to_s+ is invoked upon it.
145
+ #
146
+ # +overwrite+:: Optional. Pass +true+ to allow the same HTTP header name to
147
+ # be set more than once - the new value overwrites the old.
148
+ # By default this is prohibited and an exception will be
149
+ # raised to avoid accidental value overwrites.
150
+ #
151
+ def add_header( name, value, overwrite = false )
152
+ name = name.to_s
153
+ dname = name.downcase
154
+ value = value.to_s
155
+
156
+ if ( overwrite == false && @headers.has_key?( dname ) )
157
+ hash = @headers[ dname ]
158
+ name = hash.keys[ 0 ]
159
+ value = hash.values[ 0 ]
160
+ raise "Hoodoo::Services::Response\#add_header: Value '#{ value }' already defined for header '#{ name }'"
161
+ else
162
+ @headers[ dname ] = { name => value }
163
+ end
164
+ end
165
+
166
+ # Check the stored value of a given HTTP header. Checks are case
167
+ # insensitive. Returns the value stored by a prior #add_header call, or
168
+ # +nil+ for no value (or an explicitly stored value of +nil+)
169
+ #
170
+ # +name+:: HTTP header name (e.g. "Content-Type", "CONTENT-TYPE").
171
+ #
172
+ def get_header( name )
173
+ value_hash = @headers[ name.downcase ]
174
+ return nil if value_hash.nil?
175
+ return value_hash.values[ 0 ]
176
+ end
177
+
178
+ # Returns the list previously set headers in a name: value Hash.
179
+ #
180
+ def headers
181
+ @headers.inject( {} ) do | result, kv_array |
182
+ result.merge( kv_array[ 1 ] )
183
+ end
184
+ end
185
+
186
+ # Add an error to the internal collection. Passes input parameters through
187
+ # to Hoodoo::Errors#add_error, so see that for details. For convenience,
188
+ # returns the for-rack representation of the response so far, so that code
189
+ # which wishes to add one error and abort request processing immediately
190
+ # can just do:
191
+ #
192
+ # return response_object.add_error( ... )
193
+ #
194
+ # ...as part of processing a Rack invocation of the +call+ method. This is
195
+ # really only useful for the service middleware.
196
+ #
197
+ # +code+:: Error code (e.g. "platform.generic").
198
+ # +options+:: Options Hash - see Hoodoo::Errors#add_error.
199
+ #
200
+ # Example:
201
+ #
202
+ # response.add_error(
203
+ # 'generic.not_found',
204
+ # 'message' => 'Optional custom message',
205
+ # 'reference' => { :ident => 'mandatory reference data' }
206
+ # )
207
+ #
208
+ # In the above example, the mandatory reference data +uuid+ comes
209
+ # from the description for the 'platform.not_found' message - see the
210
+ # Hoodoo::ErrorDescriptions#initialize _implementation_ and Platform API.
211
+ #
212
+ def add_error( code, options = nil )
213
+ @errors.add_error( code, options )
214
+ return for_rack()
215
+ end
216
+
217
+ # Add a precompiled error to the error collection. Pass error code,
218
+ # error message and reference data directly.
219
+ #
220
+ # In most cases you should be calling #add_error instead, *NOT* here.
221
+ #
222
+ # *No* *validation* is performed. You should only really call here if
223
+ # storing an error / errors from another, trusted source with assumed
224
+ # validity (e.g. another service called remotely with errors in the JSON
225
+ # response). It's possible to store invalid error data using this call,
226
+ # which means counter-to-documentation results could be returned to API
227
+ # clients. That is Very Bad.
228
+ #
229
+ # Pass optionally the HTTP status code to use if this happens to be the
230
+ # first stored error. If this is omitted, 500 is kept as the default.
231
+ #
232
+ # As with #add_error, returns a Rack representation of the response.
233
+ #
234
+ def add_precompiled_error( code, message, reference, http_status = 500 )
235
+ @errors.add_precompiled_error( code, message, reference, http_status )
236
+ return for_rack()
237
+ end
238
+
239
+ # Add errors from a Hoodoo::Errors instance to this response's error
240
+ # collection.
241
+ #
242
+ # +errors_object+:: Hoodoo::Errors instance to merge into the error
243
+ # collection of 'this' response object.
244
+ #
245
+ # Returns +true+ if errors were merged, else +false+ (the source
246
+ # collection was empty).
247
+ #
248
+ def add_errors( errors_object )
249
+ return @errors.merge!( errors_object )
250
+ end
251
+
252
+ # Set the standard not found error message (generic.not_found), to
253
+ # be used durning a 'show' call when the requested resource does not
254
+ # exist.
255
+ #
256
+ # +ident+:: The identifier of the resource which was not found
257
+ #
258
+ # Example:
259
+ #
260
+ # return response.not_found( ident ) if resource.nil?
261
+ #
262
+ def not_found( ident )
263
+ @errors.add_error( 'generic.not_found', :reference => { :ident => ident } )
264
+ end
265
+
266
+ # Convert the internal response data into something that Rack expects.
267
+ # The return value of this method can be passed back to Rack from Rack
268
+ # middleware or applications. Usually, this is only called directly by
269
+ # Hoodoo::Services::Middleware.
270
+ #
271
+ def for_rack
272
+
273
+ rack_response = Rack::Response.new
274
+
275
+ # Work out the status code and basic response body
276
+
277
+ if @errors.has_errors?
278
+ http_status_code = @errors.http_status_code
279
+ body_data = @errors.render( @interaction_id )
280
+ else
281
+ http_status_code = @http_status_code
282
+ body_data = @body
283
+ end
284
+
285
+ rack_response.status = http_status_code.to_i
286
+
287
+ # We're not using JSON5, so the Platform API says that outmost arrays
288
+ # are wrapped with a top-level object key "_data".
289
+
290
+ if body_data.is_a?( ::Array )
291
+ response_hash = { '_data' => body_data }
292
+ response_hash[ '_dataset_size' ] = @dataset_size unless @dataset_size.nil?
293
+ response_string = ::JSON.generate( response_hash )
294
+ elsif body_data.is_a?( ::Hash )
295
+ response_string = ::JSON.generate( body_data )
296
+ elsif body_data.is_a?( ::String )
297
+ response_string = body_data
298
+ else
299
+ raise "Hoodoo::Services::Response\#for_rack given unrecognised body data class '#{ body_data.class.name }'"
300
+ end
301
+
302
+ rack_response.write( response_string )
303
+
304
+ # Finally, sort out the headers
305
+
306
+ headers().each do | header_name, header_value |
307
+ rack_response[ header_name ] = header_value
308
+ end
309
+
310
+ # Return the complete response
311
+
312
+ return rack_response.finish
313
+ end
314
+ end
315
+
316
+ end; end