Skip to content

Commit

Permalink
Add Validation property to response. (#21)
Browse files Browse the repository at this point in the history
* Moved Reference into own file

* updated with reference params
  • Loading branch information
mkatychev authored Mar 29, 2021
1 parent 0b78ecc commit b641def
Show file tree
Hide file tree
Showing 15 changed files with 374 additions and 340 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,6 @@ chrono = "0.4"

[dev-dependencies]
rstest = "0.6"

[profile.dev]
split-debuginfo = "unpacked"
3 changes: 3 additions & 0 deletions filmreel/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,12 @@ colored = "2.0"
glob = "0.3"
jql = "2.8"
lazy_static = "1.4.0"
paste = "1.0.5"
pretty_assertions = "0.7.1"
regex = "1.4"
serde = { version = "1.0.118", features = ["derive"] }
serde_json = "1.0"

[dev-dependencies]
rstest = "0.6"
paste = "1.0.5"
3 changes: 1 addition & 2 deletions filmreel/src/cut.rs
Original file line number Diff line number Diff line change
Expand Up @@ -592,8 +592,7 @@ mod serde_tests {
}
"#;
test_ser_de!(
register_ser,
register_de,
register,
register!({
"FIRST_NAME"=> "Primus",
"RESPONSE"=> "ALRIGHT"
Expand Down
170 changes: 38 additions & 132 deletions filmreel/src/frame.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::{
cut::Register,
error::FrError,
utils::{get_jql_value, ordered_set, ordered_str_map},
response::Response,
utils::{ordered_set, ordered_str_map},
};
use serde::{Deserialize, Serialize};
use serde_json::{error::Error as SerdeError, json, to_value, Value};
Expand All @@ -13,16 +14,13 @@ use std::collections::{HashMap, HashSet};
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub struct Frame<'a> {
pub protocol: Protocol,
// Both the reads and writes can be optional
#[serde(default, borrow, skip_serializing_if = "InstructionSet::is_empty")]
pub cut: InstructionSet<'a>,
pub cut: InstructionSet<'a>, // Both the reads and writes can be optional
pub(crate) request: Request,
pub response: Response,
pub response: Response<'a>,
}

const MISSING_VAR_ERR: &str = "Variable is not present in InstructionSet";
const INVALID_INSTRUCTION_TYPE_ERR: &str =
"Frame write instruction did not correspond to a string object";
const DUPE_VAR_REFERENCE_ERR: &str =
"Cut Variables cannot be referenced by both read and write instructions";

Expand Down Expand Up @@ -61,15 +59,18 @@ impl<'a> Frame<'a> {
/// performs Register.read_operation on Strings with Cut Variables
pub fn hydrate(&mut self, reg: &Register, hide: bool) -> Result<(), FrError> {
let set = self.cut.clone();
Self::hydrate_val(&set, &mut self.request.body, reg, hide)?;
Self::hydrate_val(&set, &mut self.request.etc, reg, hide)?;
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)?;
}
Self::hydrate_val(&set, &mut self.response.etc, 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)?;
}

// URI and entrypoint is given an explicit read operation
Self::hydrate_str(&set, &mut self.request.uri, reg, hide)?;
Expand Down Expand Up @@ -147,7 +148,9 @@ impl<'a> Frame<'a> {
#[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,
}

Expand All @@ -158,15 +161,15 @@ pub enum Protocol {
#[derive(Serialize, Clone, Deserialize, Default, Debug, PartialEq)]
#[serde(default)]
pub struct InstructionSet<'a> {
#[serde(rename(serialize = "from", deserialize = "from"))]
#[serde(
rename(serialize = "from", deserialize = "from"),
skip_serializing_if = "HashSet::is_empty",
serialize_with = "ordered_set",
borrow
)]
pub(crate) reads: HashSet<&'a str>,
#[serde(rename(serialize = "to", deserialize = "to"))]
#[serde(
rename(serialize = "to", deserialize = "to"),
skip_serializing_if = "HashMap::is_empty",
serialize_with = "ordered_str_map",
borrow
Expand Down Expand Up @@ -203,25 +206,27 @@ impl<'a> InstructionSet<'a> {
/// Encapsulates the request payload to be sent.
///
/// [Request Object](https://github.com/Bestowinc/filmReel/blob/master/frame.md#request)
#[derive(Serialize, Clone, Deserialize, Default, Debug, PartialEq)]
#[derive(Serialize, Clone, Deserialize, Debug, PartialEq)]
pub struct Request {
pub(crate) body: Value,
#[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>,
#[serde(flatten)]
pub(crate) etc: Value,
pub(crate) uri: 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<Value, SerdeError> {
serde_json::to_value(&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 {
Expand All @@ -231,7 +236,7 @@ impl Request {
"".to_string()
}

pub fn get_etc(&self) -> Value {
pub fn get_etc(&self) -> Option<Value> {
self.etc.clone()
}

Expand All @@ -247,67 +252,15 @@ impl Request {
}
}

/// Encapsulates the expected response payload.
///
/// [Request Object](https://github.com/Bestowinc/filmReel/blob/master/frame.md#request)
#[derive(Serialize, Clone, Deserialize, Debug, Default, PartialEq)]
pub struct Response {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub body: Option<Value>,
#[serde(flatten)]
pub etc: Value,
pub status: u32,
}

impl Response {
/// Cast to a serialized Frame as serde_json::Value object for consistency in jql object
/// traversal: `"response"."body"` should always traverse a serialized Frame struct
fn to_frame_value(&self) -> Result<Value, FrError> {
let mut frame_value = json!({"response":{}});
frame_value["response"] = to_value(self)?;
Ok(frame_value)
}

/// Using the write instructions found in the frame InstructionSet, look for matches to be
/// passed to write operations
pub fn match_payload_response<'a>(
&self,
set: &'a InstructionSet,
payload_response: &Response,
) -> Result<Option<HashMap<&'a str, Value>>, FrError> {
let frame_response: Value = self.to_frame_value()?;
let payload_response: Value = payload_response.to_frame_value()?;

let mut write_matches: HashMap<&str, Value> = HashMap::new();
for (k, query) in set.writes.iter() {
// ensure frame jql query returns a string object
let frame_str = match get_jql_value(&frame_response, query) {
Ok(Value::String(v)) => Ok(v),
Ok(_) => Err(FrError::FrameParsef(
INVALID_INSTRUCTION_TYPE_ERR,
query.to_string(),
)),
Err(e) => Err(e),
}?;
let payload_val = get_jql_value(&payload_response, query)?;

if let Value::String(payload_str) = &payload_val {
let write_match = Register::write_match(k, &frame_str, payload_str)?;
if let Some(mat) = write_match {
write_matches.insert(k, to_value(mat)?);
}
continue;
}
// handle non string payload values returned by the jql query
Register::expect_standalone_var(k, &frame_str)?;
write_matches.insert(k, payload_val);
}

if write_matches.iter().next().is_some() {
return Ok(Some(write_matches));
impl Default for Request {
fn default() -> Self {
Self {
body: None,
uri: Value::Null,
etc: Some(json!({})),
header: None,
entrypoint: None,
}

Ok(None)
}
}

Expand Down Expand Up @@ -360,7 +313,7 @@ macro_rules! from {
mod tests {
use super::*;
use crate::register;
// use rstest::*;
use pretty_assertions::assert_eq;
use serde_json::json;

const FRAME_JSON: &str = r#"
Expand Down Expand Up @@ -430,74 +383,27 @@ mod tests {
hydrate_writes: false,
},
request: Request {
body: json!({
body: Some(json!({
"name": "Mario Rossi",
"email": "[email protected]",
"object": json!({ "key": "value"})
}),
})),
header: Some(json!({"Authorization": "Bearer jWt"})),
entrypoint: Some(json!("localhost:8080")),
etc: json!({}),
uri: json!("user_api.User/CreateUser"),
etc: Some(json!({})),
},

response: Response {
body: Some(json!("${RESPONSE}")),
etc: json!({}),
body: Some(json!("${RESPONSE}")),
status: 0,
..Default::default()
},
},
frame
);
}

#[test]
fn test_match_payload_response() {
let frame = Frame {
protocol: Protocol::GRPC,
cut: InstructionSet {
reads: from![],
writes: to! ({
"USER_ID"=> "'response'.'body'.'id'",
"CREATED"=> "'response'.'body'.'created'",
"ignore"=> "'response'.'body'.'array'.[0].'ignore'"
}),
hydrate_writes: true,
},
request: Request {
..Default::default()
},
response: Response {
body: Some(json!({
"id": "${USER_ID}",
"created": "${CREATED}",
"array": [{"ignore":"${ignore}"}]
})),
etc: json!({}),
status: 0,
},
};

let payload_response = Response {
body: Some(json!({
"id": "ID_010101",
"created": 101010,
"array": [{"ignore": "value"}]
})),
etc: json!({}),
status: 0,
};
let mat = frame
.response
.match_payload_response(&frame.cut, &payload_response)
.unwrap();
let mut expected_match = HashMap::new();
expected_match.insert("USER_ID", to_value("ID_010101").unwrap());
expected_match.insert("CREATED", to_value(101010).unwrap());
expected_match.insert("ignore", to_value("value").unwrap());
assert_eq!(expected_match, mat.unwrap());
}

#[test]
fn test_instruction_set_validate() {
let set = InstructionSet {
Expand Down
7 changes: 5 additions & 2 deletions filmreel/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ pub mod cut;
mod error;
pub mod frame;
pub mod reel;
pub mod response;
pub mod utils;
pub mod value;

#[cfg(test)]
mod serde_tests;
Expand All @@ -32,7 +32,10 @@ use serde::Serialize;
use std::{fs, path::Path};

// Convenience in converting a Path to a String
pub fn file_to_string<P: AsRef<Path>>(path: P) -> std::io::Result<String> {
pub fn file_to_string<P>(path: P) -> std::io::Result<String>
where
P: AsRef<Path>,
{
// https://github.com/serde-rs/json/issues/160
let json_string: String = fs::read_to_string(path)?;

Expand Down
Loading

0 comments on commit b641def

Please sign in to comment.