ask-schema
A compact Ruby DSL for building standards-compliant JSON Schema documents. Zero dependencies.
gem "ask-schema"
require "ask-schema"
schema = Ask::Schema.create do
string :name, description: "Full name"
integer :age, description: "Age in years", minimum: 0
boolean :active, required: false
end
schema.new("user", description: "A user profile").to_json
# => {
# "name": "user",
# "description": "A user profile",
# "schema": {
# "type": "object",
# "properties": {
# "name": { "type": "string", "description": "Full name" },
# "age": { "type": "integer", "description": "Age in years", "minimum": 0 },
# "active": { "type": "boolean" }
# },
# "required": ["name", "age"],
# "additionalProperties": false,
# "strict": true
# }
# }
Quick Start
Block-based DSL
schema = Ask::Schema.create do
string :name, description: "The user's name"
integer :age, description: "Age in years"
boolean :active, required: false
end
instance = schema.new("user_profile", description: "A user profile")
instance.to_json_schema
# => { name: "user_profile", description: "A user profile", schema: { ... } }
Class-based DSL
class Address < Ask::Schema
string :street
string :city
string :zip
string :country, required: false
end
class User < Ask::Schema
string :name, description: "Full name"
string :email, format: "email"
integer :age
object :address, of: Address
end
User.new("user").to_json_schema
Primitive Types
Each primitive type supports standard JSON Schema constraints.
String
string :username,
description: "Username",
enum: %w[admin user guest],
min_length: 3,
max_length: 50,
pattern: "^[a-zA-Z0-9_]+$",
format: "email"
Number
number :price,
description: "Price in USD",
minimum: 0,
maximum: 999999.99,
multiple_of: 0.01
Integer
integer :age,
minimum: 0,
maximum: 150
Boolean
boolean :active, description: "Is the user active?"
Null
null :deleted_at, description: "When the record was deleted"
Complex Types
Object
# Inline object
object :address do
string :street
string :city
string :zip
end
# Reference to a defined schema
define(:address) do
string :street
string :city
end
object :billing, of: :address
# Reference to a Schema class
object :shipping, of: Address
Array
# Array of primitive type
array :tags, of: :string, description: "List of tags"
# Array with min/max items
array :prices, of: :number, min_items: 1, max_items: 100
# Array with complex items (block)
array :contacts do
object do
string :name
string :email
end
end
# Array with any_of items
array :identifiers do
any_of do
string
integer
end
end
any_of / one_of
any_of :contact do
string description: "Phone number"
object do
string :email
end
end
one_of :payment_method do
string :credit_card
string :paypal
end
Optional (nullable)
optional :nickname do
string
end
# Produces: anyOf: [{ type: "string" }, { type: "null" }]
Named Definitions and References
Use define to create reusable named sub-schemas and reference (or of:) to reference them:
class User < Ask::Schema
define(:address) do
string :street
string :city
string :zip
end
string :name
object :home_address, of: :address
object :work_address, of: :address
end
Output includes proper $defs and $ref:
{
"type": "object",
"properties": {
"name": { "type": "string" },
"home_address": { "$ref": "#/$defs/address" },
"work_address": { "$ref": "#/$defs/address" }
},
"$defs": {
"address": {
"type": "object",
"properties": { "street": { "type": "string" }, ... }
}
}
}
Conditionals
If/Then/Else
schema = Ask::Schema.create do
integer :age
string :country
given(age: 18, country: "US") do
requires :license_number
validates :license_number, type: :string, pattern: /^[A-Z]{2}\d{6}$/
otherwise do
requires :country_name
end
end
end
Dependent Required
dependent :shipping_address do
requires :name, :street, :city
end
Coercion rules
| Ruby value | JSON Schema |
|---|---|
18 (scalar) |
{ const: 18 } |
["admin", "user"] (Array) |
{ enum: ["admin", "user"] } |
/^[A-Z]+$/ (Regexp) |
{ pattern: "^[A-Z]+$" } |
{ minimum: 0 } (Hash) |
Passed through as-is |
Validation
schema = Ask::Schema.create { string :name }
schema.valid? # => true
schema.validate! # => nil (or raises Ask::Schema::ValidationError)
# Circular reference detection
schema = Ask::Schema.create do
define(:a) { object :b, of: :b }
define(:b) { object :a, of: :a }
end
schema.valid? # => false
schema.validate! # => raises Ask::Schema::ValidationError
Output Formats
instance.to_json_schema
# => Hash with :name, :description, :schema keys
instance.to_json
# => Pretty-printed JSON string
Configuration
class StrictSchema < Ask::Schema
string :name
strict true # defaults to true
additional_properties false # defaults to false
end
Integration with ask-tools
ask-schema powers tool parameter schemas in ask-tools:
class WeatherTool < Ask::Tool
description "Get weather for a location"
params do
string :location, description: "City name"
string :unit, enum: %w[celsius fahrenheit]
end
def execute(location:, unit: "celsius")
# ...
end
end
Under the hood, Ask::Schema.create is used to build the JSON Schema for tool parameters.
Error Types
| Error | When |
|---|---|
Ask::Schema::InvalidArrayTypeError |
Invalid type for array :of |
Ask::Schema::InvalidObjectTypeError |
Invalid type for object :of |
Ask::Schema::ValidationError |
Schema validation fails (e.g., circular refs) |
Ask::Schema::InvalidSchemaTypeError |
Unknown schema type specified |
Ask::Schema::InvalidSchemaError |
Schema definition is invalid |
Ask::Schema::LimitExceededError |
Maximum limits exceeded |
Development
bundle install
bundle exec rake test
Status
Phase 3 of the ask-rb ecosystem migration. This gem replaces ruby_llm-schema
in the ask-rb stack. It should be built after ask-core and ask-llm-providers
are stable.
Current state: v0.1.0 — initial port complete with full feature parity.
License
MIT