open_agent/types/validated.rs
1// ============================================================================
2// NEWTYPE WRAPPERS FOR COMPILE-TIME TYPE SAFETY
3// ============================================================================
4
5/// Validated model name with compile-time type safety.
6///
7/// This newtype wrapper ensures that model names are validated at construction time
8/// rather than at runtime, catching invalid configurations earlier in development.
9///
10/// # Validation Rules
11///
12/// - Must not be empty
13/// - Must not be only whitespace
14///
15/// # Example
16///
17/// ```
18/// use open_agent::ModelName;
19///
20/// // Valid model name
21/// let model = ModelName::new("qwen2.5-32b-instruct").unwrap();
22/// assert_eq!(model.as_str(), "qwen2.5-32b-instruct");
23///
24/// // Invalid: empty string
25/// assert!(ModelName::new("").is_err());
26///
27/// // Invalid: whitespace only
28/// assert!(ModelName::new(" ").is_err());
29/// ```
30#[derive(Debug, Clone, PartialEq, Eq, Hash)]
31pub struct ModelName(String);
32
33impl ModelName {
34 /// Creates a new `ModelName` after validation.
35 ///
36 /// # Errors
37 ///
38 /// Returns an error if the model name is empty or contains only whitespace.
39 pub fn new(name: impl Into<String>) -> crate::Result<Self> {
40 let name = name.into();
41 let trimmed = name.trim();
42
43 if trimmed.is_empty() {
44 return Err(Error::invalid_input(
45 "Model name cannot be empty or whitespace",
46 ));
47 }
48
49 Ok(ModelName(name))
50 }
51
52 /// Returns the model name as a string slice.
53 pub fn as_str(&self) -> &str {
54 &self.0
55 }
56
57 /// Consumes the `ModelName` and returns the inner `String`.
58 pub fn into_inner(self) -> String {
59 self.0
60 }
61}
62
63impl std::fmt::Display for ModelName {
64 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65 write!(f, "{}", self.0)
66 }
67}
68
69/// Validated base URL with compile-time type safety.
70///
71/// This newtype wrapper ensures that base URLs are validated at construction time
72/// rather than at runtime, catching invalid configurations earlier in development.
73///
74/// # Validation Rules
75///
76/// - Must not be empty
77/// - Must start with `http://` or `https://`
78///
79/// # Example
80///
81/// ```
82/// use open_agent::BaseUrl;
83///
84/// // Valid base URLs
85/// let url = BaseUrl::new("http://localhost:1234/v1").unwrap();
86/// assert_eq!(url.as_str(), "http://localhost:1234/v1");
87///
88/// let url = BaseUrl::new("https://api.openai.com/v1").unwrap();
89/// assert_eq!(url.as_str(), "https://api.openai.com/v1");
90///
91/// // Invalid: no http/https prefix
92/// assert!(BaseUrl::new("localhost:1234").is_err());
93///
94/// // Invalid: empty string
95/// assert!(BaseUrl::new("").is_err());
96/// ```
97#[derive(Debug, Clone, PartialEq, Eq, Hash)]
98pub struct BaseUrl(String);
99
100impl BaseUrl {
101 /// Creates a new `BaseUrl` after validation.
102 ///
103 /// # Errors
104 ///
105 /// Returns an error if the URL is empty or doesn't start with http:// or https://.
106 pub fn new(url: impl Into<String>) -> crate::Result<Self> {
107 let url = url.into();
108 let trimmed = url.trim();
109
110 if trimmed.is_empty() {
111 return Err(Error::invalid_input("base_url cannot be empty"));
112 }
113
114 if !trimmed.starts_with("http://") && !trimmed.starts_with("https://") {
115 return Err(Error::invalid_input(
116 "base_url must start with http:// or https://",
117 ));
118 }
119
120 Ok(BaseUrl(url))
121 }
122
123 /// Returns the base URL as a string slice.
124 pub fn as_str(&self) -> &str {
125 &self.0
126 }
127
128 /// Consumes the `BaseUrl` and returns the inner `String`.
129 pub fn into_inner(self) -> String {
130 self.0
131 }
132}
133
134impl std::fmt::Display for BaseUrl {
135 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
136 write!(f, "{}", self.0)
137 }
138}
139
140/// Validated temperature value with compile-time type safety.
141///
142/// This newtype wrapper ensures that temperature values are validated at construction time
143/// rather than at runtime, catching invalid configurations earlier in development.
144///
145/// # Validation Rules
146///
147/// - Must be between 0.0 and 2.0 (inclusive)
148///
149/// # Example
150///
151/// ```
152/// use open_agent::Temperature;
153///
154/// // Valid temperatures
155/// let temp = Temperature::new(0.7).unwrap();
156/// assert_eq!(temp.value(), 0.7);
157///
158/// let temp = Temperature::new(0.0).unwrap();
159/// assert_eq!(temp.value(), 0.0);
160///
161/// let temp = Temperature::new(2.0).unwrap();
162/// assert_eq!(temp.value(), 2.0);
163///
164/// // Invalid: below range
165/// assert!(Temperature::new(-0.1).is_err());
166///
167/// // Invalid: above range
168/// assert!(Temperature::new(2.1).is_err());
169/// ```
170#[derive(Debug, Clone, Copy, PartialEq)]
171pub struct Temperature(f32);
172
173impl Temperature {
174 /// Creates a new `Temperature` after validation.
175 ///
176 /// # Errors
177 ///
178 /// Returns an error if the temperature is not between 0.0 and 2.0 (inclusive).
179 pub fn new(temp: f32) -> crate::Result<Self> {
180 if !(0.0..=2.0).contains(&temp) {
181 return Err(Error::invalid_input(
182 "temperature must be between 0.0 and 2.0",
183 ));
184 }
185
186 Ok(Temperature(temp))
187 }
188
189 /// Returns the temperature value.
190 pub fn value(&self) -> f32 {
191 self.0
192 }
193}
194
195impl std::fmt::Display for Temperature {
196 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
197 write!(f, "{}", self.0)
198 }
199}
200
201// ============================================================================
202// AGENT CONFIGURATION
203// ============================================================================