
Security News
Astral Launches pyx: A Python-Native Package Registry
Astral unveils pyx, a Python-native package registry in beta, designed to speed installs, enhance security, and integrate deeply with uv.
Castkit is a lightweight, type-safe data object system for Ruby. It provides a declarative DSL for defining data transfer objects (DTOs) with built-in support for typecasting, validation, nested data structures, serialization, deserialization, and contract-driven programming.
Inspired by tools like Jackson (Java) and Python dataclasses, Castkit brings structured data modeling to Ruby in a way that emphasizes:
Castkit is designed to work seamlessly in service-oriented and API-driven architectures, providing structure without overreach.
Castkit provides a global configuration interface to customize behavior across the entire system. You can configure Castkit by passing a block to Castkit.configure
.
Castkit.configure do |config|
config.enable_warnings = false
config.enforce_typing = true
end
Option | Type | Default | Description |
---|---|---|---|
enable_warnings | Boolean | true | Enables runtime warnings for misconfigurations. |
enforce_typing | Boolean | true | Raises if type mismatch during load (e.g., true vs. "true" ). |
enforce_attribute_access | Boolean | true | Raises if an unknown access level is defined. |
enforce_unwrapped_prefix | Boolean | true | Requires unwrapped: true when using attribute prefixes. |
enforce_array_options | Boolean | true | Raises if an array attribute is missing the of: option. |
raise_type_errors | Boolean | true | Raises if an unregistered or invalid type is used. |
strict_by_default | Boolean | true | Applies strict: true by default to all DTOs and Contracts. |
Castkit comes with built-in support for primitive types and allows registration of custom ones:
{
array: Castkit::Types::Collection,
boolean: Castkit::Types::Boolean,
date: Castkit::Types::Date,
datetime: Castkit::Types::DateTime,
float: Castkit::Types::Float,
hash: Castkit::Types::Base,
integer: Castkit::Types::Integer,
string: Castkit::Types::String
}
Alias | Canonical |
---|---|
collection | array |
bool | boolean |
int | integer |
map | hash |
number | float |
str | string |
timestamp | datetime |
uuid | string |
Castkit.configure do |config|
config.register_type(:mytype, MyTypeClass, aliases: [:custom])
end
Castkit attributes define the shape, type, and behavior of fields on a DataObject. Attributes are declared using the attribute
method or shorthand type methods provided by Castkit::Core::AttributeTypes
.
class UserDto < Castkit::DataObject
string :name, required: true
boolean :admin, default: false
array :tags, of: :string, ignore_nil: true
end
Castkit supports a strict set of primitive types defined in Castkit::Configuration::DEFAULT_TYPES
and aliased in TYPE_ALIASES
.
:array
:boolean
:date
:datetime
:float
:hash
:integer
:string
Castkit provides shorthand aliases for common primitive types:
Alias | Canonical | Description |
---|---|---|
collection | array | Alias for arrays |
bool | boolean | Alias for true/false types |
int | integer | Alias for integer values |
map | hash | Alias for hashes (key-value pairs) |
number | float | Alias for numeric values |
str | string | Alias for strings |
timestamp | datetime | Alias for date-time values |
uuid | string | Commonly used for identifiers |
No other types are supported unless explicitly registered via Castkit.configuration.register_type
.
Option | Type | Default | Description |
---|---|---|---|
required | Boolean | true | Whether the field is required on initialization. |
default | Object/Proc | nil | Default value or lambda called at runtime. |
access | Array | [:read, :write] | Controls read/write visibility. |
ignore_nil | Boolean | false | Exclude nil values from serialization. |
ignore_blank | Boolean | false | Exclude empty strings, arrays, and hashes. |
ignore | Boolean | false | Fully ignore the field (no serialization/deserialization). |
composite | Boolean | false | Used for computed, virtual fields. |
transient | Boolean | false | Excluded from serialized output. |
unwrapped | Boolean | false | Merges nested DataObject fields into parent. |
prefix | String | nil | Used with unwrapped to prefix keys. |
aliases | Array | [] | Accept alternative keys during deserialization. |
of: | Symbol | nil | Required for :array attributes. |
validator: | Proc | nil | Optional callable that validates the value. |
Access determines when the field is considered readable/writable.
string :email, access: [:read]
string :password, access: [:write]
Castkit supports grouping attributes using required
and optional
blocks to reduce repetition and improve clarity when defining large DTOs.
class UserDto < Castkit::DataObject
required do
string :id
string :name
end
optional do
integer :age
boolean :admin
end
end
This is equivalent to:
class UserDto < Castkit::DataObject
string :id # required: true
string :name # required: true
integer :age, required: false
boolean :admin, required: false
end
Grouped declarations are especially useful when your DTO has many optional fields or a mix of required/optional fields across different types.
class Metadata < Castkit::DataObject
string :locale
end
class PageDto < Castkit::DataObject
dataobject :metadata, unwrapped: true, prefix: "meta"
end
# Serializes as:
# { "meta_locale": "en" }
Composite fields are computed virtual attributes:
class ProductDto < Castkit::DataObject
string :name, required: true
string :sku, access: [:read]
float :price, default: 0.0
composite :description, :string do
"#{name}: #{sku} - #{price}"
end
end
Transient fields are excluded from serialization and can be defined in two ways:
class ProductDto < Castkit::DataObject
string :id, transient: true
transient do
string :internal_token
end
end
string :email, aliases: ["emailAddress", "user.email"]
dto.load({ "emailAddress" => "foo@bar.com" })
class ProductDto < Castkit::DataObject
string :name, required: true
float :price, default: 0.0, validator: ->(v) { raise "too low" if v < 0 }
array :tags, of: :string, ignore_blank: true
string :sku, access: [:read]
composite :description, :string do
"#{name}: #{sku} - #{price}"
end
transient do
string :id
end
end
Castkit::DataObject
is the base class for all structured DTOs. It offers a complete lifecycle for data ingestion, transformation, and output, supporting strict typing, validation, access control, aliasing, serialization, and root-wrapped payloads.
class UserDto < Castkit::DataObject
string :id
string :name
integer :age, required: false
end
user = UserDto.new(name: "Alice", age: 30)
user.to_h #=> { name: "Alice", age: 30 }
user.to_json #=> '{"name":"Alice","age":30}'
By default, Castkit operates in strict mode and raises if unknown keys are passed. You can override this:
class LooseDto < Castkit::DataObject
strict false
ignore_unknown true # equivalent to strict false
warn_on_unknown true # emits a warning instead of raising
end
To build a relaxed version dynamically:
LooseClone = MyDto.relaxed(warn_on_unknown: true)
class WrappedDto < Castkit::DataObject
root :user
string :name
end
WrappedDto.new(name: "Test").to_h
#=> { "user" => { "name" => "Test" } }
You can deserialize using:
UserDto.from_h(hash)
UserDto.deserialize(hash)
contract = UserDto.to_contract
UserDto.validate!(id: "123", name: "Alice")
from_contract = Castkit::DataObject.from_contract(contract)
To override default serialization behavior:
class CustomSerializer < Castkit::Serializers::Base
def call
{ payload: object.to_h }
end
end
class MyDto < Castkit::DataObject
string :field
serializer CustomSerializer
end
dto = UserDto.new(name: "Alice", foo: "bar")
dto.unknown_attributes
#=> { foo: "bar" }
UserDto.register!(as: :User)
# Registers under Castkit::DataObjects::User
Castkit::Contract
provides a lightweight mechanism for validating structured input without requiring a full data model. Ideal for validating service inputs, API payloads, or command parameters.
You can define a contract using the .build
DSL:
UserContract = Castkit::Contract.build(:user) do
string :id
string :email, required: false
end
Or subclass directly:
class MyContract < Castkit::Contract::Base
string :id
integer :count, required: false
end
UserContract.validate(id: "123")
UserContract.validate!(id: "123")
Returns a Castkit::Contract::Result
with:
#success?
/ #failure?
#errors
hash#to_h
/ #to_s
LooseContract = Castkit::Contract.build(:loose, strict: false) do
string :token
end
StrictContract = Castkit::Contract.build(:strict, allow_unknown: false, warn_on_unknown: true) do
string :id
end
class UserDto < Castkit::DataObject
string :id
string :email
end
UserContract = Castkit::Contract.from_dataobject(UserDto)
UserDto = UserContract.to_dataobject
# or
UserDto = UserContract.dataobject
UserContract.register!(as: :UserInput)
# Registers under Castkit::Contracts::UserInput
Only a subset of options are supported:
required
aliases
min
, max
, format
of
(for arrays)validator
unwrapped
, prefix
force_type
class AddressDto < Castkit::DataObject
string :city
end
class UserDto < Castkit::DataObject
string :id
dataobject :address, of: AddressDto
end
UserContract = Castkit::Contract.from_dataobject(UserDto)
UserContract.validate!(id: "abc", address: { city: "Boston" })
Castkit is designed to be modular and extendable. Future guides will cover:
Castkit::Serializers::Base
)castkit-activerecord
for syncing with ActiveRecord modelscastkit-msgpack
for binary encodingcastkit-oj
for high-performance JSONCastkit supports modular extensions through a lightweight plugin system. Plugins can modify or extend the behavior of Castkit::DataObject
classes, such as adding serialization support, transformation helpers, or framework integrations.
Plugins are just Ruby modules and can be registered and activated globally or per-class.
Plugins can be activated on any DataObject or at runtime:
module MyPlugin
def self.setup!(klass)
# Optional: called after inclusion
klass.string :plugin_id
end
def plugin_feature
"Enabled!"
end
end
Castkit.configure do |config|
config.register_plugin(:my_plugin, MyPlugin)
end
class MyDto < Castkit::DataObject
Castkit::Plugins.activate(self, :my_plugin)
end
This includes the MyPlugin
module into MyDto
and calls MyPlugin.setup!(MyDto)
if defined.
Plugins must be registered before use:
Castkit.configure do |config|
config.register_plugin(:oj, Castkit::Plugins::Oj)
end
You can then activate them:
Castkit::Plugins.activate(MyDto, :oj)
Method | Description |
---|---|
Castkit::Plugins.register(:name, mod) | Registers a plugin under a custom name. |
Castkit::Plugins.activate(klass, *names) | Includes one or more plugins into a class. |
Castkit::Plugins.lookup!(:name) | Looks up the plugin by name or constant. |
Castkit looks for plugins under the Castkit::Plugins
namespace by default:
module Castkit
module Plugins
module Oj
def self.setup!(klass)
klass.include SerializationSupport
end
end
end
end
To activate this:
Castkit::Plugins.activate(MyDto, :oj)
You can also manually register plugins not under this namespace.
module Castkit
module Plugins
module Timestamps
def self.setup!(klass)
klass.datetime :created_at
klass.datetime :updated_at
end
end
end
end
Castkit::Plugins.activate(UserDto, :timestamps)
This approach allows reusable, modular feature sets across DTOs with clean setup behavior.
Castkit includes a command-line interface to help scaffold and inspect DTO components with ease.
The CLI is structured around two primary commands:
castkit generate
β scaffolds boilerplate for Castkit components.castkit list
β introspects and displays registered or defined components.The castkit generate
command provides subcommands for creating files for all core Castkit component types.
castkit generate dataobject User name:string age:integer
Creates:
lib/castkit/data_objects/user.rb
spec/castkit/data_objects/user_spec.rb
castkit generate contract UserInput id:string email:string
Creates:
lib/castkit/contracts/user_input.rb
spec/castkit/contracts/user_input_spec.rb
castkit generate plugin Oj
Creates:
lib/castkit/plugins/oj.rb
spec/castkit/plugins/oj_spec.rb
castkit generate validator Money
Creates:
lib/castkit/validators/money.rb
spec/castkit/validators/money_spec.rb
castkit generate type money
Creates:
lib/castkit/types/money.rb
spec/castkit/types/money_spec.rb
castkit generate serializer Json
Creates:
lib/castkit/serializers/json.rb
spec/castkit/serializers/json_spec.rb
You can disable test generation with --no-spec
.
The castkit list
command provides an interface to view internal Castkit definitions or project-registered components.
castkit list types
Displays a grouped list of:
Castkit.configure
)Example:
Native Types:
Castkit::Types::String - :string, :str, :uuid
Custom Types:
MyApp::Types::Money - :money
castkit list validators
Displays all validator classes defined in lib/castkit/validators
or custom-defined under Castkit::Validators
.
Castkit validators are tagged [Castkit]
, and others as [Custom]
.
castkit list contracts
Lists all contracts in the Castkit::Contracts
namespace and related files.
castkit list dataobjects
Lists all DTOs in the Castkit::DataObjects
namespace.
castkit list serializers
Lists all serializer classes and their source origin.
castkit generate dataobject Product name:string price:float
castkit generate contract ProductInput name:string
castkit list types
castkit list validators
The CLI is designed to provide a familiar Rails-like generator experience, tailored for Castkitβs data-first architecture.
You can test DTOs and Contracts by treating them like plain Ruby objects:
dto = MyDto.new(name: "Alice")
expect(dto.name).to eq("Alice")
You can also assert validation errors:
expect {
MyDto.new(name: nil)
}.to raise_error(Castkit::AttributeError, /name is required/)
MIT. See LICENSE.
Created with β€οΈ by Nathan Lucas
FAQs
Unknown package
We found that castkit demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago.Β It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Astral unveils pyx, a Python-native package registry in beta, designed to speed installs, enhance security, and integrate deeply with uv.
Security News
The Latio podcast explores how static and runtime reachability help teams prioritize exploitable vulnerabilities and streamline AppSec workflows.
Security News
The latest Opengrep releases add Apex scanning, precision rule tuning, and performance gains for open source static code analysis.