Skip to content

Commit 6c5d8d9

Browse files
docs: Add development workflow docs and diagram. (#67)
This commit adds a general guide to developing data filters in C# applications with this library, and adds a diagram of the flow of requests for data filters to the project README. Signed-off-by: Philip Conrad <philip@chariot-chaser.net>
1 parent ed03d03 commit 6c5d8d9

File tree

3 files changed

+71
-2
lines changed

3 files changed

+71
-2
lines changed

README.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,24 @@
66
> [!IMPORTANT]
77
> The reference documentation for this library is available at https://open-policy-agent.github.io/ucast-linq
88
9+
## Overview
10+
11+
This library allows users to integrate dynamic data filtering rules into [LINQ queries](https://learn.microsoft.com/en-us/dotnet/csharp/linq/get-started/introduction-to-linq-queries) that are driven by policies written in Rego.
12+
13+
Below is a high-level sequence diagram of the process:
14+
```mermaid
15+
sequenceDiagram
16+
C# Application ->>+ OPA: Compile API Request
17+
OPA ->>- C# Application: UCAST Result
18+
19+
C# Application ->> C# Application: Translate UCAST to LINQ Query
20+
21+
participant Database@{ "type" : "database" }
22+
C# Application ->>+ Database: LINQ Query
23+
Database ->>- C# Application: Query Results
24+
```
25+
26+
See the [Development Workflow](docs/dev-workflow.md) docs for more details.
927

1028
## Installation
1129

@@ -15,7 +33,6 @@
1533
dotnet add package OpenPolicyAgent.Ucast.Linq
1634
```
1735

18-
1936
## Example Usage
2037

2138
Let's assume that we have a collection of random integers, and wish to filter them with a LINQ query using multiple criteria:
@@ -97,7 +114,6 @@ SimpleRecord { Value = 1894 }
97114

98115
</details>
99116

100-
101117
## Community
102118

103119
For questions, discussions, and announcements, please join the OPA community on [Slack](https://slack.openpolicyagent.org/)!

docs/dev-workflow.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# General Development Workflow
2+
3+
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.
4+
5+
## OPA side
6+
7+
1. The user writes a [data filtering policy][data-filters] in Rego. ([Docs on writing data filter policies][writing-data-filters])
8+
- These policies might be unlike others you've written before in Rego.
9+
This is because they are not evaluated to generate decisions (as is typical for Rego rules).
10+
Instead, these policies are *partially evaluated*, and the resulting simplified rules are compiled down into [Universal Conditions AST (UCAST)][ucast] expression trees.
11+
1. The user loads the policy into an OPA server instance.
12+
13+
### Mapping names between LINQ and Rego
14+
15+
It is important that the data filtering policy is written with correct names for model properties that will be used on the C# side.
16+
By default, when mapping an object's properties to Rego refs, the mapper assumes `snake_case` for everything.
17+
18+
Example:
19+
```csharp
20+
// Assuming we map this class to have the prefix "hydro"
21+
// Ex: MappingConfiguration<HydrologyData>(prefix: "hydro");
22+
public class HydrologyData
23+
{
24+
public int Id { get; set; } // => hydro.id
25+
public Guid Uuid { get; set; } // => hydro.uuid
26+
public string? Name { get; set; } // => hydro.name
27+
public DateTime LastUpdated { get; set; } // => hydro.last_updated
28+
public bool FloodStage { get; set; } // => hydro.flood_stage
29+
public double WaterLevelMeters { get; set; } // => hydro.water_level_meters
30+
public double? FlowRateMinute { get; set; } // => hydro.flow_rate_minute
31+
}
32+
```
33+
34+
Note: The above mapping assumes fields with identical spelling except for capitalization are **not** present in the model.
35+
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.
36+
37+
## C# side
38+
39+
The process is: building the field mapping, querying OPA for a UCAST value, and then plugging in the UCAST at the LINQ query site.
40+
The steps for doing those things looks like:
41+
42+
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`)
43+
1. The C# program then makes an HTTP request to the OPA server's [Compile API][opa-compile-api] endpoint, to get the [UCAST][ucast] expression tree that describes the data filters.
44+
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.
45+
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.)
46+
47+
[data-filters]: https://www.openpolicyagent.org/docs/filtering
48+
[writing-data-filters]: https://www.openpolicyagent.org/docs/filtering/fragment
49+
[opa-compile-api]: https://www.openpolicyagent.org/docs/rest-api#compile-api
50+
[ucast]: https://www.openpolicyagent.org/docs/filtering/ucast-syntax
51+
[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: Development Workflow
4+
href: dev-workflow.md

0 commit comments

Comments
 (0)