1pub use super::ContentSource;
4use crate::Status;
5use indexmap::IndexMap;
6use serde::{de::Unexpected, Deserialize, Serialize, Serializer};
7use std::borrow::Cow;
8use std::str::FromStr;
9use thiserror::Error;
10use warg_crypto::hash::AnyHash;
11use warg_protocol::{
12 registry::{LogId, PackageName, RecordId, RegistryIndex},
13 ProtoEnvelopeBody,
14};
15
16#[derive(Clone, Debug, Serialize, Deserialize)]
18#[serde(tag = "type", rename_all = "camelCase")]
19pub enum UploadEndpoint {
20 #[serde(rename_all = "camelCase")]
22 Http {
23 method: String,
26 url: String,
28 #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
31 headers: IndexMap<String, String>,
32 },
33}
34
35#[derive(Clone, Debug, Serialize, Deserialize)]
37pub struct MissingContent {
38 pub upload: Vec<UploadEndpoint>,
40}
41
42#[derive(Serialize, Deserialize)]
44#[serde(rename_all = "camelCase")]
45pub struct PublishRecordRequest<'a> {
46 pub package_name: Cow<'a, PackageName>,
48 pub record: Cow<'a, ProtoEnvelopeBody>,
50 #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
54 pub content_sources: IndexMap<AnyHash, Vec<ContentSource>>,
55}
56
57#[derive(Serialize, Deserialize)]
59#[serde(rename_all = "camelCase")]
60pub struct PackageRecord {
61 pub record_id: RecordId,
63 #[serde(flatten)]
65 pub state: PackageRecordState,
66}
67
68impl PackageRecord {
69 pub fn missing_content(&self) -> impl Iterator<Item = (&AnyHash, &MissingContent)> {
71 match &self.state {
72 PackageRecordState::Sourcing {
73 missing_content, ..
74 } => itertools::Either::Left(missing_content.iter()),
75 _ => itertools::Either::Right(std::iter::empty()),
76 }
77 }
78}
79
80#[derive(Serialize, Deserialize)]
86#[serde(tag = "state", rename_all = "camelCase")]
87#[allow(clippy::large_enum_variant)]
88pub enum PackageRecordState {
89 #[serde(rename_all = "camelCase")]
91 Sourcing {
92 missing_content: IndexMap<AnyHash, MissingContent>,
94 },
95 #[serde(rename_all = "camelCase")]
97 Processing,
98 #[serde(rename_all = "camelCase")]
100 Rejected {
101 reason: String,
103 },
104 #[serde(rename_all = "camelCase")]
106 Published {
107 registry_index: RegistryIndex,
109 },
110}
111
112#[non_exhaustive]
114#[derive(Debug, Error)]
115pub enum PackageError {
116 #[error("log `{0}` was not found")]
118 LogNotFound(LogId),
119 #[error("record `{0}` was not found")]
121 RecordNotFound(RecordId),
122 #[error("the record is not currently sourcing content")]
124 RecordNotSourcing,
125 #[error("namespace `{0}` is not defined on the registry")]
127 NamespaceNotDefined(String),
128 #[error("namespace `{0}` is an imported namespace from another registry")]
130 NamespaceImported(String),
131 #[error("unauthorized operation: {0}")]
133 Unauthorized(String),
134 #[error("the requested operation is not supported: {0}")]
136 NotSupported(String),
137 #[error("the package conflicts with pending publish of record `{0}`")]
139 ConflictPendingPublish(RecordId),
140 #[error("the package was rejected by the registry: {0}")]
142 Rejection(String),
143 #[error("{message}")]
145 Message {
146 status: u16,
148 message: String,
150 },
151}
152
153impl PackageError {
154 pub fn status(&self) -> u16 {
156 match self {
157 Self::Unauthorized { .. } => 401,
158 Self::LogNotFound(_) | Self::RecordNotFound(_) | Self::NamespaceNotDefined(_) => 404,
159 Self::NamespaceImported(_) | Self::ConflictPendingPublish(_) => 409,
160 Self::RecordNotSourcing => 405,
161 Self::Rejection(_) => 422,
162 Self::NotSupported(_) => 501,
163 Self::Message { status, .. } => *status,
164 }
165 }
166}
167
168#[derive(Serialize, Deserialize)]
169#[serde(rename_all = "camelCase")]
170enum EntityType {
171 Log,
172 Record,
173 Namespace,
174 NamespaceImport,
175 Name,
176}
177
178#[derive(Serialize, Deserialize)]
179#[serde(untagged, rename_all = "camelCase")]
180enum RawError<'a, T>
181where
182 T: Clone + ToOwned,
183 <T as ToOwned>::Owned: Serialize + for<'b> Deserialize<'b>,
184{
185 Unauthorized {
186 status: Status<401>,
187 message: Cow<'a, str>,
188 },
189 NotFound {
190 status: Status<404>,
191 #[serde(rename = "type")]
192 ty: EntityType,
193 id: Cow<'a, T>,
194 },
195 Conflict {
196 status: Status<409>,
197 #[serde(rename = "type")]
198 ty: EntityType,
199 id: Cow<'a, T>,
200 },
201 RecordNotSourcing {
202 status: Status<405>,
203 },
204 Rejection {
205 status: Status<422>,
206 message: Cow<'a, str>,
207 },
208 NotSupported {
209 status: Status<501>,
210 message: Cow<'a, str>,
211 },
212 Message {
213 status: u16,
214 message: Cow<'a, str>,
215 },
216}
217
218impl Serialize for PackageError {
219 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
220 match self {
221 Self::Unauthorized(message) => RawError::Unauthorized::<()> {
222 status: Status::<401>,
223 message: Cow::Borrowed(message),
224 }
225 .serialize(serializer),
226 Self::LogNotFound(log_id) => RawError::NotFound {
227 status: Status::<404>,
228 ty: EntityType::Log,
229 id: Cow::Borrowed(log_id),
230 }
231 .serialize(serializer),
232 Self::RecordNotFound(record_id) => RawError::NotFound {
233 status: Status::<404>,
234 ty: EntityType::Record,
235 id: Cow::Borrowed(record_id),
236 }
237 .serialize(serializer),
238 Self::NamespaceNotDefined(namespace) => RawError::NotFound {
239 status: Status::<404>,
240 ty: EntityType::Namespace,
241 id: Cow::Borrowed(namespace),
242 }
243 .serialize(serializer),
244 Self::NamespaceImported(namespace) => RawError::Conflict {
245 status: Status::<409>,
246 ty: EntityType::NamespaceImport,
247 id: Cow::Borrowed(namespace),
248 }
249 .serialize(serializer),
250 Self::ConflictPendingPublish(record_id) => RawError::Conflict {
251 status: Status::<409>,
252 ty: EntityType::Record,
253 id: Cow::Borrowed(record_id),
254 }
255 .serialize(serializer),
256 Self::RecordNotSourcing => RawError::RecordNotSourcing::<()> {
257 status: Status::<405>,
258 }
259 .serialize(serializer),
260 Self::Rejection(message) => RawError::Rejection::<()> {
261 status: Status::<422>,
262 message: Cow::Borrowed(message),
263 }
264 .serialize(serializer),
265 Self::NotSupported(message) => RawError::NotSupported::<()> {
266 status: Status::<501>,
267 message: Cow::Borrowed(message),
268 }
269 .serialize(serializer),
270 Self::Message { status, message } => RawError::Message::<()> {
271 status: *status,
272 message: Cow::Borrowed(message),
273 }
274 .serialize(serializer),
275 }
276 }
277}
278
279impl<'de> Deserialize<'de> for PackageError {
280 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
281 where
282 D: serde::Deserializer<'de>,
283 {
284 match RawError::<String>::deserialize(deserializer)? {
285 RawError::Unauthorized { status: _, message } => {
286 Ok(Self::Unauthorized(message.into_owned()))
287 }
288 RawError::NotFound { status: _, ty, id } => match ty {
289 EntityType::Log => Ok(Self::LogNotFound(
290 AnyHash::from_str(&id)
291 .map_err(|_| {
292 serde::de::Error::invalid_value(Unexpected::Str(&id), &"a valid log id")
293 })?
294 .into(),
295 )),
296 EntityType::Record => Ok(Self::RecordNotFound(
297 AnyHash::from_str(&id)
298 .map_err(|_| {
299 serde::de::Error::invalid_value(
300 Unexpected::Str(&id),
301 &"a valid record id",
302 )
303 })?
304 .into(),
305 )),
306 EntityType::Namespace => Ok(Self::NamespaceNotDefined(id.into_owned())),
307 _ => Err(serde::de::Error::invalid_value(
308 Unexpected::Enum,
309 &"a valid entity type",
310 )),
311 },
312 RawError::Conflict { status: _, ty, id } => match ty {
313 EntityType::NamespaceImport => Ok(Self::NamespaceImported(id.into_owned())),
314 EntityType::Record => Ok(Self::ConflictPendingPublish(
315 AnyHash::from_str(&id)
316 .map_err(|_| {
317 serde::de::Error::invalid_value(
318 Unexpected::Str(&id),
319 &"a valid record id",
320 )
321 })?
322 .into(),
323 )),
324 _ => Err(serde::de::Error::invalid_value(
325 Unexpected::Enum,
326 &"a valid entity type",
327 )),
328 },
329 RawError::RecordNotSourcing { status: _ } => Ok(Self::RecordNotSourcing),
330 RawError::Rejection { status: _, message } => Ok(Self::Rejection(message.into_owned())),
331 RawError::NotSupported { status: _, message } => {
332 Ok(Self::NotSupported(message.into_owned()))
333 }
334 RawError::Message { status, message } => Ok(Self::Message {
335 status,
336 message: message.into_owned(),
337 }),
338 }
339 }
340}