use crate::{
cut::Register,
error::FrError,
response::Response,
utils::{ordered_set, ordered_str_map},
};
use serde::{Deserialize, Serialize};
use serde_json::{error::Error as SerdeError, json, to_value, Value};
use std::{
borrow::Cow,
collections::{HashMap, HashSet},
convert::TryFrom,
path::PathBuf,
};
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub struct Frame<'a> {
pub protocol: Protocol,
#[serde(default, skip_serializing_if = "InstructionSet::is_empty")]
pub cut: InstructionSet<'a>, pub(crate) request: Request,
pub response: Response<'a>,
}
const MISSING_VAR_ERR: &str = "Variable is not present in InstructionSet";
const DUPE_VAR_REFERENCE_ERR: &str =
"Cut Variables cannot be referenced by both read and write instructions";
const DUPE_KEY_UPON_HYDRATION_ERR: &str = "Hydrated key produced a duplicate key value";
const INVALID_KEY_HYDRATION_ERR: &str =
"Key attempted to be hydrated with a non-string cut variable";
impl<'a> Frame<'a> {
pub fn new(json_string: &str) -> Result<Self, FrError> {
let frame: Self = serde_json::from_str(json_string)?;
frame.cut.validate()?;
frame.response.validate()?;
Ok(frame)
}
pub fn to_value(&self) -> Value {
to_value(self).expect("serialization error")
}
pub fn get_request(&self) -> Request {
self.request.clone()
}
pub fn get_request_uri(&self) -> Result<String, FrError> {
let unst = serde_json::to_string(&self.request.uri)?;
Ok(unst.replace("\"", ""))
}
pub fn get_response_value(&self) -> Result<Value, SerdeError> {
to_value(&self.response.body)
}
pub fn hydrate(&mut self, reg: &Register, hide: bool) -> Result<(), FrError> {
let set = self.cut.clone();
if let Some(request_body) = &mut self.request.body {
Self::hydrate_val(&set, request_body, reg, hide)?;
}
if let Some(response_body) = &mut self.response.body {
Self::hydrate_val(&set, response_body, reg, hide)?;
}
if let Some(header) = &mut self.request.header {
Self::hydrate_val(&set, header, reg, hide)?;
}
if let Some(etc) = &mut self.request.etc {
Self::hydrate_val(&set, etc, reg, hide)?;
}
Self::hydrate_str(&set, &mut self.request.uri, reg, hide)?;
if let Some(entrypoint) = &mut self.request.entrypoint {
Self::hydrate_str(&set, entrypoint, reg, hide)?;
}
Ok(())
}
pub fn hydrate_val(
set: &InstructionSet,
val: &mut Value,
reg: &Register,
hide: bool,
) -> Result<(), FrError> {
match val {
Value::Object(map) => {
let keys: HashSet<String> = map.keys().cloned().collect();
let mut replace_keys: Vec<(&String, String)> = Vec::new(); for (key, val) in map.iter_mut() {
Self::hydrate_val(set, val, reg, hide)?;
let mut new_key = Value::String(key.clone());
let key_changed = Self::hydrate_str(set, &mut new_key, reg, hide)?;
if !key_changed {
continue;
}
match new_key {
Value::String(new_key) if keys.contains(&new_key) => {
return Err(FrError::FrameParsef(DUPE_KEY_UPON_HYDRATION_ERR, new_key));
}
Value::String(new_key) => {
replace_keys.push((keys.get(key).unwrap(), new_key));
}
_ => return Err(FrError::FrameParse(INVALID_KEY_HYDRATION_ERR)),
}
}
for (old_key, new_key) in replace_keys.into_iter() {
let val = map.remove(old_key).unwrap();
map.insert(new_key, val);
}
Ok(())
}
Value::Array(vec) => {
for val in vec.iter_mut() {
Self::hydrate_val(set, val, reg, hide)?;
}
Ok(())
}
Value::String(_) => {
Self::hydrate_str(set, val, reg, hide)?;
Ok(())
}
_ => Ok(()),
}
}
fn hydrate_str(
set: &InstructionSet,
val: &mut Value,
reg: &Register,
hide: bool,
) -> Result<bool, FrError> {
{
let matches = reg.read_match(val.as_str().expect("hydrate_str None found"))?;
if matches.is_empty() {
return Ok(false);
}
for mat in matches.into_iter() {
if let Some(n) = mat.name() {
if !set.contains(n) {
return Err(FrError::FrameParsef(MISSING_VAR_ERR, n.to_string()));
}
if set.reads.contains(n) {
reg.read_operation(mat, val, hide)?;
continue;
}
if set.writes.contains_key(n) && set.hydrate_writes {
reg.read_operation(mat, val, hide)?;
}
}
}
Ok(true)
}
}
}
impl<'a> TryFrom<PathBuf> for Frame<'a> {
type Error = FrError;
fn try_from(path: PathBuf) -> Result<Self, Self::Error> {
let buf = crate::file_to_reader(&path)?;
let frame: Frame = serde_json::from_reader(buf)?;
Ok(frame)
}
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub enum Protocol {
#[serde(rename(serialize = "gRPC", deserialize = "gRPC"))]
#[allow(clippy::upper_case_acronyms)]
GRPC,
#[allow(clippy::upper_case_acronyms)]
HTTP,
}
#[derive(Serialize, Clone, Deserialize, Default, Debug, PartialEq)]
#[serde(default)]
pub struct InstructionSet<'a> {
#[serde(
rename(serialize = "from", deserialize = "from"),
skip_serializing_if = "HashSet::is_empty",
serialize_with = "ordered_set"
)]
pub(crate) reads: HashSet<Cow<'a, str>>,
#[serde(
rename(serialize = "to", deserialize = "to"),
skip_serializing_if = "HashMap::is_empty",
serialize_with = "ordered_str_map"
)]
pub(crate) writes: HashMap<Cow<'a, str>, Cow<'a, str>>,
#[serde(skip_serializing, default)]
pub hydrate_writes: bool,
}
impl<'a> InstructionSet<'a> {
fn is_empty(&self) -> bool {
self.reads.is_empty() && self.writes.is_empty()
}
fn contains(&self, var: &str) -> bool {
self.reads.contains(var) || self.writes.contains_key(var)
}
fn validate(&self) -> Result<(), FrError> {
let writes_set: HashSet<Cow<str>> = self.writes.keys().cloned().collect();
let intersection = self.reads.intersection(&writes_set).next();
if intersection.is_some() {
return Err(FrError::FrameParsef(
DUPE_VAR_REFERENCE_ERR,
format!("{:?}", intersection),
));
}
Ok(())
}
}
#[derive(Serialize, Clone, Deserialize, Debug, PartialEq)]
pub struct Request {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) body: Option<Value>,
pub(crate) uri: Value,
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub(crate) etc: Option<Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) header: Option<Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) entrypoint: Option<Value>,
}
impl Request {
pub fn to_payload(&self) -> Result<String, SerdeError> {
serde_json::to_string_pretty(&self.body)
}
pub fn to_val_payload(&self) -> Result<Option<Value>, SerdeError> {
self.body.as_ref().map(serde_json::to_value).transpose()
}
pub fn get_uri(&self) -> String {
if let Value::String(string) = &self.uri {
return string.to_string();
}
"".to_string()
}
pub fn get_etc(&self) -> Option<Value> {
self.etc.clone()
}
pub fn get_header(&self) -> Option<Value> {
self.header.clone()
}
pub fn get_entrypoint(&self) -> Option<String> {
if let Some(entrypoint) = self.entrypoint.clone() {
return Some(String::from(entrypoint.as_str()?));
}
None
}
}
impl Default for Request {
fn default() -> Self {
Self {
body: None,
uri: Value::Null,
etc: Some(json!({})),
header: None,
entrypoint: None,
}
}
}
#[macro_export]
macro_rules! to {
({$( $key: expr => $val: expr ),*}) => {{
use ::std::collections::HashMap;
use ::std::borrow::Cow;
let mut map: HashMap<Cow<str>, Cow<str>> = HashMap::new();
$(map.insert($key.into(), $val.into());)*
map
}}
}
#[macro_export]
macro_rules! from {
($( $cut_var: expr ),*) => {{
use ::std::collections::HashSet;
use ::std::borrow::Cow;
#[allow(unused_mut)]
let mut set:HashSet<Cow<str>> = HashSet::new();
$( set.insert($cut_var.into()); )*
set
}}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::register;
use pretty_assertions::assert_eq;
use serde_json::json;
const FRAME_JSON: &str = r#"
{
"protocol": "gRPC",
"cut": {
"from": [
"EMAIL",
"FIRST",
"HOST",
"LAST",
"METHOD",
"OBJECT",
"PORT",
"USER_TOKEN"
]
},
"request": {
"header": {
"Authorization": "${USER_TOKEN}"
},
"entrypoint": "${HOST}:${PORT}",
"body": {
"name": "${FIRST} ${LAST}",
"email": "${EMAIL}",
"object": "${OBJECT}"
},
"uri": "user_api.User/${METHOD}"
},
"response": {
"body": "${RESPONSE}",
"status": 0
}
}
"#;
#[test]
fn test_hydrate() {
let reg = register!({
"EMAIL"=> "new_user@humanmail.com",
"FIRST"=> "Mario",
"HOST"=> "localhost",
"LAST"=> "Rossi",
"METHOD"=> "CreateUser",
"OBJECT"=>json!({ "key": "value"}),
"PORT"=> "8080",
"USER_TOKEN"=> "Bearer jWt"
});
let mut frame: Frame = Frame::new(FRAME_JSON).unwrap();
frame.hydrate(®, false).unwrap();
assert_eq!(
Frame {
protocol: Protocol::GRPC,
cut: InstructionSet {
reads: from![
"EMAIL",
"FIRST",
"HOST",
"LAST",
"METHOD",
"OBJECT",
"PORT",
"USER_TOKEN"
],
writes: HashMap::new(),
hydrate_writes: false,
},
request: Request {
body: Some(json!({
"name": "Mario Rossi",
"email": "new_user@humanmail.com",
"object": json!({ "key": "value"})
})),
header: Some(json!({"Authorization": "Bearer jWt"})),
entrypoint: Some(json!("localhost:8080")),
uri: json!("user_api.User/CreateUser"),
etc: Some(json!({})),
},
response: Response {
body: Some(json!("${RESPONSE}")),
status: 0,
..Default::default()
},
},
frame
);
}
const KEY_VAR_JSON: &str = r#"
{
"protocol": "gRPC",
"cut": {
"from": [
"KEY",
"KEY_2"
]
},
"request": {
"body": {},
"uri": ""
},
"response": {
"body": {
"${KEY}": "val",
"${KEY_2}": "val_2",
"${KEY}${KEY_2}": "val_3"
},
"status": 0
}
}
"#;
#[test]
fn test_key_hydrate() {
let reg = register!({
"KEY"=> "key",
"KEY_2"=> "key_2"
});
let mut frame: Frame = Frame::new(KEY_VAR_JSON).unwrap();
frame.hydrate(®, false).unwrap();
assert_eq!(
Frame {
protocol: Protocol::GRPC,
cut: InstructionSet {
reads: from!["KEY", "KEY_2"],
writes: HashMap::new(),
hydrate_writes: false,
},
request: Request {
body: Some(json!({})),
uri: "".into(),
..Default::default()
},
response: Response {
body: Some(json!( {
"key": "val",
"key_2": "val_2",
"keykey_2": "val_3"
})),
status: 0,
..Default::default()
},
},
frame
);
}
#[test]
fn test_instruction_set_validate() {
let set = InstructionSet {
reads: from!["USER_ID"],
writes: to! ({"USER_ID"=> "'response'.'body'.'id'"}),
hydrate_writes: false,
};
assert!(set.validate().is_err());
}
}