|
4 | 4 | "encoding/json" |
5 | 5 | "errors" |
6 | 6 | "fmt" |
| 7 | + "reflect" |
7 | 8 | "strings" |
8 | 9 |
|
9 | 10 | "cuelang.org/go/cue" |
@@ -204,188 +205,92 @@ func getResolvedManifest(manifest string) (string, error) { |
204 | 205 | return string(resolved), nil |
205 | 206 | } |
206 | 207 |
|
207 | | -// clearDocRefs clears $ref strings across the entire OpenAPI document. |
| 208 | +// clearDocRefs uses reflection to walk the entire OpenAPI document and clear |
| 209 | +// all $ref strings so that json.Marshal outputs fully inlined schemas. |
| 210 | +// It uses two tracking mechanisms: |
| 211 | +// - visited: permanent set for general pointers to avoid re-processing |
| 212 | +// - schemaStack: path-based set for *Schema pointers to detect circular |
| 213 | +// schema references (add on enter, remove on exit), allowing the same |
| 214 | +// schema to appear in multiple non-circular positions |
208 | 215 | func clearDocRefs(doc *openapi3.T) { |
209 | | - stack := make(map[*openapi3.Schema]bool) |
210 | | - visited := make(map[*openapi3.PathItem]bool) |
| 216 | + visited := make(map[uintptr]bool) |
| 217 | + schemaStack := make(map[uintptr]bool) |
| 218 | + walkAndClearRefs(reflect.ValueOf(doc), visited, schemaStack) |
| 219 | +} |
211 | 220 |
|
212 | | - if doc.Components != nil { |
213 | | - for _, sr := range doc.Components.Schemas { |
214 | | - clearSchemaRefs(sr, stack) |
215 | | - } |
216 | | - for _, pr := range doc.Components.Parameters { |
217 | | - clearParameterRefs(pr, stack, visited) |
218 | | - } |
219 | | - for _, hr := range doc.Components.Headers { |
220 | | - clearHeaderRefs(hr, stack, visited) |
221 | | - } |
222 | | - for _, rb := range doc.Components.RequestBodies { |
223 | | - clearRequestBodyRefs(rb, stack, visited) |
| 221 | +var schemaRefType = reflect.TypeOf((*openapi3.SchemaRef)(nil)) |
| 222 | + |
| 223 | +func walkAndClearRefs(v reflect.Value, visited map[uintptr]bool, schemaStack map[uintptr]bool) { |
| 224 | + switch v.Kind() { |
| 225 | + case reflect.Ptr: |
| 226 | + if v.IsNil() { |
| 227 | + return |
224 | 228 | } |
225 | | - for _, rr := range doc.Components.Responses { |
226 | | - clearResponseRefs(rr, stack, visited) |
| 229 | + |
| 230 | + // SchemaRef needs path-based cycle detection so shared (non-circular) |
| 231 | + // schemas are fully expanded while true cycles are broken. |
| 232 | + if v.Type() == schemaRefType { |
| 233 | + sr := v.Interface().(*openapi3.SchemaRef) |
| 234 | + sr.Ref = "" |
| 235 | + if sr.Value == nil { |
| 236 | + return |
| 237 | + } |
| 238 | + schemaPtr := reflect.ValueOf(sr.Value).Pointer() |
| 239 | + if schemaStack[schemaPtr] { |
| 240 | + sr.Value = &openapi3.Schema{} |
| 241 | + return |
| 242 | + } |
| 243 | + schemaStack[schemaPtr] = true |
| 244 | + walkAndClearRefs(reflect.ValueOf(sr.Value), visited, schemaStack) |
| 245 | + delete(schemaStack, schemaPtr) |
| 246 | + return |
227 | 247 | } |
228 | | - for _, cr := range doc.Components.Callbacks { |
229 | | - clearCallbackRefs(cr, stack, visited) |
| 248 | + |
| 249 | + ptr := v.Pointer() |
| 250 | + if visited[ptr] { |
| 251 | + return |
230 | 252 | } |
231 | | - for _, er := range doc.Components.Examples { |
232 | | - if er != nil { |
233 | | - er.Ref = "" |
| 253 | + visited[ptr] = true |
| 254 | + |
| 255 | + elem := v.Elem() |
| 256 | + if elem.Kind() == reflect.Struct { |
| 257 | + if refField := elem.FieldByName("Ref"); refField.IsValid() && refField.Kind() == reflect.String { |
| 258 | + refField.SetString("") |
234 | 259 | } |
235 | 260 | } |
236 | | - for _, lr := range doc.Components.Links { |
237 | | - if lr != nil { |
238 | | - lr.Ref = "" |
| 261 | + walkAndClearRefs(elem, visited, schemaStack) |
| 262 | + |
| 263 | + case reflect.Struct: |
| 264 | + // Handle types with unexported map fields (Paths, Callback, Responses) |
| 265 | + // accessed via a Map() method. |
| 266 | + if v.CanAddr() { |
| 267 | + if mapMethod := v.Addr().MethodByName("Map"); mapMethod.IsValid() { |
| 268 | + results := mapMethod.Call(nil) |
| 269 | + if len(results) == 1 && results[0].Kind() == reflect.Map { |
| 270 | + walkAndClearRefs(results[0], visited, schemaStack) |
| 271 | + } |
239 | 272 | } |
240 | 273 | } |
241 | | - for _, ssr := range doc.Components.SecuritySchemes { |
242 | | - if ssr != nil { |
243 | | - ssr.Ref = "" |
| 274 | + for i := 0; i < v.NumField(); i++ { |
| 275 | + field := v.Field(i) |
| 276 | + if field.CanInterface() { |
| 277 | + walkAndClearRefs(field, visited, schemaStack) |
244 | 278 | } |
245 | 279 | } |
246 | | - } |
247 | 280 |
|
248 | | - if doc.Paths != nil { |
249 | | - for _, pathItem := range doc.Paths.Map() { |
250 | | - clearPathItemRefs(pathItem, stack, visited) |
| 281 | + case reflect.Map: |
| 282 | + for _, key := range v.MapKeys() { |
| 283 | + walkAndClearRefs(v.MapIndex(key), visited, schemaStack) |
251 | 284 | } |
252 | | - } |
253 | | -} |
254 | 285 |
|
255 | | -func clearContentRefs(content openapi3.Content, stack map[*openapi3.Schema]bool, visited map[*openapi3.PathItem]bool) { |
256 | | - for _, mt := range content { |
257 | | - if mt != nil { |
258 | | - clearSchemaRefs(mt.Schema, stack) |
| 286 | + case reflect.Slice: |
| 287 | + for i := 0; i < v.Len(); i++ { |
| 288 | + walkAndClearRefs(v.Index(i), visited, schemaStack) |
259 | 289 | } |
260 | | - } |
261 | | -} |
262 | | - |
263 | | -func clearParameterRefs(pr *openapi3.ParameterRef, stack map[*openapi3.Schema]bool, visited map[*openapi3.PathItem]bool) { |
264 | | - if pr == nil { |
265 | | - return |
266 | | - } |
267 | | - pr.Ref = "" |
268 | | - if pr.Value != nil { |
269 | | - clearSchemaRefs(pr.Value.Schema, stack) |
270 | | - clearContentRefs(pr.Value.Content, stack, visited) |
271 | | - } |
272 | | -} |
273 | | - |
274 | | -func clearHeaderRefs(hr *openapi3.HeaderRef, stack map[*openapi3.Schema]bool, visited map[*openapi3.PathItem]bool) { |
275 | | - if hr == nil { |
276 | | - return |
277 | | - } |
278 | | - hr.Ref = "" |
279 | | - if hr.Value != nil { |
280 | | - clearSchemaRefs(hr.Value.Schema, stack) |
281 | | - clearContentRefs(hr.Value.Content, stack, visited) |
282 | | - } |
283 | | -} |
284 | 290 |
|
285 | | -func clearRequestBodyRefs(rb *openapi3.RequestBodyRef, stack map[*openapi3.Schema]bool, visited map[*openapi3.PathItem]bool) { |
286 | | - if rb == nil { |
287 | | - return |
288 | | - } |
289 | | - rb.Ref = "" |
290 | | - if rb.Value != nil { |
291 | | - clearContentRefs(rb.Value.Content, stack, visited) |
292 | | - } |
293 | | -} |
294 | | - |
295 | | -func clearResponseRefs(rr *openapi3.ResponseRef, stack map[*openapi3.Schema]bool, visited map[*openapi3.PathItem]bool) { |
296 | | - if rr == nil { |
297 | | - return |
298 | | - } |
299 | | - rr.Ref = "" |
300 | | - if rr.Value != nil { |
301 | | - clearContentRefs(rr.Value.Content, stack, visited) |
302 | | - for _, hr := range rr.Value.Headers { |
303 | | - clearHeaderRefs(hr, stack, visited) |
| 291 | + case reflect.Interface: |
| 292 | + if !v.IsNil() { |
| 293 | + walkAndClearRefs(v.Elem(), visited, schemaStack) |
304 | 294 | } |
305 | 295 | } |
306 | 296 | } |
307 | | - |
308 | | -func clearCallbackRefs(cr *openapi3.CallbackRef, stack map[*openapi3.Schema]bool, visited map[*openapi3.PathItem]bool) { |
309 | | - if cr == nil { |
310 | | - return |
311 | | - } |
312 | | - cr.Ref = "" |
313 | | - if cr.Value != nil { |
314 | | - for _, pathItem := range cr.Value.Map() { |
315 | | - clearPathItemRefs(pathItem, stack, visited) |
316 | | - } |
317 | | - } |
318 | | -} |
319 | | - |
320 | | -func clearPathItemRefs(pathItem *openapi3.PathItem, stack map[*openapi3.Schema]bool, visited map[*openapi3.PathItem]bool) { |
321 | | - if pathItem == nil || visited[pathItem] { |
322 | | - return |
323 | | - } |
324 | | - visited[pathItem] = true |
325 | | - for _, pr := range pathItem.Parameters { |
326 | | - clearParameterRefs(pr, stack, visited) |
327 | | - } |
328 | | - for _, op := range pathItem.Operations() { |
329 | | - clearOperationRefs(op, stack, visited) |
330 | | - } |
331 | | -} |
332 | | - |
333 | | -func clearOperationRefs(op *openapi3.Operation, stack map[*openapi3.Schema]bool, visited map[*openapi3.PathItem]bool) { |
334 | | - if op == nil { |
335 | | - return |
336 | | - } |
337 | | - for _, pr := range op.Parameters { |
338 | | - clearParameterRefs(pr, stack, visited) |
339 | | - } |
340 | | - clearRequestBodyRefs(op.RequestBody, stack, visited) |
341 | | - if op.Responses != nil { |
342 | | - for _, rr := range op.Responses.Map() { |
343 | | - clearResponseRefs(rr, stack, visited) |
344 | | - } |
345 | | - } |
346 | | - for _, cr := range op.Callbacks { |
347 | | - clearCallbackRefs(cr, stack, visited) |
348 | | - } |
349 | | -} |
350 | | - |
351 | | -// clearSchemaRefs recursively clears $ref strings on all nested SchemaRefs |
352 | | -// so that json.Marshal outputs fully inlined schemas. The stack set tracks |
353 | | -// Schema values (not SchemaRef pointers) on the current recursion path to |
354 | | -// detect circular references. kin-openapi resolves $refs by creating |
355 | | -// different SchemaRef objects that share the same underlying Schema pointer, |
356 | | -// so tracking by *Schema is necessary to catch all cycles. |
357 | | -func clearSchemaRefs(sr *openapi3.SchemaRef, stack map[*openapi3.Schema]bool) { |
358 | | - if sr == nil { |
359 | | - return |
360 | | - } |
361 | | - sr.Ref = "" |
362 | | - s := sr.Value |
363 | | - if s == nil { |
364 | | - return |
365 | | - } |
366 | | - if stack[s] { |
367 | | - sr.Value = &openapi3.Schema{} |
368 | | - return |
369 | | - } |
370 | | - stack[s] = true |
371 | | - for _, child := range s.AllOf { |
372 | | - clearSchemaRefs(child, stack) |
373 | | - } |
374 | | - for _, child := range s.AnyOf { |
375 | | - clearSchemaRefs(child, stack) |
376 | | - } |
377 | | - for _, child := range s.OneOf { |
378 | | - clearSchemaRefs(child, stack) |
379 | | - } |
380 | | - clearSchemaRefs(s.Not, stack) |
381 | | - if s.Items != nil { |
382 | | - clearSchemaRefs(s.Items, stack) |
383 | | - } |
384 | | - for _, prop := range s.Properties { |
385 | | - clearSchemaRefs(prop, stack) |
386 | | - } |
387 | | - if s.AdditionalProperties.Schema != nil { |
388 | | - clearSchemaRefs(s.AdditionalProperties.Schema, stack) |
389 | | - } |
390 | | - delete(stack, s) |
391 | | -} |
0 commit comments