hash_cast 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,13 @@
1
+ class HCast::Casters::StringCaster
2
+
3
+ def self.cast(value, attr_name, options = {})
4
+ if value.is_a?(String)
5
+ value
6
+ elsif value.is_a?(Symbol)
7
+ value.to_s
8
+ else
9
+ raise HCast::Errors::CastingError, "should be a string"
10
+ end
11
+ end
12
+
13
+ end
@@ -0,0 +1,18 @@
1
+ class HCast::Casters::SymbolCaster
2
+ MAX_SYMBOL_LENGTH = 1000
3
+
4
+ def self.cast(value, attr_name, options = {})
5
+ if value.is_a?(Symbol)
6
+ value
7
+ elsif value.is_a?(String)
8
+ if value.length > MAX_SYMBOL_LENGTH
9
+ raise HCast::Errors::CastingError, "is too long to be a symbol"
10
+ else
11
+ value.to_sym
12
+ end
13
+ else
14
+ raise HCast::Errors::CastingError, "should be a symbol"
15
+ end
16
+ end
17
+
18
+ end
@@ -0,0 +1,19 @@
1
+ require 'time'
2
+
3
+ class HCast::Casters::TimeCaster
4
+
5
+ def self.cast(value, attr_name, options = {})
6
+ if value.is_a?(Time)
7
+ value
8
+ elsif value.is_a?(String)
9
+ begin
10
+ Time.parse(value)
11
+ rescue ArgumentError => e
12
+ raise HCast::Errors::CastingError, "is invalid time"
13
+ end
14
+ else
15
+ raise HCast::Errors::CastingError, "should be a time"
16
+ end
17
+ end
18
+
19
+ end
@@ -0,0 +1,13 @@
1
+ # List of build in casters
2
+ module HCast::Casters
3
+ require 'hcast/casters/array_caster'
4
+ require 'hcast/casters/boolean_caster'
5
+ require 'hcast/casters/date_caster'
6
+ require 'hcast/casters/datetime_caster'
7
+ require 'hcast/casters/float_caster'
8
+ require 'hcast/casters/hash_caster'
9
+ require 'hcast/casters/integer_caster'
10
+ require 'hcast/casters/string_caster'
11
+ require 'hcast/casters/symbol_caster'
12
+ require 'hcast/casters/time_caster'
13
+ end
@@ -0,0 +1,136 @@
1
+ module HCast
2
+ # Copied from here https://github.com/rails/rails/blob/master/activesupport/lib/active_support/concern.rb
3
+ #
4
+ # A typical module looks like this:
5
+ #
6
+ # module M
7
+ # def self.included(base)
8
+ # base.extend ClassMethods
9
+ # base.class_eval do
10
+ # scope :disabled, -> { where(disabled: true) }
11
+ # end
12
+ # end
13
+ #
14
+ # module ClassMethods
15
+ # ...
16
+ # end
17
+ # end
18
+ #
19
+ # By using <tt>ActiveSupport::Concern</tt> the above module could instead be
20
+ # written as:
21
+ #
22
+ # require 'active_support/concern'
23
+ #
24
+ # module M
25
+ # extend ActiveSupport::Concern
26
+ #
27
+ # included do
28
+ # scope :disabled, -> { where(disabled: true) }
29
+ # end
30
+ #
31
+ # module ClassMethods
32
+ # ...
33
+ # end
34
+ # end
35
+ #
36
+ # Moreover, it gracefully handles module dependencies. Given a +Foo+ module
37
+ # and a +Bar+ module which depends on the former, we would typically write the
38
+ # following:
39
+ #
40
+ # module Foo
41
+ # def self.included(base)
42
+ # base.class_eval do
43
+ # def self.method_injected_by_foo
44
+ # ...
45
+ # end
46
+ # end
47
+ # end
48
+ # end
49
+ #
50
+ # module Bar
51
+ # def self.included(base)
52
+ # base.method_injected_by_foo
53
+ # end
54
+ # end
55
+ #
56
+ # class Host
57
+ # include Foo # We need to include this dependency for Bar
58
+ # include Bar # Bar is the module that Host really needs
59
+ # end
60
+ #
61
+ # But why should +Host+ care about +Bar+'s dependencies, namely +Foo+? We
62
+ # could try to hide these from +Host+ directly including +Foo+ in +Bar+:
63
+ #
64
+ # module Bar
65
+ # include Foo
66
+ # def self.included(base)
67
+ # base.method_injected_by_foo
68
+ # end
69
+ # end
70
+ #
71
+ # class Host
72
+ # include Bar
73
+ # end
74
+ #
75
+ # Unfortunately this won't work, since when +Foo+ is included, its <tt>base</tt>
76
+ # is the +Bar+ module, not the +Host+ class. With <tt>ActiveSupport::Concern</tt>,
77
+ # module dependencies are properly resolved:
78
+ #
79
+ # require 'active_support/concern'
80
+ #
81
+ # module Foo
82
+ # extend ActiveSupport::Concern
83
+ # included do
84
+ # def self.method_injected_by_foo
85
+ # ...
86
+ # end
87
+ # end
88
+ # end
89
+ #
90
+ # module Bar
91
+ # extend ActiveSupport::Concern
92
+ # include Foo
93
+ #
94
+ # included do
95
+ # self.method_injected_by_foo
96
+ # end
97
+ # end
98
+ #
99
+ # class Host
100
+ # include Bar # works, Bar takes care now of its dependencies
101
+ # end
102
+ module Concern
103
+ class MultipleIncludedBlocks < StandardError #:nodoc:
104
+ def initialize
105
+ super "Cannot define multiple 'included' blocks for a Concern"
106
+ end
107
+ end
108
+
109
+ def self.extended(base) #:nodoc:
110
+ base.instance_variable_set(:@_dependencies, [])
111
+ end
112
+
113
+ def append_features(base)
114
+ if base.instance_variable_defined?(:@_dependencies)
115
+ base.instance_variable_get(:@_dependencies) << self
116
+ return false
117
+ else
118
+ return false if base < self
119
+ @_dependencies.each { |dep| base.send(:include, dep) }
120
+ super
121
+ base.extend const_get(:ClassMethods) if const_defined?(:ClassMethods)
122
+ base.class_eval(&@_included_block) if instance_variable_defined?(:@_included_block)
123
+ end
124
+ end
125
+
126
+ def included(base = nil, &block)
127
+ if base.nil?
128
+ raise MultipleIncludedBlocks if instance_variable_defined?(:@_included_block)
129
+
130
+ @_included_block = block
131
+ else
132
+ super
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,11 @@
1
+ class HCast::Config
2
+ attr_accessor :input_keys, :output_keys
3
+
4
+ def input_keys
5
+ @input_keys || :symbol
6
+ end
7
+
8
+ def output_keys
9
+ @output_keys || :symbol
10
+ end
11
+ end
@@ -0,0 +1,67 @@
1
+ module HCast::Errors
2
+
3
+ # Base error class for all HCast errors
4
+ class HCastError < StandardError; end
5
+
6
+ # Raised when caster with given name is not registered in HCast
7
+ class CasterNotFoundError < HCastError; end
8
+
9
+ # Raised when some of the given to HCast argument is not valid
10
+ class ArgumentError < HCastError; end
11
+
12
+ class AttributeError < HCastError
13
+ attr_reader :namespaces
14
+
15
+ def initialize(message, namespace = nil)
16
+ super(message)
17
+ @namespaces = []
18
+ @namespaces << namespace if namespace
19
+ end
20
+
21
+ def add_namespace(namespace)
22
+ namespaces << namespace
23
+ end
24
+
25
+ def message
26
+ to_s
27
+ end
28
+
29
+ def to_s
30
+ if namespaces.empty?
31
+ super
32
+ else
33
+ reverted_namespaces = namespaces.reverse
34
+ msg = reverted_namespaces.first.to_s
35
+ msg += reverted_namespaces[1..-1].inject("") { |res, item| res += "[#{item}]"}
36
+ msg + " " + super
37
+ end
38
+ end
39
+
40
+ end
41
+ # Raised when hash attribute can't be casted
42
+ class CastingError < AttributeError; end
43
+
44
+ # Raised when required hash attribute wasn't given for casting
45
+ class MissingAttributeError < AttributeError; end
46
+
47
+ # Raised when unexpected hash attribute was given for casting
48
+ class UnexpectedAttributeError < AttributeError; end
49
+
50
+ # Raised when hash has validation errors
51
+ class ValidationError < StandardError
52
+ attr_reader :errors
53
+
54
+ def initialize(message, errors)
55
+ @errors = errors
56
+ super(message)
57
+ end
58
+
59
+ def message
60
+ "#{@message}\n#{errors.to_hash}"
61
+ end
62
+
63
+ def short_message
64
+ 'Validation error'
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,30 @@
1
+ module HCast::Metadata
2
+ class Attribute
3
+ attr_reader :name, :caster, :options
4
+ attr_accessor :children
5
+
6
+ def initialize(name, caster, options)
7
+ @name = name
8
+ @caster = caster
9
+ @options = options
10
+ @children = []
11
+ end
12
+
13
+ def has_children?
14
+ !children.empty?
15
+ end
16
+
17
+ def required?
18
+ !optional?
19
+ end
20
+
21
+ def optional?
22
+ !!options[:optional]
23
+ end
24
+
25
+ def allow_nil?
26
+ !!options[:allow_nil]
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,3 @@
1
+ module HCast
2
+ VERSION = "0.4.0"
3
+ end
data/lib/hcast.rb ADDED
@@ -0,0 +1,46 @@
1
+ require 'hcast/version'
2
+ require 'hcast/errors'
3
+ require 'hcast/config'
4
+ require 'hcast/casters'
5
+ require 'hcast/concern.rb'
6
+ require 'hcast/metadata/attribute'
7
+ require 'hcast/attributes_parser'
8
+ require 'hcast/attributes_caster'
9
+ require 'hcast/caster'
10
+
11
+ module HCast
12
+ @@casters = {}
13
+
14
+ # Defines caster without adding own class
15
+ # @note Not yet implemented
16
+ def self.create(&block)
17
+ end
18
+
19
+ # Returns list of defined casters
20
+ def self.casters
21
+ @@casters
22
+ end
23
+
24
+ # Adds new casters to HCast
25
+ # Allow extend HCast with your own casters
26
+ # @param caster_name [Symbol] caster name
27
+ # @param caster [Class] caster
28
+ def self.add_caster(caster_name, caster)
29
+ @@casters[caster_name] = caster
30
+ end
31
+
32
+ def self.config
33
+ @@config ||= HCast::Config.new
34
+ end
35
+ end
36
+
37
+ HCast.add_caster(:array, HCast::Casters::ArrayCaster)
38
+ HCast.add_caster(:boolean, HCast::Casters::BooleanCaster)
39
+ HCast.add_caster(:date, HCast::Casters::DateCaster)
40
+ HCast.add_caster(:datetime, HCast::Casters::DateTimeCaster)
41
+ HCast.add_caster(:float, HCast::Casters::FloatCaster)
42
+ HCast.add_caster(:hash, HCast::Casters::HashCaster)
43
+ HCast.add_caster(:integer, HCast::Casters::IntegerCaster)
44
+ HCast.add_caster(:string, HCast::Casters::StringCaster)
45
+ HCast.add_caster(:symbol, HCast::Casters::SymbolCaster)
46
+ HCast.add_caster(:time, HCast::Casters::TimeCaster)