use std::path::PathBuf;
use hyper::HeaderMap;
use serde::de::{self, Unexpected};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::args::Args;
#[derive(Serialize, Deserialize, Clone)]
pub struct HttpConfig {
pub cert: Option<PathBuf>,
pub key: Option<PathBuf>,
#[serde(default = "default_listen")]
pub listen: bool,
#[serde(
default = "default_feeder_call_gas",
deserialize_with = "deserialize_feeder_call_gas",
serialize_with = "serialize_feeder_call_gas"
)]
pub feeder_call_gas: u64,
listen_address: Option<String>,
#[serde(default = "default_ws_sub_channel_cap")]
pub ws_sub_channel_cap: usize,
#[serde(default = "default_ws_event_channel_cap")]
pub ws_event_channel_cap: usize,
#[serde(with = "vec_header_map", default = "default_http_headers")]
pub headers: HeaderMap,
}
fn deserialize_feeder_call_gas<'de, D>(deserializer: D) -> Result<u64, D::Error>
where
D: Deserializer<'de>,
{
String::deserialize(deserializer)?
.parse::<u64>()
.map_err(|_| {
de::Error::invalid_value(
Unexpected::Str("a valid u64 as a string"),
&"a u64 integer",
)
})
}
fn serialize_feeder_call_gas<S>(
value: &u64,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&value.to_string())
}
impl Default for HttpConfig {
fn default() -> Self {
Self {
cert: None,
key: None,
headers: default_http_headers(),
listen: default_listen(),
feeder_call_gas: default_feeder_call_gas(),
listen_address: None,
ws_sub_channel_cap: default_ws_sub_channel_cap(),
ws_event_channel_cap: default_ws_event_channel_cap(),
}
}
}
const fn default_feeder_call_gas() -> u64 {
3 * 1_000_000_000
}
const fn default_listen() -> bool {
true
}
const fn default_ws_sub_channel_cap() -> usize {
16
}
const fn default_ws_event_channel_cap() -> usize {
1024
}
fn default_http_headers() -> HeaderMap {
HeaderMap::new()
}
impl HttpConfig {
pub fn listen_addr(&self) -> String {
self.listen_address
.clone()
.unwrap_or("127.0.0.1:8080".into())
}
pub(crate) fn merge(&mut self, args: &Args) {
if let Some(http_listen_addr) = &args.http_listen_addr {
self.listen_address = Some(http_listen_addr.into());
}
}
}
mod vec_header_map {
use super::*;
use std::fmt;
use serde::de::{Deserializer, Error as _, SeqAccess, Visitor};
use serde::ser::{Error as _, SerializeSeq, Serializer};
use hyper::header::{HeaderName, HeaderValue};
pub fn serialize<S>(
headers: &HeaderMap,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let tuple_vec_len = headers.len();
let mut tuple_vec = Vec::with_capacity(tuple_vec_len);
for (k, v) in headers {
let k_str = k.as_str();
let v_str = v.to_str().map_err(S::Error::custom)?;
tuple_vec.push((k_str, v_str));
}
let mut seq = serializer.serialize_seq(Some(tuple_vec_len))?;
for elem in tuple_vec {
seq.serialize_element(&elem)?;
}
seq.end()
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<HeaderMap, D::Error>
where
D: Deserializer<'de>,
{
struct TupleVecVisitor;
impl<'de> Visitor<'de> for TupleVecVisitor {
type Value = Vec<(String, String)>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a tuple header name and value")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let tuple_vec_len = seq.size_hint().unwrap_or_default();
let mut tuple_vec = Vec::with_capacity(tuple_vec_len);
while let Some(elem) = seq.next_element()? {
tuple_vec.push(elem);
}
Ok(tuple_vec)
}
}
let tuple_vec = deserializer.deserialize_seq(TupleVecVisitor)?;
let mut headers = HeaderMap::with_capacity(tuple_vec.len());
for (k, v) in tuple_vec {
let name = HeaderName::from_bytes(k.as_bytes())
.map_err(D::Error::custom)?;
let value = HeaderValue::from_str(&v).map_err(D::Error::custom)?;
headers.insert(name, value);
}
Ok(headers)
}
}
#[cfg(test)]
mod tests {
use super::*;
use hyper::http::HeaderValue;
#[test]
fn serialize_config() {
let mut config = HttpConfig::default();
config
.headers
.insert("name1", HeaderValue::from_str("value1").unwrap());
config
.headers
.insert("name2", HeaderValue::from_str("value2").unwrap());
let toml = toml::to_string(&config)
.expect("serializing configuration should succeed");
println!("{toml}");
}
#[test]
fn deserialize_config() {
let config_str = r#"listen = true
feeder_call_gas = "18446744"
ws_sub_channel_cap = 16
ws_event_channel_cap = 1024
headers = [["name1", "value1"], ["name2", "value2"]]"#;
toml::from_str::<HttpConfig>(config_str)
.expect("deserializing config should succeed");
}
#[test]
fn deserialize_invalid_feeder_call_gas() {
let config_str = r#"feeder_call_gas = "invalid_number""#;
let result = toml::from_str::<HttpConfig>(config_str);
assert!(result.is_err());
let config_str = r#"feeder_call_gas = 18446744"#;
let result = toml::from_str::<HttpConfig>(config_str);
assert!(result.is_err());
}
#[test]
fn deserialize_large_feeder_call_gas() {
let config_str = r#"feeder_call_gas = "9999999999999999999""#;
let config: HttpConfig = toml::from_str(config_str)
.expect("parsing large u64 value should succeed");
assert_eq!(config.feeder_call_gas, 9999999999999999999);
let config_str = r#"feeder_call_gas = "18446744073709551615""#; let config: HttpConfig = toml::from_str(config_str)
.expect("parsing u64::MAX should succeed");
assert_eq!(config.feeder_call_gas, u64::MAX);
}
}