castkit 0.2.0 → 0.3.1

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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -1
  3. data/README.md +297 -13
  4. data/castkit.gemspec +3 -0
  5. data/lib/castkit/attribute.rb +82 -59
  6. data/lib/castkit/attributes/definition.rb +64 -0
  7. data/lib/castkit/attributes/options.rb +214 -0
  8. data/lib/castkit/castkit.rb +18 -5
  9. data/lib/castkit/cli/generate.rb +112 -0
  10. data/lib/castkit/cli/list.rb +200 -0
  11. data/lib/castkit/cli/main.rb +43 -0
  12. data/lib/castkit/cli.rb +24 -0
  13. data/lib/castkit/configuration.rb +31 -8
  14. data/lib/castkit/contract/{generic.rb → base.rb} +5 -17
  15. data/lib/castkit/contract/result.rb +2 -2
  16. data/lib/castkit/contract/validator.rb +5 -1
  17. data/lib/castkit/contract.rb +5 -5
  18. data/lib/castkit/core/attributes.rb +87 -44
  19. data/lib/castkit/data_object.rb +11 -30
  20. data/lib/castkit/{ext → dsl}/attribute/access.rb +1 -1
  21. data/lib/castkit/{ext → dsl}/attribute/error_handling.rb +1 -1
  22. data/lib/castkit/{ext → dsl}/attribute/options.rb +1 -1
  23. data/lib/castkit/{ext → dsl}/attribute/validation.rb +3 -3
  24. data/lib/castkit/dsl/attribute.rb +47 -0
  25. data/lib/castkit/{ext → dsl}/data_object/contract.rb +2 -2
  26. data/lib/castkit/{ext → dsl}/data_object/deserialization.rb +6 -2
  27. data/lib/castkit/dsl/data_object/plugins.rb +86 -0
  28. data/lib/castkit/{ext → dsl}/data_object/serialization.rb +1 -1
  29. data/lib/castkit/dsl/data_object.rb +61 -0
  30. data/lib/castkit/inflector.rb +1 -1
  31. data/lib/castkit/plugins.rb +82 -0
  32. data/lib/castkit/serializers/base.rb +94 -0
  33. data/lib/castkit/serializers/default_serializer.rb +156 -0
  34. data/lib/castkit/types/{generic.rb → base.rb} +30 -10
  35. data/lib/castkit/types/boolean.rb +14 -10
  36. data/lib/castkit/types/collection.rb +13 -2
  37. data/lib/castkit/types/date.rb +2 -2
  38. data/lib/castkit/types/date_time.rb +2 -2
  39. data/lib/castkit/types/float.rb +5 -5
  40. data/lib/castkit/types/integer.rb +5 -5
  41. data/lib/castkit/types/string.rb +2 -2
  42. data/lib/castkit/types.rb +1 -1
  43. data/lib/castkit/validators/base.rb +59 -0
  44. data/lib/castkit/validators/boolean_validator.rb +39 -0
  45. data/lib/castkit/validators/collection_validator.rb +29 -0
  46. data/lib/castkit/validators/float_validator.rb +31 -0
  47. data/lib/castkit/validators/integer_validator.rb +31 -0
  48. data/lib/castkit/validators/numeric_validator.rb +2 -2
  49. data/lib/castkit/validators/string_validator.rb +3 -4
  50. data/lib/castkit/version.rb +1 -1
  51. data/lib/castkit.rb +1 -4
  52. data/lib/generators/attribute.rb +39 -0
  53. data/lib/generators/base.rb +97 -0
  54. data/lib/generators/contract.rb +68 -0
  55. data/lib/generators/data_object.rb +48 -0
  56. data/lib/generators/plugin.rb +25 -0
  57. data/lib/generators/serializer.rb +28 -0
  58. data/lib/generators/templates/attribute.rb.tt +21 -0
  59. data/lib/generators/templates/attribute_spec.rb.tt +41 -0
  60. data/lib/generators/templates/contract.rb.tt +26 -0
  61. data/lib/generators/templates/contract_spec.rb.tt +76 -0
  62. data/lib/generators/templates/data_object.rb.tt +17 -0
  63. data/lib/generators/templates/data_object_spec.rb.tt +36 -0
  64. data/lib/generators/templates/plugin.rb.tt +37 -0
  65. data/lib/generators/templates/plugin_spec.rb.tt +18 -0
  66. data/lib/generators/templates/serializer.rb.tt +24 -0
  67. data/lib/generators/templates/serializer_spec.rb.tt +14 -0
  68. data/lib/generators/templates/type.rb.tt +57 -0
  69. data/lib/generators/templates/type_spec.rb.tt +42 -0
  70. data/lib/generators/templates/validator.rb.tt +26 -0
  71. data/lib/generators/templates/validator_spec.rb.tt +23 -0
  72. data/lib/generators/type.rb +29 -0
  73. data/lib/generators/validator.rb +41 -0
  74. metadata +92 -16
  75. data/.rspec_status +0 -196
  76. data/lib/castkit/core/registerable.rb +0 -59
  77. data/lib/castkit/default_serializer.rb +0 -154
  78. data/lib/castkit/serializer.rb +0 -92
  79. data/lib/castkit/validators/base_validator.rb +0 -39
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+ require_relative "cli/main"
5
+
6
+ module Castkit
7
+ # Entrypoint for Castkit’s command-line interface.
8
+ #
9
+ # Delegates to the `Castkit::CLI::Main` Thor class, which defines all CLI commands.
10
+ #
11
+ # @example Executing from a binstub
12
+ # Castkit::CLI.start(ARGV)
13
+ #
14
+ module CLI
15
+ # Starts the Castkit CLI.
16
+ #
17
+ # @param args [Array<String>] the command-line arguments
18
+ # @param kwargs [Hash] additional keyword arguments passed to Thor
19
+ # @return [void]
20
+ def self.start(*args, **kwargs)
21
+ Castkit::CLI::Main.start(*args, **kwargs)
22
+ end
23
+ end
24
+ end
@@ -10,14 +10,14 @@ module Castkit
10
10
  class Configuration
