cedar_policy_cli/utils/
schema.rs1use cedar_policy::Schema;
18use clap::{Args, ValueEnum};
19use miette::{Result, WrapErr};
20use std::path::{Path, PathBuf};
21
22use crate::utils::read_from_file;
23
24#[derive(Debug, Default, Clone, Copy, ValueEnum)]
25pub enum SchemaFormat {
26 #[default]
28 Cedar,
29 Json,
31}
32
33#[derive(Args, Debug)]
35pub struct SchemaArgs {
36 #[arg(short, long = "schema", value_name = "FILE")]
38 pub schema_file: PathBuf,
39 #[arg(long, value_enum, default_value_t)]
41 pub schema_format: SchemaFormat,
42}
43
44impl SchemaArgs {
45 pub(crate) fn get_schema(&self) -> Result<Schema> {
47 read_schema_from_file(&self.schema_file, self.schema_format)
48 }
49}
50
51#[derive(Args, Debug)]
54pub struct OptionalSchemaArgs {
55 #[arg(short, long = "schema", value_name = "FILE")]
57 pub schema_file: Option<PathBuf>,
58 #[arg(long, value_enum, default_value_t)]
60 pub schema_format: SchemaFormat,
61}
62
63impl OptionalSchemaArgs {
64 pub(crate) fn get_schema(&self) -> Result<Option<Schema>> {
66 let Some(schema_file) = &self.schema_file else {
67 return Ok(None);
68 };
69 read_schema_from_file(schema_file, self.schema_format).map(Some)
70 }
71}
72
73fn read_schema_from_file(path: impl AsRef<Path>, format: SchemaFormat) -> Result<Schema> {
74 let path = path.as_ref();
75 let schema_src = read_from_file(path, "schema")?;
76 match format {
77 SchemaFormat::Json => Schema::from_json_str(&schema_src)
78 .wrap_err_with(|| format!("failed to parse schema from file {}", path.display())),
79 SchemaFormat::Cedar => {
80 let (schema, warnings) = Schema::from_cedarschema_str(&schema_src)
81 .wrap_err_with(|| format!("failed to parse schema from file {}", path.display()))?;
82 for warning in warnings {
83 let report = miette::Report::new(warning);
84 eprintln!("{report:?}");
85 }
86 Ok(schema)
87 }
88 }
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94 use crate::utils::test_utils::{render_err, TEMPFILE_FILTER};
95 use std::io::Write;
96
97 #[test]
98 fn cedar_schema_from_file_parse_error() {
99 let mut f = tempfile::NamedTempFile::new().unwrap();
100 f.write_all(b"not a valid schema").unwrap();
101 let err = read_schema_from_file(f.path(), SchemaFormat::Cedar).unwrap_err();
102 insta::with_settings!({filters => vec![TEMPFILE_FILTER]}, {
103 insta::assert_snapshot!(render_err(&err), @r"
104 × failed to parse schema from file <TEMPFILE>
105 ╰─▶ error parsing schema: unexpected token `not`
106 ╭────
107 1 │ not a valid schema
108 · ─┬─
109 · ╰── expected `@`, `action`, `entity`, `namespace`, or `type`
110 ╰────
111 ");
112 });
113 }
114
115 #[test]
116 fn json_schema_from_file_parse_error() {
117 let mut f = tempfile::NamedTempFile::new().unwrap();
118 f.write_all(b"not json").unwrap();
119 let err = read_schema_from_file(f.path(), SchemaFormat::Json).unwrap_err();
120 insta::with_settings!({filters => vec![TEMPFILE_FILTER]}, {
121 insta::assert_snapshot!(render_err(&err), @r"
122 × failed to parse schema from file <TEMPFILE>
123 ╰─▶ expected ident at line 1 column 2
124 help: this API was expecting a schema in the JSON format; did you mean to use a different function, which expects the Cedar schema format?
125 ");
126 });
127 }
128
129 #[test]
130 fn schema_from_missing_file() {
131 let err = read_schema_from_file("/tmp/nonexistent_cedar_schema.json", SchemaFormat::Cedar)
132 .unwrap_err();
133 insta::assert_snapshot!(render_err(&err), @r"
134 × failed to open schema file /tmp/nonexistent_cedar_schema.json
135 ╰─▶ No such file or directory (os error 2)
136 ");
137 }
138}