ai_transform/lib.rs
1#![warn(clippy::pedantic)]
2
3//! Procedural macro for AI-powered data transformations between JSON-serializable types.
4//!
5//! Transforms data from one JSON-serializable type to another using `OpenAI`'s
6//! language models. The idea is to enable semantic understanding when that is
7//! useful and easier than writing deterministic parser by hand.
8//!
9//! **Important**: This crate requires [`ai_transform_runtime`] to run properly.
10//!
11//! ```toml
12//! [dependencies]
13//! ai-transform = "0.1.0"
14//! ai-transform-runtime = "0.1.0" # Required!
15//! ```
16//!
17//! # How It Works
18//!
19//! 1. **Macro Expansion**: Generates `ai_transform_runtime::transform::<S, T>(value)`
20//! 2. **Serialization**: Converts source value to JSON
21//! 3. **Schema Generation**: Creates example JSON for both types using `Default`
22//! 4. **AI Request**: Sends transformation prompt to `OpenAI` with context
23//! 5. **Response Processing**: Extracts and validates JSON from AI response
24//! 6. **Deserialization**: Converts result to target type
25//!
26//! # Considerations
27//!
28//! - Each call makes an HTTP request to `OpenAI`'s API
29//! - Consider caching for repeated transformations
30//! - Response time: ~1-5 seconds depending on complexity
31//!
32//! # Configuration
33//!
34//! Environment variables:
35//! - `OPENAI_API_KEY`: Your API key (required)
36//! - `OPENAI_MODEL`: Model to use (default: `"gpt-4o"`)
37//! - `OPENAI_BASE_URL`: API endpoint (default: `OpenAI`'s URL)
38//!
39//! See [`transform!`] for usage info.
40//!
41//! [`ai_transform_runtime`]: https://docs.rs/ai-transform-runtime
42
43use proc_macro::TokenStream;
44use quote::quote;
45use syn::{parse_macro_input, Type};
46
47/// Type representing macro input for parsing.
48struct TransformInput {
49 source_type: Type,
50 target_type: Type,
51 source_value: syn::Expr,
52}
53
54impl syn::parse::Parse for TransformInput {
55 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
56 let source_type: Type = input.parse()?;
57 input.parse::<syn::Token![,]>()?;
58 let target_type: Type = input.parse()?;
59 input.parse::<syn::Token![,]>()?;
60 let source_value: syn::Expr = input.parse()?;
61
62 Ok(TransformInput {
63 source_type,
64 target_type,
65 source_value,
66 })
67 }
68}
69
70/// AI-powered data transformation macro.
71///
72/// **Important:** It is intended to be used when you understand that such a
73/// transformation from source type to target type makes sense.
74///
75/// # Syntax
76///
77/// ```rust,ignore
78/// transform!(SourceType, TargetType, source_value)
79/// ```
80///
81/// # Requirements
82///
83/// **Dependencies**: Add both this and runtime to your `Cargo.toml`:
84/// ```toml
85/// [dependencies]
86/// ai-transform = "0.1.0"
87/// ai-transform-runtime = "0.1.0"
88/// serde = { version = "1.0", features = ["derive"] }
89/// tokio = { version = "1.0", features = ["macros"] }
90/// ```
91///
92/// **Environment**: Set your `OpenAI` API key:
93/// ```bash
94/// export OPENAI_API_KEY="your-api-key-here"
95/// ```
96///
97/// **Type Requirements**: Both types must implement:
98/// - `serde::Serialize` + `serde::Deserialize` + `Default`
99///
100/// # Arguments
101///
102/// * `SourceType` - Input data type
103/// * `TargetType` - Output data type
104/// * `source_value` - Expression evaluating to a `SourceType` value
105///
106/// # Returns
107///
108/// `Future<Result<TargetType, ai_transform_runtime::error::TransformError>>`
109///
110/// # Examples
111///
112/// ## Basic Field Mapping
113///
114/// ```rust,ignore
115/// use ai_transform::transform;
116/// use serde::{Deserialize, Serialize};
117///
118/// #[derive(Serialize, Deserialize, Default)]
119/// struct User { name: String, age: u32 }
120///
121/// #[derive(Serialize, Deserialize, Default)]
122/// struct Profile { full_name: String, years_old: u32, is_adult: bool }
123///
124/// # #[tokio::main]
125/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
126/// let user = User { name: "Alice".into(), age: 28 };
127///
128/// // AI automatically maps: name → full_name, age → years_old
129/// // and computes: is_adult from age
130/// let profile: Profile = transform!(User, Profile, user).await?;
131/// # Ok(())
132/// # }
133/// ```
134///
135/// ## Error Handling
136///
137/// ```rust,ignore
138/// # use ai_transform::transform;
139/// # use serde::{Deserialize, Serialize};
140/// use ai_transform_runtime::error::TransformError;
141///
142/// # #[derive(Serialize, Deserialize, Default, Debug)]
143/// # struct Source { data: String }
144/// # #[derive(Serialize, Deserialize, Default, Debug)]
145/// # struct Target { result: String }
146///
147/// # #[tokio::main]
148/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
149/// # let source = Source::default();
150/// match transform!(Source, Target, source).await {
151/// Ok(result) => println!("Success: {:?}", result),
152/// Err(TransformError::EnvVarError(var)) => {
153/// eprintln!("Missing environment variable: {}", var);
154/// }
155/// Err(TransformError::ApiError { status, body }) => {
156/// eprintln!("OpenAI API error {}: {}", status, body);
157/// }
158/// Err(e) => eprintln!("Other error: {}", e),
159/// }
160/// # Ok(())
161/// # }
162/// ```
163///
164/// # Errors
165///
166/// See [`ai_transform_runtime::error::TransformError`] for details and all error variants.
167#[proc_macro]
168pub fn transform(input: TokenStream) -> TokenStream {
169 let input = parse_macro_input!(input as TransformInput);
170
171 let source_type = &input.source_type;
172 let target_type = &input.target_type;
173 let source_value = &input.source_value;
174
175 TokenStream::from(quote! {
176 ai_transform_runtime::transform::<#source_type, #target_type>(#source_value)
177 })
178}