ai_transform_runtime/
lib.rs

1#![warn(clippy::pedantic)]
2
3//! Runtime library, providing the core functionality for the [`ai_transform`] macro, including
4//! the `OpenAI` client, error types, and the main transformation logic.
5//!
6//! ## Usage
7//!
8//! Typically used indirectly through the [`ai_transform`] macro, but can
9//! also be used directly:
10//!
11//! ```rust,no_run
12//! use ai_transform_runtime::{transform, error::TransformError};
13//! use serde::{Deserialize, Serialize};
14//!
15//! #[derive(Serialize, Deserialize, Default)]
16//! struct User { name: String, age: u32 }
17//!
18//! #[derive(Serialize, Deserialize, Default, Debug)]
19//! struct Profile { full_name: String, years_old: u32, is_adult: bool }
20//!
21//! #[tokio::main]
22//! async fn main() -> Result<(), TransformError> {
23//!     let user = User { name: "Alice".to_string(), age: 28 };
24//!     let profile: Profile = transform(user).await?;
25//!     println!("{:?}", profile);
26//!     Ok(())
27//! }
28//! ```
29//!
30//! ## How It Works
31//!
32//! 1. **Serialization**: Converts the source value to JSON
33//! 2. **Schema Generation**: Creates example JSON for both source and target types
34//! 3. **AI Request**: Sends a transformation prompt to `OpenAI`'s API
35//! 4. **Response Processing**: Extracts and cleans the JSON response
36//! 5. **Deserialization**: Converts the result back to the target type
37//!
38//! ## Configuration
39//!
40//! Environment variables:
41//! - `OPENAI_API_KEY`: Your `OpenAI` API key (**required**)
42//! - `OPENAI_MODEL`: Model to use (default: `"gpt-4o"`)
43//! - `OPENAI_BASE_URL`: API base URL (default: `"https://api.openai.com/v1"`)
44//!
45//! ## Considerations
46//!
47//! - Each call makes a network request to `OpenAI`'s API
48//! - Response time depends on data complexity and `OpenAI`'s response time
49//! - API usage incurs costs based on `OpenAI`'s pricing
50//!
51//! ## Requirements
52//!
53//! Both source and target types must implement:
54//! - `serde::Serialize` + `serde::Deserialize` + `Default`
55//!
56//! [`ai_transform`]: https://docs.rs/ai-transform
57
58mod client;
59
60pub mod error;
61
62use crate::client::OpenAIClient;
63use crate::error::TransformError;
64use serde::{Deserialize, Serialize};
65
66/// Transforms data from one type to another using AI-powered transformation.
67///
68/// # Arguments
69///
70/// * `val` - The source value to transform
71///
72/// # Returns
73///
74/// `Result<T, TransformError>` - Success contains transformed value, error contains failure details
75///
76/// # Examples
77///
78/// ```rust,no_run
79/// use ai_transform_runtime::transform;
80/// use serde::{Deserialize, Serialize};
81///
82/// #[derive(Serialize, Deserialize, Default)]
83/// struct User { name: String, age: u32 }
84///
85/// #[derive(Serialize, Deserialize, Default)]
86/// struct Profile { full_name: String, years_old: u32 }
87///
88/// # #[tokio::main]
89/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
90/// let user = User { name: "Alice".into(), age: 28 };
91/// let profile: Profile = transform(user).await?;
92/// # Ok(())
93/// # }
94/// ```
95///
96/// # Errors
97///
98/// See [`TransformError`] for all possible failure modes
99pub async fn transform<
100    S: Serialize + for<'de> Deserialize<'de> + Default,
101    T: Serialize + for<'de> Deserialize<'de> + Default,
102>(
103    val: S,
104) -> Result<T, TransformError> {
105    let source_json = serde_json::to_string(&val).map_err(TransformError::SerializationError)?;
106    let client: OpenAIClient = OpenAIClient::new()?;
107
108    let source_example = <S>::default();
109    let target_example = <T>::default();
110
111    let source_schema =
112        serde_json::to_string(&source_example).map_err(TransformError::SerializationError)?;
113    let target_schema =
114        serde_json::to_string(&target_example).map_err(TransformError::SerializationError)?;
115
116    let target_json = client
117        .transform_json(
118            &source_json,
119            stringify!(S),
120            stringify!(T),
121            &source_schema,
122            &target_schema,
123        )
124        .await?;
125
126    let result: T =
127        serde_json::from_str(&target_json).map_err(TransformError::DeserializationError)?;
128
129    Ok(result)
130}
131
132#[cfg(test)]
133mod tests {
134    // Verify `OPENAI_API_KEY` environment variable is set
135    // in order to use the macro
136    #[test]
137    #[ignore]
138    fn test_api_key_in_env() {
139        use std::env;
140
141        let api_key = env::var("OPENAI_API_KEY").unwrap_or_else(|_| String::new());
142        assert_ne!(
143            api_key, "",
144            "You must supple `OPENAI_API_KEY` via environment variable"
145        );
146    }
147}