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>,
182 pub harness_session_id: Option<String>,
188 pub auth_subject: Option<String>,
190}
191
192impl ContractParams for SessionListParams {
193 const OPERATION: &'static str = "listSessions";
194
195 fn values(&self) -> Vec<(&'static str, String)> {
196 let mut values = Vec::new();
197 push_num(&mut values, "limit", self.limit);
198 push(&mut values, "cursor", self.cursor.as_deref());
199 push(&mut values, "sort", self.sort.as_deref());
200 push_enum(&mut values, "direction", self.direction);
201 push(&mut values, "since", self.since.as_deref());
202 push(&mut values, "until", self.until.as_deref());
203 push(&mut values, "harness_id", self.harness_id.as_deref());
204 push(
205 &mut values,
206 "harness_session_id",
207 self.harness_session_id.as_deref(),
208 );
209 push(&mut values, "auth_subject", self.auth_subject.as_deref());
210 values
211 }
212}
213
214#[derive(Debug, Clone, Default, PartialEq, Eq)]
216pub struct SessionTracesParams {
217 pub payload: Option<PayloadDetail>,
219}
220
221impl ContractParams for SessionTracesParams {
222 const OPERATION: &'static str = "getSessionTraces";
223
224 fn values(&self) -> Vec<(&'static str, String)> {
225 let mut values = Vec::new();
226 push_enum(&mut values, "payload", self.payload);
227 values
228 }
229}
230
231#[derive(Debug, Clone, Default, PartialEq, Eq)]
233pub struct TraceParams {
234 pub payload: Option<PayloadDetail>,
236}
237
238impl ContractParams for TraceParams {
239 const OPERATION: &'static str = "getTrace";
240
241 fn values(&self) -> Vec<(&'static str, String)> {
242 let mut values = Vec::new();
243 push_enum(&mut values, "payload", self.payload);
244 values
245 }
246}
247
248#[derive(Debug, Clone, Default, PartialEq, Eq)]
254pub struct TraceListParams {
255 pub session_id: String,
257}
258
259impl ContractParams for TraceListParams {
260 const OPERATION: &'static str = "listTraces";
261
262 fn values(&self) -> Vec<(&'static str, String)> {
263 vec![("session_id", self.session_id.clone())]
264 }
265}
266
267#[derive(Debug, Clone, Default, PartialEq, Eq)]
271pub struct SearchSpansParams {
272 pub query: String,
274 pub top_k: Option<u32>,
276}
277
278impl ContractParams for SearchSpansParams {
279 const OPERATION: &'static str = "searchSpans";
280
281 fn values(&self) -> Vec<(&'static str, String)> {
282 let mut values = vec![("query", self.query.clone())];
283 push_num(&mut values, "top_k", self.top_k);
284 values
285 }
286}
287
288#[derive(Debug, Clone, Default, PartialEq, Eq)]
290pub struct ExportSessionParams {
291 pub detail: Option<ExportDetail>,
293}
294
295impl ContractParams for ExportSessionParams {
296 const OPERATION: &'static str = "exportSession";
297
298 fn values(&self) -> Vec<(&'static str, String)> {
299 let mut values = Vec::new();
300 push_enum(&mut values, "detail", self.detail);
301 values
302 }
303}
304
305#[derive(Debug, Clone, Default, PartialEq, Eq)]
307pub struct ExportSessionsParams {
308 pub since: Option<String>,
310 pub until: Option<String>,
312 pub detail: Option<ExportDetail>,
314}
315
316impl ContractParams for ExportSessionsParams {
317 const OPERATION: &'static str = "exportSessions";
318
319 fn values(&self) -> Vec<(&'static str, String)> {
320 let mut values = Vec::new();
321 push(&mut values, "since", self.since.as_deref());
322 push(&mut values, "until", self.until.as_deref());
323 push_enum(&mut values, "detail", self.detail);
324 values
325 }
326}
327
328#[derive(Debug, Clone, Default, PartialEq, Eq)]
330pub struct SkillsListParams {
331 pub limit: Option<u32>,
333 pub cursor: Option<String>,
335 pub q: Option<String>,
337 pub scope: Option<SkillScope>,
339 pub sort: Option<SkillSort>,
341}
342
343impl ContractParams for SkillsListParams {
344 const OPERATION: &'static str = "listSkills";
345
346 fn values(&self) -> Vec<(&'static str, String)> {
347 let mut values = Vec::new();
348 push_num(&mut values, "limit", self.limit);
349 push(&mut values, "cursor", self.cursor.as_deref());
350 push(&mut values, "q", self.q.as_deref());
351 push_enum(&mut values, "scope", self.scope);
352 push_enum(&mut values, "sort", self.sort);
353 values
354 }
355}
356
357#[derive(Debug, Clone, Default, PartialEq, Eq)]
359pub struct StatsParams {
360 pub since: Option<String>,
362 pub until: Option<String>,
364 pub auth_subject: Option<String>,
368}
369
370impl ContractParams for StatsParams {
371 const OPERATION: &'static str = "getStats";
372
373 fn values(&self) -> Vec<(&'static str, String)> {
374 let mut values = Vec::new();
375 push(&mut values, "since", self.since.as_deref());
376 push(&mut values, "until", self.until.as_deref());
377 push(&mut values, "auth_subject", self.auth_subject.as_deref());
378 values
379 }
380}
381
382fn push(values: &mut Vec<(&'static str, String)>, wire: &'static str, value: Option<&str>) {
383 if let Some(value) = value {
384 values.push((wire, value.to_owned()));
385 }
386}
387
388fn push_num(values: &mut Vec<(&'static str, String)>, wire: &'static str, value: Option<u32>) {
389 if let Some(value) = value {
390 values.push((wire, value.to_string()));
391 }
392}
393
394fn push_enum<E: ContractEnum>(
395 values: &mut Vec<(&'static str, String)>,
396 wire: &'static str,
397 value: Option<E>,
398) {
399 if let Some(value) = value {
400 values.push((wire, value.as_str().to_owned()));
401 }
402}
403
404#[cfg(test)]
405#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
406mod tests {
407 use super::*;
408
409 #[test]
410 fn an_unset_optional_parameter_is_omitted_rather_than_defaulted() {
411 assert!(SessionListParams::default().values().is_empty());
415 }
416
417 #[test]
418 fn a_set_parameter_travels_under_the_contracts_own_name() {
419 let params = SessionListParams {
420 limit: Some(25),
421 direction: Some(SortDirection::Desc),
422 ..Default::default()
423 };
424 assert_eq!(
425 params.values(),
426 vec![("limit", "25".to_owned()), ("direction", "desc".to_owned())],
427 );
428 }
429
430 #[test]
431 fn a_required_parameter_is_always_sent_even_when_it_is_empty() {
432 assert_eq!(
435 SearchSpansParams::default().values(),
436 vec![("query", String::new())],
437 );
438 }
439}