use serde::{Deserialize, Serialize};
pub trait ContractParams {
const OPERATION: &'static str;
fn values(&self) -> Vec<(&'static str, String)>;
}
pub trait ContractEnum: Sized + Copy {
const DECLARED_BY: &'static [(&'static str, &'static str)];
const VALUES: &'static [&'static str];
fn as_str(self) -> &'static str;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum PayloadDetail {
Full,
Preview,
}
impl ContractEnum for PayloadDetail {
const DECLARED_BY: &'static [(&'static str, &'static str)] =
&[("getSessionTraces", "payload"), ("getTrace", "payload")];
const VALUES: &'static [&'static str] = &["full", "preview"];
fn as_str(self) -> &'static str {
match self {
Self::Full => "full",
Self::Preview => "preview",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ExportDetail {
Spans,
Traces,
}
impl ContractEnum for ExportDetail {
const DECLARED_BY: &'static [(&'static str, &'static str)] =
&[("exportSession", "detail"), ("exportSessions", "detail")];
const VALUES: &'static [&'static str] = &["spans", "traces"];
fn as_str(self) -> &'static str {
match self {
Self::Spans => "spans",
Self::Traces => "traces",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SortDirection {
Asc,
Desc,
}
impl ContractEnum for SortDirection {
const DECLARED_BY: &'static [(&'static str, &'static str)] = &[("listSessions", "direction")];
const VALUES: &'static [&'static str] = &["asc", "desc"];
fn as_str(self) -> &'static str {
match self {
Self::Asc => "asc",
Self::Desc => "desc",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SkillScope {
All,
Mine,
Team,
}
impl ContractEnum for SkillScope {
const DECLARED_BY: &'static [(&'static str, &'static str)] = &[("listSkills", "scope")];
const VALUES: &'static [&'static str] = &["all", "mine", "team"];
fn as_str(self) -> &'static str {
match self {
Self::All => "all",
Self::Mine => "mine",
Self::Team => "team",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SkillSort {
Downloads,
}
impl ContractEnum for SkillSort {
const DECLARED_BY: &'static [(&'static str, &'static str)] = &[("listSkills", "sort")];
const VALUES: &'static [&'static str] = &["downloads"];
fn as_str(self) -> &'static str {
match self {
Self::Downloads => "downloads",
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct SessionListParams {
pub limit: Option<u32>,
pub cursor: Option<String>,
pub sort: Option<String>,
pub direction: Option<SortDirection>,
pub since: Option<String>,
pub until: Option<String>,
pub harness_id: Option<String>,
pub harness_session_id: Option<String>,
pub auth_subject: Option<String>,
}
impl ContractParams for SessionListParams {
const OPERATION: &'static str = "listSessions";
fn values(&self) -> Vec<(&'static str, String)> {
let mut values = Vec::new();
push_num(&mut values, "limit", self.limit);
push(&mut values, "cursor", self.cursor.as_deref());
push(&mut values, "sort", self.sort.as_deref());
push_enum(&mut values, "direction", self.direction);
push(&mut values, "since", self.since.as_deref());
push(&mut values, "until", self.until.as_deref());
push(&mut values, "harness_id", self.harness_id.as_deref());
push(
&mut values,
"harness_session_id",
self.harness_session_id.as_deref(),
);
push(&mut values, "auth_subject", self.auth_subject.as_deref());
values
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct SessionTracesParams {
pub payload: Option<PayloadDetail>,
}
impl ContractParams for SessionTracesParams {
const OPERATION: &'static str = "getSessionTraces";
fn values(&self) -> Vec<(&'static str, String)> {
let mut values = Vec::new();
push_enum(&mut values, "payload", self.payload);
values
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct TraceParams {
pub payload: Option<PayloadDetail>,
}
impl ContractParams for TraceParams {
const OPERATION: &'static str = "getTrace";
fn values(&self) -> Vec<(&'static str, String)> {
let mut values = Vec::new();
push_enum(&mut values, "payload", self.payload);
values
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct TraceListParams {
pub session_id: String,
}
impl ContractParams for TraceListParams {
const OPERATION: &'static str = "listTraces";
fn values(&self) -> Vec<(&'static str, String)> {
vec![("session_id", self.session_id.clone())]
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct SearchSpansParams {
pub query: String,
pub top_k: Option<u32>,
}
impl ContractParams for SearchSpansParams {
const OPERATION: &'static str = "searchSpans";
fn values(&self) -> Vec<(&'static str, String)> {
let mut values = vec![("query", self.query.clone())];
push_num(&mut values, "top_k", self.top_k);
values
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ExportSessionParams {
pub detail: Option<ExportDetail>,
}
impl ContractParams for ExportSessionParams {
const OPERATION: &'static str = "exportSession";
fn values(&self) -> Vec<(&'static str, String)> {
let mut values = Vec::new();
push_enum(&mut values, "detail", self.detail);
values
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ExportSessionsParams {
pub since: Option<String>,
pub until: Option<String>,
pub detail: Option<ExportDetail>,
}
impl ContractParams for ExportSessionsParams {
const OPERATION: &'static str = "exportSessions";
fn values(&self) -> Vec<(&'static str, String)> {
let mut values = Vec::new();
push(&mut values, "since", self.since.as_deref());
push(&mut values, "until", self.until.as_deref());
push_enum(&mut values, "detail", self.detail);
values
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct SkillsListParams {
pub limit: Option<u32>,
pub cursor: Option<String>,
pub q: Option<String>,
pub scope: Option<SkillScope>,
pub sort: Option<SkillSort>,
}
impl ContractParams for SkillsListParams {
const OPERATION: &'static str = "listSkills";
fn values(&self) -> Vec<(&'static str, String)> {
let mut values = Vec::new();
push_num(&mut values, "limit", self.limit);
push(&mut values, "cursor", self.cursor.as_deref());
push(&mut values, "q", self.q.as_deref());
push_enum(&mut values, "scope", self.scope);
push_enum(&mut values, "sort", self.sort);
values
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct StatsParams {
pub since: Option<String>,
pub until: Option<String>,
pub auth_subject: Option<String>,
}
impl ContractParams for StatsParams {
const OPERATION: &'static str = "getStats";
fn values(&self) -> Vec<(&'static str, String)> {
let mut values = Vec::new();
push(&mut values, "since", self.since.as_deref());
push(&mut values, "until", self.until.as_deref());
push(&mut values, "auth_subject", self.auth_subject.as_deref());
values
}
}
fn push(values: &mut Vec<(&'static str, String)>, wire: &'static str, value: Option<&str>) {
if let Some(value) = value {
values.push((wire, value.to_owned()));
}
}
fn push_num(values: &mut Vec<(&'static str, String)>, wire: &'static str, value: Option<u32>) {
if let Some(value) = value {
values.push((wire, value.to_string()));
}
}
fn push_enum<E: ContractEnum>(
values: &mut Vec<(&'static str, String)>,
wire: &'static str,
value: Option<E>,
) {
if let Some(value) = value {
values.push((wire, value.as_str().to_owned()));
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
mod tests {
use super::*;
#[test]
fn an_unset_optional_parameter_is_omitted_rather_than_defaulted() {
assert!(SessionListParams::default().values().is_empty());
}
#[test]
fn a_set_parameter_travels_under_the_contracts_own_name() {
let params = SessionListParams {
limit: Some(25),
direction: Some(SortDirection::Desc),
..Default::default()
};
assert_eq!(
params.values(),
vec![("limit", "25".to_owned()), ("direction", "desc".to_owned())],
);
}
#[test]
fn a_required_parameter_is_always_sent_even_when_it_is_empty() {
assert_eq!(
SearchSpansParams::default().values(),
vec![("query", String::new())],
);
}
}