github.com/snyk/vervet/v3@v3.7.0/include_headers.go (about)

     1  package vervet
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"path/filepath"
     7  
     8  	"github.com/getkin/kin-openapi/openapi3"
     9  )
    10  
    11  const (
    12  	// ExtSnykIncludeHeaders is used to annotate a response with a list of
    13  	// headers. While OpenAPI supports header references, it does not yet
    14  	// support including a collection of common headers. This extension is used
    15  	// by vervet to include headers from a referenced document when compiling
    16  	// OpenAPI specs.
    17  	ExtSnykIncludeHeaders = "x-snyk-include-headers"
    18  )
    19  
    20  // IncludeHeaders adds response headers included with the ExtSnykIncludeHeaders
    21  // extension property.
    22  func IncludeHeaders(doc *Document) error {
    23  	w := &includeHeaders{doc: doc}
    24  	err := w.apply()
    25  	if err != nil {
    26  		return err
    27  	}
    28  	return doc.ResolveRefs()
    29  }
    30  
    31  type includeHeaders struct {
    32  	doc *Document
    33  }
    34  
    35  func (w *includeHeaders) apply() error {
    36  	for _, pathItem := range w.doc.Paths {
    37  		if err := w.applyOperation(pathItem.Connect); err != nil {
    38  			return err
    39  		}
    40  		if err := w.applyOperation(pathItem.Delete); err != nil {
    41  			return err
    42  		}
    43  		if err := w.applyOperation(pathItem.Get); err != nil {
    44  			return err
    45  		}
    46  		if err := w.applyOperation(pathItem.Head); err != nil {
    47  			return err
    48  		}
    49  		if err := w.applyOperation(pathItem.Options); err != nil {
    50  			return err
    51  		}
    52  		if err := w.applyOperation(pathItem.Patch); err != nil {
    53  			return err
    54  		}
    55  		if err := w.applyOperation(pathItem.Post); err != nil {
    56  			return err
    57  		}
    58  		if err := w.applyOperation(pathItem.Put); err != nil {
    59  			return err
    60  		}
    61  	}
    62  	return nil
    63  }
    64  
    65  type includeHeadersRef struct {
    66  	Ref   string           `json:"$ref"`
    67  	Value openapi3.Headers `json:"-"`
    68  }
    69  
    70  func (w *includeHeaders) applyOperation(op *openapi3.Operation) error {
    71  	if op == nil {
    72  		return nil // nothing to do
    73  	}
    74  	for _, respRef := range op.Responses {
    75  		resp := respRef.Value
    76  		headersRefJson := resp.ExtensionProps.Extensions[ExtSnykIncludeHeaders]
    77  		if headersRefJson == nil {
    78  			continue
    79  		}
    80  		inclRef := &includeHeadersRef{Value: openapi3.Headers{}}
    81  		err := json.Unmarshal(headersRefJson.(json.RawMessage), &inclRef)
    82  		if err != nil {
    83  			return err
    84  		}
    85  		relPath, err := w.doc.LoadReference(w.doc.RelativePath(), inclRef.Ref, &inclRef.Value)
    86  		if err != nil {
    87  			return fmt.Errorf("failed to load reference: %w", err)
    88  		}
    89  
    90  		if resp.Headers == nil {
    91  			resp.Headers = openapi3.Headers{}
    92  		}
    93  		for headerKey, headerRef := range inclRef.Value {
    94  			if _, ok := resp.Headers[headerKey]; ok {
    95  				continue // Response's declared headers take precedence over includes.
    96  			}
    97  			headerRef.Ref = filepath.Join(relPath, headerRef.Ref)
    98  			resp.Headers[headerKey] = headerRef
    99  		}
   100  		// Remove the extension once it has been processed
   101  		delete(resp.ExtensionProps.Extensions, ExtSnykIncludeHeaders)
   102  	}
   103  	return nil
   104  }