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 }