Skip to content

Latest commit

 

History

History
51 lines (40 loc) · 3.51 KB

File metadata and controls

51 lines (40 loc) · 3.51 KB

General Development Workflow

The process can coarsely be split between OPA and C# sides. In practice, there will be some co-design where a dev may want a certain field in the model to map to a certain name in the Rego policy, and vice versa. Below is the rough process most users will find useful for spinning up a C# application that uses this library.

OPA side

  1. The user writes a data filtering policy in Rego. (Docs on writing data filter policies)
    • These policies might be unlike others you've written before in Rego. This is because they are not evaluated to generate decisions (as is typical for Rego rules). Instead, these policies are partially evaluated, and the resulting simplified rules are compiled down into Universal Conditions AST (UCAST) expression trees.
  2. The user loads the policy into an OPA server instance.

Mapping names between LINQ and Rego

It is important that the data filtering policy is written with correct names for model properties that will be used on the C# side. By default, when mapping an object's properties to Rego refs, the mapper assumes snake_case for everything.

Example:

// Assuming we map this class to have the prefix "hydro"
// Ex: MappingConfiguration<HydrologyData>(prefix: "hydro");
public class HydrologyData
{
    public int Id { get; set; } // => hydro.id
    public Guid Uuid { get; set; } // => hydro.uuid
    public string? Name { get; set; } // => hydro.name
    public DateTime LastUpdated { get; set; } // => hydro.last_updated
    public bool FloodStage { get; set; } // => hydro.flood_stage
    public double WaterLevelMeters { get; set; } // => hydro.water_level_meters
    public double? FlowRateMinute { get; set; } // => hydro.flow_rate_minute
}

Note: The above mapping assumes fields with identical spelling except for capitalization are not present in the model. If you have such a case, you will want to look into using the namesToProperties field in the MappingConfiguration constructor to control how those properties are mapped.

C# side

The process is: building the field mapping, querying OPA for a UCAST value, and then plugging in the UCAST at the LINQ query site. The steps for doing those things looks like:

  1. This library's MappingConfiguration<T> type is used to create a mapping from properties of the model object type to field references in the UCAST sent back from OPA. This allows a Rego policy to refer to specific model fields by name. (e.g. hydro.water_level_meters)
  2. The C# program then makes an HTTP request to the OPA server's Compile API endpoint, to get the UCAST expression tree that describes the data filters.
  3. This library's QueryableExtensions.ApplyUCASTFilter() LINQ operator can then be used to build a LINQ Expression tree as part of a larger LINQ query, using the UCASTNode hierarchy, and the MappingConfiguration from earlier.
  4. LINQ then handles JIT compiling the LINQ query into whatever format makes sense for the underlying data source. (e.g. SQL queries for an EF Core model query.)