castkit 0.2.0 → 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 +118 -119
- data/CHANGELOG.md +1 -1
- data/README.md +287 -11
- data/castkit.gemspec +1 -0
- data/lib/castkit/castkit.rb +5 -2
- 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 +31 -8
- data/lib/castkit/contract/{generic.rb → base.rb} +5 -5
- data/lib/castkit/contract/result.rb +2 -2
- data/lib/castkit/contract.rb +5 -5
- data/lib/castkit/data_object.rb +11 -7
- data/lib/castkit/ext/data_object/contract.rb +1 -1
- data/lib/castkit/ext/data_object/plugins.rb +86 -0
- data/lib/castkit/inflector.rb +1 -1
- 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/{generic.rb → base.rb} +6 -7
- data/lib/castkit/types/boolean.rb +14 -10
- data/lib/castkit/types/collection.rb +13 -2
- data/lib/castkit/types/date.rb +2 -2
- data/lib/castkit/types/date_time.rb +2 -2
- data/lib/castkit/types/float.rb +5 -5
- data/lib/castkit/types/integer.rb +5 -5
- data/lib/castkit/types/string.rb +2 -2
- data/lib/castkit/types.rb +1 -1
- 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/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 +50 -7
- data/lib/castkit/default_serializer.rb +0 -154
- data/lib/castkit/serializer.rb +0 -92
- data/lib/castkit/validators/base_validator.rb +0 -39
@@ -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
|
@@ -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,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Castkit
|
4
|
+
module Contracts
|
5
|
+
# Contract definition for <%= config[:class_name] %>.
|
6
|
+
#
|
7
|
+
# This contract can be used to validate structured input like:
|
8
|
+
#
|
9
|
+
# @example Validating input (soft)
|
10
|
+
# result = Castkit::Contracts::<%= config[:class_name] %>.validate(params)
|
11
|
+
# puts result.inspect # Castkit::Contract::Result instance
|
12
|
+
#
|
13
|
+
# @example Validating input (hard)
|
14
|
+
# being
|
15
|
+
# result = Castkit::Contracts::<%= config[:class_name] %>.validate!(params)
|
16
|
+
# rescue Castkit::ContractError => e
|
17
|
+
# puts e.errors
|
18
|
+
# end
|
19
|
+
class <%= config[:class_name] %> < Castkit::Contract::Base<% if config[:attributes].empty? %>
|
20
|
+
# string :id<% else %><% config[:attributes].each do |attr| %>
|
21
|
+
<%= attr[:type] %> :<%= attr[:name] %><% end %><% end %>
|
22
|
+
end
|
23
|
+
end
|
24
|
+
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,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Castkit
|
4
|
+
module DataObjects
|
5
|
+
# Data transfer object for <%= config[:class_name] %>.
|
6
|
+
#
|
7
|
+
# @example Instantiation
|
8
|
+
# <%= config[:class_name] %>.new(id: "123", name: "example")
|
9
|
+
class <%= config[:class_name] %> < Castkit::DataObject
|
10
|
+
<% config[:attributes].each do |attr| -%>
|
11
|
+
<%= attr[:type] %> :<%= attr[:name] %>
|
12
|
+
<% end -%>
|
13
|
+
end
|
14
|
+
end
|
15
|
+
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,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Castkit
|
4
|
+
module Types
|
5
|
+
# Type definition for <%= config[:class_name] %> (:<%= config[:name] %>) attributes.
|
6
|
+
#
|
7
|
+
# This class is used internally by Castkit when an attribute is defined with:
|
8
|
+
#
|
9
|
+
# @example Registering <%= config[:class_name] %> as a valid type
|
10
|
+
# Castkit.configure do |config|
|
11
|
+
# config.register_type(:<%= config[:name] %>, Castkit::Types::<%= config[:class_name] %>, aliases: %i[custom_alias])
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# @example Defining an attribute
|
15
|
+
# class Data < Castkit::DataObject
|
16
|
+
# <%= config[:name] %> :attribute_name
|
17
|
+
# attribute :attribute_name, :<%= config[:name] %>
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# @example Defining an attribute using an alias
|
21
|
+
# class Data < Castkit::DataObject
|
22
|
+
# custom_alias: :attribute_name
|
23
|
+
# attribute :attribute_name, :custom_alias
|
24
|
+
# end
|
25
|
+
class <%= config[:class_name] %> < Castkit::Types::Base
|
26
|
+
# Deserializes the input value to a <%= config[:class_name] %> instance.
|
27
|
+
#
|
28
|
+
# @param value [Object, nil] the value to deserialize
|
29
|
+
# @return [<%= config[:class_name] %>] the deserialized value
|
30
|
+
def deserialize(value)
|
31
|
+
# deserialization logic
|
32
|
+
# value.to_s
|
33
|
+
end
|
34
|
+
|
35
|
+
# Serializes the <%= config[:class_name] %> value.
|
36
|
+
#
|
37
|
+
# @param value [<%= config[:class_name] %>] the value to serialize
|
38
|
+
# @return [Object] the serialized value
|
39
|
+
def serialize(value)
|
40
|
+
# serialization logic
|
41
|
+
# value.to_s
|
42
|
+
end
|
43
|
+
|
44
|
+
# Validates the input value with a custom Castkit::Validators::<%= config[:class_name] %> validator.
|
45
|
+
#
|
46
|
+
# @param value [Object, nil] the value to validate
|
47
|
+
# @param options [Hash] the validation options
|
48
|
+
# @param context [Hash] the validation context
|
49
|
+
def validate!(value, options = {}, context = {})
|
50
|
+
# validation logic
|
51
|
+
# Castkit::Validators::<%= config[:class_name] %>.call(value, options: options, context: context)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
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_relative "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
|