castkit 0.1.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rspec_status +195 -219
- data/CHANGELOG.md +42 -0
- data/README.md +744 -83
- data/castkit.gemspec +1 -0
- data/lib/castkit/attribute.rb +6 -24
- data/lib/castkit/castkit.rb +61 -10
- data/lib/castkit/cli/generate.rb +98 -0
- data/lib/castkit/cli/list.rb +200 -0
- data/lib/castkit/cli/main.rb +43 -0
- data/lib/castkit/cli.rb +24 -0
- data/lib/castkit/configuration.rb +116 -46
- data/lib/castkit/contract/base.rb +168 -0
- data/lib/castkit/contract/data_object.rb +62 -0
- data/lib/castkit/contract/result.rb +74 -0
- data/lib/castkit/contract/validator.rb +248 -0
- data/lib/castkit/contract.rb +67 -0
- data/lib/castkit/{data_object_extensions → core}/attribute_types.rb +21 -7
- data/lib/castkit/{data_object_extensions → core}/attributes.rb +8 -3
- data/lib/castkit/core/config.rb +74 -0
- data/lib/castkit/core/registerable.rb +59 -0
- data/lib/castkit/data_object.rb +56 -67
- data/lib/castkit/error.rb +15 -3
- data/lib/castkit/ext/attribute/access.rb +67 -0
- data/lib/castkit/ext/attribute/error_handling.rb +63 -0
- data/lib/castkit/ext/attribute/options.rb +142 -0
- data/lib/castkit/ext/attribute/validation.rb +85 -0
- data/lib/castkit/ext/data_object/contract.rb +96 -0
- data/lib/castkit/ext/data_object/deserialization.rb +167 -0
- data/lib/castkit/ext/data_object/plugins.rb +86 -0
- data/lib/castkit/ext/data_object/serialization.rb +61 -0
- data/lib/castkit/inflector.rb +47 -0
- data/lib/castkit/plugins.rb +82 -0
- data/lib/castkit/serializers/base.rb +94 -0
- data/lib/castkit/serializers/default_serializer.rb +156 -0
- data/lib/castkit/types/base.rb +122 -0
- data/lib/castkit/types/boolean.rb +47 -0
- data/lib/castkit/types/collection.rb +35 -0
- data/lib/castkit/types/date.rb +34 -0
- data/lib/castkit/types/date_time.rb +34 -0
- data/lib/castkit/types/float.rb +46 -0
- data/lib/castkit/types/integer.rb +46 -0
- data/lib/castkit/types/string.rb +44 -0
- data/lib/castkit/types.rb +15 -0
- data/lib/castkit/validators/base.rb +59 -0
- data/lib/castkit/validators/boolean_validator.rb +39 -0
- data/lib/castkit/validators/collection_validator.rb +29 -0
- data/lib/castkit/validators/float_validator.rb +31 -0
- data/lib/castkit/validators/integer_validator.rb +31 -0
- data/lib/castkit/validators/numeric_validator.rb +2 -2
- data/lib/castkit/validators/string_validator.rb +3 -4
- data/lib/castkit/version.rb +1 -1
- data/lib/castkit.rb +2 -0
- data/lib/generators/base.rb +97 -0
- data/lib/generators/contract.rb +68 -0
- data/lib/generators/data_object.rb +48 -0
- data/lib/generators/plugin.rb +25 -0
- data/lib/generators/serializer.rb +28 -0
- data/lib/generators/templates/contract.rb.tt +24 -0
- data/lib/generators/templates/contract_spec.rb.tt +76 -0
- data/lib/generators/templates/data_object.rb.tt +15 -0
- data/lib/generators/templates/data_object_spec.rb.tt +36 -0
- data/lib/generators/templates/plugin.rb.tt +37 -0
- data/lib/generators/templates/plugin_spec.rb.tt +18 -0
- data/lib/generators/templates/serializer.rb.tt +24 -0
- data/lib/generators/templates/serializer_spec.rb.tt +14 -0
- data/lib/generators/templates/type.rb.tt +55 -0
- data/lib/generators/templates/type_spec.rb.tt +42 -0
- data/lib/generators/templates/validator.rb.tt +26 -0
- data/lib/generators/templates/validator_spec.rb.tt +23 -0
- data/lib/generators/type.rb +29 -0
- data/lib/generators/validator.rb +41 -0
- metadata +74 -15
- data/lib/castkit/attribute_extensions/access.rb +0 -65
- data/lib/castkit/attribute_extensions/casting.rb +0 -147
- data/lib/castkit/attribute_extensions/error_handling.rb +0 -83
- data/lib/castkit/attribute_extensions/options.rb +0 -131
- data/lib/castkit/attribute_extensions/serialization.rb +0 -89
- data/lib/castkit/attribute_extensions/validation.rb +0 -72
- data/lib/castkit/data_object_extensions/config.rb +0 -113
- data/lib/castkit/data_object_extensions/deserialization.rb +0 -110
- data/lib/castkit/default_serializer.rb +0 -123
- data/lib/castkit/serializer.rb +0 -92
- data/lib/castkit/validators.rb +0 -4
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
require_relative "../validators/collection_validator"
|
5
|
+
|
6
|
+
module Castkit
|
7
|
+
module Types
|
8
|
+
# Type definition for `:array` attributes.
|
9
|
+
#
|
10
|
+
# Wraps any value in an array using `Array(value)` coercion. This ensures consistent array representation
|
11
|
+
# even if the input is a single value or nil.
|
12
|
+
#
|
13
|
+
# This class is used internally by Castkit when an attribute is defined with:
|
14
|
+
# `array :tags, of: :string`
|
15
|
+
class Collection < Base
|
16
|
+
# Deserializes the value into an array using `Array(value)`.
|
17
|
+
#
|
18
|
+
# @param value [Object]
|
19
|
+
# @return [::Array]
|
20
|
+
def deserialize(value)
|
21
|
+
Array(value)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Validates the Array value.
|
25
|
+
#
|
26
|
+
# @param value [Object]
|
27
|
+
# @param options [Hash] validation options
|
28
|
+
# @param context [Symbol, String, nil] attribute context for error messages
|
29
|
+
# @return [void]
|
30
|
+
def validate!(value, options: {}, context: nil)
|
31
|
+
Castkit::Validators::CollectionValidator.call(value, options: options, context: context)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "date"
|
4
|
+
require_relative "base"
|
5
|
+
|
6
|
+
module Castkit
|
7
|
+
module Types
|
8
|
+
# Type definition for `:date` attributes.
|
9
|
+
#
|
10
|
+
# Handles deserialization from strings and other input into `Date` objects,
|
11
|
+
# and serializes `Date` values into ISO8601 strings.
|
12
|
+
#
|
13
|
+
# This class is used internally by Castkit when an attribute is defined with:
|
14
|
+
# `date :published_on`
|
15
|
+
class Date < Base
|
16
|
+
# Deserializes the input value to a `Date` instance.
|
17
|
+
#
|
18
|
+
# @param value [Object]
|
19
|
+
# @return [::Date]
|
20
|
+
# @raise [ArgumentError] if parsing fails
|
21
|
+
def deserialize(value)
|
22
|
+
::Date.parse(value.to_s)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Serializes a `Date` object to ISO8601 string format.
|
26
|
+
#
|
27
|
+
# @param value [::Date]
|
28
|
+
# @return [String]
|
29
|
+
def serialize(value)
|
30
|
+
value.iso8601
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "date"
|
4
|
+
require_relative "base"
|
5
|
+
|
6
|
+
module Castkit
|
7
|
+
module Types
|
8
|
+
# Type definition for `:datetime` attributes.
|
9
|
+
#
|
10
|
+
# Handles deserialization from strings and other input into `DateTime` objects,
|
11
|
+
# and serializes `DateTime` values into ISO8601 strings.
|
12
|
+
#
|
13
|
+
# This class is used internally by Castkit when an attribute is defined with:
|
14
|
+
# `datetime :published_ad`
|
15
|
+
class DateTime < Base
|
16
|
+
# Deserializes the input value to a `DateTime` instance.
|
17
|
+
#
|
18
|
+
# @param value [Object]
|
19
|
+
# @return [::DateTime]
|
20
|
+
# @raise [ArgumentError] if parsing fails
|
21
|
+
def deserialize(value)
|
22
|
+
::DateTime.parse(value.to_s)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Serializes a `DateTime` object to ISO8601 string format.
|
26
|
+
#
|
27
|
+
# @param value [::DateTime]
|
28
|
+
# @return [String]
|
29
|
+
def serialize(value)
|
30
|
+
value.iso8601
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
require_relative "../validators/float_validator"
|
5
|
+
|
6
|
+
module Castkit
|
7
|
+
module Types
|
8
|
+
# Type definition for `:integer` attributes.
|
9
|
+
#
|
10
|
+
# Handles deserialization from raw input (e.g., strings, floats) to Float,
|
11
|
+
# applies optional numeric validation rules (e.g., `min`, `max`), and returns
|
12
|
+
# the value unchanged during serialization.
|
13
|
+
#
|
14
|
+
# This class is used internally by Castkit when an attribute is defined with:
|
15
|
+
# `integer :count`
|
16
|
+
class Float < Base
|
17
|
+
# Deserializes the input value to an Float.
|
18
|
+
#
|
19
|
+
# @param value [Object]
|
20
|
+
# @return [Float]
|
21
|
+
def deserialize(value)
|
22
|
+
value.to_f
|
23
|
+
end
|
24
|
+
|
25
|
+
# Serializes the Float value.
|
26
|
+
#
|
27
|
+
# @param value [Float]
|
28
|
+
# @return [Float]
|
29
|
+
def serialize(value)
|
30
|
+
value
|
31
|
+
end
|
32
|
+
|
33
|
+
# Validates the Float value using Castkit's FloatValidator.
|
34
|
+
#
|
35
|
+
# Supports options like `min:` and `max:`.
|
36
|
+
#
|
37
|
+
# @param value [Object]
|
38
|
+
# @param options [Hash] validation options
|
39
|
+
# @param context [Symbol, String] attribute context for error messages
|
40
|
+
# @return [void]
|
41
|
+
def validate!(value, options: {}, context: {})
|
42
|
+
Castkit::Validators::FloatValidator.call(value, options: options, context: context)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
require_relative "../validators/integer_validator"
|
5
|
+
|
6
|
+
module Castkit
|
7
|
+
module Types
|
8
|
+
# Type definition for `:integer` attributes.
|
9
|
+
#
|
10
|
+
# Handles deserialization from raw input (e.g., strings, floats) to Integer,
|
11
|
+
# applies optional numeric validation rules (e.g., `min`, `max`), and returns
|
12
|
+
# the value unchanged during serialization.
|
13
|
+
#
|
14
|
+
# This class is used internally by Castkit when an attribute is defined with:
|
15
|
+
# `integer :count`
|
16
|
+
class Integer < Base
|
17
|
+
# Deserializes the input value to an Integer.
|
18
|
+
#
|
19
|
+
# @param value [Object]
|
20
|
+
# @return [Integer]
|
21
|
+
def deserialize(value)
|
22
|
+
value.to_i
|
23
|
+
end
|
24
|
+
|
25
|
+
# Serializes the Integer value.
|
26
|
+
#
|
27
|
+
# @param value [Integer]
|
28
|
+
# @return [Integer]
|
29
|
+
def serialize(value)
|
30
|
+
value
|
31
|
+
end
|
32
|
+
|
33
|
+
# Validates the Integer value using Castkit's IntegerValidator.
|
34
|
+
#
|
35
|
+
# Supports options like `min:` and `max:`.
|
36
|
+
#
|
37
|
+
# @param value [Object]
|
38
|
+
# @param options [Hash] validation options
|
39
|
+
# @param context [Symbol, String] attribute context for error messages
|
40
|
+
# @return [void]
|
41
|
+
def validate!(value, options: {}, context: {})
|
42
|
+
Castkit::Validators::IntegerValidator.call(value, options: options, context: context)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
require_relative "../validators/string_validator"
|
5
|
+
|
6
|
+
module Castkit
|
7
|
+
module Types
|
8
|
+
# Type definition for `:string` attributes.
|
9
|
+
#
|
10
|
+
# Coerces any input to a string using `to_s`, and validates that the resulting value is a `String`.
|
11
|
+
# Supports optional format validation via a `:format` option (Regexp or Proc).
|
12
|
+
#
|
13
|
+
# This class is used internally by Castkit when an attribute is defined with:
|
14
|
+
# `string :id`
|
15
|
+
class String < Base
|
16
|
+
# Deserializes the value by coercing it to a string using `to_s`.
|
17
|
+
#
|
18
|
+
# @param value [Object]
|
19
|
+
# @return [String]
|
20
|
+
def deserialize(value)
|
21
|
+
value.to_s
|
22
|
+
end
|
23
|
+
|
24
|
+
# Serializes the value as-is.
|
25
|
+
#
|
26
|
+
# @param value [String]
|
27
|
+
# @return [String]
|
28
|
+
def serialize(value)
|
29
|
+
value
|
30
|
+
end
|
31
|
+
|
32
|
+
# Validates the value is a `String` and optionally matches a format.
|
33
|
+
#
|
34
|
+
# @param value [Object]
|
35
|
+
# @param options [Hash] validation options (e.g., `format: /regex/`)
|
36
|
+
# @param context [Symbol, String]
|
37
|
+
# @raise [Castkit::AttributeError] if validation fails
|
38
|
+
# @return [void]
|
39
|
+
def validate!(value, options: {}, context: {})
|
40
|
+
Castkit::Validators::StringValidator.call(value, options: options, context: context)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "types/base"
|
4
|
+
require_relative "types/collection"
|
5
|
+
require_relative "types/boolean"
|
6
|
+
require_relative "types/date"
|
7
|
+
require_relative "types/date_time"
|
8
|
+
require_relative "types/float"
|
9
|
+
require_relative "types/integer"
|
10
|
+
require_relative "types/string"
|
11
|
+
|
12
|
+
module Castkit
|
13
|
+
# Object types supported natively by Castkit.
|
14
|
+
module Types; end
|
15
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Castkit
|
4
|
+
module Validators
|
5
|
+
# Abstract base class for all attribute validators.
|
6
|
+
#
|
7
|
+
# Validators are responsible for enforcing value constraints (e.g., type checks,
|
8
|
+
# format rules, numerical bounds). All validators must inherit from this base class
|
9
|
+
# and implement the `#call` instance method.
|
10
|
+
#
|
11
|
+
# Supports both instance-level and class-level invocation via `.call`.
|
12
|
+
#
|
13
|
+
# @abstract Subclasses must implement `#call`.
|
14
|
+
class Base
|
15
|
+
class << self
|
16
|
+
# Entry point for validating a value using the validator class.
|
17
|
+
#
|
18
|
+
# Instantiates the validator and invokes `#call` with the provided arguments.
|
19
|
+
#
|
20
|
+
# @param value [Object] the value to validate
|
21
|
+
# @param options [Hash] additional validation options (e.g., `min`, `max`, `format`)
|
22
|
+
# @param context [Symbol, String, Hash] context for the validation (usually the attribute name)
|
23
|
+
# @return [void]
|
24
|
+
# @raise [Castkit::AttributeError] if validation fails and `raise_type_errors` is true
|
25
|
+
def call(value, options:, context:)
|
26
|
+
new.call(value, options: options, context: context)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Validates the value.
|
31
|
+
#
|
32
|
+
# @abstract
|
33
|
+
# @param value [Object] the value to validate
|
34
|
+
# @param options [Hash] validation options (e.g., min, max, format)
|
35
|
+
# @param context [Symbol, String, Hash] context for validation errors
|
36
|
+
# @return [void]
|
37
|
+
# @raise [NotImplementedError] if not implemented in subclass
|
38
|
+
def call(value, options:, context:)
|
39
|
+
raise NotImplementedError, "#{self.class.name} must implement `#call`"
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
|
44
|
+
# Emits or raises a type error depending on global configuration.
|
45
|
+
#
|
46
|
+
# @param type [Symbol] the expected type (e.g., `:integer`)
|
47
|
+
# @param value [Object, nil] the received value
|
48
|
+
# @param context [Symbol, String, nil] context to include in error messages
|
49
|
+
# @raise [Castkit::AttributeError] if `raise_type_errors` is enabled
|
50
|
+
# @return [void]
|
51
|
+
def type_error!(type, value, context: nil)
|
52
|
+
message = "#{context || "value"} must be a #{type}, got #{value}"
|
53
|
+
raise Castkit::AttributeError, message if Castkit.configuration.raise_type_errors
|
54
|
+
|
55
|
+
Castkit.warning "[Castkit] #{message}" if Castkit.configuration.enable_warnings
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
|
5
|
+
module Castkit
|
6
|
+
module Validators
|
7
|
+
# Validator for boolean attributes.
|
8
|
+
#
|
9
|
+
# Accepts various representations of boolean values, including strings and integers.
|
10
|
+
# Converts common truthy/falsy string values into booleans, otherwise raises a type error.
|
11
|
+
#
|
12
|
+
# This validator is typically used internally by `Castkit::Types::Boolean`.
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
# validator = Castkit::Validators::BooleanValidator.new
|
16
|
+
# validator.call("true", _options: {}, context: :enabled) # => true
|
17
|
+
# validator.call("0", _options: {}, context: :enabled) # => false
|
18
|
+
# validator.call("nope", _options: {}, context: :enabled) # raises Castkit::AttributeError
|
19
|
+
class BooleanValidator
|
20
|
+
# Validates the Boolean value.
|
21
|
+
#
|
22
|
+
# @param value [Object] the input to validate
|
23
|
+
# @param _options [Hash] unused, provided for consistency with other validators
|
24
|
+
# @param context [Symbol, String] the attribute name or path for error messages
|
25
|
+
# @return [Boolean]
|
26
|
+
# @raise [Castkit::AttributeError] if the value is not a recognizable boolean
|
27
|
+
def call(value, _options:, context:)
|
28
|
+
case value.to_s.downcase
|
29
|
+
when "true", "1"
|
30
|
+
true
|
31
|
+
when "false", "0"
|
32
|
+
false
|
33
|
+
else
|
34
|
+
type_error!(:boolean, value, context: context)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../validators/base"
|
4
|
+
|
5
|
+
module Castkit
|
6
|
+
module Validators
|
7
|
+
# Validator for array (collection) attributes.
|
8
|
+
#
|
9
|
+
# Ensures that the provided value is an instance of `Array`. This validator is
|
10
|
+
# typically used by `Castkit::Types::Collection` for attributes defined as arrays.
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# validator = Castkit::Validators::CollectionValidator.new
|
14
|
+
# validator.call([1, 2, 3], _options: {}, context: :tags) # => passes
|
15
|
+
# validator.call("foo", _options: {}, context: :tags) # raises Castkit::AttributeError
|
16
|
+
class CollectionValidator < Castkit::Validators::Base
|
17
|
+
# Validates that the value is an Array.
|
18
|
+
#
|
19
|
+
# @param value [Object] the value to validate
|
20
|
+
# @param _options [Hash] unused, for interface consistency
|
21
|
+
# @param context [Symbol, String] the field or context for error messaging
|
22
|
+
# @return [void]
|
23
|
+
# @raise [Castkit::AttributeError] if value is not an Array
|
24
|
+
def call(value, _options:, context:)
|
25
|
+
type_error!(:array, value, context: context) unless value.is_a?(::Array)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "numeric_validator"
|
4
|
+
|
5
|
+
module Castkit
|
6
|
+
module Validators
|
7
|
+
# Validator for Float attributes.
|
8
|
+
#
|
9
|
+
# Ensures the value is a `Float`, and applies any numeric bounds (`min`, `max`)
|
10
|
+
# defined in the attribute options. Inherits shared logic from `NumericValidator`.
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# validator = Castkit::Validators::FloatValidator.new
|
14
|
+
# validator.call(3.14, options: { min: 0.0 }, context: :price) # => passes
|
15
|
+
# validator.call(42, options: {}, context: :price) # raises Castkit::AttributeError
|
16
|
+
class FloatValidator < Castkit::Validators::NumericValidator
|
17
|
+
# Validates that the value is a Float and within optional bounds.
|
18
|
+
#
|
19
|
+
# @param value [Object, nil] the value to validate
|
20
|
+
# @param options [Hash] validation options (e.g., `min`, `max`)
|
21
|
+
# @param context [Symbol, String] the attribute name or key for error messages
|
22
|
+
# @raise [Castkit::AttributeError] if value is not a Float or out of range
|
23
|
+
# @return [void]
|
24
|
+
def call(value, options:, context:)
|
25
|
+
return type_error!(:float, value, context: context) unless value.is_a?(::Float)
|
26
|
+
|
27
|
+
super
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "numeric_validator"
|
4
|
+
|
5
|
+
module Castkit
|
6
|
+
module Validators
|
7
|
+
# Validator for Integer attributes.
|
8
|
+
#
|
9
|
+
# Ensures the value is an instance of `Integer` and optionally checks numerical constraints
|
10
|
+
# such as `min` and `max`, inherited from `Castkit::Validators::NumericValidator`.
|
11
|
+
#
|
12
|
+
# @example Validating an Integer attribute
|
13
|
+
# IntegerValidator.call(42, options: { min: 10, max: 100 }, context: :count)
|
14
|
+
#
|
15
|
+
# @see Castkit::Validators::NumericValidator
|
16
|
+
class IntegerValidator < Castkit::Validators::NumericValidator
|
17
|
+
# Validates the Integer value.
|
18
|
+
#
|
19
|
+
# @param value [Object, nil] the value to validate
|
20
|
+
# @param options [Hash] validation options (e.g., `min`, `max`)
|
21
|
+
# @param context [Symbol, String] the attribute name or context for error messages
|
22
|
+
# @raise [Castkit::AttributeError] if the value is not an Integer or fails validation rules
|
23
|
+
# @return [void]
|
24
|
+
def call(value, options:, context:)
|
25
|
+
return type_error!(:integer, value, context: context) unless value.is_a?(::Integer)
|
26
|
+
|
27
|
+
super
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -1,13 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "
|
3
|
+
require_relative "base"
|
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::
|
10
|
+
class NumericValidator < Castkit::Validators::Base
|
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 "
|
3
|
+
require_relative "base"
|
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::
|
10
|
+
class StringValidator < Castkit::Validators::Base
|
11
11
|
# Validates the string value.
|
12
12
|
#
|
13
13
|
# @param value [Object] the value to validate
|
@@ -16,8 +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
|
-
|
20
|
-
|
19
|
+
return type_error!(:string, value, context: context) unless value.is_a?(::String)
|
21
20
|
return unless options[:format]
|
22
21
|
|
23
22
|
case options[:format]
|
data/lib/castkit/version.rb
CHANGED
data/lib/castkit.rb
CHANGED
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "thor/group"
|
4
|
+
require "castkit/inflector"
|
5
|
+
|
6
|
+
module Castkit
|
7
|
+
module Generators
|
8
|
+
# Abstract base class for all Castkit generators.
|
9
|
+
#
|
10
|
+
# Provides standard behavior for generating a component and optional spec file from
|
11
|
+
# ERB templates. Subclasses must define a `component` (e.g., `:type`, `:contract`)
|
12
|
+
# and may override `config` or `default_values`.
|
13
|
+
#
|
14
|
+
# Template variables are injected using the `config` hash, which includes:
|
15
|
+
# - `:name` – underscored version of the component name
|
16
|
+
# - `:class_name` – PascalCase version of the component name
|
17
|
+
#
|
18
|
+
# @abstract
|
19
|
+
class Base < Thor::Group
|
20
|
+
include Thor::Actions
|
21
|
+
|
22
|
+
class << self
|
23
|
+
# Sets or retrieves the component type (e.g., :type, :data_object).
|
24
|
+
#
|
25
|
+
# @param value [Symbol, nil]
|
26
|
+
# @return [Symbol]
|
27
|
+
def component(value = nil)
|
28
|
+
value.nil? ? @component : (@component = value)
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [String] the root path to look for templates
|
32
|
+
def source_root
|
33
|
+
File.expand_path("templates", __dir__)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
argument :name, desc: "The name of the component to generate"
|
38
|
+
class_option :spec, type: :boolean, default: true, desc: "Also generate a spec file"
|
39
|
+
|
40
|
+
# Creates the main component file using a template.
|
41
|
+
#
|
42
|
+
# Template: `component.rb.tt`
|
43
|
+
# Target: `lib/castkit/#{component}s/#{name}.rb`
|
44
|
+
#
|
45
|
+
# @return [void]
|
46
|
+
def create_component
|
47
|
+
template(
|
48
|
+
"#{self.class.component}.rb.tt",
|
49
|
+
"lib/castkit/#{self.class.component}s/#{config[:name]}.rb", **config
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Creates the associated spec file, if enabled.
|
54
|
+
#
|
55
|
+
# Template: `component_spec.rb.tt`
|
56
|
+
# Target: `spec/castkit/#{component}s/#{name}_spec.rb`
|
57
|
+
#
|
58
|
+
# @return [void]
|
59
|
+
def create_spec
|
60
|
+
return unless options[:spec]
|
61
|
+
|
62
|
+
template(
|
63
|
+
"#{self.class.component}_spec.rb.tt",
|
64
|
+
"spec/castkit/#{self.class.component}s/#{config[:name]}_spec.rb", **config
|
65
|
+
)
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
# Default values for test inputs based on type.
|
71
|
+
#
|
72
|
+
# These are used in spec templates to provide sample data.
|
73
|
+
#
|
74
|
+
# @return [Hash{Symbol => Object}]
|
75
|
+
def default_values
|
76
|
+
{
|
77
|
+
string: '"example"',
|
78
|
+
integer: 42,
|
79
|
+
float: 3.14,
|
80
|
+
boolean: true,
|
81
|
+
array: [],
|
82
|
+
hash: {}
|
83
|
+
}
|
84
|
+
end
|
85
|
+
|
86
|
+
# Returns the default config hash passed into templates.
|
87
|
+
#
|
88
|
+
# @return [Hash{Symbol => Object}]
|
89
|
+
def config
|
90
|
+
{
|
91
|
+
name: Castkit::Inflector.underscore(name),
|
92
|
+
class_name: Castkit::Inflector.pascalize(name)
|
93
|
+
}
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "thor/group"
|
4
|
+
require "castkit/inflector"
|
5
|
+
require_relative "base"
|
6
|
+
|
7
|
+
module Castkit
|
8
|
+
module Generators
|
9
|
+
# Generator for creating Castkit contracts.
|
10
|
+
#
|
11
|
+
# Generates a contract class and optionally a corresponding spec file.
|
12
|
+
# Accepts an optional list of attribute definitions in the form `name:type`.
|
13
|
+
#
|
14
|
+
# Example:
|
15
|
+
# $ castkit generate contract User name:string age:integer
|
16
|
+
#
|
17
|
+
# This will generate:
|
18
|
+
# - lib/castkit/contracts/user.rb
|
19
|
+
# - spec/castkit/contracts/user_spec.rb
|
20
|
+
#
|
21
|
+
# @see Castkit::Generators::Base
|
22
|
+
class Contract < Castkit::Generators::Base
|
23
|
+
component :contract
|
24
|
+
|
25
|
+
argument :fields, type: :array, default: [], desc: "Attribute definitions (e.g., name:string age:integer)"
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
# @return [Hash] configuration passed into templates
|
30
|
+
def config
|
31
|
+
super.merge(
|
32
|
+
attributes: parsed_fields,
|
33
|
+
default_values: default_values,
|
34
|
+
invalid_types: invalid_types
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Parses `name:type` fields into attribute definitions.
|
39
|
+
#
|
40
|
+
# @return [Array<Hash{Symbol => Object}>] list of parsed attribute hashes
|
41
|
+
def parsed_fields
|
42
|
+
fields.map do |field|
|
43
|
+
name, type = field.split(":")
|
44
|
+
{ name: name, type: (type || "string").to_sym }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Default "invalid" test values for each supported type.
|
49
|
+
#
|
50
|
+
# Used in generated specs to simulate bad input.
|
51
|
+
#
|
52
|
+
# @return [Hash{Symbol => Object}]
|
53
|
+
def invalid_types
|
54
|
+
{
|
55
|
+
string: true,
|
56
|
+
integer: '"invalid"',
|
57
|
+
float: '"bad"',
|
58
|
+
boolean: '"not_a_bool"',
|
59
|
+
date: 123,
|
60
|
+
datetime: [],
|
61
|
+
array: {},
|
62
|
+
hash: [],
|
63
|
+
uuid: 999
|
64
|
+
}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|