polar_core/
resource_block.rs

1use serde::{Deserialize, Serialize};
2
3use std::collections::{HashMap, HashSet};
4
5use super::error::{invalid_state, PolarError, PolarResult, ValidationError};
6use super::kb::KnowledgeBase;
7use super::rules::*;
8use super::terms::*;
9
10pub const ACTOR_UNION_NAME: &str = "Actor";
11pub const RESOURCE_UNION_NAME: &str = "Resource";
12
13// TODO(gj): round up longhand `has_permission/3` and `has_role/3` rules to incorporate their
14// referenced permissions & roles (implied & implier side) into the exhaustiveness checks.
15
16// TODO(gj): round up longhand `has_relation/3` rules to check that every declared `relation` has a
17// corresponding `has_relation/3` implementation.
18
19// TODO(gj): disallow same string to be declared as a perm/role and a relation.
20// This'll come into play for "owner"-style actor relationships.
21
22// This type is used as a pre-validation bridge between LALRPOP & Rust.
23#[derive(Clone, Debug, PartialEq, Eq)]
24pub enum Production {
25    Declaration((Term, Term)), // (Symbol, List<String> | Dict<Symbol, Symbol>)
26    ShorthandRule(Term, (Term, Option<(Term, Term)>)), // (String, (String, Option<(Symbol, String)>))
27}
28
29fn validate_relation_keyword(keyword: &Term) -> PolarResult<()> {
30    if keyword.as_symbol()?.0 != "on" {
31        return Err(ValidationError::ResourceBlock {
32            msg: format!(
33                "Unexpected relation keyword '{}'. Did you mean 'on'?",
34                keyword
35            ),
36            term: keyword.clone(),
37        }
38        .into());
39    }
40    Ok(())
41}
42
43#[derive(Debug)]
44pub enum ParsedDeclaration {
45    Roles(Term),       // List<String>
46    Permissions(Term), // List<String>
47    Relations(Term),   // Dict<Symbol, Symbol>
48}
49
50pub fn validate_parsed_declaration((name, term): (Term, Term)) -> PolarResult<ParsedDeclaration> {
51    match (name.as_symbol()?.0.as_ref(), term.value()) {
52        ("roles", Value::List(_)) => Ok(ParsedDeclaration::Roles(term)),
53        ("permissions", Value::List(_)) => Ok(ParsedDeclaration::Permissions(term)),
54        ("relations", Value::Dictionary(_)) => Ok(ParsedDeclaration::Relations(term)),
55
56        ("roles", Value::Dictionary(_)) | ("permissions", Value::Dictionary(_)) => Err(ValidationError::ResourceBlock {
57            msg: format!("Expected '{}' declaration to be a list of strings; found a dictionary", name),
58            term
59        }),
60        ("relations", Value::List(_)) => Err(ValidationError::ResourceBlock {
61            msg: "Expected 'relations' declaration to be a dictionary; found a list".to_owned(),
62            term
63        }),
64
65        (_, Value::List(_)) => Err(ValidationError::ResourceBlock {
66            msg: format!("Unexpected declaration '{}'. Did you mean for this to be 'roles = [ ... ];' or 'permissions = [ ... ];'?", name),
67            term
68        }),
69        (_, Value::Dictionary(_)) => Err(ValidationError::ResourceBlock {
70            msg: format!("Unexpected declaration '{}'. Did you mean for this to be 'relations = {{ ... }};'?", name),
71            term
72        }),
73        _ => unreachable!(),
74    }.map_err(Into::into)
75}
76
77pub fn block_type_from_keyword(keyword: Option<Term>, resource: &Term) -> PolarResult<BlockType> {
78    if let Some(keyword) = keyword {
79        match keyword.as_symbol()?.0.as_ref() {
80            "actor" => Ok(BlockType::Actor),
81            "resource" => Ok(BlockType::Resource),
82            other => Err(ValidationError::ResourceBlock {
83                msg: format!("Expected 'actor' or 'resource' but found '{}'.", other),
84                term: keyword.clone(),
85            }
86            .into()),
87        }
88    } else {
89        // TODO(gj): add `resource` into this message -- e.g., ("Expected `actor {resource}` or
90        // `resource {resource}` ...", resource=resource).
91        Err(ValidationError::ResourceBlock {
92            msg: "Expected 'actor' or 'resource' but found nothing.".to_owned(),
93            term: resource.clone(),
94        }
95        .into())
96    }
97}
98
99pub fn resource_block_from_productions(
100    keyword: Option<Term>,
101    resource: Term,
102    productions: Vec<Production>,
103) -> (ResourceBlock, Vec<PolarError>) {
104    let mut errors = vec![];
105
106    let block_type = match block_type_from_keyword(keyword, &resource) {
107        Ok(block_type) => block_type,
108        Err(e) => {
109            errors.push(e);
110            // NOTE(gj): it doesn't matter what we default to here since the (unrecoverable)
111            // `ResourceBlock` error pushed on the line above means we aren't going to make it to
112            // rule type validation, which is the only place where the `BlockType` distinction
113            // matters. I think `Resource` makes marginally more sense than `Actor` since the
114            // `BlockType` distinction will go away and there will only be `Resource` blocks once
115            // we add better union types and can specify the `Actor` union as a union instead of as
116            // `actor Blah {}` "blocks".
117            BlockType::Resource
118        }
119    };
120
121    let mut roles: Option<Term> = None;
122    let mut permissions: Option<Term> = None;
123    let mut relations: Option<Term> = None;
124    let mut shorthand_rules = vec![];
125
126    // TODO(gj): attach 'previous' to error via `related_info` section.
127    let make_error = |name: &str, _previous: &Term, new: &Term| {
128        ValidationError::ResourceBlock {
129            msg: format!(
130                "Multiple '{}' declarations in '{}' resource block.",
131                name, resource
132            ),
133            term: new.clone(),
134        }
135        .into()
136    };
137
138    for production in productions {
139        match production {
140            Production::Declaration(declaration) => {
141                match validate_parsed_declaration(declaration) {
142                    Ok(ParsedDeclaration::Roles(new)) => {
143                        // TODO(gj): combine roles _and_ push error so that we can use the declared
144                        // roles in validating shorthand rules even in the face of resource block
145                        // errors?
146                        if let Some(previous) = roles {
147                            errors.push(make_error("roles", &previous, &new));
148                        }
149                        roles = Some(new);
150                    }
151                    Ok(ParsedDeclaration::Permissions(new)) => {
152                        if let Some(previous) = permissions {
153                            errors.push(make_error("permissions", &previous, &new));
154                        }
155                        permissions = Some(new);
156                    }
157                    Ok(ParsedDeclaration::Relations(new)) => {
158                        if let Some(previous) = relations {
159                            errors.push(make_error("relations", &previous, &new));
160                        }
161                        relations = Some(new);
162                    }
163                    Err(e) => errors.push(e),
164                }
165            }
166            Production::ShorthandRule(head, body) => {
167                // TODO(gj): Warn the user on duplicate rule definitions.
168                shorthand_rules.push(ShorthandRule { head, body });
169            }
170        }
171    }
172
173    (
174        ResourceBlock {
175            block_type,
176            resource,
177            roles,
178            permissions,
179            relations,
180            shorthand_rules,
181        },
182        errors,
183    )
184}
185
186#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
187pub enum Declaration {
188    Role,
189    Permission,
190    /// `Term` is a `Symbol` that is the (registered) type of the relation. E.g., `Org` in `parent: Org`.
191    Relation(Term),
192}
193
194#[derive(Clone, Debug, Hash, PartialEq, Eq)]
195pub struct ShorthandRule {
196    /// `Term` is a `String`. E.g., `"member"` in `"member" if "owner";`.
197    pub head: Term,
198    /// The first `Term` is the 'implier' `String`, e.g., `"owner"` in `"member" if "owner";`. The
199    /// `Option` is the optional 'relation' `Symbol` and `String`, e.g., `on "parent"` in `"member"
200    /// if "owner" on "parent";`.
201    pub body: (Term, Option<(Term, Term)>),
202}
203
204impl ShorthandRule {
205    pub fn as_rule(&self, resource_name: &Term, blocks: &ResourceBlocks) -> PolarResult<Rule> {
206        let Self { head, body } = self;
207        Ok(Rule {
208            name: blocks.get_rule_name_for_declaration_in_resource_block(head, resource_name)?,
209            params: shorthand_rule_head_to_params(head, resource_name)?,
210            body: shorthand_rule_body_to_rule_body(body, resource_name, blocks)?,
211            // Copy SourceInfo from head of shorthand rule.
212            source_info: head.source_info().clone(),
213            required: false,
214        })
215    }
216}
217
218type Declarations = HashMap<Term, Declaration>;
219
220impl Declaration {
221    fn as_rule_name(&self) -> Symbol {
222        sym!(&format!("has_{}", self))
223    }
224}
225
226// TODO(gj): this will go away when we have true unions in the future.
227/// Resource blocks can either be declared as actors or resources.
228#[derive(Clone, Debug, PartialEq, Eq)]
229pub enum BlockType {
230    Actor,
231    Resource,
232}
233
234/// Successfully-parsed but not-yet-fully-validated-or-persisted resource block.
235#[derive(Clone, Debug, PartialEq, Eq)]
236pub struct ResourceBlock {
237    pub block_type: BlockType,
238    pub resource: Term,
239    pub roles: Option<Term>,
240    pub permissions: Option<Term>,
241    pub relations: Option<Term>,
242    pub shorthand_rules: Vec<ShorthandRule>,
243}
244
245#[derive(Clone, Default)]
246pub struct ResourceBlocks {
247    /// Map from resource (`Symbol`) to the declarations in that resource's block.
248    declarations: HashMap<Term, Declarations>,
249    /// Map from resource (`Symbol`) to the shorthand rules declared in that resource's block.
250    pub shorthand_rules: HashMap<Term, Vec<ShorthandRule>>,
251    /// Set of all resource block types declared as actors. Internally treated like a union type
252    /// where all declared types are members of the union.
253    pub actors: HashSet<Term>,
254    /// Set of all resource block types declared as resources. Internally treated like a union type
255    /// where all declared types are members of the union.
256    pub resources: HashSet<Term>,
257}
258
259impl ResourceBlocks {
260    pub fn new() -> Self {
261        Self {
262            declarations: HashMap::new(),
263            shorthand_rules: HashMap::new(),
264            actors: HashSet::new(),
265            resources: HashSet::new(),
266        }
267    }
268
269    pub fn clear(&mut self) {
270        self.declarations.clear();
271        self.shorthand_rules.clear();
272        self.actors.clear();
273        self.resources.clear();
274    }
275
276    fn add(
277        &mut self,
278        block_type: BlockType,
279        resource: Term,
280        declarations: Declarations,
281        shorthand_rules: Vec<ShorthandRule>,
282    ) -> Vec<PolarError> {
283        let mut errors = vec![];
284
285        // Merge existing declarations if we are reopening a resource block, otherwise add new
286        if let Some(existing) = self.declarations.get_mut(&resource) {
287            for (key, new) in declarations {
288                if let Some(existing) = existing.insert(key.clone(), new.clone()) {
289                    if existing != new {
290                        errors.push(
291                            ValidationError::DuplicateResourceBlockDeclaration {
292                                resource: resource.clone(),
293                                declaration: key.clone(),
294                                existing,
295                                new: new.clone(),
296                            }
297                            .into(),
298                        );
299                    }
300                }
301            }
302        } else {
303            // or insert a new set of declarations for the resource type
304            self.declarations.insert(resource.clone(), declarations);
305        }
306
307        // Merge existing shorthand rules if we are reopening a resource block, otherwise add new
308        self.shorthand_rules
309            .entry(resource.clone())
310            .and_modify(|existing| existing.extend(shorthand_rules.clone()))
311            .or_insert(shorthand_rules);
312
313        match block_type {
314            BlockType::Actor => {
315                self.actors.insert(resource.clone());
316                self.resources.insert(resource)
317            }
318            BlockType::Resource => self.resources.insert(resource),
319        };
320
321        errors
322    }
323
324    /// Look up `declaration` in `resource` block.
325    ///
326    /// Invariant: `resource` _must_ exist.
327    fn get_declaration_in_resource_block(
328        &self,
329        declaration: &Term,
330        resource_name: &Term,
331    ) -> PolarResult<&Declaration> {
332        let maybe_declarations = self.declarations.get(resource_name);
333        let maybe_declaration = maybe_declarations.and_then(|ds| ds.get(declaration));
334        if let Some(declaration) = maybe_declaration {
335            Ok(declaration)
336        } else {
337            Err(ValidationError::ResourceBlock {
338                msg: format!("Undeclared term {} referenced in rule in '{}' resource block. Did you mean to declare it as a role, permission, or relation?", declaration, resource_name),
339                term: declaration.clone(),
340            }.into())
341        }
342    }
343
344    /// Look up `relation` in `resource` block and return its type.
345    pub fn get_relation_type_in_resource_block(
346        &self,
347        relation: &Term,
348        resource: &Term,
349    ) -> PolarResult<&Term> {
350        let declaration = self.get_declaration_in_resource_block(relation, resource)?;
351        if let Declaration::Relation(related_type) = declaration {
352            Ok(related_type)
353        } else {
354            invalid_state(format!("Expected Relation; got: {:?}", declaration))
355        }
356    }
357
358    /// Look up `declaration` in `resource` block and return the appropriate rule name for
359    /// rewriting.
360    fn get_rule_name_for_declaration_in_resource_block(
361        &self,
362        declaration: &Term,
363        resource_name: &Term,
364    ) -> PolarResult<Symbol> {
365        Ok(self
366            .get_declaration_in_resource_block(declaration, resource_name)?
367            .as_rule_name())
368    }
369
370    /// Traverse from `resource` block to a related resource block via `relation`, then look up
371    /// `declaration` in the related block and return the appropriate rule name for rewriting.
372    fn get_rule_name_for_declaration_in_related_resource_block(
373        &self,
374        declaration: &Term,
375        relation: &Term,
376        resource: &Term,
377    ) -> PolarResult<Symbol> {
378        let related_block = self.get_relation_type_in_resource_block(relation, resource)?;
379
380        if let Some(declarations) = self.declarations.get(related_block) {
381            if let Some(declaration) = declarations.get(declaration) {
382                Ok(declaration.as_rule_name())
383            } else {
384                Err(ValidationError::ResourceBlock {
385                    msg: format!("{}: Term {} not declared in related resource block '{}'. Did you mean to declare it as a role, permission, or relation in the '{}' resource block?", resource, declaration, related_block, related_block),
386                    term: declaration.clone(),
387                }.into())
388            }
389        } else {
390            Err(ValidationError::ResourceBlock {
391                msg: format!("{}: Relation {} in rule body `{} on {}` has type '{}', but no such resource block exists. Try declaring one: `resource {} {{}}`", resource, relation, declaration, relation, related_block, related_block),
392                term: related_block.clone(),
393            }.into())
394        }
395    }
396
397    pub fn declarations(&self) -> &HashMap<Term, Declarations> {
398        &self.declarations
399    }
400
401    pub fn has_roles(&self) -> bool {
402        let mut declarations = self.declarations().values().flat_map(HashMap::values);
403        declarations.any(|d| matches!(d, Declaration::Role))
404    }
405
406    pub fn relation_tuples(&self) -> Vec<(&Term, &Term, &Term)> {
407        let mut tuples = vec![];
408        for (object, declarations) in self.declarations() {
409            for (name, declaration) in declarations {
410                if let Declaration::Relation(subject) = declaration {
411                    tuples.push((subject, name, object));
412                }
413            }
414        }
415        tuples
416    }
417}
418
419// TODO(gj): build up errors but keep on truckin'.
420fn index_declarations(
421    roles: Option<Term>,
422    permissions: Option<Term>,
423    relations: Option<Term>,
424    resource: &Term,
425) -> PolarResult<HashMap<Term, Declaration>> {
426    let mut declarations = HashMap::new();
427
428    if let Some(roles) = roles {
429        for role in roles.as_list()? {
430            if let Some(existing) = declarations.insert(role.clone(), Declaration::Role) {
431                return Err(ValidationError::DuplicateResourceBlockDeclaration {
432                    resource: resource.clone(),
433                    declaration: role.clone(),
434                    existing,
435                    new: Declaration::Role,
436                }
437                .into());
438            }
439        }
440    }
441
442    if let Some(permissions) = permissions {
443        for permission in permissions.as_list()? {
444            if let Some(existing) = declarations.insert(permission.clone(), Declaration::Permission)
445            {
446                return Err(ValidationError::DuplicateResourceBlockDeclaration {
447                    resource: resource.clone(),
448                    declaration: permission.clone(),
449                    existing,
450                    new: Declaration::Permission,
451                }
452                .into());
453            }
454        }
455    }
456
457    if let Some(relations) = relations {
458        for (relation, relation_type) in &relations.as_dict()?.fields {
459            // Stringify relation so that we can index into the declarations map with a string
460            // reference to the relation. E.g., relation `creator: User` gets stored as
461            // `"creator" => Relation(User)` so that when we encounter a shorthand rule
462            // `"admin" if "creator";` we can easily look up what type of declaration `"creator"`
463            // is.
464            let stringified_relation = relation_type.clone_with_value(value!(relation.0.as_str()));
465            let declaration = Declaration::Relation(relation_type.clone());
466
467            if let Some(existing) =
468                declarations.insert(stringified_relation.clone(), declaration.clone())
469            {
470                return Err(ValidationError::DuplicateResourceBlockDeclaration {
471                    resource: resource.clone(),
472                    declaration: stringified_relation,
473                    existing,
474                    new: declaration,
475                }
476                .into());
477            }
478        }
479    }
480    Ok(declarations)
481}
482
483fn resource_name_as_var(resource_name: &Term, related: bool) -> PolarResult<Value> {
484    let name = &resource_name.as_symbol()?.0;
485    let mut lowercased = name.to_lowercase();
486
487    // If the resource's name is already lowercase, append "_instance" to distinguish the variable
488    // name from the resource's name. In most cases, the resource name will not be lowercase (e.g.,
489    // `Organization` or `RepositorySettings`).
490    if &lowercased == name {
491        lowercased += "_instance";
492    }
493
494    // If the `related` flag is specified, prepend "related_" to distinguish the variable name for
495    // the *related* resource from the variable name for the relatee resource. Without this logic,
496    // resources in a recursive relationship will have the same variable name.
497    if related {
498        lowercased.insert_str(0, "related_");
499    }
500
501    Ok(value!(sym!(lowercased)))
502}
503
504/// Turn a shorthand rule body into an `And`-wrapped call (for a local rule) or pair of calls (for
505/// a cross-resource rule).
506fn shorthand_rule_body_to_rule_body(
507    (implier, relation): &(Term, Option<(Term, Term)>),
508    resource_name: &Term,
509    blocks: &ResourceBlocks,
510) -> PolarResult<Term> {
511    // Create a variable derived from the current block's resource name. E.g., if we're in the
512    // `Repo` resource block, the variable name will be `repo`.
513    let resource_var = implier.clone_with_value(resource_name_as_var(resource_name, false)?);
514
515    // The actor variable will always be named `actor`.
516    let actor_var = implier.clone_with_value(value!(sym!("actor")));
517
518    // If there's a relation, e.g., `if <implier> <keyword> <relation>`...
519    if let Some((keyword, relation)) = relation {
520        // ...then we need to validate the keyword...
521        validate_relation_keyword(keyword)?;
522
523        // ...and then link the rewritten `<implier>` and `<relation>` rules via a shared variable.
524        // To be clever, we'll name the variable according to the type of the relation, e.g., if
525        // the declared relation is `parent: Org` we'll name the variable `org`.
526        let relation_type = blocks.get_relation_type_in_resource_block(relation, resource_name)?;
527        let relation_type_var =
528            relation.clone_with_value(resource_name_as_var(relation_type, true)?);
529
530        // For the rewritten `<relation>` call, the rule name will always be `has_relation` and the
531        // arguments, in order, will be: the shared variable we just created above, the
532        // `<relation>` string, and the resource variable we created at the top of the function.
533        // E.g., `vec![org, "parent", repo]`.
534        let relation_call = relation.clone_with_value(value!(Call {
535            name: sym!("has_relation"),
536            args: vec![relation_type_var.clone(), relation.clone(), resource_var],
537            kwargs: None
538        }));
539
540        // To get the rule name for the rewritten `<implier>` call, we need to figure out what type
541        // (role, permission, or relation) `<implier>` is declared as _in the resource block
542        // related to the current resource block via `<relation>`_. That is, given
543        // `resource Repo { roles=["writer"]; relations={parent:Org}; "writer" if "owner" on "parent"; }`,
544        // we need to find out whether `"owner"` is declared as a role, permission, or relation in
545        // the `Org` resource block. The args for the rewritten `<implier>` call are, in order: the
546        // actor variable, the `<implier>` string, and the shared variable we created above for the
547        // related type.
548        let implier_call = implier.clone_with_value(value!(Call {
549            name: blocks.get_rule_name_for_declaration_in_related_resource_block(
550                implier,
551                relation,
552                resource_name
553            )?,
554            args: vec![actor_var, implier.clone(), relation_type_var],
555            kwargs: None
556        }));
557
558        // Wrap the rewritten `<relation>` and `<implier>` calls in an `And`.
559        Ok(implier.clone_with_value(value!(op!(And, relation_call, implier_call))))
560    } else {
561        // If there's no `<relation>` (e.g., `... if "writer";`), we're dealing with a local rule,
562        // and the rewriting process is a bit simpler. To get the appropriate rule name, we look up
563        // the declared type (role, permission, or relation) of `<implier>` in the current resource
564        // block. The call's args are, in order: the actor variable, the `<implier>` string, and
565        // the resource variable. E.g., `vec![actor, "writer", repo]`.
566        let implier_call = implier.clone_with_value(value!(Call {
567            name: blocks.get_rule_name_for_declaration_in_resource_block(implier, resource_name)?,
568            args: vec![actor_var, implier.clone(), resource_var],
569            kwargs: None
570        }));
571
572        // Wrap the rewritten `<implier>` call in an `And`.
573        Ok(implier.clone_with_value(value!(op!(And, implier_call))))
574    }
575}
576
577/// Turn a shorthand rule head into a trio of params that go in the head of the rewritten rule.
578fn shorthand_rule_head_to_params(head: &Term, resource: &Term) -> PolarResult<Vec<Parameter>> {
579    let resource_name = &resource.as_symbol()?.0;
580    let params = vec![
581        Parameter {
582            parameter: head.clone_with_value(value!(sym!("actor"))),
583            specializer: Some(head.clone_with_value(value!(pattern!(instance!(ACTOR_UNION_NAME))))),
584        },
585        Parameter {
586            parameter: head.clone(),
587            specializer: None,
588        },
589        Parameter {
590            parameter: head.clone_with_value(resource_name_as_var(resource, false)?),
591            specializer: Some(
592                resource.clone_with_value(value!(pattern!(instance!(resource_name)))),
593            ),
594        },
595    ];
596    Ok(params)
597}
598
599impl ResourceBlock {
600    pub fn add_to_kb(self, kb: &mut KnowledgeBase) -> Vec<PolarError> {
601        let mut errors = vec![];
602        // Check that resource block's resource has been registered as a class.
603        errors.extend(kb.get_registered_class(&self.resource).err());
604
605        let ResourceBlock {
606            block_type,
607            resource,
608            roles,
609            permissions,
610            relations,
611            shorthand_rules,
612        } = self;
613
614        match index_declarations(roles, permissions, relations, &resource) {
615            Ok(declarations) => {
616                errors.extend(kb.resource_blocks.add(
617                    block_type,
618                    resource,
619                    declarations,
620                    shorthand_rules,
621                ));
622            }
623            Err(e) => errors.push(e),
624        }
625
626        errors
627    }
628}
629
630#[cfg(test)]
631mod tests {
632    use std::collections::HashSet;
633
634    use permutohedron::Heap;
635
636    use super::*;
637    use crate::error::{
638        ErrorKind::{Runtime, Validation},
639        RuntimeError, ValidationError,
640    };
641    use crate::events::QueryEvent;
642    use crate::parser::{parse_lines, Line};
643    use crate::polar::Polar;
644    use crate::sources::Source;
645
646    #[track_caller]
647    fn expect_error(p: &Polar, policy: &str, expected: &str) {
648        let error = p.load_str(policy).unwrap_err().unwrap_validation();
649        let msg = match error {
650            ValidationError::ResourceBlock { msg, .. } => msg,
651            ValidationError::UnregisteredClass { .. }
652            | ValidationError::DuplicateResourceBlockDeclaration { .. } => error.to_string(),
653            _ => panic!("Unexpected error: {}", error),
654        };
655        assert!(msg.contains(expected));
656    }
657
658    #[test]
659    fn test_resource_block_rewrite_shorthand_rules_with_lowercase_resource_specializer() {
660        let repo_resource = term!(sym!("repo"));
661        let repo_roles = term!(["reader"]);
662        let repo_relations = term!(btreemap! { sym!("parent") => term!(sym!("org")) });
663        let repo_declarations =
664            index_declarations(Some(repo_roles), None, Some(repo_relations), &repo_resource);
665
666        let org_resource = term!(sym!("org"));
667        let org_roles = term!(["member"]);
668        let org_declarations = index_declarations(Some(org_roles), None, None, &org_resource);
669
670        let mut blocks = ResourceBlocks::new();
671        blocks.add(
672            BlockType::Resource,
673            repo_resource,
674            repo_declarations.unwrap(),
675            vec![],
676        );
677        blocks.add(
678            BlockType::Resource,
679            org_resource,
680            org_declarations.unwrap(),
681            vec![],
682        );
683        let shorthand_rule = ShorthandRule {
684            head: term!("reader"),
685            body: (term!("member"), Some((term!(sym!("on")), term!("parent")))),
686        };
687        let rewritten_role_role = shorthand_rule
688            .as_rule(&term!(sym!("repo")), &blocks)
689            .unwrap();
690        assert_eq!(
691            rewritten_role_role.to_string(),
692            format!("has_role(actor: {}{{}}, \"reader\", repo_instance: repo{{}}) if has_relation(related_org_instance, \"parent\", repo_instance) and has_role(actor, \"member\", related_org_instance);", ACTOR_UNION_NAME),
693        );
694    }
695
696    #[test]
697    fn test_resource_block_rewrite_shorthand_rules_with_recursive_relation() {
698        let resource = term!(sym!("Dir"));
699        let permissions = term!(["read"]);
700        let relations = term!(btreemap! { sym!("parent") => resource.clone() });
701        let declarations = index_declarations(None, Some(permissions), Some(relations), &resource);
702
703        let mut blocks = ResourceBlocks::new();
704        blocks.add(
705            BlockType::Resource,
706            resource.clone(),
707            declarations.unwrap(),
708            vec![],
709        );
710
711        let shorthand_rule = ShorthandRule {
712            head: term!("read"),
713            body: (term!("read"), Some((term!(sym!("on")), term!("parent")))),
714        };
715        let rewritten_role_role = shorthand_rule.as_rule(&resource, &blocks).unwrap();
716
717        assert_eq!(
718            rewritten_role_role.to_string(),
719            format!("has_permission(actor: {}{{}}, \"read\", dir: Dir{{}}) if has_relation(related_dir, \"parent\", dir) and has_permission(actor, \"read\", related_dir);", ACTOR_UNION_NAME),
720        );
721    }
722
723    #[test]
724    fn test_resource_block_local_rewrite_shorthand_rules() {
725        let resource = term!(sym!("Org"));
726        let roles = term!(["owner", "member"]);
727        let permissions = term!(["invite", "create_repo"]);
728        let declarations = index_declarations(Some(roles), Some(permissions), None, &resource);
729        let mut blocks = ResourceBlocks::new();
730        blocks.add(BlockType::Resource, resource, declarations.unwrap(), vec![]);
731        let shorthand_rule = ShorthandRule {
732            head: term!("member"),
733            body: (term!("owner"), None),
734        };
735        let rewritten_role_role = shorthand_rule
736            .as_rule(&term!(sym!("Org")), &blocks)
737            .unwrap();
738        assert_eq!(
739            rewritten_role_role.to_string(),
740            format!("has_role(actor: {}{{}}, \"member\", org: Org{{}}) if has_role(actor, \"owner\", org);", ACTOR_UNION_NAME),
741        );
742
743        let shorthand_rule = ShorthandRule {
744            head: term!("invite"),
745            body: (term!("owner"), None),
746        };
747        let rewritten_permission_role = shorthand_rule
748            .as_rule(&term!(sym!("Org")), &blocks)
749            .unwrap();
750        assert_eq!(
751            rewritten_permission_role.to_string(),
752            format!("has_permission(actor: {}{{}}, \"invite\", org: Org{{}}) if has_role(actor, \"owner\", org);", ACTOR_UNION_NAME),
753        );
754
755        let shorthand_rule = ShorthandRule {
756            head: term!("create_repo"),
757            body: (term!("invite"), None),
758        };
759        let rewritten_permission_permission = shorthand_rule
760            .as_rule(&term!(sym!("Org")), &blocks)
761            .unwrap();
762        assert_eq!(
763            rewritten_permission_permission.to_string(),
764            format!("has_permission(actor: {}{{}}, \"create_repo\", org: Org{{}}) if has_permission(actor, \"invite\", org);", ACTOR_UNION_NAME),
765        );
766    }
767
768    #[test]
769    fn test_resource_block_nonlocal_rewrite_shorthand_rules() {
770        let repo_resource = term!(sym!("Repo"));
771        let repo_roles = term!(["reader"]);
772        let repo_relations = term!(btreemap! { sym!("parent") => term!(sym!("Org")) });
773        let repo_declarations =
774            index_declarations(Some(repo_roles), None, Some(repo_relations), &repo_resource);
775        let org_resource = term!(sym!("Org"));
776        let org_roles = term!(["member"]);
777        let org_declarations = index_declarations(Some(org_roles), None, None, &org_resource);
778        let mut blocks = ResourceBlocks::new();
779        blocks.add(
780            BlockType::Resource,
781            repo_resource,
782            repo_declarations.unwrap(),
783            vec![],
784        );
785        blocks.add(
786            BlockType::Resource,
787            org_resource,
788            org_declarations.unwrap(),
789            vec![],
790        );
791        let shorthand_rule = ShorthandRule {
792            head: term!("reader"),
793            body: (term!("member"), Some((term!(sym!("on")), term!("parent")))),
794        };
795        let rewritten_role_role = shorthand_rule
796            .as_rule(&term!(sym!("Repo")), &blocks)
797            .unwrap();
798        assert_eq!(
799            rewritten_role_role.to_string(),
800            format!("has_role(actor: {}{{}}, \"reader\", repo: Repo{{}}) if has_relation(related_org, \"parent\", repo) and has_role(actor, \"member\", related_org);", ACTOR_UNION_NAME),
801        );
802    }
803
804    #[test]
805    fn test_resource_block_resource_must_be_registered() {
806        let p = Polar::new();
807        let valid_policy = "resource Org{}";
808        expect_error(&p, valid_policy, "Unregistered class: Org");
809        p.register_constant(sym!("Org"), term!("unimportant"))
810            .unwrap();
811        assert!(p.load_str(valid_policy).is_ok());
812    }
813
814    #[test]
815    fn test_resource_block_declarations_spread_over_multiple_resource_blocks() {
816        let p = Polar::new();
817
818        let repo_instance = ExternalInstance {
819            instance_id: 1,
820            constructor: None,
821            repr: None,
822            class_repr: None,
823            class_id: None,
824        };
825        let repo_term = term!(Value::ExternalInstance(repo_instance.clone()));
826        let repo_name = sym!("Repo");
827        p.register_constant(repo_name.clone(), repo_term).unwrap();
828        p.register_mro(repo_name, vec![repo_instance.instance_id])
829            .unwrap();
830
831        // Use declarations from one resource block in shorthand rules in another.
832        let valid_policy = r#"
833            resource Repo {
834              relations = { readable_parent: Repo };
835              roles = ["reader"];
836              permissions = ["read"];
837
838              "write" if "writer";
839              "write" if "write" on "writable_parent";
840            }
841
842            resource Repo {
843              relations = { writable_parent: Repo };
844              roles = ["writer"];
845              permissions = ["write"];
846
847              "read" if "reader";
848              "read" if "read" on "readable_parent";
849            }
850
851            has_role(_: Actor, _: String, _: Resource);
852            has_relation(subject: Repo, "writable_parent", object: Repo) if
853                object.parent_id = subject.id;
854            has_relation(subject: Repo, "readable_parent", object: Repo) if
855                object.parent_id = subject.id;
856        "#;
857
858        p.load_str(valid_policy).unwrap();
859        // Create explicit scope to allow the RWLock obtained from kb.read() to
860        // be dropped explicitly and independently of the function scope.
861        {
862            let blocks = &p.kb.read().unwrap().resource_blocks;
863            let declarations = blocks.declarations.get(&term!(sym!("Repo"))).unwrap();
864            assert_eq!(declarations.len(), 6);
865            let shorthand_rules = blocks.shorthand_rules.get(&term!(sym!("Repo"))).unwrap();
866            assert_eq!(shorthand_rules.len(), 4);
867        }
868        p.clear_rules();
869
870        // Raise a validation error if shorthand rules reference declarations
871        // not found in any matching block
872        let invalid_policy = r#"
873            resource Repo {
874                permissions = ["write"];
875            }
876
877            resource Repo {
878                roles = ["reader"];
879                "read" if "reader";
880            }
881
882            has_role(actor: Actor, _role: String, repo: Repo) if
883                repo in actor.repos;
884        "#;
885        expect_error(
886            &p,
887            invalid_policy,
888            r#"Undeclared term "read" referenced in rule in 'Repo' resource block"#,
889        );
890
891        // Multiple blocks can declare distinct roles/permissions/relations.
892        let valid_policy = r#"
893            resource Repo {
894              relations = { readable_parent: Repo };
895              roles = ["reader"];
896              permissions = ["read"];
897
898              "read" if "reader";
899              "read" if "read" on "readable_parent";
900            }
901
902            resource Repo {
903              relations = { writable_parent: Repo };
904              roles = ["writer"];
905              permissions = ["write"];
906
907              "write" if "writer";
908              "write" if "write" on "writable_parent";
909            }
910
911            has_role(_: Actor, _: String, _: Resource);
912            has_relation(subject: Repo, "writable_parent", object: Repo) if
913                object.parent_id = subject.id;
914            has_relation(subject: Repo, "readable_parent", object: Repo) if
915                object.parent_id = subject.id;
916        "#;
917
918        p.load_str(valid_policy).unwrap();
919        // Create explicit scope to allow the RWLock obtained from kb.read() to
920        // be dropped explicitly and independently of the function scope.
921        {
922            let blocks = &p.kb.read().unwrap().resource_blocks;
923            let declarations = blocks.declarations.get(&term!(sym!("Repo"))).unwrap();
924            assert_eq!(declarations.len(), 6);
925            let shorthand_rules = blocks.shorthand_rules.get(&term!(sym!("Repo"))).unwrap();
926            assert_eq!(shorthand_rules.len(), 4);
927        }
928        p.clear_rules();
929
930        // Duplicate declarations are fine if they're used in multiple blocks.
931        let valid_policy = r#"
932            resource Repo {
933              relations = { readable_parent: Repo, writable_parent: Repo };
934              roles = ["reader", "writer"];
935              permissions = ["read", "write"];
936
937              "read" if "reader";
938              "write" if "writer";
939
940              "read" if "read" on "readable_parent";
941              "write" if "write" on "writable_parent";
942            }
943
944            resource Repo {
945              relations = { readable_parent: Repo, writable_parent: Repo };
946              roles = ["reader", "writer"];
947              permissions = ["read", "write"];
948
949              "reader" if "writer";
950
951              "read" if "reader" on "readable_parent";
952              "write" if "writer" on "writable_parent";
953            }
954
955            has_role(_: Actor, _: String, _: Resource);
956            has_relation(subject: Repo, "writable_parent", object: Repo) if
957                object.parent_id = subject.id;
958            has_relation(subject: Repo, "readable_parent", object: Repo) if
959                object.parent_id = subject.id;
960        "#;
961        p.load_str(valid_policy).unwrap();
962        {
963            let blocks = &p.kb.read().unwrap().resource_blocks;
964            let declarations = blocks.declarations.get(&term!(sym!("Repo"))).unwrap();
965            assert_eq!(declarations.len(), 6);
966            let shorthand_rules = blocks.shorthand_rules.get(&term!(sym!("Repo"))).unwrap();
967            assert_eq!(shorthand_rules.len(), 7);
968        }
969    }
970
971    #[test]
972    fn test_resource_block_declarations_overwriting() {
973        let p = Polar::new();
974
975        let repo_instance = ExternalInstance {
976            instance_id: 1,
977            constructor: None,
978            repr: None,
979            class_repr: None,
980            class_id: None,
981        };
982        let repo_term = term!(Value::ExternalInstance(repo_instance.clone()));
983        let repo_name = sym!("Repo");
984        p.register_constant(repo_name.clone(), repo_term).unwrap();
985        p.register_mro(repo_name, vec![repo_instance.instance_id])
986            .unwrap();
987
988        // validate overwriting declarations of the same type is ok
989        let valid_policy = r#"
990            resource Repo {
991                roles = ["reader"];
992            }
993
994            resource Repo {
995                roles = ["reader"];
996            }
997
998            has_role(actor: Actor, _role: String, repo: Repo) if
999               repo in actor.repos;
1000        "#;
1001        assert!(p.load_str(valid_policy).is_ok());
1002        p.clear_rules();
1003
1004        // validate overwriting of different types throws error
1005        let invalid_policy = r#"
1006            resource Repo {
1007                roles = ["reader"];
1008            }
1009
1010            resource Repo {
1011                permissions = ["reader"];
1012            }
1013
1014            has_role(actor: Actor, _role: String, repo: Repo) if
1015               repo in actor.repos;
1016        "#;
1017
1018        expect_error(
1019            &p,
1020            invalid_policy,
1021            r#"Cannot overwrite existing role declaration "reader" in resource Repo with permission"#,
1022        );
1023
1024        // validate overwriting of different types throws error
1025        let invalid_policy = r#"
1026            resource Repo {
1027                roles = ["reader"];
1028            }
1029
1030            resource Repo {
1031                relations = { reader: Repo };
1032            }
1033
1034            has_role(actor: Actor, _role: String, repo: Repo) if
1035                repo in actor.repos;
1036            has_relation(subject: Repo, "reader", object: Repo) if
1037                object.parent_id = subject.id;
1038        "#;
1039
1040        expect_error(
1041            &p,
1042            invalid_policy,
1043            r#"Cannot overwrite existing role declaration "reader" in resource Repo with relation"#,
1044        );
1045
1046        // validating overwriting relations of different types throws error
1047        let invalid_policy = r#"
1048            actor User {}
1049            resource Repo {
1050                relations = { reader: Repo };
1051            }
1052
1053            resource Repo {
1054                relations = { reader: User };
1055            }
1056
1057            has_role(actor: Actor, _role: String, repo: Repo) if
1058                repo in actor.repos;
1059            has_relation(subject: Repo, "reader", object: Repo) if
1060                object.parent_id = subject.id;
1061        "#;
1062        let user_instance = ExternalInstance {
1063            instance_id: 2,
1064            constructor: None,
1065            repr: None,
1066            class_repr: None,
1067            class_id: None,
1068        };
1069        let user_term = term!(Value::ExternalInstance(user_instance.clone()));
1070        let user_name = sym!("User");
1071        p.register_constant(user_name.clone(), user_term).unwrap();
1072        p.register_mro(user_name, vec![user_instance.instance_id])
1073            .unwrap();
1074        expect_error(
1075            &p,
1076            invalid_policy,
1077            r#"Cannot overwrite existing relation declaration "reader" in resource Repo with relation"#,
1078        );
1079    }
1080
1081    #[test]
1082    fn test_resource_block_with_undeclared_local_shorthand_rule_head() {
1083        let p = Polar::new();
1084        p.register_constant(sym!("Org"), term!("unimportant"))
1085            .unwrap();
1086        expect_error(
1087            &p,
1088            r#"resource Org{"member" if "owner";}"#,
1089            r#"Undeclared term "member" referenced in rule in 'Org' resource block. Did you mean to declare it as a role, permission, or relation?"#,
1090        );
1091    }
1092
1093    #[test]
1094    fn test_resource_block_with_undeclared_local_shorthand_rule_body() {
1095        let p = Polar::new();
1096        p.register_constant(sym!("Org"), term!("unimportant"))
1097            .unwrap();
1098        expect_error(
1099            &p,
1100            r#"resource Org {
1101                roles=["member"];
1102                "member" if "owner";
1103            }"#,
1104            r#"Undeclared term "owner" referenced in rule in 'Org' resource block. Did you mean to declare it as a role, permission, or relation?"#,
1105        );
1106    }
1107
1108    #[test]
1109    fn test_resource_block_with_undeclared_nonlocal_shorthand_rule_body() {
1110        let p = Polar::new();
1111        p.register_constant(sym!("Repo"), term!("unimportant"))
1112            .unwrap();
1113        p.register_constant(sym!("Org"), term!("unimportant"))
1114            .unwrap();
1115
1116        expect_error(
1117            &p,
1118            r#"resource Repo {
1119                roles = ["writer"];
1120                relations = { parent: Org };
1121                "writer" if "owner" on "parent";
1122            }"#,
1123            r#"Repo: Relation "parent" in rule body `"owner" on "parent"` has type 'Org', but no such resource block exists. Try declaring one: `resource Org {}`"#,
1124        );
1125
1126        expect_error(
1127            &p,
1128            r#"resource Repo {
1129                roles = ["writer"];
1130                relations = { parent: Org };
1131                "writer" if "owner" on "parent";
1132            }
1133            resource Org {}"#,
1134            r#"Repo: Term "owner" not declared in related resource block 'Org'. Did you mean to declare it as a role, permission, or relation in the 'Org' resource block?"#,
1135        );
1136    }
1137
1138    #[test]
1139    #[ignore = "probably easier after the entity PR goes in"]
1140    fn test_resource_block_resource_relations_can_only_appear_after_on() {
1141        let p = Polar::new();
1142        p.register_constant(sym!("Repo"), term!("unimportant"))
1143            .unwrap();
1144        expect_error(
1145            &p,
1146            r#"resource Repo {
1147                roles = ["owner"];
1148                relations = { parent: Org };
1149                "parent" if "owner";
1150            }"#,
1151            r#"Repo: resource relation "parent" can only appear in a rule body following the keyword 'on'."#,
1152        );
1153    }
1154
1155    #[test]
1156    #[ignore = "outside scope of current task; leaving test here to be implemented later"]
1157    fn test_resource_block_resource_relations_are_the_only_thing_that_can_appear_after_on() {
1158        let p = Polar::new();
1159        p.register_constant(sym!("Repo"), term!("unimportant"))
1160            .unwrap();
1161        expect_error(
1162            &p,
1163            r#"resource Repo {
1164                roles = ["writer"];
1165                "writer" if "owner" on "parent";
1166            }"#,
1167            r#"Repo: term "parent" must be declared as a relation: `relations = { parent: <SomeResource> };`"#,
1168        );
1169    }
1170
1171    #[test]
1172    #[ignore = "not yet implemented"]
1173    fn test_resource_block_with_circular_shorthand_rules() {
1174        let p = Polar::new();
1175        p.register_constant(sym!("Repo"), term!("unimportant"))
1176            .unwrap();
1177        let policy = r#"resource Repo {
1178            roles = [ "writer" ];
1179            "writer" if "writer";
1180        }"#;
1181        panic!("{}", p.load_str(policy).unwrap_err());
1182
1183        // let policy = r#"resource Repo {
1184        //     roles = [ "writer", "reader" ];
1185        //     "writer" if "reader";
1186        //     "reader" if "writer";
1187        // }"#;
1188        // panic!("{}", p.load_str(policy).unwrap_err());
1189        //
1190        // let policy = r#"resource Repo {
1191        //     roles = [ "writer", "reader", "admin" ];
1192        //     "admin" if "reader";
1193        //     "writer" if "admin";
1194        //     "reader" if "writer";
1195        // }"#;
1196        // panic!("{}", p.load_str(policy).unwrap_err());
1197    }
1198
1199    #[test]
1200    fn test_resource_block_with_unregistered_relation_type() {
1201        let p = Polar::new();
1202        p.register_constant(sym!("Repo"), term!("unimportant"))
1203            .unwrap();
1204        let policy = r#"resource Repo { relations = { parent: Org }; }"#;
1205        expect_error(&p, policy, "Unregistered class: Org");
1206        p.register_constant(sym!("Org"), term!("unimportant"))
1207            .unwrap();
1208        p.load_str(policy).unwrap();
1209    }
1210
1211    #[test]
1212    fn test_resource_block_with_clashing_declarations() {
1213        let p = Polar::new();
1214        p.register_constant(sym!("Org"), term!("unimportant"))
1215            .unwrap();
1216
1217        expect_error(
1218            &p,
1219            r#"resource Org{
1220              roles = ["egg","egg"];
1221              "egg" if "egg";
1222            }"#,
1223            r#"Cannot overwrite existing role declaration "egg" in resource Org with role"#,
1224        );
1225
1226        expect_error(
1227            &p,
1228            r#"resource Org{
1229              roles = ["egg","tootsie"];
1230              permissions = ["spring","egg"];
1231
1232              "egg" if "tootsie";
1233              "tootsie" if "spring";
1234            }"#,
1235            r#"Cannot overwrite existing role declaration "egg" in resource Org with permission"#,
1236        );
1237
1238        expect_error(
1239            &p,
1240            r#"resource Org{
1241              permissions = [ "egg" ];
1242              relations = { egg: Roll };
1243            }"#,
1244            r#"Cannot overwrite existing permission declaration "egg" in resource Org with relation"#,
1245        );
1246    }
1247
1248    #[test]
1249    fn test_resource_block_parsing_permutations() {
1250        // Policy pieces
1251        let roles = r#"roles = ["writer", "reader"];"#;
1252        let permissions = r#"permissions = ["push", "pull"];"#;
1253        let relations = r#"relations = { creator: User, parent: Org };"#;
1254        let shorthand_rules = vec![
1255            r#""pull" if "reader";"#,
1256            r#""push" if "writer";"#,
1257            r#""writer" if "creator";"#,
1258            r#""reader" if "member" on "parent";"#,
1259        ];
1260
1261        // Maximal block
1262        let block = ResourceBlock {
1263            block_type: BlockType::Resource,
1264            resource: term!(sym!("Repo")),
1265            roles: Some(term!(["writer", "reader"])),
1266            permissions: Some(term!(["push", "pull"])),
1267            relations: Some(term!(btreemap! {
1268                sym!("creator") => term!(sym!("User")),
1269                sym!("parent") => term!(sym!("Org")),
1270            })),
1271            shorthand_rules: vec![
1272                // TODO(gj): shorthand_rule! macro
1273                ShorthandRule {
1274                    head: term!("pull"),
1275                    body: (term!("reader"), None),
1276                },
1277                ShorthandRule {
1278                    head: term!("push"),
1279                    body: (term!("writer"), None),
1280                },
1281                ShorthandRule {
1282                    head: term!("writer"),
1283                    body: (term!("creator"), None),
1284                },
1285                ShorthandRule {
1286                    head: term!("reader"),
1287                    body: (term!("member"), Some((term!(sym!("on")), term!("parent")))),
1288                },
1289            ],
1290        };
1291
1292        // Helpers
1293
1294        let equal = |line: &Line, expected: &ResourceBlock| match line {
1295            Line::ResourceBlock {
1296                keyword,
1297                resource,
1298                productions,
1299            } => {
1300                let (parsed, _) = resource_block_from_productions(
1301                    keyword.clone(),
1302                    resource.clone(),
1303                    productions.clone(),
1304                );
1305                let parsed_shorthand_rules: HashSet<&ShorthandRule> =
1306                    HashSet::from_iter(&parsed.shorthand_rules);
1307                let expected_shorthand_rules = HashSet::from_iter(&expected.shorthand_rules);
1308                parsed.resource == expected.resource
1309                    && parsed.roles == expected.roles
1310                    && parsed.permissions == expected.permissions
1311                    && parsed.relations == expected.relations
1312                    && parsed_shorthand_rules == expected_shorthand_rules
1313            }
1314            _ => false,
1315        };
1316
1317        let test_case = |mut parts: Vec<&str>, expected: &ResourceBlock| {
1318            for permutation in Heap::new(&mut parts) {
1319                let mut policy = "resource Repo {\n".to_owned();
1320                policy += &permutation.join("\n");
1321                policy += "}";
1322                assert!(equal(
1323                    &parse_lines(Source::new(policy)).unwrap()[0],
1324                    expected
1325                ));
1326            }
1327        };
1328
1329        // Test each case with and without shorthand rules.
1330        let test_cases = |parts: Vec<&str>, expected: &ResourceBlock| {
1331            let mut parts_with_shorthand_rules = parts.clone();
1332            parts_with_shorthand_rules.append(&mut shorthand_rules.clone());
1333            test_case(parts_with_shorthand_rules, expected);
1334
1335            let expected_without_shorthand_rules = ResourceBlock {
1336                shorthand_rules: vec![],
1337                ..expected.clone()
1338            };
1339            test_case(parts, &expected_without_shorthand_rules);
1340        };
1341
1342        // Cases
1343
1344        // Roles, Permissions, Relations
1345        test_cases(vec![roles, permissions, relations], &block);
1346
1347        // Roles, Permissions, _________
1348        let expected = ResourceBlock {
1349            relations: None,
1350            ..block.clone()
1351        };
1352        test_cases(vec![roles, permissions], &expected);
1353
1354        // Roles, ___________, Relations
1355        let expected = ResourceBlock {
1356            permissions: None,
1357            ..block.clone()
1358        };
1359        test_cases(vec![roles, relations], &expected);
1360
1361        // _____, Permissions, Relations
1362        let expected = ResourceBlock {
1363            roles: None,
1364            ..block.clone()
1365        };
1366        test_cases(vec![permissions, relations], &expected);
1367
1368        // Roles, ___________, _________
1369        let expected = ResourceBlock {
1370            permissions: None,
1371            relations: None,
1372            ..block.clone()
1373        };
1374        test_cases(vec![roles], &expected);
1375
1376        // _____, Permissions, _________
1377        let expected = ResourceBlock {
1378            roles: None,
1379            relations: None,
1380            ..block.clone()
1381        };
1382        test_cases(vec![permissions], &expected);
1383
1384        // _____, ___________, Relations
1385        let expected = ResourceBlock {
1386            roles: None,
1387            permissions: None,
1388            ..block.clone()
1389        };
1390        test_cases(vec![relations], &expected);
1391
1392        // _____, ___________, _________
1393        let expected = ResourceBlock {
1394            roles: None,
1395            permissions: None,
1396            relations: None,
1397            ..block
1398        };
1399        test_cases(vec![], &expected);
1400    }
1401
1402    #[test]
1403    fn test_resource_block_declaration_keywords() {
1404        let p = Polar::new();
1405        expect_error(
1406            &p,
1407            r#"resource Org{roles={};}"#,
1408            r#"Expected 'roles' declaration to be a list of strings; found a dictionary"#,
1409        );
1410        expect_error(
1411            &p,
1412            r#"resource Org{relations=[];}"#,
1413            r#"Expected 'relations' declaration to be a dictionary; found a list"#,
1414        );
1415        expect_error(
1416            &p,
1417            r#"resource Org{foo=[];}"#,
1418            r#"Unexpected declaration 'foo'. Did you mean for this to be 'roles = [ ... ];' or 'permissions = [ ... ];'?"#,
1419        );
1420        expect_error(
1421            &p,
1422            r#"resource Org{foo={};}"#,
1423            r#"Unexpected declaration 'foo'. Did you mean for this to be 'relations = { ... };'?"#,
1424        );
1425    }
1426
1427    #[test]
1428    fn test_resource_block_relation_keywords() {
1429        let p = Polar::new();
1430        p.register_constant(sym!("Org"), term!("unimportant"))
1431            .unwrap();
1432        expect_error(
1433            &p,
1434            r#"resource Org {
1435              roles = ["foo", "bar"];
1436              relations = { baz: Org };
1437              "foo" if "bar" onn "baz";
1438            }"#,
1439            "Unexpected relation keyword 'onn'. Did you mean 'on'?",
1440        );
1441    }
1442
1443    #[test]
1444    fn test_resource_block_types() {
1445        let p = Polar::new();
1446
1447        expect_error(
1448            &p,
1449            "Org{}",
1450            "Expected 'actor' or 'resource' but found nothing.",
1451        );
1452
1453        expect_error(
1454            &p,
1455            "seahorse Org{}",
1456            "Expected 'actor' or 'resource' but found 'seahorse'.",
1457        );
1458    }
1459
1460    #[test]
1461    fn test_resource_block_declaration_keywords_are_not_reserved_words() {
1462        let p = Polar::new();
1463        p.load_str(
1464            "on(actor, resource, roles, permissions, relations) if on(actor, resource, roles, permissions, relations);",
1465        )
1466        .unwrap();
1467    }
1468
1469    // TODO(gj): test union types in all of the positions where classes can appear, such as in
1470    // `new` expressions.
1471
1472    #[test]
1473    #[ignore = "unimplemented"]
1474    fn test_resource_block_union_types_are_not_constructable() {
1475        let p = Polar::new();
1476        let q = p.new_query(&format!("new {}()", ACTOR_UNION_NAME), false);
1477        let msg = match q {
1478            Err(PolarError(Validation(ValidationError::ResourceBlock { msg, .. }))) => msg,
1479            Err(e) => panic!("{}", e),
1480            _ => panic!("succeeded when I should've failed"),
1481        };
1482        assert_eq!(msg, "hi");
1483    }
1484
1485    #[test]
1486    fn test_union_type_matches() {
1487        // When unions exist, `Actor matches Actor` because a union matches itself.
1488        let polar = Polar::new();
1489        polar
1490            .register_constant(sym!("User"), term!("unimportant"))
1491            .unwrap();
1492        polar.load_str("actor User {}").unwrap();
1493        let query = polar.new_query(
1494            &format!("{} matches {}", ACTOR_UNION_NAME, ACTOR_UNION_NAME),
1495            false,
1496        );
1497        let next_event = query.unwrap().next_event().unwrap();
1498        assert!(matches!(next_event, QueryEvent::Result { .. }));
1499
1500        // When unions exist, `not Actor matches Resource` because a union doesn't match a
1501        // different union.
1502        let polar = Polar::new();
1503        polar
1504            .register_constant(sym!("User"), term!("unimportant"))
1505            .unwrap();
1506        polar
1507            .register_constant(sym!("Repo"), term!("unimportant"))
1508            .unwrap();
1509        polar.load_str("actor User {} resource Repo {}").unwrap();
1510        let query = polar.new_query(
1511            &format!("not {} matches {}", ACTOR_UNION_NAME, RESOURCE_UNION_NAME),
1512            false,
1513        );
1514        let next_event = query.unwrap().next_event().unwrap();
1515        assert!(matches!(next_event, QueryEvent::Result { .. }));
1516    }
1517
1518    #[test]
1519    fn test_union_type_names_are_reserved() {
1520        let polar = Polar::new();
1521        let err = polar
1522            .register_constant(sym!(ACTOR_UNION_NAME), term!("unimportant"))
1523            .expect_err("Expected register_constant to throw error.");
1524        assert!(matches!(
1525            err.0,
1526            Runtime(RuntimeError::InvalidRegistration { .. })
1527        ));
1528    }
1529
1530    #[test]
1531    fn test_validate_rules_with_union_type_specializers() {
1532        let mut kb = KnowledgeBase::new();
1533        kb.register_constant(
1534            sym!("Fruit"),
1535            term!(Value::ExternalInstance(ExternalInstance {
1536                instance_id: 1,
1537                constructor: None,
1538                repr: None,
1539                class_repr: None,
1540                class_id: None,
1541            })),
1542        )
1543        .unwrap();
1544        kb.register_constant(
1545            sym!("Citrus"),
1546            term!(Value::ExternalInstance(ExternalInstance {
1547                instance_id: 2,
1548                constructor: None,
1549                repr: None,
1550                class_repr: None,
1551                class_id: None,
1552            })),
1553        )
1554        .unwrap();
1555        kb.register_constant(
1556            sym!("Orange"),
1557            term!(Value::ExternalInstance(ExternalInstance {
1558                instance_id: 3,
1559                constructor: None,
1560                repr: None,
1561                class_repr: None,
1562                class_id: None,
1563            })),
1564        )
1565        .unwrap();
1566        kb.add_mro(sym!("Fruit"), vec![1]).unwrap();
1567        // Citrus is a subclass of Fruit
1568        kb.add_mro(sym!("Citrus"), vec![2, 1]).unwrap();
1569        // Orange is a subclass of Citrus
1570        kb.add_mro(sym!("Orange"), vec![3, 2, 1]).unwrap();
1571
1572        kb.register_constant(
1573            sym!("User"),
1574            term!(Value::ExternalInstance(ExternalInstance {
1575                instance_id: 4,
1576                constructor: None,
1577                repr: None,
1578                class_repr: None,
1579                class_id: None,
1580            })),
1581        )
1582        .unwrap();
1583        kb.add_mro(sym!("User"), vec![4]).unwrap();
1584
1585        // Add member to 'Resource' union.
1586        kb.resource_blocks.resources.insert(term!(sym!("Citrus")));
1587        // Add member to 'Actor' union.
1588        kb.resource_blocks.actors.insert(term!(sym!("User")));
1589
1590        // Union matches union.
1591        kb.add_rule_type(rule!("f", ["x"; instance!(sym!(RESOURCE_UNION_NAME))]));
1592        kb.add_rule(rule!("f", ["x"; instance!(sym!(RESOURCE_UNION_NAME))]));
1593        assert!(kb.validate_rules().is_empty());
1594
1595        kb.clear_rules();
1596        kb.resource_blocks.resources.insert(term!(sym!("Citrus")));
1597        kb.resource_blocks.actors.insert(term!(sym!("User")));
1598
1599        // TODO(gj): revisit when we have unions beyond Actor & Resource. Union A matches
1600        // union B if union A is a member of union B.
1601        //
1602        // Union A does not match union B.
1603        kb.add_rule_type(rule!("f", ["x"; instance!(sym!(RESOURCE_UNION_NAME))]));
1604        kb.add_rule(rule!("f", ["x"; instance!(sym!(ACTOR_UNION_NAME))]));
1605        let diagnostic = kb.validate_rules().into_iter().next().unwrap();
1606        let error = diagnostic.unwrap_error().unwrap_validation();
1607        assert!(matches!(error, ValidationError::InvalidRule { .. }));
1608
1609        kb.clear_rules();
1610        kb.resource_blocks.resources.insert(term!(sym!("Citrus")));
1611        kb.resource_blocks.actors.insert(term!(sym!("User")));
1612
1613        // Member of union matches union.
1614        kb.add_rule_type(rule!("f", ["x"; instance!(sym!(RESOURCE_UNION_NAME))]));
1615        kb.add_rule(rule!("f", ["x"; instance!(sym!("Citrus"))]));
1616        assert!(kb.validate_rules().is_empty());
1617
1618        kb.clear_rules();
1619        kb.resource_blocks.resources.insert(term!(sym!("Citrus")));
1620        kb.resource_blocks.actors.insert(term!(sym!("User")));
1621
1622        // TODO(gj): revisit when we have unions beyond Actor & Resource. Member of union A matches
1623        // union B if union A is a member of union B.
1624        //
1625        // Member of union A does not match union B.
1626        kb.add_rule_type(rule!("f", ["x"; instance!(sym!(ACTOR_UNION_NAME))]));
1627        kb.add_rule(rule!("f", ["x"; instance!(sym!("Citrus"))]));
1628        let diagnostic = kb.validate_rules().into_iter().next().unwrap();
1629        let error = diagnostic.unwrap_error().unwrap_validation();
1630        assert!(matches!(error, ValidationError::InvalidRule { .. }));
1631
1632        kb.clear_rules();
1633        kb.resource_blocks.resources.insert(term!(sym!("Citrus")));
1634        kb.resource_blocks.actors.insert(term!(sym!("User")));
1635
1636        // Subclass of member of union matches union.
1637        kb.add_rule_type(rule!("f", ["x"; instance!(sym!(RESOURCE_UNION_NAME))]));
1638        kb.add_rule(rule!("f", ["x"; instance!(sym!("Orange"))]));
1639        assert!(kb.validate_rules().is_empty());
1640
1641        kb.clear_rules();
1642        kb.resource_blocks.resources.insert(term!(sym!("Citrus")));
1643        kb.resource_blocks.actors.insert(term!(sym!("User")));
1644
1645        // Superclass of member of union does not match union.
1646        kb.add_rule_type(rule!("f", ["x"; instance!(sym!(RESOURCE_UNION_NAME))]));
1647        kb.add_rule(rule!("f", ["x"; instance!(sym!("Fruit"))]));
1648        let diagnostic = kb.validate_rules().into_iter().next().unwrap();
1649        let error = diagnostic.unwrap_error().unwrap_validation();
1650        assert!(matches!(error, ValidationError::InvalidRule { .. }));
1651
1652        // kb.clear_rules();
1653        // kb.resource_blocks.resources.insert(term!(sym!("Citrus")));
1654        // kb.resource_blocks.actors.insert(term!(sym!("User")));
1655        //
1656        // TODO(gj): revisit when we have unions beyond Actor & Resource. Not currently possible to
1657        // have an instance of a member of a union as a specializer until we have true unions where
1658        // we could define, e.g., `type MyUnion = Integer;`
1659        //
1660        // Instance of member of union matches union.
1661        // kb.add_rule_type(rule!("f", ["x"; instance!(sym!(RESOURCE_UNION_NAME))]));
1662        // kb.add_rule(rule!("f", ["x"; 1]));
1663        // assert!(kb.validate_rules().is_ok());
1664
1665        // kb.clear_rules();
1666        // kb.resource_blocks.resources.insert(term!(sym!("Citrus")));
1667        // kb.resource_blocks.actors.insert(term!(sym!("User")));
1668        //
1669        // TODO(gj): revisit when we have unions beyond Actor & Resource. Not currently possible to
1670        // have an instance of a member of a union as a specializer until we have true unions where
1671        // we could define, e.g., `type MyUnion = Integer;`
1672        //
1673        // Instance of subclass of member of union matches union.
1674        // kb.add_rule_type(rule!("f", ["x"; instance!(sym!(RESOURCE_UNION_NAME))]));
1675        // kb.add_rule(rule!("f", ["x"; 1]));
1676        // assert!(kb.validate_rules().is_ok());
1677    }
1678
1679    // TODO(gj): add test for union pattern with fields. Behavior will probably be the same as for
1680    // fieldless union pattern where we create a choicepoint of matches against every union member
1681    // with the same set of fields.
1682
1683    // Test creation of resource-specific rule type (for `has_relation`) and general rule type (for
1684    // `has_role`):
1685    //   - has_relation between (Organization, "parent", Repository)
1686    //   - has_role created because at least one resource block has roles declared
1687    #[test]
1688    fn test_create_resource_specific_rule_types() -> PolarResult<()> {
1689        let policy = r#"
1690            resource Organization {
1691                roles = ["member"];
1692            }
1693
1694            resource Repository {
1695                roles = ["reader"];
1696                relations = {parent: Organization};
1697                "reader" if "member" on "parent";
1698            }
1699
1700            has_relation(organization: Organization, "parent", repository: Repository) if
1701                repository.org_id = organization.id;
1702
1703            has_role(user: Actor, _role: String, organization: Organization) if
1704                organization.id in user.org_ids;
1705        "#;
1706
1707        let polar = Polar::new();
1708
1709        let repo_instance = ExternalInstance {
1710            instance_id: 1,
1711            constructor: None,
1712            repr: None,
1713            class_repr: None,
1714            class_id: None,
1715        };
1716        let repo_term = term!(Value::ExternalInstance(repo_instance.clone()));
1717        let repo_name = sym!("Repository");
1718        polar.register_constant(repo_name.clone(), repo_term)?;
1719        polar.register_mro(repo_name.clone(), vec![repo_instance.instance_id])?;
1720
1721        let org_instance = ExternalInstance {
1722            instance_id: 2,
1723            constructor: None,
1724            repr: None,
1725            class_repr: None,
1726            class_id: None,
1727        };
1728        let org_term = term!(Value::ExternalInstance(org_instance.clone()));
1729        let org_name = sym!("Organization");
1730        polar.register_constant(org_name.clone(), org_term)?;
1731        polar.register_mro(org_name.clone(), vec![org_instance.instance_id])?;
1732
1733        polar.load_str(policy)?;
1734
1735        let kb = polar.kb.read().unwrap();
1736
1737        let has_role_rule_types = kb.get_rule_types(&sym!("has_role")).unwrap();
1738        // has_role(actor: Actor, role: String, resource: Resource)
1739        let expected = rule!("has_role", ["actor"; instance!(ACTOR_UNION_NAME), "role"; instance!("String"), "resource"; instance!(RESOURCE_UNION_NAME)]);
1740        assert_eq!(1, has_role_rule_types.len());
1741        assert_eq!(has_role_rule_types[0], expected);
1742
1743        let has_relation_rule_types = kb.get_rule_types(&sym!("has_relation")).unwrap();
1744        // has_relation(organization: Organization, "parent", repository: Repository)
1745        let expected = rule!("has_relation", ["subject"; instance!(org_name), "parent", "object"; instance!(repo_name)]);
1746        assert_eq!(1, has_relation_rule_types.len());
1747        assert_eq!(has_relation_rule_types[0], expected,);
1748
1749        Ok(())
1750    }
1751
1752    // Test creation of rule types for actor roles
1753    //   - has_role created because at least one resource block has roles declared
1754    #[test]
1755    fn test_create_resource_specific_rule_types_actor_roles() -> PolarResult<()> {
1756        let policy = r#"
1757            actor Team {
1758                roles = ["member", "owner"];
1759
1760                "member" if "owner";
1761            }
1762        "#;
1763
1764        let polar = Polar::new();
1765
1766        let team_instance = ExternalInstance {
1767            instance_id: 1,
1768            constructor: None,
1769            repr: None,
1770            class_repr: None,
1771            class_id: None,
1772        };
1773        let team_term = term!(Value::ExternalInstance(team_instance.clone()));
1774        let team_name = sym!("Team");
1775        polar.register_constant(team_name.clone(), team_term)?;
1776        polar.register_mro(team_name, vec![team_instance.instance_id])?;
1777
1778        polar.load_str(policy)?;
1779
1780        let kb = polar.kb.read().unwrap();
1781
1782        let has_role_rule_types = kb.get_rule_types(&sym!("has_role")).unwrap();
1783        // has_role(actor: Actor, role: String, resource: Resource)
1784        let expected = rule!("has_role", ["actor"; instance!(ACTOR_UNION_NAME), "role"; instance!("String"), "resource"; instance!(RESOURCE_UNION_NAME)]);
1785        assert_eq!(1, has_role_rule_types.len());
1786        assert_eq!(has_role_rule_types[0], expected);
1787
1788        Ok(())
1789    }
1790}