11
11
  # Default mapping of primitive type definitions.
12
12
  #
13
- # @return [Hash{Symbol => Castkit::Types::Generic}]
13
+ # @return [Hash{Symbol => Castkit::Types::Base}]
14
14
  DEFAULT_TYPES = {
15
15
  array: Castkit::Types::Collection.new,
16
16
  boolean: Castkit::Types::Boolean.new,
17
17
  date: Castkit::Types::Date.new,
18
18
  datetime: Castkit::Types::DateTime.new,
19
19
  float: Castkit::Types::Float.new,
20
- hash: Castkit::Types::Generic.new,
20
+ hash: Castkit::Types::Base.new,
21
21
  integer: Castkit::Types::Integer.new,
22
22
  string: Castkit::Types::String.new
23
23
  }.freeze
@@ -36,9 +36,15 @@ module Castkit
36
36
  uuid: :string
37
37
  }.freeze
38
38
 
39
- # @return [Hash{Symbol => Castkit::Types::Generic}] registered types
39
+ # @return [Hash{Symbol => Castkit::Types::Base}] registered types
40
40
  attr_reader :types
41
41
 
42
+ # Set default plugins that will be used globally in all Castkit::DataObject subclasses.
43
+ # This is equivalent to calling `enable_plugins` in every class.
44
+ #
45
+ # @return [Array<Symbol>] default plugin names to be applied to all DataObject subclasses
46
+ attr_accessor :default_plugins
47
+
42
48
  # Whether to raise an error if values should be validated before deserializing, e.g. true -> "true"
43
49
  # @return [Boolean]
44
50
  attr_accessor :enforce_typing
@@ -79,6 +85,7 @@ module Castkit
79
85
  @raise_type_errors = true
80
86
  @enable_warnings = true
81
87
  @strict_by_default = true
88
+ @default_plugins = []
82
89
 
83
90
  apply_type_aliases!
84
91
  end
@@ -86,17 +93,17 @@ module Castkit
86
93
  # Registers a new type definition.
87
94
  #
