cedar_policy_validator/human_schema/
fmt.rs1use std::{collections::HashSet, fmt::Display};
2
3use itertools::Itertools;
4use miette::Diagnostic;
5use nonempty::NonEmpty;
6use smol_str::SmolStr;
7use thiserror::Error;
8
9use crate::{
10 ActionType, EntityType, NamespaceDefinition, SchemaFragment, SchemaType, SchemaTypeVariant,
11};
12
13impl Display for SchemaFragment {
14 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
15 for (ns, def) in &self.0 {
16 if ns.is_empty() {
17 write!(f, "{def}")?
18 } else {
19 write!(f, "namespace {ns} {{\n{def}}}")?
20 }
21 }
22 Ok(())
23 }
24}
25
26impl Display for NamespaceDefinition {
27 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28 for (n, ty) in &self.common_types {
29 writeln!(f, "type {n} = {ty};")?
30 }
31 for (n, ty) in &self.entity_types {
32 writeln!(f, "entity {n}{ty};")?
33 }
34 for (n, a) in &self.actions {
35 writeln!(f, "action \"{}\"{a};", n.escape_debug())?
36 }
37 Ok(())
38 }
39}
40
41impl Display for SchemaType {
42 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43 match self {
44 SchemaType::Type(ty) => match ty {
45 SchemaTypeVariant::Boolean => write!(f, "__cedar::Bool"),
46 SchemaTypeVariant::Entity { name } => write!(f, "{name}"),
47 SchemaTypeVariant::Extension { name } => write!(f, "__cedar::{name}"),
48 SchemaTypeVariant::Long => write!(f, "__cedar::Long"),
49 SchemaTypeVariant::Record {
50 attributes,
51 additional_attributes: _,
52 } => {
53 write!(f, "{{")?;
54 for (i, (n, ty)) in attributes.iter().enumerate() {
55 write!(
56 f,
57 "\"{}\"{}: {}",
58 n.escape_debug(),
59 if ty.required { "" } else { "?" },
60 ty.ty
61 )?;
62 if i < (attributes.len() - 1) {
63 write!(f, ", ")?;
64 }
65 }
66 write!(f, "}}")?;
67 Ok(())
68 }
69 SchemaTypeVariant::Set { element } => write!(f, "Set < {element} >"),
70 SchemaTypeVariant::String => write!(f, "__cedar::String"),
71 },
72 SchemaType::TypeDef { type_name } => write!(f, "{type_name}"),
73 }
74 }
75}
76
77fn non_empty_slice<T>(v: &[T]) -> Option<NonEmpty<&T>> {
79 let vs: Vec<&T> = v.iter().collect();
80 NonEmpty::from_vec(vs)
81}
82
83fn fmt_vec<T: Display>(f: &mut std::fmt::Formatter<'_>, ets: NonEmpty<T>) -> std::fmt::Result {
84 let contents = ets.iter().map(T::to_string).join(", ");
85 write!(f, "[{contents}]")
86}
87
88impl Display for EntityType {
89 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90 if let Some(non_empty) = non_empty_slice(&self.member_of_types) {
91 write!(f, " in ")?;
92 fmt_vec(f, non_empty)?;
93 }
94
95 let ty = &self.shape.0;
96 if !ty.is_empty_record() {
98 write!(f, " = {ty}")?;
99 }
100
101 Ok(())
102 }
103}
104
105impl Display for ActionType {
106 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107 if let Some(parents) = self
108 .member_of
109 .as_ref()
110 .and_then(|refs| non_empty_slice(refs.as_slice()))
111 {
112 write!(f, " in ")?;
113 fmt_vec(f, parents)?;
114 }
115 if let Some(spec) = &self.applies_to {
116 match (
117 spec.principal_types
118 .as_ref()
119 .map(|refs| non_empty_slice(refs.as_slice())),
120 spec.resource_types
121 .as_ref()
122 .map(|refs| non_empty_slice(refs.as_slice())),
123 ) {
124 (Some(None), _) | (_, Some(None)) => {
128 write!(f, "")?;
129 }
130 (Some(Some(ps)), Some(Some(rs))) => {
132 write!(f, " appliesTo {{")?;
133 write!(f, "\n principal: ")?;
134 fmt_vec(f, ps)?;
135 write!(f, ",\n resource: ")?;
136 fmt_vec(f, rs)?;
137 write!(f, ",\n context: {}", &spec.context.0)?;
138 write!(f, "\n}}")?;
139 }
140 (Some(Some(ps)), None) => {
142 write!(f, " appliesTo {{")?;
143 write!(f, "\n principal: ")?;
144 fmt_vec(f, ps)?;
145 write!(f, ",\n context: {}", &spec.context.0)?;
146 write!(f, "\n}}")?;
147 }
148 (None, Some(Some(rs))) => {
150 write!(f, " appliesTo {{")?;
151 write!(f, "\n resource: ")?;
152 fmt_vec(f, rs)?;
153 write!(f, ",\n context: {}", &spec.context.0)?;
154 write!(f, "\n}}")?;
155 }
156 (None, None) => {
158 write!(f, " appliesTo {{")?;
159 write!(f, "\n context: {}", &spec.context.0)?;
160 write!(f, "\n}}")?;
161 }
162 }
163 } else {
164 write!(f, " appliesTo {{")?;
166 write!(f, "\n context: {{}}")?;
168 write!(f, "\n}}")?;
169 }
170 Ok(())
171 }
172}
173
174#[derive(Debug, Diagnostic, Error)]
175pub enum ToHumanSchemaStrError {
176 #[error("There exist type name collisions: {:?}", .0)]
177 NameCollisions(NonEmpty<SmolStr>),
178}
179
180pub fn json_schema_to_custom_schema_str(
181 json_schema: &SchemaFragment,
182) -> Result<String, ToHumanSchemaStrError> {
183 let mut name_collisions: Vec<SmolStr> = Vec::new();
184 for (name, ns) in json_schema.0.iter().filter(|(name, _)| !name.is_empty()) {
185 let entity_types: HashSet<SmolStr> = ns
186 .entity_types
187 .keys()
188 .map(|ty_name| format!("{name}::{ty_name}").into())
189 .collect();
190 let common_types: HashSet<SmolStr> = ns
191 .common_types
192 .keys()
193 .map(|ty_name| format!("{name}::{ty_name}").into())
194 .collect();
195 name_collisions.extend(entity_types.intersection(&common_types).cloned());
196 }
197 if let Some((head, tail)) = name_collisions.split_first() {
198 return Err(ToHumanSchemaStrError::NameCollisions(NonEmpty {
199 head: head.clone(),
200 tail: tail.to_vec(),
201 }));
202 }
203 Ok(json_schema.to_string())
204}