cedar_policy/ffi/
format.rs1#![allow(clippy::module_name_repetitions)]
21
22use super::utils::DetailedError;
23use cedar_policy_formatter::{policies_str_to_pretty, Config};
24use serde::{Deserialize, Serialize};
25#[cfg(feature = "wasm")]
26use wasm_bindgen::prelude::wasm_bindgen;
27
28#[cfg(feature = "wasm")]
29extern crate tsify;
30
31#[cfg_attr(feature = "wasm", wasm_bindgen(js_name = "formatPolicies"))]
33#[allow(clippy::needless_pass_by_value)]
34pub fn format(call: FormattingCall) -> FormattingAnswer {
35 let config = Config {
36 line_width: call.line_width,
37 indent_width: call.indent_width,
38 };
39 match policies_str_to_pretty(&call.policy_text, &config) {
40 Ok(prettified_policy) => FormattingAnswer::Success {
41 formatted_policy: prettified_policy,
42 },
43 Err(err) => FormattingAnswer::Failure {
44 errors: vec![err.into()],
45 },
46 }
47}
48
49pub fn format_json(json: serde_json::Value) -> Result<serde_json::Value, serde_json::Error> {
57 let ans = format(serde_json::from_value(json)?);
58 serde_json::to_value(ans)
59}
60
61pub fn format_json_str(json: &str) -> Result<String, serde_json::Error> {
69 let ans = format(serde_json::from_str(json)?);
70 serde_json::to_string(&ans)
71}
72
73#[derive(Serialize, Deserialize, Debug)]
75#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
76#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
77#[serde(rename_all = "camelCase")]
78#[serde(deny_unknown_fields)]
79pub struct FormattingCall {
80 policy_text: String,
82 #[serde(default = "default_line_width")]
84 line_width: usize,
85 #[serde(default = "default_indent_width")]
87 indent_width: isize,
88}
89
90const fn default_line_width() -> usize {
91 80
92}
93const fn default_indent_width() -> isize {
94 2
95}
96
97#[derive(Debug, Serialize, Deserialize)]
99#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
100#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
101#[serde(tag = "type")]
102#[serde(rename_all = "camelCase")]
103pub enum FormattingAnswer {
104 Failure {
106 errors: Vec<DetailedError>,
108 },
109 Success {
111 formatted_policy: String,
113 },
114}
115
116#[allow(clippy::panic, clippy::indexing_slicing)]
118#[cfg(test)]
119mod test {
120 use super::*;
121
122 use crate::ffi::test_utils::*;
123 use cool_asserts::assert_matches;
124 use serde_json::json;
125
126 #[track_caller]
129 fn assert_format_succeeds(json: serde_json::Value) -> String {
130 let ans_val = format_json(json).unwrap();
131 let result: Result<FormattingAnswer, _> = serde_json::from_value(ans_val);
132 assert_matches!(result, Ok(FormattingAnswer::Success { formatted_policy }) => {
133 formatted_policy
134 })
135 }
136
137 #[track_caller]
140 fn assert_format_fails(json: serde_json::Value) -> Vec<DetailedError> {
141 let ans_val = format_json(json).unwrap();
142 let result: Result<FormattingAnswer, _> = serde_json::from_value(ans_val);
143 assert_matches!(result, Ok(FormattingAnswer::Failure { errors }) => errors)
144 }
145
146 #[test]
147 fn test_format_succeeds() {
148 let json = json!({
149 "policyText": "permit(principal in UserGroup::\"alice_friends\", action == Action::\"viewPhoto\", resource);",
150 "lineWidth": 100,
151 "indentWidth": 4,
152 });
153
154 let result = assert_format_succeeds(json);
155 assert_eq!(result, "permit (\n principal in UserGroup::\"alice_friends\",\n action == Action::\"viewPhoto\",\n resource\n);\n");
156 }
157
158 #[test]
159 fn test_format_succeed_default_values() {
160 let json = json!({
161 "policyText": "permit(principal in UserGroup::\"alice_friends\", action == Action::\"viewPhoto\", resource);",
162 });
163
164 let result = assert_format_succeeds(json);
165 assert_eq!(result, "permit (\n principal in UserGroup::\"alice_friends\",\n action == Action::\"viewPhoto\",\n resource\n);\n");
166 }
167
168 #[test]
169 fn test_format_fails() {
170 let json = json!({
171 "policyText": "foo(principal in UserGroup::\"alice_friends\", action == Action::\"viewPhoto\", resource);",
172 "lineWidth": 100,
173 "indentWidth": 4,
174 });
175
176 let errs = assert_format_fails(json);
177 assert_exactly_one_error(
178 &errs,
179 "cannot parse input policies: invalid policy effect: foo",
180 Some("effect must be either `permit` or `forbid`"),
181 );
182 }
183}