88
95
  # @param type [Symbol] the symbolic type name (e.g., :uuid)
89
- # @param klass [Class<Castkit::Types::Generic>] the class to register
96
+ # @param klass [Class<Castkit::Types::Base>] the class to register
90
97
  # @param override [Boolean] whether to allow overwriting existing registration
91
- # @raise [Castkit::TypeError] if the type class is invalid or not a subclass of Generic
98
+ # @raise [Castkit::TypeError] if the type class is invalid or not a subclass of Castkit::Types::Base
92
99
  # @return [void]
93
100
  def register_type(type, klass, aliases: [], override: false)
94
101
  type = type.to_sym
95
102
  return if types.key?(type) && !override
96
103
 
97
104
  instance = klass.new
98
- unless instance.is_a?(Castkit::Types::Generic)
99
- raise Castkit::TypeError, "Expected subclass of Castkit::Types::Generic for `#{type}`"
105
+ unless instance.is_a?(Castkit::Types::Base)
106
+ raise Castkit::TypeError, "Expected subclass of Castkit::Types::Base for `#{type}`"
100
107
  end
101
108
 
102
109
  types[type] = instance
@@ -107,10 +114,26 @@ module Castkit
107
114
  aliases.each { |alias_type| register_type(alias_type, klass, override: override) }
108
115
  end
109
116
 
117
+ # Register a custom plugin for use with Castkit::DataObject.
118
+ #
119
+ # @example Loading as a default plugin
120
+ # Castkit.configure do |config|
121
+ # config.register_plugin(:custom, CustomPlugin)
122
+ # config.default_plugins [:custom]
123
+ # end
124
+ #
125
+ # @example Loading it directly in a Castkit::DataObject
126
+ # class UserDto < Castkit::DataObject
127
+ # enable_plugins :custom
128
+ # end
129
+ def register_plugin(name, plugin)
130
+ Castkit::Plugins.register(name, plugin)
131
+ end
132
+
110
133
  # Returns the type handler for a given type symbol.
111
134
  #
112
135
  # @param type [Symbol]
113
- # @return [Castkit::Types::Generic]
136
+ # @return [Castkit::Types::Base]
114
137
  # @raise [Castkit::TypeError] if the type is not registered
115
138
  def fetch_type(type)
116
139
  @types.fetch(type.to_sym) do
@@ -2,7 +2,6 @@
2
2
 
3
3
  require_relative "../core/config"
4
4
  require_relative "../core/attribute_types"
5
- require_relative "../core/registerable"
6
5
  require_relative "result"
7
6
 
8
7
  module Castkit
@@ -14,7 +13,7 @@ module Castkit
14
13
  # ephemeral or reusable contract classes.
15
14
  #
16
15
  # @example Subclassing directly
17
- # class MyContract < Castkit::Contract::Generic
16
+ # class MyContract < Castkit::Contract::Base
18
17
  # string :id
19
18
  # integer :count, required: false
20
19
  # end
@@ -30,26 +29,15 @@ module Castkit
30
29
  # UserContract.validate!(id: "123")
31
30
  #
32
31
  # @see Castkit::Contract.build
33
- class Generic
32
+ class Base
34
33
  extend Castkit::Core::Config
35
34
  extend Castkit::Core::AttributeTypes
36
- extend Castkit::Core::Registerable
37
35
 
38
36
  ATTRIBUTE_OPTIONS = %i[
39
37
  required aliases min max format of validator unwrapped prefix force_type
40
38
  ].freeze
41
39
 
42
40
  class << self
43
- # Registers the current class under `Castkit::Contracts`.
44
- #
45
- # @param as [String, Symbol, nil] The constant name to use (PascalCase). Defaults to the name used when building
46
- # the contract. If no name was provided, an error is raised.
47
- # @return [Class] the registered contract class
48
- # @raise [Castkit::Error] If a name cannot be resolved.
49
- def register!(as: nil)
50
- super(namespace: :contracts, as: as || definition[:name])
51
- end
52
-
53
41
  # Defines an attribute for the contract.
54
42
  #
55
43
  # Only a subset of options is allowed inside a contract.
