use crate::Status;
use indexmap::IndexMap;
use serde::{de::Unexpected, Deserialize, Serialize, Serializer};
use std::borrow::Cow;
use std::str::FromStr;
use thiserror::Error;
use warg_crypto::hash::AnyHash;
use warg_protocol::{
registry::{LogId, PackageName, RegistryLen},
PublishedProtoEnvelopeBody,
};
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PublishedRecord {
#[serde(flatten)]
pub envelope: PublishedProtoEnvelopeBody,
pub fetch_token: String,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct FetchLogsRequest<'a> {
pub log_length: RegistryLen,
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
pub operator: Option<Cow<'a, str>>,
#[serde(default, skip_serializing_if = "IndexMap::is_empty")]
pub packages: Cow<'a, IndexMap<LogId, Option<String>>>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FetchLogsResponse {
#[serde(default)]
pub more: bool,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub operator: Vec<PublishedRecord>,
#[serde(default, skip_serializing_if = "IndexMap::is_empty")]
pub packages: IndexMap<LogId, Vec<PublishedRecord>>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub warnings: Vec<FetchWarning>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FetchPackageNamesRequest<'a> {
pub packages: Cow<'a, Vec<LogId>>,
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FetchPackageNamesResponse {
pub packages: IndexMap<LogId, Option<PackageName>>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct FetchWarning {
pub message: String,
}
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum FetchError {
#[error("checkpoint log length `{0}` was not found")]
CheckpointNotFound(RegistryLen),
#[error("log `{0}` was not found")]
LogNotFound(LogId),
#[error("fetch token `{0}` was not found")]
FetchTokenNotFound(String),
#[error("{message}")]
Message {
status: u16,
message: String,
},
}
impl FetchError {
pub fn status(&self) -> u16 {
match self {
Self::CheckpointNotFound(_) | Self::LogNotFound(_) | Self::FetchTokenNotFound(_) => 404,
Self::Message { status, .. } => *status,
}
}
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
enum EntityType {
LogLength,
Log,
FetchToken,
}
#[derive(Serialize, Deserialize)]
#[serde(untagged, rename_all = "camelCase")]
enum RawError<'a, T>
where
T: Clone + ToOwned,
<T as ToOwned>::Owned: Serialize + for<'b> Deserialize<'b>,
{
CheckpointNotFound {
status: Status<404>,
#[serde(rename = "type")]
ty: EntityType,
id: RegistryLen,
},
NotFound {
status: Status<404>,
#[serde(rename = "type")]
ty: EntityType,
id: Cow<'a, T>,
},
Message {
status: u16,
message: Cow<'a, T>,
},
}
impl Serialize for FetchError {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match self {
Self::CheckpointNotFound(log_length) => RawError::CheckpointNotFound::<RegistryLen> {
status: Status::<404>,
ty: EntityType::LogLength,
id: *log_length,
}
.serialize(serializer),
Self::LogNotFound(log_id) => RawError::NotFound {
status: Status::<404>,
ty: EntityType::Log,
id: Cow::Borrowed(log_id),
}
.serialize(serializer),
Self::FetchTokenNotFound(token) => RawError::NotFound {
status: Status::<404>,
ty: EntityType::FetchToken,
id: Cow::Borrowed(token),
}
.serialize(serializer),
Self::Message { status, message } => RawError::Message {
status: *status,
message: Cow::Borrowed(message),
}
.serialize(serializer),
}
}
}
impl<'de> Deserialize<'de> for FetchError {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
match RawError::<String>::deserialize(deserializer)? {
RawError::CheckpointNotFound { id, .. } => Ok(Self::CheckpointNotFound(id)),
RawError::NotFound { status: _, ty, id } => match ty {
EntityType::Log => Ok(Self::LogNotFound(
AnyHash::from_str(&id)
.map_err(|_| {
serde::de::Error::invalid_value(Unexpected::Str(&id), &"a valid log id")
})?
.into(),
)),
EntityType::FetchToken => Ok(Self::FetchTokenNotFound(id.into_owned())),
_ => Err(serde::de::Error::invalid_value(
Unexpected::Str(&id),
&"a valid log length",
)),
},
RawError::Message { status, message } => Ok(Self::Message {
status,
message: message.into_owned(),
}),
}
}
}