philiprehberger-safe_yaml
Safe YAML loading with restricted types, schema validation, and size limits
Requirements
- Ruby >= 3.1
Installation
Add to your Gemfile:
gem "philiprehberger-safe_yaml"Or install directly:
gem install philiprehberger-safe_yamlUsage
require "philiprehberger/safe_yaml"
# Load a YAML string safely
data = Philiprehberger::SafeYaml.load("name: Alice\nage: 30\n")
# => {"name"=>"Alice", "age"=>30}
# Load from a file
data = Philiprehberger::SafeYaml.load_file("config.yml")Size Limits
# Reject oversized input
Philiprehberger::SafeYaml.load(huge_string, max_size: 1024)
# => raises Philiprehberger::SafeYaml::SizeError if input exceeds 1024 bytesPermitted Classes
# Allow specific classes during deserialization
data = Philiprehberger::SafeYaml.load(yaml_string, permitted_classes: [Symbol, Date])Safe Serialization
require "philiprehberger/safe_yaml"
data = { "host" => "localhost", "port" => 3000, "debug" => true }
yaml_string = Philiprehberger::SafeYaml.dump(data)
Philiprehberger::SafeYaml.dump_file(data, "config.yml")Schema Validation
schema = Philiprehberger::SafeYaml::Schema.new do
required :name, String
required :age, Integer
optional :email, String
end
data = Philiprehberger::SafeYaml.load("name: Alice\nage: 30\n")
# Validate with exceptions
schema.validate!(data)
# => true (or raises SchemaError)
# Validate without exceptions
result = schema.validate(data)
# => { valid: true, errors: [] }Alias Limits
# Block all aliases (default behavior)
Philiprehberger::SafeYaml.load(yaml_with_aliases)
# => raises Psych::BadAlias
# Allow up to 3 aliases
data = Philiprehberger::SafeYaml.load(yaml_with_aliases, max_aliases: 3)
# Exceeding the limit raises an error
Philiprehberger::SafeYaml.load(yaml_with_many_aliases, max_aliases: 2)
# => raises Philiprehberger::SafeYaml::ErrorLoad and Validate
schema = Philiprehberger::SafeYaml::Schema.new do
required :name, String
required :port, Integer
end
# Parse and validate in one step
data = Philiprehberger::SafeYaml.load_and_validate("name: app\nport: 3000\n", schema: schema)
# => {"name"=>"app", "port"=>3000}
# Raises SchemaError if validation fails
Philiprehberger::SafeYaml.load_and_validate("name: app\n", schema: schema)
# => raises Philiprehberger::SafeYaml::SchemaErrorSanitize
raw = "# This is a comment\nname: Alice\n# Another comment\nage: 30\n"
cleaned = Philiprehberger::SafeYaml.sanitize(raw)
# => "name: Alice\nage: 30\n"Defaults Merge
defaults = { 'host' => 'localhost', 'port' => 3000, 'db' => { 'pool' => 5, 'timeout' => 30 } }
yaml = "port: 8080\ndb:\n pool: 10\n"
data = Philiprehberger::SafeYaml.load_with_defaults(yaml, defaults: defaults)
# => {"host"=>"localhost", "port"=>8080, "db"=>{"pool"=>10, "timeout"=>30}}Custom Validation Rules
schema = Philiprehberger::SafeYaml::Schema.new do
required :port, Integer, rule: ->(v) { (1..65_535).cover?(v) }, message: 'must be between 1 and 65535'
required :status, String, rule: ->(v) { %w[active inactive].include?(v) }
optional :email, String, rule: ->(v) { v.include?('@') }, message: 'must be a valid email'
end
schema.validate!({ 'port' => 80, 'status' => 'active' })
# => true
result = schema.validate({ 'port' => 0, 'status' => 'unknown' })
# => { valid: false, errors: ["key port: must be between 1 and 65535", "key status: failed validation rule"] }API
| Method / Class | Description |
|---|---|
SafeYaml.load(string, **opts) |
Safely load a YAML string |
SafeYaml.load_file(path, **opts) |
Safely load a YAML file |
SafeYaml.load_and_validate(string, schema:, **opts) |
Load and validate in one step |
SafeYaml.load_with_defaults(string, defaults:, **opts) |
Load and deep merge over defaults |
SafeYaml.sanitize(string) |
Strip comments and normalize whitespace |
SafeYaml.dump(data, permitted_classes:) |
Safely dump data to a YAML string |
SafeYaml.dump_file(data, path, permitted_classes:) |
Safely dump data to a YAML file |
Loader.load(string, permitted_classes:, max_aliases:, max_size:) |
Core safe loading with all options |
Loader.load_file(path, **opts) |
Read file and delegate to Loader.load
|
Loader.dump(data, permitted_classes:) |
Dump data to YAML with type validation |
Loader.dump_file(data, path, permitted_classes:) |
Write validated YAML to file |
Schema.new(&block) |
Define a validation schema with DSL |
Schema#required(key, type, rule:, message:) |
Declare a required key with expected type and optional validation rule |
Schema#optional(key, type, rule:, message:) |
Declare an optional key with expected type and optional validation rule |
Schema#validate!(data) |
Validate and raise SchemaError on failure |
Schema#validate(data) |
Validate and return { valid:, errors: }
|
SafeYaml::Error |
Base error class |
SafeYaml::SchemaError |
Raised on schema validation failure |
SafeYaml::SizeError |
Raised when input exceeds max_size |
Development
bundle install
bundle exec rspec
bundle exec rubocopSupport
If you find this project useful: