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}