1use crate::proto::entities::entities_model_to_api;
18
19use super::super::api;
20use super::{ast::ProtobufConversionError, models, traits};
21use prost::Message as _;
22use traits::TryValidate as _;
23
24macro_rules! fallible_conversions {
29 ( $A:ty, $A_expr:expr, $B:ty ) => {
30 impl From<&$A> for $B {
31 fn from(v: &$A) -> $B {
32 Self::from(&v.0)
33 }
34 }
35
36 impl TryFrom<$B> for $A {
37 type Error = ProtobufConversionError;
38 fn try_from(v: $B) -> Result<$A, Self::Error> {
39 Ok($A_expr(v.try_into()?))
40 }
41 }
42 };
43}
44
45fallible_conversions!(api::Entity, api::Entity, models::Entity);
48fallible_conversions!(api::EntityUid, api::EntityUid, models::EntityUid);
49fallible_conversions!(api::Entities, api::Entities, models::Entities);
50fallible_conversions!(api::Schema, api::Schema, models::Schema);
51fallible_conversions!(api::EntityTypeName, api::EntityTypeName, models::Name);
52fallible_conversions!(api::EntityNamespace, api::EntityNamespace, models::Name);
53fallible_conversions!(api::Expression, api::Expression, models::Expr);
54fallible_conversions!(api::Request, api::Request, models::Request);
55
56impl From<&api::Template> for models::TemplateBody {
59 fn from(v: &api::Template) -> Self {
60 Self::from(&v.ast)
61 }
62}
63
64impl TryFrom<models::TemplateBody> for api::Template {
65 type Error = ProtobufConversionError;
66 fn try_from(v: models::TemplateBody) -> Result<Self, Self::Error> {
67 Ok(Self::from_ast(v.try_into()?))
68 }
69}
70
71impl From<&api::Policy> for models::Policy {
72 fn from(v: &api::Policy) -> Self {
73 Self::from(&v.ast)
74 }
75}
76
77impl From<&api::PolicySet> for models::PolicySet {
78 fn from(v: &api::PolicySet) -> Self {
79 Self::from(&v.ast)
80 }
81}
82
83impl TryFrom<models::PolicySet> for api::PolicySet {
84 type Error = ProtobufConversionError;
85 fn try_from(v: models::PolicySet) -> Result<Self, Self::Error> {
86 let ast: cedar_policy_core::ast::PolicySet = v.try_into()?;
87 Ok(Self::from_ast(ast))
88 }
89}
90
91macro_rules! standard_protobuf_impl {
94 ( $api:ty, $model:ty) => {
95 impl traits::Protobuf for $api {
96 fn encode(&self) -> Result<Vec<u8>, traits::EncodeError> {
97 traits::encode_to_vec::<$model, _>(self)
98 }
99 fn decode_unchecked(buf: impl prost::bytes::Buf) -> Result<Self, traits::DecodeError> {
100 traits::try_decode::<$model, _, _>(buf)
101 }
102 }
103 };
104}
105
106standard_protobuf_impl!(api::Entity, models::Entity);
109standard_protobuf_impl!(api::Schema, models::Schema);
110standard_protobuf_impl!(api::EntityTypeName, models::Name);
111standard_protobuf_impl!(api::EntityNamespace, models::Name);
112standard_protobuf_impl!(api::Template, models::TemplateBody);
113standard_protobuf_impl!(api::Expression, models::Expr);
114standard_protobuf_impl!(api::Request, models::Request);
115
116impl traits::Protobuf for api::Entities {
119 fn encode(&self) -> Result<Vec<u8>, traits::EncodeError> {
120 traits::encode_to_vec::<models::Entities, _>(self)
121 }
122 fn decode(buf: impl prost::bytes::Buf) -> Result<Self, traits::DecodeError> {
123 let entities: Self = traits::try_decode::<models::Entities, _, _>(buf)?;
125 entities
126 .try_validate()
127 .map_err(|e| ProtobufConversionError::InvalidValue(format!("invalid: {e}")).into())
128 }
129 fn decode_unchecked(buf: impl prost::bytes::Buf) -> Result<Self, traits::DecodeError> {
130 let msg = models::Entities::decode(buf)?;
131 let core_entities = entities_model_to_api(
133 msg,
134 cedar_policy_core::entities::TCComputation::AssumeAlreadyComputed,
135 )?;
136 Ok(Self(core_entities))
137 }
138}
139
140impl traits::Protobuf for api::PolicySet {
141 fn encode(&self) -> Result<Vec<u8>, traits::EncodeError> {
142 traits::encode_to_vec::<models::PolicySet, _>(self)
143 }
144 fn decode_unchecked(buf: impl prost::bytes::Buf) -> Result<Self, traits::DecodeError> {
145 traits::try_decode::<models::PolicySet, _, Self>(buf)
146 }
147}
148
149#[cfg(test)]
150mod roundtrip_test {
151 use super::models;
152 use prost::Message as _;
153 use std::{collections::HashMap, str::FromStr};
154
155 fn roundtrip_policies(policies: crate::PolicySet) {
158 let policies_proto = models::PolicySet::from(&policies);
160 let buf = policies_proto.encode_to_vec();
162 let roundtripped_proto = models::PolicySet::decode(&buf[..])
164 .expect("Failed to deserialize PolicySet from protobuf");
165 let roundtripped = crate::PolicySet::try_from(roundtripped_proto)
167 .expect("Failed to convert from protobuf to PolicySet");
168 similar_asserts::assert_eq!(policies, roundtripped);
169 }
170
171 fn roundtrip_policies_text(text: &str) {
172 let pset = crate::PolicySet::from_str(text).expect("Failed to parse policy set");
173 roundtrip_policies(pset);
174 }
175
176 #[test]
177 fn roundtrip_policyset_with_template_link() {
178 let mut pset = crate::PolicySet::from_str(
179 r#"
180 permit(principal == ?principal, action, resource);
181 "#,
182 )
183 .expect("Failed to parse policy set");
184 pset.link(
185 crate::PolicyId::new("policy0"),
186 crate::PolicyId::new("link0"),
187 HashMap::from([(
188 crate::SlotId::principal(),
189 crate::EntityUid::from_strs("User", "alice"),
190 )]),
191 )
192 .expect("Failed to link template");
193 roundtrip_policies(pset);
194 }
195
196 #[test]
197 fn roundtrip_policyset_empty() {
198 roundtrip_policies_text("");
199 }
200
201 #[test]
202 fn roundtrip_policyset_with_static_policy() {
203 roundtrip_policies_text(
204 r#"
205 permit(principal, action, resource);
206 "#,
207 );
208 }
209
210 #[test]
211 fn roundtrip_policyset_with_multiple_static_policies() {
212 roundtrip_policies_text(
213 r#"
214 permit(principal, action, resource);
215
216 forbid(principal, action, resource) when { context.is_restricted };
217
218 permit(principal == User::"alice", action == Action::"read", resource in Folder::"shared");
219 "#,
220 );
221 }
222
223 #[test]
224 fn roundtrip_policyset_with_when_and_unless() {
225 roundtrip_policies_text(
226 r#"
227 permit(principal, action, resource)
228 when { resource.owner == principal }
229 unless { principal.suspended };
230 "#,
231 );
232 }
233
234 #[test]
235 fn roundtrip_policyset_with_annotations() {
236 roundtrip_policies_text(
237 r#"
238 @advice("allow owner access")
239 permit(principal, action == Action::"write", resource)
240 when { resource.owner == principal };
241 "#,
242 );
243 }
244
245 #[test]
246 fn roundtrip_policyset_with_multiple_template_links() {
247 let mut pset = crate::PolicySet::from_str(
248 r#"
249 permit(principal == ?principal, action, resource in ?resource);
250 "#,
251 )
252 .expect("Failed to parse policy set");
253 pset.link(
254 crate::PolicyId::new("policy0"),
255 crate::PolicyId::new("link0"),
256 HashMap::from([
257 (
258 crate::SlotId::principal(),
259 crate::EntityUid::from_strs("User", "alice"),
260 ),
261 (
262 crate::SlotId::resource(),
263 crate::EntityUid::from_strs("Folder", "shared"),
264 ),
265 ]),
266 )
267 .expect("Failed to link template");
268 pset.link(
269 crate::PolicyId::new("policy0"),
270 crate::PolicyId::new("link1"),
271 HashMap::from([
272 (
273 crate::SlotId::principal(),
274 crate::EntityUid::from_strs("User", "bob"),
275 ),
276 (
277 crate::SlotId::resource(),
278 crate::EntityUid::from_strs("Folder", "private"),
279 ),
280 ]),
281 )
282 .expect("Failed to link template");
283 roundtrip_policies(pset);
284 }
285
286 #[test]
287 fn roundtrip_policyset_with_static_and_templates() {
288 let mut pset = crate::PolicySet::from_str(
289 r#"
290 forbid(principal, action, resource) unless { context.authenticated };
291
292 permit(principal == ?principal, action, resource);
293 "#,
294 )
295 .expect("Failed to parse policy set");
296 println!("{:?}", pset);
297 pset.link(
298 crate::PolicyId::new("policy1"),
299 crate::PolicyId::new("link0"),
300 HashMap::from([(
301 crate::SlotId::principal(),
302 crate::EntityUid::from_strs("User", "admin"),
303 )]),
304 )
305 .expect("Failed to link template");
306 roundtrip_policies(pset);
307 }
308
309 #[test]
310 fn roundtrip_policyset_with_is_constraint() {
311 roundtrip_policies_text(
312 r#"
313 permit(principal is User, action, resource is Folder);
314 "#,
315 );
316 }
317
318 #[test]
319 fn roundtrip_policyset_with_is_in_constraint() {
320 roundtrip_policies_text(
321 r#"
322 permit(principal is User in Group::"admins", action, resource);
323 "#,
324 );
325 }
326
327 #[test]
328 fn roundtrip_policyset_with_action_in_set() {
329 roundtrip_policies_text(
330 r#"
331 permit(principal, action in [Action::"read", Action::"list"], resource);
332 "#,
333 );
334 }
335
336 #[test]
337 fn roundtrip_policyset_with_extension_functions() {
338 roundtrip_policies_text(
339 r#"
340 forbid(principal, action, resource)
341 when { !context.src_ip.isInRange(ip("10.0.0.0/8")) };
342 "#,
343 );
344 }
345
346 #[test]
347 fn roundtrip_policyset_with_unlinked_template() {
348 roundtrip_policies_text(
349 r#"
350 permit(principal == ?principal, action, resource);
351 "#,
352 );
353 }
354}
355
356#[cfg(test)]
357mod decode_test {
358 use crate::proto::traits::Protobuf;
359 use cool_asserts::assert_matches;
360 use std::collections::HashMap;
361 use std::str::FromStr;
362
363 fn decode_eq_decode_unchecked<T: Protobuf + PartialEq>(x: T) {
365 let buf = x.encode().expect("encode failed");
366 let checked = T::decode(&buf[..]).expect("decode failed");
367 let unchecked = T::decode_unchecked(&buf[..]).expect("decode_unchecked failed");
368 similar_asserts::assert_eq!(checked, unchecked);
369 }
370
371 #[test]
373 fn decode_random_bytes_does_not_panic() {
374 use crate::proto::traits::Protobuf;
375
376 let inputs: &[&[u8]] = &[
377 b"",
378 b"\x00",
379 b"\xff\xff\xff\xff",
380 b"not a protobuf",
381 &[0u8; 1024],
382 &{
383 let mut v = Vec::new();
384 for i in 0u8..=255 {
385 v.push(i);
386 }
387 v
388 },
389 ];
390
391 for input in inputs {
392 let _ = crate::Entity::decode(*input);
393 let _ = crate::Entities::decode(*input);
394 let _ = crate::Schema::decode(*input);
395 let _ = crate::EntityTypeName::decode(*input);
396 let _ = crate::EntityNamespace::decode(*input);
397 let _ = crate::Template::decode(*input);
398 let _ = crate::Expression::decode(*input);
399 let _ = crate::Request::decode(*input);
400 let _ = crate::PolicySet::decode(*input);
401 }
402 }
403
404 #[test]
405 fn decode_conversion_error_path() {
406 use crate::proto::traits::Protobuf;
407 let model = crate::proto::models::Entity {
410 uid: Some(crate::proto::models::EntityUid {
411 ty: Some(crate::proto::models::Name {
412 id: String::new(), path: vec![],
414 }),
415 eid: "x".to_string(),
416 }),
417 attrs: HashMap::new(),
418 ancestors: vec![],
419 tags: HashMap::new(),
420 };
421 let buf = prost::Message::encode_to_vec(&model);
422 assert_matches!(
423 crate::Entity::decode(&buf[..]),
424 Err(crate::proto::traits::DecodeError::Conversion(_))
425 );
426 }
427
428 #[test]
429 fn roundtrip_decode_unchecked_entities() {
430 let entities = crate::Entities::from_json_str(
431 r#"[
432 {"uid": {"type": "User", "id": "alice"}, "attrs": {"age": 25}, "parents": [{"type": "Group", "id": "admins"}]},
433 {"uid": {"type": "Group", "id": "admins"}, "attrs": {}, "parents": []}
434 ]"#,
435 None,
436 )
437 .expect("Failed to parse entities");
438 decode_eq_decode_unchecked::<crate::Entities>(entities);
439 }
440
441 #[test]
442 fn roundtrip_decode_unchecked_policy_set() {
443 let pset = crate::PolicySet::from_str(
444 r#"
445 permit(principal == User::"alice", action, resource);
446 forbid(principal, action, resource) when { context.restricted };
447 "#,
448 )
449 .expect("Failed to parse policy set");
450 decode_eq_decode_unchecked::<crate::PolicySet>(pset);
451 }
452
453 #[test]
454 fn roundtrip_decode_unchecked_template() {
455 let template =
456 crate::Template::from_str(r#"permit(principal == ?principal, action, resource);"#)
457 .expect("Failed to parse template");
458
459 decode_eq_decode_unchecked::<crate::Template>(template);
460 }
461
462 #[test]
463 fn roundtrip_decode_unchecked_entity() {
464 let entity = crate::Entity::from_json_value(
465 serde_json::json!({"uid": {"type": "User", "id": "bob"}, "attrs": {"active": true}, "parents": []}),
466 None,
467 )
468 .expect("Failed to parse entity");
469 decode_eq_decode_unchecked::<crate::Entity>(entity);
470 }
471
472 #[test]
473 fn roundtrip_decode_unchecked_request() {
474 let request = crate::Request::new(
475 crate::EntityUid::from_strs("User", "alice"),
476 crate::EntityUid::from_strs("Action", "read"),
477 crate::EntityUid::from_strs("Document", "doc1"),
478 crate::Context::empty(),
479 None,
480 )
481 .expect("Failed to create request");
482 decode_eq_decode_unchecked::<crate::Request>(request);
483 }
484}
485
486#[cfg(test)]
487mod encode_test {
488 use super::models;
489 use super::traits::{EncodeCheck, EncodeError, Protobuf, MAX_ENCODE_DEPTH};
490 use crate::proto::test_utils::*;
491 use crate::Expression;
492 use cedar_policy_core::ast;
493 use cool_asserts::assert_matches;
494 use std::str::FromStr;
495
496 fn deep_unary(n: usize) -> models::Expr {
502 let mut e = lit_bool(true);
503 for _ in 0..n {
504 e = not(e);
505 }
506 e
507 }
508
509 fn deep_record_expr(n: usize) -> models::Expr {
511 let mut e = lit_bool(true);
512 for _ in 0..n {
513 e = record([("k", e)]);
514 }
515 e
516 }
517
518 fn deep_if(n: usize) -> models::Expr {
520 let mut e = lit_bool(true);
521 for _ in 0..n {
522 e = if_then_else(lit_bool(true), e, lit_bool(false));
523 }
524 e
525 }
526
527 fn deep_and(n: usize) -> models::Expr {
529 let mut e = lit_bool(true);
530 for _ in 0..n {
531 e = models::Expr {
532 expr_kind: Some(models::expr::ExprKind::And(Box::new(models::expr::And {
533 left: Some(Box::new(lit_bool(true))),
534 right: Some(Box::new(e)),
535 }))),
536 };
537 }
538 e
539 }
540
541 fn deep_or(n: usize) -> models::Expr {
543 let mut e = lit_bool(false);
544 for _ in 0..n {
545 e = models::Expr {
546 expr_kind: Some(models::expr::ExprKind::Or(Box::new(models::expr::Or {
547 left: Some(Box::new(lit_bool(true))),
548 right: Some(Box::new(e)),
549 }))),
550 };
551 }
552 e
553 }
554
555 fn deep_binary(n: usize) -> models::Expr {
557 let mut e = lit_long(0);
558 for _ in 0..n {
559 e = binary(models::expr::binary_app::Op::Add, e, lit_long(1));
560 }
561 e
562 }
563
564 fn deep_ext(n: usize) -> models::Expr {
566 let mut e = lit_str("1.0");
567 for _ in 0..n {
568 e = ext_call("decimal", [e]);
569 }
570 e
571 }
572
573 fn deep_get_attr(n: usize) -> models::Expr {
575 let mut e = var(models::expr::Var::Context);
576 for _ in 0..n {
577 e = get_attr(e, "x");
578 }
579 e
580 }
581
582 fn deep_has_attr(n: usize) -> models::Expr {
584 let mut e = var(models::expr::Var::Context);
587 for _ in 0..n {
588 e = has_attr(e, "x");
589 }
590 e
591 }
592
593 fn deep_like(n: usize) -> models::Expr {
595 use models::expr::{like, ExprKind, Like};
596 let mut e = lit_str("hello");
600 for _ in 0..n {
601 e = models::Expr {
602 expr_kind: Some(ExprKind::Like(Box::new(Like {
603 expr: Some(Box::new(e)),
604 pattern: vec![
605 like::PatternElem {
606 data: Some(like::pattern_elem::Data::Wildcard(
607 like::pattern_elem::Wildcard::Unit.into(),
608 )),
609 },
610 like::PatternElem {
611 data: Some(like::pattern_elem::Data::C("x".to_string())),
612 },
613 ],
614 }))),
615 };
616 }
617 e
618 }
619
620 fn deep_is(n: usize) -> models::Expr {
622 let mut e = var(models::expr::Var::Principal);
623 for _ in 0..n {
624 e = models::Expr {
625 expr_kind: Some(models::expr::ExprKind::Is(Box::new(models::expr::Is {
626 expr: Some(Box::new(e)),
627 entity_type: Some(name("User")),
628 }))),
629 };
630 }
631 e
632 }
633
634 fn deep_set(n: usize) -> models::Expr {
636 let mut e = lit_long(1);
637 for _ in 0..n {
638 e = set([e]);
639 }
640 e
641 }
642
643 const MAX_NESTING: usize = (MAX_ENCODE_DEPTH - 1) / 2;
647
648 #[track_caller]
659 fn assert_encode_check(
660 name: &str,
661 expr: &models::Expr,
662 expect_ok: bool,
663 expect_still_decodes: bool,
664 ) {
665 if expect_ok {
666 assert!(
667 expr.check_for_encode().is_ok(),
668 "{name}: expected Ok but got MaxDepthExceeded"
669 );
670 let bytes = prost::Message::encode_to_vec(expr);
672 assert!(
673 <models::Expr as prost::Message>::decode(&bytes[..]).is_ok(),
674 "{name}: decoding failed despite depth check",
675 );
676 } else {
677 assert_matches!(
678 expr.check_for_encode(),
679 Err(EncodeError::MaxDepthExceeded),
680 "{name} did not error with MaxDepthExceeded"
681 );
682 if expect_still_decodes {
684 let bytes = prost::Message::encode_to_vec(expr);
685 assert!(
686 <models::Expr as prost::Message>::decode(&bytes[..]).is_ok(),
687 "{name}: decoding failed when MaxDepthExceeded but expected to still decode",
688 );
689 }
690 }
691 }
692
693 #[test]
694 fn depth_all_expr_variants_at_limit() {
695 let builders: &[(&str, fn(usize) -> models::Expr, usize)] = &[
696 ("unary", deep_unary, MAX_NESTING),
697 ("record", deep_record_expr, (MAX_ENCODE_DEPTH - 1) / 3),
698 ("if", deep_if, MAX_NESTING),
699 ("and", deep_and, MAX_NESTING),
700 ("or", deep_or, MAX_NESTING),
701 ("binary", deep_binary, MAX_NESTING),
702 ("ext", deep_ext, MAX_NESTING),
703 ("get_attr", deep_get_attr, MAX_NESTING),
704 ("has_attr", deep_has_attr, MAX_NESTING),
705 ("like", deep_like, MAX_NESTING),
706 ("is", deep_is, MAX_NESTING),
707 ("set", deep_set, MAX_NESTING),
708 ];
709 for (name, builder, limit) in builders {
710 let ok = builder(*limit);
711 assert_encode_check(name, &ok, true, true); let bad = builder(*limit + 1);
713 assert_encode_check(name, &bad, false, true); let bad = builder(*limit + 2);
715 assert_encode_check(name, &bad, false, true); let bad = builder(*limit + 8);
717 assert_encode_check(name, &bad, false, false); }
719 }
720
721 #[test]
722 fn depth_lit_euid_at_limit() {
723 let max_euid_nesting = MAX_ENCODE_DEPTH / 2 - 2;
727
728 let mut ok = lit_euid("User", "alice");
729 for _ in 0..max_euid_nesting {
730 ok = not(ok);
731 }
732 assert_encode_check("lit_euid_ok", &ok, true, true);
733
734 let mut bad = lit_euid("User", "alice");
735 for _ in 0..=max_euid_nesting {
736 bad = not(bad);
737 }
738 assert_encode_check("lit_euid_bad", &bad, false, true);
739 }
740
741 #[test]
742 fn depth_ext_fn_name_without_args() {
743 let target_depth = MAX_ENCODE_DEPTH - 1;
745 let nesting = (target_depth - 1) / 2;
746 let ext_no_args = models::Expr {
747 expr_kind: Some(models::expr::ExprKind::ExtApp(
748 models::expr::ExtensionFunctionApp {
749 fn_name: Some(name("decimal")),
750 args: vec![],
751 },
752 )),
753 };
754 let mut e = ext_no_args;
755 for _ in 0..nesting {
756 e = not(e);
757 }
758 assert_encode_check("ext_fn_name_no_args", &e, false, true);
759 }
760
761 #[test]
766 fn encode_expression_api_returns_error_on_deep_expr() {
767 let mut e = ast::Expr::var(ast::Var::Principal);
768 for _ in 0..=MAX_NESTING {
769 e = ast::Expr::not(e);
770 }
771 let expression = crate::Expression(e);
772 assert_matches!(expression.encode(), Err(EncodeError::MaxDepthExceeded));
773 }
774
775 #[test]
776 fn encode_policyset_with_deep_condition_fails() {
777 let mut e = ast::Expr::val(true);
778 for _ in 0..=MAX_NESTING {
779 e = ast::Expr::not(e);
780 }
781 let template = ast::Template::new(
782 ast::PolicyID::from_string("deep_policy"),
783 None,
784 ast::Annotations::new(),
785 ast::Effect::Permit,
786 ast::PrincipalConstraint::any(),
787 ast::ActionConstraint::any(),
788 ast::ResourceConstraint::any(),
789 Some(e),
790 );
791 let mut pset = ast::PolicySet::new();
792 pset.add_template(template).expect("add template");
793 let api_pset = crate::PolicySet::from_ast(pset);
794 assert_matches!(api_pset.encode(), Err(EncodeError::MaxDepthExceeded));
795 }
796
797 #[test]
798 fn encode_entities_with_deep_attr_fails() {
799 let deep_expr = deep_unary(MAX_NESTING + 1);
800 let ent = entity("User", "alice", [("deep_attr", deep_expr)]);
801 assert_matches!(ent.check_for_encode(), Err(EncodeError::MaxDepthExceeded));
802 }
803
804 #[test]
805 fn encode_entity_with_deep_tag_fails() {
806 let deep_expr = deep_unary(MAX_NESTING + 1);
807 let ent = entity_full("User", "alice", [], [], [("deep_tag", deep_expr)]);
808 assert_matches!(ent.check_for_encode(), Err(EncodeError::MaxDepthExceeded));
809 }
810
811 #[test]
812 fn encode_request_with_deep_context_fails() {
813 let deep_expr = deep_unary(MAX_NESTING + 1);
814 let req = models::Request {
815 principal: Some(entity_uid(name("User"), "alice")),
816 action: Some(entity_uid(name("Action"), "read")),
817 resource: Some(entity_uid(qualified_name("Doc", &["MyApp"]), "readme")),
818 context: [("deep".to_string(), deep_expr)].into_iter().collect(),
819 };
820 assert_matches!(req.check_for_encode(), Err(EncodeError::MaxDepthExceeded));
821 }
822
823 #[test]
824 fn encode_request_shallow_context_succeeds() {
825 let req = models::Request {
826 principal: Some(entity_uid(name("User"), "alice")),
827 action: Some(entity_uid(name("Action"), "read")),
828 resource: Some(entity_uid(qualified_name("Doc", &["MyApp"]), "readme")),
829 context: [("flag".to_string(), lit_bool(true))].into_iter().collect(),
830 };
831 assert!(req.check_for_encode().is_ok());
832 }
833
834 fn deep_set_type(n: usize) -> models::Type {
840 let mut ty = long_type();
841 for _ in 0..n {
842 ty = set_type(ty);
843 }
844 ty
845 }
846
847 fn deep_record_type(n: usize) -> models::Type {
849 let mut ty = long_type();
850 for _ in 0..n {
851 ty = record_type([("x", required(ty))]);
852 }
853 ty
854 }
855
856 #[test]
857 fn encode_schema_set_type_limit() {
858 let max_set_depth = MAX_ENCODE_DEPTH - 6;
863 let ok_schema = schema([entity_decl(
864 "Foo",
865 [("a", required(deep_set_type(max_set_depth)))],
866 )]);
867 assert!(ok_schema.check_for_encode().is_ok());
868
869 let bad_schema = schema([entity_decl(
870 "Foo",
871 [("a", required(deep_set_type(max_set_depth + 1)))],
872 )]);
873 assert_matches!(
874 bad_schema.check_for_encode(),
875 Err(EncodeError::MaxDepthExceeded)
876 );
877 let bytes = prost::Message::encode_to_vec(&bad_schema);
879 assert!(<models::Schema as prost::Message>::decode(&bytes[..]).is_ok());
880 }
881
882 #[test]
883 fn encode_schema_record_type_limit() {
884 let max_rec_depth = (MAX_ENCODE_DEPTH - 6) / 4;
890 let ok_schema = schema([entity_decl(
891 "Bar",
892 [("a", required(deep_record_type(max_rec_depth)))],
893 )]);
894 assert!(ok_schema.check_for_encode().is_ok());
895
896 let bad_schema = schema([entity_decl(
897 "Bar",
898 [("a", required(deep_record_type(max_rec_depth + 1)))],
899 )]);
900 assert_matches!(
901 bad_schema.check_for_encode(),
902 Err(EncodeError::MaxDepthExceeded)
903 );
904 let bytes = prost::Message::encode_to_vec(&bad_schema);
906 assert!(<models::Schema as prost::Message>::decode(&bytes[..]).is_ok());
907 }
908
909 #[test]
910 fn encode_schema_tag_set_type_limit() {
911 let max_tag_set = MAX_ENCODE_DEPTH - 4;
914 let ok_decl = entity_decl_full("Foo", [], [], Some(deep_set_type(max_tag_set)));
915 let ok = schema([ok_decl]);
916 assert!(ok.check_for_encode().is_ok());
917
918 let bad_decl = entity_decl_full("Foo", [], [], Some(deep_set_type(max_tag_set + 1)));
919 let bad = schema([bad_decl]);
920 assert_matches!(bad.check_for_encode(), Err(EncodeError::MaxDepthExceeded));
921 let bytes = prost::Message::encode_to_vec(&bad);
923 assert!(<models::Schema as prost::Message>::decode(&bytes[..]).is_ok());
924 }
925
926 #[test]
927 fn encode_schema_tag_record_type_limit() {
928 let max_tag_rec = (MAX_ENCODE_DEPTH - 4) / 4;
931 let ok_decl = entity_decl_full("Foo", [], [], Some(deep_record_type(max_tag_rec)));
932 let ok = schema([ok_decl]);
933 assert!(ok.check_for_encode().is_ok());
934
935 let bad_decl = entity_decl_full("Foo", [], [], Some(deep_record_type(max_tag_rec + 1)));
936 let bad = schema([bad_decl]);
937 assert_matches!(bad.check_for_encode(), Err(EncodeError::MaxDepthExceeded));
938 let bytes = prost::Message::encode_to_vec(&bad);
940 assert!(<models::Schema as prost::Message>::decode(&bytes[..]).is_ok());
941 }
942
943 #[test]
944 fn encode_schema_action_context_set_type_limit() {
945 let max_ctx_set = MAX_ENCODE_DEPTH - 6;
949 let ok_action = action_decl(
950 ("Action", "read"),
951 ["User"],
952 ["Doc"],
953 [("ctx", required(deep_set_type(max_ctx_set)))],
954 );
955 let ok = schema_full(
956 [entity_decl("User", []), entity_decl("Doc", [])],
957 [ok_action],
958 );
959 assert!(ok.check_for_encode().is_ok());
960
961 let bad_action = action_decl(
962 ("Action", "read"),
963 ["User"],
964 ["Doc"],
965 [("ctx", required(deep_set_type(max_ctx_set + 1)))],
966 );
967 let bad = schema_full(
968 [entity_decl("User", []), entity_decl("Doc", [])],
969 [bad_action],
970 );
971 assert_matches!(bad.check_for_encode(), Err(EncodeError::MaxDepthExceeded));
972 let bytes = prost::Message::encode_to_vec(&bad);
974 assert!(<models::Schema as prost::Message>::decode(&bytes[..]).is_ok());
975 }
976
977 #[test]
978 fn encode_schema_action_context_record_type_limit() {
979 let max_ctx_rec = (MAX_ENCODE_DEPTH - 6) / 4;
981 let ok_action = action_decl(
982 ("Action", "write"),
983 ["User"],
984 ["Doc"],
985 [("ctx", required(deep_record_type(max_ctx_rec)))],
986 );
987 let ok = schema_full(
988 [entity_decl("User", []), entity_decl("Doc", [])],
989 [ok_action],
990 );
991 assert!(ok.check_for_encode().is_ok());
992
993 let bad_action = action_decl(
994 ("Action", "write"),
995 ["User"],
996 ["Doc"],
997 [("ctx", required(deep_record_type(max_ctx_rec + 1)))],
998 );
999 let bad = schema_full(
1000 [entity_decl("User", []), entity_decl("Doc", [])],
1001 [bad_action],
1002 );
1003 assert_matches!(bad.check_for_encode(), Err(EncodeError::MaxDepthExceeded));
1004 let bytes = prost::Message::encode_to_vec(&bad);
1006 assert!(<models::Schema as prost::Message>::decode(&bytes[..]).is_ok());
1007 }
1008
1009 #[test]
1010 fn encode_schema_entity_type_leaf_at_limit() {
1011 let max_set_depth = MAX_ENCODE_DEPTH - 7;
1017
1018 let mut ok_ty = entity_type("User");
1020 for _ in 0..max_set_depth {
1021 ok_ty = set_type(ok_ty);
1022 }
1023 let ok_schema = schema([entity_decl("Foo", [("a", required(ok_ty))])]);
1024 assert!(ok_schema.check_for_encode().is_ok());
1025
1026 let mut bad_ty = entity_type("User");
1027 for _ in 0..=max_set_depth {
1028 bad_ty = set_type(bad_ty);
1029 }
1030 let bad_schema = schema([entity_decl("Foo", [("a", required(bad_ty))])]);
1031 assert_matches!(
1032 bad_schema.check_for_encode(),
1033 Err(EncodeError::MaxDepthExceeded)
1034 );
1035 let bytes = prost::Message::encode_to_vec(&bad_schema);
1037 assert!(<models::Schema as prost::Message>::decode(&bytes[..]).is_ok());
1038
1039 let mut ok_ext = extension_type("decimal");
1041 for _ in 0..max_set_depth {
1042 ok_ext = set_type(ok_ext);
1043 }
1044 let ok_schema = schema([entity_decl("Bar", [("b", required(ok_ext))])]);
1045 assert!(ok_schema.check_for_encode().is_ok());
1046
1047 let mut bad_ext = extension_type("decimal");
1048 for _ in 0..=max_set_depth {
1049 bad_ext = set_type(bad_ext);
1050 }
1051 let bad_schema = schema([entity_decl("Bar", [("b", required(bad_ext))])]);
1052 assert_matches!(
1053 bad_schema.check_for_encode(),
1054 Err(EncodeError::MaxDepthExceeded)
1055 );
1056 let bytes = prost::Message::encode_to_vec(&bad_schema);
1058 assert!(<models::Schema as prost::Message>::decode(&bytes[..]).is_ok());
1059 }
1060
1061 #[test]
1062 fn encode_schema_multiple_entities_one_deep_fails() {
1063 let max_set_depth = MAX_ENCODE_DEPTH - 6;
1065 let s = schema([
1066 entity_decl("Shallow", [("x", required(long_type()))]),
1067 entity_decl("Deep", [("a", required(deep_set_type(max_set_depth + 1)))]),
1068 ]);
1069 assert_matches!(s.check_for_encode(), Err(EncodeError::MaxDepthExceeded));
1070 let bytes = prost::Message::encode_to_vec(&s);
1072 assert!(<models::Schema as prost::Message>::decode(&bytes[..]).is_ok());
1073 }
1074
1075 #[test]
1076 fn encode_schema_multiple_actions_one_deep_fails() {
1077 let max_ctx_set = MAX_ENCODE_DEPTH - 6;
1078 let shallow_action = action_decl(
1079 ("Action", "read"),
1080 ["User"],
1081 ["Doc"],
1082 [("flag", required(bool_type()))],
1083 );
1084 let deep_action = action_decl(
1085 ("Action", "write"),
1086 ["User"],
1087 ["Doc"],
1088 [("ctx", required(deep_set_type(max_ctx_set + 1)))],
1089 );
1090 let s = schema_full(
1091 [entity_decl("User", []), entity_decl("Doc", [])],
1092 [shallow_action, deep_action],
1093 );
1094 assert_matches!(s.check_for_encode(), Err(EncodeError::MaxDepthExceeded));
1095 let bytes = prost::Message::encode_to_vec(&s);
1097 assert!(<models::Schema as prost::Message>::decode(&bytes[..]).is_ok());
1098 }
1099
1100 #[test]
1101 fn encode_schema_shallow_type_succeeds() {
1102 let (schema, _) =
1103 crate::Schema::from_cedarschema_str("entity User { name: String, age: Long };")
1104 .expect("parse schema");
1105 assert!(schema.encode().is_ok());
1106 }
1107
1108 #[test]
1109 fn encode_schema_mixed_nesting_succeeds() {
1110 let nested_type = record_type([
1111 ("inner_set", required(set_type(set_type(long_type())))),
1112 (
1113 "inner_rec",
1114 optional(record_type([("x", required(string_type()))])),
1115 ),
1116 ]);
1117 let s = schema([entity_decl("Complex", [("data", required(nested_type))])]);
1118 assert!(s.check_for_encode().is_ok());
1119 }
1120
1121 #[test]
1126 fn encode_shallow_expression_succeeds() {
1127 let expression = crate::Expression::from_str("1 + 2").expect("parse");
1128 assert!(expression.encode().is_ok());
1129 }
1130
1131 #[test]
1132 fn encode_at_limit_roundtrips_through_prost() {
1133 let expr = deep_unary(MAX_NESTING);
1134 assert!(expr.check_for_encode().is_ok());
1135 let mut e = ast::Expr::val(true);
1137 for _ in 0..MAX_NESTING {
1138 e = ast::Expr::not(e);
1139 }
1140 let expression = crate::Expression(e);
1141 let buf = expression.encode().expect("should encode within limit");
1142 Expression::decode(&buf[..]).expect("should decode within prost's recursion limit");
1143 }
1144}