1use crate::Status;
4use indexmap::IndexMap;
5use serde::{de::Unexpected, Deserialize, Serialize, Serializer};
6use std::borrow::Cow;
7use std::str::FromStr;
8use thiserror::Error;
9use warg_crypto::hash::AnyHash;
10use warg_protocol::{
11 registry::{LogId, PackageName, RegistryLen},
12 PublishedProtoEnvelopeBody,
13};
14
15#[derive(Debug, Serialize, Deserialize)]
17#[serde(rename_all = "camelCase")]
18pub struct PublishedRecord {
19 #[serde(flatten)]
21 pub envelope: PublishedProtoEnvelopeBody,
22 pub fetch_token: String,
24}
25
26#[derive(Debug, Serialize, Deserialize)]
28#[serde(rename_all = "camelCase", deny_unknown_fields)]
29pub struct FetchLogsRequest<'a> {
30 pub log_length: RegistryLen,
32 #[serde(skip_serializing_if = "Option::is_none")]
34 pub limit: Option<u16>,
35 #[serde(skip_serializing_if = "Option::is_none")]
37 pub operator: Option<Cow<'a, str>>,
38 #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
40 pub packages: Cow<'a, IndexMap<LogId, Option<String>>>,
41}
42
43#[derive(Debug, Serialize, Deserialize)]
45#[serde(rename_all = "camelCase")]
46pub struct FetchLogsResponse {
47 #[serde(default)]
49 pub more: bool,
50 #[serde(default, skip_serializing_if = "Vec::is_empty")]
52 pub operator: Vec<PublishedRecord>,
53 #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
55 pub packages: IndexMap<LogId, Vec<PublishedRecord>>,
56 #[serde(default, skip_serializing_if = "Vec::is_empty")]
58 pub warnings: Vec<FetchWarning>,
59}
60
61#[derive(Debug, Serialize, Deserialize)]
63#[serde(rename_all = "camelCase")]
64pub struct FetchPackageNamesRequest<'a> {
65 pub packages: Cow<'a, Vec<LogId>>,
67}
68
69#[derive(Serialize, Deserialize)]
73#[serde(rename_all = "camelCase")]
74pub struct FetchPackageNamesResponse {
75 pub packages: IndexMap<LogId, Option<PackageName>>,
77}
78
79#[derive(Serialize, Deserialize, Debug)]
81pub struct FetchWarning {
82 pub message: String,
84}
85
86#[non_exhaustive]
88#[derive(Debug, Error)]
89pub enum FetchError {
90 #[error("checkpoint log length `{0}` was not found")]
92 CheckpointNotFound(RegistryLen),
93 #[error("log `{0}` was not found")]
95 LogNotFound(LogId),
96 #[error("fetch token `{0}` was not found")]
98 FetchTokenNotFound(String),
99 #[error("{message}")]
101 Message {
102 status: u16,
104 message: String,
106 },
107}
108
109impl FetchError {
110 pub fn status(&self) -> u16 {
112 match self {
113 Self::CheckpointNotFound(_) | Self::LogNotFound(_) | Self::FetchTokenNotFound(_) => 404,
114 Self::Message { status, .. } => *status,
115 }
116 }
117}
118
119#[derive(Serialize, Deserialize)]
120#[serde(rename_all = "camelCase")]
121enum EntityType {
122 LogLength,
123 Log,
124 FetchToken,
125}
126
127#[derive(Serialize, Deserialize)]
128#[serde(untagged, rename_all = "camelCase")]
129enum RawError<'a, T>
130where
131 T: Clone + ToOwned,
132 <T as ToOwned>::Owned: Serialize + for<'b> Deserialize<'b>,
133{
134 CheckpointNotFound {
135 status: Status<404>,
136 #[serde(rename = "type")]
137 ty: EntityType,
138 id: RegistryLen,
139 },
140 NotFound {
141 status: Status<404>,
142 #[serde(rename = "type")]
143 ty: EntityType,
144 id: Cow<'a, T>,
145 },
146 Message {
147 status: u16,
148 message: Cow<'a, T>,
149 },
150}
151
152impl Serialize for FetchError {
153 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
154 match self {
155 Self::CheckpointNotFound(log_length) => RawError::CheckpointNotFound::<RegistryLen> {
156 status: Status::<404>,
157 ty: EntityType::LogLength,
158 id: *log_length,
159 }
160 .serialize(serializer),
161 Self::LogNotFound(log_id) => RawError::NotFound {
162 status: Status::<404>,
163 ty: EntityType::Log,
164 id: Cow::Borrowed(log_id),
165 }
166 .serialize(serializer),
167 Self::FetchTokenNotFound(token) => RawError::NotFound {
168 status: Status::<404>,
169 ty: EntityType::FetchToken,
170 id: Cow::Borrowed(token),
171 }
172 .serialize(serializer),
173 Self::Message { status, message } => RawError::Message {
174 status: *status,
175 message: Cow::Borrowed(message),
176 }
177 .serialize(serializer),
178 }
179 }
180}
181
182impl<'de> Deserialize<'de> for FetchError {
183 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
184 where
185 D: serde::Deserializer<'de>,
186 {
187 match RawError::<String>::deserialize(deserializer)? {
188 RawError::CheckpointNotFound { id, .. } => Ok(Self::CheckpointNotFound(id)),
189 RawError::NotFound { status: _, ty, id } => match ty {
190 EntityType::Log => Ok(Self::LogNotFound(
191 AnyHash::from_str(&id)
192 .map_err(|_| {
193 serde::de::Error::invalid_value(Unexpected::Str(&id), &"a valid log id")
194 })?
195 .into(),
196 )),
197 EntityType::FetchToken => Ok(Self::FetchTokenNotFound(id.into_owned())),
198 _ => Err(serde::de::Error::invalid_value(
199 Unexpected::Str(&id),
200 &"a valid log length",
201 )),
202 },
203 RawError::Message { status, message } => Ok(Self::Message {
204 status,
205 message: message.into_owned(),
206 }),
207 }
208 }
209}