castkit 0.1.1 → 0.2.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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec_status +196 -210
  3. data/CHANGELOG.md +71 -0
  4. data/README.md +470 -85
  5. data/lib/castkit/attribute.rb +6 -24
  6. data/lib/castkit/castkit.rb +65 -5
  7. data/lib/castkit/configuration.rb +98 -46
  8. data/lib/castkit/contract/data_object.rb +62 -0
  9. data/lib/castkit/contract/generic.rb +168 -0
  10. data/lib/castkit/contract/result.rb +74 -0
  11. data/lib/castkit/contract/validator.rb +248 -0
  12. data/lib/castkit/contract.rb +67 -0
  13. data/lib/castkit/{data_object_extensions → core}/attribute_types.rb +21 -7
  14. data/lib/castkit/{data_object_extensions → core}/attributes.rb +8 -3
  15. data/lib/castkit/core/config.rb +74 -0
  16. data/lib/castkit/core/registerable.rb +59 -0
  17. data/lib/castkit/data_object.rb +59 -43
  18. data/lib/castkit/default_serializer.rb +87 -32
  19. data/lib/castkit/error.rb +15 -3
  20. data/lib/castkit/ext/attribute/access.rb +67 -0
  21. data/lib/castkit/ext/attribute/error_handling.rb +63 -0
  22. data/lib/castkit/ext/attribute/options.rb +142 -0
  23. data/lib/castkit/ext/attribute/validation.rb +85 -0
  24. data/lib/castkit/ext/data_object/contract.rb +96 -0
  25. data/lib/castkit/ext/data_object/deserialization.rb +167 -0
  26. data/lib/castkit/ext/data_object/serialization.rb +61 -0
  27. data/lib/castkit/inflector.rb +47 -0
  28. data/lib/castkit/types/boolean.rb +43 -0
  29. data/lib/castkit/types/collection.rb +24 -0
  30. data/lib/castkit/types/date.rb +34 -0
  31. data/lib/castkit/types/date_time.rb +34 -0
  32. data/lib/castkit/types/float.rb +46 -0
  33. data/lib/castkit/types/generic.rb +123 -0
  34. data/lib/castkit/types/integer.rb +46 -0
  35. data/lib/castkit/types/string.rb +44 -0
  36. data/lib/castkit/types.rb +15 -0
  37. data/lib/castkit/validators/base_validator.rb +39 -0
  38. data/lib/castkit/validators/numeric_validator.rb +2 -2
  39. data/lib/castkit/validators/string_validator.rb +3 -3
  40. data/lib/castkit/version.rb +1 -1
  41. data/lib/castkit.rb +2 -0
  42. metadata +29 -14
  43. data/castkit-0.1.0.gem +0 -0
  44. data/lib/castkit/attribute_extensions/access.rb +0 -65
  45. data/lib/castkit/attribute_extensions/casting.rb +0 -147
  46. data/lib/castkit/attribute_extensions/error_handling.rb +0 -83
  47. data/lib/castkit/attribute_extensions/options.rb +0 -131
  48. data/lib/castkit/attribute_extensions/serialization.rb +0 -89
  49. data/lib/castkit/attribute_extensions/validation.rb +0 -72
  50. data/lib/castkit/data_object_extensions/config.rb +0 -105
  51. data/lib/castkit/data_object_extensions/deserialization.rb +0 -110
  52. data/lib/castkit/validators.rb +0 -4
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Castkit
4
+ module Validators
5
+ # Abstract base class for all attribute validators.
6
+ #
7
+ # Validators ensure that a value conforms to specific rules (e.g., type, format, range).
8
+ # Subclasses must implement the instance method `#call`.
9
+ #
10
+ # @abstract
11
+ class BaseValidator
12
+ class << self
13
+ # Invokes the validator with the given arguments.
14
+ #
15
+ # @param value [Object] the attribute value to validate
16
+ # @param options [Hash] the attribute options (e.g., `min`, `max`, `format`)
17
+ # @param context [Symbol, String, Hash] the attribute name or context for error reporting
18
+ # @return [void]
19
+ # @raise [Castkit::AttributeError] if validation fails
20
+ def call(value, options:, context:)
21
+ new.call(value, options: options, context: context)
22
+ end
23
+ end
24
+
25
+ # Validates the attribute value.
26
+ #
27
+ # @abstract Override in subclasses.
28
+ #
29
+ # @param value [Object] the attribute value to validate
30
+ # @param options [Hash] the attribute options
31
+ # @param context [Symbol, String, Hash] the attribute name or context
32
+ # @return [void]
33
+ # @raise [NotImplementedError] unless implemented in a subclass
34
+ def call(value, options:, context:)
35
+ raise NotImplementedError, "#{self.class.name} must implement `#call`"
36
+ end
37
+ end
38
+ end
39
+ end
@@ -1,13 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../validator"
3
+ require_relative "base_validator"
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::Validator
10
+ class NumericValidator < Castkit::Validators::BaseValidator
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 "../validator"
3
+ require_relative "base_validator"
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::Validator
10
+ class StringValidator < Castkit::Validators::BaseValidator
11
11
  # Validates the string value.
