extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate toml;
use std::fmt;
use serde::de;
use serde::de::{value, Deserialize, Deserializer, SeqAccess, Visitor};
use serde::ser::{Serialize, Serializer, SerializeSeq};
use std::collections::HashMap;
#[derive(Debug, Serialize, Deserialize, PartialEq, Default)]
pub struct Config {
pub build: Option<Context>,
pub context: Option<HashMap<String, Context>>,
pub redirects: Option<Vec<Redirect>>,
pub headers: Option<Vec<Header>>,
pub template: Option<Template>,
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Default)]
pub struct Context {
pub base: Option<String>,
pub publish: Option<String>,
pub command: Option<String>,
pub functions: Option<String>,
pub environment: Option<HashMap<String, String>>,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct Redirect {
pub from: String,
pub to: String,
#[serde(default = "default_status")]
pub status: i64,
#[serde(default)]
pub force: bool,
pub headers: Option<HashMap<String, String>>,
pub query: Option<HashMap<String, String>>,
pub conditions: Option<HashMap<String, Vec<String>>>,
pub signed: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Default)]
pub struct Header {
#[serde(rename="for")]
pub path: String,
#[serde(rename="values")]
pub headers: HashMap<String, HeaderValues>,
}
#[derive(Debug, PartialEq, Default)]
pub struct HeaderValues {
pub values: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Default)]
pub struct Template {
#[serde(rename="incoming-hooks")]
pub hooks: Option<Vec<String>>,
pub environment: Option<HashMap<String, String>>,
}
pub fn from_str(io: &str) -> Result<Config, toml::de::Error> {
toml::from_str::<Config>(io)
}
impl Config {
pub fn context_env(self, ctx: &str, branch: &str) -> HashMap<String, String> {
let mut result = HashMap::<String, String>::new();
if let Some(c) = self.build {
if let Some(ref env) = c.environment {
for (k, v) in env {
result.insert(k.to_string(), v.to_string());
}
}
}
if let Some(c) = self.context {
if let Some(x) = c.get(ctx) {
if let Some(ref env) = x.environment {
for (k, v) in env {
result.insert(k.to_string(), v.to_string());
}
}
}
if let Some(x) = c.get(branch) {
if let Some(ref env) = x.environment {
for (k, v) in env {
result.insert(k.to_string(), v.to_string());
}
}
}
}
result
}
}
impl<'de> Deserialize<'de> for HeaderValues
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct HeaderValuesVisitor;
impl<'de> Visitor<'de> for HeaderValuesVisitor {
type Value = HeaderValues;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("string or vector")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where E: de::Error,
{
if v.contains(",") {
let values = v.split(",").map(|s| String::from(s.trim())).collect();
return Ok(HeaderValues{ values: values })
}
Ok(HeaderValues{ values: vec![v.to_owned()] })
}
fn visit_seq<V>(self, v: V) -> Result<Self::Value, V::Error>
where V: SeqAccess<'de>,
{
let items = Deserialize::deserialize(value::SeqAccessDeserializer::new(v))?;
Ok(HeaderValues{ values: items })
}
}
deserializer.deserialize_any(HeaderValuesVisitor{})
}
}
impl Serialize for HeaderValues
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if self.values.len() > 1 {
let mut seq = serializer.serialize_seq(Some(self.values.len()))?;
for e in self.values.to_owned() {
seq.serialize_element(&e)?;
}
seq.end()
} else {
serializer.serialize_str(&self.values[0])
}
}
}
fn default_status() -> i64 {
301
}
impl Default for Redirect {
fn default() -> Redirect {
Redirect {
from: String::new(),
to: String::new(),
status: default_status(),
force: false,
signed: None,
conditions: None,
query: None,
headers: None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn partial_equal() {
let r = Redirect {
from: "/foo".to_string(),
to: "/bar".to_string(),
status: 301,
force: false,
headers: None,
query: None,
conditions: None,
signed: None,
};
let r2 = Redirect {
from: "/foo".to_string(),
to: "/bar".to_string(),
status: 301,
force: false,
headers: None,
query: None,
conditions: None,
signed: None,
};
assert_eq!(r, r2)
}
#[test]
fn default_redirect() {
let r = Redirect {from: "/foo".to_string(), to: "/bar".to_string(), ..Default::default()};
assert_eq!("/foo", r.from);
assert_eq!("/bar", r.to);
assert_eq!(301, r.status);
}
}