1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
use Serialize;
use DeserializeOwned;
use crateResult;
use crateSchemaType;
/// The `Instructor` trait combines JSON schema generation, serialization, and validation.
///
/// This trait is automatically implemented for any type that implements the required traits
/// (SchemaType, DeserializeOwned, and Serialize), but you can also provide a custom
/// implementation to add your own validation logic.
///
/// # Nested Types and Schema Embedding
///
/// When you have nested structs or enums, they should also derive `Instructor` to ensure
/// their full schema is embedded in the parent type. This produces complete JSON schemas
/// that help LLMs generate correct structured output.
///
/// ```rust
/// # use rstructor::Instructor;
/// # use serde::{Serialize, Deserialize};
/// // Parent type derives Instructor
/// #[derive(Instructor, Serialize, Deserialize)]
/// struct Parent {
/// child: Child, // Child's schema will be embedded
/// }
///
/// // Nested types should also derive Instructor for complete schema
/// #[derive(Instructor, Serialize, Deserialize)]
/// struct Child {
/// name: String,
/// }
/// ```
///
/// This ensures the generated schema includes all nested properties.
///
/// # Validation
///
/// The `validate` method is called automatically when an LLM generates a structured response,
/// allowing you to apply domain-specific validation logic beyond what type checking provides.
///
/// To add custom validation, use the `validate` attribute with a function path:
///
/// ```
/// use rstructor::{Instructor, RStructorError};
/// use serde::{Serialize, Deserialize};
///
/// #[derive(Instructor, Serialize, Deserialize)]
/// #[llm(validate = "validate_product")]
/// struct Product {
/// name: String,
/// price: f64,
/// quantity: u32,
/// }
///
/// fn validate_product(product: &Product) -> rstructor::Result<()> {
/// // Price must be positive
/// if product.price <= 0.0 {
/// return Err(RStructorError::ValidationError(
/// format!("Product price must be positive, got {}", product.price)
/// ));
/// }
///
/// // Name can't be empty
/// if product.name.trim().is_empty() {
/// return Err(RStructorError::ValidationError(
/// "Product name cannot be empty".to_string()
/// ));
/// }
///
/// Ok(())
/// }
/// ```
///
/// # Example: Using with LLM clients
///
/// ```no_run
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
/// use rstructor::{LLMClient, OpenAIClient, OpenAIModel, Instructor};
/// use serde::{Serialize, Deserialize};
///
/// // Define a product model
/// #[derive(Instructor, Serialize, Deserialize, Debug)]
/// struct ProductInfo {
/// name: String,
/// price: f64,
/// }
///
/// // Create a client
/// let client = OpenAIClient::new("your-api-key")?
/// .model(OpenAIModel::Gpt55);
///
/// // Get structured data with automatic validation
/// let product = client.materialize::<ProductInfo>("Describe a laptop").await?;
/// # Ok(())
/// # }
/// ```
// The blanket implementation is removed
// Instead, the derive macro will handle implementing Instructor for each type
// This avoids the conflicting implementation errors
/// Helper trait to mark a type as implementing custom validation.
///
/// This is a marker trait only used for documentation purposes to indicate
/// that a type provides a custom validation implementation beyond the default.
/// You don't need to implement this trait directly - it's only for clarity
/// in documentation.