github.com/webdestroya/awsmocker@v0.2.6/mocked_response.go (about)

     1  package awsmocker
     2  
     3  import (
     4  	"encoding/xml"
     5  	"net/http"
     6  	"reflect"
     7  	"strings"
     8  
     9  	"github.com/clbanning/mxj"
    10  )
    11  
    12  const (
    13  	ContentTypeXML  = "text/xml"
    14  	ContentTypeJSON = "application/x-amz-json-1.1"
    15  	ContentTypeText = "text/plain"
    16  )
    17  
    18  var (
    19  	byteArrayType = reflect.SliceOf(reflect.TypeOf((*byte)(nil)).Elem())
    20  )
    21  
    22  type MockedResponse struct {
    23  	// modify the status code. default is 200
    24  	StatusCode int
    25  
    26  	// force the content type. default will be determined by request content type
    27  	ContentType string
    28  
    29  	Encoding ResponseEncoding
    30  
    31  	// a string, struct or map that will be encoded as the response
    32  	//
    33  	// Also accepts a function that is of the following signatures:
    34  	// func(*ReceivedRequest) (string) = string payload (with 200 OK, inferred content type)
    35  	// func(*ReceivedRequest) (string, int) = string payload, <int> status code (with inferred content type)
    36  	// func(*ReceivedRequest) (string, int, string) = string payload, <int> status code, content type
    37  	Body any
    38  
    39  	// Do not wrap the xml response in ACTIONResponse>ACTIONResult
    40  	DoNotWrap bool
    41  	RootTag   string
    42  
    43  	// If provided, then all other fields are ignored, and the user
    44  	// is responsible for building an HTTP response themselves
    45  	Handler MockedRequestHandler
    46  
    47  	rawBody string
    48  
    49  	action string
    50  }
    51  
    52  type wrapperStruct struct {
    53  	XMLName   xml.Name `xml:"_ACTION_NAME_HERE_Response"`
    54  	Result    any      `xml:"_ACTION_NAME_HERE_Result"`
    55  	RequestId string   `xml:"ResponseMetadata>RequestId"`
    56  }
    57  
    58  func (m *MockedResponse) prep() {
    59  	if m.StatusCode == 0 {
    60  		m.StatusCode = http.StatusOK
    61  	}
    62  }
    63  
    64  func (m *MockedResponse) getResponse(rr *ReceivedRequest) *httpResponse {
    65  
    66  	if m.Handler != nil {
    67  		// user wants to do it all themselves
    68  		return &httpResponse{
    69  			forcedHttpResponse: m.Handler(rr),
    70  		}
    71  	}
    72  
    73  	if m.rawBody != "" && m.ContentType != "" {
    74  		return &httpResponse{
    75  			Body:        m.rawBody,
    76  			StatusCode:  m.StatusCode,
    77  			contentType: m.ContentType,
    78  		}
    79  	}
    80  
    81  	actionName := m.action
    82  	if actionName == "" {
    83  		actionName = rr.Action
    84  	}
    85  
    86  	rBody := reflect.Indirect(reflect.ValueOf(m.Body))
    87  	bodyKind := rBody.Kind()
    88  
    89  	switch bodyKind {
    90  	case reflect.Func:
    91  
    92  		switch rBody.Interface().(type) {
    93  		case func(*ReceivedRequest) string:
    94  		case func(*ReceivedRequest) (string, int):
    95  		case func(*ReceivedRequest) (string, int, string):
    96  			// valid function
    97  		default:
    98  			return generateErrorStruct(0, "InvalidBodyFunc", "the function you gave for the body has the wrong signature").getResponse(rr)
    99  		}
   100  
   101  		respRet := rBody.Call([]reflect.Value{reflect.ValueOf(rr)})
   102  		respBody := respRet[0].String()
   103  		respStatus := http.StatusOK
   104  		var respContentType string
   105  
   106  		if len(respRet) > 1 {
   107  			respStatus = int(respRet[1].Int())
   108  		}
   109  
   110  		if len(respRet) > 2 {
   111  			respContentType = respRet[2].String()
   112  		} else {
   113  			respContentType = inferContentType(respBody)
   114  		}
   115  
   116  		return &httpResponse{
   117  			Body:        respBody,
   118  			contentType: respContentType,
   119  			StatusCode:  respStatus,
   120  		}
   121  	case reflect.String:
   122  
   123  		m.rawBody = rBody.String()
   124  		// m.ContentType = ContentTypeText
   125  		if m.ContentType == "" && len(m.rawBody) > 1 {
   126  			m.ContentType = inferContentType(m.rawBody)
   127  		}
   128  		return &httpResponse{
   129  			Body:        m.rawBody,
   130  			StatusCode:  m.StatusCode,
   131  			contentType: m.ContentType,
   132  		}
   133  
   134  	case reflect.Map, reflect.Array, reflect.Slice, reflect.Struct:
   135  
   136  		switch {
   137  		case m.Encoding == ResponseEncodingJSON:
   138  			fallthrough
   139  		case m.Encoding == ResponseEncodingDefault && rr.AssumedResponseType == ContentTypeJSON:
   140  			return &httpResponse{
   141  				Body:        EncodeAsJson(m.Body),
   142  				StatusCode:  m.StatusCode,
   143  				contentType: ContentTypeJSON,
   144  			}
   145  
   146  		case m.Encoding == ResponseEncodingXML:
   147  			fallthrough
   148  		case m.Encoding == ResponseEncodingDefault && rr.AssumedResponseType == ContentTypeXML:
   149  
   150  			if m.DoNotWrap {
   151  
   152  				if m.RootTag == "" {
   153  					m.RootTag = "" + actionName + "Response"
   154  				}
   155  
   156  				xmlout, err := mxj.AnyXmlIndent(m.Body, "", "  ", m.RootTag, "")
   157  				if err != nil {
   158  					return generateErrorStruct(0, "BadMockBody", "Could not serialize body to XML: %s", err).getResponse(rr)
   159  				}
   160  
   161  				return &httpResponse{
   162  					bodyRaw:     xmlout,
   163  					StatusCode:  m.StatusCode,
   164  					contentType: ContentTypeXML,
   165  				}
   166  			} else if bodyKind == reflect.Struct {
   167  				wrappedObj := wrapperStruct{
   168  					Result:    m.Body,
   169  					RequestId: "01234567-89ab-cdef-0123-456789abcdef",
   170  				}
   171  
   172  				xmlout, err := mxj.AnyXmlIndent(wrappedObj, "", "  ", ""+actionName+"Response")
   173  				if err != nil {
   174  					return generateErrorStruct(0, "BadMockBody", "Could not serialize body to XML: %s", err).getResponse(rr)
   175  				}
   176  
   177  				return &httpResponse{
   178  					Body:        strings.ReplaceAll(string(xmlout), "_ACTION_NAME_HERE_", actionName),
   179  					StatusCode:  m.StatusCode,
   180  					contentType: ContentTypeXML,
   181  				}
   182  			} else {
   183  				resultName := "" + actionName + "Result"
   184  				wrappedMap := map[string]interface{}{
   185  					resultName: m.Body,
   186  					"ResponseMetadata": map[string]string{
   187  						"RequestId": "01234567-89ab-cdef-0123-456789abcdef",
   188  					},
   189  				}
   190  
   191  				xmlout, err := mxj.AnyXmlIndent(wrappedMap, "", "  ", ""+actionName+"Response")
   192  				if err != nil {
   193  					return generateErrorStruct(0, "BadMockBody", "Could not serialize body to XML: %s", err).getResponse(rr)
   194  				}
   195  
   196  				return &httpResponse{
   197  					bodyRaw:     xmlout,
   198  					StatusCode:  m.StatusCode,
   199  					contentType: ContentTypeXML,
   200  				}
   201  			}
   202  		case bodyKind == reflect.Slice && rBody.Type() == byteArrayType:
   203  
   204  			cType := m.ContentType
   205  			if cType == "" {
   206  				cType = http.DetectContentType(m.Body.([]byte))
   207  			}
   208  
   209  			return &httpResponse{
   210  				bodyRaw:     m.Body.([]byte),
   211  				StatusCode:  m.StatusCode,
   212  				contentType: cType,
   213  			}
   214  		}
   215  	}
   216  	return generateErrorStruct(0, "BadMockResponse", "Don't know how to encode a kind=%v using content type=%s", bodyKind, m.ContentType).getResponse(rr)
   217  }