Skip to content

Add a merge API to cedar_policy::Context #1013

@ericox

Description

@ericox

Category

User level API features/changes

Describe the feature you'd like to request

As a developer working on translation of user-defined rust types to cedar_policy::Context, I find that I often want to reach for a merge method to merge two contexts ctx1.merge(ctx2). A use-case for this is when translating nested structs of attributes into a nested Context.

Consider a top-level aggregate of multiple user-defined Contexts.

#[derive(Builder, Debug, Clone, PartialEq, Eq)]
#[builder(pattern = "owned")]
pub struct MyContext {
    pub foo: FooContext,
    pub bar: BarContext,
}

Where sub-contexts may have other nested user-defined Contexts that I need to translate into a cedar_policy::Context, are defined as:

// Contains other Contexts I need to translate, recursively.
pub struct FooContext {
    pub ctx_a: Option<FooContextA>,
    pub ctx_b: Option<FooContextB>,
}

// A leaf Context.
pub struct BarContext {
    pub key: String,
    pub value: String,
}

A merge API could help write code like this to stitch together sub-context translation results:

impl MyContext {
    pub fn try_into_cedar_context(self) -> Result<cedar_policy::Context, MyContextToCedarContextError> {
            let foo_ctx = self.foo.try_into_cedar_context()?;
            let bar_ctx = self.bar.try_into_cedar_context()?;
            Ok(foo_ctx.merge(bar_ctx));
    }
}

// Example impl for a non-top-level context.
impl FooContext {
    // It would be good to run a fold/collect to reduce over the contexts. Rough idea, may not compile.
    pub fn try_into_cedar_context(self) -> Result<cedar_policy::Context, MyContextToCedarContextError> {
          Ok(vec![self.ctx_a, self.ctx_b].iter()
                    .fold(Context::new(), |mut acc, ctx| { 
                         acc.merge(ctx);
                         acc
                  })?);
    }
}

Describe alternatives you've considered

Aside from the merge proposal another good alternative could be implementing the extend trait like HashMap does, https://doc.rust-lang.org/std/collections/struct.HashMap.html#method.extend.

The workaround that I ended up doing was to implement non-top-level translations to a HashMap<String, RestrictedExpression>. Then I can build the top-level context by merging the restricted expression maps and passing that to Context::from_pairs(result).

// Translation of a leaf context.
impl BarContext {
    pub fn try_into_cedar_context(self) -> Result<HashMap<String, RestrictedExpression, MyContextToCedarContextError> {
          Ok(HashMap::from([
               ("key".to_string(), RestrictedExpression::new_string(self.key)),
               ("value".to_string(), RestrictedExpression::new_string(self.value)),
           ])?);
    }
}

At the top-level I converted the sub-contexts to a RestrictedExpression record, and then used Context::from_pairs:

impl MyContext {
    pub fn try_into_cedar_context(self) -> Result<cedar_policy::Context, MyContextToCedarContextError> {
         let foo_expr_map = self.foo.try_into_cedar_context()?;
         let bar_expr_map = self.bar.try_into_cedar_context()?;
         Ok(Context::from_pairs([
             ("foo".to_string(), RestrictedExpression::new_record(foo_expr_map.into_iter())?),
             ("bar".to_string(), RestrictedExpression::new_record(bar_expr_map.into_iter())?)
         ]))
    }
}

Additional context

No response

Is this something that you'd be interested in working on?

  • 👋 I may be able to implement this feature request
  • ⚠️ This feature might incur a breaking change

Metadata

Metadata

Assignees

No one assigned

    Labels

    feature-requestThis issue requets a substantial new feature

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions