Skip to content

Commit 90324ef

Browse files
committed
Added line / col and security scheme tracking.
After witing this up into wiretap, I realized there are a couple of small gaps. This is a breaking change, but as I just released 10x last night, and it’s new years eve, I am going to say that no-one alive cares.
1 parent 88448a4 commit 90324ef

15 files changed

Lines changed: 940 additions & 13 deletions

config/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ var defaultIgnoredHeaders = []string{
230230
"if-match", "if-none-match", "if-modified-since",
231231
"last-modified", "transfer-encoding", "vary", "x-forwarded-for",
232232
"x-forwarded-proto", "x-real-ip", "x-request-id",
233+
"request-start-time", // Added by some API clients for timing
233234
}
234235

235236
// GetEffectiveStrictIgnoredHeaders returns the list of headers to ignore

errors/strict_errors.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ func UndeclaredPropertyError(
2828
direction string,
2929
requestPath string,
3030
requestMethod string,
31+
specLine int,
32+
specCol int,
3133
) *ValidationError {
3234
dirStr := direction
3335
if dirStr == "" {
@@ -47,6 +49,8 @@ func UndeclaredPropertyError(
4749
RequestMethod: requestMethod,
4850
ParameterName: name,
4951
Context: truncateForContext(value),
52+
SpecLine: specLine,
53+
SpecCol: specCol,
5054
}
5155
}
5256

errors/strict_errors_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ func TestUndeclaredPropertyError(t *testing.T) {
1818
"request",
1919
"/users",
2020
"POST",
21+
42,
22+
10,
2123
)
2224

2325
assert.NotNil(t, err)
@@ -30,6 +32,8 @@ func TestUndeclaredPropertyError(t *testing.T) {
3032
assert.Equal(t, "/users", err.RequestPath)
3133
assert.Equal(t, "POST", err.RequestMethod)
3234
assert.Equal(t, "extra", err.ParameterName)
35+
assert.Equal(t, 42, err.SpecLine)
36+
assert.Equal(t, 10, err.SpecCol)
3337
}
3438

3539
func TestUndeclaredPropertyError_Response(t *testing.T) {
@@ -41,12 +45,16 @@ func TestUndeclaredPropertyError_Response(t *testing.T) {
4145
"response",
4246
"/items/123",
4347
"GET",
48+
100,
49+
5,
4450
)
4551

4652
assert.NotNil(t, err)
4753
assert.Contains(t, err.Message, "response property 'undeclared'")
4854
assert.Contains(t, err.Reason, "id, name")
4955
assert.Equal(t, "{...}", err.Context) // Map truncated
56+
assert.Equal(t, 100, err.SpecLine)
57+
assert.Equal(t, 5, err.SpecCol)
5058
}
5159

5260
func TestUndeclaredPropertyError_EmptyDirection(t *testing.T) {
@@ -58,9 +66,13 @@ func TestUndeclaredPropertyError_EmptyDirection(t *testing.T) {
5866
"", // Empty direction defaults to "request"
5967
"/test",
6068
"POST",
69+
0, // Zero values for unknown location
70+
0,
6171
)
6272

6373
assert.Contains(t, err.Message, "request property")
74+
assert.Equal(t, 0, err.SpecLine)
75+
assert.Equal(t, 0, err.SpecCol)
6476
}
6577

6678
func TestUndeclaredHeaderError(t *testing.T) {

helpers/parameter_utilities.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,59 @@ func ExtractSecurityForOperation(request *http.Request, item *v3.PathItem) []*ba
105105
return schemes
106106
}
107107

108+
// ExtractSecurityHeaderNames extracts header names from applicable security schemes.
109+
// Returns header names from apiKey schemes with in:"header", plus "Authorization"
110+
// for http/oauth2/openIdConnect schemes.
111+
//
112+
// This function is used by strict mode validation to recognize security headers
113+
// as "declared" headers that should not trigger undeclared header errors.
114+
func ExtractSecurityHeaderNames(
115+
security []*base.SecurityRequirement,
116+
securitySchemes map[string]*v3.SecurityScheme,
117+
) []string {
118+
if security == nil || securitySchemes == nil {
119+
return nil
120+
}
121+
122+
seen := make(map[string]bool)
123+
var headers []string
124+
125+
for _, sec := range security {
126+
if sec == nil || sec.ContainsEmptyRequirement {
127+
continue // No security required for this option
128+
}
129+
130+
if sec.Requirements == nil {
131+
continue
132+
}
133+
134+
for pair := sec.Requirements.First(); pair != nil; pair = pair.Next() {
135+
schemeName := pair.Key()
136+
scheme, ok := securitySchemes[schemeName]
137+
if !ok || scheme == nil {
138+
continue
139+
}
140+
141+
var headerName string
142+
switch strings.ToLower(scheme.Type) {
143+
case "apikey":
144+
if strings.ToLower(scheme.In) == Header {
145+
headerName = scheme.Name
146+
}
147+
case "http", "oauth2", "openidconnect":
148+
headerName = "Authorization"
149+
}
150+
151+
if headerName != "" && !seen[strings.ToLower(headerName)] {
152+
seen[strings.ToLower(headerName)] = true
153+
headers = append(headers, headerName)
154+
}
155+
}
156+
}
157+
158+
return headers
159+
}
160+
108161
func cast(v string) any {
109162
if v == "true" || v == "false" {
110163
b, _ := strconv.ParseBool(v)

0 commit comments

Comments
 (0)