Skip to content

Commit 978bbfe

Browse files
committed
Add TOON format support with new rustapi-toon crate
Introduces the rustapi-toon crate for TOON (Token-Oriented Object Notation) support, enabling LLM-optimized data serialization and extraction. Updates workspace and rustapi-rs to support a new 'toon' feature, adds content negotiation, LLM response wrappers, and OpenAPI integration for TOON. Also includes a new example (toon-api) and benchmarking setup (toon_bench).
1 parent 6506066 commit 978bbfe

14 files changed

Lines changed: 2151 additions & 1 deletion

File tree

Cargo.lock

Lines changed: 324 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@ members = [
77
"crates/rustapi-validate",
88
"crates/rustapi-openapi",
99
"crates/rustapi-extras",
10+
"crates/rustapi-toon",
1011
"examples/hello-world",
1112
"examples/sqlx-crud",
1213
"examples/crud-api",
1314
"examples/auth-api",
1415
"examples/proof-of-concept",
16+
"examples/toon-api",
17+
"benches/toon_bench",
1518
]
1619

1720
[workspace.package]
@@ -72,9 +75,16 @@ prometheus = "0.13"
7275
# OpenAPI
7376
utoipa = { version = "4.2" }
7477

78+
# TOON format
79+
toon-format = { version = "0.4", default-features = false }
80+
81+
# Benchmarking
82+
criterion = { version = "0.5", features = ["html_reports"] }
83+
7584
# Internal crates
7685
rustapi-core = { path = "crates/rustapi-core", version = "0.1.2", default-features = false }
7786
rustapi-macros = { path = "crates/rustapi-macros", version = "0.1.2" }
7887
rustapi-validate = { path = "crates/rustapi-validate", version = "0.1.2" }
7988
rustapi-openapi = { path = "crates/rustapi-openapi", version = "0.1.2", default-features = false }
8089
rustapi-extras = { path = "crates/rustapi-extras", version = "0.1.2" }
90+
rustapi-toon = { path = "crates/rustapi-toon", version = "0.1.2" }

crates/rustapi-rs/Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ readme = "README.md"
1515
rustapi-core = { workspace = true, default-features = false }
1616
rustapi-macros = { workspace = true }
1717
rustapi-extras = { workspace = true, optional = true }
18+
rustapi-toon = { workspace = true, optional = true }
1819

1920
# Re-exports for user convenience
2021
tokio = { workspace = true }
@@ -39,6 +40,9 @@ config = ["dep:rustapi-extras", "rustapi-extras/config"]
3940
cookies = ["dep:rustapi-extras", "rustapi-extras/cookies", "rustapi-core/cookies"]
4041
sqlx = ["dep:rustapi-extras", "rustapi-extras/sqlx"]
4142

43+
# TOON format support
44+
toon = ["dep:rustapi-toon"]
45+
4246
# Meta features
4347
extras = ["jwt", "cors", "rate-limit"]
44-
full = ["extras", "config", "cookies", "sqlx"]
48+
full = ["extras", "config", "cookies", "sqlx", "toon"]

crates/rustapi-rs/src/lib.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,32 @@ pub use rustapi_extras::sqlx;
9393
#[cfg(feature = "sqlx")]
9494
pub use rustapi_extras::{convert_sqlx_error, SqlxErrorExt};
9595

