use cedar_policy_core::{
ast::{ExprValidationError, Infallible, PolicySetValidationError, TemplateValidationError},
entities::err::EntitiesError,
validator::SchemaError,
};
use crate::api;
use super::ast::ProtobufConversionError;
#[derive(Debug, thiserror::Error)]
pub enum DecodeError {
#[error(transparent)]
Proto(prost::DecodeError),
#[error(transparent)]
Conversion(ProtobufConversionError),
}
impl From<prost::DecodeError> for DecodeError {
fn from(e: prost::DecodeError) -> Self {
Self::Proto(e)
}
}
impl From<ProtobufConversionError> for DecodeError {
fn from(e: ProtobufConversionError) -> Self {
Self::Conversion(e)
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum EncodeError {
#[error("data structure depth exceeds maximum encodable depth of {MAX_ENCODE_DEPTH}")]
MaxDepthExceeded,
#[error(transparent)]
Proto(#[from] prost::EncodeError),
}
pub const MAX_ENCODE_DEPTH: usize = 90;
pub trait EncodeCheck {
fn check_for_encode(&self) -> Result<(), EncodeError> {
self.check_for_encode_from_depth(1)
}
fn check_for_encode_from_depth(&self, init: usize) -> Result<(), EncodeError>;
}
pub trait TryValidate: Sized {
type Err: Display;
fn try_validate(self) -> Result<Self, Self::Err>;
}
mod private {
use crate::api;
pub trait Sealed {}
impl Sealed for api::PolicySet {}
impl Sealed for api::Entities {}
impl Sealed for api::Entity {}
impl Sealed for api::Schema {}
impl Sealed for api::EntityTypeName {}
impl Sealed for api::EntityNamespace {}
impl Sealed for api::Template {}
impl Sealed for api::Expression {}
impl Sealed for api::Request {}
}
pub trait Protobuf: Sized + TryValidate + private::Sealed {
fn encode(&self) -> Result<Vec<u8>, EncodeError>;
fn decode(buf: impl prost::bytes::Buf) -> Result<Self, DecodeError> {
Self::decode_unchecked(buf)?
.try_validate()
.map_err(|e| ProtobufConversionError::InvalidValue(format!("invalid: {e}")).into())
}
fn decode_unchecked(buf: impl prost::bytes::Buf) -> Result<Self, DecodeError>;
}
#[expect(
dead_code,
reason = "experimental feature, we might have use for this one in the future"
)]
pub(crate) fn encode_with_buf<M: prost::Message + EncodeCheck + for<'a> From<&'a T>, T>(
thing: &T,
buf: &mut impl prost::bytes::BufMut,
) -> Result<(), EncodeError> {
let model = M::from(thing);
model.check_for_encode()?;
model.encode(buf)?;
Ok(())
}
pub(crate) fn encode_to_vec<M: prost::Message + EncodeCheck + for<'a> From<&'a T>, T>(
thing: &T,
) -> Result<Vec<u8>, EncodeError> {
let model = M::from(thing);
model.check_for_encode()?;
Ok(model.encode_to_vec())
}
use std::{default::Default, fmt::Display};
#[expect(
dead_code,
reason = "available for types with infallible From conversions"
)]
pub(crate) fn decode<M: prost::Message + Default, T: From<M>>(
buf: impl prost::bytes::Buf,
) -> Result<T, DecodeError> {
Ok(M::decode(buf)?.into())
}
pub(crate) fn try_decode<
M: prost::Message + Default,
E: Into<ProtobufConversionError>,
T: TryFrom<M, Error = E>,
>(
buf: impl prost::bytes::Buf,
) -> Result<T, DecodeError> {
M::decode(buf)?
.try_into()
.map_err(|e: E| DecodeError::Conversion(e.into()))
}
impl TryValidate for api::PolicySet {
type Err = PolicySetValidationError;
fn try_validate(self) -> Result<Self, Self::Err> {
self.ast.try_validate().map(Into::into)
}
}
impl TryValidate for api::Entities {
type Err = EntitiesError;
fn try_validate(self) -> Result<Self, EntitiesError> {
Ok(Self(self.0.try_validate()?))
}
}
impl TryValidate for api::Entity {
type Err = EntitiesError;
fn try_validate(self) -> Result<Self, EntitiesError> {
Ok(Self(self.0.try_validate()?))
}
}
impl TryValidate for api::Schema {
type Err = SchemaError;
fn try_validate(self) -> Result<Self, SchemaError> {
Ok(Self(self.0.try_validate()?))
}
}
impl TryValidate for api::Template {
type Err = TemplateValidationError;
fn try_validate(self) -> Result<Self, TemplateValidationError> {
Ok(Self {
ast: self.ast.try_validate()?,
..self
})
}
}
impl TryValidate for api::Expression {
type Err = ExprValidationError;
fn try_validate(self) -> Result<Self, ExprValidationError> {
Ok(Self(self.0.try_validate()?))
}
}
impl TryValidate for api::Request {
type Err = Infallible;
fn try_validate(self) -> Result<Self, Infallible> {
Ok(self)
}
}
impl TryValidate for api::EntityTypeName {
type Err = Infallible;
fn try_validate(self) -> Result<Self, Infallible> {
Ok(self)
}
}
impl TryValidate for api::EntityNamespace {
type Err = Infallible;
fn try_validate(self) -> Result<Self, Infallible> {
Ok(self)
}
}
use super::models;
impl EncodeCheck for models::Expr {
#[expect(
clippy::too_many_lines,
reason = "many variants in expr and more readable that way"
)]
fn check_for_encode_from_depth(&self, init: usize) -> Result<(), EncodeError> {
let mut stack: Vec<(&Self, usize)> = vec![(self, init)];
while let Some((expr, depth)) = stack.pop() {
if depth > MAX_ENCODE_DEPTH {
return Err(EncodeError::MaxDepthExceeded);
}
if let Some(ref kind) = expr.expr_kind {
use models::expr::ExprKind;
let child_depth = depth + 2;
match kind {
ExprKind::Lit(lit) => {
if let Some(models::expr::literal::Lit::Euid(_)) = lit.lit {
let euid_name_depth = depth + 3;
if euid_name_depth > MAX_ENCODE_DEPTH {
return Err(EncodeError::MaxDepthExceeded);
}
}
}
ExprKind::Var(_) | ExprKind::Slot(_) => {}
ExprKind::If(if_expr) => {
if let Some(ref e) = if_expr.test_expr {
stack.push((e, child_depth));
}
if let Some(ref e) = if_expr.then_expr {
stack.push((e, child_depth));
}
if let Some(ref e) = if_expr.else_expr {
stack.push((e, child_depth));
}
}
ExprKind::And(bin) => {
if let Some(ref e) = bin.left {
stack.push((e, child_depth));
}
if let Some(ref e) = bin.right {
stack.push((e, child_depth));
}
}
ExprKind::Or(bin) => {
if let Some(ref e) = bin.left {
stack.push((e, child_depth));
}
if let Some(ref e) = bin.right {
stack.push((e, child_depth));
}
}
ExprKind::UApp(unary) => {
if let Some(ref e) = unary.expr {
stack.push((e, child_depth));
}
}
ExprKind::BApp(binary) => {
if let Some(ref e) = binary.left {
stack.push((e, child_depth));
}
if let Some(ref e) = binary.right {
stack.push((e, child_depth));
}
}
ExprKind::ExtApp(ext) => {
for arg in &ext.args {
stack.push((arg, child_depth));
}
if ext.fn_name.is_some() {
let name_depth = depth + 2;
if name_depth > MAX_ENCODE_DEPTH {
return Err(EncodeError::MaxDepthExceeded);
}
}
}
ExprKind::GetAttr(get) => {
if let Some(ref e) = get.expr {
stack.push((e, child_depth));
}
}
ExprKind::HasAttr(has) => {
if let Some(ref e) = has.expr {
stack.push((e, child_depth));
}
}
ExprKind::Like(like) => {
if let Some(ref e) = like.expr {
stack.push((e, child_depth));
}
if !like.pattern.is_empty() {
let pattern_depth = depth + 2;
if pattern_depth > MAX_ENCODE_DEPTH {
return Err(EncodeError::MaxDepthExceeded);
}
}
}
ExprKind::Is(is) => {
if let Some(ref e) = is.expr {
stack.push((e, child_depth));
}
if is.entity_type.is_some() {
let name_depth = depth + 2;
if name_depth > MAX_ENCODE_DEPTH {
return Err(EncodeError::MaxDepthExceeded);
}
}
}
ExprKind::Set(set) => {
for elem in &set.elements {
stack.push((elem, child_depth));
}
}
ExprKind::Record(record) => {
let record_child_depth = depth + 3;
for value in record.items.values() {
stack.push((value, record_child_depth));
}
}
}
}
}
Ok(())
}
}
impl EncodeCheck for models::Entity {
fn check_for_encode_from_depth(&self, init: usize) -> Result<(), EncodeError> {
for expr in self.attrs.values().chain(self.tags.values()) {
expr.check_for_encode_from_depth(init + 2)?;
}
Ok(())
}
}
impl EncodeCheck for models::Entities {
fn check_for_encode_from_depth(&self, init: usize) -> Result<(), EncodeError> {
for entity in &self.entities {
entity.check_for_encode_from_depth(init + 1)?;
}
Ok(())
}
}
impl EncodeCheck for models::TemplateBody {
fn check_for_encode_from_depth(&self, init: usize) -> Result<(), EncodeError> {
if let Some(ref expr) = self.non_scope_constraints {
expr.check_for_encode_from_depth(init + 1)?;
}
Ok(())
}
}
impl EncodeCheck for models::PolicySet {
fn check_for_encode_from_depth(&self, init: usize) -> Result<(), EncodeError> {
for template in &self.templates {
template.check_for_encode_from_depth(init + 1)?;
}
Ok(())
}
}
impl EncodeCheck for models::Name {
fn check_for_encode_from_depth(&self, _init: usize) -> Result<(), EncodeError> {
Ok(())
}
}
impl EncodeCheck for models::Request {
fn check_for_encode_from_depth(&self, init: usize) -> Result<(), EncodeError> {
for expr in self.context.values() {
expr.check_for_encode_from_depth(init + 2)?;
}
Ok(())
}
}
impl EncodeCheck for models::Schema {
fn check_for_encode_from_depth(&self, init: usize) -> Result<(), EncodeError> {
for entity_decl in &self.entity_decls {
for attr_type in entity_decl.attributes.values() {
check_type_depth(attr_type, init + 3)?;
}
if let Some(ref tag_type) = entity_decl.tags {
check_type_depth_inner(tag_type, init + 3)?;
}
}
for action_decl in &self.action_decls {
for attr_type in action_decl.context.values() {
check_type_depth(attr_type, init + 3)?;
}
}
Ok(())
}
}
fn check_type_depth(attr_type: &models::AttributeType, init: usize) -> Result<(), EncodeError> {
if let Some(ref ty) = attr_type.attr_type {
check_type_depth_inner(ty, init + 2)?;
}
Ok(())
}
fn check_type_depth_inner(root: &models::Type, starting_depth: usize) -> Result<(), EncodeError> {
let mut stack: Vec<(&models::Type, usize)> = vec![(root, starting_depth)];
while let Some((ty, depth)) = stack.pop() {
if depth > MAX_ENCODE_DEPTH {
return Err(EncodeError::MaxDepthExceeded);
}
if let Some(ref data) = ty.data {
use models::r#type::Data;
match data {
Data::Prim(_) => {}
Data::Entity(_) | Data::Ext(_) => {
let name_depth = depth + 1;
if name_depth > MAX_ENCODE_DEPTH {
return Err(EncodeError::MaxDepthExceeded);
}
}
Data::SetElem(inner_type) => {
stack.push((inner_type, depth + 1));
}
Data::Record(record) => {
for attr_type in record.attrs.values() {
if let Some(ref inner_ty) = attr_type.attr_type {
stack.push((inner_ty, depth + 4));
}
}
}
}
}
}
Ok(())
}