Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 37 additions & 31 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,36 @@ Environment variables control implementation selection:
- Integration tests can switch between real and mock implementations
- Test files follow `*_test.go` pattern alongside implementation files

<<<<<<< HEAD
## API Features

### Page Size

The `query-resources` endpoint supports a `page_size` query parameter to control how many documents are returned per page.

**Query Parameter:**

- `page_size` (int, optional): Number of results per page (min: 1, max: 1000, default: 50)

**Examples:**

```bash
# Custom page size
GET /query/resources?v=1&type=project&page_size=20

# Combined with other filters
GET /query/resources?v=1&type=project&tags=active&date_field=updated_at&date_from=2025-01-01&page_size=100
```

**Interaction with post-query filters:** `cel_filter` and access control checks are applied after OpenSearch returns results, so a page may return fewer than `page_size` results.

**Implementation Details:**

- Goa design: `design/types.go` (`Sortable` type), `design/query-svc.go` (HTTP param)
- Converter: `cmd/service/converters.go` (`payloadToCriteria()` passes `p.PageSize` to criteria)
- Domain model: `internal/domain/model/search_criteria.go` (`PageSize` field)
- OpenSearch pagination: `internal/infrastructure/opensearch/client.go` (page token generated when `len(hits) == pageSize`)
- Constants: `pkg/constants/query.go` (`DefaultPageSize`, `MaxPageSize`)

### Date Range Filtering

The query service supports filtering resources by date ranges on fields within the `data` object.
Expand Down Expand Up @@ -154,17 +181,13 @@ GET /query/resources?v=1&type=project&tags=active&date_field=updated_at&date_fro
- OpenSearch query: `internal/infrastructure/opensearch/template.go` (range query with gte/lte)
- API design: `design/query-svc.go` (Goa design specification)
- Test coverage: `cmd/service/converters_test.go` (17 comprehensive test cases)
=======
## CEL Filter Feature

The service supports Common Expression Language (CEL) filtering for post-query resource filtering.
### CEL Filter

### Overview
The service supports Common Expression Language (CEL) filtering for post-query resource filtering.

CEL filtering allows API consumers to filter resources on arbitrary data fields using a safe, non-Turing complete expression language. The filter is applied after the OpenSearch query but before access control checks.

### Implementation Details

**Location**: `internal/infrastructure/filter/cel_filter.go`

**Key Components**:
Expand All @@ -178,37 +201,21 @@ CEL filtering allows API consumers to filter resources on arbitrary data fields
- Filters resources before access control checks
- Reduces number of access control checks needed

### Available Variables in CEL Expressions
**Available Variables in CEL Expressions:**

- `data` (map): Resource data object
- `resource_type` (string): Resource type
- `id` (string): Resource ID

Note: `type` is a reserved word in CEL, so we use `resource_type` instead.

### Example Usage
**Example Usage:**

```go
// API call
```bash
GET /query/resources?type=project&cel_filter=data.slug == "tlf"

// Expression is evaluated against each resource after OpenSearch query
// Only matching resources proceed to access control checks
```

### Adding CEL Filter Tests

When writing tests that involve resource search:

```go
// Use MockResourceFilter for testing
mockFilter := mock.NewMockResourceFilter()

