Skip to content

Commit 663badd

Browse files
Add validate_expression_tool for Mapbox expression validation
This commit adds a comprehensive Mapbox expression validation tool that performs offline validation of style expressions without requiring API access. Features: - Validates all Mapbox expression operators (90+ operators supported) - Checks expression syntax (array format, string operators) - Validates argument counts for each operator - Recursive validation of nested expressions - Detects and warns about deeply nested expressions (depth > 10) - Returns structured errors, warnings, and info messages with suggestions - Provides metadata (expressionType, returnType, depth) - Accepts both JSON strings and expression arrays as input Supported operator categories: - Decision: case, match, coalesce - Lookup: get, has, in, index-of, length - Math: +, -, *, /, %, ^, min, max, round, floor, ceil, abs, sqrt, log10, log2, ln, e, pi - Comparison: ==, !=, >, <, >=, <= - Logical: !, all, any - String: concat, downcase, upcase - Color: rgb, rgba, to-rgba - Type conversion: array, boolean, number, string, to-boolean, to-color, to-number, to-string, typeof - Interpolation: interpolate, step - Feature data: get, has, feature-state, geometry-type, id, properties - Camera: zoom, pitch, distance-from-center - Variable binding: let, var The tool is implemented as a BaseTool (offline, no HttpRequest dependency) and includes 25 comprehensive test cases covering literals, valid expressions, invalid expressions, nested expressions, error handling, and metadata. Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
1 parent e279a15 commit 663badd

File tree

7 files changed

+865
-1
lines changed

7 files changed

+865
-1
lines changed

README.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,87 @@ Generate a geojson.io URL to visualize GeoJSON data. This tool:
357357
- "Generate a preview URL for this GeoJSON data"
358358
- "Create a geojson.io link for my uploaded route.geojson file"
359359

360+
#### Validate Expression tool
361+
362+
Validates Mapbox style expressions for syntax, operators, and argument correctness. This offline validation tool performs comprehensive checks on Mapbox expressions without requiring API access.
363+
364+
**Parameters:**
365+
366+
- `expression` (string or array, required): Mapbox expression to validate (JSON string or expression array)
367+
- `context` (string, optional): Context where the expression will be used ("style", "filter", "layout", "paint")
368+
369+
**What it validates:**
370+
371+
- Expression syntax (array format with operator as first element)
372+
- Valid operators (get, case, match, interpolate, math operators, etc.)
373+
- Correct argument counts for each operator
374+
- Nested expression validation
375+
- Expression depth (warns about deeply nested expressions)
376+
377+
**Returns:**
378+
379+
Validation results including:
380+
381+
- `valid` (boolean): Overall validity
382+
- `errors` (array): Critical errors that make the expression invalid
383+
- `warnings` (array): Non-critical issues (e.g., deeply nested expressions)
384+
- `info` (array): Informational messages
385+
- `metadata`: Object with expressionType, returnType, and depth
386+
387+
Each issue includes:
388+
389+
- `severity`: "error", "warning", or "info"
390+
- `message`: Description of the issue
391+
- `path`: Path to the problem in the expression (optional)
392+
- `suggestion`: How to fix the issue (optional)
393+
394+
**Supported operators:**
395+
396+
- **Decision**: case, match, coalesce
397+
- **Lookup**: get, has, in, index-of, length, slice
398+
- **Math**: +, -, \*, /, %, ^, min, max, round, floor, ceil, abs, sqrt, log10, log2, ln, e, pi
399+
- **Comparison**: ==, !=, >, <, >=, <=
400+
- **Logical**: !, all, any
401+
- **String**: concat, downcase, upcase
402+
- **Color**: rgb, rgba, to-rgba
403+
- **Type conversion**: array, boolean, number, string, to-boolean, to-color, to-number, to-string, typeof
404+
- **Interpolation**: interpolate, step
405+
- **Feature data**: get, has, feature-state, geometry-type, id, properties
406+
- **Camera**: zoom, pitch, distance-from-center
407+
- **Variable binding**: let, var
408+
409+
**Example:**
410+
411+
```json
412+
{
413+
"expression": ["get", "population"]
414+
}
415+
```
416+
417+
**Returns:**
418+
419+
```json
420+
{
421+
"valid": true,
422+
"errors": [],
423+
"warnings": [],
424+
"info": [],
425+
"metadata": {
426+
"expressionType": "get",
427+
"returnType": "any",
428+
"depth": 0
429+
}
430+
}
431+
```
432+
433+
**Example prompts:**
434+
435+
- "Validate this Mapbox expression: ['get', 'name']"
436+
- "Check if my case expression is correctly formatted"
437+
- "Is this interpolate expression valid?"
438+
439+
**Note:** This is an offline validation tool that doesn't require API access or token scopes.
440+
360441
#### Coordinate Conversion tool
361442

