1use cedar_policy_core::entities::JSONValue;
18use serde::{Deserialize, Serialize};
19use serde_with::serde_as;
20use smol_str::SmolStr;
21use std::collections::{BTreeMap, HashMap};
22
23use crate::Result;
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
31#[serde(transparent)]
32pub struct SchemaFragment(
33 #[serde(with = "::serde_with::rust::maps_duplicate_key_is_error")]
34 pub HashMap<SmolStr, NamespaceDefinition>,
35);
36
37impl SchemaFragment {
38 pub fn from_json_value(json: serde_json::Value) -> Result<Self> {
41 serde_json::from_value(json).map_err(Into::into)
42 }
43
44 pub fn from_file(file: impl std::io::Read) -> Result<Self> {
46 serde_json::from_reader(file).map_err(Into::into)
47 }
48}
49
50#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
52#[serde_as]
53#[serde(deny_unknown_fields)]
54#[doc(hidden)]
55pub struct NamespaceDefinition {
56 #[serde(default)]
57 #[serde(with = "::serde_with::rust::maps_duplicate_key_is_error")]
58 #[serde(rename = "commonTypes")]
59 pub common_types: HashMap<SmolStr, SchemaType>,
60 #[serde(rename = "entityTypes")]
61 #[serde(with = "::serde_with::rust::maps_duplicate_key_is_error")]
62 pub entity_types: HashMap<SmolStr, EntityType>,
63 #[serde(with = "::serde_with::rust::maps_duplicate_key_is_error")]
64 pub actions: HashMap<SmolStr, ActionType>,
65}
66
67impl NamespaceDefinition {
68 pub fn new(
69 entity_types: impl IntoIterator<Item = (SmolStr, EntityType)>,
70 actions: impl IntoIterator<Item = (SmolStr, ActionType)>,
71 ) -> Self {
72 Self {
73 common_types: HashMap::new(),
74 entity_types: entity_types.into_iter().collect(),
75 actions: actions.into_iter().collect(),
76 }
77 }
78}
79
80#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
84#[serde(deny_unknown_fields)]
85pub struct EntityType {
86 #[serde(default)]
87 #[serde(rename = "memberOfTypes")]
88 pub member_of_types: Vec<SmolStr>,
89 #[serde(default)]
90 pub shape: AttributesOrContext,
91}
92
93#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
94#[serde(transparent)]
95pub struct AttributesOrContext(
96 pub SchemaType,
99);
100
101impl AttributesOrContext {
102 pub fn into_inner(self) -> SchemaType {
103 self.0
104 }
105}
106
107impl Default for AttributesOrContext {
108 fn default() -> Self {
109 Self(SchemaType::Type(SchemaTypeVariant::Record {
110 attributes: BTreeMap::new(),
111 additional_attributes: false,
112 }))
113 }
114}
115
116#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
119#[serde(deny_unknown_fields)]
120pub struct ActionType {
121 #[serde(default)]
125 pub attributes: Option<HashMap<SmolStr, JSONValue>>,
126 #[serde(default)]
127 #[serde(rename = "appliesTo")]
128 pub applies_to: Option<ApplySpec>,
129 #[serde(default)]
130 #[serde(rename = "memberOf")]
131 pub member_of: Option<Vec<ActionEntityUID>>,
132}
133
134#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
143#[serde(deny_unknown_fields)]
144pub struct ApplySpec {
145 #[serde(default)]
146 #[serde(rename = "resourceTypes")]
147 pub resource_types: Option<Vec<SmolStr>>,
148 #[serde(default)]
149 #[serde(rename = "principalTypes")]
150 pub principal_types: Option<Vec<SmolStr>>,
151 #[serde(default)]
152 pub context: AttributesOrContext,
153}
154
155#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
156#[serde(deny_unknown_fields)]
157pub struct ActionEntityUID {
158 pub id: SmolStr,
159
160 #[serde(rename = "type")]
161 #[serde(default)]
162 pub ty: Option<SmolStr>,
163}
164
165impl ActionEntityUID {
166 pub fn default_type(id: SmolStr) -> Self {
167 Self { id, ty: None }
168 }
169}
170
171impl std::fmt::Display for ActionEntityUID {
172 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
173 if let Some(ty) = &self.ty {
174 write!(f, "{}::", ty)?
175 } else {
176 write!(f, "Action::")?
177 }
178 write!(f, "\"{}\"", self.id)
179 }
180}
181
182#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
185#[serde(untagged)]
190pub enum SchemaType {
191 Type(SchemaTypeVariant),
192 TypeDef {
193 #[serde(rename = "type")]
194 type_name: SmolStr,
195 },
196}
197
198impl From<SchemaTypeVariant> for SchemaType {
199 fn from(variant: SchemaTypeVariant) -> Self {
200 Self::Type(variant)
201 }
202}
203
204#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
205#[serde(tag = "type")]
206#[serde(deny_unknown_fields)]
207pub enum SchemaTypeVariant {
208 String,
209 Long,
210 Boolean,
211 Set {
212 element: Box<SchemaType>,
213 },
214 Record {
215 #[serde(with = "serde_with::rust::maps_duplicate_key_is_error")]
216 attributes: BTreeMap<SmolStr, TypeOfAttribute>,
217 #[serde(rename = "additionalAttributes")]
218 #[serde(default = "additional_attributes_default")]
219 additional_attributes: bool,
220 },
221 Entity {
222 name: SmolStr,
223 },
224 Extension {
225 name: SmolStr,
226 },
227}
228
229pub(crate) static SCHEMA_TYPE_VARIANT_TAGS: &[&str] = &[
237 "String",
238 "Long",
239 "Boolean",
240 "Set",
241 "Record",
242 "Entity",
243 "Extension",
244];
245
246impl SchemaType {
247 pub fn is_extension(&self) -> Option<bool> {
252 match self {
253 Self::Type(SchemaTypeVariant::Extension { .. }) => Some(true),
254 Self::Type(SchemaTypeVariant::Set { element }) => element.is_extension(),
255 Self::Type(SchemaTypeVariant::Record { attributes, .. }) => {
256 attributes
257 .values()
258 .fold(Some(false), |a, e| match e.ty.is_extension() {
259 Some(true) => Some(true),
260 Some(false) => a,
261 None => None,
262 })
263 }
264 Self::Type(_) => Some(false),
265 Self::TypeDef { .. } => None,
266 }
267 }
268}
269
270#[cfg(feature = "arbitrary")]
271#[allow(clippy::panic)]
273impl<'a> arbitrary::Arbitrary<'a> for SchemaType {
274 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<SchemaType> {
275 use cedar_policy_core::ast::Name;
276 use std::collections::BTreeSet;
277
278 Ok(SchemaType::Type(match u.int_in_range::<u8>(1..=8)? {
279 1 => SchemaTypeVariant::String,
280 2 => SchemaTypeVariant::Long,
281 3 => SchemaTypeVariant::Boolean,
282 4 => SchemaTypeVariant::Set {
283 element: Box::new(u.arbitrary()?),
284 },
285 5 => {
286 let attributes = {
287 let attr_names: BTreeSet<String> = u.arbitrary()?;
288 attr_names
289 .into_iter()
290 .map(|attr_name| Ok((attr_name.into(), u.arbitrary()?)))
291 .collect::<arbitrary::Result<_>>()?
292 };
293 SchemaTypeVariant::Record {
294 attributes,
295 additional_attributes: u.arbitrary()?,
296 }
297 }
298 6 => {
299 let name: Name = u.arbitrary()?;
300 SchemaTypeVariant::Entity {
301 name: name.to_string().into(),
302 }
303 }
304 7 => SchemaTypeVariant::Extension {
305 name: "ipaddr".into(),
306 },
307 8 => SchemaTypeVariant::Extension {
308 name: "decimal".into(),
309 },
310 n => panic!("bad index: {n}"),
311 }))
312 }
313 fn size_hint(_depth: usize) -> (usize, Option<usize>) {
314 (1, None) }
316}
317
318#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Eq, PartialOrd, Ord)]
333#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
334pub struct TypeOfAttribute {
335 #[serde(flatten)]
336 pub ty: SchemaType,
337 #[serde(default = "record_attribute_required_default")]
338 pub required: bool,
339}
340
341fn additional_attributes_default() -> bool {
344 false
345}
346
347fn record_attribute_required_default() -> bool {
349 true
350}
351
352#[cfg(test)]
353mod test {
354 use crate::ValidatorSchema;
355
356 use super::*;
357
358 #[test]
359 fn test_entity_type_parser1() {
360 let user = r#"
361 {
362 "memberOfTypes" : ["UserGroup"]
363 }
364 "#;
365 let et = serde_json::from_str::<EntityType>(user).expect("Parse Error");
366 assert_eq!(et.member_of_types, vec!["UserGroup"]);
367 assert_eq!(
368 et.shape.into_inner(),
369 SchemaType::Type(SchemaTypeVariant::Record {
370 attributes: BTreeMap::new(),
371 additional_attributes: false
372 })
373 );
374 }
375
376 #[test]
377 fn test_entity_type_parser2() {
378 let src = r#"
379 { }
380 "#;
381 let et = serde_json::from_str::<EntityType>(src).expect("Parse Error");
382 assert_eq!(et.member_of_types.len(), 0);
383 assert_eq!(
384 et.shape.into_inner(),
385 SchemaType::Type(SchemaTypeVariant::Record {
386 attributes: BTreeMap::new(),
387 additional_attributes: false
388 })
389 );
390 }
391
392 #[test]
393 fn test_action_type_parser1() {
394 let src = r#"
395 {
396 "appliesTo" : {
397 "resourceTypes": ["Album"],
398 "principalTypes": ["User"]
399 },
400 "memberOf": [{"id": "readWrite"}]
401 }
402 "#;
403 let at: ActionType = serde_json::from_str(src).expect("Parse Error");
404 let spec = ApplySpec {
405 resource_types: Some(vec!["Album".into()]),
406 principal_types: Some(vec!["User".into()]),
407 context: AttributesOrContext::default(),
408 };
409 assert_eq!(at.applies_to, Some(spec));
410 assert_eq!(
411 at.member_of,
412 Some(vec![ActionEntityUID {
413 ty: None,
414 id: "readWrite".into()
415 }])
416 );
417 }
418
419 #[test]
420 fn test_action_type_parser2() {
421 let src = r#"
422 { }
423 "#;
424 let at: ActionType = serde_json::from_str(src).expect("Parse Error");
425 assert_eq!(at.applies_to, None);
426 assert!(at.member_of.is_none());
427 }
428
429 #[test]
430 fn test_schema_file_parser() {
431 let src = serde_json::json!(
432 {
433 "entityTypes": {
434
435 "User": {
436 "memberOfTypes": ["UserGroup"]
437 },
438 "Photo": {
439 "memberOfTypes": ["Album", "Account"]
440 },
441
442 "Album": {
443 "memberOfTypes": ["Album", "Account"]
444 },
445 "Account": { },
446 "UserGroup": { }
447 },
448
449 "actions": {
450 "readOnly": { },
451 "readWrite": { },
452 "createAlbum": {
453 "appliesTo" : {
454 "resourceTypes": ["Account", "Album"],
455 "principalTypes": ["User"]
456 },
457 "memberOf": [{"id": "readWrite"}]
458 },
459 "addPhotoToAlbum": {
460 "appliesTo" : {
461 "resourceTypes": ["Album"],
462 "principalTypes": ["User"]
463 },
464 "memberOf": [{"id": "readWrite"}]
465 },
466 "viewPhoto": {
467 "appliesTo" : {
468 "resourceTypes": ["Photo"],
469 "principalTypes": ["User"]
470 },
471 "memberOf": [{"id": "readOnly"}, {"id": "readWrite"}]
472 },
473 "viewComments": {
474 "appliesTo" : {
475 "resourceTypes": ["Photo"],
476 "principalTypes": ["User"]
477 },
478 "memberOf": [{"id": "readOnly"}, {"id": "readWrite"}]
479 }
480 }
481 });
482 let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
483
484 assert_eq!(schema_file.entity_types.len(), 5);
485 assert_eq!(schema_file.actions.len(), 6);
486 }
487
488 #[test]
489 fn test_parse_namespaces() {
490 let src = r#"
491 {
492 "foo::foo::bar::baz": {
493 "entityTypes": {},
494 "actions": {}
495 }
496 }"#;
497 let schema: SchemaFragment = serde_json::from_str(src).expect("Parse Error");
498 let (namespace, _descriptor) = schema.0.into_iter().next().unwrap();
499 assert_eq!(namespace, "foo::foo::bar::baz".to_string());
500 }
501
502 #[test]
503 fn test_schema_file_with_misspelled_required() {
504 let src = serde_json::json!(
505 {
506 "entityTypes": {
507 "User": {
508 "shape": {
509 "type": "Record",
510 "attributes": {
511 "favorite": {
512 "type": "Entity",
513 "name": "Photo",
514 "requiredddddd": false
515 }
516 }
517 }
518 }
519 },
520 "actions": {}
521 });
522 let schema: NamespaceDefinition = serde_json::from_value(src).unwrap();
523 println!("{:#?}", schema);
524 }
525
526 #[test]
527 fn test_schema_file_with_misspelled_field() {
528 let src = serde_json::json!(
529 {
530 "entityTypes": {
531 "User": {
532 "shape": {
533 "type": "Record",
534 "attributes": {
535 "favorite": {
536 "type": "Entity",
537 "nameeeeee": "Photo",
538 }
539 }
540 }
541 }
542 },
543 "actions": {}
544 });
545 let schema: NamespaceDefinition = serde_json::from_value(src).unwrap();
546 println!("{:#?}", schema);
547 }
548
549 #[test]
550 fn test_schema_file_with_extra_field() {
551 let src = serde_json::json!(
552 {
553 "entityTypes": {
554 "User": {
555 "shape": {
556 "type": "Record",
557 "attributes": {
558 "favorite": {
559 "type": "Entity",
560 "name": "Photo",
561 "extra": "Should not exist"
562 }
563 }
564 }
565 }
566 },
567 "actions": {}
568 });
569 let schema: NamespaceDefinition = serde_json::from_value(src).unwrap();
570 println!("{:#?}", schema);
571 }
572
573 #[test]
574 fn test_schema_file_with_misplaced_field() {
575 let src = serde_json::json!(
576 {
577 "entityTypes": {
578 "User": {
579 "shape": {
580 "memberOfTypes": [],
581 "type": "Record",
582 "attributes": {
583 "favorite": {
584 "type": "Entity",
585 "name": "Photo",
586 }
587 }
588 }
589 }
590 },
591 "actions": {}
592 });
593 let schema: NamespaceDefinition = serde_json::from_value(src).unwrap();
594 println!("{:#?}", schema);
595 }
596
597 #[test]
598 #[should_panic(expected = "UndeclaredCommonTypes({\"Entity\"})")]
602 fn schema_file_with_missing_field() {
603 let src = serde_json::json!(
604 {
605 "entityTypes": {
606 "User": {
607 "shape": {
608 "type": "Record",
609 "attributes": {
610 "favorite": {
611 "type": "Entity",
612 }
613 }
614 }
615 }
616 },
617 "actions": {}
618 });
619 let schema: NamespaceDefinition = serde_json::from_value(src).unwrap();
620 println!("{:#?}", schema);
621 TryInto::<ValidatorSchema>::try_into(schema).unwrap();
622 }
623
624 #[test]
625 #[should_panic(expected = "data did not match any variant of untagged enum SchemaType")]
629 fn schema_file_with_missing_type() {
630 let src = serde_json::json!(
631 {
632 "entityTypes": {
633 "User": {
634 "shape": { }
635 }
636 },
637 "actions": {}
638 });
639 let schema: NamespaceDefinition = serde_json::from_value(src).unwrap();
640 println!("{:#?}", schema);
641 }
642
643 #[test]
644 fn test_schema_file_with_field_from_other_type() {
645 let src = serde_json::json!(
646 {
647 "entityTypes": {
648 "User": {
649 "shape": {
650 "type": "Record",
651 "attributes": {
652 "favorite": {
653 "type": "String",
654 "name": "Photo",
658 "attributes": {},
659 "element": "",
660 }
661 }
662 }
663 }
664 },
665 "actions": {}
666 });
667 let schema: NamespaceDefinition = serde_json::from_value(src).unwrap();
668 println!("{:#?}", schema);
669 }
670
671 #[test]
672 fn schema_file_unexpected_malformed_attribute() {
673 let src = serde_json::json!(
674 {
675 "entityTypes": {
676 "User": {
677 "shape": {
678 "type": "Record",
679 "attributes": {
680 "a": {
681 "type": "Long",
682 "attributes": {
687 "b": {"foo": "bar"}
688 }
689 }
690 }
691 }
692 }
693 },
694 "actions": {}
695 });
696 let schema: NamespaceDefinition = serde_json::from_value(src).unwrap();
697 println!("{:#?}", schema);
698 }
699}
700
701#[cfg(test)]
706mod test_duplicates_error {
707 use super::*;
708
709 #[test]
710 #[should_panic(expected = "invalid entry: found duplicate key")]
711 fn namespace() {
712 let src = r#"{
713 "Foo": {
714 "entityTypes" : {},
715 "actions": {}
716 },
717 "Foo": {
718 "entityTypes" : {},
719 "actions": {}
720 }
721 }"#;
722 serde_json::from_str::<SchemaFragment>(src).unwrap();
723 }
724
725 #[test]
726 #[should_panic(expected = "invalid entry: found duplicate key")]
727 fn entity_type() {
728 let src = r#"{
729 "Foo": {
730 "entityTypes" : {
731 "Bar": {},
732 "Bar": {},
733 },
734 "actions": {}
735 }
736 }"#;
737 serde_json::from_str::<SchemaFragment>(src).unwrap();
738 }
739
740 #[test]
741 #[should_panic(expected = "invalid entry: found duplicate key")]
742 fn action() {
743 let src = r#"{
744 "Foo": {
745 "entityTypes" : {},
746 "actions": {
747 "Bar": {},
748 "Bar": {}
749 }
750 }
751 }"#;
752 serde_json::from_str::<SchemaFragment>(src).unwrap();
753 }
754
755 #[test]
756 #[should_panic(expected = "invalid entry: found duplicate key")]
757 fn common_types() {
758 let src = r#"{
759 "Foo": {
760 "entityTypes" : {},
761 "actions": { },
762 "commonTypes": {
763 "Bar": {"type": "Long"},
764 "Bar": {"type": "String"}
765 }
766 }
767 }"#;
768 serde_json::from_str::<SchemaFragment>(src).unwrap();
769 }
770
771 #[test]
772 fn record_type() {
773 let src = r#"{
774 "Foo": {
775 "entityTypes" : {
776 "Bar": {
777 "shape": {
778 "type": "Record",
779 "attributes": {
780 "Baz": {"type": "Long"},
781 "Baz": {"type": "String"}
782 }
783 }
784 }
785 },
786 "actions": { }
787 }
788 }"#;
789 serde_json::from_str::<SchemaFragment>(src).unwrap();
790 }
791}