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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d13b67e4f2a1e249d92cb88332e6a87724273673ddea91e6e522d7bde8fc3de6
4
- data.tar.gz: 8608ab59c8bb96c9bc3d79250e5a106b025f2a09e4b18bb09ce462ea95c0f4a1
3
+ metadata.gz: f048458a9f967984b6b590c1e6bf2348a43c90936374b4d8755c6f7151057fe8
4
+ data.tar.gz: 182e73cec494a329bc670999cceab4a3c1e057eb6108097c759989102232ec94
5
5
  SHA512:
6
- metadata.gz: 36d802f0842118f2f064e2a15e3fb691f263b6d609267b2d65da5a8ee6e9c844a507387c3ab60cd088616dd8ec51bc0fa768adf9ad92bf2e515c4a2b77d65e32
7
- data.tar.gz: aad2e99da5cc53214faf19880eae57edf66668fdbe63147a44597f2a605962bcb22b6ec58a6f0b1aad9d79aef6f248e51e4cfe36e55fa7e60262dbc1502e080e
6
+ metadata.gz: 4d8bc7bdd0ed6c26f7fb7a621306995b4fbfbbe6d1d331ffa813ffc4c13a2d2795b72af9f977bc618e950c8dfdc5b017bb95563a436520e4e545efdf1a9a0b50
7
+ data.tar.gz: 196ed35dd105ad854ac74fa4540cbfbb04de30885069719ab80bb84d5a41b8ea8e64df1e085753477dab3bf54f998db20684b5cd6460af4c87447594c320edd3
data/CHANGELOG.md CHANGED
@@ -1,5 +1,38 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.3.0] - 2025-04-16
4
+
5
+ ### Added
6
+
7
+ - **CLI System (`castkit`)**:
8
+ - Introduced a full-featured CLI for generating and inspecting Castkit components.
9
+ - Supports:
10
+ - `castkit generate [component]` for scaffolding types, data objects, contracts, serializers, validators, and plugins.
11
+ - `castkit list [types|validators|contracts|dataobjects|serializers]` for inspecting the internal registry and available classes.
12
+ - Enables developer productivity and exploration through a single entry point.
13
+
14
+ - **Plugin System**:
15
+ - Added `Castkit::Plugins` for modular runtime extensions.
16
+ - Plugins can be registered and activated on DTO classes via:
17
+
18
+ ```ruby
19
+ Castkit.configure do |config|
20
+ config.register_plugin(:oj, MyOjPlugin)
21
+ end
22
+
23
+ class MyDto < Castkit::DataObject
24
+ enable_plugins :oj
25
+ end
26
+ ```
27
+
28
+ - Plugins support `setup!(klass)` for optional initialization logic.
29
+
30
+ - **Base Class Renames**:
31
+ - Introduced consistent naming conventions with `Castkit::Types::Base`, `Castkit::Serializers::Base`, `Castkit::Validators::Base`, etc.
32
+ - These will be excluded from list output automatically in CLI commands.
33
+
34
+ ---
35
+
3
36
  ## [0.2.0] - 2025-04-14
4
37
 
5
38
  ### Added
@@ -10,7 +43,7 @@
10
43
  - Developers may register custom types via `Castkit.configure`:
11
44
 
12
45
  ```ruby
13
- class CustomType < Castkit::Types::Generic
46
+ class CustomType < Castkit::Types::Base
14
47
  def deserialize(value) ... end
15
48
  def validate!(value, **) ... end
16
49
  end
@@ -42,6 +75,8 @@
42
75
 
43
76
  - **Union type validation support** for both `Castkit::DataObject` and contracts, allowing attributes to accept multiple types (e.g., `[:string, :integer]`).
44
77
 
78
+ ---
79
+
45
80
  ## [0.1.2] - 2025-04-14
46
81
 
47
82
  ### Added
@@ -71,6 +106,8 @@
71
106
  end
72
107
  ```
73
108
 
109
+ ---
110
+
74
111
  ## [0.1.1] - 2025-04-13
75
112
 
76
113
  ### Added
@@ -87,6 +124,8 @@
87
124
 
