1use serde::{Deserialize, Serialize};
23
24pub trait ContractParams {
26 const OPERATION: &'static str;
28
29 fn values(&self) -> Vec<(&'static str, String)>;
36}
37
38pub trait ContractEnum: Sized + Copy {
40 const DECLARED_BY: &'static [(&'static str, &'static str)];
43
44 const VALUES: &'static [&'static str];
46
47 fn as_str(self) -> &'static str;
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
53#[serde(rename_all = "lowercase")]
54pub enum PayloadDetail {
55 Full,
57 Preview,
59}
60
61impl ContractEnum for PayloadDetail {
62 const DECLARED_BY: &'static [(&'static str, &'static str)] =
63 &[("getSessionTraces", "payload"), ("getTrace", "payload")];
64 const VALUES: &'static [&'static str] = &["full", "preview"];
65
66 fn as_str(self) -> &'static str {
67 match self {
68 Self::Full => "full",
69 Self::Preview => "preview",
70 }
71 }
72}
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
76#[serde(rename_all = "lowercase")]
77pub enum ExportDetail {
78 Spans,
80 Traces,
82}
83
84impl ContractEnum for ExportDetail {
85 const DECLARED_BY: &'static [(&'static str, &'static str)] =
86 &[("exportSession", "detail"), ("exportSessions", "detail")];
87 const VALUES: &'static [&'static str] = &["spans", "traces"];
88
89 fn as_str(self) -> &'static str {
90 match self {
91 Self::Spans => "spans",
92 Self::Traces => "traces",
93 }
94 }
95}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
99#[serde(rename_all = "lowercase")]
100pub enum SortDirection {
101 Asc,
103 Desc,
105}
106
107impl ContractEnum for SortDirection {
108 const DECLARED_BY: &'static [(&'static str, &'static str)] = &[("listSessions", "direction")];
109 const VALUES: &'static [&'static str] = &["asc", "desc"];
110
111 fn as_str(self) -> &'static str {
112 match self {
113 Self::Asc => "asc",
114 Self::Desc => "desc",
115 }
116 }
117}
118
119#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
121#[serde(rename_all = "lowercase")]
122pub enum SkillScope {
123 All,
125 Mine,
127 Team,
129}
130
131impl ContractEnum for SkillScope {
132 const DECLARED_BY: &'static [(&'static str, &'static str)] = &[("listSkills", "scope")];
133 const VALUES: &'static [&'static str] = &["all", "mine", "team"];
134
135 fn as_str(self) -> &'static str {
136 match self {
137 Self::All => "all",
138 Self::Mine => "mine",
139 Self::Team => "team",
140 }
141 }
142}
143
144#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
146#[serde(rename_all = "lowercase")]
147pub enum SkillSort {
148 Downloads,
150}
151
152impl ContractEnum for SkillSort {
153 const DECLARED_BY: &'static [(&'static str, &'static str)] = &[("listSkills", "sort")];
154 const VALUES: &'static [&'static str] = &["downloads"];
155
156 fn as_str(self) -> &'static str {
157 match self {
158 Self::Downloads => "downloads",
159 }
160 }
161}
162
163#[derive(Debug, Clone, Default, PartialEq, Eq)]
165pub struct SessionListParams {
166 pub limit: Option<u32>,
168 pub cursor: Option<String>,
170 pub sort: Option<String>,
172 pub direction: Option<SortDirection>,
174 pub since: Option<String>,
176 pub until: Option<String>,
178 pub harness_id: Option<String>,
180 pub harness_session_id: Option<String>,
182 pub auth_subject: Option<String>,
184}
185
186impl ContractParams for SessionListParams {
187 const OPERATION: &'static str = "listSessions";
188
189 fn values(&self) -> Vec<(&'static str, String)> {
190 let mut values = Vec::new();
191 push_num(&mut values, "limit", self.limit);
192 push(&mut values, "cursor", self.cursor.as_deref());
193 push(&mut values, "sort", self.sort.as_deref());
194 push_enum(&mut values, "direction", self.direction);
195 push(&mut values, "since", self.since.as_deref());
196 push(&mut values, "until", self.until.as_deref());
197 push(&mut values, "harness_id", self.harness_id.as_deref());
198 push(
199 &mut values,
200 "harness_session_id",
201 self.harness_session_id.as_deref(),
202 );
203 push(&mut values, "auth_subject", self.auth_subject.as_deref());
204 values
205 }
206}
207
208#[derive(Debug, Clone, Default, PartialEq, Eq)]
210pub struct SessionTracesParams {
211 pub payload: Option<PayloadDetail>,
213}
214
215impl ContractParams for SessionTracesParams {
216 const OPERATION: &'static str = "getSessionTraces";
217
218 fn values(&self) -> Vec<(&'static str, String)> {
219 let mut values = Vec::new();
220 push_enum(&mut values, "payload", self.payload);
221 values
222 }
223}
224
225#[derive(Debug, Clone, Default, PartialEq, Eq)]
227pub struct TraceParams {
228 pub payload: Option<PayloadDetail>,
230}
231
232impl ContractParams for TraceParams {
233 const OPERATION: &'static str = "getTrace";
234
235 fn values(&self) -> Vec<(&'static str, String)> {
236 let mut values = Vec::new();
237 push_enum(&mut values, "payload", self.payload);
238 values
239 }
240}
241
242#[derive(Debug, Clone, Default, PartialEq, Eq)]
248pub struct TraceListParams {
249 pub session_id: String,
251}
252
253impl ContractParams for TraceListParams {
254 const OPERATION: &'static str = "listTraces";
255
256 fn values(&self) -> Vec<(&'static str, String)> {
257 vec![("session_id", self.session_id.clone())]
258 }
259}
260
261#[derive(Debug, Clone, Default, PartialEq, Eq)]
265pub struct SearchSpansParams {
266 pub query: String,
268 pub top_k: Option<u32>,
270}
271
272impl ContractParams for SearchSpansParams {
273 const OPERATION: &'static str = "searchSpans";
274
275 fn values(&self) -> Vec<(&'static str, String)> {
276 let mut values = vec![("query", self.query.clone())];
277 push_num(&mut values, "top_k", self.top_k);
278 values
279 }
280}
281
282#[derive(Debug, Clone, Default, PartialEq, Eq)]
284pub struct ExportSessionParams {
285 pub detail: Option<ExportDetail>,
287}
288
289impl ContractParams for ExportSessionParams {
290 const OPERATION: &'static str = "exportSession";
291
292 fn values(&self) -> Vec<(&'static str, String)> {
293 let mut values = Vec::new();
294 push_enum(&mut values, "detail", self.detail);
295 values
296 }
297}
298
299#[derive(Debug, Clone, Default, PartialEq, Eq)]
301pub struct ExportSessionsParams {
302 pub since: Option<String>,
304 pub until: Option<String>,
306 pub detail: Option<ExportDetail>,
308}
309
310impl ContractParams for ExportSessionsParams {
311 const OPERATION: &'static str = "exportSessions";
312
313 fn values(&self) -> Vec<(&'static str, String)> {
314 let mut values = Vec::new();
315 push(&mut values, "since", self.since.as_deref());
316 push(&mut values, "until", self.until.as_deref());
317 push_enum(&mut values, "detail", self.detail);
318 values
319 }
320}
321
322#[derive(Debug, Clone, Default, PartialEq, Eq)]
324pub struct SkillsListParams {
325 pub limit: Option<u32>,
327 pub cursor: Option<String>,
329 pub q: Option<String>,
331 pub scope: Option<SkillScope>,
333 pub sort: Option<SkillSort>,
335}
336
337impl ContractParams for SkillsListParams {
338 const OPERATION: &'static str = "listSkills";
339
340 fn values(&self) -> Vec<(&'static str, String)> {
341 let mut values = Vec::new();
342 push_num(&mut values, "limit", self.limit);
343 push(&mut values, "cursor", self.cursor.as_deref());
344 push(&mut values, "q", self.q.as_deref());
345 push_enum(&mut values, "scope", self.scope);
346 push_enum(&mut values, "sort", self.sort);
347 values
348 }
349}
350
351#[derive(Debug, Clone, Default, PartialEq, Eq)]
353pub struct StatsParams {
354 pub since: Option<String>,
356 pub until: Option<String>,
358}
359
360impl ContractParams for StatsParams {
361 const OPERATION: &'static str = "getStats";
362
363 fn values(&self) -> Vec<(&'static str, String)> {
364 let mut values = Vec::new();
365 push(&mut values, "since", self.since.as_deref());
366 push(&mut values, "until", self.until.as_deref());
367 values
368 }
369}
370
371fn push(values: &mut Vec<(&'static str, String)>, wire: &'static str, value: Option<&str>) {
372 if let Some(value) = value {
373 values.push((wire, value.to_owned()));
374 }
375}
376
377fn push_num(values: &mut Vec<(&'static str, String)>, wire: &'static str, value: Option<u32>) {
378 if let Some(value) = value {
379 values.push((wire, value.to_string()));
380 }
381}
382
383fn push_enum<E: ContractEnum>(
384 values: &mut Vec<(&'static str, String)>,
385 wire: &'static str,
386 value: Option<E>,
387) {
388 if let Some(value) = value {
389 values.push((wire, value.as_str().to_owned()));
390 }
391}
392
393#[cfg(test)]
394#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
395mod tests {
396 use super::*;
397
398 #[test]
399 fn an_unset_optional_parameter_is_omitted_rather_than_defaulted() {
400 assert!(SessionListParams::default().values().is_empty());
404 }
405
406 #[test]
407 fn a_set_parameter_travels_under_the_contracts_own_name() {
408 let params = SessionListParams {
409 limit: Some(25),
410 direction: Some(SortDirection::Desc),
411 ..Default::default()
412 };
413 assert_eq!(
414 params.values(),
415 vec![("limit", "25".to_owned()), ("direction", "desc".to_owned())],
416 );
417 }
418
419 #[test]
420 fn a_required_parameter_is_always_sent_even_when_it_is_empty() {
421 assert_eq!(
424 SearchSpansParams::default().values(),
425 vec![("query", String::new())],
426 );
427 }
428}