castkit 0.3.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +39 -0
- data/README.md +11 -3
- data/castkit.gemspec +2 -0
- data/lib/castkit/attribute.rb +82 -59
- data/lib/castkit/attributes/definition.rb +64 -0
- data/lib/castkit/attributes/options.rb +214 -0
- data/lib/castkit/castkit.rb +13 -3
- data/lib/castkit/cli/generate.rb +14 -0
- data/lib/castkit/contract/base.rb +0 -12
- data/lib/castkit/contract/validator.rb +5 -1
- data/lib/castkit/core/attributes.rb +87 -44
- data/lib/castkit/data_object.rb +2 -25
- data/lib/castkit/{ext → dsl}/attribute/access.rb +1 -1
- data/lib/castkit/{ext → dsl}/attribute/error_handling.rb +1 -1
- data/lib/castkit/{ext → dsl}/attribute/options.rb +1 -1
- data/lib/castkit/{ext → dsl}/attribute/validation.rb +3 -3
- data/lib/castkit/dsl/attribute.rb +47 -0
- data/lib/castkit/{ext → dsl}/data_object/contract.rb +1 -1
- data/lib/castkit/{ext → dsl}/data_object/deserialization.rb +6 -2
- data/lib/castkit/{ext → dsl}/data_object/plugins.rb +1 -1
- data/lib/castkit/{ext → dsl}/data_object/serialization.rb +1 -1
- data/lib/castkit/dsl/data_object.rb +61 -0
- data/lib/castkit/types/base.rb +24 -3
- data/lib/castkit/validators/boolean_validator.rb +3 -3
- data/lib/castkit/version.rb +1 -1
- data/lib/castkit.rb +1 -4
- data/lib/generators/attribute.rb +39 -0
- data/lib/generators/templates/attribute.rb.tt +21 -0
- data/lib/generators/templates/attribute_spec.rb.tt +41 -0
- data/lib/generators/templates/contract.rb.tt +2 -0
- data/lib/generators/templates/data_object.rb.tt +2 -0
- data/lib/generators/templates/type.rb.tt +2 -0
- data/lib/generators/templates/validator.rb.tt +1 -1
- metadata +45 -12
- data/.rspec_status +0 -195
- data/lib/castkit/core/registerable.rb +0 -59
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f048458a9f967984b6b590c1e6bf2348a43c90936374b4d8755c6f7151057fe8
|
|
4
|
+
data.tar.gz: 182e73cec494a329bc670999cceab4a3c1e057eb6108097c759989102232ec94
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
@@ -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
|
@@ -215,7 +215,7 @@ class Metadata < Castkit::DataObject
|
|
|
215
215
|
end
|
|
216
216
|
|
|
217
217
|
class PageDto < Castkit::DataObject
|
|
218
|
-
dataobject :metadata, unwrapped: true, prefix: "meta"
|
|
218
|
+
dataobject :metadata, Metadata, unwrapped: true, prefix: "meta"
|
|
219
219
|
end
|
|
220
220
|
|
|
221
221
|
# Serializes as:
|
|
@@ -523,7 +523,7 @@ end
|
|
|
523
523
|
|
|
524
524
|
class UserDto < Castkit::DataObject
|
|
525
525
|
string :id
|
|
526
|
-
dataobject :address,
|
|
526
|
+
dataobject :address, AddressDto
|
|
527
527
|
end
|
|
528
528
|
|
|
529
529
|
UserContract = Castkit::Contract.from_dataobject(UserDto)
|
|
@@ -576,7 +576,7 @@ Castkit.configure do |config|
|
|
|
576
576
|
end
|
|
577
577
|
|
|
578
578
|
class MyDto < Castkit::DataObject
|
|
579
|
-
|
|
579
|
+
enable_plugins :my_plugin
|
|
580
580
|
end
|
|
581
581
|
```
|
|
582
582
|
|
|
@@ -600,6 +600,14 @@ You can then activate them:
|
|
|
600
600
|
Castkit::Plugins.activate(MyDto, :oj)
|
|
601
601
|
```
|
|
602
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
|
+
|
|
603
611
|
---
|
|
604
612
|
|
|
605
613
|
### 🧰 Plugin API
|
data/castkit.gemspec
CHANGED
|
@@ -38,5 +38,7 @@ Gem::Specification.new do |spec|
|
|
|
38
38
|
# Development dependencies
|
|
39
39
|
spec.add_development_dependency "rspec"
|
|
40
40
|
spec.add_development_dependency "rubocop"
|
|
41
|
+
spec.add_development_dependency "simplecov"
|
|
42
|
+
spec.add_development_dependency "simplecov-cobertura"
|
|
41
43
|
spec.add_development_dependency "yard"
|
|
42
44
|
end
|
data/lib/castkit/attribute.rb
CHANGED
|
@@ -1,46 +1,103 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "castkit"
|
|
3
4
|
require_relative "error"
|
|
4
|
-
require_relative "
|
|
5
|
-
require_relative "
|
|
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
|
-
#
|
|
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::
|
|
14
|
-
|
|
15
|
-
|
|
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
|
|
77
|
+
# @return [Symbol, Class, Array] the declared or normalized type
|
|
21
78
|
attr_reader :type
|
|
22
79
|
|
|
23
|
-
# @return [Hash]
|
|
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
|
|
29
|
-
# @param type [Symbol, Class, Array] the type or
|
|
30
|
-
# @param default [Object, Proc] optional default
|
|
31
|
-
# @param options [Hash] additional
|
|
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
|
-
#
|
|
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
|
|
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 =
|
|
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
|
-
#
|
|
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)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "options"
|
|
4
|
+
|
|
5
|
+
module Castkit
|
|
6
|
+
module Attributes
|
|
7
|
+
# Provides a class-based DSL for defining reusable attribute definitions.
|
|
8
|
+
#
|
|
9
|
+
# Extend this class in a subclass of `Castkit::Attributes::Base` to define
|
|
10
|
+
# shared attribute settings that can be reused across multiple DataObjects.
|
|
11
|
+
#
|
|
12
|
+
# @example Defining a reusable attribute
|
|
13
|
+
# class UuidDefinition < Castkit::Attributes::Base
|
|
14
|
+
# type :string
|
|
15
|
+
# required true
|
|
16
|
+
# format /\A[0-9a-f\-]{36}\z/
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# attribute :id, UuidDefinition.definition
|
|
20
|
+
#
|
|
21
|
+
class Definition
|
|
22
|
+
extend Castkit::Attributes::Options
|
|
23
|
+
|
|
24
|
+
class << self
|
|
25
|
+
# @return [Hash] the internal definition hash, containing the type and options
|
|
26
|
+
def definition
|
|
27
|
+
@definition ||= {
|
|
28
|
+
type: nil,
|
|
29
|
+
options: Castkit::Attributes::Options::DEFAULTS.dup
|
|
30
|
+
}
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @return [Hash] the attribute options defined on this class
|
|
34
|
+
def options
|
|
35
|
+
definition[:options]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Defines the attribute's type and configuration using a DSL block.
|
|
39
|
+
#
|
|
40
|
+
# @param type [Symbol, Class<Castkit::DataObject>] the attribute type (e.g., :string, :integer)
|
|
41
|
+
# @param options [Hash] additional options to merge after the block (e.g., default:, access:)
|
|
42
|
+
# @yield DSL block used to set options like `required`, `format`, `readonly`, etc.
|
|
43
|
+
# @return [Array<(Symbol, Hash)>] a tuple of the final type and options hash
|
|
44
|
+
#
|
|
45
|
+
# @example
|
|
46
|
+
# define :string, default: "none" do
|
|
47
|
+
# required true
|
|
48
|
+
# access [:read]
|
|
49
|
+
# end
|
|
50
|
+
def define(type, **options, &block)
|
|
51
|
+
@__castkit_attribute_dsl = true
|
|
52
|
+
|
|
53
|
+
definition[:type] = type
|
|
54
|
+
instance_eval(&block)
|
|
55
|
+
definition[:options] = definition[:options].merge(options)
|
|
56
|
+
|
|
57
|
+
definition
|
|
58
|
+
ensure
|
|
59
|
+
@__castkit_attribute_dsl = false
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Castkit
|
|
4
|
+
module Attributes
|
|
5
|
+
# Provides a DSL for configuring attribute options within an attribute definition.
|
|
6
|
+
#
|
|
7
|
+
# This module is designed to be extended by class-level definition objects such as
|
|
8
|
+
# `Castkit::Attributes::Definition`, and is used to build reusable sets of options
|
|
9
|
+
# for attributes declared within `Castkit::DataObject` classes.
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
12
|
+
# class OptionalString < Castkit::Attributes::Definition
|
|
13
|
+
# type :string
|
|
14
|
+
# required false
|
|
15
|
+
# ignore_blank true
|
|
16
|
+
# end
|
|
17
|
+
module Options
|
|
18
|
+
# Valid access modes for an attribute.
|
|
19
|
+
#
|
|
20
|
+
# @return [Array<Symbol>]
|
|
21
|
+
ACCESS_MODES = %i[read write].freeze
|
|
22
|
+
|
|
23
|
+
# Default configuration for attribute options.
|
|
24
|
+
#
|
|
25
|
+
# @return [Hash{Symbol => Object}]
|
|
26
|
+
DEFAULTS = {
|
|
27
|
+
required: true,
|
|
28
|
+
ignore_nil: false,
|
|
29
|
+
ignore_blank: false,
|
|
30
|
+
ignore: false,
|
|
31
|
+
composite: false,
|
|
32
|
+
transient: false,
|
|
33
|
+
unwrapped: false,
|
|
34
|
+
prefix: nil,
|
|
35
|
+
access: ACCESS_MODES,
|
|
36
|
+
force_type: !Castkit.configuration.enforce_typing
|
|
37
|
+
}.freeze
|
|
38
|
+
|
|
39
|
+
# Sets or retrieves the attribute type.
|
|
40
|
+
#
|
|
41
|
+
# @param value [Symbol, nil] The type to assign (e.g., :string), or nil to fetch.
|
|
42
|
+
# @return [Symbol]
|
|
43
|
+
def type(value = nil)
|
|
44
|
+
value.nil? ? definition[:type] : (definition[:type] = value.to_sym)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Sets the element type for array attributes.
|
|
48
|
+
#
|
|
49
|
+
# @param value [Symbol, Class] the type of elements in the array
|
|
50
|
+
# @return [void]
|
|
51
|
+
def of(value)
|
|
52
|
+
return unless @type == :array
|
|
53
|
+
|
|
54
|
+
set_option(:of, value)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Sets the default value or proc for the attribute.
|
|
58
|
+
#
|
|
59
|
+
# @param value [Object, Proc] the default value or lambda
|
|
60
|
+
# @return [void]
|
|
61
|
+
def default(value = nil)
|
|
62
|
+
set_option(:default, value)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Enables or disables forced typecasting, or sets a custom flag.
|
|
66
|
+
#
|
|
67
|
+
# @param value [Boolean, nil] the forced type flag
|
|
68
|
+
# @return [void]
|
|
69
|
+
def force_type(value = nil)
|
|
70
|
+
set_option(:force_type, value || !Castkit.configuration.enforce_typing)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Marks the attribute as required or optional.
|
|
74
|
+
#
|
|
75
|
+
# @param value [Boolean]
|
|
76
|
+
# @return [void]
|
|
77
|
+
def required(value = nil)
|
|
78
|
+
set_option(:required, value || true)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Marks the attribute to be ignored entirely.
|
|
82
|
+
#
|
|
83
|
+
# @param value [Boolean]
|
|
84
|
+
# @return [void]
|
|
85
|
+
def ignore(value = nil)
|
|
86
|
+
set_option(:ignore, value || true)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Ignores `nil` values during serialization or persistence.
|
|
90
|
+
#
|
|
91
|
+
# @param value [Boolean]
|
|
92
|
+
# @return [void]
|
|
93
|
+
def ignore_nil(value = nil)
|
|
94
|
+
set_option(:ignore_nil, value || true)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Ignores blank values (`""`, `[]`, `{}`) during serialization.
|
|
98
|
+
#
|
|
99
|
+
# @param value [Boolean]
|
|
100
|
+
# @return [void]
|
|
101
|
+
def ignore_blank(value = nil)
|
|
102
|
+
set_option(:ignore_blank, value || true)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Adds a prefix for unwrapped attribute keys.
|
|
106
|
+
#
|
|
107
|
+
# @param value [String, Symbol, nil]
|
|
108
|
+
# @return [void]
|
|
109
|
+
def prefix(value = nil)
|
|
110
|
+
set_option(:prefix, value)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Marks the attribute as unwrapped (inline merging of nested fields).
|
|
114
|
+
#
|
|
115
|
+
# @param value [Boolean]
|
|
116
|
+
# @return [void]
|
|
117
|
+
def unwrapped(value = nil)
|
|
118
|
+
set_option(:unwrapped, value || true)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Sets access modes for the attribute.
|
|
122
|
+
#
|
|
123
|
+
# @param value [Array<Symbol>, Symbol] valid values: `:read`, `:write`, or both
|
|
124
|
+
# @return [void]
|
|
125
|
+
def access(value = nil)
|
|
126
|
+
value = validate_access_modes!(value)
|
|
127
|
+
set_option(:access, value)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Shortcut to make the attribute readonly (`access: [:read]`).
|
|
131
|
+
#
|
|
132
|
+
# @param value [Boolean]
|
|
133
|
+
# @return [void]
|
|
134
|
+
def readonly(value = nil)
|
|
135
|
+
value = value || true ? [:read] : ACCESS_MODES
|
|
136
|
+
set_option(:access, value)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Marks the attribute as a composite (e.g., nested `DataObject`).
|
|
140
|
+
#
|
|
141
|
+
# @param value [Boolean]
|
|
142
|
+
# @return [void]
|
|
143
|
+
def composite(value = nil)
|
|
144
|
+
set_option(:composite, value || true)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Marks the attribute as transient (not included in persistence or serialization).
|
|
148
|
+
#
|
|
149
|
+
# @param value [Boolean]
|
|
150
|
+
# @return [void]
|
|
151
|
+
def transient(value = nil)
|
|
152
|
+
set_option(:transient, value || true)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Sets a format constraint (e.g., regex validation).
|
|
156
|
+
#
|
|
157
|
+
# @param value [Regexp]
|
|
158
|
+
# @return [void]
|
|
159
|
+
def format(value)
|
|
160
|
+
set_option(:format, value)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Attaches a custom validator callable for this attribute.
|
|
164
|
+
#
|
|
165
|
+
# @param value [Proc, #call]
|
|
166
|
+
# @return [void]
|
|
167
|
+
def validator(value)
|
|
168
|
+
set_option(:validator, value)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
private
|
|
172
|
+
|
|
173
|
+
# Converts class or symbol into a normalized type symbol.
|
|
174
|
+
#
|
|
175
|
+
# @param type [Class, Symbol]
|
|
176
|
+
# @return [Symbol]
|
|
177
|
+
# @raise [Castkit::AttributeError] if type cannot be resolved
|
|
178
|
+
def process_type(type)
|
|
179
|
+
case type
|
|
180
|
+
when Class
|
|
181
|
+
return :boolean if [TrueClass, FalseClass].include?(type)
|
|
182
|
+
|
|
183
|
+
type.name.downcase.to_sym
|
|
184
|
+
when Symbol
|
|
185
|
+
type
|
|
186
|
+
else
|
|
187
|
+
raise Castkit::AttributeError.new("Unknown type: #{type.inspect}", context: to_h)
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Sets an option key-value pair in the current definition.
|
|
192
|
+
#
|
|
193
|
+
# @param option [Symbol]
|
|
194
|
+
# @param value [Object, nil]
|
|
195
|
+
# @return [Object, nil]
|
|
196
|
+
def set_option(option, value)
|
|
197
|
+
value.nil? ? definition[:options][option] : (definition[:options][option] = value)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Validates and normalizes access mode array.
|
|
201
|
+
#
|
|
202
|
+
# @param value [Array<Symbol>, Symbol, nil]
|
|
203
|
+
# @return [Array<Symbol>]
|
|
204
|
+
# @raise [Castkit::AttributeError] if invalid modes are present
|
|
205
|
+
def validate_access_modes!(value)
|
|
206
|
+
value_array = Array(value || ACCESS_MODES).compact
|
|
207
|
+
unknown_modes = value_array - ACCESS_MODES
|
|
208
|
+
return value_array if unknown_modes.empty?
|
|
209
|
+
|
|
210
|
+
raise Castkit::AttributeError.new("Unknown access flags: #{unknown_modes.inspect}", context: to_h)
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
data/lib/castkit/castkit.rb
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "
|
|
4
|
-
require_relative "inflector"
|
|
3
|
+
require_relative "core/attribute_types"
|
|
5
4
|
|
|
6
5
|
# Castkit is a lightweight, type-safe data object system for Ruby.
|
|
7
6
|
#
|
|
@@ -63,7 +62,10 @@ module Castkit
|
|
|
63
62
|
# @param obj [Object] the object to test
|
|
64
63
|
# @return [Boolean] true if obj is a Castkit::DataObject class
|
|
65
64
|
def dataobject?(obj)
|
|
66
|
-
obj.is_a?(Class) &&
|
|
65
|
+
obj.is_a?(Class) && (
|
|
66
|
+
obj <= Castkit::DataObject ||
|
|
67
|
+
obj.ancestors.include?(Castkit::DSL::DataObject)
|
|
68
|
+
)
|
|
67
69
|
end
|
|
68
70
|
|
|
69
71
|
# Returns a type caster lambda for the given type.
|
|
@@ -105,3 +107,11 @@ module Castkit
|
|
|
105
107
|
end
|
|
106
108
|
end
|
|
107
109
|
end
|
|
110
|
+
|
|
111
|
+
require_relative "configuration"
|
|
112
|
+
require_relative "plugins"
|
|
113
|
+
require_relative "inflector"
|
|
114
|
+
require_relative "version"
|
|
115
|
+
require_relative "attribute"
|
|
116
|
+
require_relative "contract"
|
|
117
|
+
require_relative "data_object"
|
data/lib/castkit/cli/generate.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "thor"
|
|
4
|
+
require_relative "../../generators/attribute"
|
|
4
5
|
require_relative "../../generators/contract"
|
|
5
6
|
require_relative "../../generators/data_object"
|
|
6
7
|
require_relative "../../generators/plugin"
|
|
@@ -30,6 +31,19 @@ module Castkit
|
|
|
30
31
|
Castkit::Generators::Contract.start(args)
|
|
31
32
|
end
|
|
32
33
|
|
|
34
|
+
desc "attribute NAME", "Generates a new Castkit attribute"
|
|
35
|
+
method_option :spec, type: :boolean, default: true
|
|
36
|
+
# Generates a new attribute definition class.
|
|
37
|
+
#
|
|
38
|
+
# @param name [String] the class name for the attribute
|
|
39
|
+
# @param fields [Array<String>] optional attribute options
|
|
40
|
+
# @return [void]
|
|
41
|
+
def attribute(name, *fields)
|
|
42
|
+
args = [Castkit::Inflector.pascalize(name), fields]
|
|
43
|
+
args << "--no-spec" unless options[:spec]
|
|
44
|
+
Castkit::Generators::Attribute.start(args)
|
|
45
|
+
end
|
|
46
|
+
|
|
33
47
|
desc "dataobject NAME", "Generates a new Castkit DataObject"
|
|
34
48
|
method_option :spec, type: :boolean, default: true
|
|
35
49
|
# Generates a new DataObject class.
|
|
@@ -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
|
|
@@ -33,23 +32,12 @@ module Castkit
|
|
|
33
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.
|