@@ -69,7 +57,6 @@ module Castkit
69
57
  # @return [Castkit::Contract::Result]
70
58
  def validate(input)
71
59
  validate!(input)
72
- Castkit::Contract::Result.new(definition[:name].to_s, input)
73
60
  rescue Castkit::ContractError => e
74
61
  Castkit::Contract::Result.new(definition[:name].to_s, input, errors: e.errors)
75
62
  end
@@ -81,6 +68,7 @@ module Castkit
81
68
  # @return [void]
82
69
  def validate!(input)
83
70
  Castkit::Contract::Validator.call!(attributes.values, input, **validation_rules)
71
+ Castkit::Contract::Result.new(definition[:name].to_s, input)
84
72
  end
85
73
 
86
74
  # Returns internal contract metadata.
@@ -88,7 +76,7 @@ module Castkit
88
76
  # @return [Hash]
89
77
  def definition
90
78
  @definition ||= {
91
- name: :ephemeral_contract,
79
+ name: :ephemeral,
92
80
  attributes: {}
93
81
  }
94
82
  end
@@ -108,7 +96,7 @@ module Castkit
108
96
  # @param source [Castkit::DataObject, nil]
109
97
  # @param block [Proc, nil]
110
98
  # @return [Hash]
111
- def define(name = :ephemeral_contract, source = nil, validation_rules: {}, &block)
99
+ def define(name = :ephemeral, source = nil, validation_rules: {}, &block)
112
100
  validate_definition!(source, &block)
113
101
 
114
102
  if source
@@ -52,10 +52,10 @@ module Castkit
52
52
  #
53
53
  # @return [String]
54
54
  def to_s
55
- return "[Castkit] Contract validation passed for #{contract.inspect}" if success?
55
+ return "[Castkit] Contract validation passed for #{contract}" if success?
56
56
 
57
57
  parsed_errors = errors.map { |k, v| " #{k}: #{v.inspect}" }.join("\n")
58
- "[Castkit] Contract validation failed for #{contract.inspect}:\n#{parsed_errors}"
58
+ "[Castkit] Contract validation failed for #{contract}:\n#{parsed_errors}"
59
59
  end
60
60
 
61
61
  # @return [Hash{Symbol => Object}] the input validation and error hash
@@ -188,7 +188,11 @@ module Castkit
188
188
  # @return [Object, nil]
189
189
  def resolve_input_value(input, attribute)
190
190
  attribute.key_path(with_aliases: true).each do |path|
191
- value = path.reduce(input) { |memo, key| memo.is_a?(Hash) ? memo[key] : nil }
191
+ value = path.reduce(input) do |memo, key|
192
+ next memo unless memo.is_a?(Hash)
193
+
194
+ memo.key?(key) ? memo[key] : memo[key.to_s]
195
+ end
192
196
  return value unless value.nil?
193
197
  end
194
198
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "contract/generic"
3
+ require_relative "contract/base"
4
4
 
5
5
  module Castkit
6
6
  # Castkit::Contract provides a lightweight mechanism for defining and validating
@@ -31,9 +31,9 @@ module Castkit
31
31
  # @param name [String, Symbol, nil] Optional name for the contract.
32
32
  # @param validation_rules [Hash] Optional validation rules (e.g., `strict: true`).
33
33
  # @yield Optional DSL block to define attributes.
34
- # @return [Class<Castkit::Contract::Generic>]
34
+ # @return [Class<Castkit::Contract::Base>]
35
35
  def build(name = nil, **validation_rules, &block)
36
- klass = Class.new(Castkit::Contract::Generic)
36
+ klass = Class.new(Castkit::Contract::Base)
37
37
  klass.send(:define, name, nil, validation_rules: validation_rules, &block)
38
38
 
39
39
  klass
@@ -52,12 +52,12 @@ module Castkit
52
52
  #
53
53
  # @param source [Class<Castkit::DataObject>] the DataObject to generate the contract from
54
54
  # @param as [String, Symbol, nil] Optional custom name to use for the contract
