Conversion Rules
Conversion rules are the core of ngen’s type mapping system. They define how OpenAPI schema types, properties, and parameters are converted into Rust types during code generation.
Overview
A conversion rule maps a source type or property (from OpenAPI) to a target Rust type. Rules can match based on:
- Property names - Direct mapping by field name
- Schema titles - Mapping entire schemas by their title
- JSON types - Mapping by primitive JSON types
- Property-based matching - Advanced matching based on schema structure
- Payload filtering - Applying rules only to specific HTTP methods or status codes
Basic Rule Structure
{ from = "source", to = "target" }
from: What to match (property name, schema title, or type pattern)to: Target Rust type to generate
Warning
When specifying the
tomapping, you should use fully qualified Rust type names, except for core primitive types likeString,u64,bool, etc.For example:
- Use
chrono::DateTime<Utc>instead ofDateTime<Utc>- Use
std::collections::HashMap<String, String>instead ofHashMap<String, String>- Use
uuid::Uuidinstead ofUuidIf you do not do this, the resulting, generated code will not compile.
Simple Examples
[general]
convert = [
{ from = "userId", to = "UserId" }, # Property name mapping
{ from = "email", to = "String" }, # Property to primitive type
{ from = "UserRequest", to = "CreateUser" }, # Schema title mapping
]
Property Name Matching
The most simple type of rule matches properties by their name:
convert = [
{ from = "user_id", to = "UserId" },
{ from = "room_id", to = "RoomId" },
{ from = "timestamp", to = "chrono::DateTime<Utc>" },
{ from = "is_admin", to = "bool" },
]
This maps any OpenAPI property with these names to the corresponding Rust types.
Example
The following JSON object would match the above rules:
{
"user_id": "@alice:example.com",
"room_id": "!abc123:example.com",
"timestamp": "2024-01-15T10:30:00Z",
"is_admin": true
}
The properties would be converted to:
user_id→UserIdtyperoom_id→RoomIdtypetimestamp→chrono::DateTime<Utc>typeis_admin→booltype
Schema Title Matching
Schema title matching applies to entire OpenAPI schemas:
convert = [
{ from = "CreateUserRequest", to = "UserCreateRequest" },
{ from = "UserResponse", to = "User" },
{ from = "ErrorResponse", to = "ApiError" },
]
When ngen encounters a schema with title: "CreateUserRequest", it generates a Rust type
named UserCreateRequest.
Type-Based Matching
Type-based rules match OpenAPI properties by their JSON schema type using the t# prefix:
convert = [
{ from = "t#string", to = "String" },
{ from = "t#int", to = "i64" },
{ from = "t#bool", to = "bool" },
{ from = "t#array", to = "Vec<T>" },
{ from = "t#object", to = "HashMap<String, Value>" },
]
These act as fallback rules when no specific property name matches.
Available JSON Types
t#string- String valuest#int- Integer valuest#number- Numeric values (integers and floats)t#bool- Boolean valuest#array- Array/list valuest#object- Object/map valuest#null- Null values
Property-Based Matching
Property-based matching allows rules to match schemas based on their internal structure
using where clauses:
convert = [
# Match schemas that have ALL specified properties
{ from = "UserRequest", to = "CreateUserRequest", where = {
all_properties = ["name", "email", "password"]
}},
# Match schemas that have EXACTLY these properties (no more, no less)
{ from = "LoginRequest", to = "AuthRequest", where = {
exact_properties = ["username", "password"]
}},
# Match schemas that have ANY of these properties
{ from = "PaymentRequest", to = "PaymentMethod", where = {
any_properties = ["credit_card", "paypal_email", "bank_account"]
}},
]
Where Clause Options
all_properties
Schema must contain all listed properties (but may have additional ones):
{ from = "UserSchema", to = "User", where = {
all_properties = ["id", "name"]
}}
Matches schemas with id, name, and optionally other properties.
exact_properties
Schema must contain exactly these properties (no more, no less):
{ from = "LoginSchema", to = "LoginRequest", where = {
exact_properties = ["username", "password"]
}}
Only matches schemas with exactly username and password properties.
any_properties
Schema must contain at least one of the listed properties:
{ from = "ContactSchema", to = "ContactInfo", where = {
any_properties = ["email", "phone", "address"]
}}
Matches schemas with email OR phone OR address (or any combination).
Combined Conditions
You can combine multiple where conditions:
{ from = "OrderSchema", to = "OrderRequest", where = {
all_properties = ["customer_id", "items"], # Must have these
any_properties = ["credit_card", "paypal"] # Plus at least one payment method
}}
Type-Based Property Matching
Property-based matching also works with type patterns:
{ from = "StringOnlyRequest", to = "TextRequest", where = {
all_properties = ["t#string"] # All properties must be strings
}}
Payload Filtering
Payload filtering restricts when rules apply based on HTTP methods or response status codes:
Request Method Filtering
convert = [
{ from = "UserData", to = "CreateUserRequest", payload = {
type = "request",
method = ["POST", "PUT"]
}},
{ from = "UserData", to = "UserQuery", payload = {
type = "request",
method = ["GET"]
}},
]
Response Status Filtering
convert = [
{ from = "ErrorResponse", to = "ClientError", payload = {
type = "response",
status = [400, 401, 403, 404]
}},
{ from = "ErrorResponse", to = "ServerError", payload = {
type = "response",
status = [500, 502, 503]
}},
]
Payload Types
type = "request": Apply only to request bodiestype = "response": Apply only to response bodiesmethod: Array of HTTP methods (GET, POST, PUT, DELETE, etc.)status: Array of HTTP status codes (200, 404, 500, etc.)
Rule Priority and Matching Order
Rules are evaluated in the following priority order (highest to lowest):
- Property-based matching with
whereclauses - Property name matching (exact field names)
- Schema title matching (schema-level titles)
- Type-based matching (
t#patterns)
Priority Example
convert = [
# Highest priority: property-based matching
{ from = "UserData", to = "DetailedUser", where = {
all_properties = ["id", "name", "email", "profile"]
}},
# Medium priority: property name matching
{ from = "name", to = "UserName" },
# Lower priority: schema title matching
{ from = "UserData", to = "BasicUser" },
# Lowest priority: type-based matching
{ from = "t#string", to = "String" },
]
For a schema titled “UserData” with properties id, name, email, profile:
- The property-based rule matches first → generates
DetailedUser - Other rules are ignored for this schema
- Individual properties still use their specific rules
Advanced Features
Complex Type Mappings
convert = [
# Generic types
{ from = "items", to = "Vec<Item>" },
{ from = "metadata", to = "HashMap<String, Value>" },
# Optional types
{ from = "optional_field", to = "Option<String>" },
# Custom wrapper types
{ from = "timestamp", to = "DateTime<Utc>" },
{ from = "uuid", to = "Uuid" },
# Domain-specific types
{ from = "room_id", to = "matrix_types::RoomId" },
{ from = "event_id", to = "matrix_types::EventId" },
]
Composition Schema Support
Property-based matching works with OpenAPI composition schemas (allOf, anyOf, oneOf):
# This rule matches schemas composed of multiple sub-schemas
{ from = "ComposedRequest", to = "ComplexRequest", where = {
all_properties = ["base_field", "extension_field", "composed_field"]
}}
The rule will match if the combined schema (including all composed parts) contains the specified properties.