From 582c0e8fb85aa12ecd017e8ab07fa2ccfbf76df3 Mon Sep 17 00:00:00 2001 From: Domenic Del Nano Date: Wed, 3 Aug 2016 22:54:42 -0400 Subject: [PATCH 1/2] Add intial implementation of UnmarshalJSON for transaction.Headers --- transaction/transaction.go | 69 +++++++++++++++++++++++++++++---- transaction/transaction_test.go | 38 +++++++++++++++++- 2 files changed, 98 insertions(+), 9 deletions(-) diff --git a/transaction/transaction.go b/transaction/transaction.go index 4139826..432300a 100644 --- a/transaction/transaction.go +++ b/transaction/transaction.go @@ -1,6 +1,10 @@ package transaction -import "encoding/json" +import ( + "encoding/json" + "fmt" + "reflect" +) // Transaction represents a Dredd transaction object. // http://dredd.readthedocs.org/en/latest/hooks/#transaction-object-structure @@ -11,16 +15,16 @@ type Transaction struct { Protocol string `json:"protocol,omitempty"` FullPath string `json:"fullPath,omitempty"` Request *struct { - Body string `json:"body,omitempty"` - Headers map[string]interface{} `json:"headers,omitempty"` - URI string `json:"uri,omitempty"` - Method string `json:"method,omitempty"` + Body string `json:"body,omitempty"` + Headers Headers `json:"headers,omitempty"` + URI string `json:"uri,omitempty"` + Method string `json:"method,omitempty"` } `json:"request,omitempty"` Expected *json.RawMessage `json:"expected,omitempty"` Real *struct { - Body string `json:"body"` - Headers map[string]interface{} `json:"headers"` - StatusCode int `json:"statusCode"` + Body string `json:"body"` + Headers Headers `json:"headers"` + StatusCode int `json:"statusCode"` } `json:"real,omitempty"` Origin *json.RawMessage `json:"origin,omitempty"` Test *json.RawMessage `json:"test,omitempty"` @@ -31,6 +35,55 @@ type Transaction struct { TestOrder []string `json:"hooks_modifications,omitempty"` } +type Headers map[string][]string + +func (hdrs *Headers) UnmarshalJSON(data []byte) error { + var headers map[string]interface{} = make(map[string]interface{}) + err := json.Unmarshal(data, &headers) + + if err != nil { + return err + } + + *hdrs = make(map[string][]string) + for k := range headers { + header := headers[k] + value := reflect.ValueOf(header) + kind := value.Kind() + + switch kind { + case reflect.Float32, reflect.Float64, reflect.Int, reflect.String: + (*hdrs)[k] = []string{ + stringFromValue(value), + } + case reflect.Slice: + for i := 0; i < value.Len(); i++ { + (*hdrs)[k] = append((*hdrs)[k], stringFromValueAtIndex(value, i)) + } + + default: + // TODO: Figure out how to use this + panic(fmt.Sprintf("unsupported Kind %s", kind.String())) + } + + } + + return nil +} + +func stringFromValueAtIndex(val reflect.Value, index int) string { + return fmt.Sprint(val.Index(index).Interface()) +} + +func stringFromValue(val reflect.Value) string { + return fmt.Sprint(val.Interface()) +} + +// func (t *Transaction) UnmarshalJSON(data []byte) error { +// fmt.Printf("%v", string(data)) +// return json.Unmarshal(data, Transaction{}) +// } + // AddTestOrderPoint adds a value to the hooks_modification key used when // running dredd with TEST_DREDD_HOOKS_HANDLER_ORDER enabled. func (t *Transaction) AddTestOrderPoint(value string) { diff --git a/transaction/transaction_test.go b/transaction/transaction_test.go index 465b3a1..53f7752 100644 --- a/transaction/transaction_test.go +++ b/transaction/transaction_test.go @@ -1,6 +1,14 @@ package transaction -import "testing" +import ( + "encoding/json" + "reflect" + "testing" +) + +type HeadersWrapper struct { + Headers Headers `json:"headers,omitempty"` +} func TestSettingFailToString(t *testing.T) { trans := Transaction{} @@ -10,3 +18,31 @@ func TestSettingFailToString(t *testing.T) { t.Errorf("Transaction.Fail member must be able to be set as string") } } + +func TestHeadersJsonUnmarshal(t *testing.T) { + data := []byte(`{ + "headers":{ + "User-Agent":"Dredd/1.4.0 (Darwin 15.4.0; x64)", + "Content-Length":11, + "Set-Cookie":["Test=Yo"] + } + }`) + + hdrs := &HeadersWrapper{} + err := json.Unmarshal(data, hdrs) + if err != nil { + t.Errorf("Error %v", err) + } + + if len(hdrs.Headers["Set-Cookie"]) != 1 || !reflect.DeepEqual(hdrs.Headers["Set-Cookie"], []string{"Test=Yo"}) { + t.Errorf("Set-Cookie should be slice with length of 1") + } + + if !reflect.DeepEqual(hdrs.Headers["User-Agent"], []string{"Dredd/1.4.0 (Darwin 15.4.0; x64)"}) { + t.Errorf("User-Agent should be converted to a slice") + } + + if !reflect.DeepEqual(hdrs.Headers["Content-Length"], []string{"11"}) { + t.Errorf("Content-Length should be converted to a string and be a slice") + } +} From 3dbec75ffa0693dc75b0f55e63401e564b08839f Mon Sep 17 00:00:00 2001 From: Domenic Del Nano Date: Thu, 4 Aug 2016 21:11:58 -0400 Subject: [PATCH 2/2] Use map[string][]string instead of map[string]interface for headers --- transaction/transaction.go | 89 +++++++++++++++++++++++---------- transaction/transaction_test.go | 37 +++++++++++++- 2 files changed, 99 insertions(+), 27 deletions(-) diff --git a/transaction/transaction.go b/transaction/transaction.go index 432300a..6161cb5 100644 --- a/transaction/transaction.go +++ b/transaction/transaction.go @@ -1,6 +1,7 @@ package transaction import ( + "bytes" "encoding/json" "fmt" "reflect" @@ -9,34 +10,75 @@ import ( // Transaction represents a Dredd transaction object. // http://dredd.readthedocs.org/en/latest/hooks/#transaction-object-structure type Transaction struct { - Name string `json:"name,omitempty"` - Host string `json:"host,omitempty"` - Port string `json:"port,omitempty"` - Protocol string `json:"protocol,omitempty"` - FullPath string `json:"fullPath,omitempty"` - Request *struct { - Body string `json:"body,omitempty"` - Headers Headers `json:"headers,omitempty"` - URI string `json:"uri,omitempty"` - Method string `json:"method,omitempty"` - } `json:"request,omitempty"` + Name string `json:"name,omitempty"` + Host string `json:"host,omitempty"` + Port string `json:"port,omitempty"` + Protocol string `json:"protocol,omitempty"` + FullPath string `json:"fullPath,omitempty"` + Request *Request `json:"request,omitempty"` Expected *json.RawMessage `json:"expected,omitempty"` - Real *struct { - Body string `json:"body"` - Headers Headers `json:"headers"` - StatusCode int `json:"statusCode"` - } `json:"real,omitempty"` - Origin *json.RawMessage `json:"origin,omitempty"` - Test *json.RawMessage `json:"test,omitempty"` - Results *json.RawMessage `json:"results,omitempty"` - Skip bool `json:"skip,omitempty"` - Fail interface{} `json:"fail,omitempty"` + Real *RealRequest `json:"real,omitempty"` + Origin *json.RawMessage `json:"origin,omitempty"` + Test *json.RawMessage `json:"test,omitempty"` + Results *json.RawMessage `json:"results,omitempty"` + Skip bool `json:"skip,omitempty"` + Fail interface{} `json:"fail,omitempty"` TestOrder []string `json:"hooks_modifications,omitempty"` } +type Request struct { + Body string `json:"body,omitempty"` + Headers Headers `json:"headers,omitempty"` + URI string `json:"uri,omitempty"` + Method string `json:"method,omitempty"` +} + +type RealRequest struct { + Body string `json:"body"` + Headers Headers `json:"headers"` + StatusCode int `json:"statusCode"` +} + type Headers map[string][]string +func (hdrs *Headers) MarshalJSON() ([]byte, error) { + buf := bytes.Buffer{} + buf.WriteByte('{') + + first := true + for k, v := range *hdrs { + if first { + first = false + } else { + buf.WriteString(",") + } + numValues := len(v) + writeJSONKeyToBuffer(&buf, string(k)) + if numValues == 1 { + writeJSONValueToBuffer(&buf, v[0]) + continue + } + data, err := json.Marshal(v) + + if err != nil { + return nil, err + } + buf.WriteString(string(data)) + } + buf.WriteByte('}') + return buf.Bytes(), nil +} + +func writeJSONKeyToBuffer(buf *bytes.Buffer, key string) { + writeJSONValueToBuffer(buf, key) + buf.WriteByte(':') +} + +func writeJSONValueToBuffer(buf *bytes.Buffer, key string) { + buf.WriteString(`"` + key + `"`) +} + func (hdrs *Headers) UnmarshalJSON(data []byte) error { var headers map[string]interface{} = make(map[string]interface{}) err := json.Unmarshal(data, &headers) @@ -79,11 +121,6 @@ func stringFromValue(val reflect.Value) string { return fmt.Sprint(val.Interface()) } -// func (t *Transaction) UnmarshalJSON(data []byte) error { -// fmt.Printf("%v", string(data)) -// return json.Unmarshal(data, Transaction{}) -// } - // AddTestOrderPoint adds a value to the hooks_modification key used when // running dredd with TEST_DREDD_HOOKS_HANDLER_ORDER enabled. func (t *Transaction) AddTestOrderPoint(value string) { diff --git a/transaction/transaction_test.go b/transaction/transaction_test.go index 53f7752..e4dcde0 100644 --- a/transaction/transaction_test.go +++ b/transaction/transaction_test.go @@ -34,7 +34,7 @@ func TestHeadersJsonUnmarshal(t *testing.T) { t.Errorf("Error %v", err) } - if len(hdrs.Headers["Set-Cookie"]) != 1 || !reflect.DeepEqual(hdrs.Headers["Set-Cookie"], []string{"Test=Yo"}) { + if !reflect.DeepEqual(hdrs.Headers["Set-Cookie"], []string{"Test=Yo"}) { t.Errorf("Set-Cookie should be slice with length of 1") } @@ -46,3 +46,38 @@ func TestHeadersJsonUnmarshal(t *testing.T) { t.Errorf("Content-Length should be converted to a string and be a slice") } } + +func TestHeadersMarshalJSON(t *testing.T) { + headers := make(map[string][]string) + headers["Content-Type"] = []string{"9"} + headers["User-Agent"] = []string{"Dredd/1.4.0 (Darwin 15.4.0; x64)"} + headers["Set-Cookie"] = []string{"Test=Yo", "Another=This"} + wrapper := &HeadersWrapper{ + Headers: headers, + } + + data, err := json.Marshal(wrapper) + + if err != nil { + t.Errorf("json.Marshal failed with %v", err) + } + + var expected interface{} + err = json.Unmarshal([]byte(`{"headers":{"Content-Type":"9","User-Agent":"Dredd/1.4.0 (Darwin 15.4.0; x64)","Set-Cookie":["Test=Yo","Another=This"]}}`), &expected) + + if err != nil { + t.Errorf("failed to unmarshal expected json string") + } + + var actual interface{} + err = json.Unmarshal(data, &actual) + + if err != nil { + t.Errorf("failed to unmarshal Marshaled json") + } + + if !reflect.DeepEqual(expected, actual) { + t.Errorf("json.Marshal failed, data \n %#v did not matched expected \n %#v", actual, expected) + + } +}