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_core::validator::types;
22use nonempty::NonEmpty;
23use smol_str::SmolStr;
24use std::collections::HashMap;
25
26impl From<&cedar_policy_core::validator::ValidatorSchema> for models::Schema {
27    fn from(v: &cedar_policy_core::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_core::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_core::validator::ValidatorEntityType::from),
43            v.action_decls
44                .iter()
45                .map(cedar_policy_core::validator::ValidatorActionId::from),
46        )
47    }
48}
49
50impl From<&cedar_policy_core::validator::ValidationMode> for models::ValidationMode {
51    // PANIC SAFETY: experimental feature
52    #[allow(clippy::unimplemented)]
53    fn from(v: &cedar_policy_core::validator::ValidationMode) -> Self {
54        match v {
55            cedar_policy_core::validator::ValidationMode::Strict => models::ValidationMode::Strict,
56            cedar_policy_core::validator::ValidationMode::Permissive => {
57                models::ValidationMode::Permissive
58            }
59            #[cfg(feature = "partial-validate")]
60            cedar_policy_core::validator::ValidationMode::Partial => {
61                models::ValidationMode::Partial
62            }
63        }
64    }
65}
66
67impl From<&models::ValidationMode> for cedar_policy_core::validator::ValidationMode {
68    fn from(v: &models::ValidationMode) -> Self {
69        match v {
70            models::ValidationMode::Strict => cedar_policy_core::validator::ValidationMode::Strict,
71            models::ValidationMode::Permissive => {
72                cedar_policy_core::validator::ValidationMode::Permissive
73            }
74            #[cfg(feature = "partial-validate")]
75            models::ValidationMode::Partial => {
76                cedar_policy_core::validator::ValidationMode::Partial
77            }
78            #[cfg(not(feature = "partial-validate"))]
79            models::ValidationMode::Partial => {
80                panic!("Protobuf specifies partial validation, but `partial-validate` feature not enabled in this build")
81            }
82        }
83    }
84}
85
86// PANIC SAFETY: experimental feature
87#[allow(clippy::fallible_impl_from)]
88impl From<&cedar_policy_core::validator::ValidatorActionId> for models::ActionDecl {
89    // PANIC SAFETY: experimental feature
90    #[allow(clippy::panic)]
91    fn from(v: &cedar_policy_core::validator::ValidatorActionId) -> Self {
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_core::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            None,
123        )
124    }
125}
126
127impl From<&cedar_policy_core::validator::ValidatorEntityType> for models::EntityDecl {
128    fn from(v: &cedar_policy_core::validator::ValidatorEntityType) -> Self {
129        let name = Some(models::Name::from(v.name()));
130        let descendants = v.descendants.iter().map(models::Name::from).collect();
131        let attributes = attributes_to_model(v.attributes());
132        let tags = v.tag_type().map(models::Type::from);
133        match &v.kind {
134            cedar_policy_core::validator::ValidatorEntityTypeKind::Standard(_) => Self {
135                name,
136                descendants,
137                attributes,
138                tags,
139                enum_choices: vec![],
140            },
141            cedar_policy_core::validator::ValidatorEntityTypeKind::Enum(enum_choices) => Self {
142                name,
143                descendants,
144                attributes,
145                tags,
146                enum_choices: enum_choices.into_iter().map(ToString::to_string).collect(),
147            },
148        }
149    }
150}
151
152impl From<&models::EntityDecl> for cedar_policy_core::validator::ValidatorEntityType {
153    // PANIC SAFETY: experimental feature
154    #[allow(clippy::expect_used)]
155    fn from(v: &models::EntityDecl) -> Self {
156        let name = ast::EntityType::from(v.name.as_ref().expect("name field should exist"));
157        let descendants = v.descendants.iter().map(ast::EntityType::from);
158        match NonEmpty::collect(v.enum_choices.iter().map(SmolStr::from)) {
159            // `enum_choices` is empty, so `v` represents a standard entity type
160            None => Self::new_standard(
161                name,
162                descendants,
163                model_to_attributes(&v.attributes),
164                types::OpenTag::default(),
165                v.tags.as_ref().map(types::Type::from),
166                None,
167            ),
168            Some(enum_choices) => {
169                // `enum_choices` is not empty, so `v` represents an enumerated entity type.
170                // enumerated entity types must have no attributes or tags.
171                assert_eq!(&v.attributes, &HashMap::new());
172                assert_eq!(&v.tags, &None);
173                Self::new_enum(name.clone(), descendants, enum_choices, name.loc().cloned())
174            }
175        }
176    }
177}
178
179impl From<&models::Type> for types::Type {
180    // PANIC SAFETY: experimental feature
181    #[allow(clippy::expect_used)]
182    fn from(v: &models::Type) -> Self {
183        match v.data.as_ref().expect("data field should exist") {
184            models::r#type::Data::Prim(vt) => {
185                match models::r#type::Prim::try_from(vt.to_owned()).expect("decode should succeed")
186                {
187                    models::r#type::Prim::Bool => types::Type::primitive_boolean(),
188                    models::r#type::Prim::String => types::Type::primitive_string(),
189                    models::r#type::Prim::Long => types::Type::primitive_long(),
190                }
191            }
192            models::r#type::Data::SetElem(elty) => types::Type::Set {
193                element_type: Some(Box::new(types::Type::from(elty.as_ref()))),
194            },
195            models::r#type::Data::Entity(e) => {
196                types::Type::EntityOrRecord(types::EntityRecordKind::Entity(
197                    types::EntityLUB::single_entity(ast::EntityType::from(e)),
198                ))
199            }
200            models::r#type::Data::Record(r) => {
201                types::Type::EntityOrRecord(types::EntityRecordKind::Record {
202                    attrs: model_to_attributes(&r.attrs),
203                    open_attributes: types::OpenTag::default(),
204                })
205            }
206            models::r#type::Data::Ext(name) => types::Type::ExtensionType {
207                name: ast::Name::from(name),
208            },
209        }
210    }
211}
212
213// PANIC SAFETY: experimental feature
214#[allow(clippy::fallible_impl_from)]
215impl From<&types::Type> for models::Type {
216    // PANIC SAFETY: experimental feature
217    #[allow(clippy::expect_used, clippy::panic)]
218    fn from(v: &types::Type) -> Self {
219        match v {
220            types::Type::Never => panic!("can't encode Never type in protobuf; Never should never appear in a Schema"),
221            types::Type::True | types::Type::False => panic!("can't encode singleton boolean type in protobuf; singleton boolean types should never appear in a Schema"),
222            types::Type::Primitive { primitive_type } => match primitive_type {
223                types::Primitive::Bool => Self {
224                    data: Some(models::r#type::Data::Prim(models::r#type::Prim::Bool.into())),
225                },
226                types::Primitive::Long => Self {
227                    data: Some(models::r#type::Data::Prim(models::r#type::Prim::Long.into())),
228                },
229                types::Primitive::String => Self {
230                    data: Some(models::r#type::Data::Prim(models::r#type::Prim::String.into())),
231                },
232            },
233            types::Type::Set { element_type } => Self {
234                data: Some(models::r#type::Data::SetElem(Box::new(models::Type::from(
235                    element_type
236                        .as_ref()
237                        .expect("can't encode Set without element type in protobuf; Set-without-element-type should never appear in a Schema")
238                        .as_ref(),
239                )))),
240            },
241            types::Type::EntityOrRecord(types::EntityRecordKind::Entity(lub)) => Self {
242                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()))),
243            },
244            types::Type::EntityOrRecord(types::EntityRecordKind::Record { attrs, open_attributes }) => {
245                assert_eq!(open_attributes, &types::OpenTag::ClosedAttributes, "can't encode open record in protobuf");
246                Self {
247                    data: Some(models::r#type::Data::Record(models::r#type::Record { attrs: attributes_to_model(attrs) })),
248                }
249            }
250            types::Type::EntityOrRecord(types::EntityRecordKind::AnyEntity) => panic!("can't encode AnyEntity type in protobuf; AnyEntity should never appear in a Schema"),
251            types::Type::ExtensionType { name } => Self {
252                data: Some(models::r#type::Data::Ext(models::Name::from(name))),
253            },
254        }
255    }
256}
257
258fn model_to_attributes(v: &HashMap<String, models::AttributeType>) -> types::Attributes {
259    types::Attributes::with_attributes(v.iter().map(|(k, v)| (k.into(), v.into())))
260}
261
262fn attributes_to_model(v: &types::Attributes) -> HashMap<String, models::AttributeType> {
263    v.iter()
264        .map(|(k, v)| (k.to_string(), models::AttributeType::from(v)))
265        .collect()
266}
267
268impl From<&models::AttributeType> for types::AttributeType {
269    // PANIC SAFETY: experimental feature
270    #[allow(clippy::expect_used)]
271    fn from(v: &models::AttributeType) -> Self {
272        Self {
273            attr_type: types::Type::from(
274                v.attr_type.as_ref().expect("attr_type field should exist"),
275            ),
276            is_required: v.is_required,
277            #[cfg(feature = "extended-schema")]
278            loc: None,
279        }
280    }
281}
282
283impl From<&types::AttributeType> for models::AttributeType {
284    fn from(v: &types::AttributeType) -> Self {
285        Self {
286            attr_type: Some(models::Type::from(&v.attr_type)),
287            is_required: v.is_required,
288        }
289    }
290}