castkit 0.1.2 → 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 +195 -219
- data/CHANGELOG.md +42 -0
- data/README.md +744 -83
- data/castkit.gemspec +1 -0
- data/lib/castkit/attribute.rb +6 -24
- data/lib/castkit/castkit.rb +61 -10
- 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 +116 -46
- data/lib/castkit/contract/base.rb +168 -0
- data/lib/castkit/contract/data_object.rb +62 -0
- data/lib/castkit/contract/result.rb +74 -0
- data/lib/castkit/contract/validator.rb +248 -0
- data/lib/castkit/contract.rb +67 -0
- data/lib/castkit/{data_object_extensions → core}/attribute_types.rb +21 -7
- data/lib/castkit/{data_object_extensions → core}/attributes.rb +8 -3
- data/lib/castkit/core/config.rb +74 -0
- data/lib/castkit/core/registerable.rb +59 -0
- data/lib/castkit/data_object.rb +56 -67
- data/lib/castkit/error.rb +15 -3
- data/lib/castkit/ext/attribute/access.rb +67 -0
- data/lib/castkit/ext/attribute/error_handling.rb +63 -0
- data/lib/castkit/ext/attribute/options.rb +142 -0
- data/lib/castkit/ext/attribute/validation.rb +85 -0
- data/lib/castkit/ext/data_object/contract.rb +96 -0
- data/lib/castkit/ext/data_object/deserialization.rb +167 -0
- data/lib/castkit/ext/data_object/plugins.rb +86 -0
- data/lib/castkit/ext/data_object/serialization.rb +61 -0
- data/lib/castkit/inflector.rb +47 -0
- 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/base.rb +122 -0
- data/lib/castkit/types/boolean.rb +47 -0
- data/lib/castkit/types/collection.rb +35 -0
- data/lib/castkit/types/date.rb +34 -0
- data/lib/castkit/types/date_time.rb +34 -0
- data/lib/castkit/types/float.rb +46 -0
- data/lib/castkit/types/integer.rb +46 -0
- data/lib/castkit/types/string.rb +44 -0
- data/lib/castkit/types.rb +15 -0
- 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/castkit.rb +2 -0
- 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 +74 -15
- data/lib/castkit/attribute_extensions/access.rb +0 -65
- data/lib/castkit/attribute_extensions/casting.rb +0 -147
- data/lib/castkit/attribute_extensions/error_handling.rb +0 -83
- data/lib/castkit/attribute_extensions/options.rb +0 -131
- data/lib/castkit/attribute_extensions/serialization.rb +0 -89
- data/lib/castkit/attribute_extensions/validation.rb +0 -72
- data/lib/castkit/data_object_extensions/config.rb +0 -113
- data/lib/castkit/data_object_extensions/deserialization.rb +0 -110
- data/lib/castkit/default_serializer.rb +0 -123
- data/lib/castkit/serializer.rb +0 -92
- data/lib/castkit/validators.rb +0 -4
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: castkit
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nathan Lucas
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-04-
|
11
|
+
date: 2025-04-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: thor
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: rspec
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -72,27 +86,72 @@ files:
|
|
72
86
|
- castkit.gemspec
|
73
87
|
- lib/castkit.rb
|
74
88
|
- lib/castkit/attribute.rb
|
75
|
-
- lib/castkit/attribute_extensions/access.rb
|
76
|
-
- lib/castkit/attribute_extensions/casting.rb
|
77
|
-
- lib/castkit/attribute_extensions/error_handling.rb
|
78
|
-
- lib/castkit/attribute_extensions/options.rb
|
79
|
-
- lib/castkit/attribute_extensions/serialization.rb
|
80
|
-
- lib/castkit/attribute_extensions/validation.rb
|
81
89
|
- lib/castkit/castkit.rb
|
90
|
+
- lib/castkit/cli.rb
|
91
|
+
- lib/castkit/cli/generate.rb
|
92
|
+
- lib/castkit/cli/list.rb
|
93
|
+
- lib/castkit/cli/main.rb
|
82
94
|
- lib/castkit/configuration.rb
|
95
|
+
- lib/castkit/contract.rb
|
96
|
+
- lib/castkit/contract/base.rb
|
97
|
+
- lib/castkit/contract/data_object.rb
|
98
|
+
- lib/castkit/contract/result.rb
|
99
|
+
- lib/castkit/contract/validator.rb
|
100
|
+
- lib/castkit/core/attribute_types.rb
|
101
|
+
- lib/castkit/core/attributes.rb
|
102
|
+
- lib/castkit/core/config.rb
|
103
|
+
- lib/castkit/core/registerable.rb
|
83
104
|
- lib/castkit/data_object.rb
|
84
|
-
- lib/castkit/data_object_extensions/attribute_types.rb
|
85
|
-
- lib/castkit/data_object_extensions/attributes.rb
|
86
|
-
- lib/castkit/data_object_extensions/config.rb
|
87
|
-
- lib/castkit/data_object_extensions/deserialization.rb
|
88
|
-
- lib/castkit/default_serializer.rb
|
89
105
|
- lib/castkit/error.rb
|
90
|
-
- lib/castkit/
|
106
|
+
- lib/castkit/ext/attribute/access.rb
|
107
|
+
- lib/castkit/ext/attribute/error_handling.rb
|
108
|
+
- lib/castkit/ext/attribute/options.rb
|
109
|
+
- lib/castkit/ext/attribute/validation.rb
|
110
|
+
- lib/castkit/ext/data_object/contract.rb
|
111
|
+
- lib/castkit/ext/data_object/deserialization.rb
|
112
|
+
- lib/castkit/ext/data_object/plugins.rb
|
113
|
+
- lib/castkit/ext/data_object/serialization.rb
|
114
|
+
- lib/castkit/inflector.rb
|
115
|
+
- lib/castkit/plugins.rb
|
116
|
+
- lib/castkit/serializers/base.rb
|
117
|
+
- lib/castkit/serializers/default_serializer.rb
|
118
|
+
- lib/castkit/types.rb
|
119
|
+
- lib/castkit/types/base.rb
|
120
|
+
- lib/castkit/types/boolean.rb
|
121
|
+
- lib/castkit/types/collection.rb
|
122
|
+
- lib/castkit/types/date.rb
|
123
|
+
- lib/castkit/types/date_time.rb
|
124
|
+
- lib/castkit/types/float.rb
|
125
|
+
- lib/castkit/types/integer.rb
|
126
|
+
- lib/castkit/types/string.rb
|
91
127
|
- lib/castkit/validator.rb
|
92
|
-
- lib/castkit/validators.rb
|
128
|
+
- lib/castkit/validators/base.rb
|
129
|
+
- lib/castkit/validators/boolean_validator.rb
|
130
|
+
- lib/castkit/validators/collection_validator.rb
|
131
|
+
- lib/castkit/validators/float_validator.rb
|
132
|
+
- lib/castkit/validators/integer_validator.rb
|
93
133
|
- lib/castkit/validators/numeric_validator.rb
|
94
134
|
- lib/castkit/validators/string_validator.rb
|
95
135
|
- lib/castkit/version.rb
|
136
|
+
- lib/generators/base.rb
|
137
|
+
- lib/generators/contract.rb
|
138
|
+
- lib/generators/data_object.rb
|
139
|
+
- lib/generators/plugin.rb
|
140
|
+
- lib/generators/serializer.rb
|
141
|
+
- lib/generators/templates/contract.rb.tt
|
142
|
+
- lib/generators/templates/contract_spec.rb.tt
|
143
|
+
- lib/generators/templates/data_object.rb.tt
|
144
|
+
- lib/generators/templates/data_object_spec.rb.tt
|
145
|
+
- lib/generators/templates/plugin.rb.tt
|
146
|
+
- lib/generators/templates/plugin_spec.rb.tt
|
147
|
+
- lib/generators/templates/serializer.rb.tt
|
148
|
+
- lib/generators/templates/serializer_spec.rb.tt
|
149
|
+
- lib/generators/templates/type.rb.tt
|
150
|
+
- lib/generators/templates/type_spec.rb.tt
|
151
|
+
- lib/generators/templates/validator.rb.tt
|
152
|
+
- lib/generators/templates/validator_spec.rb.tt
|
153
|
+
- lib/generators/type.rb
|
154
|
+
- lib/generators/validator.rb
|
96
155
|
- sig/castkit.rbs
|
97
156
|
homepage: https://github.com/bnlucas/castkit
|
98
157
|
licenses:
|
@@ -1,65 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Castkit
|
4
|
-
module AttributeExtensions
|
5
|
-
# Provides access control helpers for attributes.
|
6
|
-
#
|
7
|
-
# These helpers determine whether an attribute is readable, writeable,
|
8
|
-
# or should be included during serialization/deserialization based on the
|
9
|
-
# configured `:access` and `:ignore` options.
|
10
|
-
module Access
|
11
|
-
# Returns the normalized access modes for the attribute (e.g., [:read, :write]).
|
12
|
-
#
|
13
|
-
# @return [Array<Symbol>] list of access symbols
|
14
|
-
def access
|
15
|
-
Array(options[:access]).map(&:to_sym)
|
16
|
-
end
|
17
|
-
|
18
|
-
# Whether the attribute should be included during serialization.
|
19
|
-
#
|
20
|
-
# @return [Boolean]
|
21
|
-
def readable?
|
22
|
-
access.include?(:read)
|
23
|
-
end
|
24
|
-
|
25
|
-
# Whether the attribute should be accepted during deserialization.
|
26
|
-
#
|
27
|
-
# Composite attributes are excluded from writeability.
|
28
|
-
#
|
29
|
-
# @return [Boolean]
|
30
|
-
def writeable?
|
31
|
-
access.include?(:write) && !composite?
|
32
|
-
end
|
33
|
-
|
34
|
-
# Whether the attribute is both readable and writeable.
|
35
|
-
#
|
36
|
-
# @return [Boolean]
|
37
|
-
def full_access?
|
38
|
-
readable? && writeable?
|
39
|
-
end
|
40
|
-
|
41
|
-
# Whether the attribute should be skipped during serialization.
|
42
|
-
#
|
43
|
-
# This is true if it's not readable or is marked as ignored.
|
44
|
-
#
|
45
|
-
# @return [Boolean]
|
46
|
-
def skip_serialization?
|
47
|
-
!readable? || ignore?
|
48
|
-
end
|
49
|
-
|
50
|
-
# Whether the attribute should be skipped during deserialization.
|
51
|
-
#
|
52
|
-
# @return [Boolean]
|
53
|
-
def skip_deserialization?
|
54
|
-
!writeable?
|
55
|
-
end
|
56
|
-
|
57
|
-
# Whether the attribute is ignored completely.
|
58
|
-
#
|
59
|
-
# @return [Boolean]
|
60
|
-
def ignore?
|
61
|
-
options[:ignore]
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
@@ -1,147 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "date"
|
4
|
-
require_relative "../data_object"
|
5
|
-
require_relative "error_handling"
|
6
|
-
|
7
|
-
module Castkit
|
8
|
-
module AttributeExtensions
|
9
|
-
# Provides typecasting logic for attributes based on their declared type.
|
10
|
-
#
|
11
|
-
# Supports primitive types, arrays, nested Castkit::DataObject types, and union types.
|
12
|
-
module Casting
|
13
|
-
include Castkit::AttributeExtensions::ErrorHandling
|
14
|
-
|
15
|
-
PRIMITIVE_CASTERS = {
|
16
|
-
integer: lambda(&:to_i),
|
17
|
-
float: lambda(&:to_f),
|
18
|
-
string: lambda(&:to_s),
|
19
|
-
hash: ->(v) { v },
|
20
|
-
array: ->(v) { Array(v) }
|
21
|
-
}.freeze
|
22
|
-
|
23
|
-
private
|
24
|
-
|
25
|
-
# Casts a value based on the attribute's declared type.
|
26
|
-
#
|
27
|
-
# @param value [Object] the input value to cast
|
28
|
-
# @return [Object, nil] the cast value
|
29
|
-
# @raise [Castkit::AttributeError] if the value cannot be cast
|
30
|
-
def cast(value)
|
31
|
-
handle_error(:array_of_type) if type == :array && options[:of].nil?
|
32
|
-
value = default if value.nil?
|
33
|
-
return if value.nil? && optional?
|
34
|
-
|
35
|
-
cast_value(value)
|
36
|
-
end
|
37
|
-
|
38
|
-
# Delegates casting logic based on the attribute's type.
|
39
|
-
#
|
40
|
-
# This method is invoked internally by `#cast` after nil handling and default fallback.
|
41
|
-
#
|
42
|
-
# - For union types (`Array` of types), attempts each in order.
|
43
|
-
# - For array types, maps over elements with `#cast_element`.
|
44
|
-
# - For nested data objects, delegates to `.cast`.
|
45
|
-
# - For primitive types, uses `#cast_primitive`.
|
46
|
-
#
|
47
|
-
# @param value [Object] the raw input value to cast
|
48
|
-
# @return [Object, nil] the cast value
|
49
|
-
# @raise [Castkit::AttributeError] if casting fails for all union types or invalid primitives
|
50
|
-
def cast_value(value)
|
51
|
-
if type.is_a?(Array)
|
52
|
-
try_union_cast(value)
|
53
|
-
elsif type == :array
|
54
|
-
Array(value).map { |v| cast_element(v) }
|
55
|
-
elsif dataobject?
|
56
|
-
type.cast(value)
|
57
|
-
else
|
58
|
-
cast_primitive(value)
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
# Attempts to cast the value against a union of possible types.
|
63
|
-
#
|
64
|
-
# @param value [Object]
|
65
|
-
# @return [Object] the first successful cast result
|
66
|
-
# @raise [Castkit::AttributeError] if no types match
|
67
|
-
def try_union_cast(value)
|
68
|
-
last_error = nil
|
69
|
-
|
70
|
-
type.each do |t|
|
71
|
-
return try_cast_type(value, t)
|
72
|
-
rescue Castkit::AttributeError => e
|
73
|
-
last_error = e
|
74
|
-
end
|
75
|
-
|
76
|
-
raise last_error || handle_error(:union, types: type)
|
77
|
-
end
|
78
|
-
|
79
|
-
# Tries to cast a value to a specific type.
|
80
|
-
#
|
81
|
-
# @param value [Object]
|
82
|
-
# @param type [Symbol, Class] the type to try
|
83
|
-
# @return [Object, nil]
|
84
|
-
def try_cast_type(value, type)
|
85
|
-
return type.cast(value) if Castkit.dataobject?(type)
|
86
|
-
|
87
|
-
cast_primitive(value, type: type)
|
88
|
-
end
|
89
|
-
|
90
|
-
# Casts an element of an array attribute.
|
91
|
-
#
|
92
|
-
# @param value [Object]
|
93
|
-
# @return [Object, nil]
|
94
|
-
def cast_element(value)
|
95
|
-
if Castkit.dataobject?(options[:of])
|
96
|
-
options[:of].cast(value)
|
97
|
-
else
|
98
|
-
validate_element_type!(value)
|
99
|
-
cast_primitive(value, type: options[:of])
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
# Casts a primitive value based on its type.
|
104
|
-
#
|
105
|
-
# @param value [Object]
|
106
|
-
# @param type [Symbol]
|
107
|
-
# @return [Object, nil]
|
108
|
-
# @raise [Castkit::AttributeError]
|
109
|
-
def cast_primitive(value, type: self.type)
|
110
|
-
return cast_boolean(value) if type == :boolean
|
111
|
-
return Date.parse(value.to_s) if type == :date
|
112
|
-
return DateTime.parse(value.to_s) if type == :datetime
|
113
|
-
|
114
|
-
PRIMITIVE_CASTERS.fetch(type) do
|
115
|
-
handle_error(:primitive, type: type)
|
116
|
-
end.call(value)
|
117
|
-
end
|
118
|
-
|
119
|
-
# Casts a value to boolean.
|
120
|
-
#
|
121
|
-
# @param value [Object]
|
122
|
-
# @return [Boolean, nil]
|
123
|
-
# @raise [Castkit::AttributeError]
|
124
|
-
def cast_boolean(value)
|
125
|
-
case value.to_s.downcase
|
126
|
-
when "true", "1"
|
127
|
-
true
|
128
|
-
when "false", "0"
|
129
|
-
false
|
130
|
-
else
|
131
|
-
handle_error(:boolean, value: value)
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
# Validates element type for arrays, if `enforce_array_of_type` is enabled.
|
136
|
-
#
|
137
|
-
# @param value [Object]
|
138
|
-
# @return [void]
|
139
|
-
def validate_element_type!(value)
|
140
|
-
return unless Castkit.configuration.enforce_array_of_type
|
141
|
-
|
142
|
-
validator = Castkit.configuration.validator_for(options[:of])
|
143
|
-
validator.call(value, options: options, context: "#{field}[]")
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|
147
|
-
end
|
@@ -1,83 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative "../castkit"
|
4
|
-
|
5
|
-
module Castkit
|
6
|
-
module AttributeExtensions
|
7
|
-
# Provides centralized handling of attribute casting and validation errors.
|
8
|
-
#
|
9
|
-
# The behavior of each error is controlled by configuration flags in `Castkit.configuration`.
|
10
|
-
module ErrorHandling
|
11
|
-
# Maps known error types to their handling behavior.
|
12
|
-
#
|
13
|
-
# Each entry includes:
|
14
|
-
# - `:config` – the config flag that determines enforcement
|
15
|
-
# - `:message` – a lambda that generates an error message
|
16
|
-
# - `:error` – the error class to raise
|
17
|
-
#
|
18
|
-
# @return [Hash<Symbol, Hash>]
|
19
|
-
ERROR_OPTIONS = {
|
20
|
-
array_of_type: {
|
21
|
-
config: :enforce_array_of_type,
|
22
|
-
message: ->(*_) { "`of:` must be provided for array type" },
|
23
|
-
error: Castkit::AttributeError
|
24
|
-
},
|
25
|
-
primitive: {
|
26
|
-
config: :enforce_known_primitive_type,
|
27
|
-
message: ->(_attr, type:) { "unknown primitive type: #{type.inspect}" },
|
28
|
-
error: Castkit::AttributeError
|
29
|
-
},
|
30
|
-
boolean: {
|
31
|
-
config: :enforce_boolean_casting,
|
32
|
-
message: ->(_attr, value:) { "must be a boolean, got: #{value.inspect}" },
|
33
|
-
error: Castkit::AttributeError
|
34
|
-
},
|
35
|
-
union: {
|
36
|
-
config: :enforce_union_match,
|
37
|
-
message: ->(_attr, types:) { "could not be cast to any of #{types.inspect}" },
|
38
|
-
error: Castkit::AttributeError
|
39
|
-
},
|
40
|
-
access: {
|
41
|
-
config: :enforce_attribute_access,
|
42
|
-
message: ->(_attr, mode:) { "invalid access mode `#{mode}`" },
|
43
|
-
error: Castkit::AttributeError
|
44
|
-
},
|
45
|
-
unwrapped: {
|
46
|
-
config: :enforce_unwrapped_prefix,
|
47
|
-
message: ->(*_) { "prefix can only be used with unwrapped attribute" },
|
48
|
-
error: Castkit::AttributeError
|
49
|
-
},
|
50
|
-
array_options: {
|
51
|
-
config: :enforce_array_options,
|
52
|
-
message: ->(*_) { "array attribute must specify `of:` type" },
|
53
|
-
error: Castkit::AttributeError
|
54
|
-
}
|
55
|
-
}.freeze
|
56
|
-
|
57
|
-
private
|
58
|
-
|
59
|
-
# Handles a validation or casting error based on the provided error key and context.
|
60
|
-
#
|
61
|
-
# If the corresponding configuration flag is enabled, an exception is raised.
|
62
|
-
# Otherwise, a warning is logged and the method returns `nil`.
|
63
|
-
#
|
64
|
-
# @param key [Symbol] the type of error (must match a key in ERROR_OPTIONS)
|
65
|
-
# @param kwargs [Hash] additional values passed to the message lambda
|
66
|
-
# @option kwargs [Symbol] :context (optional) the attribute context (e.g., field name)
|
67
|
-
# @return [nil]
|
68
|
-
# @raise [Castkit::AttributeError] if enforcement is enabled for the given error type
|
69
|
-
def handle_error(key, **kwargs)
|
70
|
-
config_key = ERROR_OPTIONS.dig(key, :config)
|
71
|
-
message_fn = ERROR_OPTIONS.dig(key, :message)
|
72
|
-
error_class = ERROR_OPTIONS.dig(key, :error) || Castkit::Error
|
73
|
-
|
74
|
-
context = kwargs.delete(:context)
|
75
|
-
message = message_fn.call(self, **kwargs)
|
76
|
-
raise error_class.new(message, context: context) if Castkit.configuration.public_send(config_key)
|
77
|
-
|
78
|
-
Castkit.warning "[Castkit] #{message}"
|
79
|
-
nil
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|
@@ -1,131 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative "../data_object"
|
4
|
-
|
5
|
-
module Castkit
|
6
|
-
module AttributeExtensions
|
7
|
-
# Provides access to normalized attribute options and helper predicates.
|
8
|
-
#
|
9
|
-
# These methods support Castkit attribute behavior such as default values,
|
10
|
-
# key mapping, optionality, and structural roles (e.g. composite or unwrapped).
|
11
|
-
module Options
|
12
|
-
# Default options for attributes.
|
13
|
-
#
|
14
|
-
# @return [Hash<Symbol, Object>]
|
15
|
-
DEFAULT_OPTIONS = {
|
16
|
-
required: true,
|
17
|
-
ignore_nil: false,
|
18
|
-
ignore_blank: false,
|
19
|
-
ignore: false,
|
20
|
-
composite: false,
|
21
|
-
unwrapped: false,
|
22
|
-
prefix: nil,
|
23
|
-
access: %i[read write]
|
24
|
-
}.freeze
|
25
|
-
|
26
|
-
# Returns the default value for the attribute.
|
27
|
-
#
|
28
|
-
# If the default is callable, it is invoked.
|
29
|
-
#
|
30
|
-
# @return [Object]
|
31
|
-
def default
|
32
|
-
val = @default
|
33
|
-
val.respond_to?(:call) ? val.call : val
|
34
|
-
end
|
35
|
-
|
36
|
-
# Returns the serialization/deserialization key.
|
37
|
-
#
|
38
|
-
# Falls back to the field name if `:key` is not specified.
|
39
|
-
#
|
40
|
-
# @return [Symbol, String]
|
41
|
-
def key
|
42
|
-
options[:key] || field
|
43
|
-
end
|
44
|
-
|
45
|
-
# Returns the key path for accessing nested keys.
|
46
|
-
#
|
47
|
-
# Optionally includes alias key paths if `with_aliases` is true.
|
48
|
-
#
|
49
|
-
# @param with_aliases [Boolean]
|
50
|
-
# @return [Array<Array<Symbol>>] nested key paths
|
51
|
-
def key_path(with_aliases: false)
|
52
|
-
path = key.to_s.split(".").map(&:to_sym) || []
|
53
|
-
return path unless with_aliases
|
54
|
-
|
55
|
-
[path] + alias_paths
|
56
|
-
end
|
57
|
-
|
58
|
-
# Returns all alias key paths as arrays of symbols.
|
59
|
-
#
|
60
|
-
# @return [Array<Array<Symbol>>]
|
61
|
-
def alias_paths
|
62
|
-
options[:aliases].map { |a| a.to_s.split(".").map(&:to_sym) }
|
63
|
-
end
|
64
|
-
|
65
|
-
# Whether the attribute is required for object construction.
|
66
|
-
#
|
67
|
-
# @return [Boolean]
|
68
|
-
def required?
|
69
|
-
options[:required]
|
70
|
-
end
|
71
|
-
|
72
|
-
# Whether the attribute is optional.
|
73
|
-
#
|
74
|
-
# @return [Boolean]
|
75
|
-
def optional?
|
76
|
-
!required?
|
77
|
-
end
|
78
|
-
|
79
|
-
# Whether to ignore `nil` values during serialization.
|
80
|
-
#
|
81
|
-
# @return [Boolean]
|
82
|
-
def ignore_nil?
|
83
|
-
options[:ignore_nil]
|
84
|
-
end
|
85
|
-
|
86
|
-
# Whether to ignore blank values (`[]`, `{}`, empty strings) during serialization.
|
87
|
-
#
|
88
|
-
# @return [Boolean]
|
89
|
-
def ignore_blank?
|
90
|
-
options[:ignore_blank]
|
91
|
-
end
|
92
|
-
|
93
|
-
# Whether the attribute is a nested Castkit::DataObject.
|
94
|
-
#
|
95
|
-
# @return [Boolean]
|
96
|
-
def dataobject?
|
97
|
-
Castkit.dataobject?(type)
|
98
|
-
end
|
99
|
-
|
100
|
-
# Whether the attribute is a collection of Castkit::DataObjects.
|
101
|
-
#
|
102
|
-
# @return [Boolean]
|
103
|
-
def dataobject_collection?
|
104
|
-
type == :array && Castkit.dataobject?(options[:of])
|
105
|
-
end
|
106
|
-
|
107
|
-
# Whether the attribute is considered composite (not exposed in serialized output).
|
108
|
-
#
|
109
|
-
# @return [Boolean]
|
110
|
-
def composite?
|
111
|
-
options[:composite]
|
112
|
-
end
|
113
|
-
|
114
|
-
# Whether the attribute is unwrapped into the parent object.
|
115
|
-
#
|
116
|
-
# Only applies to Castkit::DataObject types.
|
117
|
-
#
|
118
|
-
# @return [Boolean]
|
119
|
-
def unwrapped?
|
120
|
-
dataobject? && options[:unwrapped]
|
121
|
-
end
|
122
|
-
|
123
|
-
# Returns the prefix used for unwrapped attributes.
|
124
|
-
#
|
125
|
-
# @return [String, nil]
|
126
|
-
def prefix
|
127
|
-
options[:prefix]
|
128
|
-
end
|
129
|
-
end
|
130
|
-
end
|
131
|
-
end
|
@@ -1,89 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Castkit
|
4
|
-
module AttributeExtensions
|
5
|
-
# Handles serialization (`dump`) and deserialization (`load`) of attribute values.
|
6
|
-
#
|
7
|
-
# Supports primitive types, arrays, and nested Castkit::DataObject instances.
|
8
|
-
module Serialization
|
9
|
-
# Serializes a value into a format suitable for output (e.g., JSON or Hash).
|
10
|
-
#
|
11
|
-
# If the value is a Castkit::DataObject, a custom serializer is used if configured.
|
12
|
-
#
|
13
|
-
# @param value [Object, nil] the value to serialize
|
14
|
-
# @param visited [Set, nil] used for circular reference detection
|
15
|
-
# @return [Object, nil] the serialized value
|
16
|
-
def dump(value, visited: nil)
|
17
|
-
return value if value.nil?
|
18
|
-
|
19
|
-
if type == :array
|
20
|
-
Array(value).map { |val| dump_element(val, visited: visited) }
|
21
|
-
else
|
22
|
-
dump_element(value, visited: visited)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
# Deserializes and validates a value during object instantiation.
|
27
|
-
#
|
28
|
-
# Applies default value, casts, and runs validators.
|
29
|
-
#
|
30
|
-
# @param value [Object] the input value
|
31
|
-
# @param context [Symbol] the attribute name or context key
|
32
|
-
# @return [Object] the deserialized and validated value
|
33
|
-
# @raise [Castkit::AttributeError] if value is required but missing
|
34
|
-
def load(value, context:)
|
35
|
-
value = default if value.nil?
|
36
|
-
return raise_error!("#{field} is required for instantiation") if value.nil? && required?
|
37
|
-
|
38
|
-
value = cast(value)
|
39
|
-
validate_value!(value, context: context)
|
40
|
-
|
41
|
-
value
|
42
|
-
end
|
43
|
-
|
44
|
-
private
|
45
|
-
|
46
|
-
# Serializes a single element value.
|
47
|
-
#
|
48
|
-
# - Uses a serializer if the value is a Castkit::DataObject.
|
49
|
-
# - Converts `to_h` if the value is hash-like.
|
50
|
-
#
|
51
|
-
# @param value [Object, nil] the element to dump
|
52
|
-
# @param visited [Set, nil]
|
53
|
-
# @return [Object, nil]
|
54
|
-
def dump_element(value, visited: nil)
|
55
|
-
return value if value.nil? || primitive?(value)
|
56
|
-
|
57
|
-
if value.is_a?(Castkit::DataObject)
|
58
|
-
serializer = options[:serializer] || value.class.serializer || Castkit::DefaultSerializer
|
59
|
-
serializer.call(value, visited: visited)
|
60
|
-
elsif hashable?(value)
|
61
|
-
value.to_h(visited)
|
62
|
-
else
|
63
|
-
value
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
# Checks whether a value is a hashable object suitable for `to_h` dumping.
|
68
|
-
#
|
69
|
-
# @param value [Object, nil]
|
70
|
-
# @return [Boolean]
|
71
|
-
def hashable?(value)
|
72
|
-
value.respond_to?(:to_h) && !primitive?(value) && !value.is_a?(Castkit::Attribute)
|
73
|
-
end
|
74
|
-
|
75
|
-
# Determines if a value is a primitive type.
|
76
|
-
#
|
77
|
-
# @param value [Object, nil]
|
78
|
-
# @return [Boolean]
|
79
|
-
def primitive?(value)
|
80
|
-
case value
|
81
|
-
when String, Symbol, Numeric, TrueClass, FalseClass, NilClass, Hash, Array
|
82
|
-
true
|
83
|
-
else
|
84
|
-
false
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|
@@ -1,72 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative "error_handling"
|
4
|
-
require_relative "options"
|
5
|
-
|
6
|
-
module Castkit
|
7
|
-
module AttributeExtensions
|
8
|
-
# Provides validation logic for attribute configuration.
|
9
|
-
#
|
10
|
-
# These checks are typically performed at attribute initialization to catch misconfigurations early.
|
11
|
-
module Validation
|
12
|
-
include Castkit::AttributeExtensions::ErrorHandling
|
13
|
-
|
14
|
-
private
|
15
|
-
|
16
|
-
# Runs all validation checks on the attribute definition.
|
17
|
-
#
|
18
|
-
# This includes:
|
19
|
-
# - Custom validator integrity
|
20
|
-
# - Access mode validity
|
21
|
-
# - Unwrapped prefix usage
|
22
|
-
# - Array `of:` type presence
|
23
|
-
#
|
24
|
-
# @return [void]
|
25
|
-
def validate!
|
26
|
-
validate_custom_validator!
|
27
|
-
validate_access!
|
28
|
-
validate_unwrapped_options!
|
29
|
-
validate_array_options!
|
30
|
-
end
|
31
|
-
|
32
|
-
# Validates the presence and interface of a custom validator.
|
33
|
-
#
|
34
|
-
# @return [void]
|
35
|
-
# @raise [Castkit::AttributeError] if the validator is not callable
|
36
|
-
def validate_custom_validator!
|
37
|
-
return unless options[:validator]
|
38
|
-
return if options[:validator].respond_to?(:call)
|
39
|
-
|
40
|
-
raise_error!("Custom validator for `#{field}` must respond to `.call`")
|
41
|
-
end
|
42
|
-
|
43
|
-
# Validates that each declared access mode is valid.
|
44
|
-
#
|
45
|
-
# @return [void]
|
46
|
-
# @raise [Castkit::AttributeError] if any access mode is invalid and enforcement is enabled
|
47
|
-
def validate_access!
|
48
|
-
access.each do |mode|
|
49
|
-
next if Castkit::AttributeExtensions::Options::DEFAULT_OPTIONS[:access].include?(mode)
|
50
|
-
|
51
|
-
handle_error(:access, mode: mode, context: to_h)
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
# Ensures prefix is only used with unwrapped attributes.
|
56
|
-
#
|
57
|
-
# @return [void]
|
58
|
-
# @raise [Castkit::AttributeError] if prefix is used without `unwrapped: true`
|
59
|
-
def validate_unwrapped_options!
|
60
|
-
handle_error(:unwrapped, context: to_h) if prefix && !unwrapped?
|
61
|
-
end
|
62
|
-
|
63
|
-
# Ensures `of:` is provided for array-typed attributes.
|
64
|
-
#
|
65
|
-
# @return [void]
|
66
|
-
# @raise [Castkit::AttributeError] if `of:` is missing for `type: :array`
|
67
|
-
def validate_array_options!
|
68
|
-
handle_error(:array_options, context: to_h) if type == :array && options[:of].nil?
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|