96+
// Re-export TOON (feature-gated)
97+
#[cfg(feature = "toon")]
98+
pub mod toon {
99+
//! TOON (Token-Oriented Object Notation) support
100+
//!
101+
//! TOON is a compact format for LLM communication that reduces token usage by 20-40%.
102+
//!
103+
//! # Example
104+
//!
105+
//! ```rust,ignore
106+
//! use rustapi_rs::toon::{Toon, Negotiate, AcceptHeader};
107+
//!
108+
//! // As extractor
109+
//! async fn handler(Toon(data): Toon<MyType>) -> impl IntoResponse { ... }
110+
//!
111+
//! // As response
112+
//! async fn handler() -> Toon<MyType> { Toon(my_data) }
113+
//!
114+
//! // Content negotiation (returns JSON or TOON based on Accept header)
115+
//! async fn handler(accept: AcceptHeader) -> Negotiate<MyType> {
116+
//! Negotiate::new(my_data, accept.preferred)
117+
//! }
118+
//! ```
119+
pub use rustapi_toon::*;
120+
}
121+
96122
/// Prelude module - import everything you need with `use rustapi_rs::prelude::*`
97123
pub mod prelude {
98124
// Core types
@@ -188,6 +214,10 @@ pub mod prelude {
188214
// SQLx types (feature-gated)
189215
#[cfg(feature = "sqlx")]
190216
pub use rustapi_extras::{convert_sqlx_error, SqlxErrorExt};
217+
218+
// TOON types (feature-gated)
219+
#[cfg(feature = "toon")]
220+
pub use rustapi_toon::{AcceptHeader, LlmResponse, Negotiate, OutputFormat, Toon};
191221
}
192222

193223
#[cfg(test)]

crates/rustapi-toon/Cargo.toml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
[package]
2+
name = "rustapi-toon"
3+
description = "TOON (Token-Oriented Object Notation) support for RustAPI - LLM-optimized data format"
4+
version.workspace = true
5+
edition.workspace = true
6+
authors.workspace = true
7+
license.workspace = true
8+
repository.workspace = true
9+
keywords = ["web", "framework", "api", "toon", "llm"]
10+
categories = ["web-programming::http-server"]
11+
rust-version.workspace = true
12+
readme = "README.md"
13+
14+
[dependencies]
15+
# Core dependencies
16+
rustapi-core = { workspace = true }
17+
rustapi-openapi = { workspace = true }
18+
19+
# TOON format
20+
toon-format = { workspace = true }
21+
22+
# Serialization
23+
serde = { workspace = true }
24+
serde_json = { workspace = true }
25+
26+
# HTTP types
27+
bytes = { workspace = true }
28+
http = { workspace = true }
29+
http-body-util = { workspace = true }
30+
31+
# Async
32+
futures-util = { workspace = true }
33+
34+
# Logging
35+
tracing = { workspace = true }
36+
37+
# Error handling
38+
thiserror = { workspace = true }
39+
40+
[dev-dependencies]
41+
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
42+
serde_json = { workspace = true }

crates/rustapi-toon/README.md

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# rustapi-toon
2+
3+
TOON (Token-Oriented Object Notation) support for RustAPI framework.
4+
5+
## What is TOON?
6+
7+
TOON is a compact, human-readable format designed for passing structured data to Large Language Models (LLMs) with significantly reduced token usage (typically 20-40% savings).
8+
9+
## Quick Example
10+
11+
**JSON (16 tokens, 40 bytes):**
12+
```json
13+
{
14+
"users": [
15+
{ "id": 1, "name": "Alice" },
16+
{ "id": 2, "name": "Bob" }
17+
]
18+
}
19+
```
20+
21+
**TOON (13 tokens, 28 bytes) - 18.75% token savings:**
22+
```
23+
users[2]{id,name}:
24+
1,Alice
25+
2,Bob
26+
```
27+
28+
## Usage
29+
30+
Add to your `Cargo.toml`:
31+
32+
```toml
33+
[dependencies]
34+
rustapi-rs = { version = "0.1", features = ["toon"] }
35+
```
36+
37+
### Toon Extractor
38+
39+
Parse TOON request bodies:
40+
41+
```rust
42+
use rustapi_rs::prelude::*;
43+
use rustapi_rs::toon::Toon;
44+
45+
#[derive(Deserialize)]
46+
struct CreateUser {
47+
name: String,
48+
email: String,
49+
}
50+
51+
async fn create_user(Toon(user): Toon<CreateUser>) -> impl IntoResponse {
52+
// user is parsed from TOON format
53+
Json(user)
54+
}
55+
```
56+
57+
### Toon Response
58+
59+
Return TOON formatted responses:
60+
61+
```rust
62+
use rustapi_rs::prelude::*;
63+
use rustapi_rs::toon::Toon;
64+
65+
#[derive(Serialize)]
66+
struct User {
67+
id: u64,
68+
name: String,
69+
}
70+
71+
async fn get_user() -> Toon<User> {
72+
Toon(User {
73+
id: 1,
74+
name: "Alice".to_string(),
75+
})
76+
}
77+
```
78+
79+
## Content Types
80+
81+
- Request: `application/toon` or `text/toon`
82+
- Response: `application/toon`
83+
84+
## License
85+
86+
MIT OR Apache-2.0

crates/rustapi-toon/src/error.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//! TOON Error types and conversions
2+
3+
use rustapi_core::ApiError;
4+
use thiserror::Error;
5+
6+
/// Error type for TOON operations
7+
#[derive(Error, Debug)]
8+
pub enum ToonError {
9+
/// Error during TOON encoding (serialization)
10+
#[error("TOON encoding error: {0}")]
11+
Encode(String),
12+
13+
/// Error during TOON decoding (parsing/deserialization)
14+
#[error("TOON decoding error: {0}")]
15+
Decode(String),
16+
17+
/// Invalid content type for TOON request
18+
#[error("Invalid content type: expected application/toon or text/toon")]
19+
InvalidContentType,
20+
21+
/// Empty body provided
22+
#[error("Empty request body")]
23+
EmptyBody,
24+
}
25+
26+
impl From<toon_format::ToonError> for ToonError {
27+
fn from(err: toon_format::ToonError) -> Self {
28+
match &err {
29+
toon_format::ToonError::SerializationError(_) => ToonError::Encode(err.to_string()),
30+
_ => ToonError::Decode(err.to_string()),
31+
}
32+
}
33+
}
34+
35+
impl From<ToonError> for ApiError {
36+
fn from(err: ToonError) -> Self {
37+
match err {
38+
ToonError::Encode(msg) => {
39+
ApiError::internal(format!("Failed to encode TOON: {}", msg))
40+
}
41+
ToonError::Decode(msg) => ApiError::bad_request(format!("Invalid TOON: {}", msg)),
42+
ToonError::InvalidContentType => ApiError::bad_request(
43+
"Invalid content type: expected application/toon or text/toon",
44+
),
45+
ToonError::EmptyBody => ApiError::bad_request("Empty request body"),
46+
}
47+
}
48+
}

0 commit comments

Comments
 (0)