Skip to main content

claude_wrapper/
types.rs

1use serde::{Deserialize, Serialize};
2
3/// Output format for `--output-format`.
4#[derive(Debug, Clone, Copy, Default)]
5pub enum OutputFormat {
6    /// Plain text output (default).
7    #[default]
8    Text,
9    /// Single JSON result object.
10    Json,
11    /// Streaming NDJSON.
12    StreamJson,
13}
14
15impl OutputFormat {
16    pub(crate) fn as_arg(&self) -> &'static str {
17        match self {
18            Self::Text => "text",
19            Self::Json => "json",
20            Self::StreamJson => "stream-json",
21        }
22    }
23}
24
25/// Permission mode for `--permission-mode`.
26#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
27#[serde(rename_all = "camelCase")]
28pub enum PermissionMode {
29    /// Default interactive permissions.
30    #[default]
31    Default,
32    /// Auto-accept file edits.
33    AcceptEdits,
34    /// Bypass all permission checks.
35    BypassPermissions,
36    /// Don't ask for permissions (deny by default).
37    DontAsk,
38    /// Plan mode (read-only).
39    Plan,
40    /// Auto mode.
41    Auto,
42}
43
44impl PermissionMode {
45    pub(crate) fn as_arg(&self) -> &'static str {
46        match self {
47            Self::Default => "default",
48            Self::AcceptEdits => "acceptEdits",
49            Self::BypassPermissions => "bypassPermissions",
50            Self::DontAsk => "dontAsk",
51            Self::Plan => "plan",
52            Self::Auto => "auto",
53        }
54    }
55}
56
57/// Input format for `--input-format`.
58#[derive(Debug, Clone, Copy, Default)]
59pub enum InputFormat {
60    /// Plain text input (default).
61    #[default]
62    Text,
63    /// Streaming JSON input.
64    StreamJson,
65}
66
67impl InputFormat {
68    pub(crate) fn as_arg(&self) -> &'static str {
69        match self {
70            Self::Text => "text",
71            Self::StreamJson => "stream-json",
72        }
73    }
74}
75
76/// Effort level for `--effort`.
77#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
78#[serde(rename_all = "lowercase")]
79pub enum Effort {
80    /// Low effort.
81    Low,
82    /// Medium effort (default).
83    Medium,
84    /// High effort.
85    High,
86    /// Maximum effort, most thorough.
87    Max,
88}
89
90impl Effort {
91    pub(crate) fn as_arg(&self) -> &'static str {
92        match self {
93            Self::Low => "low",
94            Self::Medium => "medium",
95            Self::High => "high",
96            Self::Max => "max",
97        }
98    }
99}
100
101/// Scope for MCP and plugin commands.
102#[derive(Debug, Clone, Copy, Default)]
103pub enum Scope {
104    /// Local scope (current directory).
105    #[default]
106    Local,
107    /// User scope (global).
108    User,
109    /// Project scope.
110    Project,
111}
112
113impl Scope {
114    pub(crate) fn as_arg(&self) -> &'static str {
115        match self {
116            Self::Local => "local",
117            Self::User => "user",
118            Self::Project => "project",
119        }
120    }
121}
122
123/// Authentication status returned by `claude auth status --json`.
124///
125/// # Example
126///
127/// ```no_run
128/// # async fn example() -> claude_wrapper::Result<()> {
129/// let claude = claude_wrapper::Claude::builder().build()?;
130/// let status = claude_wrapper::AuthStatusCommand::new()
131///     .execute_json(&claude).await?;
132///
133/// if status.logged_in {
134///     println!("Logged in as {}", status.email.unwrap_or_default());
135/// }
136/// # Ok(())
137/// # }
138/// ```
139#[cfg(feature = "json")]
140#[derive(Debug, Clone, Deserialize, Serialize)]
141#[serde(rename_all = "camelCase")]
142pub struct AuthStatus {
143    /// Whether the user is currently logged in.
144    #[serde(default)]
145    pub logged_in: bool,
146    /// Authentication method (e.g. "claude.ai").
147    #[serde(default)]
148    pub auth_method: Option<String>,
149    /// API provider (e.g. "firstParty").
150    #[serde(default)]
151    pub api_provider: Option<String>,
152    /// Authenticated user's email address.
153    #[serde(default)]
154    pub email: Option<String>,
155    /// Organization ID.
156    #[serde(default)]
157    pub org_id: Option<String>,
158    /// Organization name.
159    #[serde(default)]
160    pub org_name: Option<String>,
161    /// Subscription type (e.g. "pro", "max").
162    #[serde(default)]
163    pub subscription_type: Option<String>,
164    /// Any additional fields not explicitly modeled.
165    #[serde(flatten)]
166    pub extra: std::collections::HashMap<String, serde_json::Value>,
167}
168
169/// A message in the query JSON output.
170#[cfg(feature = "json")]
171#[derive(Debug, Clone, Deserialize, Serialize)]
172pub struct QueryMessage {
173    #[serde(default)]
174    pub role: String,
175    #[serde(default)]
176    pub content: serde_json::Value,
177    #[serde(flatten)]
178    pub extra: std::collections::HashMap<String, serde_json::Value>,
179}
180
181/// Result from a query with `--output-format json`.
182#[cfg(feature = "json")]
183#[derive(Debug, Clone, Deserialize, Serialize)]
184pub struct QueryResult {
185    #[serde(default)]
186    pub result: String,
187    #[serde(default)]
188    pub session_id: String,
189    #[serde(default, rename = "total_cost_usd", alias = "cost_usd")]
190    pub cost_usd: Option<f64>,
191    #[serde(default)]
192    pub duration_ms: Option<u64>,
193    #[serde(default)]
194    pub num_turns: Option<u32>,
195    #[serde(default)]
196    pub is_error: bool,
197    #[serde(flatten)]
198    pub extra: std::collections::HashMap<String, serde_json::Value>,
199}
200
201#[cfg(all(test, feature = "json"))]
202mod tests {
203    use super::*;
204
205    #[test]
206    fn query_result_deserializes_total_cost_usd() {
207        let json =
208            r#"{"result":"hello","session_id":"s1","total_cost_usd":0.042,"is_error":false}"#;
209        let qr: QueryResult = serde_json::from_str(json).unwrap();
210        assert_eq!(qr.cost_usd, Some(0.042));
211    }
212
213    #[test]
214    fn query_result_deserializes_cost_usd_alias() {
215        let json = r#"{"result":"hello","session_id":"s1","cost_usd":0.01,"is_error":false}"#;
216        let qr: QueryResult = serde_json::from_str(json).unwrap();
217        assert_eq!(qr.cost_usd, Some(0.01));
218    }
219
220    #[test]
221    fn query_result_missing_cost_defaults_to_none() {
222        let json = r#"{"result":"hello","session_id":"s1","is_error":false}"#;
223        let qr: QueryResult = serde_json::from_str(json).unwrap();
224        assert_eq!(qr.cost_usd, None);
225    }
226
227    #[test]
228    fn query_result_deserializes_num_turns() {
229        let json = r#"{"result":"done","session_id":"s2","total_cost_usd":0.1,"num_turns":5,"is_error":false}"#;
230        let qr: QueryResult = serde_json::from_str(json).unwrap();
231        assert_eq!(qr.num_turns, Some(5));
232        assert_eq!(qr.cost_usd, Some(0.1));
233    }
234
235    #[test]
236    fn query_result_serializes_as_total_cost_usd() {
237        let qr = QueryResult {
238            result: "ok".into(),
239            session_id: "s1".into(),
240            cost_usd: Some(0.05),
241            duration_ms: None,
242            num_turns: Some(3),
243            is_error: false,
244            extra: Default::default(),
245        };
246        let json = serde_json::to_string(&qr).unwrap();
247        assert!(json.contains("\"total_cost_usd\""));
248        assert!(json.contains("\"num_turns\""));
249    }
250}