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,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
@@ -0,0 +1,48 @@
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 DataObject classes.
10
+ #
11
+ # Generates a DataObject class and an optional spec file with attribute definitions.
12
+ # Accepts a list of field definitions in the form `name:type`.
13
+ #
14
+ # Example:
15
+ # $ castkit generate dataobject User name:string active:boolean
16
+ #
17
+ # This will generate:
18
+ # - lib/castkit/data_objects/user.rb
19
+ # - spec/castkit/data_objects/user_spec.rb
20
+ #
21
+ # @see Castkit::Generators::Base
22
+ class DataObject < Castkit::Generators::Base
23
+ component :data_object
24
+
25
+ argument :fields, type: :array, default: [], desc: "Attribute definitions (e.g., name:string active:boolean)"
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
+ )
35
+ end
36
+
37
+ # Parses `name:type` fields into attribute definitions.
38
+ #
39
+ # @return [Array<Hash{Symbol => Object}>] list of parsed attribute hashes
40
+ def parsed_fields
41
+ fields.map do |field|
42
+ name, type = field.split(":")
43
+ { name: name, type: (type || "string").to_sym }
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,25 @@
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 plugin modules.
10
+ #
11
+ # This generator will produce a module under `Castkit::Plugins::<ClassName>` and an optional spec file.
12
+ #
13
+ # Example:
14
+ # $ castkit generate plugin Oj
15
+ #
16
+ # This will generate:
17
+ # - lib/castkit/plugins/oj.rb
18
+ # - spec/castkit/plugins/oj_spec.rb
19
+ #
20
+ # @see Castkit::Generators::Base
21
+ class Plugin < Castkit::Generators::Base
22
+ component :plugin
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,28 @@
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 a custom Castkit serializer.
10
+ #
11
+ # Serializers inherit from `Castkit::Serializers::Base` and define a custom `#call` method
12
+ # for rendering a `Castkit::DataObject` into a hash representation.
13
+ #
14
+ # Example usage:
15
+ # $ castkit generate serializer Custom
16
+ #
17
+ # Generates:
18
+ # - lib/castkit/serializers/custom.rb
19
+ # - spec/castkit/serializers/custom_spec.rb
20
+ #
21
+ # These files scaffold a `Castkit::Serializers::Custom` serializer with the correct base class.
22
+ #
23
+ # @see Castkit::Generators::Base
24
+ class Serializer < Castkit::Generators::Base
25
+ component :serializer
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "castkit/attributes/definition"
4
+
5
+ module Castkit
6
+ module Attributes
7
+ # Attribute definition for <%= config[:class_name] %>.
8
+ #
9
+ # This definition can be reused across DataObjects to avoid repeating shared options.
10
+ #
11
+ # @example
12
+ # class UserDto < Castkit::DataObject
13
+ # include Castkit::Attributes
14
+ #
15
+ # attribute :name, using: <%= config[:class_name] %>
16
+ # end
17
+ class <%= config[:class_name] %> < Castkit::Attributes::Definition
18
+ <% if config[:type].empty? %># type :string<% else %>type :<%= config[:type] %><% end %>
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+ require "castkit/attributes/<%= config[:name] %>"
5
+
6
+ RSpec.describe Castkit::Attributes::<%= config[:class_name] %> do
7
+ subject(:attribute) { described_class }
8
+
9
+ let(:type) { attribute.definition[:type] }
10
+ let(:options) { attribute.definition[:options] }
11
+
12
+ describe ".definition" do
13
+ it "returns a valid definition" do
14
+ expect(type).to be_a(Symbol).or be_a(Class)
15
+ expect(options).to be_a(Hash)
16
+ end
17
+ end
18
+
19
+ describe "defined type" do
20
+ it "returns defined type" do
21
+ expect(type).to eq(:<%= config[:type] %>)
22
+ end
23
+ end
24
+
25
+ describe "defined options" do
26
+ it "includes expected keys" do
27
+ expect(options.keys).to all(be_a(Symbol))
28
+ end
29
+
30
+ it "includes known default keys" do
31
+ expect(options.keys).to include(:required, :access)
32
+ end
33
+ end
34
+
35
+ describe "DSL behavior" do
36
+ it "overrides required and access values" do
37
+ expect(options[:required]).to be(true)
38
+ expect(options[:access]).to eq(%i[read write])
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "castkit/contracts/base"
4
+
5
+ module Castkit
6
+ module Contracts
7
+ # Contract definition for <%= config[:class_name] %>.
8
+ #
9
+ # This contract can be used to validate structured input like:
10
+ #
11
+ # @example Validating input (soft)
12
+ # result = Castkit::Contracts::<%= config[:class_name] %>.validate(params)
13
+ # puts result.inspect # Castkit::Contract::Result instance
14
+ #
15
+ # @example Validating input (hard)
16
+ # being
17
+ # result = Castkit::Contracts::<%= config[:class_name] %>.validate!(params)
18
+ # rescue Castkit::ContractError => e
19
+ # puts e.errors
20
+ # end
21
+ class <%= config[:class_name] %> < Castkit::Contract::Base<% if config[:attributes].empty? %>
22
+ # string :id<% else %><% config[:attributes].each do |attr| %>
23
+ <%= attr[:type] %> :<%= attr[:name] %><% end %><% end %>
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+ require "castkit/contracts/<%= config[:name] %>"
5
+
6
+ RSpec.describe Castkit::Contracts::<%= config[:class_name] %> do
7
+ subject(:contract) { described_class }
8
+ <% if config[:attributes].empty? %>
9
+ let(:attributes) { {} }
10
+ <% else %>
11
+ let(:attributes) do
12
+ {<% config[:attributes].each do |attr| %>
13
+ <%= attr[:name] %>: <%= config[:default_values].fetch(attr[:type], "nil") %>,<% end %>
14
+ }
15
+ end
16
+ <% end %>
17
+ it "is a Castkit::Contract" do
18
+ expect(contract).to be < Castkit::Contract::Base
19
+ end
20
+ <% if config[:attributes].any? %>
21
+ describe ".validate" do
22
+ it "returns success with valid input" do
23
+ result = contract.validate(attributes)
24
+ expect(result).to be_success
25
+ end
26
+
27
+ it "returns failure with missing required fields" do
28
+ field = contract.attributes.keys.first
29
+ contract.attributes[field].options[:required] = true
30
+
31
+ result = contract.validate(attributes.reject { |k| k == field })
32
+ expect(result).to be_failure
33
+ expect(result.errors).to include({ field => "#{field} is required" })
34
+ end
35
+
36
+ it "returns failure with invalid value types" do
37
+ field = :<%= config[:attributes].first[:name] %>
38
+ field_type = :<%= config[:attributes].first[:type] %>
39
+
40
+ result = contract.validate(attributes.merge({ field => <%= config[:invalid_types].fetch(config[:attributes].first[:type], nil) %> }))
41
+ expect(result).to be_failure
42
+ expect(result.errors).to include({ field => "#{field} must be a #{field_type}" })
43
+ end
44
+ end
45
+
46
+ describe ".validate!" do
47
+ it "returns success with valid input" do
48
+ result = contract.validate!(attributes)
49
+ expect(result).to be_success
50
+ end
51
+
52
+ it "raises an error with missing required fields" do
53
+ field = contract.attributes.keys.first
54
+ contract.attributes[field].options[:required] = true
55
+
56
+ expect do
57
+ contract.validate!(attributes.reject { |k| k == field })
58
+ rescue Castkit::ContractError => e
59
+ expect(e.errors).to include({ field => "#{field} is required" })
60
+ raise e
61
+ end.to raise_error(Castkit::ContractError)
62
+ end
63
+
64
+ it "raises an error with invalid value types" do
65
+ field = :<%= config[:attributes].first[:name] %>
66
+ field_type = :<%= config[:attributes].first[:type] %>
67
+
68
+ expect do
69
+ contract.validate!(attributes.merge({ field => <%= config[:invalid_types].fetch(config[:attributes].first[:type], nil) %> }))
70
+ rescue Castkit::ContractError => e
71
+ expect(e.errors).to include({ field => "#{field} must be a #{field_type}" })
72
+ raise e
73
+ end.to raise_error(Castkit::ContractError)
74
+ end
75
+ end<% end %>
76
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "castkit/data_object"
4
+
5
+ module Castkit
6
+ module DataObjects
7
+ # Data transfer object for <%= config[:class_name] %>.
8
+ #
9
+ # @example Instantiation
10
+ # <%= config[:class_name] %>.new(id: "123", name: "example")
11
+ class <%= config[:class_name] %> < Castkit::DataObject
12
+ <% config[:attributes].each do |attr| -%>
13
+ <%= attr[:type] %> :<%= attr[:name] %>
14
+ <% end -%>
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+ require "castkit/data_objects/<%= config[:name] %>"
5
+
6
+ RSpec.describe Castkit::DataObjects::<%= config[:class_name] %> do
7
+ subject(:instance) { described_class.new(attributes) }
8
+ <% if config[:attributes].empty? %>
9
+ let(:attributes) { {} }
10
+ <% else %>
11
+ let(:attributes) do
12
+ {<% config[:attributes].each do |attr| %>
13
+ <%= attr[:name] %>: <%= config[:default_values].fetch(attr[:type], nil) %>,<% end %>
14
+ }
15
+ end
16
+ <% end %>
17
+ it "is a Castkit::DataObject" do
18
+ expect(described_class).to be < Castkit::DataObject
19
+ end
20
+ <% config[:attributes].each do |attr| %>
21
+ describe "#<%= attr[:name] %>" do
22
+ it "is defined on the DTO" do
23
+ expect(described_class.attributes.keys).to include(:<%= attr[:name] %>)
24
+ end
25
+
26
+ it "returns the attribute options" do
27
+ # test for options set on the attribute
28
+ # `<%= attr[:type] %> :<%= attr[:name] %>, required: true`
29
+ # expect(described_class.attributes[:<%= attr[:name] %>).to include(required: true)
30
+ end
31
+
32
+ it "returns the expected value" do
33
+ expect(instance.<%= attr[:name] %>).to eq(attributes[:<%= attr[:name] %>])
34
+ end
35
+ end
36
+ <% end %>end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Castkit
4
+ module Plugins
5
+ # <%= config[:class_name] %> plugin for Castkit::DataObject.
6
+ #
7
+ # This plugin can be enabled via:
8
+ #
9
+ # class MyDto < Castkit::DataObject
10
+ # enable_plugins :<%= config[:name] %>
11
+ # end
12
+ #
13
+ # Or globally:
14
+ #
15
+ # Castkit.configure do |config|
16
+ # config.register_plugin(:<%= config[:name] %>, Castkit::Plugins::<%= config[:class_name] %>)
17
+ # config.default_plugins << :<%= config[:name] %>
18
+ # end
19
+ module <%= config[:class_name] %>
20
+ # Optional setup hook called during plugin activation
21
+ #
22
+ # @param klass [Class<Castkit::DataObject>]
23
+ # @return [void]
24
+ def self.setup!(klass)
25
+ # Custom setup logic here
26
+ end
27
+
28
+ # Optionally define an Extension module to be included into the DataObject class
29
+ #
30
+ # module Extension
31
+ # def custom_behavior
32
+ # # ...
33
+ # end
34
+ # end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+ require "castkit/plugins/<%= config[:name] %>"
5
+
6
+ RSpec.describe Castkit::Plugins::<%= config[:class_name] %> do
7
+ let(:plugin) { described_class }
8
+
9
+ describe ".setup!" do
10
+ let(:klass) do
11
+ Class.new(Castkit::DataObject)
12
+ end
13
+
14
+ it "can be setup on a dataobject class" do
15
+ expect { plugin.setup!(klass) }.not_to raise_error
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "castkit/serializers/base"
4
+
5
+ module Castkit
6
+ module Serializers
7
+ # Serializer for <%= config[:class_name] %> DTOs.
8
+ #
9
+ # This can be applied to a DataObject with:
10
+ #
11
+ # @example
12
+ # class MyDto < Castkit::DataObject
13
+ # serializer Castkit::Serializers::<%= config[:class_name] %>
14
+ # end
15
+ class <%= config[:class_name] %> < Castkit::Serializers::Base
16
+ # Returns a serialized hash version of the object.
17
+ #
18
+ # @return [Hash]
19
+ def call
20
+ object.to_h
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+ require "castkit/serializers/<%= config[:name] %>"
5
+
6
+ RSpec.describe Castkit::Serializers::<%= config[:class_name] %> do
7
+ let(:object) { double("Castkit::DataObject", to_h: { foo: "bar" }) }
8
+
9
+ subject(:serializer) { described_class.new(object) }
10
+
11
+ it "serializes using #call" do
12
+ expect(serializer.call).to eq({ foo: "bar" })
13
+ end
14
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "castkit/types/base"
4
+
5
+ module Castkit
6
+ module Types
7
+ # Type definition for <%= config[:class_name] %> (:<%= config[:name] %>) attributes.
8
+ #
9
+ # This class is used internally by Castkit when an attribute is defined with:
10
+ #
11
+ # @example Registering <%= config[:class_name] %> as a valid type
12
+ # Castkit.configure do |config|
13
+ # config.register_type(:<%= config[:name] %>, Castkit::Types::<%= config[:class_name] %>, aliases: %i[custom_alias])
14
+ # end
15
+ #
16
+ # @example Defining an attribute
17
+ # class Data < Castkit::DataObject
18
+ # <%= config[:name] %> :attribute_name
19
+ # attribute :attribute_name, :<%= config[:name] %>
20
+ # end
21
+ #
22
+ # @example Defining an attribute using an alias
23
+ # class Data < Castkit::DataObject
24
+ # custom_alias: :attribute_name
25
+ # attribute :attribute_name, :custom_alias
26
+ # end
27
+ class <%= config[:class_name] %> < Castkit::Types::Base
28
+ # Deserializes the input value to a <%= config[:class_name] %> instance.
29
+ #
30
+ # @param value [Object, nil] the value to deserialize
31
+ # @return [<%= config[:class_name] %>] the deserialized value
32
+ def deserialize(value)
33
+ # deserialization logic
34
+ # value.to_s
35
+ end
36
+
37
+ # Serializes the <%= config[:class_name] %> value.
38
+ #
39
+ # @param value [<%= config[:class_name] %>] the value to serialize
40
+ # @return [Object] the serialized value
41
+ def serialize(value)
42
+ # serialization logic
43
+ # value.to_s
44
+ end
45
+
46
+ # Validates the input value with a custom Castkit::Validators::<%= config[:class_name] %> validator.
47
+ #
48
+ # @param value [Object, nil] the value to validate
49
+ # @param options [Hash] the validation options
50
+ # @param context [Hash] the validation context
51
+ def validate!(value, options = {}, context = {})
52
+ # validation logic
53
+ # Castkit::Validators::<%= config[:class_name] %>.call(value, options: options, context: context)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+ require "castkit/types/<%= config[:name] %>"
5
+
6
+ # Spec for Castkit::Types::<%= config[:class_name] %>
7
+ RSpec.describe Castkit::Types::<%= config[:class_name] %> do
8
+ subject(:type) { described_class.new }
9
+
10
+ it "is a subclass of Castkit::Types::Base" do
11
+ expect(described_class).to be < Castkit::Types::Base
12
+ end
13
+
14
+ describe "#deserialize" do
15
+ let(:input) { "input" }
16
+
17
+ it "converts a valid input" do
18
+ # expect(type.deserialize(input)).to eq(expected_value)
19
+ end
20
+ end
21
+
22
+ describe "#serialize" do
23
+ let(:value) { "value" }
24
+
25
+ it "converts the value to a serializable format" do
26
+ # expect(type.serialize(value)).to eq(expected_output)
27
+ end
28
+ end
29
+
30
+ describe "#validate!" do
31
+ let(:valid_value) { "valid" }
32
+ let(:invalid_value) { nil }
33
+
34
+ it "does not raise for valid input" do
35
+ # expect { type.validate!(valid_value) }.not_to raise_error
36
+ end
37
+
38
+ it "raises for invalid input" do
39
+ # expect { type.validate!(invalid_value) }.to raise_error(Castkit::AttributeError)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "castkit/validators/base"
4
+
5
+ module Castkit
6
+ module Validators
7
+ # Validator for :<%= config[:name] %> attributes.
8
+ #
9
+ # Subclass of Castkit::Validators::Base. Used automatically by attributes or types that declare it.
10
+ #
11
+ # @example Manual use:
12
+ # Castkit::Validators::<%= config[:class_name] %>.call(value, context: :my_field)
13
+ class <%= config[:class_name] %> < Castkit::Validators::Base
14
+ # Validates the value and raises a Castkit::AttributeError if invalid.
15
+ #
16
+ # @param value [Object] The value to validate
17
+ # @param options [Hash] Optional validation options
18
+ # @param context [Symbol] The attribute or context key for error messages
19
+ def call(value, options: {}, context: nil)
20
+ raise Castkit::AttributeError, "#{context} must be present" if value.nil?
21
+
22
+ value
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+ require "castkit/validators/<%= config[:name] %>"
5
+
6
+ RSpec.describe Castkit::Validators::<%= config[:class_name] %> do
7
+ subject(:validator) { described_class.new }
8
+
9
+ let(:context) { :<%= config[:name] %> }
10
+
11
+ describe "#call" do
12
+ it "returns the value if valid" do
13
+ valid = "example"
14
+ expect(validator.call(valid, context: context)).to eq(valid)
15
+ end
16
+
17
+ it "raises for invalid values" do
18
+ expect {
19
+ validator.call(nil, context: context)
20
+ }.to raise_error(Castkit::AttributeError, /#{context} must be present/)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,29 @@
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 a new Castkit type.
10
+ #
11
+ # Types define custom deserialization, serialization, and validation behavior
12
+ # for attributes used in `Castkit::DataObject` or `Castkit::Contract`.
13
+ #
14
+ # Example usage:
15
+ # $ castkit generate type Money
16
+ #
17
+ # Generates:
18
+ # - lib/castkit/types/money.rb
19
+ # - spec/castkit/types/money_spec.rb
20
+ #
21
+ # These files scaffold a `Castkit::Types::Money` class inheriting from `Castkit::Types::Base`,
22
+ # along with a basic RSpec test suite.
23
+ #
24
+ # @see Castkit::Generators::Base
25
+ class Type < Castkit::Generators::Base
26
+ component :type
27
+ end
28
+ end
29
+ end