apollo-configuration 0.4.0

A typed, friendly configuration system for Apollo products.
Documentation
use apollo_configuration::configuration;
use apollo_configuration::parse_yaml;
use apollo_configuration::types::{HeaderName, HeaderValue, Method, Uri};
use serde::{Deserialize, Serialize};

#[configuration]
struct HttpConfig {
    #[config(required)]
    method: Method,
    #[config(required)]
    uri: Uri,
    #[config(required)]
    header_name: HeaderName,
    #[config(required)]
    header_value: HeaderValue,
}

#[test]
fn http_types_parse() {
    let config: HttpConfig = parse_yaml(
        r#"
        method: POST
        uri: /api/v1/data
        header_name: content-type
        header_value: application/json
        "#,
        &Default::default(),
    )
    .unwrap();

    assert_eq!(*config.method, http::Method::POST);
    assert_eq!(config.uri.path(), "/api/v1/data");
    assert_eq!(config.header_name.as_str(), "content-type");
    assert_eq!(config.header_value.to_str().unwrap(), "application/json");
}

#[test]
fn method_error_message() {
    #[configuration]
    struct MethodOnly {
        #[config(required)]
        method: Method,
    }

    let result = parse_yaml::<MethodOnly>("method: \"BAD METHOD\"", &Default::default());
    let err = result.expect_err("should fail to parse invalid method");
    assert_eq!(
        format!("{err}"),
        "failed to parse value: invalid HTTP method"
    );
}

#[test]
fn uri_error_message() {
    #[configuration]
    struct UriOnly {
        #[config(required)]
        uri: Uri,
    }

    let result = parse_yaml::<UriOnly>("uri: \"bad\\x00uri\"", &Default::default());
    let err = result.expect_err("should fail to parse invalid URI");
    assert_eq!(
        format!("{err}"),
        "failed to parse value: invalid uri character"
    );
}

#[test]
fn header_name_error_message() {
    #[configuration]
    struct HeaderNameOnly {
        #[config(required)]
        name: HeaderName,
    }

    let result = parse_yaml::<HeaderNameOnly>("name: \"invalid header\"", &Default::default());
    let err = result.expect_err("should fail to parse invalid header name");
    assert_eq!(
        format!("{err}"),
        "failed to parse value: invalid HTTP header name"
    );
}

#[test]
fn header_value_error_message() {
    #[configuration]
    struct HeaderValueOnly {
        #[config(required)]
        value: HeaderValue,
    }

    let result = parse_yaml::<HeaderValueOnly>("value: \"invalid\\x00value\"", &Default::default());
    let err = result.expect_err("should fail to parse invalid header value");
    assert_eq!(
        format!("{err}"),
        "failed to parse value: failed to parse header value"
    );
}

#[test]
fn header_name_partial_eq_str() {
    let name: HeaderName = serde_yaml::from_str("content-type").unwrap();
    assert!(name == *"content-type");
    assert!(name != *"accept");
}

#[test]
fn method_partial_eq_str() {
    let method: Method = serde_yaml::from_str("POST").unwrap();
    assert!(method == *"POST");
    assert!(method != *"GET");
}

#[derive(Debug, Deserialize, Serialize)]
struct SerializableHttpConfig {
    method: Method,
    uri: Uri,
    header_name: HeaderName,
    header_value: HeaderValue,
}

#[test]
fn http_types_serialize() {
    let config: SerializableHttpConfig = serde_yaml::from_str(
        r#"
        method: POST
        uri: /api/v1/data
        header_name: content-type
        header_value: application/json
        "#,
    )
    .unwrap();
    let yaml = serde_yaml::to_string(&config).unwrap();
    insta::assert_snapshot!(yaml, @r"
    method: POST
    uri: /api/v1/data
    header_name: content-type
    header_value: application/json
    ");
}