Skip to content

Commit a7c082b

Browse files
feat: multi validation errors handling for MCP tools (#15)
* ref: convert float64 to int64 instead of int to prevent precision loss and ensure consistent behavior across platforms * feat: collect validation errors for tools * chore: improve error handling and code formatting * chore: revert int to int64 conversion * ref: implement fluent validation pattern for razorpay tools * fix: lint errors * ref: improve validation error handling pattern across API tools * fix: define the error handler func
1 parent de18a8a commit a7c082b

12 files changed

+820
-656
lines changed

.cursor/rules/new-tool-from-docs.mdc

+27-14
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,10 @@ Upon receiving this rule invocation:
5959

6060
Before the implementation use the documentation URL provided to figure out the request contract, required parameters, descriptions of the parameters, and the response contract.
6161

62-
Now follow the detailed implementation guide in [pkg/razorpay/README.md](../pkg/razorpay/README.md) for creating tools and start making code changes.
62+
Now follow the detailed implementation guide in [pkg/razorpay/README.md](mdc:../pkg/razorpay/README.md) for creating tools and start making code changes.
6363

6464
Other guidelines:
65-
1. [Razorpay Go SDK Constants](https://github.com/razorpay/razorpay-go/blob/master/constants/url.go) - Use these constants for specifying the api endpoints while writing the tests.
65+
1. [Razorpay Go SDK Constants](mdc:https:/github.com/razorpay/razorpay-go/blob/master/constants/url.go) - Use these constants for specifying the api endpoints while writing the tests.
6666
2. Use the payload and response from the docs provided to write the positive test case for the tool.
6767

6868
STYLE:
@@ -72,7 +72,7 @@ IMPORTANT: You **MUST** ALWAYS go through the Post Implementation steps once the
7272

7373
## Implementation References
7474

75-
For detailed code patterns and examples, refer to the following sections in the [pkg/razorpay/README.md](../pkg/razorpay/README.md):
75+
For detailed code patterns and examples, refer to the following sections in the [pkg/razorpay/README.md](mdc:../pkg/razorpay/README.md):
7676

7777
- **Tool Structure**: See the "Tool Structure" section for the function template
7878
- **Parameter Definition**: See the "Parameter Definition" section for defining parameters
@@ -160,7 +160,7 @@ If any checklist items remain unchecked, complete them before proceeding. The im
160160
Your tests MUST include:
161161
- Positive test case with all parameters
162162
- Negative test case for EACH required parameter
163-
- Negative test case for validation failures (e.g., wrong types)
163+
- Negative test case for multiple validation failures (e.g., wrong types)
164164
- Any edge cases specific to this tool
165165

166166
## ⚠️ Required Verification Steps ⚠️
@@ -192,16 +192,29 @@ Contains all Razorpay API tool implementations, including:
192192
- Parameter validation functions
193193
- Request/response handling
194194

195-
### `pkg/razorpay/server.go` - Core Utilities
196-
197-
**Path:** `pkg/razorpay/server.go`
198-
199-
Contains essential utility functions used by all tools:
200-
- `RequiredParam[T]` - Type-safe required parameter extraction
201-
- `OptionalParam[T]` - Type-safe optional parameter extraction
202-
- `RequiredInt` - Integer parameter extraction
203-
- `OptionalInt` - Optional integer parameter extraction
204-
- `HandleValidationError` - Common validation error handling
195+
### `pkg/razorpay/tools_params.go` - Fluent Validator
196+
197+
**Path:** `pkg/razorpay/tools_params.go`
198+
199+
Contains the fluent validator implementation for parameter validation:
200+
- `Validator` - Type that provides a fluent interface for validation
201+
- `NewValidator` - Creates a new validator for a request
202+
- `HasErrors` - Checks if validation errors exist
203+
- `HandleErrorsIfAny` - Formats all errors into a tool result
204+
- `ValidateAndAddRequiredString` - Validates required string parameters
205+
- `ValidateAndAddOptionalString` - Validates optional string parameters
206+
- `ValidateAndAddRequiredInt` - Validates required integer parameters
207+
- `ValidateAndAddOptionalInt` - Validates optional integer parameters
208+
- `ValidateAndAddRequiredFloat` - Validates required float parameters
209+
- `ValidateAndAddOptionalFloat` - Validates optional float parameters
210+
- `ValidateAndAddRequiredBool` - Validates required boolean parameters
211+
- `ValidateAndAddOptionalBool` - Validates optional boolean parameters
212+
- `ValidateAndAddRequiredMap` - Validates required map parameters
213+
- `ValidateAndAddOptionalMap` - Validates optional map parameters
214+
- `ValidateAndAddRequiredArray` - Validates required array parameters
215+
- `ValidateAndAddOptionalArray` - Validates optional array parameters
216+
- `ValidateAndAddPagination` - Validates and adds pagination parameters
217+
- `ValidateAndAddExpand` - Validates and adds expand parameters
205218

206219
### `pkg/razorpay/test_helpers.go` - Testing Utilities
207220

pkg/razorpay/README.md

+55-51
Original file line numberDiff line numberDiff line change
@@ -79,32 +79,31 @@ Available parameter types:
7979

8080
### Parameter Validation
8181

82-
Inside the handler function, use the helper functions for fetching the parameters and also enforcing the mandatory parameters:
82+
Inside the handler function, use the fluent validator pattern for parameter validation. This provides cleaner, more readable code through method chaining:
8383

8484
```go
85-
// Required parameters
86-
id, err := RequiredParam[string](r, "id")
87-
if result, err := HandleValidationError(err); result != nil {
88-
return result, err
89-
}
85+
// Create a new validator
86+
v := NewValidator(&r)
9087

91-
// Optional parameters
92-
description, err := OptionalParam[string](r, "description")
93-
if result, err := HandleValidationError(err); result != nil {
94-
return result, err
95-
}
88+
// Create a map for API request parameters
89+
payload := make(map[string]interface{})
9690

97-
// Required integers
98-
amount, err := RequiredInt(r, "amount")
99-
if result, err := HandleValidationError(err); result != nil {
100-
return result, err
101-
}
91+
// Validate and add parameters to the payload with method chaining
92+
v.ValidateAndAddRequiredString(payload, "id").
93+
ValidateAndAddOptionalString(payload, "description").
94+
ValidateAndAddRequiredInt(payload, "amount").
95+
ValidateAndAddOptionalInt(payload, "limit")
96+
97+
// Validate and add common parameters
98+
v.ValidateAndAddPagination(payload).
99+
ValidateAndAddExpand(payload)
102100

103-
// Optional integers
104-
limit, err := OptionalInt(r, "limit")
105-
if result, err := HandleValidationError(err); result != nil {
106-
return result, err
101+
// Check for validation errors
102+
if result, err := validator.HandleErrorsIfAny(); result != nil {
103+
return result, err
107104
}
105+
106+
// Proceed with API call using validated parameters in payload
108107
```
109108

110109
### Example: GET Endpoint
@@ -127,11 +126,18 @@ func FetchResource(
127126
ctx context.Context,
128127
r mcpgo.CallToolRequest,
129128
) (*mcpgo.ToolResult, error) {
130-
id, err := RequiredParam[string](r, "id")
131-
if result, err := HandleValidationError(err); result != nil {
132-
return result, err
133-
}
129+
// Create validator and a payload map
130+
payload := make(map[string]interface{})
131+
v := NewValidator(&r).
132+
ValidateAndAddRequiredString(payload, "id")
133+
134+
// Check for validation errors
135+
if result, err := validator.HandleErrorsIfAny(); result != nil {
136+
return result, err
137+
}
134138

139+
// Extract validated ID and make API call
140+
id := payload["id"].(string)
135141
resource, err := client.Resource.Fetch(id, nil, nil)
136142
if err != nil {
137143
return mcpgo.NewToolResultError(
@@ -179,34 +185,19 @@ func CreateResource(
179185
ctx context.Context,
180186
r mcpgo.CallToolRequest,
181187
) (*mcpgo.ToolResult, error) {
182-
// Required parameters
183-
amount, err := RequiredInt(r, "amount")
184-
if result, err := HandleValidationError(err); result != nil {
185-
return result, err
186-
}
188+
// Create payload map and validator
189+
data := make(map[string]interface{})
190+
v := NewValidator(&r).
191+
ValidateAndAddRequiredInt(data, "amount").
192+
ValidateAndAddRequiredString(data, "currency").
193+
ValidateAndAddOptionalString(data, "description")
187194

188-
currency, err := RequiredParam[string](r, "currency")
189-
if result, err := HandleValidationError(err); result != nil {
190-
return result, err
191-
}
195+
// Check for validation errors
196+
if result, err := validator.HandleErrorsIfAny(); result != nil {
197+
return result, err
198+
}
192199

193-
// Create request payload
194-
data := map[string]interface{}{
195-
"amount": amount,
196-
"currency": currency,
197-
}
198-
199-
// Optional parameters
200-
description, err := OptionalParam[string](r, "description")
201-
if result, err := HandleValidationError(err); result != nil {
202-
return result, err
203-
}
204-
205-
if description != "" {
206-
data["description"] = description
207-
}
208-
209-
// Call the API
200+
// Call the API with validated data
210201
resource, err := client.Resource.Create(data, nil)
211202
if err != nil {
212203
return mcpgo.NewToolResultError(
@@ -346,6 +337,16 @@ func Test_ToolName(t *testing.T) {
346337
ExpectError: true,
347338
ExpectedErrMsg: "missing required parameter: param1",
348339
},
340+
{
341+
Name: "multiple validation errors",
342+
Request: map[string]interface{}{
343+
// Missing required parameters and/or including invalid types
344+
"optional_param": "invalid_type", // Wrong type for a parameter
345+
},
346+
MockHttpClient: nil, // No HTTP client needed for validation errors
347+
ExpectError: true,
348+
ExpectedErrMsg: "Validation errors:\n- missing required parameter: param1\n- invalid parameter type: optional_param",
349+
},
349350
// Additional test cases for other scenarios
350351
}
351352

@@ -390,7 +391,10 @@ After adding a new tool, Update the "Available Tools" section in the README.md i
390391

391392
2. **Error Handling**: Always provide clear error messages
392393

393-
3. **Validation**: Always validate required parameters
394+
3. **Validation**: Always validate required parameters and collect all validation errors before returning using fluent validator pattern.
395+
- Use the `NewValidator` to create a validator
396+
- Chain validation methods (`ValidateAndAddRequiredString`, etc.)
397+
- Return formatted errors with `HandleErrorsIfAny()`
394398

395399
4. **Documentation**: Describe all the parameters clearly for the LLMs to understand.
396400

0 commit comments

Comments
 (0)