use crate::error::{Error, Result};
pub trait Operation {
type Output: serde::de::DeserializeOwned;
const METHOD: http::Method;
fn path(&self) -> String;
fn query(&self) -> Vec<(&'static str, String)> {
Vec::new()
}
fn headers(&self) -> Vec<(&'static str, String)> {
Vec::new()
}
fn body(&self) -> Result<Option<Vec<u8>>> {
Ok(None)
}
}
pub fn json_body<T: serde::Serialize>(value: &T) -> Result<Option<Vec<u8>>> {
serde_json::to_vec(value).map(Some).map_err(Error::encode)
}
pub fn push_opt<T: ToString>(
q: &mut Vec<(&'static str, String)>,
key: &'static str,
value: Option<T>,
) {
if let Some(v) = value {
q.push((key, v.to_string()));
}
}
pub fn encode_path_segment(segment: &str) -> String {
const HEX: &[u8; 16] = b"0123456789ABCDEF";
let mut encoded = String::new();
for b in segment.bytes() {
if b.is_ascii_alphanumeric() || matches!(b, b'-' | b'.' | b'_' | b'~') {
encoded.push(char::from(b));
} else {
encoded.push('%');
encoded.push(char::from(HEX[usize::from(b >> 4)]));
encoded.push(char::from(HEX[usize::from(b & 0x0F)]));
}
}
encoded
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::print_stdout,
clippy::unreadable_literal
)]
mod tests {
use super::*;
#[test]
fn encode_path_segment_leaves_unreserved_chars() {
assert_eq!(encode_path_segment("abc-123._~"), "abc-123._~");
}
#[test]
fn encode_path_segment_escapes_reserved_and_unicode_bytes() {
assert_eq!(
encode_path_segment("+1555/a b?#☃"),
"%2B1555%2Fa%20b%3F%23%E2%98%83"
);
}
}