1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
use crate::fields::{OutputFormat, ResizeStrategy};
use base64::encode_config;
use custom_error::custom_error;
use serde::{Deserialize, Serialize};

/// The default URL-safe base64 configuration.
fn b64_config() -> base64::Config {
    base64::Config::new(base64::CharacterSet::UrlSafe, false)
}

custom_error! {pub FingerprintError
    JsonError{source: serde_json::Error} = "Something went wrong when (de)serializing JSON.",
    Base64Error{source: base64::DecodeError} = "Something went wrong when decoding Base64.",
    UnicodeError{source: std::str::Utf8Error} = "Could not convert byte array to string!"
}

/// The default object that clients use to make requests to Media Proxy.
#[derive(Serialize, Deserialize)]
pub struct Query {
    /// URL of the source image. Input formats currently supported are the same as those of the [image] crate.
    pub source: String,

    /// The width of the processed image.
    pub width: Option<u32>,

    /// The height of the processed image.
    pub height: Option<u32>,

    /// Output format of the image.
    pub format: OutputFormat,

    /// Strategy for resizing image.
    #[serde(rename = "fit")]
    pub fit_mode: Option<ResizeStrategy>,
}

impl Query {
    /// Convert a `Query` to a fingerprint.
    ///
    /// In reality, this just serializes the `Query` to JSON and encodes it in base64.
    pub fn to_fingerprint(self: &Self) -> String {
        let json = serde_json::to_string(&self).unwrap();
        encode_config(json, b64_config())
    }

    /// Create a `Query` from a base64 encoded fingerprint. Fingerprints can be created with `Query.to_fingerprint()`.
    ///
    /// # Arguments
    ///
    /// * `fingerprint` - A base64 encoded `Query` in JSON.
    ///
    /// # Example
    ///
    /// ```
    /// use mediaproxy_common::query::Query;
    /// let query = Query::from_fingerprint("eyJzb3VyY2UiOiJodHRwczovL2R1bW15aW1hZ2UuY29tLzYwMHg0MDAvMDAwL2ZmZiIsIndpZHRoIjpudWxsLCJoZWlnaHQiOm51bGwsImZvcm1hdCI6ImpwZWcifQ".to_string());
    /// ```
    pub fn from_fingerprint(fingerprint: String) -> Result<Query, FingerprintError> {
        let bytes = base64::decode_config(fingerprint, b64_config())?;
        let json = std::str::from_utf8(&bytes)?;
        let query: Query = serde_json::from_str(json)?;
        Ok(query)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn query_to_fingerprint() {
        let query = Query {
            source: String::from("https://dummyimage.com/600x400/000/fff"),
            format: OutputFormat::Jpeg,
            width: None,
            height: None,
            fit_mode: Some(ResizeStrategy::default()),
        };

        assert_eq!(query.to_fingerprint(), String::from("eyJzb3VyY2UiOiJodHRwczovL2R1bW15aW1hZ2UuY29tLzYwMHg0MDAvMDAwL2ZmZiIsIndpZHRoIjpudWxsLCJoZWlnaHQiOm51bGwsImZvcm1hdCI6ImpwZWciLCJmaXQiOiJjcm9wIn0"));
    }

    #[test]
    fn fingerprint_to_query() {
        let fingerprint = String::from("eyJzb3VyY2UiOiJodHRwczovL2R1bW15aW1hZ2UuY29tLzYwMHg0MDAvMDAwL2ZmZiIsIndpZHRoIjpudWxsLCJoZWlnaHQiOm51bGwsImZvcm1hdCI6ImpwZWcifQ");
        let query = Query::from_fingerprint(fingerprint).unwrap();
        assert_eq!(query.source, "https://dummyimage.com/600x400/000/fff");
    }

    #[test]
    fn invalid_fingerprint() {
        let fingerprint = String::from("bruh"); // Perfectly fine base 64, not so fine JSON.
        let query = Query::from_fingerprint(fingerprint);
        assert_eq!(query.is_err(), true);
    }
}