# Clove Query Language
Clove is the JSON query language used for assertions in Checkmate.
## Overview
Clove expressions navigate and transform JSON data:
```
$[status] → Access field at root
$[user][name] → Access nested field (user.name)
$[user][profile][bio] → Deep nesting supported
$[items].length() → Call method on value
$[items][0][id] → Array index then field
$[@prev][count] → Previous response (special scope)
```
## Response Access
### `$` - Response Root
The `$` symbol represents the response body root. Access fields directly:
```yaml
# Response: {"status": "ok", "count": 5}
assertions:
- query: "$[status]"
expect: "ok"
- query: "$[count]"
expect: 5
```
### Nested Access
Chain brackets for nested fields:
```yaml
# Response: {"user": {"profile": {"name": "Alice"}}}
assertions:
- query: "$[user][profile][name]"
expect: "Alice"
```
### Array Responses
When the API returns an array at root level:
```yaml
# Response: [{"id": "a"}, {"id": "b"}]
assertions:
- query: "$.length()"
expect: 2
- query: "$[0][id]"
expect: "a"
- query: "$[1][id]"
expect: "b"
```
### `$[@prev]` - Previous Response
In multi-request tests, access the previous response using the `@prev` scope:
```yaml
tests:
counter_test:
requests: [increment, increment]
assertions:
- query: "$[count]"
expect_gt: "$[@prev][count]"
```
Note: The `@` prefix denotes special scopes. `[@prev]` is a scope selector, not a field name.
---
## Navigation
### Object Fields
Access fields with bracket notation:
```yaml
# Response: {"user": {"name": "John", "profile": {"bio": "Dev"}}}
assertions:
- query: "$[user]" # {"name": "John", ...}
- query: "$[user][name]" # "John"
- query: "$[user][profile][bio]" # "Dev"
```
### Array Elements
Access by index (0-based):
```yaml
# Response: {"items": [{"id": 1}, {"id": 2}]}
assertions:
- query: "$[items][0]" # First element
- query: "$[items][1]" # Second element
- query: "$[items][0][id]" # First item's id
```
### Existence Check
Append `?` to check if field exists (returns true/false):
```yaml
- query: "$[optional_field]?"
expect: true
- query: "$[user][middle_name]?"
expect: false
```
---
## Methods
### Array Methods
| Method | Description | Example |
|--------|-------------|---------|
| `.length()` | Array length | `$[items].length()` |
| `.first()` | First element | `$[items].first()` |
| `.last()` | Last element | `$[items].last()` |
| `.reverse()` | Reverse array | `$[items].reverse()` |
| `.unique()` | Remove duplicates | `$[tags].unique()` |
| `.flatten()` | Flatten nested arrays | `$[matrix].flatten()` |
### String Methods
| Method | Description | Example |
|--------|-------------|---------|
| `.length()` | String length | `$[name].length()` |
| `.upper()` | Uppercase | `$[code].upper()` |
| `.lower()` | Lowercase | `$[email].lower()` |
| `.trim()` | Remove whitespace | `$[input].trim()` |
| `.split(sep)` | Split string | `$[tags].split(",")` |
### Number Methods
| Method | Description | Example |
|--------|-------------|---------|
| `.round()` | Round to integer | `$[price].round()` |
| `.floor()` | Round down | `$[rating].floor()` |
| `.ceil()` | Round up | `$[score].ceil()` |
| `.abs()` | Absolute value | `$[delta].abs()` |
### Object Methods
| Method | Description | Example |
|--------|-------------|---------|
| `.keys()` | Get keys | `$[config].keys()` |
| `.values()` | Get values | `$[config].values()` |
---
## Chaining
Chain multiple operations:
```
$[items].first()[name].upper()
$[tags].unique().length()
$[users].last()[email].lower()
```
---
## Common Response Patterns
### Object Response (most common)
```yaml
# POST /api/users returns: {"id": "abc", "name": "John", "created_at": "..."}
assertions:
- query: "$[id]"
expect_type: string
- query: "$[name]"
expect: "John"
- query: "$[created_at]?"
expect: true
```
### Array Response
```yaml
# GET /api/users returns: [{"id": "a"}, {"id": "b"}]
assertions:
- query: "$.length()"
expect_gte: 1
- query: "$[0][id]"
expect_type: string
```
### Nested Data Response
```yaml
# Response: {"data": {"user": {"name": "John"}}, "meta": {"page": 1}}
assertions:
- query: "$[data][user][name]"
expect: "John"
- query: "$[meta][page]"
expect: 1
```
### Wrapped Array Response
```yaml
# Response: {"items": [{"id": 1}, {"id": 2}], "total": 2}
assertions:
- query: "$[items].length()"
expect: 2
- query: "$[total]"
expect: 2
- query: "$[items][0][id]"
expect: 1
```
---
## Using in Assertions
### Basic Comparison
```yaml
- query: "$[status]"
expect: "active"
```
### Type Check
```yaml
- query: "$[items]"
expect_type: array
```
### Length Check
```yaml
- query: "$[items].length()"
expect_gte: 1
```
### Existence Check
```yaml
- query: "$[optional_field]?"
expect: true
```
### Compare to Previous
```yaml
- query: "$[counter]"
expect_gt: "$[@prev][counter]"
```
---
## CLI Testing
Test Clove expressions from command line:
```bash
# Test query against JSON data
cm clove check '$[name]' -i '{"name": "John"}'
# Array at root
cm clove check '$.length()' -i '[1, 2, 3]'
# Nested access
cm clove check '$[user][name]' -i '{"user": {"name": "Alice"}}'
```
---
## Error Handling
If a path doesn't exist:
- Without `?`: Returns error
- With `?`: Returns `false`
```yaml
# This will error if field missing
- query: "$[required_field]"
expect_type: string
# This handles missing gracefully
- query: "$[optional_field]?"
expect: true
```
---
## See Also
- [Assertions](ASSERTIONS.md) - Using queries in assertions
- [Test Specs](TEST_SPECS.md) - Complete spec format