-
Notifications
You must be signed in to change notification settings - Fork 13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
restricting eval body to subset of Expression variants #323
Comments
Hi @iamjoncannon, could you provide me with a minimal reproducer for the issue? With the context you provided so far it seems like this could solved using the |
Hi @martinohmann, thanks for following up! I wrote some unit tests to describe my ideal behavior to evaluate blocks during an initial parsing. My suspicion is that I'm unable to define the types of these variants properly in my enums-- e.g. defining a list as a vec instead of the hcl::Expression::Array variant. Granted I'm not a Rust expert, but I wrestled with this enough to conclude that I might not be able to do this because the hcl::Expression variant types are private? Being able to define and restrict user inputs for my app would be really, really helpful, so again thank you so much for any time you can spend on my issue. // [dependencies]
// hcl-rs = "0.16.7"
// serde_json = "1.0.114"
// serde = "1.0.197"
use serde::{de::DeserializeOwned, Deserialize, Serialize};
#[derive(Deserialize, Serialize, Debug)]
#[serde(untagged)]
pub enum HeaderKey {
Identifier(hcl::Identifier),
Traversal(hcl::Traversal),
}
pub type HeaderObject = hcl::Object<hcl::ObjectKey, HeaderKey>;
#[derive(Deserialize, Serialize, Debug)]
#[serde(untagged)]
pub enum HeaderObjectOrVariable {
Object(HeaderObject),
Traversal(hcl::Traversal),
}
#[derive(Deserialize, Serialize, Debug)]
#[serde(untagged)]
pub enum ValidHeaderCases {
Traversal(hcl::Traversal),
String(String),
Array(Vec<HeaderObjectOrVariable>),
}
#[derive(Deserialize, Serialize, Debug)]
pub struct IdealRestrictedCallBlock {
pub headers: ValidHeaderCases,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct UnrestrictedCallBlock {
pub headers: hcl::Expression,
}
pub fn evaluate<T: DeserializeOwned>(input: &str) -> Result<T, hcl::Error> {
let call_block_body: hcl::Body = hcl::from_str(input).unwrap();
let block = call_block_body.into_blocks().next().unwrap().body;
let call_block_res: Result<T, hcl::Error> = hcl::from_body(block);
call_block_res
}
fn main() {}
#[cfg(test)]
mod tests {
use std::fmt::Debug;
use crate::{evaluate, IdealRestrictedCallBlock, UnrestrictedCallBlock, ValidHeaderCases};
#[test]
fn test_trivial_string() {
// this is just to test untagged solution with a trivial case
let test_block: &str = r#"
get "example" {
headers = "trivial case"
}
"#;
run_unrestricted_and_ideal(test_block, "test_trivial_string");
}
#[test]
fn test_header_block_as_variable() {
let test_block: &str = r#"
get "example" {
headers = my.headers.var
}
"#;
reporter(
"test_header_block_as_variable UnrestrictedCallBlock",
&evaluate::<UnrestrictedCallBlock>(test_block),
);
let res = evaluate::<IdealRestrictedCallBlock>(test_block);
match res {
Ok(res) => match res.headers {
ValidHeaderCases::Traversal(_var) => assert!(true),
ValidHeaderCases::String(str) => {
// the variable is evaluated as a string template
println!("variable evaluated as string: {str}");
assert!(false)
}
_ => assert!(false),
},
Err(err) => {
eprintln!("test_header_block_as_variable err {err:?}");
assert!(false);
}
}
}
#[test]
fn test_headers_defined_as_variable() {
let test_block: &str = r#"
get "example" {
headers = [
my.header.variable
]
}
"#;
run_unrestricted_and_ideal(test_block, "test_headers_defined_as_variable");
}
#[test]
fn test_headers_defined_as_string() {
let test_block: &str = r#"
get "example" {
headers = [
{ "authorization" : "let me in" }
]
}
"#;
run_unrestricted_and_ideal(test_block, "test_headers_defined_as_string");
}
#[test]
fn test_individual_headers_as_variables() {
let test_block: &str = r#"
get "example" {
headers = [
my.header.variable
]
}
"#;
run_unrestricted_and_ideal(test_block, "test_individual_headers_as_variables");
}
#[test]
fn test_individual_headers_values_as_variables() {
let test_block: &str = r#"
get "example" {
headers = [
{ "authorization" : my.auth.var }
]
}
"#;
run_unrestricted_and_ideal(test_block, "test_individual_headers_values_as_variables");
}
fn run_unrestricted_and_ideal(test_block: &str, test_name: &str) {
reporter(
&format!("{} UnrestrictedCallBlock", test_name),
&evaluate::<UnrestrictedCallBlock>(test_block),
);
reporter(
&format!("{} IdealRestrictedCallBlock", test_name),
&evaluate::<IdealRestrictedCallBlock>(test_block),
);
}
fn reporter<T: Debug>(name: &str, result: &Result<T, hcl::Error>) {
match result {
Ok(res) => {
println!("result {name}: {res:?}");
assert!(true)
}
Err(err) => {
println!("err {name} {err:?}");
assert!(false)
}
}
}
} |
Hi Martin, first thanks so much for this library!
I have a question--
I would like to restrict a property on an HCL body to a subset of
hcl::Expression
variants duringhcl::from_body(body)
schema validation.The body schema is:
I can encapsulate the Expression::Object variant--
However, rather than the general
hcl::Expression
, I want to restrictheaders
to be either--(e.g. user HTTP headers can either be an object with a key or a general expression, or a variable. If the user screws up the header value its their problem, but we validate that its not a )
When I use this
ObjectOrVariable
enum as the header schema--Option<Vec<ObjectOrVariable>>
-- I get this error with correct HCL--"unknown variant 'my-key', expected 'Object' or 'Variable'"
, wheremy-key
was the header key passed.Thanks in advance for any help, and apologies if I've missed something obvious.
The text was updated successfully, but these errors were encountered: