cedar_policy/ffi/
format.rs#![allow(clippy::module_name_repetitions)]
use super::utils::DetailedError;
use cedar_policy_formatter::{policies_str_to_pretty, Config};
use serde::{Deserialize, Serialize};
#[cfg(feature = "wasm")]
use wasm_bindgen::prelude::wasm_bindgen;
#[cfg(feature = "wasm")]
extern crate tsify;
#[cfg_attr(feature = "wasm", wasm_bindgen(js_name = "formatPolicies"))]
#[allow(clippy::needless_pass_by_value)]
pub fn format(call: FormattingCall) -> FormattingAnswer {
let config = Config {
line_width: call.line_width,
indent_width: call.indent_width,
};
match policies_str_to_pretty(&call.policy_text, &config) {
Ok(prettified_policy) => FormattingAnswer::Success {
formatted_policy: prettified_policy,
},
Err(err) => FormattingAnswer::Failure {
errors: vec![err.into()],
},
}
}
pub fn format_json(json: serde_json::Value) -> Result<serde_json::Value, serde_json::Error> {
let ans = format(serde_json::from_value(json)?);
serde_json::to_value(ans)
}
pub fn format_json_str(json: &str) -> Result<String, serde_json::Error> {
let ans = format(serde_json::from_str(json)?);
serde_json::to_string(&ans)
}
#[derive(Serialize, Deserialize, Debug)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub struct FormattingCall {
policy_text: String,
#[serde(default = "default_line_width")]
line_width: usize,
#[serde(default = "default_indent_width")]
indent_width: isize,
}
const fn default_line_width() -> usize {
80
}
const fn default_indent_width() -> isize {
2
}
#[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
#[serde(tag = "type")]
#[serde(rename_all = "camelCase")]
pub enum FormattingAnswer {
Failure {
errors: Vec<DetailedError>,
},
Success {
formatted_policy: String,
},
}
#[allow(clippy::panic, clippy::indexing_slicing)]
#[cfg(test)]
mod test {
use super::*;
use crate::ffi::test_utils::*;
use cool_asserts::assert_matches;
use serde_json::json;
#[track_caller]
fn assert_format_succeeds(json: serde_json::Value) -> String {
let ans_val = format_json(json).unwrap();
let result: Result<FormattingAnswer, _> = serde_json::from_value(ans_val);
assert_matches!(result, Ok(FormattingAnswer::Success { formatted_policy }) => {
formatted_policy
})
}
#[track_caller]
fn assert_format_fails(json: serde_json::Value) -> Vec<DetailedError> {
let ans_val = format_json(json).unwrap();
let result: Result<FormattingAnswer, _> = serde_json::from_value(ans_val);
assert_matches!(result, Ok(FormattingAnswer::Failure { errors }) => errors)
}
#[test]
fn test_format_succeeds() {
let json = json!({
"policyText": "permit(principal in UserGroup::\"alice_friends\", action == Action::\"viewPhoto\", resource);",
"lineWidth": 100,
"indentWidth": 4,
});
let result = assert_format_succeeds(json);
assert_eq!(result, "permit (\n principal in UserGroup::\"alice_friends\",\n action == Action::\"viewPhoto\",\n resource\n);\n");
}
#[test]
fn test_format_succeed_default_values() {
let json = json!({
"policyText": "permit(principal in UserGroup::\"alice_friends\", action == Action::\"viewPhoto\", resource);",
});
let result = assert_format_succeeds(json);
assert_eq!(result, "permit (\n principal in UserGroup::\"alice_friends\",\n action == Action::\"viewPhoto\",\n resource\n);\n");
}
#[test]
fn test_format_fails() {
let json = json!({
"policyText": "foo(principal in UserGroup::\"alice_friends\", action == Action::\"viewPhoto\", resource);",
"lineWidth": 100,
"indentWidth": 4,
});
let errs = assert_format_fails(json);
assert_exactly_one_error(
&errs,
"cannot parse input policies: invalid policy effect: foo",
Some("effect must be either `permit` or `forbid`"),
);
}
}