Skip to content
Open
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
86 changes: 40 additions & 46 deletions internal/playground/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
}
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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,
}
}
Expand Down Expand Up @@ -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",
}
}

Expand All @@ -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",
}
}

Expand Down
12 changes: 3 additions & 9 deletions internal/playground/validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
},
},
{
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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")
},
},
{
Expand Down