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 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, 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>,
pub claimed: Vec<(String, 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 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!(
TraceListParams::default().values(),
vec![("session_id", String::new())],
);
}
}