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.
- checksums.yaml +7 -0
- data/bin/hoodoo +5 -0
- data/lib/hoodoo.rb +27 -0
- data/lib/hoodoo/active.rb +32 -0
- data/lib/hoodoo/active/active_model/uuid_validator.rb +45 -0
- data/lib/hoodoo/active/active_record/base.rb +81 -0
- data/lib/hoodoo/active/active_record/creator.rb +134 -0
- data/lib/hoodoo/active/active_record/dated.rb +343 -0
- data/lib/hoodoo/active/active_record/error_mapping.rb +351 -0
- data/lib/hoodoo/active/active_record/finder.rb +606 -0
- data/lib/hoodoo/active/active_record/search_helper.rb +189 -0
- data/lib/hoodoo/active/active_record/secure.rb +431 -0
- data/lib/hoodoo/active/active_record/support.rb +106 -0
- data/lib/hoodoo/active/active_record/translated.rb +87 -0
- data/lib/hoodoo/active/active_record/uuid.rb +80 -0
- data/lib/hoodoo/active/active_record/writer.rb +321 -0
- data/lib/hoodoo/client.rb +23 -0
- data/lib/hoodoo/client/augmented_array.rb +29 -0
- data/lib/hoodoo/client/augmented_base.rb +168 -0
- data/lib/hoodoo/client/augmented_hash.rb +23 -0
- data/lib/hoodoo/client/client.rb +354 -0
- data/lib/hoodoo/client/endpoint/endpoint.rb +427 -0
- data/lib/hoodoo/client/endpoint/endpoints/amqp.rb +180 -0
- data/lib/hoodoo/client/endpoint/endpoints/auto_session.rb +194 -0
- data/lib/hoodoo/client/endpoint/endpoints/http.rb +203 -0
- data/lib/hoodoo/client/endpoint/endpoints/http_based.rb +367 -0
- data/lib/hoodoo/client/endpoint/endpoints/not_found.rb +59 -0
- data/lib/hoodoo/client/headers.rb +269 -0
- data/lib/hoodoo/communicators.rb +23 -0
- data/lib/hoodoo/communicators/fast.rb +44 -0
- data/lib/hoodoo/communicators/pool.rb +601 -0
- data/lib/hoodoo/communicators/slow.rb +84 -0
- data/lib/hoodoo/data.rb +51 -0
- data/lib/hoodoo/data/resources/caller.rb +39 -0
- data/lib/hoodoo/data/resources/errors.rb +28 -0
- data/lib/hoodoo/data/resources/log.rb +31 -0
- data/lib/hoodoo/data/resources/session.rb +26 -0
- data/lib/hoodoo/data/types/error_primitive.rb +27 -0
- data/lib/hoodoo/data/types/permissions.rb +40 -0
- data/lib/hoodoo/data/types/permissions_defaults.rb +32 -0
- data/lib/hoodoo/data/types/permissions_full.rb +28 -0
- data/lib/hoodoo/data/types/permissions_resources.rb +31 -0
- data/lib/hoodoo/discovery.rb +20 -0
- data/lib/hoodoo/errors.rb +19 -0
- data/lib/hoodoo/errors/error_descriptions.rb +229 -0
- data/lib/hoodoo/errors/errors.rb +322 -0
- data/lib/hoodoo/generator.rb +139 -0
- data/lib/hoodoo/logger.rb +23 -0
- data/lib/hoodoo/logger/fast_writer.rb +27 -0
- data/lib/hoodoo/logger/flattener_mixin.rb +36 -0
- data/lib/hoodoo/logger/logger.rb +387 -0
- data/lib/hoodoo/logger/slow_writer.rb +49 -0
- data/lib/hoodoo/logger/writer_mixin.rb +52 -0
- data/lib/hoodoo/logger/writers/file_writer.rb +45 -0
- data/lib/hoodoo/logger/writers/log_entries_dot_com_writer.rb +64 -0
- data/lib/hoodoo/logger/writers/stream_writer.rb +43 -0
- data/lib/hoodoo/middleware.rb +33 -0
- data/lib/hoodoo/presenters.rb +45 -0
- data/lib/hoodoo/presenters/base.rb +281 -0
- data/lib/hoodoo/presenters/base_dsl.rb +519 -0
- data/lib/hoodoo/presenters/common_resource_fields.rb +31 -0
- data/lib/hoodoo/presenters/embedding.rb +232 -0
- data/lib/hoodoo/presenters/types/array.rb +118 -0
- data/lib/hoodoo/presenters/types/boolean.rb +26 -0
- data/lib/hoodoo/presenters/types/date.rb +26 -0
- data/lib/hoodoo/presenters/types/date_time.rb +26 -0
- data/lib/hoodoo/presenters/types/decimal.rb +47 -0
- data/lib/hoodoo/presenters/types/enum.rb +55 -0
- data/lib/hoodoo/presenters/types/field.rb +158 -0
- data/lib/hoodoo/presenters/types/float.rb +26 -0
- data/lib/hoodoo/presenters/types/hash.rb +361 -0
- data/lib/hoodoo/presenters/types/integer.rb +26 -0
- data/lib/hoodoo/presenters/types/object.rb +117 -0
- data/lib/hoodoo/presenters/types/string.rb +53 -0
- data/lib/hoodoo/presenters/types/tags.rb +24 -0
- data/lib/hoodoo/presenters/types/text.rb +26 -0
- data/lib/hoodoo/presenters/types/uuid.rb +54 -0
- data/lib/hoodoo/services.rb +34 -0
- data/lib/hoodoo/services/discovery/discoverers/by_consul.rb +66 -0
- data/lib/hoodoo/services/discovery/discoverers/by_convention.rb +173 -0
- data/lib/hoodoo/services/discovery/discoverers/by_drb/by_drb.rb +195 -0
- data/lib/hoodoo/services/discovery/discoverers/by_drb/drb_server.rb +166 -0
- data/lib/hoodoo/services/discovery/discoverers/by_drb/drb_server_start.rb +37 -0
- data/lib/hoodoo/services/discovery/discovery.rb +186 -0
- data/lib/hoodoo/services/discovery/results/for_amqp.rb +58 -0
- data/lib/hoodoo/services/discovery/results/for_http.rb +85 -0
- data/lib/hoodoo/services/discovery/results/for_local.rb +85 -0
- data/lib/hoodoo/services/discovery/results/for_remote.rb +57 -0
- data/lib/hoodoo/services/middleware/amqp_log_message.rb +186 -0
- data/lib/hoodoo/services/middleware/amqp_log_writer.rb +119 -0
- data/lib/hoodoo/services/middleware/endpoints/inter_resource_local.rb +130 -0
- data/lib/hoodoo/services/middleware/endpoints/inter_resource_remote.rb +202 -0
- data/lib/hoodoo/services/middleware/exception_reporting/base_reporter.rb +105 -0
- data/lib/hoodoo/services/middleware/exception_reporting/exception_reporting.rb +115 -0
- data/lib/hoodoo/services/middleware/exception_reporting/reporters/airbrake_reporter.rb +64 -0
- data/lib/hoodoo/services/middleware/exception_reporting/reporters/raygun_reporter.rb +63 -0
- data/lib/hoodoo/services/middleware/interaction.rb +127 -0
- data/lib/hoodoo/services/middleware/middleware.rb +2705 -0
- data/lib/hoodoo/services/middleware/rack_monkey_patch.rb +73 -0
- data/lib/hoodoo/services/services/context.rb +153 -0
- data/lib/hoodoo/services/services/implementation.rb +132 -0
- data/lib/hoodoo/services/services/interface.rb +934 -0
- data/lib/hoodoo/services/services/permissions.rb +250 -0
- data/lib/hoodoo/services/services/request.rb +189 -0
- data/lib/hoodoo/services/services/response.rb +316 -0
- data/lib/hoodoo/services/services/service.rb +141 -0
- data/lib/hoodoo/services/services/session.rb +729 -0
- data/lib/hoodoo/utilities.rb +12 -0
- data/lib/hoodoo/utilities/string_inquirer.rb +54 -0
- data/lib/hoodoo/utilities/utilities.rb +380 -0
- data/lib/hoodoo/utilities/uuid.rb +44 -0
- data/lib/hoodoo/version.rb +17 -0
- data/spec/active/active_record/base_spec.rb +57 -0
- data/spec/active/active_record/creator_spec.rb +88 -0
- data/spec/active/active_record/dated_spec.rb +248 -0
- data/spec/active/active_record/error_mapping_spec.rb +360 -0
- data/spec/active/active_record/finder_spec.rb +744 -0
- data/spec/active/active_record/search_helper_spec.rb +384 -0
- data/spec/active/active_record/secure_spec.rb +435 -0
- data/spec/active/active_record/support_spec.rb +225 -0
- data/spec/active/active_record/translated_spec.rb +19 -0
- data/spec/active/active_record/uuid_spec.rb +72 -0
- data/spec/active/active_record/writer_spec.rb +272 -0
- data/spec/alchemy/alchemy-amq.rb +33 -0
- data/spec/client/augmented_array_spec.rb +15 -0
- data/spec/client/augmented_base_spec.rb +50 -0
- data/spec/client/augmented_hash_spec.rb +15 -0
- data/spec/client/client_spec.rb +955 -0
- data/spec/client/endpoint/endpoint_spec.rb +70 -0
- data/spec/client/endpoint/endpoints/amqp_spec.rb +16 -0
- data/spec/client/endpoint/endpoints/auto_session_spec.rb +9 -0
- data/spec/client/endpoint/endpoints/http_based_spec.rb +9 -0
- data/spec/client/endpoint/endpoints/http_spec.rb +103 -0
- data/spec/client/endpoint/endpoints/not_found_spec.rb +35 -0
- data/spec/client/headers_spec.rb +172 -0
- data/spec/communicators/fast_spec.rb +9 -0
- data/spec/communicators/pool_spec.rb +339 -0
- data/spec/communicators/slow_spec.rb +15 -0
- data/spec/data/resources/caller_spec.rb +156 -0
- data/spec/data/resources/errors_spec.rb +22 -0
- data/spec/data/resources/log_spec.rb +20 -0
- data/spec/data/resources/session_spec.rb +15 -0
- data/spec/data/types/error_primitive_spec.rb +15 -0
- data/spec/data/types/permissions_defaults_spec.rb +25 -0
- data/spec/data/types/permissions_full_spec.rb +44 -0
- data/spec/data/types/permissions_resources_spec.rb +34 -0
- data/spec/data/types/permissions_spec.rb +37 -0
- data/spec/errors/error_descriptions_spec.rb +98 -0
- data/spec/errors/errors_spec.rb +346 -0
- data/spec/integration/service_actions_spec.rb +112 -0
- data/spec/logger/fast_writer_spec.rb +18 -0
- data/spec/logger/logger_spec.rb +259 -0
- data/spec/logger/slow_writer_spec.rb +144 -0
- data/spec/logger/writers/file_writer_spec.rb +37 -0
- data/spec/logger/writers/log_entries_dot_com_writer_spec.rb +29 -0
- data/spec/logger/writers/stream_writer_spec.rb +38 -0
- data/spec/presenters/base_dsl_spec.rb +111 -0
- data/spec/presenters/base_spec.rb +871 -0
- data/spec/presenters/common_resource_fields_spec.rb +30 -0
- data/spec/presenters/embedding_spec.rb +87 -0
- data/spec/presenters/types/array_spec.rb +249 -0
- data/spec/presenters/types/boolean_spec.rb +51 -0
- data/spec/presenters/types/date_spec.rb +57 -0
- data/spec/presenters/types/date_time_spec.rb +59 -0
- data/spec/presenters/types/decimal_spec.rb +58 -0
- data/spec/presenters/types/enum_spec.rb +71 -0
- data/spec/presenters/types/field_spec.rb +77 -0
- data/spec/presenters/types/float_spec.rb +50 -0
- data/spec/presenters/types/hash_spec.rb +1069 -0
- data/spec/presenters/types/integer_spec.rb +50 -0
- data/spec/presenters/types/object_spec.rb +177 -0
- data/spec/presenters/types/string_spec.rb +65 -0
- data/spec/presenters/types/tags_spec.rb +56 -0
- data/spec/presenters/types/text_spec.rb +50 -0
- data/spec/presenters/types/uuid_spec.rb +46 -0
- data/spec/presenters/walk_spec.rb +198 -0
- data/spec/services/discovery/discoverers/by_consul_spec.rb +29 -0
- data/spec/services/discovery/discoverers/by_convention_spec.rb +67 -0
- data/spec/services/discovery/discoverers/by_drb/by_drb_spec.rb +80 -0
- data/spec/services/discovery/discoverers/by_drb/drb_server_spec.rb +205 -0
- data/spec/services/discovery/discovery_spec.rb +73 -0
- data/spec/services/discovery/results/for_amqp_spec.rb +17 -0
- data/spec/services/discovery/results/for_http_spec.rb +37 -0
- data/spec/services/discovery/results/for_local_spec.rb +21 -0
- data/spec/services/discovery/results/for_remote_spec.rb +15 -0
- data/spec/services/middleware/amqp_log_message_spec.rb +60 -0
- data/spec/services/middleware/amqp_log_writer_spec.rb +95 -0
- data/spec/services/middleware/endpoints/inter_resource_local_spec.rb +9 -0
- data/spec/services/middleware/endpoints/inter_resource_remote_spec.rb +9 -0
- data/spec/services/middleware/exception_reporting/base_reporter_spec.rb +16 -0
- data/spec/services/middleware/exception_reporting/exception_reporting_spec.rb +92 -0
- data/spec/services/middleware/exception_reporting/reporters/airbrake_reporter_spec.rb +24 -0
- data/spec/services/middleware/exception_reporting/reporters/raygun_reporter_spec.rb +23 -0
- data/spec/services/middleware/middleware_cors_spec.rb +93 -0
- data/spec/services/middleware/middleware_create_update_spec.rb +489 -0
- data/spec/services/middleware/middleware_dated_at_spec.rb +186 -0
- data/spec/services/middleware/middleware_exotic_communication_spec.rb +560 -0
- data/spec/services/middleware/middleware_logging_spec.rb +356 -0
- data/spec/services/middleware/middleware_multi_local_spec.rb +1094 -0
- data/spec/services/middleware/middleware_multi_remote_spec.rb +1440 -0
- data/spec/services/middleware/middleware_permissions_spec.rb +1014 -0
- data/spec/services/middleware/middleware_public_spec.rb +238 -0
- data/spec/services/middleware/middleware_spec.rb +1569 -0
- data/spec/services/middleware/string_inquirer_spec.rb +30 -0
- data/spec/services/services/application_spec.rb +74 -0
- data/spec/services/services/context_spec.rb +48 -0
- data/spec/services/services/implementation_spec.rb +45 -0
- data/spec/services/services/interface_spec.rb +262 -0
- data/spec/services/services/permissions_spec.rb +249 -0
- data/spec/services/services/request_spec.rb +95 -0
- data/spec/services/services/response_spec.rb +250 -0
- data/spec/services/services/session_spec.rb +432 -0
- data/spec/spec_helper.rb +298 -0
- data/spec/utilities/utilities_spec.rb +537 -0
- data/spec/utilities/uuid_spec.rb +20 -0
- 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
|