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
use async_nats::HeaderMap;

use crate::model::Manifest;

/// The name of the header in the NATS request to use for content type inference. The header value
/// should be a valid MIME type
pub const CONTENT_TYPE_HEADER: &str = "Content-Type";

// NOTE(thomastaylor312): If we do _anything_ else with mime types in the server, we should just
// pull in the `mime` crate instead
const YAML_MIME: &str = "application/yaml";
const JSON_MIME: &str = "application/json";

/// Parse the incoming bytes to a manifest
///
/// This function takes the optional headers from a NATS request to use them as a type hint for
/// parsing
pub fn parse_manifest(data: Vec<u8>, headers: Option<&HeaderMap>) -> anyhow::Result<Manifest> {
    // There is far too much cloning here, but there is no way to just return a reference to a &str
    let content_type = headers
        .and_then(|map| map.get(CONTENT_TYPE_HEADER).cloned())
        .map(|value| value.as_str().to_owned());
    if let Some(content_type) = content_type {
        match content_type.as_str() {
            JSON_MIME => serde_json::from_slice(&data).map_err(anyhow::Error::from),
            YAML_MIME => serde_yaml::from_slice(&data).map_err(anyhow::Error::from),
            _ => {
                // If the user passed a non-supported mime type, we should let them know rather than
                // just falling back
                Err(anyhow::anyhow!(
                    "Unsupported content type {content_type} given. Wadm supports YAML and JSON"
                ))
            }
        }
    } else {
        parse_yaml_or_json(data)
    }
}

/// Parse the bytes as yaml or json (in that order)
fn parse_yaml_or_json(data: Vec<u8>) -> anyhow::Result<Manifest> {
    serde_yaml::from_slice(&data).or_else(|e| {
        serde_json::from_slice(&data).map_err(|err| {
            // Combine both errors in case one was a legit parsing failure due to invalid data
            anyhow::anyhow!("JSON parsing failed: {err:?}")
                .context(format!("YAML parsing failed: {e:?}"))
        })
    })
}