diff --git a/internal/playground/validation.go b/internal/playground/validation.go index ede8066..924b9d7 100644 --- a/internal/playground/validation.go +++ b/internal/playground/validation.go @@ -142,8 +142,10 @@ func ValidateRequestWithPathItem(r *http.Request, op *Operation, specPath string } if value != "" { // Enhanced validation for array parameters - if err := validateQueryParameterValue(value, param, spec); err != nil { - errors = append(errors, err) + for _, err := range validateQueryParameterValue(value, param, spec) { + if err != nil { + errors = append(errors, err) + } } } } @@ -179,7 +181,7 @@ func extractPathParameter(requestPath, paramName, specPath string) string { // validateQueryParameterValue validates a query parameter value against its schema // Enhanced version that handles arrays and special formats -func validateQueryParameterValue(value string, param Parameter, spec *OpenAPISpec) *ValidationError { +func validateQueryParameterValue(value string, param Parameter, spec *OpenAPISpec) []*ValidationError { // Check if parameter is an array type (comma-separated) if param.Schema != nil { schema := param.Schema @@ -199,63 +201,63 @@ func validateQueryParameterValue(value string, param Parameter, spec *OpenAPISpe return validateArrayParameter(value, param, schema, spec) } } - + // Fall back to standard validation - return validateParameterValue(value, param, spec) + return []*ValidationError{validateParameterValue(value, param, spec)} } // validateArrayParameter validates a comma-separated array parameter -func validateArrayParameter(value string, param Parameter, schema map[string]interface{}, spec *OpenAPISpec) *ValidationError { +func validateArrayParameter(value string, param Parameter, schema map[string]interface{}, spec *OpenAPISpec) []*ValidationError { // Split comma-separated values values := strings.Split(value, ",") - + // Validate minItems if minItems, ok := schema["minItems"].(float64); ok { if len(values) < int(minItems) { - return &ValidationError{ + return []*ValidationError{{ Parameter: param.Name, Message: fmt.Sprintf("array must have at least %d items", int(minItems)), Value: value, - } + }} } } - + // Validate maxItems if maxItems, ok := schema["maxItems"].(float64); ok { if len(values) > int(maxItems) { - return &ValidationError{ + return []*ValidationError{{ Parameter: param.Name, Message: fmt.Sprintf("array must have at most %d items", int(maxItems)), Value: value, - } + }} } } - - // Validate each item + + // Validate each item — collect all errors (real API returns one per invalid value) + var errs []*ValidationError if items, ok := schema["items"].(map[string]interface{}); ok { - for i, itemValue := range values { + for _, itemValue := range values { itemValue = strings.TrimSpace(itemValue) if itemValue == "" { - continue // Skip empty values + continue } - - // Create a temporary parameter for item validation + itemParam := Parameter{ - Name: fmt.Sprintf("%s[%d]", param.Name, i), + Name: param.Name, Schema: items, } - + if err := validateParameterValue(itemValue, itemParam, spec); err != nil { - return &ValidationError{ + errs = append(errs, &ValidationError{ Parameter: param.Name, - Message: fmt.Sprintf("invalid value '%s' at index %d: %s", itemValue, i, err.Message), + Message: err.Message, Value: value, - } + }) } } } - - return nil + + return errs } // validateParameterValue validates a parameter value against its schema @@ -331,9 +333,15 @@ func validateParameterValue(value string, param Parameter, spec *OpenAPISpec) *V } } if !valid { + enumStrs := make([]string, 0, len(enum)) + for _, e := range enum { + if eStr, ok := e.(string); ok { + enumStrs = append(enumStrs, eStr) + } + } return &ValidationError{ Parameter: param.Name, - Message: fmt.Sprintf("value must be one of: %v", enum), + Message: fmt.Sprintf("The `%s` query parameter value [%s] is not one of [%s]", param.Name, value, strings.Join(enumStrs, ",")), Value: value, } } @@ -657,7 +665,7 @@ func FormatValidationErrors(errors []*ValidationError) map[string]interface{} { "errors": errorList, "title": "Invalid Request", "detail": "One or more parameters to your request was invalid.", - "type": "https://api.twitter.com/2/problems/invalid-request", + "type": "https://api.x.com/2/problems/invalid-request", } } @@ -674,31 +682,17 @@ func FormatSingleValidationError(err *ValidationError) map[string]interface{} { } errorObj := map[string]interface{}{ - "parameter": err.Parameter, - "value": valueStr, - "detail": err.Message, - "title": "Invalid Request", - "type": "https://api.twitter.com/2/problems/invalid-request", - } - - // Add resource_id and resource_type if provided - if err.ResourceID != "" { - errorObj["resource_id"] = err.ResourceID - } - if err.ResourceType != "" { - errorObj["resource_type"] = err.ResourceType - } - - // Also include parameters map for compatibility - errorObj["parameters"] = map[string]interface{}{ - err.Parameter: []string{valueStr}, + "parameters": map[string]interface{}{ + err.Parameter: []string{valueStr}, + }, + "message": err.Message, } return map[string]interface{}{ "errors": []map[string]interface{}{errorObj}, "title": "Invalid Request", "detail": "One or more parameters to your request was invalid.", - "type": "https://api.twitter.com/2/problems/invalid-request", + "type": "https://api.x.com/2/problems/invalid-request", } } diff --git a/internal/playground/validation_test.go b/internal/playground/validation_test.go index 17aab41..80142cf 100644 --- a/internal/playground/validation_test.go +++ b/internal/playground/validation_test.go @@ -366,7 +366,7 @@ func TestValidateParameterValue_String(t *testing.T) { wantError: true, checkMsg: func(t *testing.T, err *ValidationError) { assert.Equal(t, "visibility", err.Parameter) - assert.Contains(t, err.Message, "must be one of") + assert.Contains(t, err.Message, "is not one of") }, }, { @@ -878,7 +878,7 @@ func TestFormatValidationErrors(t *testing.T) { require.NotNil(t, res) assert.Equal(t, "Invalid Request", res["title"]) assert.Equal(t, "One or more parameters to your request was invalid.", res["detail"]) - assert.Equal(t, "https://api.twitter.com/2/problems/invalid-request", res["type"]) + assert.Equal(t, "https://api.x.com/2/problems/invalid-request", res["type"]) errors, ok := res["errors"].([]map[string]interface{}) require.True(t, ok) @@ -962,18 +962,12 @@ func TestFormatSingleValidationError(t *testing.T) { require.NotNil(t, res) assert.Equal(t, "Invalid Request", res["title"]) assert.Equal(t, "One or more parameters to your request was invalid.", res["detail"]) - assert.Equal(t, "https://api.twitter.com/2/problems/invalid-request", res["type"]) + assert.Equal(t, "https://api.x.com/2/problems/invalid-request", res["type"]) errors, ok := res["errors"].([]map[string]interface{}) require.True(t, ok) assert.Len(t, errors, 1) assert.Equal(t, "value must be at least 10", errors[0]["message"]) - - params, ok := errors[0]["parameters"].(map[string]interface{}) - require.True(t, ok) - values, ok := params["max_results"].([]string) - require.True(t, ok) - assert.Contains(t, values, "5") }, }, {