362443
Convert coordinates between different coordinate reference systems (CRS), specifically between WGS84 (EPSG:4326) and Web Mercator (EPSG:3857).

src/tools/toolRegistry.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { StyleBuilderTool } from './style-builder-tool/StyleBuilderTool.js';
2020
import { StyleComparisonTool } from './style-comparison-tool/StyleComparisonTool.js';
2121
import { TilequeryTool } from './tilequery-tool/TilequeryTool.js';
2222
import { UpdateStyleTool } from './update-style-tool/UpdateStyleTool.js';
23+
import { ValidateExpressionTool } from './validate-expression-tool/ValidateExpressionTool.js';
2324
import { httpRequest } from '../utils/httpPipeline.js';
2425

2526
// Central registry of all tools
@@ -42,7 +43,8 @@ export const ALL_TOOLS = [
4243
new GetMapboxDocSourceTool({ httpRequest }),
4344
new GetReferenceTool(),
4445
new StyleComparisonTool(),
45-
new TilequeryTool({ httpRequest })
46+
new TilequeryTool({ httpRequest }),
47+
new ValidateExpressionTool()
4648
] as const;
4749

4850
export type ToolInstance = (typeof ALL_TOOLS)[number];
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright (c) Mapbox, Inc.
2+
// Licensed under the MIT License.
3+
4+
import { z } from 'zod';
5+
6+
export const ValidateExpressionInputSchema = z.object({
7+
expression: z
8+
.union([z.string(), z.any()])
9+
.describe(
10+
'Mapbox expression to validate (JSON string or expression array)'
11+
),
12+
context: z
13+
.enum(['style', 'filter', 'layout', 'paint'])
14+
.optional()
15+
.describe('Context where the expression will be used')
16+
});
17+
18+
export type ValidateExpressionInput = z.infer<
19+
typeof ValidateExpressionInputSchema
20+
>;
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright (c) Mapbox, Inc.
2+
// Licensed under the MIT License.
3+
4+
import { z } from 'zod';
5+
6+
const ExpressionIssueSchema = z.object({
7+
severity: z.enum(['error', 'warning', 'info']).describe('Issue severity'),
8+
message: z.string().describe('Description of the issue'),
9+
path: z.string().optional().describe('Path to the problem in the expression'),
10+
suggestion: z.string().optional().describe('How to fix the issue')
11+
});
12+
13+
export const ValidateExpressionOutputSchema = z.object({
14+
valid: z.boolean().describe('Whether the expression is valid'),
15+
errors: z.array(ExpressionIssueSchema).describe('Critical errors'),
16+
warnings: z.array(ExpressionIssueSchema).describe('Non-critical warnings'),
17+
info: z.array(ExpressionIssueSchema).describe('Informational messages'),
18+
metadata: z
19+
.object({
20+
expressionType: z
21+
.string()
22+
.optional()
23+
.describe('Detected expression type (e.g., "literal", "get", "match")'),
24+
returnType: z
25+
.string()
26+
.optional()
27+
.describe('Expected return type of the expression'),
28+
depth: z.number().optional().describe('Maximum nesting depth')
29+
})
30+
.describe('Expression metadata')
31+
});
32+
33+
export type ValidateExpressionOutput = z.infer<
34+
typeof ValidateExpressionOutputSchema
35+
>;
36+
export type ExpressionIssue = z.infer<typeof ExpressionIssueSchema>;

0 commit comments

Comments
 (0)