use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::{LinkId, LinkLabel, LinkPermissions, LinkSecret, StreamId, StreamTitle};
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Visibility {
#[default]
Private,
Public,
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
pub struct CreateStreamRequest {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub title: Option<StreamTitle>,
#[serde(default)]
pub visibility: Visibility,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expires_in_secs: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub issue_links: Option<Vec<InitialStreamLink>>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct InitialStreamLink {
pub label: LinkLabel,
pub permissions: LinkPermissions,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct IssuedStreamLink {
pub link_id: LinkId,
pub label: LinkLabel,
pub permissions: LinkPermissions,
#[serde(serialize_with = "crate::ids::serialize_link_secret")]
pub secret: LinkSecret,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CreateStreamResponse {
pub stream_id: StreamId,
#[serde(deserialize_with = "deserialize_nullable_stream_title")]
pub title: Option<StreamTitle>,
pub visibility: Visibility,
pub expires_at: String,
pub links: Vec<IssuedStreamLink>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct IssueLinkRequest {
pub label: LinkLabel,
pub permissions: LinkPermissions,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expires_at: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct IssueLinkResponse {
pub link_id: LinkId,
pub label: LinkLabel,
pub permissions: LinkPermissions,
#[serde(serialize_with = "crate::ids::serialize_link_secret")]
pub secret: LinkSecret,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct RenameLinkRequest {
pub label: LinkLabel,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum StreamLinkStatus {
Active,
Expired,
Revoked,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct StreamLinkSummary {
pub link_id: LinkId,
pub label: LinkLabel,
pub permissions: LinkPermissions,
pub status: StreamLinkStatus,
pub issued_at: String,
pub expires_at: Option<String>,
pub revoked_at: Option<String>,
pub is_current: bool,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct ListLinksResponse {
pub links: Vec<StreamLinkSummary>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct StreamInfoResponse {
pub stream_id: StreamId,
#[serde(deserialize_with = "deserialize_nullable_stream_title")]
pub title: Option<StreamTitle>,
pub basin: String,
pub visibility: Visibility,
pub state: String,
pub expires_at: String,
pub active_link_count: usize,
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
pub struct UpdateStreamRequest {
#[serde(
default,
skip_serializing_if = "StreamTitleUpdate::is_unchanged",
serialize_with = "serialize_stream_title_update",
deserialize_with = "deserialize_stream_title_update"
)]
pub title: StreamTitleUpdate,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub visibility: Option<Visibility>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expires_at: Option<String>,
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub enum StreamTitleUpdate {
#[default]
Unchanged,
Set(StreamTitle),
Clear,
}
impl StreamTitleUpdate {
fn is_unchanged(&self) -> bool {
matches!(self, Self::Unchanged)
}
}
fn serialize_stream_title_update<S>(
update: &StreamTitleUpdate,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match update {
StreamTitleUpdate::Set(title) => serializer.serialize_some(title),
StreamTitleUpdate::Clear => serializer.serialize_none(),
StreamTitleUpdate::Unchanged => serializer.serialize_unit(),
}
}
fn deserialize_stream_title_update<'de, D>(deserializer: D) -> Result<StreamTitleUpdate, D::Error>
where
D: Deserializer<'de>,
{
Ok(match Option::<StreamTitle>::deserialize(deserializer)? {
Some(title) => StreamTitleUpdate::Set(title),
None => StreamTitleUpdate::Clear,
})
}
fn deserialize_nullable_stream_title<'de, D>(
deserializer: D,
) -> Result<Option<StreamTitle>, D::Error>
where
D: Deserializer<'de>,
{
Option::<StreamTitle>::deserialize(deserializer)
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct StreamTailResponse {
pub stream_id: StreamId,
pub next_s2_seq_num: u64,
pub last_timestamp_ms: Option<u64>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct StreamRangeResponse {
pub stream_id: StreamId,
pub first_s2_seq_num: Option<u64>,
pub first_timestamp_ms: Option<u64>,
pub next_s2_seq_num: u64,
pub last_timestamp_ms: Option<u64>,
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::*;
#[test]
fn omits_absent_create_stream_options() {
let request = CreateStreamRequest::default();
assert_eq!(
serde_json::to_value(request).expect("serialize create request"),
json!({ "visibility": "private" })
);
}
#[test]
fn serializes_requested_stream_lifetime() {
let request = CreateStreamRequest {
expires_in_secs: Some(604_800),
..CreateStreamRequest::default()
};
let value = serde_json::to_value(request).expect("serialize create request");
assert_eq!(value["expires_in_secs"], json!(604_800));
assert_eq!(
serde_json::from_value::<CreateStreamRequest>(value)
.expect("deserialize create request")
.expires_in_secs,
Some(604_800)
);
}
#[test]
fn serializes_link_mutations_and_omits_absent_stream_update() {
let link = IssueLinkRequest {
label: "Reader".parse().expect("label"),
permissions: LinkPermissions::read(),
expires_at: None,
};
assert_eq!(
serde_json::to_value(link).expect("serialize link request"),
json!({ "label": "Reader", "permissions": "r" })
);
assert_eq!(
serde_json::to_value(RenameLinkRequest {
label: "Deploy bot".parse().expect("label"),
})
.expect("serialize link rename request"),
json!({ "label": "Deploy bot" })
);
assert_eq!(
serde_json::to_value(UpdateStreamRequest::default()).expect("serialize update request"),
json!({})
);
assert_eq!(
serde_json::to_value(UpdateStreamRequest {
title: StreamTitleUpdate::Clear,
..UpdateStreamRequest::default()
})
.expect("serialize title clear request"),
json!({ "title": null })
);
assert_eq!(
serde_json::from_value::<UpdateStreamRequest>(json!({ "title": "Deploy log" }))
.expect("deserialize title update")
.title,
StreamTitleUpdate::Set("Deploy log".parse().expect("title"))
);
assert_eq!(
serde_json::from_value::<UpdateStreamRequest>(json!({ "title": null }))
.expect("deserialize title clear")
.title,
StreamTitleUpdate::Clear
);
}
}