88
125
  Useful when working with nested DataObject collections (e.g., for integration with ActiveRecord or serialization logic).
89
126
 
127
+ ---
128
+
90
129
  ## [0.1.0] - 2025-04-12
91
130
 
92
131
  - Initial release
data/README.md CHANGED
@@ -21,6 +21,8 @@ Castkit is designed to work seamlessly in service-oriented and API-driven archit
21
21
  - [DataObjects](#dataobjects)
22
22
  - [Contracts](#contracts)
23
23
  - [Advance Usage](#advanced-usage-coming-soon)
24
+ - [Plugins](#plugins)
25
+ - [Castkit CLI](#castkit-cli)
24
26
  - [Testing](#testing)
25
27
  - [Compatibility](#compatibility)
26
28
  - [License](#license)
@@ -58,14 +60,14 @@ Castkit comes with built-in support for primitive types and allows registration
58
60
 
59
61
  ```ruby
60
62
  {
61
- array: Castkit::Types::Collection,
62
- boolean: Castkit::Types::Boolean,
63
- date: Castkit::Types::Date,
64
- datetime: Castkit::Types::DateTime,
65
- float: Castkit::Types::Float,
66
- hash: Castkit::Types::Generic,
67
- integer: Castkit::Types::Integer,
68
- string: Castkit::Types::String
63
+ array: Castkit::Types::Collection,
64
+ boolean: Castkit::Types::Boolean,
65
+ date: Castkit::Types::Date,
66
+ datetime: Castkit::Types::DateTime,
67
+ float: Castkit::Types::Float,
68
+ hash: Castkit::Types::Base,
69
+ integer: Castkit::Types::Integer,
70
+ string: Castkit::Types::String
69
71
  }
70
72
  ```
71
73
 
@@ -213,7 +215,7 @@ class Metadata < Castkit::DataObject
213
215
  end
214
216
 
215
217
  class PageDto < Castkit::DataObject
216
- dataobject :metadata, unwrapped: true, prefix: "meta"
218
+ dataobject :metadata, Metadata, unwrapped: true, prefix: "meta"
217
219
  end
218
220
 
219
221
  # Serializes as:
@@ -374,7 +376,8 @@ from_contract = Castkit::DataObject.from_contract(contract)
374
376
  To override default serialization behavior:
375
377
 
376
378
  ```ruby
377
- class CustomSerializer < Castkit::Serializer
379
+
380
+ class CustomSerializer < Castkit::Serializers::Base
378
381
  def call
379
382
  { payload: object.to_h }
380
383
  end
@@ -427,7 +430,8 @@ end
427
430
  Or subclass directly:
428
431
 
429
432
  ```ruby
430
- class MyContract < Castkit::Contract::Generic
433
+
434
+ class MyContract < Castkit::Contract::Base
431
435
  string :id
432
436
  integer :count, required: false
433
437
  end
@@ -519,7 +523,7 @@ end
519
523
 
520
524
  class UserDto < Castkit::DataObject
521
525
  string :id
522
- dataobject :address, of: AddressDto
526
+ dataobject :address, AddressDto
523
527
  end
524
528
 
525
529
  UserContract = Castkit::Contract.from_dataobject(UserDto)
@@ -532,7 +536,7 @@ UserContract.validate!(id: "abc", address: { city: "Boston" })
532
536
 
533
537
  Castkit is designed to be modular and extendable. Future guides will cover:
534
538
 
535
- - Custom serializers (`Castkit::Serializer`)
539
+ - Custom serializers (`Castkit::Serializers::Base`)
536
540
  - Integration layers:
537
541
  - `castkit-activerecord` for syncing with ActiveRecord models
538
542
  - `castkit-msgpack` for binary encoding
@@ -543,6 +547,286 @@ Castkit is designed to be modular and extendable. Future guides will cover:
543
547
 
544
548
  ---
545
549
 
550
+ ## Plugins
551
+
552
+ Castkit supports modular extensions through a lightweight plugin system. Plugins can modify or extend the behavior of `Castkit::DataObject` classes, such as adding serialization support, transformation helpers, or framework integrations.
553
+
554
+ Plugins are just Ruby modules and can be registered and activated globally or per-class.
555
+
556
+ ---
557
+
558
+ ### 📦 Activating Plugins
559
+
560
+ Plugins can be activated on any DataObject or at runtime:
561
+
562
+ ```ruby
563
+ module MyPlugin
564
+ def self.setup!(klass)
565
+ # Optional: called after inclusion
566
+ klass.string :plugin_id
567
+ end
568
+
569
+ def plugin_feature
570
+ "Enabled!"
571
+ end
572
+ end
573
+
574
+ Castkit.configure do |config|
575
+ config.register_plugin(:my_plugin, MyPlugin)
576
+ end
577
+
578
+ class MyDto < Castkit::DataObject
579
+ enable_plugins :my_plugin
580
+ end
581
+ ```
582
+
583
+ This includes the `MyPlugin` module into `MyDto` and calls `MyPlugin.setup!(MyDto)` if defined.
584
+
585
+ ---
586
+
587
+ ### 🧩 Registering Plugins
588
+
589
+ Plugins must be registered before use:
590
+
591
+ ```ruby
592
+ Castkit.configure do |config|
593
+ config.register_plugin(:oj, Castkit::Plugins::Oj)
594
+ end
595
+ ```
596
+
597
+ You can then activate them:
598
+
599
+ ```ruby
600
+ Castkit::Plugins.activate(MyDto, :oj)
601
+ ```
602
+
603
+ Or by using the `enable_plugins` helper method in `Castkit::DataObject`:
604
+
605
+ ```ruby
606
+ class MyDto < Castkit::DataObject
607
+ enable_plugins :oj, :yaml
608
+ end
609
+ ```
610
+
611
+ ---
612
+
613
+ ### 🧰 Plugin API
614
+
615
+ | Method | Description |
616
+ |------------------------------|-------------|
617
+ | `Castkit::Plugins.register(:name, mod)` | Registers a plugin under a custom name. |
618
+ | `Castkit::Plugins.activate(klass, *names)` | Includes one or more plugins into a class. |
619
+ | `Castkit::Plugins.lookup!(:name)` | Looks up the plugin by name or constant. |
620
+
621
+ ---
622
+
623
+ ### 📁 Plugin Structure
624
+
625
+ Castkit looks for plugins under the `Castkit::Plugins` namespace by default:
626
+
627
+ ```ruby
628
+ module Castkit
629
+ module Plugins
630
+ module Oj
631
+ def self.setup!(klass)
632
+ klass.include SerializationSupport
633
+ end
634
+ end
635
+ end
636
+ end
637
+ ```
638
+
639
+ To activate this:
640
+
641
+ ```ruby
642
+ Castkit::Plugins.activate(MyDto, :oj)
643
+ ```
644
+
645
+ You can also manually register plugins not under this namespace.
646
+
647
+ ---
648
+
649
+ ### ✅ Example Use Case
650
+
651
+ ```ruby
652
+ module Castkit
653
+ module Plugins
654
+ module Timestamps
655
+ def self.setup!(klass)
656
+ klass.datetime :created_at
657
+ klass.datetime :updated_at
658
+ end
659
+ end
660
+ end
661
+ end
662
+
663
+ Castkit::Plugins.activate(UserDto, :timestamps)
664
+ ```
665
+
666
+ This approach allows reusable, modular feature sets across DTOs with clean setup behavior.
667
+
668
+ ---
669
+
670
+ ## Castkit CLI
671
+
672
+ Castkit includes a command-line interface to help scaffold and inspect DTO components with ease.
673
+
674
+ The CLI is structured around two primary commands:
675
+
676
+ - `castkit generate` — scaffolds boilerplate for Castkit components.
677
+ - `castkit list` — introspects and displays registered or defined components.
678
+
679
+ ---
680
+
681
+ ## ✨ Generate Commands
682
+
683
+ The `castkit generate` command provides subcommands for creating files for all core Castkit component types.
684
+
685
+ ### 🧱 DataObject
686
+
687
+ ```bash
688
+ castkit generate dataobject User name:string age:integer
689
+ ```
690
+
691
+ Creates:
692
+
693
+ - `lib/castkit/data_objects/user.rb`
694
+ - `spec/castkit/data_objects/user_spec.rb`
695
+
696
+ ### 📄 Contract
697
+
698
+ ```bash
699
+ castkit generate contract UserInput id:string email:string
700
+ ```
701
+
702
+ Creates:
703
+
704
+ - `lib/castkit/contracts/user_input.rb`
705
+ - `spec/castkit/contracts/user_input_spec.rb`
706
+
707
+ ### 🔌 Plugin
708
+
709
+ ```bash
710
+ castkit generate plugin Oj
711
+ ```
712
+
713
+ Creates:
714
+
715
+ - `lib/castkit/plugins/oj.rb`
716
+ - `spec/castkit/plugins/oj_spec.rb`
717
+
718
+ ### 🧪 Validator
719
+
720
+ ```bash
721
+ castkit generate validator Money
722
+ ```
723
+
724
+ Creates:
725
+
726
+ - `lib/castkit/validators/money.rb`
727
+ - `spec/castkit/validators/money_spec.rb`
728
+
729
+ ### 🧬 Type
730
+
731
+ ```bash
732
+ castkit generate type money
733
+ ```
734
+
735
+ Creates:
736
+
737
+ - `lib/castkit/types/money.rb`
738
+ - `spec/castkit/types/money_spec.rb`
739
+
740
+ ### 📦 Serializer
741
+
742
+ ```bash
743
+ castkit generate serializer Json
744
+ ```
745
+
746
+ Creates:
747
+
748
+ - `lib/castkit/serializers/json.rb`
749
+ - `spec/castkit/serializers/json_spec.rb`
750
+
751
+ You can disable test generation with `--no-spec`.
752
+
753
+ ---
754
+
755
+ ## 📋 List Commands
756
+
757
+ The `castkit list` command provides an interface to view internal Castkit definitions or project-registered components.
758
+
759
+ ### 🧾 List Types
760
+
761
+ ```bash
762
+ castkit list types
763
+ ```
764
+
765
+ Displays a grouped list of:
766
+
767
+ - Native types (defined by Castkit)
768
+ - Custom types (registered via `Castkit.configure`)
769
+
770
+ Example:
771
+
772
+ ```bash
773
+ Native Types:
774
+ Castkit::Types::String - :string, :str, :uuid
775
+
776
+ Custom Types:
777
+ MyApp::Types::Money - :money
778
+ ```
779
+
780
+ ### 🔍 List Validators
781
+
782
+ ```bash
783
+ castkit list validators
784
+ ```
785
+
786
+ Displays all validator classes defined in `lib/castkit/validators` or custom-defined under `Castkit::Validators`.
787
+
788
+ Castkit validators are tagged `[Castkit]`, and others as `[Custom]`.
789
+
790
+ ### 📑 List Contracts
791
+
792
+ ```bash
793
+ castkit list contracts
794
+ ```
795
+
796
+ Lists all contracts in the `Castkit::Contracts` namespace and related files.
797
+
798
+ ### 📦 List DataObjects
799
+
800
+ ```bash
801
+ castkit list dataobjects
802
+ ```
803
+
804
+ Lists all DTOs in the `Castkit::DataObjects` namespace.
805
+
806
+ ### 🧪 List Serializers
807
+
808
+ ```bash
809
+ castkit list serializers
810
+ ```
811
+
812
+ Lists all serializer classes and their source origin.
813
+
814
+ ---
815
+
816
+ ## 🧰 Example Usage
817
+
818
+ ```bash
819
+ castkit generate dataobject Product name:string price:float
820
+ castkit generate contract ProductInput name:string
821
+
822
+ castkit list types
823
+ castkit list validators
824
+ ```
825
+
826
+ The CLI is designed to provide a familiar Rails-like generator experience, tailored for Castkit’s data-first architecture.
827
+
828
+ ---
829
+
546
830
  ## Testing
547
831
 
548
832
  You can test DTOs and Contracts by treating them like plain Ruby objects:
data/castkit.gemspec CHANGED
@@ -33,9 +33,12 @@ Gem::Specification.new do |spec|
33
33
  spec.require_paths = ["lib"]
34
34
 
35
35
  # Runtime dependencies
36
+ spec.add_dependency "thor"
36
37
 
37
38
  # Development dependencies
38
39
  spec.add_development_dependency "rspec"
39
40
  spec.add_development_dependency "rubocop"
41
+ spec.add_development_dependency "simplecov"
42
+ spec.add_development_dependency "simplecov-cobertura"
40
43
  spec.add_development_dependency "yard"
41
44
  end
@@ -1,46 +1,103 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "castkit"
3
4
  require_relative "error"
4
- require_relative "ext/attribute/options"
5
- require_relative "ext/attribute/access"
6
- require_relative "ext/attribute/validation"
5
+ require_relative "attributes/options"
6
+ require_relative "dsl/attribute"
7
7
 
8
8
  module Castkit
9
- # Represents a typed attribute on a Castkit::DataObject.
9
+ # Represents a typed attribute on a `Castkit::DataObject`.
10
10
  #
11
- # Provides casting, validation, access control, and serialization behavior.
11
+ # This class is responsible for:
12
+ # - Type normalization (symbol, class, or data object)
13
+ # - Default and option resolution
14
+ # - Validation hooks
15
+ # - Access and serialization control
16
+ #
17
+ # Attributes are created automatically when using the DSL in `DataObject`, but
18
+ # can also be created manually or through reusable definitions.
19
+ #
20
+ # @see Castkit::Attributes::Definition
21
+ # @see Castkit::DSL::Attribute::Options
22
+ # @see Castkit::DSL::Attribute::Access
23
+ # @see Castkit::DSL::Attribute::Validation
12
24
  class Attribute
13
- include Castkit::Ext::Attribute::Options
14
- include Castkit::Ext::Attribute::Access
15
- include Castkit::Ext::Attribute::Validation
25
+ include Castkit::DSL::Attribute
26
+
27
+ class << self
28
+ # Defines a reusable attribute definition via a DSL wrapper.
29
+ #
30
+ # @param type [Symbol, Class] The base type to define.
31
+ # @param options [Hash] Additional attribute options.
32
+ # @yield The block to configure options or transformations.
33
+ # @return [Array<(Symbol, Hash)>] a tuple of the final type and options hash
34
+ def define(type, **options, &block)
35
+ normalized_type = normalize_type(type)
36
+ Castkit::Attributes::Definition.define(normalized_type, **options, &block)
37
+ end
38
+
39
+ # Normalizes a declared type (symbol, class, or array) for internal usage.
40
+ #
41
+ # @param type [Symbol, Class, Array] the input type
42
+ # @return [Symbol, Class<Castkit::DataObject>] the normalized form
43
+ def normalize_type(type)
44
+ return type.map { |t| normalize_type(t) } if type.is_a?(Array)
45
+ return type if Castkit.dataobject?(type)
46
+
47
+ process_type(type).to_sym
48
+ end
49
+
50
+ # Converts a raw type into a normalized symbol.
51
+ #
52
+ # Recognized forms:
53
+ # - `TrueClass`/`FalseClass` → `:boolean`
54
+ # - Class → `class.name.downcase.to_sym`
55
+ # - Symbol → passed through
56
+ #
57
+ # @param type [Symbol, Class] the type to convert
58
+ # @return [Symbol] normalized type symbol
59
+ # @raise [Castkit::AttributeError] if the type is invalid
60
+ def process_type(type)
61
+ case type
62
+ when Class
63
+ return :boolean if [TrueClass, FalseClass].include?(type)
64
+
65
+ type.name.downcase.to_sym
66
+ when Symbol
67
+ type
68
+ else
69
+ raise Castkit::AttributeError, "Unknown type: #{type.inspect}"
70
+ end
71
+ end
72
+ end
16
73
 
17
74
  # @return [Symbol] the attribute name
18
75
  attr_reader :field
19
76
 
20
- # @return [Symbol, Class, Array] the declared type (normalized)
77
+ # @return [Symbol, Class, Array] the declared or normalized type
21
78
  attr_reader :type
22
79
 
23
- # @return [Hash] attribute options (including aliases, default, access, etc.)
80
+ # @return [Hash] full option hash, including merged defaults
24
81
  attr_reader :options
25
82
 
26
83
  # Initializes a new attribute definition.
27
84
  #
28
- # @param field [Symbol] the name of the attribute
29
- # @param type [Symbol, Class, Array] the type or array of types
30
- # @param default [Object, Proc] optional default value
31
- # @param options [Hash] additional configuration options
85
+ # @param field [Symbol] the attribute name
86
+ # @param type [Symbol, Class, Array<Symbol, Class>] the type (or list of types)
87
+ # @param default [Object, Proc, nil] optional static or callable default
88
+ # @param options [Hash] additional attribute options
32
89
  def initialize(field, type, default: nil, **options)
33
90
  @field = field
34
- @type = normalize_type(type)
91
+ @type = self.class.normalize_type(type)
35
92
  @default = default
36
93
  @options = populate_options(options)
37
94
 
38
95
  validate!
39
96
  end
40
97
 
41
- # Returns a hash representation of the attribute definition.
98
+ # Converts the attribute definition to a serializable hash.
42
99
  #
43
- # @return [Hash]
100
+ # @return [Hash] the full attribute metadata
44
101
  def to_hash
45
102
  {
46
103
  field: field,
@@ -55,56 +112,22 @@ module Castkit
55
112
 
56
113
  private
57
114
 
58
- # Populates default values and normalizes internal options.
115
+ # Populates default values and prepares internal options.
59
116
  #
60
- # @param options [Hash]
61
- # @return [Hash]
117
+ # @param options [Hash] the user-provided options
118
+ # @return [Hash] the merged and normalized options
62
119
  def populate_options(options)
63
- options = DEFAULT_OPTIONS.merge(options)
120
+ options = Castkit::Attributes::Options::DEFAULTS.merge(options)
64
121
  options[:aliases] = Array(options[:aliases] || [])
65
- options[:of] = normalize_type(options[:of]) if options[:of]
122
+ options[:of] = self.class.normalize_type(options[:of]) if options[:of]
66
123
 
67
124
  options
68
125
  end
69
126
 
70
- # Normalizes a declared type to a symbol or class reference.
71
- #
72
- # @param type [Symbol, Class, Array]
73
- # @return [Symbol, Class, Array]
74
- # @raise [Castkit::AttributeError] if the type is not valid
75
- def normalize_type(type)
76
- return type.map { |t| normalize_type(t) } if type.is_a?(Array)
77
- return type if Castkit.dataobject?(type)
78
-
79
- process_type(type)
80
- end
81
-
82
- # Converts a single type value into a normalized internal representation.
83
- #
84
- # - Maps `TrueClass`/`FalseClass` to `:boolean`
85
- # - Converts class names (e.g., `String`, `Integer`) to lowercase symbols
86
- # - Accepts already-symbolized types (e.g., `:string`)
87
- #
88
- # @param type [Class, Symbol] the declared type to process
89
- # @return [Symbol] the normalized type
90
- # @raise [Castkit::AttributeError] if the type is not a recognized form
91
- def process_type(type)
92
- case type
93
- when Class
94
- return :boolean if [TrueClass, FalseClass].include?(type)
95
-
96
- type.name.downcase.to_sym
97
- when Symbol
98
- type
99
- else
100
- raise_error!("Unknown type: #{type.inspect}")
101
- end
102
- end
103
-
104
- # Raises a Castkit::AttributeError with optional context.
127
+ # Raises a standardized attribute error with context.
105
128
  #
106
- # @param message [String]
107
- # @param context [Hash, nil]
129
+ # @param message [String] the error message
130
+ # @param context [Hash, nil] optional override for context payload
108
131
  # @raise [Castkit::AttributeError]
109
132
  def raise_error!(message, context: nil)
110
133
  raise Castkit::AttributeError.new(message, context: context || to_h)