Skip to main content

ironflow_core/provider/
tool_profile.rs

1//! [`ToolProfile`] -- the name of a set of tools a provider exposes to a step.
2
3use std::borrow::Cow;
4use std::fmt;
5
6use serde::de::Error as _;
7use serde::{Deserialize, Deserializer, Serialize};
8
9/// The name of a tool profile, shared by the provider that registers it and
10/// the steps that select it.
11///
12/// Declare each profile once as a constant and use that constant on both
13/// sides: [`HttpAgentProvider::with_tool_profile`](crate::providers::http::HttpAgentProvider::with_tool_profile)
14/// and [`AgentConfig::tool_profile`](super::AgentConfig::tool_profile). A
15/// misspelled constant does not compile, where a misspelled string would only
16/// fail the step at run time.
17///
18/// It serializes as its plain name.
19///
20/// # Examples
21///
22/// ```
23/// use ironflow_core::provider::{AgentConfig, ToolProfile};
24///
25/// const BUG: ToolProfile = ToolProfile::new("bug");
26///
27/// let config = AgentConfig::new("Find the root cause").tool_profile(BUG);
28/// assert_eq!(config.tool_profile, Some(BUG));
29/// assert_eq!(BUG.to_string(), "bug");
30/// ```
31#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
32#[serde(transparent)]
33pub struct ToolProfile(Cow<'static, str>);
34
35impl ToolProfile {
36    /// Name a tool profile.
37    ///
38    /// # Panics
39    ///
40    /// Panics if `name` is empty. In a `const`, that is a compile error.
41    ///
42    /// # Examples
43    ///
44    /// ```
45    /// use ironflow_core::provider::ToolProfile;
46    ///
47    /// const SUGGESTION: ToolProfile = ToolProfile::new("suggestion");
48    /// assert_eq!(SUGGESTION.as_str(), "suggestion");
49    /// ```
50    ///
51    /// ```should_panic
52    /// use ironflow_core::provider::ToolProfile;
53    ///
54    /// let _ = ToolProfile::new("");
55    /// ```
56    pub const fn new(name: &'static str) -> Self {
57        assert!(!name.is_empty(), "tool profile name must not be empty");
58        Self(Cow::Borrowed(name))
59    }
60
61    /// The profile name.
62    ///
63    /// # Examples
64    ///
65    /// ```
66    /// use ironflow_core::provider::ToolProfile;
67    ///
68    /// assert_eq!(ToolProfile::new("bug").as_str(), "bug");
69    /// ```
70    pub fn as_str(&self) -> &str {
71        &self.0
72    }
73}
74
75impl fmt::Display for ToolProfile {
76    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77        f.write_str(&self.0)
78    }
79}
80
81impl<'de> Deserialize<'de> for ToolProfile {
82    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
83        let name = String::deserialize(deserializer)?;
84        if name.is_empty() {
85            return Err(D::Error::custom("tool profile name must not be empty"));
86        }
87        Ok(Self(Cow::Owned(name)))
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use serde_json::{from_value, json, to_value};
94
95    use super::*;
96
97    const BUG: ToolProfile = ToolProfile::new("bug");
98
99    #[test]
100    fn serializes_as_its_name() {
101        assert_eq!(to_value(BUG).unwrap(), json!("bug"));
102    }
103
104    #[test]
105    fn deserialized_profile_equals_the_constant() {
106        let back: ToolProfile = from_value(json!("bug")).unwrap();
107        assert_eq!(back, BUG);
108    }
109
110    #[test]
111    fn empty_name_is_rejected_on_deserialize() {
112        let err = from_value::<ToolProfile>(json!("")).unwrap_err();
113        assert_eq!(err.to_string(), "tool profile name must not be empty");
114    }
115
116    #[test]
117    fn unicode_name_is_kept_verbatim() {
118        let profile = ToolProfile::new("débogage");
119        assert_eq!(profile.as_str(), "débogage");
120        assert_eq!(to_value(&profile).unwrap(), json!("débogage"));
121    }
122}