// Pass to service constructor
service := service.NewResourceSearch(mockSearcher, mockAccessChecker, mockFilter)
```

### Common CEL Operations
**Common CEL Operations:**

- Equality: `data.status == "active"`
- Comparison: `data.priority > 5`
Expand All @@ -217,14 +224,13 @@ service := service.NewResourceSearch(mockSearcher, mockAccessChecker, mockFilter
- List membership: `data.category in ["security", "networking"]`
- Field existence: `has(data.archived)`

### Performance Considerations
**Performance Considerations:**

- Compiled CEL programs are cached (100 max entries, 5-minute TTL)
- Each resource evaluation has 100ms timeout
- Post-query filtering means pagination may return fewer results than page size
- For best performance, use specific OpenSearch criteria first, then CEL for refinement

### Important Limitations
**Important Limitations:**

**Pagination**: CEL filters apply only to results from each OpenSearch page. If the target resource is not in the first page of OpenSearch results, it won't be found even if it matches the CEL filter. Always use specific primary search criteria (`type`, `name`, `parent`) to narrow OpenSearch results first.
>>>>>>> 3e45fc4d33aba656a5abe1c3df0d3f2bd0fd6be7
CEL filters apply only to results from each OpenSearch page. If the target resource is not in the first page of OpenSearch results, it won't be found even if it matches the CEL filter. Always use specific primary search criteria (`type`, `name`, `parent`) to narrow OpenSearch results first.
2 changes: 1 addition & 1 deletion charts/lfx-v2-query-service/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ apiVersion: v2
name: lfx-v2-query-service
description: LFX Platform V2 Query Service chart
type: application
version: 0.4.10
version: 0.4.11
appVersion: "latest"
2 changes: 1 addition & 1 deletion cmd/service/converters.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func (s *querySvcsrvc) payloadToCriteria(ctx context.Context, p *querysvc.QueryR
CelFilter: p.CelFilter,
SortBy: p.Sort,
PageToken: p.PageToken,
PageSize: constants.DefaultPageSize,
PageSize: p.PageSize,
}
switch p.Sort {
case "name_asc":
Expand Down
51 changes: 36 additions & 15 deletions cmd/service/converters_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@ func TestPayloadToCriteria(t *testing.T) {
{
name: "basic payload conversion",
payload: &querysvc.QueryResourcesPayload{
Name: stringPtr("test-project"),
Type: stringPtr("project"),
Tags: []string{"active", "governance"},
Name: stringPtr("test-project"),
Type: stringPtr("project"),
Tags: []string{"active", "governance"},
PageSize: constants.DefaultPageSize,
},
expectedCriteria: model.SearchCriteria{
Name: stringPtr("test-project"),
Expand All @@ -50,8 +51,9 @@ func TestPayloadToCriteria(t *testing.T) {
{
name: "payload with parent",
payload: &querysvc.QueryResourcesPayload{
Parent: stringPtr("parent-id"),
Name: stringPtr("child-resource"),
Parent: stringPtr("parent-id"),
Name: stringPtr("child-resource"),
PageSize: constants.DefaultPageSize,
},
expectedCriteria: model.SearchCriteria{
Name: stringPtr("child-resource"),
Expand All @@ -63,8 +65,9 @@ func TestPayloadToCriteria(t *testing.T) {
{
name: "payload with sorting - name_asc",
payload: &querysvc.QueryResourcesPayload{
Name: stringPtr("test"),
Sort: "name_asc",
Name: stringPtr("test"),
Sort: "name_asc",
PageSize: constants.DefaultPageSize,
},
expectedCriteria: model.SearchCriteria{
Name: stringPtr("test"),
Expand All @@ -77,8 +80,9 @@ func TestPayloadToCriteria(t *testing.T) {
{
name: "payload with sorting - name_desc",
payload: &querysvc.QueryResourcesPayload{
Name: stringPtr("test"),
Sort: "name_desc",
Name: stringPtr("test"),
Sort: "name_desc",
PageSize: constants.DefaultPageSize,
},
expectedCriteria: model.SearchCriteria{
Name: stringPtr("test"),
Expand All @@ -91,8 +95,9 @@ func TestPayloadToCriteria(t *testing.T) {
{
name: "payload with sorting - updated_asc",
payload: &querysvc.QueryResourcesPayload{
Name: stringPtr("test"),
Sort: "updated_asc",
Name: stringPtr("test"),
Sort: "updated_asc",
PageSize: constants.DefaultPageSize,
},
expectedCriteria: model.SearchCriteria{
Name: stringPtr("test"),
Expand All @@ -105,8 +110,9 @@ func TestPayloadToCriteria(t *testing.T) {
{
name: "payload with sorting - updated_desc",
payload: &querysvc.QueryResourcesPayload{
Name: stringPtr("test"),
Sort: "updated_desc",
Name: stringPtr("test"),
Sort: "updated_desc",
PageSize: constants.DefaultPageSize,
},
expectedCriteria: model.SearchCriteria{
Name: stringPtr("test"),
Expand All @@ -116,18 +122,33 @@ func TestPayloadToCriteria(t *testing.T) {
},
expectedError: false,
},
{
name: "payload with explicit page_size",
payload: &querysvc.QueryResourcesPayload{
Name: stringPtr("test"),
PageSize: 20,
},
expectedCriteria: model.SearchCriteria{
Name: stringPtr("test"),
PageSize: 20,
},
expectedError: false,
},
{
name: "payload with invalid page token",
payload: &querysvc.QueryResourcesPayload{
Name: stringPtr("test"),
PageToken: stringPtr("invalid-token"),
PageSize: constants.DefaultPageSize,
},
expectedCriteria: model.SearchCriteria{}, // Will be empty due to error
expectedError: true,
},
{
name: "empty payload",
payload: &querysvc.QueryResourcesPayload{},
name: "empty payload",
payload: &querysvc.QueryResourcesPayload{
PageSize: constants.DefaultPageSize,
},
expectedCriteria: model.SearchCriteria{
PageSize: constants.DefaultPageSize,
},
Expand Down
1 change: 1 addition & 0 deletions design/query-svc.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ var _ = dsl.Service("query-svc", func() {
dsl.Param("cel_filter")
dsl.Param("sort")
dsl.Param("page_token")
dsl.Param("page_size")
dsl.Header("bearer_token:Authorization")
dsl.Response(dsl.StatusOK, func() {
dsl.Header("cache_control:Cache-Control")
Expand Down
6 changes: 6 additions & 0 deletions design/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ var Sortable = dsl.Type("Sortable", func() {
dsl.Attribute("page_token", dsl.String, "Opaque token for pagination", func() {
dsl.Example("****")
})
dsl.Attribute("page_size", dsl.Int, "Number of results per page", func() {
dsl.Minimum(1)
dsl.Maximum(1000)
dsl.Default(50)
dsl.Example(20)
})
})

var Resource = dsl.Type("Resource", func() {
Expand Down
10 changes: 6 additions & 4 deletions gen/http/cli/lfx_v2_query_service/cli.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading