Skip to content

Commit dce7888

Browse files
authored
Enhance README with detailed Quaero information
Expanded the README to provide a comprehensive overview of Quaero, including its purpose, supported operators, installation instructions, usage examples, and translation across different systems.
1 parent 70235d1 commit dce7888

File tree

1 file changed

+105
-58
lines changed

1 file changed

+105
-58
lines changed

README.md

Lines changed: 105 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,97 +1,144 @@
11
# Quaero
22

3-
A small query language that can transform queries into system-specific filters.
3+
> A web-friendly query language that transforms into Microsoft Graph, Active Directory, and SCIM filters
44
5-
Quaero is designed to be web-friendly by using letter-based operators instead of symbols. It's very similar to Microsoft Graph (OData) and SCIM filter syntax.
6-
See [example queries](#example-queries) for some examples.
5+
[![NuGet](https://img.shields.io/nuget/v/Quaero.svg)](https://www.nuget.org/packages/Quaero)
6+
[![Build Status](https://img.shields.io/github/actions/workflow/status/khellang/Quaero/ci.yml?branch=main)](https://github.com/khellang/Quaero/actions)
77

8-
This repository contains translation implementations for Microsoft Graph, Active Directory (LDAP) and SCIM filters, as well as in-memory predicates.
8+
## What is Quaero?
99

10-
## Supported operators
10+
Quaero is a small, intuitive query language designed to unify filtering across different systems.
11+
Instead of learning multiple query syntaxes for Microsoft Graph, LDAP, and SCIM, you write queries once in Quaero and transform them into the target system's native filter format.
1112

12-
The language supports a wide range of operators supported by most filter/query languages.
13+
**Key Benefits:**
14+
- **Universal syntax** - Write queries once, use everywhere
15+
- **Web-friendly** - Uses readable operators like `eq`, `gt`, `and` instead of symbols
16+
- **Type-safe** - Built for .NET with strong typing support
17+
- **Optimizable** - Automatic query optimization removes redundancies
1318

14-
### Equality operators
19+
## Quick Start
1520

16-
- Equals (`eq`)
17-
- Not equals (`ne`)
18-
- Logical negation (`not`)
19-
- In (`in`)
21+
### Installation
2022

21-
### Relational operators
23+
```bash
24+
# Core library
25+
dotnet add package Quaero
2226

23-
- Less than (`lt`)
24-
- Greater than (`gt`)
25-
- Less than or equal to (`le`)
26-
- Greater than or equal to (`ge`)
27-
28-
### Conditional operators
27+
# Target-specific packages
28+
dotnet add package Quaero.MicrosoftGraph
29+
dotnet add package Quaero.Ldap
30+
dotnet add package Quaero.Scim
31+
```
2932

30-
- And (`and`)
31-
- Or (`or`)
33+
### Basic Usage
3234

33-
### Functions
35+
```csharp
36+
using Quaero;
3437

35-
- Starts with (`sw`)
36-
- Ends with (`ew`)
37-
- Contains (`co`)
38+
// Parse a filter from string
39+
Filter filter = Filter.Parse("age gt 42 and department eq \"Engineering\"");
3840

39-
## Example queries
41+
// Or build programmatically
42+
Filter filter = Filter.And(
43+
Filter.GreaterThan("age", 42),
44+
Filter.Equals("department", "Engineering")
45+
);
4046

41-
| Quaero | Microsoft Graph | LDAP | SCIM |
42-
|------------------------------------------------|------------------------------------------------|---------------------------------------------------------|-----------------------------------------------------|
43-
| `age gt 42` | `age gt 42` | `(age>=43)` | `age gt 42` |
44-
| `age ge 42` | `age ge 42` | `(age>=42)` | `age ge 42` |
45-
| `age lt 42` | `age lt 42` | `(age<=41)` | `age lt 42` |
46-
| `age le 42` | `age le 42` | `(age<=42)` | `age le 42` |
47-
| `name eq "John"` | `name eq 'John'` | `(name=John)` | `name eq "John"` |
48-
| `not(name eq "John")` | `name ne 'John'` | `(!(name=John))` | `name ne "John"` |
49-
| `department in ["Retail", "Sales"]` | `department in ('Retail', 'Sales')` | `(\|(department=Retail)(department=Sales))` | `(department eq "Retail" or department eq "Sales")` |
50-
| `isRead eq false` | `isRead eq false` | `(isRead=FALSE)` | `isRead eq false` |
51-
| `mail ew "outlook.com"` | `endsWith(mail, 'outlook.com')` | `(mail=*outlook.com)` | `mail ew "outlook.com"` |
52-
| `parent ne null` | `parent ne null` | `(parent=*)` | `parent pr` |
53-
| `name pr` | `name ne null` | `(name=*)` | `name pr` |
54-
| `id eq "275e50ae-ceb8-4f33-9e68-b3b9dc87ea68"` | `id eq '275e50ae-ceb8-4f33-9e68-b3b9dc87ea68'` | `(id=\AE\50\5E\27\B8\CE\33\4F\9E\68\B3\B9\DC\87\EA\68)` | `id eq "275e50ae-ceb8-4f33-9e68-b3b9dc87ea68"` |
47+
// Transform to target system
48+
string microsoftGraph = filter.ToMicrosoftGraphFilter();
49+
string ldapFilter = filter.ToLdapFilter();
50+
string scimFilter = filter.ToScimFilter();
5551

56-
## Usage
52+
// Use in-memory
53+
Func<Employee, bool> predicate = filter.ToPredicate<Employee>();
54+
```
5755

58-
Either parse a filter expression from a string using `Filter.Parse` or `Filter.TryParse`:
56+
## Supported Operators
57+
58+
| Operator | Description | Example |
59+
|----------|-------------|---------|
60+
| `eq` | Equals | `name eq "John"` |
61+
| `ne` | Not equals | `status ne "inactive"` |
62+
| `gt` | Greater than | `age gt 21` |
63+
| `ge` | Greater than or equal | `salary ge 50000` |
64+
| `lt` | Less than | `score lt 100` |
65+
| `le` | Less than or equal | `rating le 5` |
66+
| `sw` | Starts with | `email sw "admin"` |
67+
| `ew` | Ends with | `domain ew ".com"` |
68+
| `co` | Contains | `title co "Manager"` |
69+
| `in` | In list | `status in ["active", "pending"]` |
70+
| `pr` | Present (not null) | `phoneNumber pr` |
71+
| `and` | Logical AND | `age gt 18 and verified eq true` |
72+
| `or` | Logical OR | `role eq "admin" or role eq "moderator"` |
73+
| `not` | Logical NOT | `not(status eq "deleted")` |
74+
75+
## Translation Examples
76+
77+
Here's how the same Quaero query translates across different systems:
78+
79+
| System | Query |
80+
|-------|---------------------------------------------|
81+
| **Quaero** | `age gt 42 and department eq "Engineering"` |
82+
| **Microsoft Graph** | `age gt 42 and department eq 'Engineering'` |
83+
| **LDAP** | `(&(age>=43)(department=Engineering))` |
84+
| **SCIM** | `age gt 42 and department eq "Engineering"` |
85+
86+
### Complex Query Example
5987

6088
```csharp
61-
Filter filter = Filter.Parse("age gt 42");
62-
```
89+
// Quaero query
90+
string query = @"(department in [""Sales"", ""Marketing""] and salary ge 75000)
91+
or (role eq ""Senior Developer"" and experience gt 5)";
6392

64-
Or manually construct a filter expression using the factory methods on the `Filter` class:
93+
Filter filter = Filter.Parse(query);
6594

66-
```csharp
67-
Filter filter = Filter.GreaterThan("age", 42);
95+
// Microsoft Graph: ((department in ('Sales', 'Marketing') and salary ge 75000) or (role eq 'Senior Developer' and experience gt 5))
96+
// LDAP: (|((&(|(department=Sales)(department=Marketing))(salary>=75000))(&(role=Senior Developer)(experience>=6))))
97+
// SCIM: (department in ["Sales", "Marketing"] and salary ge 75000) or (role eq "Senior Developer" and experience gt 5)
6898
```
6999

70-
Once the filter has been successfully parsed, it can be optimized by calling the `Optimize` extension method. This will take care of removing redundancies and shortening the resulting query.
100+
## Advanced Features
71101

72-
To transform the query into an LDAP filter, reference the `Quaero.Ldap` package and call the `ToLdapFilter` method on it:
102+
### Query Optimization
73103

74104
```csharp
75-
string result = filter.ToLdapFilter();
105+
Filter filter = Filter.Parse("name eq \"John\" and name eq \"John\"");
106+
Filter optimized = filter.Optimize(); // Removes redundant conditions
76107
```
77108

78-
For Microsoft Graph, reference the `Quaero.MicrosoftGraph` package and call `ToMicrosoftGraphFilter` on the filter:
109+
### In-Memory Evaluation
79110

80111
```csharp
81-
string result = filter.ToMicrosoftGraphFilter();
82-
```
112+
record Employee(string Name, int Age, string Department);
83113

84-
And for SCIM, reference the `Quaero.Scim` package and call `ToScimFilter` on the filter:
114+
var employees = new List<Employee>
115+
{
116+
new("Alice", 30, "Engineering"),
117+
new("Bob", 45, "Sales"),
118+
new("Carol", 28, "Engineering")
119+
};
85120

86-
```csharp
87-
string result = filter.ToScimFilter();
121+
Filter filter = Filter.Parse("age gt 35 or department eq \"Engineering\"");
122+
Func<Employee, bool> predicate = filter.ToPredicate<Employee>();
123+
124+
var results = employees.Where(predicate).ToList();
125+
// Returns: Alice, Bob, Carol
88126
```
89127

90-
If you want to evaluate a filter in-memory, you can call the `ToPredicate<T>` method on it:
128+
## Error Handling
91129

92130
```csharp
93-
record Person(string Name, int Age);
94-
Func<Person, bool> predicate = filter.ToPredicate<Person>();
131+
// Safe parsing
132+
if (Filter.TryParse("invalid query", out Filter? filter))
133+
{
134+
// Success
135+
string result = filter.ToMicrosoftGraphFilter();
136+
}
137+
else
138+
{
139+
// Handle parse error
140+
Console.WriteLine("Invalid query syntax");
141+
}
95142
```
96143

97144
## Sponsors

0 commit comments

Comments
 (0)