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}