warg_api/v1/
content.rs

1//! Types relating to the content API.
2
3pub 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;
11
12/// Represents a response for content digest.
13#[derive(Serialize, Deserialize)]
14#[serde(rename_all = "camelCase")]
15pub struct ContentSourcesResponse {
16    /// The content sources for the requested content digest, as well as additional
17    /// content digests imported by the requested content digest.
18    pub content_sources: IndexMap<AnyHash, Vec<ContentSource>>,
19}
20
21/// Represents a content API error.
22#[non_exhaustive]
23#[derive(Debug, Error)]
24pub enum ContentError {
25    /// The provided content digest was not found.
26    #[error("content digest `{0}` was not found")]
27    ContentDigestNotFound(AnyHash),
28    /// An error with a message occurred.
29    #[error("{message}")]
30    Message {
31        /// The HTTP status code.
32        status: u16,
33        /// The error message
34        message: String,
35    },
36}
37
38impl ContentError {
39    /// Returns the HTTP status code of the error.
40    pub fn status(&self) -> u16 {
41        match self {
42            Self::ContentDigestNotFound(_) => 404,
43            Self::Message { status, .. } => *status,
44        }
45    }
46}
47
48#[derive(Serialize, Deserialize)]
49#[serde(rename_all = "camelCase")]
50enum EntityType {
51    ContentDigest,
52}
53
54#[derive(Serialize, Deserialize)]
55#[serde(untagged, rename_all = "camelCase")]
56enum RawError<'a, T>
57where
58    T: Clone + ToOwned,
59    <T as ToOwned>::Owned: Serialize + for<'b> Deserialize<'b>,
60{
61    NotFound {
62        status: Status<404>,
63        #[serde(rename = "type")]
64        ty: EntityType,
65        id: Cow<'a, T>,
66    },
67    Message {
68        status: u16,
69        message: Cow<'a, str>,
70    },
71}
72
73impl Serialize for ContentError {
74    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
75        match self {
76            Self::ContentDigestNotFound(digest) => RawError::NotFound {
77                status: Status::<404>,
78                ty: EntityType::ContentDigest,
79                id: Cow::Borrowed(digest),
80            }
81            .serialize(serializer),
82            Self::Message { status, message } => RawError::Message::<()> {
83                status: *status,
84                message: Cow::Borrowed(message),
85            }
86            .serialize(serializer),
87        }
88    }
89}
90
91impl<'de> Deserialize<'de> for ContentError {
92    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
93    where
94        D: serde::Deserializer<'de>,
95    {
96        match RawError::<String>::deserialize(deserializer)? {
97            RawError::NotFound { status: _, ty, id } => match ty {
98                EntityType::ContentDigest => Ok(Self::ContentDigestNotFound(
99                    AnyHash::from_str(&id).map_err(|_| {
100                        serde::de::Error::invalid_value(Unexpected::Str(&id), &"a valid digest")
101                    })?,
102                )),
103            },
104            RawError::Message { status, message } => Ok(Self::Message {
105                status,
106                message: message.into_owned(),
107            }),
108        }
109    }
110}