Skip to main content

rustapi_toon/
openapi.rs

1//! OpenAPI/Swagger Extensions for TOON Format
2//!
3//! This module provides OpenAPI schema definitions and documentation helpers
4//! for TOON format responses.
5
6use crate::TOON_CONTENT_TYPE;
7
8/// TOON format description for OpenAPI
9pub const TOON_FORMAT_DESCRIPTION: &str = r#"
10**TOON (Token-Oriented Object Notation)**
11
12A compact, human-readable format designed for passing structured data to Large Language Models (LLMs) with significantly reduced token usage (typically 40-60% savings).
13
14### Format Example
15
16**JSON (561 bytes, ~141 tokens):**
17```json
18[
19  {"id": 1, "name": "Alice", "role": "admin", "active": true},
20  {"id": 2, "name": "Bob", "role": "user", "active": false}
21]
22```
23
24**TOON (259 bytes, ~65 tokens) - 54% savings:**
25```
26[2]{id,name,role,active}:
27  1,Alice,admin,true
28  2,Bob,user,false
29```
30
31### Usage
32
33Set `Accept: application/toon` header to receive TOON formatted responses.
34
35### When to Use TOON
36
37- Sending data to LLM APIs (reduces token costs)
38- Bandwidth-constrained environments
39- Caching large datasets
40- Any scenario where token efficiency matters
41"#;
42
43/// Generate OpenAPI schema for TOON format
44pub fn toon_schema() -> serde_json::Value {
45    serde_json::json!({
46        "type": "string",
47        "format": "toon",
48        "description": "TOON (Token-Oriented Object Notation) formatted data",
49        "externalDocs": {
50            "description": "TOON Format Specification",
51            "url": "https://toonformat.dev/"
52        }
53    })
54}
55
56/// Generate OpenAPI vendor extension for TOON support
57pub fn toon_extension() -> serde_json::Value {
58    serde_json::json!({
59        "x-toon-support": {
60            "enabled": true,
61            "contentTypes": [TOON_CONTENT_TYPE, "text/toon"],
62            "tokenSavings": "40-60%",
63            "documentation": "https://toonformat.dev/"
64        }
65    })
66}
67
68/// Schema for token comparison headers
69pub fn token_headers_schema() -> serde_json::Value {
70    serde_json::json!({
71        "X-Token-Count-JSON": {
72            "description": "Estimated token count for JSON format (~4 chars/token)",
73            "schema": {
74                "type": "integer",
75                "example": 141
76            }
77        },
78        "X-Token-Count-TOON": {
79            "description": "Estimated token count for TOON format (~4 chars/token)",
80            "schema": {
81                "type": "integer",
82                "example": 65
83            }
84        },
85        "X-Token-Savings": {
86            "description": "Percentage of tokens saved by using TOON format",
87            "schema": {
88                "type": "string",
89                "example": "53.90%"
90            }
91        },
92        "X-Format-Used": {
93            "description": "The format used in the response (json or toon)",
94            "schema": {
95                "type": "string",
96                "enum": ["json", "toon"]
97            }
98        }
99    })
100}
101
102/// Generate example responses showing JSON vs TOON
103pub fn format_comparison_example<T: serde::Serialize>(data: &T) -> serde_json::Value {
104    let json_str = serde_json::to_string_pretty(data).unwrap_or_default();
105    let toon_str = toon_format::encode_default(data).unwrap_or_default();
106
107    let json_bytes = json_str.len();
108    let toon_bytes = toon_str.len();
109    let json_tokens = json_bytes / 4;
110    let toon_tokens = toon_bytes / 4;
111    let savings = if json_tokens > 0 {
112        ((json_tokens - toon_tokens) as f64 / json_tokens as f64) * 100.0
113    } else {
114        0.0
115    };
116
117    serde_json::json!({
118        "json": {
119            "content": json_str,
120            "bytes": json_bytes,
121            "estimatedTokens": json_tokens
122        },
123        "toon": {
124            "content": toon_str,
125            "bytes": toon_bytes,
126            "estimatedTokens": toon_tokens
127        },
128        "savings": {
129            "bytes": format!("{:.1}%", ((json_bytes - toon_bytes) as f64 / json_bytes as f64) * 100.0),
130            "tokens": format!("{:.1}%", savings)
131        }
132    })
133}
134
135/// OpenAPI info description with TOON support notice
136pub fn api_description_with_toon(base_description: &str) -> String {
137    format!(
138        "{}\n\n---\n\n### 🚀 TOON Format Support\n\nThis API supports **TOON (Token-Oriented Object Notation)** \
139        for reduced token usage when sending data to LLMs.\n\n\
140        Set `Accept: application/toon` header to receive TOON formatted responses.\n\n\
141        **Benefits:**\n\
142        - 40-60% token savings\n\
143        - Human-readable format\n\
144        - Reduced API costs\n\n\
145        [Learn more about TOON](https://toonformat.dev/)",
146        base_description
147    )
148}
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153    use serde::Serialize;
154
155    #[derive(Serialize)]
156    struct TestUser {
157        id: u64,
158        name: String,
159    }
160
161    #[test]
162    fn test_toon_schema() {
163        let schema = toon_schema();
164        assert_eq!(schema["type"], "string");
165        assert_eq!(schema["format"], "toon");
166    }
167
168    #[test]
169    fn test_toon_extension() {
170        let ext = toon_extension();
171        assert!(ext["x-toon-support"]["enabled"].as_bool().unwrap());
172    }
173
174    #[test]
175    fn test_token_headers_schema() {
176        let headers = token_headers_schema();
177        assert!(headers["X-Token-Count-JSON"].is_object());
178        assert!(headers["X-Token-Count-TOON"].is_object());
179        assert!(headers["X-Token-Savings"].is_object());
180        assert!(headers["X-Format-Used"].is_object());
181    }
182
183    #[test]
184    fn test_format_comparison_example() {
185        let users = vec![
186            TestUser {
187                id: 1,
188                name: "Alice".to_string(),
189            },
190            TestUser {
191                id: 2,
192                name: "Bob".to_string(),
193            },
194        ];
195        let comparison = format_comparison_example(&users);
196
197        assert!(comparison["json"]["bytes"].as_u64().unwrap() > 0);
198        assert!(comparison["toon"]["bytes"].as_u64().unwrap() > 0);
199        // TOON should be smaller
200        assert!(
201            comparison["toon"]["bytes"].as_u64().unwrap()
202                < comparison["json"]["bytes"].as_u64().unwrap()
203        );
204    }
205
206    #[test]
207    fn test_api_description_with_toon() {
208        let desc = api_description_with_toon("My API");
209        assert!(desc.contains("TOON Format Support"));
210        assert!(desc.contains("40-60% token savings"));
211    }
212}