55
- # @return [Class<Castkit::Contract::Generic>]
55
+ # @return [Class<Castkit::Contract::Base>]
56
56
  def from_dataobject(source, as: nil)
57
57
  name = as || Castkit::Inflector.unqualified_name(source)
58
58
  name = Castkit::Inflector.underscore(name).to_sym
59
59
 
60
- klass = Class.new(Castkit::Contract::Generic)
60
+ klass = Class.new(Castkit::Contract::Base)
61
61
  klass.send(:define, name, source, validation_rules: source.validation_rules)
62
62
 
63
63
  klass
@@ -4,45 +4,51 @@ module Castkit
4
4
  module Core
5
5
  # Provides DSL and implementation for declaring attributes within a Castkit::DataObject.
6
6
  #
7
- # Includes support for regular, composite, transient, readonly/writeonly, and grouped attribute definitions.
7
+ # Supports reusable attribute definitions, transient fields, composite readers, and
8
+ # grouped declarations such as `readonly`, `optional`, and `transient` blocks.
9
+ #
10
+ # This module is included into `Castkit::DataObject` and handles attribute registration,
11
+ # accessor generation, and typed writing behavior.
8
12
  module Attributes
9
- # Declares an attribute with the given type and options.
13
+ # Declares an attribute on the data object.
10
14
  #
15
+ # Accepts either inline options or a reusable attribute definition (`using` or `definition`).
11
16
  # If `:transient` is true, defines only standard accessors and skips serialization logic.
12
17
  #
13
- # @param field [Symbol]
14
- # @param type [Symbol, Class]
15
- # @param options [Hash]
18
+ # @param field [Symbol] the attribute name
19
+ # @param type [Symbol, Class] the attribute's declared type
20
+ # @param definition [Hash, nil] an optional pre-built definition object (`{ type:, options: }`)
21
+ # @param using [Castkit::Attributes::Base, nil] an optional class-based definition (`.definition`)
22
+ # @param options [Hash] additional options like `default`, `access`, `required`, etc.
16
23
  # @return [void]
17
- # @raise [Castkit::DataObjectError] if the attribute is already defined
18
- def attribute(field, type, **options)
24
+ # @raise [Castkit::DataObjectError] if attribute already defined or type mismatch
25
+ def attribute(field, type = nil, definition = nil, using: nil, **options)
19
26
  field = field.to_sym
20
27
  raise Castkit::DataObjectError, "Attribute '#{field}' already defined" if attributes.key?(field)
21
28
 
22
- options = build_options(options)
29
+ type, options = use_definition(field, definition || using&.definition, type, options)
23
30
  return define_attribute(field, type, **options) unless options[:transient]
24
31
 
25
32
  attr_accessor field
26
33
  end
27
34
 
28
- # Declares a computed (composite) attribute.
35
+ # Declares a composite (computed) attribute.
29
36
  #
30
- # The provided block defines the read behavior.
31
- #
32
- # @param field [Symbol]
33
- # @param type [Symbol, Class]
34
- # @param options [Hash]
35
- # @yieldreturn [Object] evaluated composite value
37
+ # @param field [Symbol] the name of the attribute
38
+ # @param type [Symbol, Class] the attribute type
39
+ # @param options [Hash] additional attribute options
40
+ # @yieldreturn [Object] the value to return when the reader is called
41
+ # @return [void]
36
42
  def composite(field, type, **options, &block)
37
43
  attribute(field, type, **options, composite: true)
38
44
  define_method(field, &block)
39
45
  end
40
46
 
41
- # Declares a group of transient attributes within the given block.
47
+ # Declares a group of transient attributes within a block.
42
48
  #
43
- # These attributes are not serialized or included in `to_h`.
49
+ # These attributes are excluded from serialization (`to_h`) and not stored.
44
50
  #
45
- # @yield defines one or more transient attributes via `attribute`
51
+ # @yield a block containing `attribute` calls
46
52
  # @return [void]
47
53
  def transient(&block)
48
54
  @__transient_context = true
@@ -51,61 +57,97 @@ module Castkit
51
57
  @__transient_context = nil
52
58
  end
53
59
 
