cedar_policy/proto/
validator.rs

1/*
2 * Copyright Cedar Contributors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#![allow(clippy::use_self)]
18
19use super::models;
20use cedar_policy_core::ast;
21use cedar_policy_validator::types;
22use nonempty::NonEmpty;
23use smol_str::SmolStr;
24use std::collections::{BTreeMap, HashMap};
25
26impl From<&cedar_policy_validator::ValidatorSchema> for models::Schema {
27    fn from(v: &cedar_policy_validator::ValidatorSchema) -> Self {
28        Self {
29            entity_decls: v.entity_types().map(models::EntityDecl::from).collect(),
30            action_decls: v.action_ids().map(models::ActionDecl::from).collect(),
31        }
32    }
33}
34
35impl From<&models::Schema> for cedar_policy_validator::ValidatorSchema {
36    // PANIC SAFETY: experimental feature
37    #[allow(clippy::expect_used)]
38    fn from(v: &models::Schema) -> Self {
39        Self::new(
40            v.entity_decls
41                .iter()
42                .map(cedar_policy_validator::ValidatorEntityType::from),
43            v.action_decls
44                .iter()
45                .map(cedar_policy_validator::ValidatorActionId::from),
46        )
47    }
48}
49
50impl From<&cedar_policy_validator::ValidationMode> for models::ValidationMode {
51    // PANIC SAFETY: experimental feature
52    #[allow(clippy::unimplemented)]
53    fn from(v: &cedar_policy_validator::ValidationMode) -> Self {
54        match v {
55            cedar_policy_validator::ValidationMode::Strict => models::ValidationMode::Strict,
56            cedar_policy_validator::ValidationMode::Permissive => {
57                models::ValidationMode::Permissive
58            }
59            #[cfg(feature = "partial-validate")]
60            cedar_policy_validator::ValidationMode::Partial => unimplemented!(),
61        }
62    }
63}
64
65impl From<&models::ValidationMode> for cedar_policy_validator::ValidationMode {
66    fn from(v: &models::ValidationMode) -> Self {
67        match v {
68            models::ValidationMode::Strict => cedar_policy_validator::ValidationMode::Strict,
69            models::ValidationMode::Permissive => {
70                cedar_policy_validator::ValidationMode::Permissive
71            }
72        }
73    }
74}
75
76// PANIC SAFETY: experimental feature
77#[allow(clippy::fallible_impl_from)]
78impl From<&cedar_policy_validator::ValidatorActionId> for models::ActionDecl {
79    // PANIC SAFETY: experimental feature
80    #[allow(clippy::panic)]
81    fn from(v: &cedar_policy_validator::ValidatorActionId) -> Self {
82        debug_assert_eq!(
83            v.attribute_types().keys().collect::<Vec<&SmolStr>>(),
84            Vec::<&SmolStr>::new(),
85            "action attributes are not currently supported in protobuf"
86        );
87        debug_assert_eq!(
88            v.attributes().collect::<Vec<_>>(),
89            vec![],
90            "action attributes are not currently supported in protobuf"
91        );
92        let ctx_attrs = match v.context() {
93            types::Type::EntityOrRecord(types::EntityRecordKind::Record {
94                attrs,
95                open_attributes: types::OpenTag::ClosedAttributes,
96            }) => attrs,
97            ty => panic!("expected context to be a closed record, but got {ty:?}"),
98        };
99        Self {
100            name: Some(models::EntityUid::from(v.name())),
101            principal_types: v.applies_to_principals().map(models::Name::from).collect(),
102            resource_types: v.applies_to_resources().map(models::Name::from).collect(),
103            descendants: v.descendants().map(models::EntityUid::from).collect(),
104            context: attributes_to_model(ctx_attrs),
105        }
106    }
107}
108
109impl From<&models::ActionDecl> for cedar_policy_validator::ValidatorActionId {
110    // PANIC SAFETY: experimental feature
111    #[allow(clippy::expect_used)]
112    fn from(v: &models::ActionDecl) -> Self {
113        Self::new(
114            ast::EntityUID::from(v.name.as_ref().expect("name field should exist")),
115            v.principal_types.iter().map(ast::EntityType::from),
116            v.resource_types.iter().map(ast::EntityType::from),
117            v.descendants.iter().map(ast::EntityUID::from),
118            types::Type::EntityOrRecord(types::EntityRecordKind::Record {
119                attrs: model_to_attributes(&v.context),
120                open_attributes: types::OpenTag::default(),
121            }),
122            // protobuf formats do not include action attributes, so we
123            // translate into a `ValidatorActionId` with no action attributes
124            types::Attributes::with_attributes([]),
125            BTreeMap::new(),
126            None,
127        )
128    }
129}
130
131impl From<&cedar_policy_validator::ValidatorEntityType> for models::EntityDecl {
132    fn from(v: &cedar_policy_validator::ValidatorEntityType) -> Self {
133        let name = Some(models::Name::from(v.name()));
134        let descendants = v.descendants.iter().map(models::Name::from).collect();
135        let attributes = attributes_to_model(v.attributes());
136        let tags = v.tag_type().map(models::Type::from);
137        match &v.kind {
138            cedar_policy_validator::ValidatorEntityTypeKind::Standard(_) => Self {
139                name,
140                descendants,
141                attributes,
142                tags,
143                enum_choices: vec![],
144            },
145            cedar_policy_validator::ValidatorEntityTypeKind::Enum(enum_choices) => Self {
146                name,
147                descendants,
148                attributes,
149                tags,
150                enum_choices: enum_choices.into_iter().map(ToString::to_string).collect(),
151            },
152        }
153    }
154}
155
156impl From<&models::EntityDecl> for cedar_policy_validator::ValidatorEntityType {
157    // PANIC SAFETY: experimental feature
158    #[allow(clippy::expect_used)]
159    fn from(v: &models::EntityDecl) -> Self {
160        let name = ast::EntityType::from(v.name.as_ref().expect("name field should exist"));
161        let descendants = v.descendants.iter().map(ast::EntityType::from);
162        match NonEmpty::collect(v.enum_choices.iter().map(SmolStr::from)) {
163            // `enum_choices` is empty, so `v` represents a standard entity type
164            None => Self::new_standard(
165                name,
166                descendants,
167                model_to_attributes(&v.attributes),
168                types::OpenTag::default(),
169                v.tags.as_ref().map(types::Type::from),
170                None,
171            ),
172            Some(enum_choices) => {
173                // `enum_choices` is not empty, so `v` represents an enumerated entity type.
174                // enumerated entity types must have no attributes or tags.
175                assert_eq!(&v.attributes, &HashMap::new());
176                assert_eq!(&v.tags, &None);
177                Self::new_enum(name.clone(), descendants, enum_choices, name.loc().cloned())
178            }
179        }
180    }
181}
182
183impl From<&models::Type> for types::Type {
184    // PANIC SAFETY: experimental feature
185    #[allow(clippy::expect_used)]
186    fn from(v: &models::Type) -> Self {
187        match v.data.as_ref().expect("data field should exist") {
188            models::r#type::Data::Prim(vt) => {
189                match models::r#type::Prim::try_from(vt.to_owned()).expect("decode should succeed")
190                {
191                    models::r#type::Prim::Bool => types::Type::primitive_boolean(),
192                    models::r#type::Prim::String => types::Type::primitive_string(),
193                    models::r#type::Prim::Long => types::Type::primitive_long(),
194                }
195            }
196            models::r#type::Data::SetElem(elty) => types::Type::Set {
197                element_type: Some(Box::new(types::Type::from(elty.as_ref()))),
198            },
199            models::r#type::Data::Entity(e) => {
200                types::Type::EntityOrRecord(types::EntityRecordKind::Entity(
201                    types::EntityLUB::single_entity(ast::EntityType::from(e)),
202                ))
203            }
204            models::r#type::Data::Record(r) => {
205                types::Type::EntityOrRecord(types::EntityRecordKind::Record {
206                    attrs: model_to_attributes(&r.attrs),
207                    open_attributes: types::OpenTag::default(),
208                })
209            }
210            models::r#type::Data::Ext(name) => types::Type::ExtensionType {
211                name: ast::Name::from(name),
212            },
213        }
214    }
215}
216
217// PANIC SAFETY: experimental feature
218#[allow(clippy::fallible_impl_from)]
219impl From<&types::Type> for models::Type {
220    // PANIC SAFETY: experimental feature
221    #[allow(clippy::expect_used, clippy::panic)]
222    fn from(v: &types::Type) -> Self {
223        match v {
224            types::Type::Never => panic!("can't encode Never type in protobuf; Never should never appear in a Schema"),
225            types::Type::True | types::Type::False => panic!("can't encode singleton boolean type in protobuf; singleton boolean types should never appear in a Schema"),
226            types::Type::Primitive { primitive_type } => match primitive_type {
227                types::Primitive::Bool => Self {
228                    data: Some(models::r#type::Data::Prim(models::r#type::Prim::Bool.into())),
229                },
230                types::Primitive::Long => Self {
231                    data: Some(models::r#type::Data::Prim(models::r#type::Prim::Long.into())),
232                },
233                types::Primitive::String => Self {
234                    data: Some(models::r#type::Data::Prim(models::r#type::Prim::String.into())),
235                },
236            },
237            types::Type::Set { element_type } => Self {
238                data: Some(models::r#type::Data::SetElem(Box::new(models::Type::from(
239                    element_type
240                        .as_ref()
241                        .expect("can't encode Set without element type in protobuf; Set-without-element-type should never appear in a Schema")
242                        .as_ref(),
243                )))),
244            },
245            types::Type::EntityOrRecord(types::EntityRecordKind::Entity(lub)) => Self {
246                data: Some(models::r#type::Data::Entity(models::Name::from(lub.get_single_entity().expect("can't encode non-singleton LUB in protobuf; non-singleton LUB types should never appear in a Schema").as_ref()))),
247            },
248            types::Type::EntityOrRecord(types::EntityRecordKind::Record { attrs, open_attributes }) => {
249                assert_eq!(open_attributes, &types::OpenTag::ClosedAttributes, "can't encode open record in protobuf");
250                Self {
251                    data: Some(models::r#type::Data::Record(models::r#type::Record { attrs: attributes_to_model(attrs) })),
252                }
253            }
254            types::Type::EntityOrRecord(types::EntityRecordKind::ActionEntity { name, attrs }) => {
255                debug_assert_eq!(attrs.keys().collect::<Vec<&SmolStr>>(), Vec::<&SmolStr>::new(), "can't encode action attributes in protobuf");
256                Self {
257                    data: Some(models::r#type::Data::Entity(models::Name::from(name.as_ref()))),
258                }
259            }
260            types::Type::EntityOrRecord(types::EntityRecordKind::AnyEntity) => panic!("can't encode AnyEntity type in protobuf; AnyEntity should never appear in a Schema"),
261            types::Type::ExtensionType { name } => Self {
262                data: Some(models::r#type::Data::Ext(models::Name::from(name))),
263            },
264        }
265    }
266}
267
268fn model_to_attributes(v: &HashMap<String, models::AttributeType>) -> types::Attributes {
269    types::Attributes::with_attributes(v.iter().map(|(k, v)| (k.into(), v.into())))
270}
271
272fn attributes_to_model(v: &types::Attributes) -> HashMap<String, models::AttributeType> {
273    v.iter()
274        .map(|(k, v)| (k.to_string(), models::AttributeType::from(v)))
275        .collect()
276}
277
278impl From<&models::AttributeType> for types::AttributeType {
279    // PANIC SAFETY: experimental feature
280    #[allow(clippy::expect_used)]
281    fn from(v: &models::AttributeType) -> Self {
282        Self {
283            attr_type: types::Type::from(
284                v.attr_type.as_ref().expect("attr_type field should exist"),
285            ),
286            is_required: v.is_required,
287            #[cfg(feature = "extended-schema")]
288            loc: None,
289        }
290    }
291}
292
293impl From<&types::AttributeType> for models::AttributeType {
294    fn from(v: &types::AttributeType) -> Self {
295        Self {
296            attr_type: Some(models::Type::from(&v.attr_type)),
297            is_required: v.is_required,
298        }
299    }
300}