12
12
  #
13
13
  # @param value [Object] the value to validate
@@ -16,7 +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
- raise Castkit::AttributeError, "#{context} must be a String" unless value.is_a?(String)
19
+ raise Castkit::AttributeError, "#{context} must be a string" unless value.is_a?(String)
20
20
 
21
21
  return unless options[:format]
22
22
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Castkit
4
- VERSION = "0.1.1"
4
+ VERSION = "0.2.0"
5
5
  end
data/lib/castkit.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "castkit/version"
4
+ require_relative "castkit/attribute"
5
+ require_relative "castkit/contract"
4
6
  require_relative "castkit/data_object"
5
7
 
6
8
  # Castkit is a lightweight, type-safe data object system for Ruby.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: castkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.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-13 00:00:00.000000000 Z
11
+ date: 2025-04-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -69,28 +69,43 @@ files:
69
69
  - LICENSE.txt
70
70
  - README.md
71
71
  - Rakefile
72
- - castkit-0.1.0.gem
73
72
  - castkit.gemspec
74
73
  - lib/castkit.rb
75
74
  - lib/castkit/attribute.rb
76
- - lib/castkit/attribute_extensions/access.rb
77
- - lib/castkit/attribute_extensions/casting.rb
78
- - lib/castkit/attribute_extensions/error_handling.rb
79
- - lib/castkit/attribute_extensions/options.rb
80
- - lib/castkit/attribute_extensions/serialization.rb
81
- - lib/castkit/attribute_extensions/validation.rb
82
75
  - lib/castkit/castkit.rb
83
76
  - lib/castkit/configuration.rb
77
+ - lib/castkit/contract.rb
78
+ - lib/castkit/contract/data_object.rb
79
+ - lib/castkit/contract/generic.rb
80
+ - lib/castkit/contract/result.rb
81
+ - lib/castkit/contract/validator.rb
82
+ - lib/castkit/core/attribute_types.rb
83
+ - lib/castkit/core/attributes.rb
84
+ - lib/castkit/core/config.rb
85
+ - lib/castkit/core/registerable.rb
84
86
  - lib/castkit/data_object.rb
85
- - lib/castkit/data_object_extensions/attribute_types.rb
86
- - lib/castkit/data_object_extensions/attributes.rb
87
- - lib/castkit/data_object_extensions/config.rb
88
- - lib/castkit/data_object_extensions/deserialization.rb
89
87
  - lib/castkit/default_serializer.rb
90
88
  - lib/castkit/error.rb
89
+ - lib/castkit/ext/attribute/access.rb
90
+ - lib/castkit/ext/attribute/error_handling.rb
91
+ - lib/castkit/ext/attribute/options.rb
92
+ - lib/castkit/ext/attribute/validation.rb
93
+ - lib/castkit/ext/data_object/contract.rb
94
+ - lib/castkit/ext/data_object/deserialization.rb
95
+ - lib/castkit/ext/data_object/serialization.rb
96
+ - lib/castkit/inflector.rb
91
97
  - lib/castkit/serializer.rb
98
+ - lib/castkit/types.rb
99
+ - lib/castkit/types/boolean.rb
100
+ - lib/castkit/types/collection.rb
101
+ - lib/castkit/types/date.rb
102
+ - lib/castkit/types/date_time.rb
103
+ - lib/castkit/types/float.rb
104
+ - lib/castkit/types/generic.rb
105
+ - lib/castkit/types/integer.rb
106
+ - lib/castkit/types/string.rb
92
107
  - lib/castkit/validator.rb
93
- - lib/castkit/validators.rb
108
+ - lib/castkit/validators/base_validator.rb
94
109
  - lib/castkit/validators/numeric_validator.rb
95
110
  - lib/castkit/validators/string_validator.rb
96
111
  - lib/castkit/version.rb
data/castkit-0.1.0.gem DELETED
Binary file
@@ -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
- warn "[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] the value to serialize
14
- # @param visited [Set, nil] used for circular reference detection
15
- # @return [Object] 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] the element to dump
52
- # @param visited [Set, nil]
53
- # @return [Object]
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]
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]
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