Skip to main content

open_agent/types/
validated.rs

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