Type Safety
SuperTOML brings type-safety to configuration files through JSON Schema. Every configuration value and dimension is validated against a schema, catching errors before they reach production.
Why Type Safety Matters
In an AI-powered world where code is increasingly generated, type-safety is like immunity. Configuration files deserve the same level of type-safety as your programs because:
- Configuration bugs are runtime bugs - A typo in a config file can crash your application
- Configs change independently - They're not version-controlled alongside code
- Multiple teams edit configs - Without validation, mistakes are inevitable
- AI generates configs too - Type-safety ensures generated configs are valid
Schema Definition
Every configuration value in SuperTOML has an associated JSON Schema:
[default-configs]
per_km_rate = {
value = 20.0,
schema = { type = "number", minimum = 0, maximum = 100 }
}
The schema validates:
- The default value itself
- Any override values for this key
- Values returned from resolution
JSON Schema Types
Primitive Types
[default-configs]
# String
app_name = { value = "MyApp", schema = { type = "string" } }
# Integer
port = { value = 8080, schema = { type = "integer", minimum = 1, maximum = 65535 } }
# Number (float)
rate = { value = 1.5, schema = { type = "number", minimum = 0 } }
# Boolean
enabled = { value = true, schema = { type = "boolean" } }
# Null
optional_value = { value = "null", schema = { type = "null" } }
String Constraints
[default-configs]
# Email format
admin_email = {
value = "admin@example.com",
schema = { type = "string", format = "email" }
}
# Pattern matching
api_key = {
value = "sk_test_1234567890",
schema = { type = "string", pattern = "^sk_[a-z]+_[a-zA-Z0-9]+$" }
}
# Length constraints
description = {
value = "A short description",
schema = { type = "string", minLength = 10, maxLength = 500 }
}
Numeric Constraints
[default-configs]
# Integer with range
priority = {
value = 5,
schema = { type = "integer", minimum = 1, maximum = 10 }
}
# Number with exclusive bounds
discount = {
value = 0.1,
schema = {
type = "number",
exclusiveMinimum = 0,
exclusiveMaximum = 1
}
}
# Multiple of
batch_size = {
value = 100,
schema = { type = "integer", multipleOf = 10 }
}
Enumerations
[default-configs]
# String enum
log_level = {
value = "info",
schema = {
type = "string",
enum = ["debug", "info", "warn", "error"]
}
}
# Integer enum
status_code = {
value = 200,
schema = { type = "integer", enum = [200, 201, 400, 404, 500] }
}
Arrays
[default-configs]
# Array of strings
allowed_hosts = {
value = ["localhost", "example.com"],
schema = {
type = "array",
items = { type = "string" },
minItems = 1
}
}
# Array with unique items
tags = {
value = ["production", "critical"],
schema = {
type = "array",
items = { type = "string" },
uniqueItems = true
}
}
# Array with length bounds
features = {
value = ["feature_a", "feature_b"],
schema = {
type = "array",
items = { type = "string" },
minItems = 0,
maxItems = 10
}
}
Objects
[default-configs]
database = {
value = { host = "localhost", port = 5432, ssl = true },
schema = {
type = "object",
properties = {
host = { type = "string" },
port = { type = "integer", minimum = 1, maximum = 65535 },
ssl = { type = "boolean" },
pool_size = { type = "integer", default = 10 }
},
required = ["host", "port"],
additionalProperties = false
}
}
Nested Objects
[default-configs]
server = {
value = {
host = "0.0.0.0",
port = 8080,
tls = { enabled = true, cert_path = "/certs/server.crt" }
},
schema = {
type = "object",
properties = {
host = { type = "string" },
port = { type = "integer" },
tls = {
type = "object",
properties = {
enabled = { type = "boolean" },
cert_path = { type = "string" },
key_path = { type = "string" }
},
required = ["enabled"]
}
},
required = ["host", "port"]
}
}
Combining Schemas
All Of
All conditions must be satisfied:
[default-configs]
username = {
value = "john_doe",
schema = {
allOf = [
{ type = "string", minLength = 3 },
{ type = "string", maxLength = 20 },
{ type = "string", pattern = "^[a-z][a-z0-9_]*$" }
]
}
}
Any Of
At least one condition must be satisfied:
[default-configs]
identifier = {
value = "user-123",
schema = {
anyOf = [
{ type = "string", format = "uuid" },
{ type = "string", pattern = "^[a-z]+-[0-9]+$" }
]
}
}
One Of
Exactly one condition must be satisfied:
[default-configs]
port_config = {
value = 8080,
schema = {
oneOf = [
{ type = "integer", minimum = 1, maximum = 65535 },
{ type = "string", enum = ["any", "none"] }
]
}
}
Not
Must not match the schema:
[default-configs]
not_admin = {
value = "user",
schema = {
type = "string",
not = { enum = ["admin", "root", "superuser"] }
}
}
Conditional Schemas
Use if, then, and else for conditional validation:
[default-configs]
payment_config = {
value = { method = "card", card_number = "4111111111111111" },
schema = {
type = "object",
properties = {
method = { type = "string", enum = ["card", "bank"] }
},
if = { properties = { method = { const = "card" } } },
then = {
properties = {
card_number = { type = "string", pattern = "^[0-9]{16}$" }
},
required = ["card_number"]
},
else = {
properties = {
account_number = { type = "string" }
},
required = ["account_number"]
}
}
}
Schema References
Use $ref and definitions for reusable schemas:
[default-configs]
address = {
value = { street = "123 Main St", city = "Bangalore", zip = "560001" },
schema = {
type = "object",
properties = {
street = { type = "string" },
city = { type = "string" },
zip = { "$ref" = "#/definitions/zipCode" }
},
definitions = {
zipCode = { type = "string", pattern = "^[0-9]{6}$" }
}
}
}
Type Templates
Superposition supports reusable type templates that can be referenced across configurations:
# Type templates are defined at the workspace level
# and can be reused across multiple configs
[default-configs]
user_email = {
value = "user@example.com",
schema = { "$ref" = "Email" } # References a type template
}
admin_email = {
value = "admin@example.com",
schema = { "$ref" = "Email" } # Same template, different config
}
Built-in Type Templates
| Template | Schema |
|---|---|
Number | { "type": "integer" } |
Decimal | { "type": "number" } |
Boolean | { "type": "boolean" } |
String | { "type": "string" } |
Validation in Action
At Parse Time
When parsing a SuperTOML file, all values are validated against their schemas:
[default-configs]
# This will fail validation - negative value violates minimum = 0
per_km_rate = { value = -5.0, schema = { type = "number", minimum = 0 } }
Error:
ValidationError: default-configs.per_km_rate
- Value -5.0 is less than minimum 0
For Overrides
Override values are validated against the config's schema:
[default-configs]
per_km_rate = { value = 20.0, schema = { type = "number", minimum = 0, maximum = 100 } }
[dimensions]
vehicle_type = { position = 1, schema = { type = "string", enum = ["auto", "cab", "bike"] } }
[[overrides]]
_context_ = { vehicle_type = "cab" }
# This will fail - value exceeds maximum
per_km_rate = 150.0
Error:
ValidationError: context[0].per_km_rate
- Value 150.0 is greater than maximum 100
For Dimensions
Dimension values in contexts are validated against dimension schemas:
[default-configs]
per_km_rate = { value = 20.0, schema = { type = "number", minimum = 0, maximum = 100 } }
[dimensions]
hour_of_day = { position = 1, schema = { type = "integer", minimum = 0, maximum = 23 } }
[[overrides]]
# This will fail - 25 exceeds maximum 23
_context_ = { hour_of_day = 25 }
per_km_rate = 30.0
Error:
ValidationError: context[0]._context_.hour_of_day
- Value 25 is greater than maximum 23
Best Practices
1. Always Set Constraints
Don't just use { "type": "number" } - add bounds:
# ❌ Too permissive
rate = { value = 0.5, schema = { type = "number" } }
# ✅ Bounded
rate = { value = 0.5, schema = { type = "number", minimum = 0, maximum = 1 } }
2. Use Enums for Fixed Values
When values are from a known set, use enums:
# ❌ String without constraints
environment = { value = "production", schema = { type = "string" } }
# ✅ Enum for known values
environment = {
value = "production",
schema = { type = "string", enum = ["development", "staging", "production"] }
}
3. Document with Descriptions
Add descriptions to schemas for documentation:
[default-configs]
api_timeout = {
value = 30,
schema = {
type = "integer",
minimum = 1,
maximum = 300,
description = "API timeout in seconds. Must be between 1 and 300."
}
}
4. Use Type Templates for Consistency
Define common types once and reuse:
# Define once
[default-configs]
primary_email = { value = "primary@example.com", schema = { "$ref": "Email" } }
secondary_email = { value = "secondary@example.com", schema = { "$ref": "Email" } }
support_email = { value = "support@example.com", schema = { "$ref": "Email" } }
5. Validate Object Structures Strictly
Use required and additionalProperties:
# ❌ Loose validation
database = {
value = { host = "localhost" },
schema = { type = "object" }
}
# ✅ Strict validation
database = {
value = { host = "localhost", port = 5432 },
schema = {
type = "object",
properties = {
host = { type = "string" },
port = { type = "integer", minimum = 1, maximum = 65535 }
},
required = ["host", "port"],
additionalProperties = false
}
}