Skip to content

Commit 9d24fcf

Browse files
committed
docs: Add internals docs.
Signed-off-by: Philip Conrad <philip@chariot-chaser.net>
1 parent ed03d03 commit 9d24fcf

File tree

2 files changed

+71
-0
lines changed

2 files changed

+71
-0
lines changed

docs/internals.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# How the internals of the library work
2+
3+
This library allows users to integrate dynamic data filtering rules into [LINQ queries][intro-to-linq] that are driven by policies written in Rego.
4+
5+
## The overall flow
6+
7+
The process can coarsely be split between OPA and C# sides, but in practice, there's going to be some co-design where a dev might 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 probably find useful for spinning up a C# application that uses this library.
8+
9+
Below is a high-level sequence diagram of the process:
10+
```mermaid
11+
sequenceDiagram
12+
C# Application ->>+ OPA: Compile API Request
13+
OPA ->>- C# Application: UCAST Result
14+
15+
C# Application ->> C# Application: Translate UCAST to LINQ Query
16+
17+
participant Database@{ "type" : "database" }
18+
C# Application ->>+ Database: LINQ Query
19+
Database ->>- C# Application: Query Results
20+
```
21+
22+
## OPA side
23+
24+
1. The user writes a [data filtering policy][data-filters] in Rego. ([Docs on writing data filter policies][writing-data-filters])
25+
- These policies might be unlike others you've written before.
26+
This is because they are not evaluated to generate decisions (as is typical for Rego rules).
27+
Instead, these policies are *partially evaluated*, and the resulting simplified rules are compiled down into Universal Conditions AST (UCAST) expression trees.
28+
1. The user loads the policy into an OPA server instance.
29+
30+
### Mapping names between LINQ and Rego
31+
It is important that the data filtering policy is written with correct names for model properties that will be used on the C# side.
32+
By default, when mapping an object's properties to Rego refs, the mapper assumes `snake_case` for everything.
33+
34+
Example:
35+
```csharp
36+
// Assuming we map this class to have the prefix "hydro"
37+
// Ex: MappingConfiguration<HydrologyData>(prefix: "hydro");
38+
public class HydrologyData
39+
{
40+
public int Id { get; set; } // => hydro.id
41+
public Guid Uuid { get; set; } // => hydro.uuid
42+
public string? Name { get; set; } // => hydro.name
43+
public DateTime LastUpdated { get; set; } // => hydro.last_updated
44+
public bool FloodStage { get; set; } // => hydro.flood_stage
45+
public double WaterLevelMeters { get; set; } // => hydro.water_level_meters
46+
public double? FlowRateMinute { get; set; } // => hydro.flow_rate_minute
47+
}
48+
```
49+
50+
Note: The above mapping assumes fields with identical spelling except for capitalization are **not** present in the model.
51+
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.
52+
53+
54+
## C# side
55+
56+
The process is essentially: building the field mapping, querying OPA for a UCAST value, and then plugging in the UCAST at the LINQ query site.
57+
The steps for doing those things looks like:
58+
59+
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`)
60+
1. The C# program then makes an HTTP request to the OPA server's [Compile API][opa-compile-api] endpoint, to get the [Universal Conditions AST (UCAST)][ucast] expression tree that describes the data filters.
61+
1. This library's `QueryableExtensions.ApplyUCASTFilter()` LINQ operator can then be used to build a LINQ [`Expression`][csharp-linq-expression] tree as part of a larger LINQ query, using the `UCASTNode` hierarchy, and the `MappingConfiguration` from earlier.
62+
1. 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.)
63+
64+
[data-filters]: https://www.openpolicyagent.org/docs/filtering
65+
[writing-data-filters]: https://www.openpolicyagent.org/docs/filtering/fragment
66+
[opa-compile-api]: https://www.openpolicyagent.org/docs/rest-api#compile-api
67+
[ucast]: https://www.openpolicyagent.org/docs/filtering/ucast-syntax
68+
[intro-to-linq]: https://learn.microsoft.com/en-us/dotnet/csharp/linq/get-started/introduction-to-linq-queries
69+
[csharp-linq-expression]: https://learn.microsoft.com/en-us/dotnet/api/system.linq.expressions.expression

docs/toc.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
- name: API Reference
22
href: api/
3+
- name: Internals
4+
href: internals.md

0 commit comments

Comments
 (0)