#![allow(clippy::use_self, reason = "readability")]
use super::models;
use cedar_policy_core::ast;
use cedar_policy_core::validator::types;
use nonempty::NonEmpty;
use smol_str::SmolStr;
use std::collections::HashMap;
use std::sync::Arc;
impl From<&cedar_policy_core::validator::ValidatorSchema> for models::Schema {
fn from(v: &cedar_policy_core::validator::ValidatorSchema) -> Self {
Self {
entity_decls: v.entity_types().map(models::EntityDecl::from).collect(),
action_decls: v.action_ids().map(models::ActionDecl::from).collect(),
}
}
}
impl From<models::Schema> for cedar_policy_core::validator::ValidatorSchema {
fn from(v: models::Schema) -> Self {
Self::new(
v.entity_decls
.into_iter()
.map(cedar_policy_core::validator::ValidatorEntityType::from),
v.action_decls
.into_iter()
.map(cedar_policy_core::validator::ValidatorActionId::from),
)
}
}
impl From<&cedar_policy_core::validator::ValidationMode> for models::ValidationMode {
fn from(v: &cedar_policy_core::validator::ValidationMode) -> Self {
match v {
cedar_policy_core::validator::ValidationMode::Strict => models::ValidationMode::Strict,
cedar_policy_core::validator::ValidationMode::Permissive => {
models::ValidationMode::Permissive
}
#[cfg(feature = "partial-validate")]
cedar_policy_core::validator::ValidationMode::Partial => {
models::ValidationMode::Partial
}
}
}
}
impl From<models::ValidationMode> for cedar_policy_core::validator::ValidationMode {
fn from(v: models::ValidationMode) -> Self {
match v {
models::ValidationMode::Strict => cedar_policy_core::validator::ValidationMode::Strict,
models::ValidationMode::Permissive => {
cedar_policy_core::validator::ValidationMode::Permissive
}
#[cfg(feature = "partial-validate")]
models::ValidationMode::Partial => {
cedar_policy_core::validator::ValidationMode::Partial
}
#[cfg(not(feature = "partial-validate"))]
models::ValidationMode::Partial => {
panic!("Protobuf specifies partial validation, but `partial-validate` feature not enabled in this build")
}
}
}
}
#[expect(clippy::fallible_impl_from, reason = "experimental feature")]
impl From<&cedar_policy_core::validator::ValidatorActionId> for models::ActionDecl {
#[expect(clippy::panic, reason = "experimental feature")]
fn from(v: &cedar_policy_core::validator::ValidatorActionId) -> Self {
let ctx_attrs = match v.context() {
types::Type::Record {
attrs,
open_attributes: types::OpenTag::ClosedAttributes,
} => attrs,
ty => panic!("expected context to be a closed record, but got {ty:?}"),
};
Self {
name: Some(models::EntityUid::from(v.name())),
principal_types: v.applies_to_principals().map(models::Name::from).collect(),
resource_types: v.applies_to_resources().map(models::Name::from).collect(),
descendants: v.descendants().map(models::EntityUid::from).collect(),
context: attributes_to_model(ctx_attrs),
}
}
}
impl From<models::ActionDecl> for cedar_policy_core::validator::ValidatorActionId {
#[expect(clippy::expect_used, reason = "experimental feature")]
fn from(v: models::ActionDecl) -> Self {
Self::new(
ast::EntityUID::from(v.name.expect("name field should exist")),
v.principal_types.into_iter().map(ast::EntityType::from),
v.resource_types.into_iter().map(ast::EntityType::from),
v.descendants.into_iter().map(ast::EntityUID::from),
types::Type::Record {
attrs: model_to_attributes(v.context),
open_attributes: types::OpenTag::default(),
},
None,
)
}
}
impl From<&cedar_policy_core::validator::ValidatorEntityType> for models::EntityDecl {
fn from(v: &cedar_policy_core::validator::ValidatorEntityType) -> Self {
let name = Some(models::Name::from(v.name()));
let descendants = v.descendants.iter().map(models::Name::from).collect();
let attributes = attributes_to_model(v.attributes());
let tags = v.tag_type().map(models::Type::from);
match &v.kind {
cedar_policy_core::validator::ValidatorEntityTypeKind::Standard(_) => Self {
name,
descendants,
attributes,
tags,
enum_choices: vec![],
},
cedar_policy_core::validator::ValidatorEntityTypeKind::Enum(enum_choices) => Self {
name,
descendants,
attributes,
tags,
enum_choices: enum_choices.into_iter().map(ToString::to_string).collect(),
},
}
}
}
impl From<models::EntityDecl> for cedar_policy_core::validator::ValidatorEntityType {
#[expect(clippy::expect_used, reason = "experimental feature")]
fn from(v: models::EntityDecl) -> Self {
let name = ast::EntityType::from(v.name.expect("name field should exist"));
let descendants = v.descendants.into_iter().map(ast::EntityType::from);
match NonEmpty::collect(v.enum_choices.into_iter().map(SmolStr::from)) {
None => Self::new_standard(
name,
descendants,
model_to_attributes(v.attributes),
types::OpenTag::default(),
v.tags.map(types::Type::from),
None,
),
Some(enum_choices) => {
assert_eq!(v.attributes, HashMap::new());
assert_eq!(v.tags, None);
Self::new_enum(name, descendants, enum_choices, None)
}
}
}
}
impl From<models::Type> for types::Type {
#[expect(clippy::expect_used, reason = "experimental feature")]
fn from(v: models::Type) -> Self {
match v.data.expect("data field should exist") {
models::r#type::Data::Prim(vt) => {
match models::r#type::Prim::try_from(vt).expect("decode should succeed") {
models::r#type::Prim::Bool => types::Type::primitive_boolean(),
models::r#type::Prim::String => types::Type::primitive_string(),
models::r#type::Prim::Long => types::Type::primitive_long(),
}
}
models::r#type::Data::SetElem(elty) => types::Type::Set {
element_type: Some(Arc::new(types::Type::from(*elty))),
},
models::r#type::Data::Entity(e) => types::Type::Entity(types::EntityKind::Entity(
types::EntityLUB::single_entity(ast::EntityType::from(e)),
)),
models::r#type::Data::Record(r) => types::Type::Record {
attrs: model_to_attributes(r.attrs),
open_attributes: types::OpenTag::default(),
},
models::r#type::Data::Ext(name) => types::Type::ExtensionType {
name: ast::Name::from(name),
},
}
}
}
#[expect(clippy::fallible_impl_from, reason = "experimental feature")]
impl From<&types::Type> for models::Type {
#[expect(clippy::expect_used, clippy::panic, reason = "experimental feature")]
fn from(v: &types::Type) -> Self {
match v {
types::Type::Never => panic!("can't encode Never type in protobuf; Never should never appear in a Schema"),
types::Type::Bool(types::BoolType::True | types::BoolType::False) => panic!("can't encode singleton boolean type in protobuf; singleton boolean types should never appear in a Schema"),
types::Type::Bool(types::BoolType::AnyBool) => Self {
data: Some(models::r#type::Data::Prim(models::r#type::Prim::Bool.into())),
},
types::Type::Long => Self {
data: Some(models::r#type::Data::Prim(models::r#type::Prim::Long.into())),
},
types::Type::String => Self {
data: Some(models::r#type::Data::Prim(models::r#type::Prim::String.into())),
},
types::Type::Set { element_type } => Self {
data: Some(models::r#type::Data::SetElem(Box::new(models::Type::from(
element_type
.as_ref()
.expect("can't encode Set without element type in protobuf; Set-without-element-type should never appear in a Schema")
.as_ref(),
)))),
},
types::Type::Entity(types::EntityKind::Entity(lub)) => Self {
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()))),
},
types::Type::Record{ attrs, open_attributes } => {
assert_eq!(open_attributes, &types::OpenTag::ClosedAttributes, "can't encode open record in protobuf");
Self {
data: Some(models::r#type::Data::Record(models::r#type::Record { attrs: attributes_to_model(attrs) })),
}
}
types::Type::Entity(types::EntityKind::AnyEntity) => panic!("can't encode AnyEntity type in protobuf; AnyEntity should never appear in a Schema"),
types::Type::ExtensionType { name } => Self {
data: Some(models::r#type::Data::Ext(models::Name::from(name))),
},
}
}
}
fn model_to_attributes(v: HashMap<String, models::AttributeType>) -> types::Attributes {
types::Attributes::with_attributes(v.into_iter().map(|(k, v)| (k.into(), v.into())))
}
fn attributes_to_model(v: &types::Attributes) -> HashMap<String, models::AttributeType> {
v.iter()
.map(|(k, v)| (k.to_string(), models::AttributeType::from(v)))
.collect()
}
impl From<models::AttributeType> for types::AttributeType {
#[expect(clippy::expect_used, reason = "experimental feature")]
fn from(v: models::AttributeType) -> Self {
Self {
attr_type: types::Type::from(v.attr_type.expect("attr_type field should exist")).into(),
is_required: v.is_required,
#[cfg(feature = "extended-schema")]
loc: None,
}
}
}
impl From<&types::AttributeType> for models::AttributeType {
fn from(v: &types::AttributeType) -> Self {
Self {
attr_type: Some(models::Type::from(v.attr_type.as_ref())),
is_required: v.is_required,
}
}
}