Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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:

  1. Property names - Direct mapping by field name
  2. Schema titles - Mapping entire schemas by their title
  3. JSON types - Mapping by primitive JSON types
  4. Property-based matching - Advanced matching based on schema structure
  5. 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 to mapping, you should use fully qualified Rust type names, except for core primitive types like String, u64, bool, etc.

For example:

  • Use chrono::DateTime<Utc> instead of DateTime<Utc>
  • Use std::collections::HashMap<String, String> instead of HashMap<String, String>
  • Use uuid::Uuid instead of Uuid

If 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 → UserId type
  • room_id → RoomId type
  • timestamp → chrono::DateTime<Utc> type
  • is_admin → bool type

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 values
  • t#int - Integer values
  • t#number - Numeric values (integers and floats)
  • t#bool - Boolean values
  • t#array - Array/list values
  • t#object - Object/map values
  • t#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 bodies
  • type = "response": Apply only to response bodies
  • method: 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):

  1. Property-based matching with where clauses
  2. Property name matching (exact field names)
  3. Schema title matching (schema-level titles)
  4. 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.