Skip to main content

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// ============================================================================