castkit 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rspec_status +118 -119
- data/CHANGELOG.md +1 -1
- data/README.md +287 -11
- data/castkit.gemspec +1 -0
- data/lib/castkit/castkit.rb +5 -2
- data/lib/castkit/cli/generate.rb +98 -0
- data/lib/castkit/cli/list.rb +200 -0
- data/lib/castkit/cli/main.rb +43 -0
- data/lib/castkit/cli.rb +24 -0
- data/lib/castkit/configuration.rb +31 -8
- data/lib/castkit/contract/{generic.rb → base.rb} +5 -5
- data/lib/castkit/contract/result.rb +2 -2
- data/lib/castkit/contract.rb +5 -5
- data/lib/castkit/data_object.rb +11 -7
- data/lib/castkit/ext/data_object/contract.rb +1 -1
- data/lib/castkit/ext/data_object/plugins.rb +86 -0
- data/lib/castkit/inflector.rb +1 -1
- data/lib/castkit/plugins.rb +82 -0
- data/lib/castkit/serializers/base.rb +94 -0
- data/lib/castkit/serializers/default_serializer.rb +156 -0
- data/lib/castkit/types/{generic.rb → base.rb} +6 -7
- data/lib/castkit/types/boolean.rb +14 -10
- data/lib/castkit/types/collection.rb +13 -2
- data/lib/castkit/types/date.rb +2 -2
- data/lib/castkit/types/date_time.rb +2 -2
- data/lib/castkit/types/float.rb +5 -5
- data/lib/castkit/types/integer.rb +5 -5
- data/lib/castkit/types/string.rb +2 -2
- data/lib/castkit/types.rb +1 -1
- data/lib/castkit/validators/base.rb +59 -0
- data/lib/castkit/validators/boolean_validator.rb +39 -0
- data/lib/castkit/validators/collection_validator.rb +29 -0
- data/lib/castkit/validators/float_validator.rb +31 -0
- data/lib/castkit/validators/integer_validator.rb +31 -0
- data/lib/castkit/validators/numeric_validator.rb +2 -2
- data/lib/castkit/validators/string_validator.rb +3 -4
- data/lib/castkit/version.rb +1 -1
- data/lib/generators/base.rb +97 -0
- data/lib/generators/contract.rb +68 -0
- data/lib/generators/data_object.rb +48 -0
- data/lib/generators/plugin.rb +25 -0
- data/lib/generators/serializer.rb +28 -0
- data/lib/generators/templates/contract.rb.tt +24 -0
- data/lib/generators/templates/contract_spec.rb.tt +76 -0
- data/lib/generators/templates/data_object.rb.tt +15 -0
- data/lib/generators/templates/data_object_spec.rb.tt +36 -0
- data/lib/generators/templates/plugin.rb.tt +37 -0
- data/lib/generators/templates/plugin_spec.rb.tt +18 -0
- data/lib/generators/templates/serializer.rb.tt +24 -0
- data/lib/generators/templates/serializer_spec.rb.tt +14 -0
- data/lib/generators/templates/type.rb.tt +55 -0
- data/lib/generators/templates/type_spec.rb.tt +42 -0
- data/lib/generators/templates/validator.rb.tt +26 -0
- data/lib/generators/templates/validator_spec.rb.tt +23 -0
- data/lib/generators/type.rb +29 -0
- data/lib/generators/validator.rb +41 -0
- metadata +50 -7
- data/lib/castkit/default_serializer.rb +0 -154
- data/lib/castkit/serializer.rb +0 -92
- data/lib/castkit/validators/base_validator.rb +0 -39
@@ -0,0 +1,156 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
|
5
|
+
module Castkit
|
6
|
+
module Serializers
|
7
|
+
# Default serializer for Castkit::DataObject instances.
|
8
|
+
#
|
9
|
+
# Serializes attributes into a plain Ruby hash, applying access rules, nil/blank filtering,
|
10
|
+
# and nested structure handling. The output format supports JSON-compatible structures
|
11
|
+
# and respects the class-level serialization configuration.
|
12
|
+
class DefaultSerializer < Castkit::Serializers::Base
|
13
|
+
# @return [Hash{Symbol => Castkit::Attribute}] the attributes to serialize
|
14
|
+
attr_reader :attributes
|
15
|
+
|
16
|
+
# @return [Hash{Symbol => Object}] unrecognized attributes captured during deserialization
|
17
|
+
attr_reader :unknown_attributes
|
18
|
+
|
19
|
+
# @return [Hash] serialization config flags like :root, :ignore_nil, :allow_unknown
|
20
|
+
attr_reader :options
|
21
|
+
|
22
|
+
# Serializes the object to a hash.
|
23
|
+
#
|
24
|
+
# Includes unknown attributes if configured, and wraps in a root key if defined.
|
25
|
+
#
|
26
|
+
# @return [Hash] the fully serialized result
|
27
|
+
def call
|
28
|
+
result = serialize_attributes
|
29
|
+
result.merge!(unknown_attributes) if options[:allow_unknown]
|
30
|
+
|
31
|
+
options[:root] ? { options[:root].to_sym => result } : result
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
# Initializes the serializer.
|
37
|
+
#
|
38
|
+
# @param object [Castkit::DataObject] the object to serialize
|
39
|
+
# @param visited [Set, nil] tracks circular references
|
40
|
+
def initialize(object, visited: nil)
|
41
|
+
super
|
42
|
+
|
43
|
+
@skip_flag = "__castkit_#{object.object_id}"
|
44
|
+
@attributes = object.class.attributes.freeze
|
45
|
+
@unknown_attributes = object.unknown_attributes.freeze
|
46
|
+
@options = {
|
47
|
+
root: object.class.root,
|
48
|
+
ignore_nil: object.class.ignore_nil || false,
|
49
|
+
allow_unknown: object.class.allow_unknown || false
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
# Serializes all defined attributes.
|
54
|
+
#
|
55
|
+
# @return [Hash] serialized attribute key-value pairs
|
56
|
+
def serialize_attributes
|
57
|
+
attributes.values.each_with_object({}) do |attribute, hash|
|
58
|
+
next if attribute.skip_serialization?
|
59
|
+
|
60
|
+
serialized_value = serialize_attribute(attribute)
|
61
|
+
next if serialized_value == @skip_flag
|
62
|
+
|
63
|
+
assign_attribute_key!(attribute, serialized_value, hash)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Serializes a single attribute.
|
68
|
+
#
|
69
|
+
# @param attribute [Castkit::Attribute]
|
70
|
+
# @return [Object] the serialized value or skip flag
|
71
|
+
def serialize_attribute(attribute)
|
72
|
+
value = object.public_send(attribute.field)
|
73
|
+
return @skip_flag if skip_nil?(attribute, value)
|
74
|
+
|
75
|
+
serialized_value = process_attribute(attribute, value)
|
76
|
+
return @skip_flag if skip_blank?(attribute, serialized_value)
|
77
|
+
|
78
|
+
serialized_value
|
79
|
+
end
|
80
|
+
|
81
|
+
# Delegates serialization based on type.
|
82
|
+
#
|
83
|
+
# @param attribute [Castkit::Attribute]
|
84
|
+
# @param value [Object]
|
85
|
+
# @return [Object]
|
86
|
+
def process_attribute(attribute, value)
|
87
|
+
if attribute.dataobject?
|
88
|
+
serialize_dataobject(attribute, value)
|
89
|
+
elsif attribute.dataobject_collection?
|
90
|
+
Array(value).map { |v| serialize_dataobject(attribute, v) }
|
91
|
+
else
|
92
|
+
type = Array(attribute.type).first
|
93
|
+
Castkit.type_serializer(type).call(value)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Assigns value into nested hash structure based on key path.
|
98
|
+
#
|
99
|
+
# @param attribute [Castkit::Attribute]
|
100
|
+
# @param value [Object]
|
101
|
+
# @param hash [Hash]
|
102
|
+
# @return [void]
|
103
|
+
def assign_attribute_key!(attribute, value, hash)
|
104
|
+
key_path = attribute.key_path
|
105
|
+
last = key_path.pop
|
106
|
+
current = hash
|
107
|
+
|
108
|
+
key_path.each do |key|
|
109
|
+
current[key] ||= {}
|
110
|
+
current = current[key]
|
111
|
+
end
|
112
|
+
|
113
|
+
current[last] = value
|
114
|
+
end
|
115
|
+
|
116
|
+
# Whether to skip serialization for nil values.
|
117
|
+
#
|
118
|
+
# @param attribute [Castkit::Attribute]
|
119
|
+
# @param value [Object]
|
120
|
+
# @return [Boolean]
|
121
|
+
def skip_nil?(attribute, value)
|
122
|
+
value.nil? && (attribute.ignore_nil? || options[:ignore_nil])
|
123
|
+
end
|
124
|
+
|
125
|
+
# Whether to skip serialization for blank values.
|
126
|
+
#
|
127
|
+
# @param attribute [Castkit::Attribute]
|
128
|
+
# @param value [Object]
|
129
|
+
# @return [Boolean]
|
130
|
+
def skip_blank?(attribute, value)
|
131
|
+
blank?(value) && (attribute.ignore_blank? || options[:ignore_blank])
|
132
|
+
end
|
133
|
+
|
134
|
+
# True if value is nil or empty.
|
135
|
+
#
|
136
|
+
# @param value [Object]
|
137
|
+
# @return [Boolean]
|
138
|
+
def blank?(value)
|
139
|
+
value.nil? || (value.respond_to?(:empty?) && value.empty?)
|
140
|
+
end
|
141
|
+
|
142
|
+
# Serializes a DataObject using the proper serializer.
|
143
|
+
#
|
144
|
+
# @param attribute [Castkit::Attribute]
|
145
|
+
# @param value [Castkit::DataObject]
|
146
|
+
# @return [Object]
|
147
|
+
def serialize_dataobject(attribute, value)
|
148
|
+
serializer = attribute.options[:serializer]
|
149
|
+
serializer ||= value.class.serializer
|
150
|
+
serializer ||= Castkit::Serializers::DefaultSerializer
|
151
|
+
|
152
|
+
serializer.call(value, visited: visited)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
@@ -2,14 +2,14 @@
|
|
2
2
|
|
3
3
|
module Castkit
|
4
4
|
module Types
|
5
|
-
#
|
5
|
+
# Abstract base class for type definitions in Castkit.
|
6
6
|
#
|
7
7
|
# Provides default behavior for (de)serialization, validation, and coercion.
|
8
8
|
# All primitive types should subclass this and override methods as needed.
|
9
9
|
#
|
10
10
|
# The `cast!` method is the primary entry point used by attribute processing
|
11
11
|
# to validate and coerce values in a predictable order.
|
12
|
-
class
|
12
|
+
class Base
|
13
13
|
class << self
|
14
14
|
# Coerces and validates a value for use in a Castkit DataObject.
|
15
15
|
#
|
@@ -69,7 +69,7 @@ module Castkit
|
|
69
69
|
|
70
70
|
# Builds a default validator from the instance itself.
|
71
71
|
#
|
72
|
-
# @param instance [Castkit::Types::
|
72
|
+
# @param instance [Castkit::Types::Base]
|
73
73
|
# @return [Proc] a lambda wrapping `#validate!`
|
74
74
|
def default_validator(instance)
|
75
75
|
lambda do |value, options: {}, context: nil|
|
@@ -111,10 +111,9 @@ module Castkit
|
|
111
111
|
# @param type [Symbol]
|
112
112
|
# @param value [Object, nil]
|
113
113
|
# @return [void]
|
114
|
-
def type_error!(type, value)
|
115
|
-
message = "value must be a #{type}, got #{value
|
116
|
-
|
117
|
-
raise Castkit::TypeError, message if Castkit.configuration.raise_type_errors
|
114
|
+
def type_error!(type, value, context: nil)
|
115
|
+
message = "#{context || "value"} must be a #{type}, got #{value}"
|
116
|
+
raise Castkit::AttributeError, message if Castkit.configuration.raise_type_errors
|
118
117
|
|
119
118
|
Castkit.warning "[Castkit] #{message}" if Castkit.configuration.enable_warnings
|
120
119
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "
|
3
|
+
require_relative "base"
|
4
|
+
require_relative "../validators/boolean_validator"
|
4
5
|
|
5
6
|
module Castkit
|
6
7
|
module Types
|
@@ -10,7 +11,7 @@ module Castkit
|
|
10
11
|
#
|
11
12
|
# This class is used internally by Castkit when an attribute is defined with:
|
12
13
|
# `boolean :is_active`
|
13
|
-
class Boolean <
|
14
|
+
class Boolean < Base
|
14
15
|
# Deserializes the input into a boolean value.
|
15
16
|
#
|
16
17
|
# Accepts:
|
@@ -21,14 +22,7 @@ module Castkit
|
|
21
22
|
# @return [Boolean]
|
22
23
|
# @raise [Castkit::TypeError] if the value cannot be coerced to a boolean
|
23
24
|
def deserialize(value)
|
24
|
-
|
25
|
-
when "true", "1"
|
26
|
-
true
|
27
|
-
when "false", "0"
|
28
|
-
false
|
29
|
-
else
|
30
|
-
type_error!(:boolean, value)
|
31
|
-
end
|
25
|
+
value
|
32
26
|
end
|
33
27
|
|
34
28
|
# Serializes the boolean value (pass-through).
|
@@ -38,6 +32,16 @@ module Castkit
|
|
38
32
|
def serialize(value)
|
39
33
|
value
|
40
34
|
end
|
35
|
+
|
36
|
+
# Validates the Boolean value.
|
37
|
+
#
|
38
|
+
# @param value [Object]
|
39
|
+
# @param options [Hash] validation options
|
40
|
+
# @param context [Symbol, String] attribute context for error messages
|
41
|
+
# @return [void]
|
42
|
+
def validate!(value, options: {}, context: {})
|
43
|
+
Castkit::Validators::BooleanValidator.call(value, options: options, context: context)
|
44
|
+
end
|
41
45
|
end
|
42
46
|
end
|
43
47
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "
|
3
|
+
require_relative "base"
|
4
|
+
require_relative "../validators/collection_validator"
|
4
5
|
|
5
6
|
module Castkit
|
6
7
|
module Types
|
@@ -11,7 +12,7 @@ module Castkit
|
|
11
12
|
#
|
12
13
|
# This class is used internally by Castkit when an attribute is defined with:
|
13
14
|
# `array :tags, of: :string`
|
14
|
-
class Collection <
|
15
|
+
class Collection < Base
|
15
16
|
# Deserializes the value into an array using `Array(value)`.
|
16
17
|
#
|
17
18
|
# @param value [Object]
|
@@ -19,6 +20,16 @@ module Castkit
|
|
19
20
|
def deserialize(value)
|
20
21
|
Array(value)
|
21
22
|
end
|
23
|
+
|
24
|
+
# Validates the Array value.
|
25
|
+
#
|
26
|
+
# @param value [Object]
|
27
|
+
# @param options [Hash] validation options
|
28
|
+
# @param context [Symbol, String, nil] attribute context for error messages
|
29
|
+
# @return [void]
|
30
|
+
def validate!(value, options: {}, context: nil)
|
31
|
+
Castkit::Validators::CollectionValidator.call(value, options: options, context: context)
|
32
|
+
end
|
22
33
|
end
|
23
34
|
end
|
24
35
|
end
|
data/lib/castkit/types/date.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "date"
|
4
|
-
require_relative "
|
4
|
+
require_relative "base"
|
5
5
|
|
6
6
|
module Castkit
|
7
7
|
module Types
|
@@ -12,7 +12,7 @@ module Castkit
|
|
12
12
|
#
|
13
13
|
# This class is used internally by Castkit when an attribute is defined with:
|
14
14
|
# `date :published_on`
|
15
|
-
class Date <
|
15
|
+
class Date < Base
|
16
16
|
# Deserializes the input value to a `Date` instance.
|
17
17
|
#
|
18
18
|
# @param value [Object]
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "date"
|
4
|
-
require_relative "
|
4
|
+
require_relative "base"
|
5
5
|
|
6
6
|
module Castkit
|
7
7
|
module Types
|
@@ -12,7 +12,7 @@ module Castkit
|
|
12
12
|
#
|
13
13
|
# This class is used internally by Castkit when an attribute is defined with:
|
14
14
|
# `datetime :published_ad`
|
15
|
-
class DateTime <
|
15
|
+
class DateTime < Base
|
16
16
|
# Deserializes the input value to a `DateTime` instance.
|
17
17
|
#
|
18
18
|
# @param value [Object]
|
data/lib/castkit/types/float.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "
|
4
|
-
require_relative "../validators/
|
3
|
+
require_relative "base"
|
4
|
+
require_relative "../validators/float_validator"
|
5
5
|
|
6
6
|
module Castkit
|
7
7
|
module Types
|
@@ -13,7 +13,7 @@ module Castkit
|
|
13
13
|
#
|
14
14
|
# This class is used internally by Castkit when an attribute is defined with:
|
15
15
|
# `integer :count`
|
16
|
-
class Float <
|
16
|
+
class Float < Base
|
17
17
|
# Deserializes the input value to an Float.
|
18
18
|
#
|
19
19
|
# @param value [Object]
|
@@ -30,7 +30,7 @@ module Castkit
|
|
30
30
|
value
|
31
31
|
end
|
32
32
|
|
33
|
-
# Validates the Float value using Castkit's
|
33
|
+
# Validates the Float value using Castkit's FloatValidator.
|
34
34
|
#
|
35
35
|
# Supports options like `min:` and `max:`.
|
36
36
|
#
|
@@ -39,7 +39,7 @@ module Castkit
|
|
39
39
|
# @param context [Symbol, String] attribute context for error messages
|
40
40
|
# @return [void]
|
41
41
|
def validate!(value, options: {}, context: {})
|
42
|
-
Castkit::Validators::
|
42
|
+
Castkit::Validators::FloatValidator.call(value, options: options, context: context)
|
43
43
|
end
|
44
44
|
end
|
45
45
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "
|
4
|
-
require_relative "../validators/
|
3
|
+
require_relative "base"
|
4
|
+
require_relative "../validators/integer_validator"
|
5
5
|
|
6
6
|
module Castkit
|
7
7
|
module Types
|
@@ -13,7 +13,7 @@ module Castkit
|
|
13
13
|
#
|
14
14
|
# This class is used internally by Castkit when an attribute is defined with:
|
15
15
|
# `integer :count`
|
16
|
-
class Integer <
|
16
|
+
class Integer < Base
|
17
17
|
# Deserializes the input value to an Integer.
|
18
18
|
#
|
19
19
|
# @param value [Object]
|
@@ -30,7 +30,7 @@ module Castkit
|
|
30
30
|
value
|
31
31
|
end
|
32
32
|
|
33
|
-
# Validates the Integer value using Castkit's
|
33
|
+
# Validates the Integer value using Castkit's IntegerValidator.
|
34
34
|
#
|
35
35
|
# Supports options like `min:` and `max:`.
|
36
36
|
#
|
@@ -39,7 +39,7 @@ module Castkit
|
|
39
39
|
# @param context [Symbol, String] attribute context for error messages
|
40
40
|
# @return [void]
|
41
41
|
def validate!(value, options: {}, context: {})
|
42
|
-
Castkit::Validators::
|
42
|
+
Castkit::Validators::IntegerValidator.call(value, options: options, context: context)
|
43
43
|
end
|
44
44
|
end
|
45
45
|
end
|
data/lib/castkit/types/string.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "
|
3
|
+
require_relative "base"
|
4
4
|
require_relative "../validators/string_validator"
|
5
5
|
|
6
6
|
module Castkit
|
@@ -12,7 +12,7 @@ module Castkit
|
|
12
12
|
#
|
13
13
|
# This class is used internally by Castkit when an attribute is defined with:
|
14
14
|
# `string :id`
|
15
|
-
class String <
|
15
|
+
class String < Base
|
16
16
|
# Deserializes the value by coercing it to a string using `to_s`.
|
17
17
|
#
|
18
18
|
# @param value [Object]
|
data/lib/castkit/types.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative "types/base"
|
3
4
|
require_relative "types/collection"
|
4
5
|
require_relative "types/boolean"
|
5
6
|
require_relative "types/date"
|
6
7
|
require_relative "types/date_time"
|
7
8
|
require_relative "types/float"
|
8
|
-
require_relative "types/generic"
|
9
9
|
require_relative "types/integer"
|
10
10
|
require_relative "types/string"
|
11
11
|
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Castkit
|
4
|
+
module Validators
|
5
|
+
# Abstract base class for all attribute validators.
|
6
|
+
#
|
7
|
+
# Validators are responsible for enforcing value constraints (e.g., type checks,
|
8
|
+
# format rules, numerical bounds). All validators must inherit from this base class
|
9
|
+
# and implement the `#call` instance method.
|
10
|
+
#
|
11
|
+
# Supports both instance-level and class-level invocation via `.call`.
|
12
|
+
#
|
13
|
+
# @abstract Subclasses must implement `#call`.
|
14
|
+
class Base
|
15
|
+
class << self
|
16
|
+
# Entry point for validating a value using the validator class.
|
17
|
+
#
|
18
|
+
# Instantiates the validator and invokes `#call` with the provided arguments.
|
19
|
+
#
|
20
|
+
# @param value [Object] the value to validate
|
21
|
+
# @param options [Hash] additional validation options (e.g., `min`, `max`, `format`)
|
22
|
+
# @param context [Symbol, String, Hash] context for the validation (usually the attribute name)
|
23
|
+
# @return [void]
|
24
|
+
# @raise [Castkit::AttributeError] if validation fails and `raise_type_errors` is true
|
25
|
+
def call(value, options:, context:)
|
26
|
+
new.call(value, options: options, context: context)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Validates the value.
|
31
|
+
#
|
32
|
+
# @abstract
|
33
|
+
# @param value [Object] the value to validate
|
34
|
+
# @param options [Hash] validation options (e.g., min, max, format)
|
35
|
+
# @param context [Symbol, String, Hash] context for validation errors
|
36
|
+
# @return [void]
|
37
|
+
# @raise [NotImplementedError] if not implemented in subclass
|
38
|
+
def call(value, options:, context:)
|
39
|
+
raise NotImplementedError, "#{self.class.name} must implement `#call`"
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
|
44
|
+
# Emits or raises a type error depending on global configuration.
|
45
|
+
#
|
46
|
+
# @param type [Symbol] the expected type (e.g., `:integer`)
|
47
|
+
# @param value [Object, nil] the received value
|
48
|
+
# @param context [Symbol, String, nil] context to include in error messages
|
49
|
+
# @raise [Castkit::AttributeError] if `raise_type_errors` is enabled
|
50
|
+
# @return [void]
|
51
|
+
def type_error!(type, value, context: nil)
|
52
|
+
message = "#{context || "value"} must be a #{type}, got #{value}"
|
53
|
+
raise Castkit::AttributeError, message if Castkit.configuration.raise_type_errors
|
54
|
+
|
55
|
+
Castkit.warning "[Castkit] #{message}" if Castkit.configuration.enable_warnings
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
|
5
|
+
module Castkit
|
6
|
+
module Validators
|
7
|
+
# Validator for boolean attributes.
|
8
|
+
#
|
9
|
+
# Accepts various representations of boolean values, including strings and integers.
|
10
|
+
# Converts common truthy/falsy string values into booleans, otherwise raises a type error.
|
11
|
+
#
|
12
|
+
# This validator is typically used internally by `Castkit::Types::Boolean`.
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
# validator = Castkit::Validators::BooleanValidator.new
|
16
|
+
# validator.call("true", _options: {}, context: :enabled) # => true
|
17
|
+
# validator.call("0", _options: {}, context: :enabled) # => false
|
18
|
+
# validator.call("nope", _options: {}, context: :enabled) # raises Castkit::AttributeError
|
19
|
+
class BooleanValidator
|
20
|
+
# Validates the Boolean value.
|
21
|
+
#
|
22
|
+
# @param value [Object] the input to validate
|
23
|
+
# @param _options [Hash] unused, provided for consistency with other validators
|
24
|
+
# @param context [Symbol, String] the attribute name or path for error messages
|
25
|
+
# @return [Boolean]
|
26
|
+
# @raise [Castkit::AttributeError] if the value is not a recognizable boolean
|
27
|
+
def call(value, _options:, context:)
|
28
|
+
case value.to_s.downcase
|
29
|
+
when "true", "1"
|
30
|
+
true
|
31
|
+
when "false", "0"
|
32
|
+
false
|
33
|
+
else
|
34
|
+
type_error!(:boolean, value, context: context)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../validators/base"
|
4
|
+
|
5
|
+
module Castkit
|
6
|
+
module Validators
|
7
|
+
# Validator for array (collection) attributes.
|
8
|
+
#
|
9
|
+
# Ensures that the provided value is an instance of `Array`. This validator is
|
10
|
+
# typically used by `Castkit::Types::Collection` for attributes defined as arrays.
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# validator = Castkit::Validators::CollectionValidator.new
|
14
|
+
# validator.call([1, 2, 3], _options: {}, context: :tags) # => passes
|
15
|
+
# validator.call("foo", _options: {}, context: :tags) # raises Castkit::AttributeError
|
16
|
+
class CollectionValidator < Castkit::Validators::Base
|
17
|
+
# Validates that the value is an Array.
|
18
|
+
#
|
19
|
+
# @param value [Object] the value to validate
|
20
|
+
# @param _options [Hash] unused, for interface consistency
|
21
|
+
# @param context [Symbol, String] the field or context for error messaging
|
22
|
+
# @return [void]
|
23
|
+
# @raise [Castkit::AttributeError] if value is not an Array
|
24
|
+
def call(value, _options:, context:)
|
25
|
+
type_error!(:array, value, context: context) unless value.is_a?(::Array)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "numeric_validator"
|
4
|
+
|
5
|
+
module Castkit
|
6
|
+
module Validators
|
7
|
+
# Validator for Float attributes.
|
8
|
+
#
|
9
|
+
# Ensures the value is a `Float`, and applies any numeric bounds (`min`, `max`)
|
10
|
+
# defined in the attribute options. Inherits shared logic from `NumericValidator`.
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# validator = Castkit::Validators::FloatValidator.new
|
14
|
+
# validator.call(3.14, options: { min: 0.0 }, context: :price) # => passes
|
15
|
+
# validator.call(42, options: {}, context: :price) # raises Castkit::AttributeError
|
16
|
+
class FloatValidator < Castkit::Validators::NumericValidator
|
17
|
+
# Validates that the value is a Float and within optional bounds.
|
18
|
+
#
|
19
|
+
# @param value [Object, nil] the value to validate
|
20
|
+
# @param options [Hash] validation options (e.g., `min`, `max`)
|
21
|
+
# @param context [Symbol, String] the attribute name or key for error messages
|
22
|
+
# @raise [Castkit::AttributeError] if value is not a Float or out of range
|
23
|
+
# @return [void]
|
24
|
+
def call(value, options:, context:)
|
25
|
+
return type_error!(:float, value, context: context) unless value.is_a?(::Float)
|
26
|
+
|
27
|
+
super
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "numeric_validator"
|
4
|
+
|
5
|
+
module Castkit
|
6
|
+
module Validators
|
7
|
+
# Validator for Integer attributes.
|
8
|
+
#
|
9
|
+
# Ensures the value is an instance of `Integer` and optionally checks numerical constraints
|
10
|
+
# such as `min` and `max`, inherited from `Castkit::Validators::NumericValidator`.
|
11
|
+
#
|
12
|
+
# @example Validating an Integer attribute
|
13
|
+
# IntegerValidator.call(42, options: { min: 10, max: 100 }, context: :count)
|
14
|
+
#
|
15
|
+
# @see Castkit::Validators::NumericValidator
|
16
|
+
class IntegerValidator < Castkit::Validators::NumericValidator
|
17
|
+
# Validates the Integer value.
|
18
|
+
#
|
19
|
+
# @param value [Object, nil] the value to validate
|
20
|
+
# @param options [Hash] validation options (e.g., `min`, `max`)
|
21
|
+
# @param context [Symbol, String] the attribute name or context for error messages
|
22
|
+
# @raise [Castkit::AttributeError] if the value is not an Integer or fails validation rules
|
23
|
+
# @return [void]
|
24
|
+
def call(value, options:, context:)
|
25
|
+
return type_error!(:integer, value, context: context) unless value.is_a?(::Integer)
|
26
|
+
|
27
|
+
super
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -1,13 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "
|
3
|
+
require_relative "base"
|
4
4
|
|
5
5
|
module Castkit
|
6
6
|
module Validators
|
7
7
|
# Validates that a numeric value falls within the allowed range.
|
8
8
|
#
|
9
9
|
# Supports `:min` and `:max` options to enforce bounds.
|
10
|
-
class NumericValidator < Castkit::Validators::
|
10
|
+
class NumericValidator < Castkit::Validators::Base
|
11
11
|
# Validates the numeric value.
|
12
12
|
#
|
13
13
|
# @param value [Numeric] the value to validate
|
@@ -1,13 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "
|
3
|
+
require_relative "base"
|
4
4
|
|
5
5
|
module Castkit
|
6
6
|
module Validators
|
7
7
|
# Validates that a value is a String and optionally conforms to a format.
|
8
8
|
#
|
9
9
|
# Supports format validation using a Regexp or a custom Proc.
|
10
|
-
class StringValidator < Castkit::Validators::
|
10
|
+
class StringValidator < Castkit::Validators::Base
|
11
11
|
# Validates the string value.
|
12
12
|
#
|
13
13
|
# @param value [Object] the value to validate
|
@@ -16,8 +16,7 @@ module Castkit
|
|
16
16
|
# @raise [Castkit::AttributeError] if value is not a string or fails format validation
|
17
17
|
# @return [void]
|
18
18
|
def call(value, options:, context:)
|
19
|
-
|
20
|
-
|
19
|
+
return type_error!(:string, value, context: context) unless value.is_a?(::String)
|
21
20
|
return unless options[:format]
|
22
21
|
|
23
22
|
case options[:format]
|
data/lib/castkit/version.rb
CHANGED