use std::collections::HashMap;
use std::str::FromStr;
use surrealdb_types::Error as TypesError;
use crate::rpc::{Method, invalid_request};
use crate::types::{PublicArray, PublicNumber, PublicObject, PublicUuid, PublicValue};
pub static ID: &str = "id";
pub static METHOD: &str = "method";
pub static PARAMS: &str = "params";
pub static VERSION: &str = "version";
pub static TXN: &str = "txn";
pub static SESSION_ID: &str = "session";
pub static TRACE_CONTEXT: &str = "trace_context";
#[derive(Debug)]
pub struct Request {
pub id: Option<PublicValue>,
pub version: Option<u8>,
pub session_id: Option<PublicUuid>,
pub txn: Option<PublicUuid>,
pub method: Method,
pub params: PublicArray,
pub trace_context: Option<HashMap<String, String>>,
}
impl Request {
pub fn from_object(mut obj: PublicObject) -> Result<Self, TypesError> {
let id = obj.remove("id");
let id = match id {
None | Some(PublicValue::None) => None,
Some(
PublicValue::Null
| PublicValue::Uuid(_)
| PublicValue::Number(_)
| PublicValue::String(_)
| PublicValue::Datetime(_),
) => id,
_ => return Err(invalid_request()),
};
let version = match obj.remove(VERSION) {
None | Some(PublicValue::None | PublicValue::Null) => None,
Some(PublicValue::Number(v)) => match v {
PublicNumber::Int(1) => Some(1),
PublicNumber::Int(2) => Some(2),
_ => return Err(invalid_request()),
},
_ => return Err(invalid_request()),
};
let session_id = match obj.remove(SESSION_ID) {
None | Some(PublicValue::None | PublicValue::Null) => None,
Some(PublicValue::Uuid(x)) => Some(x),
Some(PublicValue::String(x)) => {
Some(PublicUuid::from_str(x.as_str()).map_err(|_| invalid_request())?)
}
_ => return Err(invalid_request()),
};
let txn = match obj.remove(TXN) {
None | Some(PublicValue::None | PublicValue::Null) => None,
Some(PublicValue::Uuid(x)) => Some(x),
Some(PublicValue::String(x)) => {
Some(PublicUuid::from_str(x.as_str()).map_err(|_| invalid_request())?)
}
_ => return Err(invalid_request()),
};
let method = match obj.remove(METHOD) {
Some(PublicValue::String(v)) => v,
_ => return Err(invalid_request()),
};
let params = match obj.remove(PARAMS) {
Some(PublicValue::Array(v)) => v,
_ => PublicArray::new(),
};
let trace_context = match obj.remove(TRACE_CONTEXT) {
None | Some(PublicValue::None | PublicValue::Null) => None,
Some(PublicValue::String(v)) => {
let mut map = HashMap::with_capacity(1);
map.insert("traceparent".to_string(), v);
Some(map)
}
Some(PublicValue::Object(o)) => {
let map: HashMap<String, String> = o
.into_inner()
.into_iter()
.filter_map(|(k, v)| match v {
PublicValue::String(s) => Some((k, s)),
_ => None,
})
.collect();
if map.is_empty() {
None
} else {
Some(map)
}
}
_ => return Err(invalid_request()),
};
let method = Method::parse_case_sensitive(method);
Ok(Request {
id,
method,
params,
version,
txn,
session_id,
trace_context,
})
}
}
#[cfg(test)]
mod tests {
use surrealdb_types::object;
use super::*;
fn ping_with_trace_context(trace_context: PublicValue) -> PublicObject {
object! {
id: 1,
method: "ping",
trace_context: trace_context,
}
}
#[test]
fn trace_context_absent_is_none() {
let obj = object! { id: 1, method: "ping" };
let req = Request::from_object(obj).unwrap();
assert!(req.trace_context.is_none());
}
#[test]
fn trace_context_null_is_none() {
let req = Request::from_object(ping_with_trace_context(PublicValue::Null)).unwrap();
assert!(req.trace_context.is_none());
}
#[test]
fn trace_context_none_is_none() {
let req = Request::from_object(ping_with_trace_context(PublicValue::None)).unwrap();
assert!(req.trace_context.is_none());
}
#[test]
fn trace_context_string_becomes_traceparent() {
let traceparent = "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01";
let req = Request::from_object(ping_with_trace_context(PublicValue::String(
traceparent.to_string(),
)))
.unwrap();
let trace_context = req.trace_context.expect("trace_context populated");
assert_eq!(trace_context.len(), 1);
assert_eq!(trace_context.get("traceparent").map(String::as_str), Some(traceparent));
}
#[test]
fn trace_context_object_keeps_string_entries() {
let obj = object! {
id: 1,
method: "ping",
trace_context: object! {
traceparent: "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01",
tracestate: "vendor=foo",
},
};
let req = Request::from_object(obj).unwrap();
let trace_context = req.trace_context.expect("trace_context populated");
assert_eq!(trace_context.len(), 2);
assert_eq!(
trace_context.get("traceparent").map(String::as_str),
Some("00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"),
);
assert_eq!(trace_context.get("tracestate").map(String::as_str), Some("vendor=foo"));
}
#[test]
fn trace_context_object_drops_non_string_entries() {
let obj = object! {
id: 1,
method: "ping",
trace_context: object! {
traceparent: "00-aaaa-bbbb-01",
answer: 42_i64,
},
};
let req = Request::from_object(obj).unwrap();
let trace_context = req.trace_context.expect("trace_context populated");
assert_eq!(trace_context.len(), 1);
assert!(trace_context.contains_key("traceparent"));
}
#[test]
fn trace_context_object_with_only_non_string_entries_is_none() {
let obj = object! {
id: 1,
method: "ping",
trace_context: object! { answer: 42_i64 },
};
let req = Request::from_object(obj).unwrap();
assert!(req.trace_context.is_none());
}
#[test]
fn trace_context_with_unsupported_kind_is_invalid_request() {
let obj = object! {
id: 1,
method: "ping",
trace_context: 42_i64,
};
assert!(Request::from_object(obj).is_err());
}
}