54
- # Declares a group of readonly attributes within the given block.
60
+ # Declares a group of readonly attributes (accessible for read only).
55
61
  #
56
- # @param options [Hash] shared options for attributes inside the block
57
- # @yield defines attributes with `access: [:read]`
62
+ # @param options [Hash] shared options for all attributes inside the block
63
+ # @yield a block containing `attribute` calls
58
64
  # @return [void]
59
65
  def readonly(**options, &block)
60
66
  with_access([:read], options, &block)
61
67
  end
62
68
 
63
- # Declares a group of writeonly attributes within the given block.
69
+ # Declares a group of writeonly attributes (accessible for write only).
64
70
  #
65
- # @param options [Hash] shared options for attributes inside the block
66
- # @yield defines attributes with `access: [:write]`
71
+ # @param options [Hash] shared options for all attributes inside the block
72
+ # @yield a block containing `attribute` calls
67
73
  # @return [void]
68
74
  def writeonly(**options, &block)
69
75
  with_access([:write], options, &block)
70
76
  end
71
77
 
72
- # Declares a group of required attributes within the given block.
78
+ # Declares a group of required attributes.
73
79
  #
74
- # @param options [Hash] shared options for attributes inside the block
75
- # @yield defines attributes with `required: true`
80
+ # @param options [Hash] shared options for all attributes inside the block
81
+ # @yield a block containing `attribute` calls
76
82
  # @return [void]
77
83
  def required(**options, &block)
78
84
  with_required(true, options, &block)
79
85
  end
80
86
 
81
- # Declares a group of optional attributes within the given block.
87
+ # Declares a group of optional attributes.
82
88
  #
83
- # @param options [Hash] shared options for attributes inside the block
84
- # @yield defines attributes with `required: false`
89
+ # @param options [Hash] shared options for all attributes inside the block
90
+ # @yield a block containing `attribute` calls
85
91
  # @return [void]
86
92
  def optional(**options, &block)
87
93
  with_required(false, options, &block)
88
94
  end
89
95
 
90
- # Returns all declared non-transient attributes.
96
+ # Returns all non-transient attributes defined on the class.
91
97
  #
92
98
  # @return [Hash{Symbol => Castkit::Attribute}]
93
99
  def attributes
94
100
  @attributes ||= {}
95
101
  end
96
102
 
97
- # Alias for `composite`
103
+ def inherited(subclass)
104
+ super
105
+
106
+ parent_attributes = instance_variable_get(:@attributes)
107
+ subclass.instance_variable_set(:@attributes, parent_attributes.dup) if parent_attributes
108
+ end
109
+
110
+ # Alias for {#attribute}
111
+ #
112
+ # @see #attribute
113
+ alias attr attribute
114
+
115
+ # Alias for {#composite}
98
116
  #
99
117
  # @see #composite
100
118
  alias property composite
101
119
 
102
120
  private
103
121
 
104
- # Defines a full attribute, including accessor methods and type logic.
122
+ # Applies a reusable definition to the current attribute call.
123
+ #
124
+ # Ensures the declared type matches and merges options.
125
+ #
126
+ # @param field [Symbol] the attribute name
127
+ # @param definition [Hash{Symbol => Object}, nil]
128
+ # @param type [Symbol, Class]
129
+ # @param options [Hash]
130
+ # @return [Array<(Symbol, Hash)>] the final type and options
131
+ # @raise [Castkit::DataObjectError] if type mismatch occurs
132
+ def use_definition(field, definition, type, options)
133
+ type ||= definition&.fetch(:type, nil)
134
+ raise Castkit::AttributeError, "Attribute `#{field} has no type" if type.nil?
135
+
136
+ if definition && type != definition[:type]
137
+ raise Castkit::AttributeError,
138
+ "Attribute `#{field}` type mismatch: expected #{definition[:type].inspect}, got #{type.inspect}"
139
+ end
140
+
141
+ options = definition[:options].merge(options) if definition
142
+ [type, build_options(options)]
143
+ end
144
+
145
+ # Instantiates and stores a Castkit::Attribute, defining accessors as needed.
105
146
  #
106
147
  # @param field [Symbol]
