github.com/snyk/vervet/v6@v6.2.4/include_headers.go (about)

     1  package vervet
     2  
     3  import (
     4  	"fmt"
     5  	"net/url"
     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  	if err := w.apply(); err != nil {
    25  		return err
    26  	}
    27  	return doc.ResolveRefs()
    28  }
    29  
    30  type includeHeaders struct {
    31  	doc *Document
    32  }
    33  
    34  func (w *includeHeaders) apply() error {
    35  	for _, pathItem := range w.doc.Paths {
    36  		if err := w.applyOperation(pathItem.Connect); err != nil {
    37  			return err
    38  		}
    39  		if err := w.applyOperation(pathItem.Delete); err != nil {
    40  			return err
    41  		}
    42  		if err := w.applyOperation(pathItem.Get); err != nil {
    43  			return err
    44  		}
    45  		if err := w.applyOperation(pathItem.Head); err != nil {
    46  			return err
    47  		}
    48  		if err := w.applyOperation(pathItem.Options); err != nil {
    49  			return err
    50  		}
    51  		if err := w.applyOperation(pathItem.Patch); err != nil {
    52  			return err
    53  		}
    54  		if err := w.applyOperation(pathItem.Post); err != nil {
    55  			return err
    56  		}
    57  		if err := w.applyOperation(pathItem.Put); err != nil {
    58  			return err
    59  		}
    60  	}
    61  	return nil
    62  }
    63  
    64  func (w *includeHeaders) applyOperation(op *openapi3.Operation) error {
    65  	if op == nil {
    66  		return nil // nothing to do
    67  	}
    68  	for _, respRef := range op.Responses {
    69  		resp := respRef.Value
    70  		headersContents, ok := resp.Extensions[ExtSnykIncludeHeaders].(map[string]interface{})
    71  		if !ok {
    72  			continue
    73  		}
    74  		ref, ok := headersContents["$ref"].(string)
    75  		if !ok {
    76  			continue
    77  		}
    78  		val := openapi3.Headers{}
    79  		relPath, err := w.doc.LoadReference(w.doc.RelativePath(), ref, &val)
    80  		if err != nil {
    81  			return fmt.Errorf("failed to load reference: %w", err)
    82  		}
    83  
    84  		if resp.Headers == nil {
    85  			resp.Headers = openapi3.Headers{}
    86  		}
    87  		for headerKey, headerRef := range val {
    88  			if _, ok := resp.Headers[headerKey]; ok {
    89  				continue // Response's declared headers take precedence over includes.
    90  			}
    91  			if isRelativePath(headerRef.Ref) {
    92  				headerRef.Ref = filepath.Join(relPath, headerRef.Ref)
    93  			}
    94  			resp.Headers[headerKey] = headerRef
    95  		}
    96  		// Remove the extension once it has been processed
    97  		delete(resp.Extensions, ExtSnykIncludeHeaders)
    98  	}
    99  	return nil
   100  }
   101  
   102  func isRelativePath(s string) bool {
   103  	if u, err := url.Parse(s); err == nil && u.Scheme != "" {
   104  		return false
   105  	}
   106  	return !filepath.IsAbs(s)
   107  }