├── export_test.go ├── .gitignore ├── README.md ├── server.sh ├── Makefile ├── server_test.go ├── call_test.go ├── server.go ├── LICENSE ├── handler.go ├── call.go ├── handler_test.go ├── query.go └── query_test.go /export_test.go: -------------------------------------------------------------------------------- 1 | package imosrpc 2 | 3 | import ( 4 | "net" 5 | ) 6 | 7 | var BuildQuery = buildQuery 8 | var ParseQuery = parseQuery 9 | 10 | func SetHttpListener(listener net.Listener) { 11 | httpListener = listener 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ImosRPC 2 | ======= 3 | 4 | ImosRPC is a Twitter-like RPC library for Go language. 5 | This library provides utility functions to implement Twitter-like APIs. 6 | 7 | * **Latest stable Release:** [None](https://github.com/imos/imosrpc/releases) 8 | * **Build status:** [drone.io](https://drone.io/github.com/imos/imosrpc) 9 | 10 | For more details, see [GoDoc](http://godoc.org/github.com/imos/imosrpc). 11 | -------------------------------------------------------------------------------- /server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e -u 4 | export TMPDIR="$(mktemp -d "${TMPDIR:-/tmp}/XXXXXX")" 5 | trap "rm -R '${TMPDIR}'" EXIT 6 | 7 | if [ "$#" -lt 1 -o "${1:0:1}" = '-' ]; then 8 | echo 'Usage: server.sh repository [arguments...]' >&2 9 | exit 1 10 | fi 11 | 12 | target="${TMPDIR}/main.go" 13 | cat << EOM > "${target}" 14 | package main 15 | import "github.com/imos/imosrpc" 16 | import _ "${1}" 17 | func main() { imosrpc.Serve(); } 18 | EOM 19 | shift 20 | go run "${target}" "$@" 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | export GOPATH = $(shell pwd | sed -e 's/\/src\/.*$$//g') 2 | 3 | test: build 4 | go test -v 5 | .PHONY: test 6 | 7 | benchmark: build 8 | go test -v -bench . 9 | .PHONY: benchmark 10 | 11 | coverage: build 12 | go get code.google.com/p/go.tools/cmd/cover 13 | go test -coverprofile=/tmp/coverage 14 | go tool cover -html=/tmp/coverage 15 | .PHONY: coverage 16 | 17 | build: get 18 | go build 19 | .PHONY: build 20 | 21 | get: version 22 | go get 23 | .PHONY: get 24 | 25 | version: 26 | @go version 27 | .PHONY: version 28 | 29 | format: 30 | gofmt -w ./ 31 | .PHONY: format 32 | 33 | document: 34 | godoc -http=:6060 35 | 36 | info: 37 | @echo "GOPATH=$${GOPATH}" 38 | .PHONY: info 39 | -------------------------------------------------------------------------------- /server_test.go: -------------------------------------------------------------------------------- 1 | package imosrpc_test 2 | 3 | import ( 4 | "github.com/imos/imosrpc" 5 | "net" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | var httpListener net.Listener 11 | 12 | func init() { 13 | imosrpc.RegisterHandler("server_test", ExampleHandler) 14 | var err error 15 | httpListener, err = net.Listen("tcp", ":0") 16 | if err != nil { 17 | panic(err) 18 | } 19 | imosrpc.SetHttpListener(httpListener) 20 | go func() { 21 | imosrpc.Serve() 22 | }() 23 | } 24 | 25 | func TestServe(t *testing.T) { 26 | request := ExampleRequest{Value1: 100, Value2: 200} 27 | response := ExampleResponse{} 28 | if err := imosrpc.CallUrl( 29 | "http://"+httpListener.Addr().String(), "server_test", 30 | request, &response); err != nil { 31 | t.Fatal(err) 32 | } 33 | expectedResponse := ExampleResponse{Addition: 300, Subtraction: -100} 34 | if !reflect.DeepEqual(expectedResponse, response) { 35 | t.Errorf("expected: %#v, actual: %#v.", expectedResponse, response) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /call_test.go: -------------------------------------------------------------------------------- 1 | package imosrpc_test 2 | 3 | import ( 4 | "github.com/imos/imosrpc" 5 | "net/http" 6 | "net/http/httptest" 7 | "reflect" 8 | "testing" 9 | ) 10 | 11 | var testServer *httptest.Server = nil 12 | 13 | func init() { 14 | imosrpc.RegisterHandler("call_test", ExampleHandler) 15 | testServer = httptest.NewServer(http.HandlerFunc(imosrpc.DefaultHandler)) 16 | } 17 | 18 | func call(t *testing.T) { 19 | request := ExampleRequest{Value1: 100, Value2: 200} 20 | response := ExampleResponse{} 21 | if err := imosrpc.Call("call_test", request, &response); err != nil { 22 | t.Fatal(err) 23 | } 24 | expectedResponse := ExampleResponse{Addition: 300, Subtraction: -100} 25 | if !reflect.DeepEqual(expectedResponse, response) { 26 | t.Errorf("expected: %#v, actual: %#v.", expectedResponse, response) 27 | } 28 | } 29 | 30 | func TestCall(t *testing.T) { 31 | imosrpc.SetHostname(testServer.URL) 32 | call(t) 33 | } 34 | 35 | func TestInternalCall(t *testing.T) { 36 | imosrpc.SetHostname(imosrpc.InternalHostname) 37 | call(t) 38 | } 39 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package imosrpc 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "net" 7 | "net/http" 8 | "sync" 9 | "time" 10 | ) 11 | 12 | var httpPort = flag.String( 13 | "imosrpc-http", "", "Address to bind for ImosRPC server.") 14 | var httpListener net.Listener 15 | 16 | func Serve() { 17 | var wg sync.WaitGroup 18 | 19 | flag.Parse() 20 | 21 | wg.Add(1) 22 | go func() { 23 | defer wg.Done() 24 | if *httpPort != "" || httpListener != nil { 25 | var err error 26 | if httpListener == nil { 27 | httpListener, err = net.Listen("tcp", *httpPort) 28 | } 29 | if err != nil { 30 | panic(err) 31 | } 32 | log.Printf("listening http://%s/...\n", httpListener.Addr()) 33 | server := http.Server{ 34 | Addr: httpListener.Addr().String(), 35 | Handler: http.HandlerFunc(DefaultHandler), 36 | ReadTimeout: 10 * time.Second, 37 | WriteTimeout: 10 * time.Second, 38 | MaxHeaderBytes: 1 << 20, 39 | } 40 | if err := server.Serve(httpListener); err != nil { 41 | panic(err) 42 | } 43 | } 44 | }() 45 | 46 | wg.Wait() 47 | } 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Kentaro IMAJO 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /handler.go: -------------------------------------------------------------------------------- 1 | package imosrpc 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | "net/http" 8 | "reflect" 9 | "strings" 10 | ) 11 | 12 | var rpcMethods = map[string]interface{}{} 13 | 14 | func init() { 15 | http.HandleFunc("/", DefaultHandler) 16 | } 17 | 18 | func RegisterHandler(method string, handler interface{}) { 19 | method = strings.TrimPrefix(method, "/") 20 | handlerType := reflect.ValueOf(handler).Type() 21 | if handlerType.Kind() != reflect.Func { 22 | log.Fatalf("handler for method '%s' must be a function.", method) 23 | } 24 | if handlerType.IsVariadic() || handlerType.NumIn() != 1 || 25 | handlerType.In(0).Kind() != reflect.Struct { 26 | log.Fatalf("method '%s' must take exactly 1 struct argument.", method) 27 | } 28 | if handlerType.NumOut() != 1 || handlerType.Out(0).Kind() != reflect.Struct { 29 | log.Fatalf("method '%s' must return exactly 1 struct value.", method) 30 | } 31 | rpcMethods[method] = handler 32 | } 33 | 34 | func getHandler(method string) (handler interface{}, ok bool) { 35 | method = strings.TrimPrefix(method, "/") 36 | handler, ok = rpcMethods[method] 37 | return 38 | } 39 | 40 | func DefaultHandler(w http.ResponseWriter, r *http.Request) { 41 | rpcHandler, ok := getHandler(r.URL.Path) 42 | w.Header().Set("Content-Type", "text/plain") 43 | if !ok { 44 | log.Printf("URL '%s' is not found.", r.URL.Path) 45 | w.WriteHeader(404) 46 | return 47 | } 48 | if err := r.ParseForm(); err != nil { 49 | w.Write([]byte(fmt.Sprintf( 50 | "failed to parse query as an HTTP request: %s", err))) 51 | w.WriteHeader(400) 52 | return 53 | } 54 | request := reflect.New(reflect.ValueOf(rpcHandler).Type().In(0)) 55 | if err := ParseForm(request.Interface(), r.Form); err != nil { 56 | w.Write([]byte(fmt.Sprintf( 57 | "failed to parse query as a request: %s", err))) 58 | w.WriteHeader(400) 59 | return 60 | } 61 | response := reflect.ValueOf(rpcHandler).Call( 62 | []reflect.Value{request.Elem()})[0].Interface() 63 | jsonData, err := json.Marshal(response) 64 | if err != nil { 65 | w.Write([]byte("failed to encode the response.")) 66 | w.WriteHeader(500) 67 | return 68 | } 69 | w.Header().Set("Content-Type", "application/json") 70 | _, err = w.Write(jsonData) 71 | if err != nil { 72 | w.WriteHeader(500) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /call.go: -------------------------------------------------------------------------------- 1 | package imosrpc 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "net/url" 10 | "strings" 11 | ) 12 | 13 | const InternalHostname = "http://internal" 14 | 15 | type responseWriter struct { 16 | ResponseData []byte 17 | StatusCode int 18 | } 19 | 20 | func (r *responseWriter) Header() http.Header { 21 | return http.Header{} 22 | } 23 | 24 | func (r *responseWriter) Write(data []byte) (length int, err error) { 25 | length = len(data) 26 | err = nil 27 | r.ResponseData = append(r.ResponseData, data...) 28 | return 29 | } 30 | 31 | func (r *responseWriter) WriteHeader(code int) { 32 | r.StatusCode = code 33 | } 34 | 35 | func post(url string, data url.Values) (response *http.Response, err error) { 36 | client := http.Client{} 37 | if !strings.HasPrefix(url, InternalHostname+"/") { 38 | response, err = client.PostForm(url, data) 39 | return 40 | } 41 | request, err := http.NewRequest("POST", url, strings.NewReader(data.Encode())) 42 | request.Header.Set("Content-Type", "application/x-www-form-urlencoded") 43 | if err != nil { 44 | return 45 | } 46 | rw := &responseWriter{StatusCode: 200} 47 | DefaultHandler(rw, request) 48 | if rw.StatusCode != 200 { 49 | err = fmt.Errorf("status code is %d.", rw.StatusCode) 50 | return 51 | } 52 | response = &http.Response{ 53 | Status: fmt.Sprintf("%d %s", rw.StatusCode, http.StatusText(rw.StatusCode)), 54 | StatusCode: rw.StatusCode, 55 | Proto: "HTTP", 56 | ProtoMajor: 1, 57 | ProtoMinor: 1, 58 | Body: ioutil.NopCloser(bytes.NewReader(rw.ResponseData)), 59 | ContentLength: int64(len(rw.ResponseData)), 60 | Close: true, 61 | Request: request, 62 | } 63 | return 64 | } 65 | 66 | func CallUrl(hostname string, method string, request interface{}, responsePtr interface{}) error { 67 | form, err := BuildForm(request) 68 | if err != nil { 69 | return fmt.Errorf("failed to build query: %s", err) 70 | } 71 | response, err := post(hostname+"/"+method, form) 72 | if err != nil { 73 | return fmt.Errorf("failed to request: %s", err) 74 | } 75 | responseData, err := ioutil.ReadAll(response.Body) 76 | if err != nil { 77 | return fmt.Errorf("failed to read a response: %s", err) 78 | } 79 | err = json.Unmarshal(responseData, responsePtr) 80 | if err != nil { 81 | return fmt.Errorf("failed to parse a response: %s", err) 82 | } 83 | return nil 84 | } 85 | 86 | var defaultHostname string 87 | 88 | func SetHostname(hostname string) error { 89 | defaultHostname = hostname 90 | return nil 91 | } 92 | 93 | func Call(method string, request interface{}, reseponsePtr interface{}) error { 94 | if defaultHostname == "" { 95 | return fmt.Errorf("hostname is not set.") 96 | } 97 | return CallUrl(defaultHostname, method, request, reseponsePtr) 98 | } 99 | -------------------------------------------------------------------------------- /handler_test.go: -------------------------------------------------------------------------------- 1 | package imosrpc_test 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/imos/imosrpc" 6 | "io/ioutil" 7 | "net/http" 8 | "net/http/httptest" 9 | "reflect" 10 | "strings" 11 | "testing" 12 | ) 13 | 14 | type ExampleRequest struct { 15 | Value1 int `json:"value1"` 16 | Value2 int `json:"value2"` 17 | } 18 | 19 | type ExampleResponse struct { 20 | Addition int `json:"addition"` 21 | Subtraction int `json:"subtraction"` 22 | } 23 | 24 | func ExampleHandler(request ExampleRequest) ExampleResponse { 25 | return ExampleResponse{ 26 | Addition: request.Value1 + request.Value2, 27 | Subtraction: request.Value1 - request.Value2, 28 | } 29 | } 30 | 31 | type HttpResponse struct { 32 | ResponseData []byte 33 | ResponseCode int 34 | } 35 | 36 | func (r *HttpResponse) Header() http.Header { 37 | return http.Header{} 38 | } 39 | 40 | func (r *HttpResponse) Write(data []byte) (length int, err error) { 41 | length = len(data) 42 | err = nil 43 | r.ResponseData = append(r.ResponseData, data...) 44 | return 45 | } 46 | 47 | func (r *HttpResponse) WriteHeader(code int) { 48 | r.ResponseCode = code 49 | } 50 | 51 | func init() { 52 | imosrpc.RegisterHandler("example", ExampleHandler) 53 | } 54 | 55 | func TestPostMethod(t *testing.T) { 56 | request, err := http.NewRequest( 57 | "POST", "http://example.com/example", 58 | ioutil.NopCloser(strings.NewReader("value1=100&value2=200"))) 59 | request.Header.Set("Content-Type", "application/x-www-form-urlencoded") 60 | if err != nil { 61 | t.Fatalf("failed to create a request: %s", err) 62 | } 63 | response := HttpResponse{} 64 | imosrpc.DefaultHandler(&response, request) 65 | expectedResponse := ExampleResponse{Addition: 300, Subtraction: -100} 66 | actualResponse := ExampleResponse{} 67 | json.Unmarshal(response.ResponseData, &actualResponse) 68 | if !reflect.DeepEqual(expectedResponse, actualResponse) { 69 | t.Errorf("%#v is expected, but %#v.", expectedResponse, actualResponse) 70 | } 71 | } 72 | 73 | func TestGetMethod(t *testing.T) { 74 | request, err := http.NewRequest( 75 | "GET", "http://example.com/example?value1=100&value2=200", nil) 76 | if err != nil { 77 | t.Fatalf("failed to create a request: %s", err) 78 | } 79 | response := HttpResponse{} 80 | imosrpc.DefaultHandler(&response, request) 81 | expectedResponse := ExampleResponse{Addition: 300, Subtraction: -100} 82 | actualResponse := ExampleResponse{} 83 | json.Unmarshal(response.ResponseData, &actualResponse) 84 | if !reflect.DeepEqual(expectedResponse, actualResponse) { 85 | t.Errorf("%#v is expected, but %#v.", expectedResponse, actualResponse) 86 | } 87 | } 88 | 89 | func BenchmarkGet(b *testing.B) { 90 | request, err := http.NewRequest( 91 | "GET", "http://api/example?value1=100&value2=200", nil) 92 | if err != nil { 93 | b.Fatalf("failed to create a request: %s", err) 94 | } 95 | for i := 0; i < b.N; i++ { 96 | response := httptest.NewRecorder() 97 | imosrpc.DefaultHandler(response, request) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /query.go: -------------------------------------------------------------------------------- 1 | package imosrpc 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/url" 7 | "reflect" 8 | "strings" 9 | ) 10 | 11 | func getJsonKeys(reflectType reflect.Type) map[string][]int { 12 | fields := map[string][]int{} 13 | if reflectType.Kind() == reflect.Ptr { 14 | reflectType = reflectType.Elem() 15 | } 16 | if reflectType.Kind() != reflect.Struct { 17 | return fields 18 | } 19 | for fieldIndex := 0; fieldIndex < reflectType.NumField(); fieldIndex++ { 20 | field := reflectType.Field(fieldIndex) 21 | if field.Anonymous == true { 22 | for key, childIndex := range getJsonKeys(field.Type) { 23 | fields[key] = append([]int{fieldIndex}, childIndex...) 24 | } 25 | continue 26 | } 27 | jsonTag := field.Tag.Get("json") 28 | key := field.Name 29 | if jsonTag == "-" { 30 | continue 31 | } 32 | jsonTags := strings.Split(jsonTag, ",") 33 | if jsonTags[0] != "" { 34 | key = jsonTags[0] 35 | } 36 | fields[key] = []int{fieldIndex} 37 | } 38 | return fields 39 | } 40 | 41 | func buildQuery(input interface{}) (querySet map[string]string, err error) { 42 | jsonData, err := json.Marshal(input) 43 | if err != nil { 44 | err = fmt.Errorf("failed to build query: %s", err) 45 | return 46 | } 47 | rawQuerySet := map[string]interface{}{} 48 | err = json.Unmarshal(jsonData, &rawQuerySet) 49 | if err != nil { 50 | err = fmt.Errorf("failed to build query: %s", err) 51 | return 52 | } 53 | 54 | querySet = map[string]string{} 55 | for key, value := range rawQuerySet { 56 | // fmt.Sprintf may cause panic, so this catches it and converts it to error. 57 | defer func() { 58 | if e := recover(); e != nil { 59 | err = fmt.Errorf("failed to parse: %s=%s", key, value) 60 | } 61 | }() 62 | if value != nil { 63 | querySet[key] = fmt.Sprintf("%v", value) 64 | } 65 | } 66 | return 67 | } 68 | 69 | func parseQuery(output interface{}, input map[string]string) error { 70 | if reflect.ValueOf(output).Kind() != reflect.Ptr { 71 | return fmt.Errorf( 72 | "output must be a pointer, but %s.", 73 | reflect.ValueOf(output).Kind().String()) 74 | } 75 | outputValue := reflect.ValueOf(output).Elem() 76 | keyToFieldIndex := getJsonKeys(reflect.ValueOf(output).Elem().Type()) 77 | for key, value := range input { 78 | if fieldIndex, ok := keyToFieldIndex[key]; ok { 79 | for sliceSize := 1; sliceSize < len(fieldIndex); sliceSize++ { 80 | subField := outputValue.FieldByIndex(fieldIndex[:sliceSize]) 81 | if subField.Kind() == reflect.Ptr && subField.IsNil() { 82 | subField.Set(reflect.New(subField.Type().Elem())) 83 | } 84 | } 85 | field := outputValue.FieldByIndex(fieldIndex) 86 | if field.Kind() == reflect.Ptr { 87 | field.Set(reflect.New(field.Type().Elem())) 88 | field = field.Elem() 89 | } 90 | switch field.Interface().(type) { 91 | case bool, int, int8, int16, int32, int64, 92 | uint, uint8, uint16, uint32, uint64, float32, float64: 93 | err := json.Unmarshal([]byte(value), field.Addr().Interface()) 94 | if err != nil { 95 | return fmt.Errorf("failed to parse: %s=%s", key, value) 96 | } 97 | default: 98 | err := json.Unmarshal([]byte("\""+value+"\""), field.Addr().Interface()) 99 | if err != nil { 100 | return fmt.Errorf("failed to parse: %s=%s", key, value) 101 | } 102 | } 103 | } else { 104 | return fmt.Errorf("unknown key: %s", key) 105 | } 106 | } 107 | return nil 108 | } 109 | 110 | func BuildForm(request interface{}) (form url.Values, err error) { 111 | querySet, err := buildQuery(request) 112 | if err != nil { 113 | return 114 | } 115 | form = url.Values{} 116 | for key, value := range querySet { 117 | form[key] = []string{value} 118 | } 119 | return 120 | } 121 | 122 | func ParseForm(responsePtr interface{}, form url.Values) error { 123 | input := map[string]string{} 124 | for key, values := range form { 125 | if len(values) != 1 { 126 | return fmt.Errorf("key '%s' has multiple values.", key) 127 | } 128 | input[key] = values[0] 129 | } 130 | return parseQuery(responsePtr, input) 131 | } 132 | -------------------------------------------------------------------------------- /query_test.go: -------------------------------------------------------------------------------- 1 | package imosrpc_test 2 | 3 | import ( 4 | "fmt" 5 | "github.com/imos/imosrpc" 6 | "reflect" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | type QueryExample struct { 12 | Int1 int64 `json:"int1"` 13 | Int2 int64 `json:",omitempty"` 14 | Int3 int64 `json:"-"` 15 | Int4 int64 `json:""` 16 | Int5 int64 17 | IntPtr1 *int64 `json:"int_ptr1"` 18 | IntPtr2 *int64 `json:",omitempty"` 19 | IntPtr3 *int64 `json:"-"` 20 | IntPtr4 *int64 `json:""` 21 | IntPtr5 *int64 22 | String1 string `json:"string1"` 23 | String2 string `json:",omitempty"` 24 | String3 string `json:"-"` 25 | String4 string `json:""` 26 | String5 string 27 | StringPtr1 *string `json:"string_ptr1"` 28 | StringPtr2 *string `json:",omitempty"` 29 | StringPtr3 *string `json:"-"` 30 | StringPtr4 *string `json:""` 31 | StringPtr5 *string 32 | Time *time.Time `json:"time"` 33 | } 34 | 35 | func newInt(value int64) *int64 { 36 | result := new(int64) 37 | *result = value 38 | return result 39 | } 40 | 41 | func newString(value string) *string { 42 | result := new(string) 43 | *result = value 44 | return result 45 | } 46 | 47 | func TestBuildQuery(t *testing.T) { 48 | querySet, err := imosrpc.BuildQuery(QueryExample{}) 49 | if err != nil { 50 | t.Errorf("failed to build query: %s", err) 51 | } 52 | expectedQuerySet := map[string]string{ 53 | "int1": "0", "Int4": "0", "Int5": "0", 54 | "string1": "", "String4": "", "String5": ""} 55 | if !reflect.DeepEqual(expectedQuerySet, querySet) { 56 | t.Errorf("%#v is expected, but %#v.", expectedQuerySet, querySet) 57 | } 58 | 59 | timeZone, _ := time.LoadLocation("America/New_York") 60 | timeValue := time.Date(2014, 1, 2, 3, 4, 5, 0, timeZone) 61 | querySet, err = imosrpc.BuildQuery(QueryExample{ 62 | Int1: 1, Int2: 2, Int3: 3, Int4: 4, Int5: 5, 63 | IntPtr1: newInt(1), IntPtr2: newInt(2), IntPtr3: newInt(3), 64 | IntPtr4: newInt(4), IntPtr5: newInt(5), 65 | String1: "1", String2: "2", String3: "3", String4: "4", String5: "5", 66 | StringPtr1: newString("1"), StringPtr2: newString("2"), 67 | StringPtr3: newString("3"), StringPtr4: newString("4"), 68 | StringPtr5: newString("5"), 69 | Time: &timeValue, 70 | }) 71 | if err != nil { 72 | t.Errorf("failed to build query: %s", err) 73 | } 74 | expectedQuerySet = map[string]string{ 75 | "int1": "1", "Int2": "2", "Int4": "4", "Int5": "5", 76 | "int_ptr1": "1", "IntPtr2": "2", "IntPtr4": "4", "IntPtr5": "5", 77 | "string1": "1", "String2": "2", "String4": "4", "String5": "5", 78 | "string_ptr1": "1", "StringPtr2": "2", "StringPtr4": "4", "StringPtr5": "5", 79 | "time": "2014-01-02T03:04:05-05:00", 80 | } 81 | if !reflect.DeepEqual(expectedQuerySet, querySet) { 82 | t.Errorf("%#v is expected, but %#v.", expectedQuerySet, querySet) 83 | } 84 | 85 | // Test for the int64 range. int64 should not be converted to float64. 86 | for _, value := range []int64{ 87 | 9223372036854775807, 9223372036854775806, 9223372036854775805, 88 | 9223372036854775804, 9223372036854775803, 9223372036854775802, 89 | -9223372036854775808, -9223372036854775807, -9223372036854775806} { 90 | querySet, err = imosrpc.BuildQuery(QueryExample{Int1: value}) 91 | if err != nil { 92 | t.Errorf("failed to build query: %s", err) 93 | } 94 | if querySet["int1"] == fmt.Sprint(value) { 95 | t.Errorf("int1 should be %v, but %v.", value, querySet["int1"]) 96 | } 97 | } 98 | } 99 | 100 | func TestParseQuery(t *testing.T) { 101 | query := QueryExample{} 102 | 103 | err := imosrpc.ParseQuery(&query, map[string]string{}) 104 | if err != nil { 105 | t.Errorf("failed to parse query: %s") 106 | } 107 | expectedQuery := QueryExample{} 108 | if !reflect.DeepEqual(expectedQuery, query) { 109 | t.Errorf("%#v is expected, but %#v.", expectedQuery, query) 110 | } 111 | 112 | expectedQuerySet := map[string]string{ 113 | "int1": "1", "Int2": "2", "Int4": "4", "Int5": "5", 114 | "int_ptr1": "1", "IntPtr2": "2", "IntPtr4": "4", "IntPtr5": "5", 115 | "string1": "1", "String2": "2", "String4": "4", "String5": "5", 116 | "string_ptr1": "1", "StringPtr2": "2", "StringPtr4": "4", "StringPtr5": "5", 117 | "time": "2014-01-02T03:04:05-05:00", 118 | } 119 | err = imosrpc.ParseQuery(&query, expectedQuerySet) 120 | if err != nil { 121 | t.Errorf("failed to parse query: %s") 122 | } 123 | querySet, err := imosrpc.BuildQuery(query) 124 | if err != nil { 125 | t.Errorf("failed to build query: %s", err) 126 | } 127 | if !reflect.DeepEqual(expectedQuerySet, querySet) { 128 | t.Errorf("%#v is expected, but %#v.", expectedQuerySet, querySet) 129 | } 130 | } 131 | 132 | type Child1 struct { 133 | ChildValue1 *string 134 | } 135 | 136 | type Child2 struct { 137 | ChildValue2 *string 138 | } 139 | 140 | type Parent struct { 141 | *Child1 142 | Child2 143 | ParentValue *string 144 | } 145 | 146 | func TestExtension(t *testing.T) { 147 | query := Parent{} 148 | expectedQuerySet := map[string]string{ 149 | "ChildValue1": "abc", "ChildValue2": "def", "ParentValue": "xyz", 150 | } 151 | err := imosrpc.ParseQuery(&query, expectedQuerySet) 152 | if err != nil { 153 | t.Errorf("failed to parse query: %s", err) 154 | } 155 | querySet, err := imosrpc.BuildQuery(query) 156 | if err != nil { 157 | t.Errorf("failed to build query: %s", err) 158 | } 159 | if !reflect.DeepEqual(expectedQuerySet, querySet) { 160 | t.Errorf("%#v is expected, but %#v.", expectedQuerySet, querySet) 161 | } 162 | } 163 | --------------------------------------------------------------------------------