107
148
  # @param type [Symbol, Class]
108
149
  # @param options [Hash]
150
+ # @return [void]
109
151
  def define_attribute(field, type, **options)
110
152
  attribute = Castkit::Attribute.new(field, type, **options)
111
153
  attributes[field] = attribute
@@ -121,13 +163,14 @@ module Castkit
121
163
  end
122
164
  end
123
165
 
124
- # Defines a type-aware writer method for the attribute.
166
+ # Defines a writer method that enforces type coercion.
125
167
  #
126
168
  # @param field [Symbol]
127
169
  # @param attribute [Castkit::Attribute]
170
+ # @return [void]
128
171
  def define_typed_writer(field, attribute)
129
172
  define_method("#{field}=") do |value|
130
- deserialized_value = Castkit.type_caster(attribute.type).call(
173
+ deserialized_value = Castkit.type_caster(attribute.type.to_sym).call(
131
174
  value,
132
175
  options: attribute.options,
133
176
  context: attribute.field
@@ -137,11 +180,12 @@ module Castkit
137
180
  end
138
181
  end
139
182
 
140
- # Applies scoped access control to all attributes declared in the given block.
183
+ # Applies a temporary `access` context to all attributes within a block.
141
184
  #
142
- # @param access [Array<Symbol>] e.g., [:read] or [:write]
185
+ # @param access [Array<Symbol>] e.g. `[:read]` or `[:write]`
143
186
  # @param options [Hash]
144
- # @yield the block containing one or more `attribute` calls
187
+ # @yield the block containing `attribute` calls
188
+ # @return [void]
145
189
  def with_access(access, options = {}, &block)
146
190
  @__access_context = access
147
191
  @__block_options = options
@@ -151,11 +195,12 @@ module Castkit
151
195
  @__block_options = nil
152
196
  end
153
197
 
154
- # Applies scoped required/optional flag to all attributes declared in the given block.
198
+ # Applies a temporary `required` context to all attributes within a block.
155
199
  #
156
200
  # @param flag [Boolean]
157
201
  # @param options [Hash]
158
- # @yield the block containing one or more `attribute` calls
202
+ # @yield the block containing `attribute` calls
203
+ # @return [void]
159
204
  def with_required(flag, options = {}, &block)
160
205
  @__required_context = flag
161
206
  @__block_options = options
@@ -165,12 +210,10 @@ module Castkit
165
210
  @__block_options = nil
166
211
  end
167
212
 
168
- # Builds effective options for the current attribute definition.
169
- #
170
- # Merges scoped flags like `required`, `access`, and `transient` if present.
213
+ # Merges any current context flags (e.g., required, access) into the options hash.
171
214
  #
172
215
  # @param options [Hash]
173
- # @return [Hash]
216
+ # @return [Hash] effective options for the attribute
174
217
  def build_options(options)
175
218
  base = @__block_options || {}
176
219
  base = base.merge(required: @__required_context) unless @__required_context.nil?
@@ -3,15 +3,9 @@
3
3
  require "json"
4
4
  require_relative "error"
5
5
  require_relative "attribute"
6
- require_relative "default_serializer"
6
+ require_relative "serializers/default_serializer"
7
7
  require_relative "contract/validator"
8
- require_relative "core/config"
9
- require_relative "core/attributes"
10
- require_relative "core/attribute_types"
11
- require_relative "core/registerable"
12
- require_relative "ext/data_object/contract"
13
- require_relative "ext/data_object/deserialization"
14
- require_relative "ext/data_object/serialization"
8
+ require_relative "dsl/data_object"
15
9
 
16
10
  module Castkit
17
11
  # Base class for defining declarative, typed data transfer objects (DTOs).
@@ -29,24 +23,9 @@ module Castkit
29
23
  # user = UserDto.new(name: "Alice", age: 30)
30
24
  # user.to_json #=> '{"name":"Alice","age":30}'
31
25
  class DataObject
32
- extend Castkit::Core::Config
33
- extend Castkit::Core::Attributes
34
- extend Castkit::Core::AttributeTypes
35
- extend Castkit::Core::Registerable
36
- extend Castkit::Ext::DataObject::Contract
37
-
38
- include Castkit::Ext::DataObject::Serialization
39
- include Castkit::Ext::DataObject::Deserialization
26
+ include Castkit::DSL::DataObject
40
27
 
41
28
  class << self
42
- # Registers the current class under `Castkit::DataObjects`.
43
- #
44
- # @param as [String, Symbol, nil] The constant name to use (PascalCase). Defaults to class name or "Anonymous".
45
- # @return [Class] the registered dataobject class
46
- def register!(as: nil)
47
- super(namespace: :dataobjects, as: as)
48
- end
49
-
50
29
  def build(&block)
51
30
  klass = Class.new(self)
52
31
  klass.class_eval(&block) if block_given?
@@ -56,12 +35,14 @@ module Castkit
56
35
 
57
36
  # Gets or sets the serializer class to use for instances of this object.
58
37
  #
59
- # @param value [Class<Castkit::Serializer>, nil]
60
- # @return [Class<Castkit::Serializer>, nil]
61
- # @raise [ArgumentError] if value does not inherit from Castkit::Serializer
38
+ # @param value [Class<Castkit::Serializers::Base>, nil]
39
+ # @return [Class<Castkit::Serializers::Base>, nil]
40
+ # @raise [ArgumentError] if value does not inherit from Castkit::Serializers::Base
62
41
  def serializer(value = nil)
63
42
  if value
64
- raise ArgumentError, "Serializer must inherit from Castkit::Serializer" unless value < Castkit::Serializer
43
+ unless value < Castkit::Serializers::Base
44
+ raise ArgumentError, "Serializer must inherit from Castkit::Serializers::Base"
45
+ end
65
46
 
66
47
  @serializer = value
67
48
  else
@@ -154,9 +135,9 @@ module Castkit
154
135
 
155
136
  # Returns the serializer instance or default for this object.
156
137
  #
157
- # @return [Class<Castkit::Serializer>]
138
+ # @return [Class<Castkit::Serializers::Base>]
158
139
  def serializer
159
- @serializer ||= self.class.serializer || Castkit::DefaultSerializer
140
+ @serializer ||= self.class.serializer || Castkit::Serializers::DefaultSerializer
160
141
  end
161
142
 
162
143
  # Returns false if self.class.allow_unknown == true, otherwise the value of self.class.strict.
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Castkit
4
- module Ext
4
+ module DSL
5
5
  module Attribute
6
6
  # Provides access control helpers for attributes.
7
7
  #
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Castkit
4
- module Ext
4
+ module DSL
5
5
  module Attribute
6
6
  # Provides centralized handling of attribute casting and validation errors.
7
7
  #
@@ -3,7 +3,7 @@
3
3
  require_relative "../../data_object"
4
4
 
5
5
  module Castkit
6
- module Ext
6
+ module DSL
7
7
  module Attribute
8
8
  # Provides access to normalized attribute options and helper predicates.
9
9
  #
@@ -4,13 +4,13 @@ require_relative "error_handling"
4
4
  require_relative "options"
5
5
 
6
6
  module Castkit
7
- module Ext
7
+ module DSL
8
8
  module Attribute
9
9
  # Provides validation logic for attribute configuration.
10
10
  #
11
11
  # These checks are typically performed at attribute initialization to catch misconfigurations early.
12
12
  module Validation
13
- include Castkit::Ext::Attribute::ErrorHandling
13
+ include Castkit::DSL::Attribute::ErrorHandling
14
14
 
15
15
  private
16
16
 
@@ -58,7 +58,7 @@ module Castkit
58
58
  # @raise [Castkit::AttributeError] if any access mode is invalid and enforcement is enabled
59
59
  def validate_access!
60
60
  access.each do |mode|
61
- next if Castkit::Ext::Attribute::Options::DEFAULT_OPTIONS[:access].include?(mode)
61
+ next if Castkit::Attributes::Options::DEFAULTS[:access].include?(mode)
62
62
 
63
63
  handle_error(:access, mode: mode, context: to_h)
64
64
  end