├── LICENSE ├── README.md ├── encoding ├── accept_mime_preference_test.go ├── decoder.go ├── default.go ├── default_error_test.go ├── doc.go ├── encode_decode_test.go ├── encoder.go ├── gob.go ├── hint_resolver.go ├── json.go ├── register_encodings.go ├── request_response_test.go ├── sniff_test.go ├── wrapper_error.go ├── wrapper_error_test.go └── xml.go ├── examples └── stringsvc │ ├── cmd │ ├── stringsvc │ │ ├── README.md │ │ └── main.go │ └── stringsvc2 │ │ ├── implementation.go │ │ └── main.go │ └── string_service.go ├── file.go ├── generator.go ├── import.go ├── interface.go ├── logging.go ├── main.go ├── method.go ├── mux └── adapter │ └── gorilla │ └── router.go ├── package.go ├── param.go ├── process-endpoint.go ├── process-logging.go ├── process-transport.go ├── struct.go ├── template-structures.go ├── tmpl ├── endpoint.tmpl ├── logging.tmpl ├── transport-client.tmpl ├── transport-http-client.tmpl ├── transport-http-loadbalanced.tmpl ├── transport-http-server.tmpl ├── transport-make-endpoint.tmpl └── transport-request-response.tmpl ├── type.go ├── util.go └── variable.go /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Theodore Schnepper 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-kit-middlewarer# 2 | go-kit-middlewarer is a utility project meant to be used to quickly generate 3 | various layers of the go-kit microservice framework. 4 | 5 | This utility is meant to be used with the ```go generate```. 6 | 7 | ## Documentation 8 | 9 | ### Usage 10 | 11 | To utilize this utility, you first need to **build** or **install** it. The 12 | easiest way to accomplish this is to run the following command: 13 | 14 | ```bash 15 | go get github.com/ayiga/go-kit-middlewarer && \ 16 | go install github.com/ayiga/go-kit-middlewarer 17 | ``` 18 | 19 | After that's complete, please ensure that go-middle-warer is in your system's 20 | **PATH** Environment Variable, as it will attempt to be called by 21 | ```go generate```. 22 | 23 | Next, a file and an interface is needed to generate the layers for. An example would 24 | be something like this StringService taken from [go-kit's example String Service](https://github.com/go-kit/kit/tree/master/examples/stringsvc1/main.go) 25 | 26 | 27 | ```go 28 | package service 29 | 30 | //go:generate go-kit-middlewarer -type=StringService 31 | 32 | // StringService from go-kit/kit's example 33 | type StringService interface { 34 | // Uppercase returns an uppercase version of the given string, or an error. 35 | Uppercase(str string) (upper string, err error) 36 | 37 | // Count returns the length of the given string 38 | Count(str string) (count int) 39 | } 40 | ``` 41 | 42 | Then, within that directory, and from terminal just run the command ```go generate```, 43 | and the layers will automatically be generated. 44 | 45 | ### Explanation 46 | What's happening is go-kit-middlewarer is looking for an interface with the name 47 | specified by the type flag. It uses this information to pre-generate Service 48 | Middlewares, and transport bindings automatically so you don't have to worry 49 | about the logistics of setting all of that up. 50 | 51 | The generated code is attempted to be organized in the following manner: 52 | ```tree 53 | . 54 | +-- endpoint 55 | | +-- defs_gen.go 56 | +-- logging 57 | | +-- middleware_gen.go 58 | +-- transport 59 | | +-- http 60 | | | +-- client_gen.go 61 | | | +-- http-client-loadbalanced_gen.go 62 | | | +-- http-client_gen.go 63 | | | +-- http-server_gen.go 64 | | | +-- make-endpoint_gen.go 65 | | | +-- request-response_gen.go 66 | +-- .go 67 | ``` 68 | 69 | The generated code will have created transport request and response structures 70 | for all methods specified within the interface. It will name them based on the 71 | names given by the arguments and results specified in the interface. If no 72 | names have been specified, then it will attempt to make some up. However, it is 73 | highly suggested that you do **name** your arguments and results. 74 | 75 | Please note that the code generated by this package has this package as a 76 | dependency. This is primarily due to some convenience functionality around 77 | supporting multiple encoding and decoding types. 78 | 79 | By default, all HTTP requests generated by this package should be able to 80 | support JSON, XML, and Gob encoding. This is assuming that the parameters and 81 | results are encodable by JSON, XML, and Gob. If they do not support a specific 82 | encoding, you do not have to use that encoding. However, they should support at 83 | least one. 84 | 85 | To support these various encodings please see the golang documentation for each: 86 | * XML 87 | * [Encode](https://golang.org/pkg/encoding/xml/#Marshaler) 88 | * [Decode](https://golang.org/pkg/encoding/xml/#Unmarshaler) 89 | * JSON 90 | * [Encode](https://golang.org/pkg/encoding/json/#Marshaler) 91 | * [Decode](https://golang.org/pkg/encoding/json/#Unmarshaler) 92 | * Gob 93 | * [Encode](https://golang.org/pkg/encoding/gob/#GobEncoder) 94 | * [Decode](https://golang.org/pkg/encoding/gob/#GobDecoder) 95 | 96 | There are currently no generated binary files, and no default implementation is 97 | provided for the given interface. However, with the pieces generated, getting 98 | up and running should be as simple as writing some minimal logic code: 99 | 100 | ```go 101 | package main 102 | 103 | import ( 104 | "net/http" 105 | "strings" 106 | 107 | trans "{{.BasePackage}}/transport/http" 108 | ) 109 | 110 | type StringService struct{} 111 | func (StringService) Uppercase(str string) (string, error) { 112 | return strings.ToUpper(str), nil 113 | } 114 | 115 | func (StringService) Count(str string) int { 116 | return len(str) 117 | } 118 | 119 | 120 | func main() { 121 | var svc StringService 122 | 123 | trans.ServersForEndpoints(svc) 124 | http.ListenAndServe(":12345", nil) 125 | } 126 | ``` 127 | 128 | ### Current Layers Generated 129 | 130 | The list of layers that are currently generated are 131 | * HTTP Transport 132 | * Endpoint Path's for HTTP 133 | * Service Middleware Logging 134 | 135 | ### TODO 136 | 137 | * Generate layers for Instrumenting 138 | * Generate layers for other transport types 139 | * Adjust functions to auto-wrap any embeded interfaces. 140 | * Clean-up main.go. (Most of this was taken from the stringer example to help 141 | parse AST trees. However, it needs to be cleaned up and shrunk wherever 142 | possible.) 143 | -------------------------------------------------------------------------------- /encoding/accept_mime_preference_test.go: -------------------------------------------------------------------------------- 1 | package encoding_test 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "net/http" 7 | 8 | "github.com/ayiga/go-kit-middlewarer/encoding" 9 | 10 | "testing" 11 | ) 12 | 13 | func TestAcceptParsing1(t *testing.T) { 14 | ctx := context.Background() 15 | r, err := http.NewRequest("GET", "/not/important", bytes.NewBuffer([]byte(string("{}")))) 16 | r.Header.Add("Content-Type", "application/json") 17 | r.Header.Add("Accept", "application/xml;q=0.7,application/json;q=0.8,application/gob;q=0.9") 18 | // text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 19 | 20 | t.Logf("Accept Header: %s\n", r.Header.Get("Accept")) 21 | 22 | req := new(request) 23 | req.embedMime = new(embedMime) 24 | 25 | def := encoding.Default() 26 | 27 | _, err = def.DecodeRequest(req)(ctx, r) 28 | if err != nil { 29 | t.Logf("Decoding Failed: %s\n", err) 30 | t.Fail() 31 | } 32 | 33 | if mime := req.GetMime(); mime != "application/gob" { 34 | t.Logf("mime != \"application/gob\": %s\n", mime) 35 | t.Fail() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /encoding/decoder.go: -------------------------------------------------------------------------------- 1 | package encoding 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "io" 7 | "io/ioutil" 8 | "net/http" 9 | 10 | httptransport "github.com/go-kit/kit/transport/http" 11 | ) 12 | 13 | // Decoder is any type that can populate a given structure via a Decode method. 14 | type Decoder interface { 15 | // Decode should populate the given interface with the information stored 16 | // within the Decoder. 17 | Decode(interface{}) error 18 | } 19 | 20 | // GenerateDecoder is a function that takes an io.Reader and returns a Decoder 21 | type GenerateDecoder func(io.Reader) Decoder 22 | 23 | // MakeRequestDecoder exists to help bridge the gaps for encoding. It takes a 24 | // request interface type, and a GenerateDecoder, and ultimately returns a 25 | // function that can decode the given request. 26 | func MakeRequestDecoder(request interface{}, gen GenerateDecoder) httptransport.DecodeRequestFunc { 27 | return func(ctx context.Context, r *http.Request) (interface{}, error) { 28 | if err := gen(r.Body).Decode(request); err != nil { 29 | return nil, err 30 | } 31 | return request, nil 32 | } 33 | } 34 | 35 | // MakeResponseDecoder exists to help bridge the gaps for encoding. It takes a 36 | // response interface type, and a GenerateDecoder, and ultimately returns a 37 | // function that can decode a given Response. 38 | func MakeResponseDecoder(response interface{}, gen GenerateDecoder) httptransport.DecodeResponseFunc { 39 | return func(ctx context.Context, r *http.Response) (interface{}, error) { 40 | if r.StatusCode < 200 || r.StatusCode > 299 { 41 | // I'm assuming we have an error at this point, and we should 42 | // represent it as such. 43 | ct := parseContentType(r.Header.Get("Content-Type")) 44 | if ct.contentType == "text/plain" { 45 | // ok, this is just a simple plain text document, there's not 46 | // much to do here... so we'll transmit it plainly. 47 | c, err := ioutil.ReadAll(r.Body) 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | // this is unfortunate, but we have no other information to 53 | // decode with, so at the very least, let's report it as an 54 | // error 55 | return errors.New(string(c)), nil 56 | } 57 | 58 | var we WrapperError 59 | // we'll let the current Decoder try to decode the error. In this 60 | // case we'll give it the custom error type we've created, to wrap 61 | // the error so we can ensure it decodes properly... 62 | if err := gen(r.Body).Decode(&we); err != nil { 63 | return nil, err 64 | } 65 | 66 | if _, ok := we.Err.(error); we.Err != nil && ok { 67 | return we.Err, nil 68 | } 69 | 70 | return we, nil 71 | } 72 | 73 | if err := gen(r.Body).Decode(response); err != nil { 74 | return nil, err 75 | } 76 | return response, nil 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /encoding/default.go: -------------------------------------------------------------------------------- 1 | package encoding 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "io/ioutil" 7 | "mime" 8 | "net/http" 9 | "strconv" 10 | "strings" 11 | 12 | httptransport "github.com/go-kit/kit/transport/http" 13 | ) 14 | 15 | // EmbededMime applies to the intermediary transmission representation of method 16 | // arguments and results. Using the structure we will attempt to communicate 17 | // with the encoders / decoders what mime type to use. 18 | type EmbededMime interface { 19 | // Retrieves the currently set mime type of this structure. If nothing has 20 | // been set, it would be best to specify a def one. 21 | GetMime() string 22 | 23 | // Sets the current mime type to use. If set this mime type should be 24 | // retrieved by future calls to GetMime on the same variable. 25 | SetMime(mime string) 26 | } 27 | 28 | // Default returns the default RequestResponseEncoding 29 | func Default() RequestResponseEncoding { 30 | return def(0) 31 | } 32 | 33 | // def is the default Encoding handler. It will attempt to resolve all 34 | // http transmitted encodings based on the information contained within the 35 | // HTTP Headers. 36 | type def int 37 | 38 | const DefaultEncoding = "application/json" 39 | 40 | // (\w+\/\w+[;q=score],?)+ 41 | type acceptContentHeader struct { 42 | mime []string 43 | value []float32 44 | } 45 | 46 | func parseAccept(accept string) acceptContentHeader { 47 | var mimes []string 48 | var values []float32 49 | 50 | parts := strings.Split(accept, ",") 51 | for _, p := range parts { 52 | mediaType, params, err := mime.ParseMediaType(p) 53 | if err != nil { 54 | continue 55 | } 56 | 57 | if params["q"] == "" { 58 | mimes = append(mimes, mediaType) 59 | values = append(values, 1.0) 60 | continue 61 | } 62 | 63 | v, err := strconv.ParseFloat(params["q"], 32) 64 | if err != nil { 65 | continue 66 | } 67 | mimes = append(mimes, mediaType) 68 | values = append(values, float32(v)) 69 | } 70 | 71 | return acceptContentHeader{ 72 | mime: mimes, 73 | value: values, 74 | } 75 | } 76 | 77 | func (a acceptContentHeader) highest() string { 78 | var score float32 79 | var max = "" 80 | for i, m := range a.mime { 81 | if a.value[i] > score { 82 | score = a.value[i] 83 | max = m 84 | } 85 | } 86 | 87 | return max 88 | } 89 | 90 | // specific;options,... 91 | type contentTypeValue struct { 92 | contentType string 93 | } 94 | 95 | func parseContentType(contentType string) contentTypeValue { 96 | parts := strings.Split(contentType, ",") 97 | mediaType, _, err := mime.ParseMediaType(parts[0]) 98 | if err != nil { 99 | return contentTypeValue{} 100 | } 101 | 102 | return contentTypeValue{ 103 | contentType: mediaType, 104 | } 105 | } 106 | 107 | func getFromEmbededMime(em EmbededMime) (mime string, encoding RequestResponseEncoding, err error) { 108 | if mime = em.GetMime(); mime != "" { 109 | encoding, err = Get(mime) 110 | return 111 | } 112 | 113 | return "", nil, ErrMimeNotSpecified 114 | } 115 | 116 | func encodeRequest(mime string, ctx context.Context, encoding RequestResponseEncoding, r *http.Request, request interface{}) error { 117 | r.Header.Set("Content-Type", mime) 118 | r.Header.Set("Accept", mime) 119 | return encoding.EncodeRequest()(ctx, r, request) 120 | } 121 | 122 | func encodeResponse(mime string, ctx context.Context, encoding RequestResponseEncoding, w http.ResponseWriter, response interface{}) error { 123 | w.Header().Set("Content-Type", mime) 124 | return encoding.EncodeResponse()(ctx, w, response) 125 | } 126 | 127 | func transferMimeDetails(em EmbededMime, ct contentTypeValue, accept acceptContentHeader) { 128 | if len(accept.mime) > 0 { 129 | acpt := accept.highest() 130 | if _, err := Get(acpt); err == nil { 131 | em.SetMime(acpt) 132 | return 133 | } 134 | } 135 | 136 | em.SetMime(ct.contentType) 137 | } 138 | 139 | // EncodeRequest implements RequestResponseEncoding 140 | func (def) EncodeRequest() httptransport.EncodeRequestFunc { 141 | return func(ctx context.Context, r *http.Request, request interface{}) error { 142 | if em, ok := request.(EmbededMime); ok { 143 | if mime, encoding, err := getFromEmbededMime(em); err == nil { 144 | return encodeRequest(mime, ctx, encoding, r, request) 145 | } 146 | } 147 | 148 | // we failed, unfortunately. However, we are making a request 149 | // so we can just specify the default encoding 150 | encoding, err := Get(DefaultEncoding) 151 | if err != nil { 152 | // we have really big problems at this point 153 | return err 154 | } 155 | 156 | return encodeRequest(DefaultEncoding, ctx, encoding, r, request) 157 | } 158 | } 159 | 160 | // DecodeRequest implements RequestResponseEncoding 161 | func (def) DecodeRequest(request interface{}) httptransport.DecodeRequestFunc { 162 | return func(ctx context.Context, r *http.Request) (interface{}, error) { 163 | ct := parseContentType(r.Header.Get("Content-Type")) 164 | accept := parseAccept(r.Header.Get("Accept")) 165 | 166 | if ct.contentType == "" { 167 | // let's try to guess the type based on the request 168 | return hintResolver(0).DecodeRequest(request)(ctx, r) 169 | } else if encoding, err := Get(ct.contentType); err == nil { 170 | if em, ok := request.(EmbededMime); ok { 171 | transferMimeDetails(em, ct, accept) 172 | } 173 | 174 | return encoding.DecodeRequest(request)(ctx, r) 175 | } 176 | 177 | // let's try to guess the type based on the request 178 | return hintResolver(0).DecodeRequest(request)(ctx, r) 179 | } 180 | } 181 | 182 | // EncodeResponse implements RequestResponseEncoding 183 | func (def) EncodeResponse() httptransport.EncodeResponseFunc { 184 | return func(ctx context.Context, w http.ResponseWriter, response interface{}) error { 185 | if em, ok := response.(EmbededMime); ok { 186 | if mime, encoding, err := getFromEmbededMime(em); err == nil { 187 | return encodeResponse(mime, ctx, encoding, w, response) 188 | } 189 | } 190 | 191 | // we failed, but we'll try to use our default, so that we will 192 | // at least make some forward progress 193 | 194 | encoding, err := Get(DefaultEncoding) 195 | if err != nil { 196 | return err 197 | 198 | } 199 | 200 | return encodeResponse(DefaultEncoding, ctx, encoding, w, response) 201 | } 202 | } 203 | 204 | // DecodeResponse implements RequestResponseEncoding 205 | func (def) DecodeResponse(response interface{}) httptransport.DecodeResponseFunc { 206 | return func(ctx context.Context, r *http.Response) (interface{}, error) { 207 | ct := parseContentType(r.Header.Get("Content-Type")) 208 | if ct.contentType == "" { 209 | // fall back 210 | // let's try to guess the type based on the response 211 | return hintResolver(0).DecodeResponse(response)(ctx, r) 212 | } else if encoding, err := Get(ct.contentType); err == nil { 213 | return encoding.DecodeResponse(response)(ctx, r) 214 | } else if r.StatusCode < 200 || r.StatusCode > 299 { 215 | if ct.contentType == "text/plain" { 216 | // ok, this is just a simple plain text document, there's not 217 | // much to do here... so we'll transmit it plainly. 218 | c, err := ioutil.ReadAll(r.Body) 219 | if err != nil { 220 | return nil, err 221 | } 222 | 223 | // this is unfortunate, but we have no other information to 224 | // decode with, so at the very least, let's report it as an 225 | // error 226 | return errors.New(string(c)), nil 227 | } 228 | 229 | var we WrapperError 230 | return hintResolver(0).DecodeResponse(&we)(ctx, r) 231 | } 232 | 233 | // let's try to guess the type based on the response 234 | return hintResolver(0).DecodeResponse(response)(ctx, r) 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /encoding/default_error_test.go: -------------------------------------------------------------------------------- 1 | package encoding_test 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "errors" 7 | "fmt" 8 | "io/ioutil" 9 | "net/http" 10 | "testing" 11 | 12 | "github.com/ayiga/go-kit-middlewarer/encoding" 13 | ) 14 | 15 | func TestDefaultErrorEncodingDecode(t *testing.T) { 16 | testError := errors.New("this is some sort of error") 17 | ctx := context.Background() 18 | buf := bytes.NewBuffer([]byte(testError.Error())) 19 | ro := new(http.Response) 20 | ro.StatusCode = 500 21 | ro.Body = ioutil.NopCloser(buf) 22 | ro.Header = make(http.Header) 23 | ro.Header.Set("Content-Type", "text/plain; encoding=utf-8") 24 | ro.Header.Set("Content-Length", fmt.Sprintf("%d", buf.Len())) 25 | 26 | resp := new(request) 27 | r, err := encoding.Default().DecodeResponse(resp)(ctx, ro) 28 | if err != nil { 29 | t.Fatalf("Unable to Decode Response: %s", err) 30 | } 31 | 32 | err, ok := r.(error) 33 | if !ok { 34 | t.Fatal("Unable to cast returned response into an error") 35 | } 36 | 37 | if got, want := err.Error(), testError.Error(); got != want { 38 | t.Errorf(".Error():\ngot:\n\t%s\nwant:\n\t%s", got, want) 39 | } 40 | 41 | if got, want := r == testError, false; got != want { 42 | t.Errorf(".Error():\ngot:\n\t%t\nwant:\n\t%t", got, want) 43 | } 44 | } 45 | 46 | func TestDefaultErrorEncodingDecodeWithDefinitiveTypeDecoder(t *testing.T) { 47 | testError := errors.New("this is some sort of error") 48 | ctx := context.Background() 49 | buf := bytes.NewBuffer([]byte(testError.Error())) 50 | ro := new(http.Response) 51 | ro.StatusCode = 500 52 | ro.Body = ioutil.NopCloser(buf) 53 | ro.Header = make(http.Header) 54 | ro.Header.Set("Content-Type", "text/plain; encoding=utf-8") 55 | ro.Header.Set("Content-Length", fmt.Sprintf("%d", buf.Len())) 56 | 57 | resp := new(request) 58 | r, err := encoding.JSON(0).DecodeResponse(resp)(ctx, ro) 59 | if err != nil { 60 | t.Fatalf("Unable to Decode Response: %s", err) 61 | } 62 | 63 | err, ok := r.(error) 64 | if !ok { 65 | t.Fatal("Unable to cast returned response into an error") 66 | } 67 | 68 | if got, want := err.Error(), testError.Error(); got != want { 69 | t.Errorf(".Error():\ngot:\n\t%s\nwant:\n\t%s", got, want) 70 | } 71 | 72 | if got, want := r == testError, false; got != want { 73 | t.Errorf(".Error():\ngot:\n\t%t\nwant:\n\t%t", got, want) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /encoding/doc.go: -------------------------------------------------------------------------------- 1 | package encoding 2 | -------------------------------------------------------------------------------- /encoding/encode_decode_test.go: -------------------------------------------------------------------------------- 1 | package encoding_test 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "io" 7 | "io/ioutil" 8 | "net/http" 9 | 10 | "github.com/ayiga/go-kit-middlewarer/encoding" 11 | 12 | "testing" 13 | ) 14 | 15 | func TestJSONEncodeDecodeRequest(t *testing.T) { 16 | req := &request{ 17 | Str: "foo", 18 | Num: 1.5, 19 | Bool: true, 20 | Null: false, 21 | } 22 | ctx := context.Background() 23 | req.embedMime = new(embedMime) 24 | req.SetMime("application/json") 25 | 26 | ri, err := http.NewRequest("GET", "/does/not/matter", nil) 27 | if err != nil { 28 | panic(err) 29 | } 30 | 31 | err = encoding.Default().EncodeRequest()(ctx, ri, req) 32 | if err != nil { 33 | t.Log("Error Encoding Request: %s\n", err) 34 | t.Fail() 35 | } 36 | 37 | buf := new(bytes.Buffer) 38 | ri.Body = ioutil.NopCloser(io.TeeReader(ri.Body, buf)) 39 | 40 | resp := new(request) 41 | resp.embedMime = new(embedMime) 42 | 43 | _, err = encoding.Default().DecodeRequest(resp)(ctx, ri) 44 | 45 | str := "{\"str\":\"foo\",\"num\":1.5,\"bool\":true,\"null\":false}\n" // trailing new-line? 46 | if s := buf.String(); s != str { 47 | t.Logf("Encoding Does not match: %s\n", s) 48 | t.Fail() 49 | } 50 | 51 | if err != nil { 52 | t.Logf("Request Decode Failed: %s\n", err) 53 | t.Fail() 54 | } 55 | 56 | if req.Str != resp.Str { 57 | t.Logf("req.Str != resp.Str \"%s\" vs \"%s\"\n", req.Str, resp.Str) 58 | t.Fail() 59 | } 60 | 61 | if req.Num != resp.Num { 62 | t.Logf("req.Num != resp.Num %f vs %f\n", req.Num, resp.Num) 63 | t.Fail() 64 | } 65 | 66 | if req.Bool != resp.Bool { 67 | t.Logf("req.Bool != resp.Bool %t vs %t\n", req.Bool, resp.Bool) 68 | t.Fail() 69 | } 70 | 71 | if req.Null != resp.Null { 72 | t.Logf("req.Null != resp.Null %s vs %s\n", req.Null, resp.Null) 73 | t.Fail() 74 | } 75 | } 76 | 77 | func TestXMLEncodeDecodeRequest(t *testing.T) { 78 | req := &request{ 79 | Str: "foo", 80 | Num: 1.5, 81 | Bool: true, 82 | Null: nil, 83 | } 84 | ctx := context.Background() 85 | 86 | req.embedMime = new(embedMime) 87 | req.SetMime("application/xml") 88 | 89 | ri, err := http.NewRequest("GET", "/does/not/matter", nil) 90 | if err != nil { 91 | panic(err) 92 | } 93 | 94 | err = encoding.Default().EncodeRequest()(ctx, ri, req) 95 | if err != nil { 96 | t.Log("Error Encoding Request: %s\n", err) 97 | t.Fail() 98 | } 99 | 100 | buf := new(bytes.Buffer) 101 | ri.Body = ioutil.NopCloser(io.TeeReader(ri.Body, buf)) 102 | 103 | resp := new(request) 104 | resp.embedMime = new(embedMime) 105 | 106 | _, err = encoding.Default().DecodeRequest(resp)(ctx, ri) 107 | 108 | str := "foo1.5true" 109 | if s := buf.String(); s != str { 110 | t.Logf("Encoding Does not match: %s\n", s) 111 | t.Fail() 112 | } 113 | 114 | if err != nil { 115 | t.Logf("Request Decode Failed: %s\n", err) 116 | t.Fail() 117 | } 118 | 119 | if req.Str != resp.Str { 120 | t.Logf("req.Str != resp.Str \"%s\" vs \"%s\"\n", req.Str, resp.Str) 121 | t.Fail() 122 | } 123 | 124 | if req.Num != resp.Num { 125 | t.Logf("req.Num != resp.Num %f vs %f\n", req.Num, resp.Num) 126 | t.Fail() 127 | } 128 | 129 | if req.Bool != resp.Bool { 130 | t.Logf("req.Bool != resp.Bool %t vs %t\n", req.Bool, resp.Bool) 131 | t.Fail() 132 | } 133 | 134 | if req.Null != resp.Null { 135 | t.Logf("req.Null != resp.Null %s vs %s\n", req.Null, resp.Null) 136 | t.Fail() 137 | } 138 | } 139 | 140 | func TestGobEncodeDecodeRequest(t *testing.T) { 141 | req := &request{ 142 | Str: "foo", 143 | Num: 1.5, 144 | Bool: true, 145 | Null: nil, 146 | } 147 | ctx := context.Background() 148 | req.embedMime = new(embedMime) 149 | req.SetMime("application/xml") 150 | 151 | ri, err := http.NewRequest("GET", "/does/not/matter", nil) 152 | if err != nil { 153 | panic(err) 154 | } 155 | 156 | err = encoding.Default().EncodeRequest()(ctx, ri, req) 157 | if err != nil { 158 | t.Log("Error Encoding Request: %s\n", err) 159 | t.Fail() 160 | } 161 | 162 | buf := new(bytes.Buffer) 163 | ri.Body = ioutil.NopCloser(io.TeeReader(ri.Body, buf)) 164 | 165 | resp := new(request) 166 | resp.embedMime = new(embedMime) 167 | 168 | _, err = encoding.Default().DecodeRequest(resp)(ctx, ri) 169 | 170 | byts := []byte{0x3c, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x3e, 171 | 0x3c, 0x73, 0x74, 0x72, 0x3e, 0x66, 0x6f, 0x6f, 0x3c, 172 | 0x2f, 0x73, 0x74, 0x72, 0x3e, 0x3c, 0x6e, 0x75, 0x6d, 173 | 0x3e, 0x31, 0x2e, 0x35, 0x3c, 0x2f, 0x6e, 0x75, 0x6d, 174 | 0x3e, 0x3c, 0x62, 0x6f, 0x6f, 0x6c, 0x3e, 0x74, 0x72, 175 | 0x75, 0x65, 0x3c, 0x2f, 0x62, 0x6f, 0x6f, 0x6c, 0x3e, 176 | 0x3c, 0x2f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 177 | 0x3e} 178 | 179 | b := buf.Bytes() 180 | 181 | if bl1, bl2 := len(byts), len(b); bl1 != bl2 { 182 | t.Logf("bl1, bl2 := len(byts), len(b); bl1 != bl2: %d vs %d\n", bl1, bl2) 183 | t.Fail() 184 | } 185 | 186 | for i := 0; i < len(b); i++ { 187 | if b[i] != byts[i] { 188 | t.Logf("b[%d] != byts[%d]: %d vs %s", i, i, b[i], byts[i]) 189 | t.Fail() 190 | } 191 | } 192 | 193 | if err != nil { 194 | t.Logf("Request Decode Failed: %s\n", err) 195 | t.Fail() 196 | } 197 | 198 | if req.Str != resp.Str { 199 | t.Logf("req.Str != resp.Str \"%s\" vs \"%s\"\n", req.Str, resp.Str) 200 | t.Fail() 201 | } 202 | 203 | if req.Num != resp.Num { 204 | t.Logf("req.Num != resp.Num %f vs %f\n", req.Num, resp.Num) 205 | t.Fail() 206 | } 207 | 208 | if req.Bool != resp.Bool { 209 | t.Logf("req.Bool != resp.Bool %t vs %t\n", req.Bool, resp.Bool) 210 | t.Fail() 211 | } 212 | 213 | if req.Null != resp.Null { 214 | t.Logf("req.Null != resp.Null %s vs %s\n", req.Null, resp.Null) 215 | t.Fail() 216 | } 217 | } 218 | 219 | func TestXMLEncodeDecodeResponse(t *testing.T) { 220 | req := &request{ 221 | Str: "foo", 222 | Num: 1.5, 223 | Bool: true, 224 | Null: nil, 225 | } 226 | ctx := context.Background() 227 | req.embedMime = new(embedMime) 228 | req.SetMime("application/xml") 229 | 230 | buf := new(bytes.Buffer) 231 | ri := createResponseWriter(buf) 232 | 233 | err := encoding.Default().EncodeResponse()(ctx, ri, req) 234 | if err != nil { 235 | t.Log("Error Encoding Request: %s\n", err) 236 | t.Fail() 237 | } 238 | 239 | ro := new(http.Response) 240 | ro.StatusCode = 200 241 | ro.Body = ioutil.NopCloser(buf) 242 | ro.Header = make(http.Header) 243 | ro.Header.Set("Content-Type", "application/xml") 244 | 245 | resp := new(request) 246 | resp.embedMime = new(embedMime) 247 | 248 | str := "foo1.5true" 249 | if s := buf.String(); s != str { 250 | t.Logf("Encoding Does not match: %s\n", s) 251 | t.Fail() 252 | } 253 | 254 | _, err = encoding.Default().DecodeResponse(resp)(ctx, ro) 255 | 256 | if err != nil { 257 | t.Logf("Request Decode Failed: %s\n", err) 258 | t.Fail() 259 | } 260 | 261 | if req.Str != resp.Str { 262 | t.Logf("req.Str != resp.Str \"%s\" vs \"%s\"\n", req.Str, resp.Str) 263 | t.Fail() 264 | } 265 | 266 | if req.Num != resp.Num { 267 | t.Logf("req.Num != resp.Num %f vs %f\n", req.Num, resp.Num) 268 | t.Fail() 269 | } 270 | 271 | if req.Bool != resp.Bool { 272 | t.Logf("req.Bool != resp.Bool %t vs %t\n", req.Bool, resp.Bool) 273 | t.Fail() 274 | } 275 | 276 | if req.Null != resp.Null { 277 | t.Logf("req.Null != resp.Null %s vs %s\n", req.Null, resp.Null) 278 | t.Fail() 279 | } 280 | } 281 | 282 | func TestGobEncodeDecodeResponse(t *testing.T) { 283 | req := &request{ 284 | Str: "foo", 285 | Num: 1.5, 286 | Bool: true, 287 | Null: false, 288 | } 289 | ctx := context.Background() 290 | req.embedMime = new(embedMime) 291 | req.SetMime("application/gob") 292 | 293 | buf := new(bytes.Buffer) 294 | ri := createResponseWriter(buf) 295 | 296 | err := encoding.Default().EncodeResponse()(ctx, ri, req) 297 | if err != nil { 298 | t.Log("Error Encoding Request: %s\n", err) 299 | t.Fail() 300 | } 301 | 302 | ro := new(http.Response) 303 | ro.StatusCode = 200 304 | ro.Body = ioutil.NopCloser(buf) 305 | ro.Header = make(http.Header) 306 | ro.Header.Set("Content-Type", "application/gob") 307 | 308 | resp := new(request) 309 | resp.embedMime = new(embedMime) 310 | 311 | byts := []byte{0x37, 0xff, 0x81, 0x03, 0x01, 0x01, 0x07, 0x72, 0x65, 312 | 0x71, 0x75, 0x65, 0x73, 0x74, 0x01, 0xff, 0x82, 0x00, 313 | 0x01, 0x04, 0x01, 0x03, 0x53, 0x74, 0x72, 0x01, 0x0c, 314 | 0x00, 0x01, 0x03, 0x4e, 0x75, 0x6d, 0x01, 0x08, 0x00, 315 | 0x01, 0x04, 0x42, 0x6f, 0x6f, 0x6c, 0x01, 0x02, 0x00, 316 | 0x01, 0x04, 0x4e, 0x75, 0x6c, 0x6c, 0x01, 0x10, 0x00, 317 | 0x00, 0x00, 0x18, 0xff, 0x82, 0x01, 0x03, 0x66, 0x6f, 318 | 0x6f, 0x01, 0xfe, 0xf8, 0x3f, 0x01, 0x01, 0x01, 0x04, 319 | 0x62, 0x6f, 0x6f, 0x6c, 0x02, 0x02, 0x00, 0x00, 0x00} 320 | 321 | b := buf.Bytes() 322 | 323 | if bl1, bl2 := len(byts), len(b); bl1 != bl2 { 324 | t.Logf("bl1, bl2 := len(byts), len(b); bl1 != bl2: %d vs %d\n", bl1, bl2) 325 | t.Fail() 326 | } 327 | 328 | for i := 0; i < len(b); i++ { 329 | if b[i] != byts[i] { 330 | t.Logf("b[%d] != byts[%d]: %d vs %s", i, i, b[i], byts[i]) 331 | t.Fail() 332 | } 333 | } 334 | 335 | _, err = encoding.Default().DecodeResponse(resp)(ctx, ro) 336 | 337 | if err != nil { 338 | t.Logf("Request Decode Failed: %s\n", err) 339 | t.Fail() 340 | } 341 | 342 | if req.Str != resp.Str { 343 | t.Logf("req.Str != resp.Str \"%s\" vs \"%s\"\n", req.Str, resp.Str) 344 | t.Fail() 345 | } 346 | 347 | if req.Num != resp.Num { 348 | t.Logf("req.Num != resp.Num %f vs %f\n", req.Num, resp.Num) 349 | t.Fail() 350 | } 351 | 352 | if req.Bool != resp.Bool { 353 | t.Logf("req.Bool != resp.Bool %t vs %t\n", req.Bool, resp.Bool) 354 | t.Fail() 355 | } 356 | 357 | if req.Null != resp.Null { 358 | t.Logf("req.Null != resp.Null %s vs %s\n", req.Null, resp.Null) 359 | t.Fail() 360 | } 361 | } 362 | -------------------------------------------------------------------------------- /encoding/encoder.go: -------------------------------------------------------------------------------- 1 | package encoding 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "io" 7 | "io/ioutil" 8 | "net/http" 9 | 10 | httptransport "github.com/go-kit/kit/transport/http" 11 | ) 12 | 13 | // Encoder is anything that, given an interface, can store an encoding of the 14 | // structure passed into Encode. 15 | type Encoder interface { 16 | // Encode takes an interface, and should be able to translate it within the 17 | // given encoding, or it will fail with an error. 18 | Encode(interface{}) error 19 | } 20 | 21 | // GenerateEncoder is a function which takes an io.Writer, and returns an 22 | // Encoder 23 | type GenerateEncoder func(w io.Writer) Encoder 24 | 25 | // MakeRequestEncoder takes a GenerateEncoder and returns an 26 | // httptransport.EncodeRequestFunc 27 | func MakeRequestEncoder(gen GenerateEncoder) httptransport.EncodeRequestFunc { 28 | return func(ctx context.Context, r *http.Request, request interface{}) error { 29 | var buf bytes.Buffer 30 | err := gen(&buf).Encode(request) 31 | r.Body = ioutil.NopCloser(&buf) 32 | return err 33 | } 34 | } 35 | 36 | // MakeResponseEncoder takes a GenerateEncoder and returns an 37 | // httpstransport.EncodeResponseFunc 38 | func MakeResponseEncoder(gen GenerateEncoder) httptransport.EncodeResponseFunc { 39 | return func(ctx context.Context, w http.ResponseWriter, response interface{}) error { 40 | if e, ok := response.(error); ok { 41 | // we have an error, we'll wrap it, to ensure it's transmission 42 | // encode-ability 43 | 44 | we := WrapError(e) 45 | return gen(w).Encode(we) 46 | } 47 | 48 | return gen(w).Encode(response) 49 | } 50 | } 51 | 52 | // MakeErrorEncoder will take a generic GenerateEncoder function and will 53 | // return an ErrorEncoder 54 | func MakeErrorEncoder(gen RequestResponseEncoding) httptransport.ErrorEncoder { 55 | return func(ctx context.Context, err error, w http.ResponseWriter) { 56 | gen.EncodeResponse()(ctx, w, err) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /encoding/gob.go: -------------------------------------------------------------------------------- 1 | package encoding 2 | 3 | import ( 4 | "encoding/gob" 5 | "io" 6 | 7 | httptransport "github.com/go-kit/kit/transport/http" 8 | ) 9 | 10 | func init() { 11 | Register("application/gob", Gob(0), nil) 12 | Register("application/octet-stream+gob", Gob(0), nil) 13 | } 14 | 15 | // GobGenerateDecoder returns a GOB Decoder 16 | func GobGenerateDecoder(r io.Reader) Decoder { 17 | return gob.NewDecoder(r) 18 | } 19 | 20 | // GobGenerateEncoder returns a GOB Encoder 21 | func GobGenerateEncoder(w io.Writer) Encoder { 22 | return gob.NewEncoder(w) 23 | } 24 | 25 | // Gob is a simple Gob encoder / decoder that conforms to RequestResponseEncoding 26 | type Gob int 27 | 28 | // EncodeRequest implements RequestResponseEncoding 29 | func (Gob) EncodeRequest() httptransport.EncodeRequestFunc { 30 | return MakeRequestEncoder(GobGenerateEncoder) 31 | } 32 | 33 | // DecodeRequest implements RequestResponseEncoding 34 | func (Gob) DecodeRequest(request interface{}) httptransport.DecodeRequestFunc { 35 | return MakeRequestDecoder(request, GobGenerateDecoder) 36 | } 37 | 38 | // EncodeResponse implements RequestResponseEncoding 39 | func (Gob) EncodeResponse() httptransport.EncodeResponseFunc { 40 | return MakeResponseEncoder(GobGenerateEncoder) 41 | } 42 | 43 | // DecodeResponse implements RequestResponseEncoding 44 | func (Gob) DecodeResponse(response interface{}) httptransport.DecodeResponseFunc { 45 | return MakeResponseDecoder(response, GobGenerateDecoder) 46 | } 47 | -------------------------------------------------------------------------------- /encoding/hint_resolver.go: -------------------------------------------------------------------------------- 1 | package encoding 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "errors" 7 | "io" 8 | "io/ioutil" 9 | "net/http" 10 | 11 | httptransport "github.com/go-kit/kit/transport/http" 12 | ) 13 | 14 | var ErrEmptyRequest = errors.New("Empty Request, nothing to sniff") 15 | 16 | func copyRequestToBuf(r *http.Request) ([]byte, error) { 17 | buf := new(bytes.Buffer) 18 | var i int64 = 0 19 | for i < r.ContentLength { 20 | l, err := io.CopyN(buf, r.Body, r.ContentLength) 21 | if err != nil { 22 | return nil, err 23 | } 24 | i += l 25 | } 26 | 27 | // replace the body, though thisi s probably not necessary 28 | return buf.Bytes(), nil 29 | } 30 | 31 | func copyResponseToBuf(r *http.Response) ([]byte, error) { 32 | buf := new(bytes.Buffer) 33 | var i int64 = 0 34 | for i < r.ContentLength { 35 | l, err := io.CopyN(buf, r.Body, r.ContentLength) 36 | if err != nil { 37 | return nil, err 38 | } 39 | i += l 40 | } 41 | 42 | // replace the body, though thisi s probably not necessary 43 | return buf.Bytes(), nil 44 | } 45 | 46 | type hintResolver int 47 | 48 | // EncodeRequest does not implement RequestResponseEncoding 49 | func (hintResolver) EncodeRequest() httptransport.EncodeRequestFunc { 50 | return func(ctx context.Context, r *http.Request, request interface{}) error { 51 | return ErrNotImplemented 52 | } 53 | } 54 | 55 | // DecodeRequest implements RequestResponseEncoding 56 | func (hintResolver) DecodeRequest(request interface{}) httptransport.DecodeRequestFunc { 57 | return func(ctx context.Context, r *http.Request) (interface{}, error) { 58 | byts, err := copyRequestToBuf(r) 59 | if err != nil { 60 | return request, err 61 | } 62 | 63 | if len(byts) <= 0 { 64 | // we have an issue, nothing to detect 65 | return request, ErrEmptyRequest 66 | } 67 | 68 | rune1 := []rune(string(byts))[0] 69 | 70 | var mimesToSkip = map[string]bool{} 71 | for mime, hints := range mimeToFirstRunes { 72 | for _, rune2 := range hints { 73 | mimesToSkip[mime] = true 74 | if rune1 == rune2 { 75 | encoding, err := Get(mime) 76 | if err != nil { 77 | // not found... this should be impossible 78 | // but it's good to check it anyway. 79 | break 80 | } 81 | 82 | r.Body = ioutil.NopCloser(bytes.NewBuffer(byts)) 83 | 84 | if _, err = encoding.DecodeRequest(request)(ctx, r); err != nil { 85 | // encoding failed... let's retry 86 | break 87 | } 88 | 89 | if em, ok := request.(EmbededMime); ok { 90 | // let's embed the mime type 91 | accept := parseAccept(r.Header.Get("Accept")) 92 | ct := parseContentType(mime) 93 | transferMimeDetails(em, ct, accept) 94 | } 95 | 96 | // we succeeded 97 | return request, nil 98 | } 99 | } 100 | } 101 | 102 | // well... I guess we'll just try all of them, on at a time... 103 | for mime, encoding := range mimeToEncodings { 104 | if mimesToSkip[mime] { 105 | continue 106 | } 107 | 108 | r.Body = ioutil.NopCloser(bytes.NewBuffer(byts)) 109 | 110 | if _, err = encoding.DecodeRequest(request)(ctx, r); err != nil { 111 | continue 112 | } 113 | 114 | if em, ok := request.(EmbededMime); ok { 115 | // let's embed the mime type 116 | accept := parseAccept(r.Header.Get("Accept")) 117 | ct := parseContentType(mime) 118 | transferMimeDetails(em, ct, accept) 119 | } 120 | 121 | // we succeeded 122 | return request, nil 123 | } 124 | 125 | return request, ErrUnableToDetermineMime 126 | } 127 | } 128 | 129 | // EncodeResponse does not implement RequestResponseEncoding 130 | func (hintResolver) EncodeResponse() httptransport.EncodeResponseFunc { 131 | return func(ctx context.Context, w http.ResponseWriter, response interface{}) error { 132 | return ErrNotImplemented 133 | } 134 | } 135 | 136 | // DecodeResponse implements RequestResponseEncoding 137 | func (hintResolver) DecodeResponse(response interface{}) httptransport.DecodeResponseFunc { 138 | return func(ctx context.Context, r *http.Response) (interface{}, error) { 139 | byts, err := copyResponseToBuf(r) 140 | if err != nil { 141 | return response, err 142 | } 143 | 144 | rune1 := []rune(string(byts))[0] 145 | 146 | var mimesToSkip = map[string]bool{} 147 | 148 | for mime, hints := range mimeToFirstRunes { 149 | for _, rune2 := range hints { 150 | mimesToSkip[mime] = true 151 | if rune1 == rune2 { 152 | encoding, err := Get(mime) 153 | if err != nil { 154 | // not found... this should be impossible 155 | // but it's good to check it anyway. 156 | break 157 | } 158 | 159 | r.Body = ioutil.NopCloser(bytes.NewBuffer(byts)) 160 | 161 | if _, err = encoding.DecodeResponse(response)(ctx, r); err != nil { 162 | // encoding failed... let's retry 163 | break 164 | } 165 | // we succeeded 166 | return response, nil 167 | } 168 | } 169 | } 170 | 171 | // well... I guess we'll just try all of them, on at a time... 172 | for mime, encoding := range mimeToEncodings { 173 | if mimesToSkip[mime] { 174 | continue 175 | } 176 | 177 | r.Body = ioutil.NopCloser(bytes.NewBuffer(byts)) 178 | 179 | if _, err = encoding.DecodeResponse(response)(ctx, r); err != nil { 180 | // error decoding, it's likely not this mime type. 181 | continue 182 | } 183 | 184 | if em, ok := response.(EmbededMime); ok { 185 | // let's embed the mime type 186 | accept := parseAccept(r.Header.Get("Accept")) 187 | ct := parseContentType(mime) 188 | transferMimeDetails(em, ct, accept) 189 | } 190 | 191 | // we succeeded 192 | return response, nil 193 | } 194 | 195 | return response, ErrUnableToDetermineMime 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /encoding/json.go: -------------------------------------------------------------------------------- 1 | package encoding 2 | 3 | import ( 4 | "encoding/json" 5 | "io" 6 | 7 | httptransport "github.com/go-kit/kit/transport/http" 8 | ) 9 | 10 | func init() { 11 | arr := []rune{'{', '[', '"', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'} 12 | Register("text/json", JSON(0), arr) 13 | Register("application/json", JSON(0), arr) 14 | } 15 | 16 | // JSONGenerateDecoder returns a JSON Decoder 17 | func JSONGenerateDecoder(r io.Reader) Decoder { 18 | return json.NewDecoder(r) 19 | } 20 | 21 | // JSONGenerateEncoder returns a JSON Encoder 22 | func JSONGenerateEncoder(w io.Writer) Encoder { 23 | return json.NewEncoder(w) 24 | } 25 | 26 | // JSON is a simple JSON encoder / decoder that conforms to RequestResponseEncoding 27 | type JSON int 28 | 29 | // EncodeRequest implements RequestResponseEncoding 30 | func (JSON) EncodeRequest() httptransport.EncodeRequestFunc { 31 | return MakeRequestEncoder(JSONGenerateEncoder) 32 | } 33 | 34 | // DecodeRequest implements RequestResponseEncoding 35 | func (JSON) DecodeRequest(request interface{}) httptransport.DecodeRequestFunc { 36 | return MakeRequestDecoder(request, JSONGenerateDecoder) 37 | } 38 | 39 | // EncodeResponse implements RequestResponseEncoding 40 | func (JSON) EncodeResponse() httptransport.EncodeResponseFunc { 41 | return MakeResponseEncoder(JSONGenerateEncoder) 42 | } 43 | 44 | // DecodeResponse implements RequestResponseEncoding 45 | func (JSON) DecodeResponse(response interface{}) httptransport.DecodeResponseFunc { 46 | return MakeResponseDecoder(response, JSONGenerateDecoder) 47 | } 48 | -------------------------------------------------------------------------------- /encoding/register_encodings.go: -------------------------------------------------------------------------------- 1 | package encoding 2 | 3 | import ( 4 | httptransport "github.com/go-kit/kit/transport/http" 5 | ) 6 | 7 | // Err are the errors that can be returned from Register or Get 8 | type Err int 9 | 10 | const ( 11 | // ErrUnknown represents a non-error 12 | ErrUnknown Err = iota 13 | // ErrAlreadyRegistered represents an mime type that has already been 14 | // registered 15 | ErrAlreadyRegistered 16 | // ErrMimeNotFound represents a mime type with no associated encoding 17 | ErrMimeNotFound 18 | // ErrNoRegistrationsExist represents that nothing has been registered with 19 | // this Encoder / Decoder 20 | ErrNoRegistrationsExist 21 | // ErrMimeNotSpecified rerpesents that no information has been specified 22 | // in order to determine the Mime-type 23 | ErrMimeNotSpecified 24 | // ErrUnableToDetermineMime represents an error that indicates that 25 | // the attempt to automatically detect the mime type has failed. 26 | ErrUnableToDetermineMime 27 | // ErrNotImplemented represents that the functionality of this method is 28 | // not implemented. 29 | ErrNotImplemented 30 | ) 31 | 32 | var errToString = map[Err]string{ 33 | ErrAlreadyRegistered: "That mime type already has already been registered", 34 | ErrMimeNotFound: "That mime type does not have an associated Encoder/Decoder", 35 | ErrNoRegistrationsExist: "Nothing has been registered, nothing to use for encoding/decoding", 36 | ErrMimeNotSpecified: "No information was given to help determine the mime type", 37 | ErrUnableToDetermineMime: "Fall back to automatically detect the mime type has failed", 38 | ErrNotImplemented: "This method is not implemented", 39 | } 40 | 41 | // Error implements the error interface 42 | func (e Err) Error() string { 43 | return errToString[e] 44 | } 45 | 46 | // RequestResponseEncoding represents a type that can be used to automatically 47 | // Encode and Decode on HTTP requests used by files generated with 48 | // go-kit-middlewarer 49 | type RequestResponseEncoding interface { 50 | // EncodeRequest should be able to return an EncodeRequestFunc that can 51 | // encode the given requests with the encoding type represented by this 52 | // type. 53 | EncodeRequest() httptransport.EncodeRequestFunc 54 | // DecodeRequest should be able to return a DecodeRequestFunc that, when 55 | // provided with an underlying type, can be used to decode a request with 56 | // the encoding type represented by this type. 57 | DecodeRequest(request interface{}) httptransport.DecodeRequestFunc 58 | // EncodeResponse should be able to return an EncodeResponseFunc that can 59 | // encode a given response with the encoding type represented by this 60 | // type. 61 | EncodeResponse() httptransport.EncodeResponseFunc 62 | // DecodeResponse should be able to return a DecodeResponseFunc that, when 63 | // provided with an underlying type, can be used to decode a response with 64 | // the encoding type represented by this type. 65 | DecodeResponse(response interface{}) httptransport.DecodeResponseFunc 66 | } 67 | 68 | var mimeToEncodings = map[string]RequestResponseEncoding{} 69 | var mimeToFirstRunes = map[string][]rune{} 70 | 71 | // Register will register the associated encoding with the given mime type 72 | func Register(mime string, encoding RequestResponseEncoding, startHint []rune) error { 73 | if mimeToEncodings[mime] != nil { 74 | return ErrAlreadyRegistered 75 | } 76 | 77 | if startHint != nil { 78 | mimeToFirstRunes[mime] = startHint 79 | } 80 | 81 | mimeToEncodings[mime] = encoding 82 | return nil 83 | } 84 | 85 | // Get will retrieve the encoding registered with the mime-type 86 | func Get(mime string) (RequestResponseEncoding, error) { 87 | if len(mimeToEncodings) == 0 { 88 | return nil, ErrNoRegistrationsExist 89 | } 90 | 91 | if mimeToEncodings[mime] == nil { 92 | return nil, ErrMimeNotFound 93 | } 94 | 95 | return mimeToEncodings[mime], nil 96 | } 97 | -------------------------------------------------------------------------------- /encoding/request_response_test.go: -------------------------------------------------------------------------------- 1 | package encoding_test 2 | 3 | import ( 4 | "bytes" 5 | "net/http" 6 | ) 7 | 8 | type embedMime struct { 9 | mime string 10 | } 11 | 12 | func (em *embedMime) GetMime() string { 13 | if em == nil || em.mime == "" { 14 | return "application/json" 15 | } 16 | 17 | return em.mime 18 | } 19 | 20 | func (em *embedMime) SetMime(mime string) { 21 | em.mime = mime 22 | } 23 | 24 | type request struct { 25 | *embedMime 26 | Str string `json:"str" xml:"str"` 27 | Num float64 `json:"num" xml:"num"` 28 | Bool bool `json:"bool" xml:"bool"` 29 | Null interface{} `json:"null" xml:"null"` 30 | } 31 | 32 | type responseWriter struct { 33 | *bytes.Buffer 34 | statusCode int 35 | headers http.Header 36 | } 37 | 38 | func (rw *responseWriter) Header() http.Header { 39 | return rw.headers 40 | } 41 | 42 | func (rw *responseWriter) WriteHeader(status int) { 43 | rw.statusCode = status 44 | } 45 | 46 | func createResponseWriter(buf *bytes.Buffer) *responseWriter { 47 | return &responseWriter{ 48 | Buffer: buf, 49 | headers: make(http.Header), 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /encoding/sniff_test.go: -------------------------------------------------------------------------------- 1 | package encoding_test 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "io/ioutil" 7 | "net/http" 8 | 9 | "github.com/ayiga/go-kit-middlewarer/encoding" 10 | 11 | "testing" 12 | ) 13 | 14 | func TestJSONRequestSniff1(t *testing.T) { 15 | var e request 16 | e.embedMime = new(embedMime) 17 | ctx := context.Background() 18 | 19 | str := "{\"str\":\"bar\",\"num\": 10,\"bool\":true,\"null\":null}" 20 | t.Logf("Data: %s\n", str) 21 | 22 | buf := bytes.NewBuffer([]byte(str)) 23 | request, err := http.NewRequest("GET", "/test", buf) 24 | if err != nil { 25 | panic(err) 26 | } 27 | 28 | def := encoding.Default() 29 | 30 | e1, err := def.DecodeRequest(&e)(ctx, request) 31 | if err != nil { 32 | t.Logf("Decode Request Failed: %s\n", err) 33 | t.Fail() 34 | } 35 | 36 | if e1 != &e { 37 | t.Logf("Returned Result is NOT the same value: %#v\n", e1) 38 | t.Fail() 39 | } 40 | 41 | if e.Str != "bar" { 42 | t.Logf("e.Str != \"bar\": \"%s\"\n", e.Str) 43 | t.Fail() 44 | } 45 | 46 | if e.Num != 10.0 { 47 | t.Logf("e.Num != 10.0: %f\n", e.Num) 48 | t.Fail() 49 | } 50 | 51 | if !e.Bool { 52 | t.Logf("!e.Bool: %f\n", e.Bool) 53 | t.Fail() 54 | } 55 | 56 | if e.Null != nil { 57 | t.Logf("e.Null != nil: %f\n", e.Null) 58 | t.Fail() 59 | } 60 | } 61 | 62 | func TestXMLRequestSniff1(t *testing.T) { 63 | var e request 64 | e.embedMime = new(embedMime) 65 | ctx := context.Background() 66 | 67 | str := "bar10.0truenull" 68 | t.Logf("Data: %s\n", str) 69 | 70 | buf := bytes.NewBuffer([]byte(str)) 71 | request, err := http.NewRequest("GET", "/test", buf) 72 | if err != nil { 73 | panic(err) 74 | } 75 | 76 | def := encoding.Default() 77 | 78 | e1, err := def.DecodeRequest(&e)(ctx, request) 79 | if err != nil { 80 | t.Logf("Decode Request Failed: %s\n", err) 81 | t.Fail() 82 | } 83 | 84 | if e1 != &e { 85 | t.Logf("Returned Result is NOT the same value: %#v\n", e1) 86 | t.Fail() 87 | } 88 | 89 | if e.Str != "bar" { 90 | t.Logf("e.Str != \"bar\": \"%s\"\n", e.Str) 91 | t.Fail() 92 | } 93 | 94 | if e.Num != 10.0 { 95 | t.Logf("e.Num != 10.0: %f\n", e.Num) 96 | t.Fail() 97 | } 98 | 99 | if !e.Bool { 100 | t.Logf("!e.Bool: %f\n", e.Bool) 101 | t.Fail() 102 | } 103 | 104 | if e.Null != nil { 105 | t.Logf("e.Null != nil: %f\n", e.Null) 106 | t.Fail() 107 | } 108 | } 109 | 110 | func TestGobRequestSniff1(t *testing.T) { 111 | var e request 112 | e.embedMime = new(embedMime) 113 | ctx := context.Background() 114 | 115 | b := []byte{0x37, 0xff, 0x81, 0x03, 0x01, 0x01, 0x07, 0x72, 0x65, 116 | 0x71, 0x75, 0x65, 0x73, 0x74, 0x01, 0xff, 0x82, 0x00, 117 | 0x01, 0x04, 0x01, 0x03, 0x53, 0x74, 0x72, 0x01, 0x0c, 118 | 0x00, 0x01, 0x03, 0x4e, 0x75, 0x6d, 0x01, 0x08, 0x00, 119 | 0x01, 0x04, 0x42, 0x6f, 0x6f, 0x6c, 0x01, 0x02, 0x00, 120 | 0x01, 0x04, 0x4e, 0x75, 0x6c, 0x6c, 0x01, 0x10, 0x00, 121 | 0x00, 0x00, 0x0e, 0xff, 0x82, 0x01, 0x03, 0x62, 0x61, 122 | 0x72, 0x01, 0xfe, 0x24, 0x40, 0x01, 0x01, 0x00} 123 | 124 | buf := bytes.NewBuffer(b) 125 | request, err := http.NewRequest("GET", "/test", buf) 126 | if err != nil { 127 | panic(err) 128 | } 129 | 130 | def := encoding.Default() 131 | 132 | e1, err := def.DecodeRequest(&e)(ctx, request) 133 | if err != nil { 134 | t.Logf("Decode Request Failed: %s\n", err) 135 | t.Fail() 136 | } 137 | 138 | if e1 != &e { 139 | t.Logf("Returned Result is NOT the same value: %#v\n", e1) 140 | t.Fail() 141 | } 142 | 143 | if e.Str != "bar" { 144 | t.Logf("e.Str != \"bar\": \"%s\"\n", e.Str) 145 | t.Fail() 146 | } 147 | 148 | if e.Num != 10.0 { 149 | t.Logf("e.Num != 10.0: %f\n", e.Num) 150 | t.Fail() 151 | } 152 | 153 | if !e.Bool { 154 | t.Logf("!e.Bool: %t\n", e.Bool) 155 | t.Fail() 156 | } 157 | 158 | if e.Null != nil { 159 | t.Logf("e.Null != nil: %s\n", e.Null) 160 | t.Fail() 161 | } 162 | } 163 | 164 | func TestJSONResponseSniff1(t *testing.T) { 165 | var e request 166 | e.embedMime = new(embedMime) 167 | ctx := context.Background() 168 | 169 | response := new(http.Response) 170 | response.StatusCode = 200 171 | 172 | str := "{\"str\":\"bar\",\"num\": 10,\"bool\":true,\"null\":null}" 173 | t.Logf("Data: %s\n", str) 174 | 175 | buf := bytes.NewBuffer([]byte(str)) 176 | 177 | response.Body = ioutil.NopCloser(buf) 178 | response.ContentLength = int64(buf.Len()) 179 | 180 | def := encoding.Default() 181 | 182 | e1, err := def.DecodeResponse(&e)(ctx, response) 183 | if err != nil { 184 | t.Logf("Decode Request Failed: %s\n", err) 185 | t.Fail() 186 | } 187 | 188 | if e1 != &e { 189 | t.Logf("Returned Result is NOT the same value: %#v\n", e1) 190 | t.Fail() 191 | } 192 | 193 | if e.Str != "bar" { 194 | t.Logf("e.Str != \"bar\": \"%s\"\n", e.Str) 195 | t.Fail() 196 | } 197 | 198 | if e.Num != 10.0 { 199 | t.Logf("e.Num != 10.0: %f\n", e.Num) 200 | t.Fail() 201 | } 202 | 203 | if !e.Bool { 204 | t.Logf("!e.Bool: %f\n", e.Bool) 205 | t.Fail() 206 | } 207 | 208 | if e.Null != nil { 209 | t.Logf("e.Null != nil: %f\n", e.Null) 210 | t.Fail() 211 | } 212 | } 213 | 214 | func TestXMLResponseSniff1(t *testing.T) { 215 | var e request 216 | e.embedMime = new(embedMime) 217 | ctx := context.Background() 218 | 219 | response := new(http.Response) 220 | response.StatusCode = 200 221 | 222 | str := "bar10.0truenull" 223 | t.Logf("Data: %s\n", str) 224 | 225 | buf := bytes.NewBuffer([]byte(str)) 226 | 227 | response.Body = ioutil.NopCloser(buf) 228 | response.ContentLength = int64(buf.Len()) 229 | 230 | def := encoding.Default() 231 | 232 | e1, err := def.DecodeResponse(&e)(ctx, response) 233 | if err != nil { 234 | t.Logf("Decode Request Failed: %s\n", err) 235 | t.Fail() 236 | } 237 | 238 | if e1 != &e { 239 | t.Logf("Returned Result is NOT the same value: %#v\n", e1) 240 | t.Fail() 241 | } 242 | 243 | if e.Str != "bar" { 244 | t.Logf("e.Str != \"bar\": \"%s\"\n", e.Str) 245 | t.Fail() 246 | } 247 | 248 | if e.Num != 10.0 { 249 | t.Logf("e.Num != 10.0: %f\n", e.Num) 250 | t.Fail() 251 | } 252 | 253 | if !e.Bool { 254 | t.Logf("!e.Bool: %f\n", e.Bool) 255 | t.Fail() 256 | } 257 | 258 | if e.Null != nil { 259 | t.Logf("e.Null != nil: %f\n", e.Null) 260 | t.Fail() 261 | } 262 | } 263 | 264 | func TestGobResponseSniff1(t *testing.T) { 265 | var e request 266 | e.embedMime = new(embedMime) 267 | ctx := context.Background() 268 | 269 | response := new(http.Response) 270 | response.StatusCode = 200 271 | 272 | b := []byte{0x37, 0xff, 0x81, 0x03, 0x01, 0x01, 0x07, 0x72, 0x65, 273 | 0x71, 0x75, 0x65, 0x73, 0x74, 0x01, 0xff, 0x82, 0x00, 274 | 0x01, 0x04, 0x01, 0x03, 0x53, 0x74, 0x72, 0x01, 0x0c, 275 | 0x00, 0x01, 0x03, 0x4e, 0x75, 0x6d, 0x01, 0x08, 0x00, 276 | 0x01, 0x04, 0x42, 0x6f, 0x6f, 0x6c, 0x01, 0x02, 0x00, 277 | 0x01, 0x04, 0x4e, 0x75, 0x6c, 0x6c, 0x01, 0x10, 0x00, 278 | 0x00, 0x00, 0x0e, 0xff, 0x82, 0x01, 0x03, 0x62, 0x61, 279 | 0x72, 0x01, 0xfe, 0x24, 0x40, 0x01, 0x01, 0x00} 280 | 281 | buf := bytes.NewBuffer(b) 282 | 283 | response.Body = ioutil.NopCloser(buf) 284 | response.ContentLength = int64(buf.Len()) 285 | 286 | def := encoding.Default() 287 | 288 | e1, err := def.DecodeResponse(&e)(ctx, response) 289 | if err != nil { 290 | t.Logf("Decode Request Failed: %s\n", err) 291 | t.Fail() 292 | } 293 | 294 | if e1 != &e { 295 | t.Logf("Returned Result is NOT the same value: %#v\n", e1) 296 | t.Fail() 297 | } 298 | 299 | if e.Str != "bar" { 300 | t.Logf("e.Str != \"bar\": \"%s\"\n", e.Str) 301 | t.Fail() 302 | } 303 | 304 | if e.Num != 10.0 { 305 | t.Logf("e.Num != 10.0: %f\n", e.Num) 306 | t.Fail() 307 | } 308 | 309 | if !e.Bool { 310 | t.Logf("!e.Bool: %f\n", e.Bool) 311 | t.Fail() 312 | } 313 | 314 | if e.Null != nil { 315 | t.Logf("e.Null != nil: %f\n", e.Null) 316 | t.Fail() 317 | } 318 | } 319 | -------------------------------------------------------------------------------- /encoding/wrapper_error.go: -------------------------------------------------------------------------------- 1 | package encoding 2 | 3 | import ( 4 | "bytes" 5 | "encoding/gob" 6 | "encoding/json" 7 | "encoding/xml" 8 | "errors" 9 | "reflect" 10 | "strings" 11 | ) 12 | 13 | func init() { 14 | gob.Register(WrapperError{}) 15 | } 16 | 17 | type WrapperError struct { 18 | Type string `json:"type" xml:"type"` 19 | ErrString string `json:"errorString" xml:"error-string"` 20 | Err interface{} `json:"error" xml:"error"` 21 | } 22 | 23 | func (we WrapperError) Error() string { 24 | if e, ok := we.Err.(error); ok { 25 | return e.Error() 26 | } 27 | 28 | return we.ErrString 29 | } 30 | 31 | var ErrUnexpectedJSONDelim = errors.New("Unexpected JSON Delim") 32 | 33 | // implements encoding/json.Unmarshaler 34 | func (we *WrapperError) UnmarshalJSON(p []byte) error { 35 | buf := bytes.NewBuffer(p) 36 | dec := json.NewDecoder(buf) 37 | 38 | typ := reflect.TypeOf(*we) 39 | getTag := func(name string) string { 40 | n, _ := typ.FieldByName(name) 41 | return n.Tag.Get("json") 42 | } 43 | 44 | for { 45 | t, err := dec.Token() 46 | if err != nil { 47 | return err 48 | } 49 | if delim, ok := t.(json.Delim); ok { 50 | // have a deliminator 51 | switch delim.String() { 52 | case "{": 53 | continue 54 | case "}": 55 | return nil 56 | default: 57 | // unexpected Delim 58 | return ErrUnexpectedJSONDelim 59 | } 60 | } 61 | 62 | if str, ok := t.(string); ok { 63 | switch str { 64 | case getTag("Type"): 65 | err = dec.Decode(&we.Type) 66 | if err != nil { 67 | break 68 | } 69 | 70 | e, err := GetErrorInstance(we.Type) 71 | if err == nil { 72 | we.Err = e 73 | } 74 | case getTag("ErrString"): 75 | err = dec.Decode(&we.ErrString) 76 | case getTag("Err"): 77 | err = dec.Decode(&we.Err) 78 | if we.Err != nil { 79 | we.Err = reflect.Indirect(reflect.ValueOf(we.Err)).Interface() 80 | } 81 | default: 82 | continue 83 | } 84 | } 85 | 86 | if err != nil { 87 | return err 88 | } 89 | } 90 | return nil 91 | } 92 | 93 | var ErrUnexpectedElementType = errors.New("Unexpected XML Element Type") 94 | 95 | // implements encoding/xml.Unmarshaler 96 | func (we *WrapperError) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { 97 | typ := reflect.TypeOf(*we) 98 | 99 | getTag := func(name string) string { 100 | n, _ := typ.FieldByName(name) 101 | return n.Tag.Get("xml") 102 | } 103 | for { 104 | t, err := d.Token() 105 | if err != nil { 106 | return err 107 | } 108 | if t == start.End() { 109 | // we've consumed everything there is 110 | return nil 111 | } 112 | 113 | startToken, ok := t.(xml.StartElement) 114 | if t == nil || !ok { 115 | return ErrUnexpectedElementType 116 | } 117 | 118 | switch startToken.Name.Local { 119 | case getTag("Type"): 120 | err = d.DecodeElement(&we.Type, &startToken) 121 | if err != nil { 122 | break 123 | } 124 | if e, err := GetErrorInstance(we.Type); err == nil { 125 | we.Err = e 126 | } 127 | case getTag("ErrString"): 128 | err = d.DecodeElement(&we.ErrString, &startToken) 129 | case getTag("Err"): 130 | err = d.DecodeElement(&we.Err, &startToken) 131 | if we.Err != nil { 132 | we.Err = reflect.Indirect(reflect.ValueOf(we.Err)).Interface() 133 | } 134 | default: 135 | } 136 | 137 | if err != nil { 138 | return err 139 | } 140 | } 141 | 142 | return nil 143 | } 144 | 145 | func WrapError(e error) *WrapperError { 146 | t := reflect.TypeOf(e) 147 | if _, err := GetErrorInstance(t.String()); err != nil { 148 | // don't transmit errors.errorString types 149 | return &WrapperError{ 150 | Type: t.String(), 151 | ErrString: e.Error(), 152 | } 153 | } 154 | 155 | return &WrapperError{ 156 | Type: t.String(), 157 | ErrString: e.Error(), 158 | Err: e, 159 | } 160 | } 161 | 162 | var ErrBlacklisted = errors.New("This Error type isn't able to registered, as it is not encodable / decodable") 163 | var ErrDuplicate = errors.New("You tried to register a duplicate type") 164 | var ErrUnknownError = errors.New("The type specified hasn't be registered") 165 | 166 | var registeredErrors = make(map[string]reflect.Type) 167 | 168 | // RegisterError will attempt to register the given Error with the encoders / 169 | // decoders and will make it available for Decoding errors for the encoders / 170 | // decoders. 171 | // 172 | // This will not automatically register this error type with encoding/gob 173 | func RegisterError(e error) error { 174 | t := reflect.TypeOf(e) 175 | if reflect.TypeOf(ErrBlacklisted) == t { 176 | return ErrBlacklisted 177 | } 178 | 179 | // ensure that we do not have a pointer 180 | if t.Kind() == reflect.Ptr { 181 | t = reflect.ValueOf(e).Type() 182 | } 183 | 184 | if registeredErrors[t.String()] != nil { 185 | return ErrDuplicate 186 | } 187 | 188 | // store the type information 189 | registeredErrors[t.String()] = t 190 | return nil 191 | } 192 | 193 | // GetErrorInstance will use reflection to attempt and instanciate a new error 194 | // of the given type string. The error returned will be a pointer. 195 | func GetErrorInstance(s string) (interface{}, error) { 196 | s = strings.TrimPrefix(s, "*") 197 | if registeredErrors[s] == nil { 198 | return nil, ErrUnknownError 199 | } 200 | 201 | return reflect.New(registeredErrors[s]).Interface(), nil 202 | } 203 | -------------------------------------------------------------------------------- /encoding/wrapper_error_test.go: -------------------------------------------------------------------------------- 1 | package encoding_test 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/gob" 7 | "fmt" 8 | "io/ioutil" 9 | "net/http" 10 | "reflect" 11 | "testing" 12 | 13 | "github.com/ayiga/go-kit-middlewarer/encoding" 14 | kithttptransport "github.com/go-kit/kit/transport/http" 15 | ) 16 | 17 | func TestDecodeErrorJSON(t *testing.T) { 18 | buf := new(bytes.Buffer) 19 | rw := createResponseWriter(buf) 20 | ctx := context.Background() 21 | 22 | // server error... 23 | rw.WriteHeader(500) 24 | err := encoding.JSON(0).EncodeResponse()(ctx, rw, http.ErrContentLength) 25 | if err != nil { 26 | t.Fatalf("Unable to Encode Response: %s", err) 27 | } 28 | 29 | t.Logf("Body Content: %s", buf.String()) 30 | 31 | ro := new(http.Response) 32 | ro.StatusCode = rw.statusCode 33 | ro.Body = ioutil.NopCloser(buf) 34 | ro.Header = make(http.Header) 35 | ro.Header.Set("Content-Type", "application/json") 36 | 37 | resp := new(request) 38 | r, err := encoding.Default().DecodeResponse(resp)(ctx, ro) 39 | if err != nil { 40 | t.Fatalf("Unable to Decode Response: %s", err) 41 | } 42 | 43 | if got, want := reflect.TypeOf(r), reflect.TypeOf(encoding.WrapperError{}); got != want { 44 | t.Fatalf("Type Of:\ngot:\n%s\nwant:\n%s", got, want) 45 | } 46 | 47 | err, ok := r.(error) 48 | if !ok { 49 | t.Fatal("Unable to cast returned response into an error") 50 | } 51 | 52 | if got, want := err.Error(), http.ErrContentLength.Error(); got != want { 53 | t.Errorf(".Error():\ngot:\n\t%s\nwant:\n\t%s", got, want) 54 | } 55 | 56 | if got, want := r == http.ErrMissingContentLength, false; got != want { 57 | t.Errorf(".Error():\ngot:\n\t%t\nwant:\n\t%t", got, want) 58 | } 59 | } 60 | 61 | func TestDecodeErrorXML(t *testing.T) { 62 | buf := new(bytes.Buffer) 63 | rw := createResponseWriter(buf) 64 | ctx := context.Background() 65 | 66 | // server error... 67 | rw.WriteHeader(500) 68 | err := encoding.XML(0).EncodeResponse()(ctx, rw, http.ErrContentLength) 69 | if err != nil { 70 | t.Fatalf("Unable to Encode Response: %s", err) 71 | } 72 | 73 | t.Logf("Body Content: %s", buf.String()) 74 | 75 | ro := new(http.Response) 76 | ro.StatusCode = rw.statusCode 77 | ro.Body = ioutil.NopCloser(buf) 78 | ro.Header = make(http.Header) 79 | ro.Header.Set("Content-Type", "application/xml") 80 | 81 | resp := new(request) 82 | r, err := encoding.Default().DecodeResponse(resp)(ctx, ro) 83 | if err != nil { 84 | t.Fatalf("Unable to Decode Response: %s", err) 85 | } 86 | 87 | if got, want := reflect.TypeOf(r), reflect.TypeOf(encoding.WrapperError{}); got != want { 88 | t.Fatalf("Type Of:\ngot:\n%s\nwant:\n%s", got, want) 89 | } 90 | 91 | err, ok := r.(error) 92 | if !ok { 93 | t.Fatal("Unable to cast returned response into an error") 94 | } 95 | 96 | if got, want := err.Error(), http.ErrContentLength.Error(); got != want { 97 | t.Errorf(".Error():\ngot:\n\t%s\nwant:\n\t%s", got, want) 98 | } 99 | 100 | if got, want := r == http.ErrMissingContentLength, false; got != want { 101 | t.Errorf(".Error():\ngot:\n\t%t\nwant:\n\t%t", got, want) 102 | } 103 | } 104 | 105 | func TestDecodeErrorGob(t *testing.T) { 106 | buf := new(bytes.Buffer) 107 | rw := createResponseWriter(buf) 108 | ctx := context.Background() 109 | 110 | // server error... 111 | rw.WriteHeader(500) 112 | err := encoding.Gob(0).EncodeResponse()(ctx, rw, http.ErrContentLength) 113 | if err != nil { 114 | t.Fatalf("Unable to Encode Response: %s", err) 115 | } 116 | 117 | t.Logf("Body Content: %s", buf.String()) 118 | 119 | ro := new(http.Response) 120 | ro.StatusCode = rw.statusCode 121 | ro.Body = ioutil.NopCloser(buf) 122 | ro.Header = make(http.Header) 123 | ro.Header.Set("Content-Type", "application/gob") 124 | 125 | resp := new(request) 126 | r, err := encoding.Default().DecodeResponse(resp)(ctx, ro) 127 | if err != nil { 128 | t.Fatalf("Unable to Decode Response: %s", err) 129 | } 130 | 131 | if got, want := reflect.TypeOf(r), reflect.TypeOf(encoding.WrapperError{}); got != want { 132 | t.Fatalf("Type Of:\ngot:\n%s\nwant:\n%s", got, want) 133 | } 134 | 135 | err, ok := r.(error) 136 | if !ok { 137 | t.Fatal("Unable to cast returned response into an error") 138 | } 139 | 140 | if got, want := err.Error(), http.ErrContentLength.Error(); got != want { 141 | t.Errorf(".Error():\ngot:\n\t%s\nwant:\n\t%s", got, want) 142 | } 143 | 144 | if got, want := r == http.ErrMissingContentLength, false; got != want { 145 | t.Errorf(".Error():\ngot:\n\t%t\nwant:\n\t%t", got, want) 146 | } 147 | } 148 | 149 | type CustomDecodableError struct { 150 | Code int `json:"code" xml:"code"` 151 | Reason string `json:"reason" xml:"reason"` 152 | } 153 | 154 | func (cde CustomDecodableError) Error() string { 155 | return fmt.Sprintf("Code: %d, Reason: %s", cde.Code, cde.Reason) 156 | } 157 | 158 | func init() { 159 | encoding.RegisterError(CustomDecodableError{}) 160 | gob.Register(CustomDecodableError{}) 161 | } 162 | 163 | func TestDecodeCustomDecodableErrorJSON(t *testing.T) { 164 | buf := new(bytes.Buffer) 165 | rw := createResponseWriter(buf) 166 | ctx := context.Background() 167 | 168 | testErr := CustomDecodableError{ 169 | Code: 50, 170 | Reason: "Halp", 171 | } 172 | 173 | // server error... 174 | rw.WriteHeader(500) 175 | err := encoding.JSON(0).EncodeResponse()(ctx, rw, &testErr) 176 | if err != nil { 177 | t.Fatalf("Unable to Encode Response: %s", err) 178 | } 179 | 180 | t.Logf("Body Content: %s", buf.String()) 181 | 182 | ro := new(http.Response) 183 | ro.StatusCode = rw.statusCode 184 | ro.Body = ioutil.NopCloser(buf) 185 | ro.Header = make(http.Header) 186 | ro.Header.Set("Content-Type", "application/json") 187 | 188 | resp := new(request) 189 | r, err := encoding.Default().DecodeResponse(resp)(ctx, ro) 190 | if err != nil { 191 | t.Fatalf("Unable to Decode Response: %s", err) 192 | } 193 | 194 | t.Logf("Decode Result: %#v", r) 195 | if got, want := reflect.TypeOf(r), reflect.TypeOf(testErr); got != want { 196 | t.Fatalf("Type Of:\ngot:\n%s\nwant:\n%s", got, want) 197 | } 198 | 199 | castErr, ok := r.(CustomDecodableError) 200 | if !ok { 201 | t.Fatal("Unable to cast returned response into an error") 202 | } 203 | 204 | if got, want := castErr.Error(), testErr.Error(); got != want { 205 | t.Errorf(".Error():\ngot:\n\t%s\nwant:\n\t%s", got, want) 206 | } 207 | 208 | if got, want := castErr.Code, testErr.Code; got != want { 209 | t.Errorf("castErr.Code:\ngot:\n\t%d\nwant:\n\t%d", got, want) 210 | } 211 | 212 | if got, want := castErr.Reason, testErr.Reason; got != want { 213 | t.Errorf("castErr.Reason:\ngot:\n\t%s\nwant:\n\t%s", got, want) 214 | } 215 | } 216 | 217 | func TestDecodeCustomDecodableErrorXML(t *testing.T) { 218 | buf := new(bytes.Buffer) 219 | rw := createResponseWriter(buf) 220 | ctx := context.Background() 221 | 222 | testErr := CustomDecodableError{ 223 | Code: 50, 224 | Reason: "Halp", 225 | } 226 | 227 | // server error... 228 | rw.WriteHeader(500) 229 | err := encoding.XML(0).EncodeResponse()(ctx, rw, &testErr) 230 | if err != nil { 231 | t.Fatalf("Unable to Encode Response: %s", err) 232 | } 233 | 234 | t.Logf("Body Content: %s", buf.String()) 235 | 236 | ro := new(http.Response) 237 | ro.StatusCode = rw.statusCode 238 | ro.Body = ioutil.NopCloser(buf) 239 | ro.Header = make(http.Header) 240 | ro.Header.Set("Content-Type", "application/xml") 241 | 242 | resp := new(request) 243 | r, err := encoding.Default().DecodeResponse(resp)(ctx, ro) 244 | if err != nil { 245 | t.Fatalf("Unable to Decode Response: %s", err) 246 | } 247 | 248 | t.Logf("Decode Result: %#v", r) 249 | if got, want := reflect.TypeOf(r), reflect.TypeOf(testErr); got != want { 250 | t.Fatalf("Type Of:\ngot:\n%s\nwant:\n%s", got, want) 251 | } 252 | 253 | castErr, ok := r.(CustomDecodableError) 254 | if !ok { 255 | t.Fatal("Unable to cast returned response into an error") 256 | } 257 | 258 | if got, want := castErr.Error(), testErr.Error(); got != want { 259 | t.Errorf(".Error():\ngot:\n\t%s\nwant:\n\t%s", got, want) 260 | } 261 | 262 | if got, want := castErr.Code, testErr.Code; got != want { 263 | t.Errorf("castErr.Code:\ngot:\n\t%d\nwant:\n\t%d", got, want) 264 | } 265 | 266 | if got, want := castErr.Reason, testErr.Reason; got != want { 267 | t.Errorf("castErr.Reason:\ngot:\n\t%s\nwant:\n\t%s", got, want) 268 | } 269 | } 270 | 271 | func TestDecodeCustomDecodableErrorGob(t *testing.T) { 272 | buf := new(bytes.Buffer) 273 | rw := createResponseWriter(buf) 274 | ctx := context.Background() 275 | 276 | testErr := CustomDecodableError{ 277 | Code: 50, 278 | Reason: "Halp", 279 | } 280 | 281 | // server error... 282 | rw.WriteHeader(500) 283 | err := encoding.Gob(0).EncodeResponse()(ctx, rw, &testErr) 284 | if err != nil { 285 | t.Fatalf("Unable to Encode Response: %s", err) 286 | } 287 | 288 | t.Logf("Body Content: %s", buf.String()) 289 | 290 | ro := new(http.Response) 291 | ro.StatusCode = rw.statusCode 292 | ro.Body = ioutil.NopCloser(buf) 293 | ro.Header = make(http.Header) 294 | ro.Header.Set("Content-Type", "application/gob") 295 | 296 | resp := new(request) 297 | r, err := encoding.Default().DecodeResponse(resp)(ctx, ro) 298 | if err != nil { 299 | t.Fatalf("Unable to Decode Response: %s", err) 300 | } 301 | 302 | t.Logf("Decode Result: %#v", r) 303 | if got, want := reflect.TypeOf(r), reflect.TypeOf(testErr); got != want { 304 | t.Fatalf("Type Of:\ngot:\n%s\nwant:\n%s", got, want) 305 | } 306 | 307 | castErr, ok := r.(CustomDecodableError) 308 | if !ok { 309 | t.Fatal("Unable to cast returned response into an error") 310 | } 311 | 312 | if got, want := castErr.Error(), testErr.Error(); got != want { 313 | t.Errorf(".Error():\ngot:\n\t%s\nwant:\n\t%s", got, want) 314 | } 315 | 316 | if got, want := castErr.Code, testErr.Code; got != want { 317 | t.Errorf("castErr.Code:\ngot:\n\t%d\nwant:\n\t%d", got, want) 318 | } 319 | 320 | if got, want := castErr.Reason, testErr.Reason; got != want { 321 | t.Errorf("castErr.Reason:\ngot:\n\t%s\nwant:\n\t%s", got, want) 322 | } 323 | } 324 | 325 | func TestEncodeDecodeHTTPErrorJSON(t *testing.T) { 326 | buf := new(bytes.Buffer) 327 | rw := createResponseWriter(buf) 328 | ctx := context.Background() 329 | testErr := kithttptransport.Error{ 330 | Domain: kithttptransport.DomainDo, 331 | Err: CustomDecodableError{ 332 | Code: 50, 333 | Reason: "Halp", 334 | }, 335 | } 336 | 337 | // server error... 338 | rw.WriteHeader(500) 339 | err := encoding.JSON(0).EncodeResponse()(ctx, rw, testErr) 340 | if err != nil { 341 | t.Fatalf("Unable to Encode Response: %s", err) 342 | } 343 | 344 | t.Logf("Body Content: %s", buf.String()) 345 | 346 | ro := new(http.Response) 347 | ro.StatusCode = rw.statusCode 348 | ro.Body = ioutil.NopCloser(buf) 349 | ro.Header = make(http.Header) 350 | ro.Header.Set("Content-Type", "application/json") 351 | 352 | resp := new(request) 353 | r, err := encoding.Default().DecodeResponse(resp)(ctx, ro) 354 | if err != nil { 355 | t.Fatalf("Unable to Decode Response: %s", err) 356 | } 357 | 358 | t.Logf("Decode Result: %#v", r) 359 | if got, want := reflect.TypeOf(r), reflect.TypeOf(testErr); got != want { 360 | t.Fatalf("Type Of:\ngot:\n%s\nwant:\n%s", got, want) 361 | } 362 | 363 | castErr, ok := r.(kithttptransport.Error) 364 | if !ok { 365 | t.Fatal("Unable to cast returned response into an error") 366 | } 367 | 368 | if got, want := castErr.Error(), testErr.Error(); got != want { 369 | t.Errorf(".Error():\ngot:\n\t%s\nwant:\n\t%s", got, want) 370 | } 371 | 372 | if got, want := castErr.Domain, testErr.Domain; got != want { 373 | t.Errorf("castErr.Domain:\ngot:\n\t%d\nwant:\n\t%d", got, want) 374 | } 375 | 376 | subErr1 := testErr.Err.(CustomDecodableError) 377 | subErr2, ok := castErr.Err.(CustomDecodableError) 378 | if !ok { 379 | t.Fatalf("Unable to cast sub error of returned response into an error") 380 | } 381 | 382 | if got, want := subErr2.Error(), subErr1.Error(); got != want { 383 | t.Errorf(".Error():\ngot:\n\t%s\nwant:\n\t%s", got, want) 384 | } 385 | 386 | if got, want := subErr2.Code, subErr1.Code; got != want { 387 | t.Errorf("castErr.Code:\ngot:\n\t%s\nwant:\n\t%s", got, want) 388 | } 389 | 390 | if got, want := subErr2.Reason, subErr1.Reason; got != want { 391 | t.Errorf("castErr.Reason:\ngot:\n\t%s\nwant:\n\t%s", got, want) 392 | } 393 | } 394 | 395 | func TestEncodeDecodeHTTPErrorXML(t *testing.T) { 396 | buf := new(bytes.Buffer) 397 | rw := createResponseWriter(buf) 398 | ctx := context.Background() 399 | testErr := kithttptransport.Error{ 400 | Domain: kithttptransport.DomainDo, 401 | Err: CustomDecodableError{ 402 | Code: 50, 403 | Reason: "Halp", 404 | }, 405 | } 406 | 407 | // server error... 408 | rw.WriteHeader(500) 409 | err := encoding.XML(0).EncodeResponse()(ctx, rw, testErr) 410 | if err != nil { 411 | t.Fatalf("Unable to Encode Response: %s", err) 412 | } 413 | 414 | t.Logf("Body Content: %s", buf.String()) 415 | 416 | ro := new(http.Response) 417 | ro.StatusCode = rw.statusCode 418 | ro.Body = ioutil.NopCloser(buf) 419 | ro.Header = make(http.Header) 420 | ro.Header.Set("Content-Type", "application/xml") 421 | 422 | resp := new(request) 423 | r, err := encoding.Default().DecodeResponse(resp)(ctx, ro) 424 | if err != nil { 425 | t.Fatalf("Unable to Decode Response: %s", err) 426 | } 427 | 428 | t.Logf("Decode Result: %#v", r) 429 | if got, want := reflect.TypeOf(r), reflect.TypeOf(testErr); got != want { 430 | t.Fatalf("Type Of:\ngot:\n%s\nwant:\n%s", got, want) 431 | } 432 | 433 | castErr, ok := r.(kithttptransport.Error) 434 | if !ok { 435 | t.Fatal("Unable to cast returned response into an error") 436 | } 437 | 438 | if got, want := castErr.Error(), testErr.Error(); got != want { 439 | t.Errorf(".Error():\ngot:\n\t%s\nwant:\n\t%s", got, want) 440 | } 441 | 442 | if got, want := castErr.Domain, testErr.Domain; got != want { 443 | t.Errorf("castErr.Domain:\ngot:\n\t%d\nwant:\n\t%d", got, want) 444 | } 445 | 446 | subErr1 := testErr.Err.(CustomDecodableError) 447 | subErr2, ok := castErr.Err.(CustomDecodableError) 448 | if !ok { 449 | t.Fatalf("Unable to cast sub error of returned response into an error") 450 | } 451 | 452 | if got, want := subErr2.Error(), subErr1.Error(); got != want { 453 | t.Errorf(".Error():\ngot:\n\t%s\nwant:\n\t%s", got, want) 454 | } 455 | 456 | if got, want := subErr2.Code, subErr1.Code; got != want { 457 | t.Errorf("castErr.Code:\ngot:\n\t%s\nwant:\n\t%s", got, want) 458 | } 459 | 460 | if got, want := subErr2.Reason, subErr1.Reason; got != want { 461 | t.Errorf("castErr.Reason:\ngot:\n\t%s\nwant:\n\t%s", got, want) 462 | } 463 | } 464 | -------------------------------------------------------------------------------- /encoding/xml.go: -------------------------------------------------------------------------------- 1 | package encoding 2 | 3 | import ( 4 | "encoding/xml" 5 | "io" 6 | 7 | httptransport "github.com/go-kit/kit/transport/http" 8 | ) 9 | 10 | func init() { 11 | arr := []rune{'<'} 12 | Register("text/xml", XML(0), arr) 13 | Register("application/xml", XML(0), arr) 14 | } 15 | 16 | // XMLGenerateDecoder returns an XML Decoder 17 | func XMLGenerateDecoder(r io.Reader) Decoder { 18 | return xml.NewDecoder(r) 19 | } 20 | 21 | // XMLGenerateEncoder returns an XML Encoder 22 | func XMLGenerateEncoder(w io.Writer) Encoder { 23 | return xml.NewEncoder(w) 24 | } 25 | 26 | // XML is a simple XML encoder / decoder that conforms to RequestResponseEncoding 27 | type XML int 28 | 29 | // EncodeRequest implements RequestResponseEncoding 30 | func (XML) EncodeRequest() httptransport.EncodeRequestFunc { 31 | return MakeRequestEncoder(XMLGenerateEncoder) 32 | } 33 | 34 | // DecodeRequest implements RequestResponseEncoding 35 | func (XML) DecodeRequest(request interface{}) httptransport.DecodeRequestFunc { 36 | return MakeRequestDecoder(request, XMLGenerateDecoder) 37 | } 38 | 39 | // EncodeResponse implements RequestResponseEncoding 40 | func (XML) EncodeResponse() httptransport.EncodeResponseFunc { 41 | return MakeResponseEncoder(XMLGenerateEncoder) 42 | } 43 | 44 | // DecodeResponse implements RequestResponseEncoding 45 | func (XML) DecodeResponse(response interface{}) httptransport.DecodeResponseFunc { 46 | return MakeResponseDecoder(response, XMLGenerateDecoder) 47 | } 48 | -------------------------------------------------------------------------------- /examples/stringsvc/cmd/stringsvc/README.md: -------------------------------------------------------------------------------- 1 | # stringservice example # 2 | This example is based on [go-kit's example String Service](https://github.com/go-kit/kit/tree/master/examples/stringsvc1/main.go). In order to utilize this example, the command has been written for you, however the generated layers have no been committed, and no binary exists. To this effect a couple 3 | of steps should be taken to get this working. 4 | 5 | ### Generate the Layers ### 6 | First, the layers for the example service need to be generated. This can be done by following these steps: 7 | 8 | - Open a Shell/Terminal 9 | - Navigate to The String Service Path: 10 | ```cd $GOPATH/src/github.com/ayiga/go-kit-middlewarer/examples/stringsvc``` 11 | - Run go's generate command: 12 | ```go generate``` 13 | 14 | ### Build the Binary ### 15 | Next the binary itself will need to be built. This binary is already written to import the layers that were generated by assuming that it is still in your `GOPATH`. So building or installing the binary should be easy. 16 | 17 | - Open a Shell/Terminal 18 | - Navigate to the StringService command's main.go folder: 19 | ```cd $GOPATH/src/github.com/ayiga/go-kit-middlewarer/examples/stringsvc/cmd/stringsvc``` 20 | - Build or install the command: ```go build .``` or ```go install``` 21 | 22 | ### Testing the Service ### 23 | Testing the result should be fairly simple, provided you have the ```curl``` command. At this point, all you should have to do is launch the service. 24 | `./stringsvc` if it is in the current working directory, and not in your `PATH` environment variable, or just `stringsvc` if it is. 25 | 26 | Once that is running, in another Terminal/Shell instance, you can use curl to make a request against the service: 27 | ```bash 28 | curl localhost:9000/stringservice/uppercase -H 'Content-Type: application/json' -d '{"str": "this is a test"}' 29 | ``` 30 | 31 | #### Some minor notes #### 32 | If the default port, `9000` doesn't work for you, you can change it in the source. 33 | 34 | The `Content-Type` should be supplied for the request in order for it to be decoded effectively. However, if it is not supplied, the encoding package will attempt to determine the encoding by sniffing it, and then by brute-force if need be. 35 | 36 | #### TODOs #### 37 | - allow the bind address to be overwritten via a commandline argument 38 | - default to `localhost:9000`, for the bind address. 39 | -------------------------------------------------------------------------------- /examples/stringsvc/cmd/stringsvc/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "os" 6 | "strings" 7 | 8 | "github.com/go-kit/kit/log" 9 | 10 | "github.com/ayiga/go-kit-middlewarer/examples/stringsvc" 11 | "github.com/ayiga/go-kit-middlewarer/examples/stringsvc/logging" 12 | trans "github.com/ayiga/go-kit-middlewarer/examples/stringsvc/transport/http" 13 | ) 14 | 15 | // StringService represents an object that will implement the StringService 16 | // interface 17 | type StringService struct{} 18 | 19 | // Uppercase implements StringService 20 | func (StringService) Uppercase(str string) (string, error) { 21 | return strings.ToUpper(str), nil 22 | } 23 | 24 | // Count implements StringService 25 | func (StringService) Count(str string) int { 26 | return len(str) 27 | } 28 | 29 | func main() { 30 | var svc stringsvc.StringService = StringService{} 31 | l := log.NewLogfmtLogger(os.Stderr) 32 | svc = logging.Middleware(l, svc)(svc) 33 | 34 | trans.ServersForEndpoints(svc) 35 | http.ListenAndServe(":9000", nil) 36 | } 37 | -------------------------------------------------------------------------------- /examples/stringsvc/cmd/stringsvc2/implementation.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | ) 7 | 8 | // StringService represents an object that will implement the StringService 9 | // interface 10 | type StringService struct{} 11 | 12 | // Uppercase implements StringService 13 | func (StringService) Uppercase(str string) (string, error) { 14 | return strings.ToUpper(str), nil 15 | } 16 | 17 | // Count implements StringService 18 | func (StringService) Count(str string) int { 19 | return len(str) 20 | } 21 | -------------------------------------------------------------------------------- /examples/stringsvc/cmd/stringsvc2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "net/http" 7 | "os" 8 | 9 | "golang.org/x/net/context" 10 | 11 | httptrans "github.com/go-kit/kit/transport/http" 12 | 13 | "github.com/ayiga/go-kit-middlewarer/encoding" 14 | trans "github.com/ayiga/go-kit-middlewarer/examples/stringsvc/transport/http" 15 | ) 16 | 17 | type arguments struct { 18 | httpPort string 19 | } 20 | 21 | var args arguments 22 | 23 | func init() { 24 | flag.StringVar(&args.httpPort, "httpPort", ":9000", "Specifies which port to listen for requests on") 25 | } 26 | 27 | func usage() { 28 | fmt.Printf("%s: server [-httpPort :port|-httpPort=:port]\n%s: client cmd arg [-httpPort :port|-httpPort=:port]\n", os.Args[0], os.Args[0]) 29 | flag.PrintDefaults() 30 | } 31 | 32 | func main() { 33 | flag.Usage = usage 34 | flag.Parse() 35 | a := flag.Args() 36 | 37 | var mode = "" 38 | if len(a) > 0 { 39 | mode = a[0] 40 | } 41 | 42 | switch mode { 43 | case "server": 44 | var svc StringService 45 | options := []httptrans.ServerOption{httptrans.ServerErrorEncoder(func(ctx context.Context, err error, w http.ResponseWriter) { 46 | w.WriteHeader(500) 47 | encoding.JSON(1).EncodeResponse()(ctx, w, err) 48 | }), 49 | } 50 | trans.ServersForEndpointsWithOptions(svc, []trans.ServerLayer{}, options) 51 | http.ListenAndServe(args.httpPort, nil) 52 | case "client": 53 | if len(a) < 3 { 54 | usage() 55 | return 56 | } 57 | 58 | client := trans.NewClient("127.0.0.1" + args.httpPort) 59 | 60 | cmd := a[1] 61 | arg := a[2] 62 | switch cmd { 63 | case "uppercase": 64 | str, err := client.Uppercase(arg) 65 | fmt.Printf("\t\"%s\".Uppercase: \"%s\", err: %s\n", arg, str, err) 66 | case "count": 67 | count := client.Count(arg) 68 | fmt.Printf("\t\"%s\".Count: %d\n", arg, count) 69 | } 70 | 71 | default: 72 | usage() 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /examples/stringsvc/string_service.go: -------------------------------------------------------------------------------- 1 | package stringsvc 2 | 3 | //go:generate go-kit-middlewarer -type=StringService 4 | 5 | // StringService from go-kit/kit's example 6 | type StringService interface { 7 | // Uppercase returns an uppercase version of the given string, or an error. 8 | Uppercase(str string) (upper string, err error) 9 | 10 | // Count returns the length of the given string 11 | Count(str string) (count int) 12 | } 13 | -------------------------------------------------------------------------------- /file.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "go/ast" 5 | ) 6 | 7 | // File holds a single parsed file and associated data. 8 | type File struct { 9 | pkg *Package // Packge to which this file belongs. 10 | file *ast.File // Parsed AST. 11 | path string 12 | fileName string 13 | // These fields are reset for each type being generated. 14 | 15 | imports []*Import 16 | types []Type 17 | interfaces []Interface 18 | structs []Struct 19 | variables []Variable 20 | // consts []*Constants 21 | } 22 | 23 | // genImportsAndTypes, false says we've consumed the entry down, and not to inform us of sub-entries 24 | func (f *File) genImportsAndTypes(node ast.Node) bool { 25 | switch t := node.(type) { 26 | case *ast.ImportSpec: 27 | // filter out context.Context, the reason for this is that we'd like to 28 | // automatically pass context into the trasnports when they occur. 29 | if t.Path.Value == "\"context\"" { 30 | return false 31 | } 32 | 33 | imp := createImport(t) 34 | f.imports = append(f.imports, imp) 35 | f.pkg.imports = append(f.pkg.imports, imp) 36 | return false 37 | case *ast.TypeSpec: 38 | switch s := t.Type.(type) { 39 | case *ast.InterfaceType: 40 | // filter out unexported 41 | if !t.Name.IsExported() { 42 | return false 43 | } 44 | 45 | i := createInterface(t.Name.Name, s, []string{}, *f) 46 | f.interfaces = append(f.interfaces, i) 47 | f.pkg.interfaces = append(f.pkg.interfaces, i) 48 | 49 | // add it to the list of declared types as well, since it 50 | // will be identified as a type to import, potentially 51 | f.pkg.types = append(f.pkg.types, createType(t.Name, f.pkg)) 52 | f.types = append(f.types, createType(t.Name, f.pkg)) 53 | return false 54 | case *ast.StructType: 55 | // filter out unexported 56 | if !t.Name.IsExported() { 57 | return false 58 | } 59 | stru := createStruct(t.Name.Name, s, *f) 60 | f.pkg.structs = append(f.pkg.structs, stru) 61 | f.structs = append(f.structs, stru) 62 | 63 | // add it to the list of declared types as well, since it 64 | // will be identified as a type to import, potentially 65 | f.pkg.types = append(f.pkg.types, createType(t.Name, f.pkg)) 66 | f.types = append(f.types, createType(t.Name, f.pkg)) 67 | return false 68 | case *ast.Ident: 69 | // filter out Unexported 70 | if !t.Name.IsExported() { 71 | return false 72 | } 73 | // new type, based off of a primitive, likely 74 | newType := createType(t.Name, f.pkg) 75 | f.pkg.types = append(f.pkg.types, newType) 76 | f.types = append(f.types, newType) 77 | return false 78 | default: 79 | log.Printf("Node Type: %#v\n", s) 80 | } 81 | case *ast.GenDecl: 82 | // Gen Decl are grouped declarations, things like 83 | // var(), const(), import() 84 | // if we don't handle this, it will be invoked at 85 | // the top level, and we'll get the underlying nodes 86 | // anyway... 87 | case *ast.ValueSpec: 88 | var typ *Type 89 | if t.Type != nil { 90 | t1 := createType(t.Type, f.pkg) 91 | typ = &t1 92 | } 93 | 94 | for i := 0; i < len(t.Names); i++ { 95 | // filter out unexported 96 | if !t.Names[i].IsExported() { 97 | continue 98 | } 99 | name := t.Names[i].Name 100 | // value := t.Values[i] 101 | v := createVariable(name, "", typ) 102 | f.pkg.variables = append(f.pkg.variables, v) 103 | f.variables = append(f.variables, v) 104 | } 105 | return false 106 | case *ast.FuncDecl, nil, *ast.Ident, *ast.CommentGroup, *ast.Comment: 107 | return false 108 | case *ast.File: // weird 109 | default: 110 | log.Printf("Node Type: %#v\n", t) 111 | } 112 | 113 | return true 114 | } 115 | -------------------------------------------------------------------------------- /generator.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "go/ast" 7 | "go/build" 8 | "go/format" 9 | "go/parser" 10 | "go/token" 11 | "os" 12 | "path/filepath" 13 | "strings" 14 | ) 15 | 16 | // Generator holds the state of the analysis. Primarily used to buffer the 17 | // output for format.Source. 18 | type Generator struct { 19 | buf bytes.Buffer // Accumulated ouptu. 20 | pkg *Package 21 | } 22 | 23 | // Printf writes the given output to the internalized buffer. 24 | func (g *Generator) Printf(format string, args ...interface{}) { 25 | fmt.Fprintf(&g.buf, format, args...) 26 | } 27 | 28 | func (g *Generator) parsePackageDir(directory string) { 29 | pkg, err := build.Default.ImportDir(directory, 0) 30 | if err != nil { 31 | log.Fatalf("cannot process directory %s: %s", directory, err) 32 | } 33 | 34 | d, e := os.Getwd() 35 | gopath := os.Getenv("GOPATH") 36 | 37 | if e != nil { 38 | log.Fatalf("Error Grabbing WD: %s\n", e) 39 | } 40 | 41 | prefix := filepath.Join(gopath, "src") + string([]rune{filepath.Separator}) 42 | 43 | d, err = filepath.Rel(prefix, d) 44 | if err != nil { 45 | log.Fatalf("Unable to get a relative path: %s\n", err) 46 | } 47 | 48 | var names []string 49 | names = append(names, pkg.GoFiles...) 50 | 51 | names = prefixDirectory(directory, names) 52 | g.parsePackage(d, names, nil) 53 | } 54 | 55 | func (g *Generator) parsePackageFiles(names []string) { 56 | g.parsePackage(".", names, nil) 57 | } 58 | 59 | // parsePackage analyzes the signle package constructed from the named files. 60 | // If text is non-nil, it is a string to be used instead of the content of the file, 61 | // to be used for testing. parsePackage exists if there is an error. 62 | func (g *Generator) parsePackage(directory string, names []string, text interface{}) { 63 | var files []*File 64 | var astFiles []*ast.File 65 | g.pkg = new(Package) 66 | fs := token.NewFileSet() 67 | for _, name := range names { 68 | if !strings.HasSuffix(name, ".go") { 69 | continue 70 | } 71 | 72 | parsedFile, err := parser.ParseFile(fs, name, text, parser.ParseComments) 73 | 74 | for _, v := range parsedFile.Comments { 75 | str := v.Text() 76 | if strings.HasPrefix(str, log.Prefix()) { 77 | lines := strings.Split(str, "\n") 78 | if len(lines) <= 0 { 79 | continue 80 | } 81 | var firstLine = lines[0] 82 | 83 | typ := strings.TrimPrefix(firstLine, log.Prefix()) 84 | if len(lines) > 1 { 85 | extras[typ] = strings.Join(lines[1:], "\n") 86 | } 87 | } 88 | } 89 | 90 | if err != nil { 91 | log.Fatalf("parsing package: %s: %s", name, err) 92 | } 93 | astFiles = append(astFiles, parsedFile) 94 | files = append(files, &File{ 95 | file: parsedFile, 96 | pkg: g.pkg, 97 | path: directory, 98 | fileName: name, 99 | }) 100 | } 101 | 102 | if len(astFiles) == 0 { 103 | log.Fatalf("%s: no buildable Go files", directory) 104 | } 105 | g.pkg.name = astFiles[0].Name.Name 106 | g.pkg.files = files 107 | g.pkg.dir = directory 108 | g.pkg.check(fs, astFiles) 109 | } 110 | 111 | // generate does 'things' 112 | func (g *Generator) generate(typeName string) { 113 | // pre-process 114 | for _, file := range g.pkg.files { 115 | // Set the state for this run of the walker. 116 | if file.file != nil { 117 | ast.Inspect(file.file, file.genImportsAndTypes) 118 | } 119 | } 120 | 121 | if *summarize != "" { 122 | g.pkg.Summarize() 123 | return 124 | } 125 | 126 | var targetFile *File 127 | 128 | for _, file := range g.pkg.files { 129 | for _, i := range file.interfaces { 130 | if i.name == typeName { 131 | targetFile = file 132 | break 133 | } 134 | } 135 | } 136 | 137 | if targetFile == nil { 138 | log.Fatalf("Unable to fine the type specified: %s\n", typeName) 139 | } 140 | 141 | // begin generation 142 | list := strings.Split(*middlewaresToGenerate, ",") 143 | list = append(list, "endpoint") 144 | for _, l := range list { 145 | if bindings[l] != nil { 146 | bindings[l](g, targetFile) 147 | } 148 | } 149 | } 150 | 151 | // format returns gofmt-ed contents of the Generator's buffer. 152 | func (g *Generator) format() []byte { 153 | src, err := format.Source(g.buf.Bytes()) 154 | if err != nil { 155 | log.Printf("warning: internal error: invalid Go generated: %s", err) 156 | log.Printf("warning: compile the pacakge to analyze the error") 157 | return g.buf.Bytes() 158 | } 159 | return src 160 | } 161 | -------------------------------------------------------------------------------- /import.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "go/ast" 6 | "path" 7 | // "path/filepath" 8 | "strings" 9 | ) 10 | 11 | type Import struct { 12 | name string 13 | path string 14 | last string 15 | isEmbeded bool 16 | isParam bool 17 | } 18 | 19 | func createImportWithPath(p string) *Import { 20 | last := path.Base(p) 21 | name := last 22 | if strings.Contains(last, "-") { 23 | lastPieces := strings.Split(last, "-") 24 | name = lastPieces[len(lastPieces)-1] 25 | } 26 | 27 | return &Import{ 28 | name: name, 29 | path: p, 30 | last: last, 31 | } 32 | } 33 | 34 | func createImport(imp *ast.ImportSpec) *Import { 35 | var name string 36 | pth := strings.TrimPrefix(strings.TrimSuffix(imp.Path.Value, "\""), "\"") 37 | last := path.Base(pth) 38 | if n := imp.Name; n == nil { 39 | name = last 40 | } else { 41 | name = n.String() 42 | } 43 | 44 | if strings.Contains(name, "-") { 45 | namePieces := strings.Split(name, "-") 46 | name = namePieces[len(namePieces)-1] 47 | } 48 | 49 | return &Import{ 50 | name: name, 51 | path: pth, 52 | last: last, 53 | } 54 | } 55 | 56 | func (i Import) ImportSpec() string { 57 | if i.name == i.last { 58 | return fmt.Sprintf("\"%s\"", i.path) 59 | } 60 | 61 | return fmt.Sprintf("%s \"%s\"", i.name, i.path) 62 | } 63 | -------------------------------------------------------------------------------- /interface.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "go/ast" 6 | "strings" 7 | ) 8 | 9 | // Value represents a declared constant. 10 | type Interface struct { 11 | name string // the name of the constant. 12 | methods []Method 13 | types []Type 14 | 15 | pkg *Package 16 | file File 17 | } 18 | 19 | func createInterface(name string, iface *ast.InterfaceType, reservedNames []string, file File) Interface { 20 | names := append([]string{}, reservedNames...) 21 | names = append(names, name) 22 | 23 | interf := Interface{ 24 | name: name, 25 | methods: make([]Method, 0, iface.Methods.NumFields()), 26 | types: nil, 27 | } 28 | for _, f := range iface.Methods.List { 29 | if len(f.Names) > 0 { 30 | // This is a method 31 | interf.methods = append(interf.methods, createMethod(f, names, file)) 32 | } else { 33 | // this is an interface. 34 | n := resolveFieldTypes(f.Type, file.pkg.name) 35 | potentialNamePieces := strings.Split(n, ".") 36 | if len(potentialNamePieces) > 0 { 37 | } 38 | 39 | interf.types = append(interf.types, createType(f.Type, file.pkg)) 40 | 41 | for _, imp := range file.pkg.imports { 42 | if strings.HasPrefix(n, fmt.Sprintf("%s.", imp.name)) { 43 | imp.isEmbeded = true 44 | } 45 | } 46 | } 47 | } 48 | return interf 49 | } 50 | -------------------------------------------------------------------------------- /logging.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | l "log" 5 | "os" 6 | ) 7 | 8 | var log *l.Logger 9 | 10 | func init() { 11 | log = l.New(os.Stdout, "go-kit-middlewarer", l.Lshortfile|l.Ltime|l.Ldate|l.Lmicroseconds) 12 | } 13 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | "strings" 9 | ) 10 | 11 | var ( 12 | typeNames = flag.String("type", "", "comma-separated list of type names; must be set") 13 | middlewaresToGenerate = flag.String("middleware", "logging,instrumenting,transport,zipkin", "comma-seperated list of middlewares to process. Options: [logging,instrumenting,transport,zipkin]") 14 | summarize = flag.String("summarize", "", "Prints out the Summary of Found structures intead of generating code") 15 | binaryName = "" 16 | ) 17 | 18 | var bindings map[string]func(*Generator, *File) 19 | 20 | func init() { 21 | bindings = make(map[string]func(*Generator, *File)) 22 | } 23 | func registerProcess(argument string, fun func(*Generator, *File)) { 24 | bindings[argument] = fun 25 | } 26 | 27 | var extras map[string]string 28 | 29 | // Usage is a replacement usage function for the flags package 30 | func Usage() { 31 | fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) 32 | fmt.Fprintf(os.Stderr, "\t%s [flags] -type T [directory]\n") 33 | } 34 | 35 | func main() { 36 | extras = make(map[string]string) 37 | binaryName = os.Args[0] 38 | log.SetPrefix(fmt.Sprintf("%s:", binaryName)) 39 | flag.Usage = Usage 40 | flag.Parse() 41 | 42 | if len(*typeNames) == 0 { 43 | flag.Usage() 44 | os.Exit(2) 45 | } 46 | 47 | types := strings.Split(*typeNames, ",") 48 | 49 | args := flag.Args() 50 | if len(args) == 0 { 51 | args = []string{"."} 52 | } 53 | 54 | var ( 55 | dir string 56 | g Generator 57 | ) 58 | 59 | if len(args) == 1 && isDirectory(args[0]) { 60 | dir = args[0] 61 | g.parsePackageDir(args[0]) 62 | // parsePackageDir(args[0]) 63 | } else { 64 | dir = filepath.Dir(args[0]) 65 | g.parsePackageFiles(args) 66 | // parsePackageFiles(args) 67 | } 68 | dir = dir 69 | // Run generate for each type. 70 | for _, typeName := range types { 71 | g.generate(typeName) 72 | } 73 | 74 | } 75 | 76 | func isDirectory(name string) bool { 77 | info, err := os.Stat(name) 78 | if err != nil { 79 | log.Fatal(err) 80 | } 81 | return info.IsDir() 82 | } 83 | 84 | func prefixDirectory(directory string, names []string) []string { 85 | if directory == "." { 86 | return names 87 | } 88 | 89 | ret := make([]string, len(names)) 90 | for i, name := range names { 91 | ret[i] = filepath.Join(directory, name) 92 | } 93 | return ret 94 | } 95 | -------------------------------------------------------------------------------- /method.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "go/ast" 6 | "strings" 7 | ) 8 | 9 | type Method struct { 10 | name string 11 | params []Param 12 | results []Param 13 | 14 | hasContextParam bool 15 | contextParamName string 16 | hasErrResult bool 17 | errorResultName string 18 | moreThanOneResult bool 19 | 20 | pkg *Package 21 | file File 22 | imports []Import 23 | types []Type 24 | interfaces []Interface 25 | } 26 | 27 | func createMethod(field *ast.Field, reservedNames []string, file File) Method { 28 | name := field.Names[0].Name 29 | names := append([]string{}, reservedNames...) 30 | names = append(names, name) 31 | fun, ok := field.Type.(*ast.FuncType) 32 | if !ok { 33 | return Method{ 34 | name: name, 35 | } 36 | } 37 | 38 | m := Method{ 39 | name: name, 40 | params: make([]Param, 0, fun.Params.NumFields()), 41 | results: make([]Param, 0, fun.Results.NumFields()), 42 | } 43 | 44 | if fun.Params != nil { 45 | for _, f := range fun.Params.List { 46 | param := createParam(f, names, "input", file) 47 | paramNames := param.names 48 | 49 | if param.typ.String() == "context.Context" { 50 | m.hasContextParam = true 51 | m.contextParamName = paramNames[0] 52 | } 53 | 54 | for _, imp := range file.pkg.imports { 55 | if strings.HasPrefix(param.typ.String(), fmt.Sprintf("%s.", imp.name)) { 56 | imp.isParam = true 57 | } 58 | } 59 | 60 | m.params = append(m.params, param) 61 | names = append(names, paramNames...) 62 | } 63 | } 64 | 65 | if fun.Results != nil { 66 | numResult := 0 67 | for _, f := range fun.Results.List { 68 | param := createParam(f, names, "output", file) 69 | paramNames := param.names 70 | 71 | if param.typ.String() == "error" { 72 | m.hasErrResult = true 73 | m.errorResultName = "err" 74 | if len(paramNames) > 0 { 75 | m.errorResultName = paramNames[0] 76 | } 77 | } 78 | 79 | for _, imp := range file.pkg.imports { 80 | if strings.HasPrefix(param.typ.String(), fmt.Sprintf("%s.", imp.name)) { 81 | imp.isParam = true 82 | } 83 | } 84 | 85 | if len(paramNames) > 0 { 86 | numResult += len(paramNames) 87 | } else { 88 | numResult++ 89 | } 90 | 91 | m.results = append(m.results, param) 92 | names = append(names, paramNames...) 93 | } 94 | 95 | m.moreThanOneResult = numResult > 1 96 | } 97 | 98 | return m 99 | } 100 | 101 | func (m Method) usedNames() []string { 102 | var result []string 103 | result = append(result, m.name) 104 | for _, p := range m.params { 105 | result = append(result, p.names...) 106 | } 107 | 108 | for _, r := range m.results { 109 | result = append(result, r.names...) 110 | } 111 | return result 112 | } 113 | 114 | func (m Method) InterfaceMethodSpec() string { 115 | params := make([]string, 0, len(m.params)) 116 | results := make([]string, 0, len(m.results)) 117 | resultNameCount := 0 118 | 119 | for _, p := range m.params { 120 | params = append(params, p.ParamSpec()) 121 | } 122 | 123 | for _, r := range m.results { 124 | results = append(results, r.ParamSpec()) 125 | resultNameCount += len(r.names) 126 | } 127 | 128 | if len(m.results) > 1 || resultNameCount > 0 { 129 | return fmt.Sprintf("%s(%s) (%s)", m.name, strings.Join(params, ", "), strings.Join(results, ", ")) 130 | } 131 | return fmt.Sprintf("%s(%s) %s", m.name, strings.Join(params, ", "), strings.Join(results, ", ")) 132 | } 133 | 134 | func (m Method) methodArguments() string { 135 | var result []string 136 | for _, p := range m.params { 137 | result = append(result, p.ParamSpec()) 138 | } 139 | 140 | return strings.Join(result, ", ") 141 | } 142 | 143 | func (m Method) methodArgumentNames() string { 144 | var result []string 145 | for _, p := range m.params { 146 | result = append(result, p.names...) 147 | } 148 | 149 | return strings.Join(result, ", ") 150 | } 151 | 152 | func (m Method) methodResults() string { 153 | var result []string 154 | for _, p := range m.results { 155 | result = append(result, p.ParamSpec()) 156 | } 157 | 158 | return strings.Join(result, ", ") 159 | } 160 | 161 | func (m Method) methodResultNames() string { 162 | var result []string 163 | for _, p := range m.results { 164 | result = append(result, p.names...) 165 | } 166 | 167 | return strings.Join(result, ", ") 168 | } 169 | -------------------------------------------------------------------------------- /mux/adapter/gorilla/router.go: -------------------------------------------------------------------------------- 1 | package gorilla 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gorilla/mux" 7 | ) 8 | 9 | // Router is a type alias for *github.com/gorilla/mux.Router to make reference 10 | // easier, as well as to implement the Mux interface. 11 | type Router struct { 12 | *mux.Router 13 | } 14 | 15 | type CastError int 16 | 17 | // implements the error interface. 18 | func (CastError) Error() string { 19 | return "Unable to cast Router to *mux.Router" 20 | } 21 | 22 | // NewRouter returns a new Router, wrapping the given gorilla mux Router. 23 | func NewRouter(router *mux.Router) *Router { 24 | return &Router{ 25 | Router: router, 26 | } 27 | } 28 | 29 | // Handle registers the handler for the given pattern. 30 | // According to net/http.ServeMux If a handler already exists for pattern, 31 | // the Handle invocation panics. 32 | func (r *Router) Handle(pattern string, handler http.Handler) { 33 | r.Router.Handle(pattern, handler) 34 | } 35 | 36 | // HandleFunc registers the handler function for the given pattern. 37 | func (r *Router) HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) { 38 | r.Router.HandleFunc(pattern, handler) 39 | } 40 | 41 | // ServerHTTP implements net/http.Handler 42 | func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { 43 | r.Router.ServeHTTP(w, req) 44 | } 45 | -------------------------------------------------------------------------------- /package.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "go/ast" 6 | "go/importer" 7 | "go/token" 8 | "go/types" 9 | ) 10 | 11 | type Package struct { 12 | dir string 13 | name string 14 | defs map[*ast.Ident]types.Object 15 | typesPkg *types.Package 16 | 17 | imports []*Import 18 | types []Type 19 | interfaces []Interface 20 | structs []Struct 21 | files []*File 22 | variables []Variable 23 | // consts []*Constants 24 | } 25 | 26 | // check type-checks the package. The package must be OK to proceed. 27 | func (pkg *Package) check(fs *token.FileSet, astFiles []*ast.File) { 28 | pkg.defs = make(map[*ast.Ident]types.Object) 29 | config := types.Config{Importer: importer.Default(), FakeImportC: true} 30 | info := &types.Info{ 31 | Defs: pkg.defs, 32 | } 33 | typesPkg, err := config.Check(pkg.dir, fs, astFiles, info) 34 | if err != nil { 35 | log.Fatalf("checking package: %s", err) 36 | } 37 | pkg.typesPkg = typesPkg 38 | } 39 | 40 | func (pkg *Package) Summarize() { 41 | fmt.Println("Summary") 42 | fmt.Printf("%s:\n", pkg.name) 43 | 44 | for _, f := range pkg.files { 45 | fmt.Printf("- %s:\n", f.fileName) 46 | fmt.Printf("\t- imports\n") 47 | 48 | for _, i := range f.imports { 49 | fmt.Printf("\t\t- %s\n", i.ImportSpec()) 50 | } 51 | 52 | fmt.Printf("\t- interfaces\n") 53 | for _, i := range f.interfaces { 54 | fmt.Printf("\t\t- %s\n", i.name) 55 | 56 | for _, t := range i.types { 57 | fmt.Printf("\t\t\t- %s\n", t.String()) 58 | } 59 | 60 | for _, m := range i.methods { 61 | fmt.Printf("\t\t\t- %s\n", m.InterfaceMethodSpec()) 62 | } 63 | } 64 | 65 | fmt.Printf("\t- structs\n") 66 | for _, i := range f.structs { 67 | fmt.Printf("\t\t- %s\n", i.name) 68 | } 69 | 70 | fmt.Printf("\t- types\n") 71 | for _, t := range f.types { 72 | fmt.Printf("\t\t- %s\n", t.String()) 73 | } 74 | 75 | fmt.Printf("\t- variables\n") 76 | for _, v := range f.variables { 77 | fmt.Printf("\t\t- %s\n", v.name) 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /param.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "go/ast" 6 | "strings" 7 | ) 8 | 9 | type Param struct { 10 | names []string 11 | typ Type 12 | } 13 | 14 | func createParam(field *ast.Field, reservedNames []string, suggestion string, file File) Param { 15 | p := Param{ 16 | names: make([]string, 0, len(field.Names)), 17 | typ: createType(field.Type, file.pkg), 18 | } 19 | 20 | for _, n := range field.Names { 21 | p.names = append(p.names, n.Name) 22 | } 23 | 24 | // no name specified, let's create one... 25 | if len(p.names) == 0 { 26 | n := determineLocalName(suggestion, reservedNames) 27 | p.names = []string{n} 28 | } 29 | 30 | return p 31 | } 32 | 33 | func (p Param) ParamSpec() string { 34 | if len(p.names) > 0 { 35 | return fmt.Sprintf("%s %s", strings.Join(p.names, ", "), p.typ) 36 | } 37 | return p.typ.String() 38 | } 39 | -------------------------------------------------------------------------------- /process-endpoint.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "os" 7 | "path" 8 | "path/filepath" 9 | "text/template" 10 | ) 11 | 12 | func processEndpoint(g *Generator, f *File) { 13 | gopath := os.Getenv("GOPATH") 14 | var buf bytes.Buffer 15 | 16 | tmpl, err := template.ParseFiles(filepath.Join(gopath, "src", "github.com", "ayiga", "go-kit-middlewarer", "tmpl", "endpoint.tmpl")) 17 | if err != nil { 18 | log.Fatalf("Template Parse Error: %s", err) 19 | } 20 | 21 | convertedPath := filepath.ToSlash(f.pkg.dir) 22 | 23 | endpointPackage := createImportWithPath(path.Join(convertedPath, "endpoint")) 24 | basePackage := createImportWithPath(convertedPath) 25 | 26 | for _, interf := range f.interfaces { 27 | err := tmpl.Execute(&buf, createTemplateBase(basePackage, endpointPackage, interf, f.imports)) 28 | if err != nil { 29 | log.Fatalf("Template execution failed: %s\n", err) 30 | } 31 | } 32 | 33 | filename := "defs_gen.go" 34 | 35 | file := openFile(filepath.Join(".", "endpoint"), filename) 36 | defer file.Close() 37 | 38 | fmt.Fprint(file, string(formatBuffer(buf, filename))) 39 | } 40 | 41 | func init() { 42 | registerProcess("endpoint", processEndpoint) 43 | } 44 | -------------------------------------------------------------------------------- /process-logging.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "os" 7 | "path" 8 | "path/filepath" 9 | "text/template" 10 | ) 11 | 12 | func processLogging(g *Generator, f *File) { 13 | gopath := os.Getenv("GOPATH") 14 | 15 | var buf bytes.Buffer 16 | 17 | extra, err := template.New("extra").Parse(extras["logging"]) 18 | if err != nil { 19 | log.Fatalf("Extra Template Parsing Error: %s", err) 20 | } 21 | 22 | files := []string{ 23 | filepath.Join(gopath, "src", "github.com", "ayiga", "go-kit-middlewarer", "tmpl", "logging.tmpl"), 24 | } 25 | tmpl, err := extra.ParseFiles(files...) 26 | if err != nil { 27 | log.Fatalf("Template Parse Error: %s", err) 28 | } 29 | 30 | convertedPath := filepath.ToSlash(f.pkg.dir) 31 | 32 | endpointPackage := createImportWithPath(path.Join(convertedPath, "endpoint")) 33 | basePackage := createImportWithPath(convertedPath) 34 | 35 | for _, interf := range f.interfaces { 36 | err := tmpl.ExecuteTemplate(&buf, "logging.tmpl", createTemplateBase(basePackage, endpointPackage, interf, f.imports)) 37 | if err != nil { 38 | log.Fatalf("Template execution failed: %s\n", err) 39 | } 40 | } 41 | 42 | filename := "middleware_gen.go" 43 | 44 | file := openFile(filepath.Join(".", "logging"), filename) 45 | defer file.Close() 46 | 47 | fmt.Fprint(file, string(formatBuffer(buf, filename))) 48 | } 49 | 50 | func init() { 51 | registerProcess("logging", processLogging) 52 | } 53 | -------------------------------------------------------------------------------- /process-transport.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "os" 7 | "path" 8 | "path/filepath" 9 | "text/template" 10 | ) 11 | 12 | func processRequestResponse(gopath string, tb TemplateBase) { 13 | var buf bytes.Buffer 14 | 15 | tmpl, err := template.ParseFiles(filepath.Join(gopath, "src", "github.com", "ayiga", "go-kit-middlewarer", "tmpl", "transport-request-response.tmpl")) 16 | if err != nil { 17 | log.Fatalf("Template Parse Error: %s", err) 18 | } 19 | 20 | err = tmpl.Execute(&buf, tb) 21 | if err != nil { 22 | log.Fatalf("Template execution failed: %s\n", err) 23 | } 24 | 25 | filename := "request-response_gen.go" 26 | 27 | file := openFile(filepath.Join(".", "transport", "http"), filename) 28 | defer file.Close() 29 | 30 | fmt.Fprint(file, string(formatBuffer(buf, filename))) 31 | } 32 | 33 | func processMakeEndpoint(gopath string, tb TemplateBase) { 34 | var buf bytes.Buffer 35 | 36 | tmpl, err := template.ParseFiles(filepath.Join(gopath, "src", "github.com", "ayiga", "go-kit-middlewarer", "tmpl", "transport-make-endpoint.tmpl")) 37 | if err != nil { 38 | log.Fatalf("Template Parse Error: %s", err) 39 | } 40 | 41 | err = tmpl.Execute(&buf, tb) 42 | if err != nil { 43 | log.Fatalf("Template execution failed: %s\n", err) 44 | } 45 | filename := "make-endpoint_gen.go" 46 | 47 | file := openFile(filepath.Join(".", "transport", "http"), filename) 48 | defer file.Close() 49 | 50 | fmt.Fprint(file, string(formatBuffer(buf, filename))) 51 | } 52 | 53 | func processHTTPServer(gopath string, tb TemplateBase) { 54 | var buf bytes.Buffer 55 | 56 | tmpl, err := template.ParseFiles(filepath.Join(gopath, "src", "github.com", "ayiga", "go-kit-middlewarer", "tmpl", "transport-http-server.tmpl")) 57 | if err != nil { 58 | log.Fatalf("Template Parse Error: %s", err) 59 | } 60 | 61 | err = tmpl.Execute(&buf, tb) 62 | if err != nil { 63 | log.Fatalf("Template execution failed: %s\n", err) 64 | } 65 | 66 | filename := "http-server_gen.go" 67 | 68 | file := openFile(filepath.Join(".", "transport", "http"), filename) 69 | defer file.Close() 70 | 71 | fmt.Fprint(file, string(formatBuffer(buf, filename))) 72 | } 73 | 74 | func processTransportClient(gopath string, tb TemplateBase) { 75 | var buf bytes.Buffer 76 | 77 | tmpl, err := template.ParseFiles(filepath.Join(gopath, "src", "github.com", "ayiga", "go-kit-middlewarer", "tmpl", "transport-client.tmpl")) 78 | if err != nil { 79 | log.Fatalf("Template Parse Error: %s", err) 80 | } 81 | 82 | err = tmpl.Execute(&buf, tb) 83 | if err != nil { 84 | log.Fatalf("Template execution failed: %s\n", err) 85 | } 86 | 87 | filename := "client_gen.go" 88 | 89 | file := openFile(filepath.Join(".", "transport", "http"), filename) 90 | defer file.Close() 91 | 92 | fmt.Fprint(file, string(formatBuffer(buf, filename))) 93 | } 94 | 95 | func processHTTPInstanceClient(gopath string, tb TemplateBase) { 96 | var buf bytes.Buffer 97 | 98 | tmpl, err := template.ParseFiles(filepath.Join(gopath, "src", "github.com", "ayiga", "go-kit-middlewarer", "tmpl", "transport-http-client.tmpl")) 99 | if err != nil { 100 | log.Fatalf("Template Parse Error: %s", err) 101 | } 102 | 103 | err = tmpl.Execute(&buf, tb) 104 | if err != nil { 105 | log.Fatalf("Template execution failed: %s\n", err) 106 | } 107 | 108 | filename := "http-client_gen.go" 109 | 110 | file := openFile(filepath.Join(".", "transport", "http"), filename) 111 | defer file.Close() 112 | 113 | fmt.Fprint(file, string(formatBuffer(buf, filename))) 114 | } 115 | 116 | func processHTTPLoadBalancedClient(gopath string, tb TemplateBase) { 117 | var buf bytes.Buffer 118 | 119 | tmpl, err := template.ParseFiles(filepath.Join(gopath, "src", "github.com", "ayiga", "go-kit-middlewarer", "tmpl", "transport-http-loadbalanced.tmpl")) 120 | if err != nil { 121 | log.Fatalf("Template Parse Error: %s", err) 122 | } 123 | 124 | err = tmpl.Execute(&buf, tb) 125 | if err != nil { 126 | log.Fatalf("Template execution failed: %s\n", err) 127 | } 128 | 129 | filename := "http-client-loadbalanced_gen.go" 130 | 131 | file := openFile(filepath.Join(".", "transport", "http"), filename) 132 | defer file.Close() 133 | 134 | fmt.Fprint(file, string(formatBuffer(buf, filename))) 135 | } 136 | 137 | func processTransport(g *Generator, f *File) { 138 | gopath := os.Getenv("GOPATH") 139 | 140 | convertedPath := filepath.ToSlash(f.pkg.dir) 141 | 142 | endpointPackage := createImportWithPath(path.Join(convertedPath, "endpoint")) 143 | basePackage := createImportWithPath(convertedPath) 144 | 145 | for _, interf := range f.interfaces { 146 | tb := createTemplateBase(basePackage, endpointPackage, interf, f.imports) 147 | processRequestResponse(gopath, tb) 148 | processMakeEndpoint(gopath, tb) 149 | processHTTPServer(gopath, tb) 150 | processTransportClient(gopath, tb) 151 | processHTTPInstanceClient(gopath, tb) 152 | processHTTPLoadBalancedClient(gopath, tb) 153 | } 154 | } 155 | 156 | func init() { 157 | registerProcess("transport", processTransport) 158 | } 159 | -------------------------------------------------------------------------------- /struct.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "go/ast" 5 | ) 6 | 7 | // Value represents a declared constant. 8 | type Struct struct { 9 | name string // the name of the constant. 10 | types []Type 11 | 12 | pkg *Package 13 | file File 14 | } 15 | 16 | func createStruct(name string, iface *ast.StructType, file File) Struct { 17 | stru := Struct{ 18 | name: name, 19 | types: nil, 20 | } 21 | 22 | return stru 23 | } 24 | -------------------------------------------------------------------------------- /template-structures.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | type TemplateCommon struct { 9 | BasePackage string 10 | BasePackageImport string 11 | BasePackageName string 12 | EndpointPackage string 13 | EndpointPackageName string 14 | EndpointPrefix string 15 | InterfaceName string 16 | InterfaceNameLcase string 17 | } 18 | 19 | type TemplateParam struct { 20 | PublicName string 21 | Name string 22 | Type string 23 | IsContext bool 24 | } 25 | 26 | func createTemplateParam(p Param) TemplateParam { 27 | return TemplateParam{ 28 | Type: p.typ.String(), 29 | } 30 | } 31 | 32 | type TemplateMethod struct { 33 | TemplateCommon 34 | HasContextParam bool 35 | ContextParamName string 36 | HasErrorResult bool 37 | ErrorResultName string 38 | HasMoreThanOneResult bool 39 | LocalName string 40 | MethodName string 41 | MethodNameLcase string 42 | MethodArguments string 43 | MethodResults string 44 | MethodResultNamesStr string 45 | MethodArgumentNamesStr string 46 | MethodArgumentNames []string 47 | MethodResultNames []string 48 | Params []TemplateParam 49 | Results []TemplateParam 50 | } 51 | 52 | func publicVariableName(str string) string { 53 | firstLetter := string([]byte{str[0]}) 54 | rest := "" 55 | if len(str) > 1 { 56 | rest = str[1:] 57 | } 58 | 59 | return strings.ToUpper(firstLetter) + rest 60 | } 61 | 62 | func privateVariableName(str string) string { 63 | firstLetter := string([]byte{str[0]}) 64 | rest := "" 65 | if len(str) > 1 { 66 | rest = str[1:] 67 | } 68 | 69 | return strings.ToLower(firstLetter) + rest 70 | } 71 | 72 | func createTemplateMethods(basePackage, endpointPackage *Import, interf Interface, methods []Method, reseveredNames []string) []TemplateMethod { 73 | results := make([]TemplateMethod, 0, len(methods)) 74 | for _, meth := range methods { 75 | var names []string 76 | names = append(names, reseveredNames...) 77 | names = append(names, meth.usedNames()...) 78 | var params []TemplateParam 79 | var methodsResults []TemplateParam 80 | 81 | var paramNames []string 82 | for _, p := range meth.params { 83 | // skip the context name, as this is primarily used to retrieve 84 | // values after transport. 85 | if !meth.hasContextParam || meth.contextParamName != p.names[0] { 86 | paramNames = append(paramNames, p.names...) 87 | } 88 | for _, n := range p.names { 89 | param := TemplateParam{ 90 | PublicName: publicVariableName(n), 91 | Name: n, 92 | Type: p.typ.String(), 93 | } 94 | if param.Type == "context.Context" { 95 | param.IsContext = true 96 | } 97 | 98 | params = append(params, param) 99 | } 100 | } 101 | 102 | var resultNames []string 103 | for _, r := range meth.results { 104 | resultNames = append(resultNames, r.names...) 105 | for _, n := range r.names { 106 | methodsResults = append(methodsResults, TemplateParam{ 107 | PublicName: publicVariableName(n), 108 | Name: n, 109 | Type: r.typ.String(), 110 | }) 111 | } 112 | } 113 | 114 | contextParamName := "_ctx" 115 | if meth.hasContextParam { 116 | contextParamName = meth.contextParamName 117 | } 118 | 119 | errorResultName := "err" 120 | if meth.hasErrResult { 121 | errorResultName = meth.errorResultName 122 | } 123 | 124 | lcaseName := determineLocalName(strings.ToLower(interf.name), reseveredNames) 125 | results = append(results, TemplateMethod{ 126 | TemplateCommon: TemplateCommon{ 127 | BasePackage: basePackage.path, 128 | BasePackageImport: basePackage.ImportSpec(), 129 | BasePackageName: basePackage.name, 130 | EndpointPackage: endpointPackage.path, 131 | EndpointPackageName: endpointPackage.name, 132 | EndpointPrefix: fmt.Sprintf("/%s", strings.ToLower(interf.name)), 133 | InterfaceName: interf.name, 134 | InterfaceNameLcase: privateVariableName(interf.name), 135 | }, 136 | HasContextParam: meth.hasContextParam, 137 | ContextParamName: contextParamName, 138 | HasErrorResult: meth.hasErrResult, 139 | ErrorResultName: errorResultName, 140 | HasMoreThanOneResult: meth.moreThanOneResult, 141 | MethodName: meth.name, 142 | MethodNameLcase: privateVariableName(meth.name), 143 | LocalName: lcaseName, 144 | MethodArguments: meth.methodArguments(), 145 | MethodResults: meth.methodResults(), 146 | MethodArgumentNamesStr: meth.methodArgumentNames(), 147 | MethodResultNamesStr: meth.methodResultNames(), 148 | MethodArgumentNames: paramNames, 149 | MethodResultNames: resultNames, 150 | Params: params, 151 | Results: methodsResults, 152 | }) 153 | } 154 | return results 155 | } 156 | 157 | type TemplateBase struct { 158 | TemplateCommon 159 | Imports []string 160 | ImportsWithoutTime []string 161 | ExtraImports []string 162 | Methods []TemplateMethod 163 | ExtraInterfaces []TemplateParam 164 | } 165 | 166 | func createTemplateBase(basePackage, endpointPackage *Import, i Interface, oimps []*Import) TemplateBase { 167 | // imps := filteredImports(i, oimps) 168 | imps := oimps 169 | 170 | names := make([]string, 0, len(imps)) 171 | for _, i := range imps { 172 | names = append(names, i.name) 173 | } 174 | 175 | var impSpecs []string 176 | var impSpecsWithoutTime []string 177 | var extraImpSpecs []string 178 | for _, i := range imps { 179 | if !i.isParam && i.isEmbeded { 180 | extraImpSpecs = append(extraImpSpecs, i.ImportSpec()) 181 | // skip non-params for these imports 182 | continue 183 | } 184 | 185 | if i.isParam { 186 | impSpecs = append(impSpecs, i.ImportSpec()) 187 | if i.path != "time" { 188 | impSpecsWithoutTime = append(impSpecsWithoutTime, i.ImportSpec()) 189 | } 190 | } 191 | } 192 | 193 | var extraInterfaces []TemplateParam 194 | for _, t := range i.types { 195 | var publicNamePieces = strings.Split(t.String(), ".") 196 | if len(publicNamePieces) < 1 { 197 | panic("This type is empty?!") 198 | } 199 | 200 | var publicName = publicNamePieces[len(publicNamePieces)-1] 201 | 202 | extraInterfaces = append(extraInterfaces, TemplateParam{ 203 | PublicName: publicName, 204 | Name: publicName, 205 | Type: t.String(), 206 | }) 207 | } 208 | 209 | return TemplateBase{ 210 | TemplateCommon: TemplateCommon{ 211 | BasePackage: basePackage.path, 212 | BasePackageImport: basePackage.ImportSpec(), 213 | BasePackageName: basePackage.name, 214 | EndpointPackage: endpointPackage.path, 215 | EndpointPackageName: endpointPackage.name, 216 | EndpointPrefix: fmt.Sprintf("/%s", strings.ToLower(i.name)), 217 | InterfaceName: i.name, 218 | InterfaceNameLcase: privateVariableName(i.name), 219 | }, 220 | Imports: impSpecs, 221 | ImportsWithoutTime: impSpecsWithoutTime, 222 | ExtraImports: extraImpSpecs, 223 | Methods: createTemplateMethods(basePackage, endpointPackage, i, i.methods, names), 224 | ExtraInterfaces: extraInterfaces, 225 | } 226 | } 227 | 228 | func filteredExtraImports(i Interface, imps []Import) []Import { 229 | var res []Import 230 | var tmp []string 231 | for _, imp := range imps { 232 | for _, t := range i.types { 233 | if strings.HasPrefix(t.String(), fmt.Sprintf("%s.", imp.name)) { 234 | if !sliceContains(tmp, imp.ImportSpec()) { 235 | res = append(res, imp) 236 | tmp = append(tmp, imp.ImportSpec()) 237 | } 238 | } 239 | } 240 | } 241 | return res 242 | } 243 | -------------------------------------------------------------------------------- /tmpl/endpoint.tmpl: -------------------------------------------------------------------------------- 1 | // Autogenerated code, do not change directly. 2 | // To make changes to this file, please modify the templates at 3 | // go-kit-middlewarer/tmpl/*.tmpl 4 | 5 | // Package endpoint defines various different enpoint paths for transports 6 | package endpoint 7 | 8 | import ( 9 | {{.BasePackageImport}} 10 | ) 11 | 12 | const ( 13 | {{range .Methods}}{{template "endpoint" .}} 14 | {{end}} 15 | ) 16 | 17 | // {{.InterfaceName}}Middleware defines a function that takes {{.BasePackageName}}.{{.InterfaceName}} and returns a {{.BasePackageName}}.{{.InterfaceName}} 18 | type {{.InterfaceName}}Middleware func({{.BasePackageName}}.{{.InterfaceName}}) {{.BasePackageName}}.{{.InterfaceName}} 19 | 20 | {{define "endpoint"}}// Path{{.MethodName}} represents an endpoint path for {{.MethodName}} 21 | Path{{.MethodName}} = "/{{.MethodNameLcase}}"{{end}} 22 | -------------------------------------------------------------------------------- /tmpl/logging.tmpl: -------------------------------------------------------------------------------- 1 | // Autogenerated code, do not change directly. 2 | // To make changes to this file, please modify the templates at 3 | // go-kit-middlewarer/tmpl/*.tmpl 4 | 5 | // Package logging defines a function for creating a go-kit logging {{.InterfaceName}}Middleware 6 | package logging 7 | 8 | import ( 9 | "time" 10 | 11 | "github.com/go-kit/kit/log" 12 | 13 | {{range .ImportsWithoutTime}}{{template "identity" .}} 14 | {{end}} 15 | 16 | "{{.EndpointPackage}}" 17 | {{.BasePackageImport}} 18 | ) 19 | 20 | type logging{{.InterfaceName}} struct { 21 | logger log.Logger 22 | {{.BasePackageName}}.{{.InterfaceName}} 23 | root {{.BasePackageName}}.{{.InterfaceName}} 24 | } 25 | 26 | // Middleware represents a middleware used to wrap a {{.BasePackage}}.{{.InterfaceName}} and provides logging functionality. 27 | func Middleware(logger log.Logger, root {{.BasePackageName}}.{{.InterfaceName}}) {{.EndpointPackageName}}.{{.InterfaceName}}Middleware { 28 | return func( next {{.BasePackageName}}.{{.InterfaceName}} ) {{.BasePackageName}}.{{.InterfaceName}} { 29 | return logging{{.InterfaceName}} { 30 | logger: logger, 31 | {{.InterfaceName}}: next, 32 | root: root, 33 | } 34 | } 35 | } 36 | 37 | {{range .Methods}} 38 | {{template "method" .}} 39 | {{end}} 40 | {{define "identity"}}{{.}}{{end}} 41 | {{define "method"}}// {{.MethodName}} implements {{.BasePackage}}.{{.InterfaceName}} 42 | func ({{.LocalName}} logging{{.InterfaceName}}) {{.MethodName}}({{.MethodArguments}}) ({{.MethodResults}}) { 43 | defer func(begin time.Time){ 44 | _ = {{.LocalName}}.logger.Log( 45 | "method", {{.EndpointPackageName}}.Path{{.MethodName}}, 46 | {{template "extra" .}} 47 | {{range .MethodResultNames}}{{template "logit" .}}{{end}} 48 | {{range .MethodArgumentNames}}{{template "logit" .}}{{end}} 49 | "took", time.Since(begin), 50 | ) 51 | }(time.Now()) 52 | 53 | 54 | {{if .MethodResults}} 55 | {{.MethodResultNamesStr}} = {{.LocalName}}.{{.InterfaceName}}.{{.MethodName}}({{.MethodArgumentNamesStr}}){{else}} 56 | {{.LocalName}}.{{.InterfaceName}}.{{.MethodName}}({{.MethodArgumentNamesStr}}){{end}} 57 | return 58 | }{{end}} 59 | {{define "logit"}}"{{.}}", {{.}}, 60 | {{end}} 61 | -------------------------------------------------------------------------------- /tmpl/transport-client.tmpl: -------------------------------------------------------------------------------- 1 | // Autogenerated code, do not change directly. 2 | // To make changes to this file, please modify the templates at 3 | // go-kit-middlewarer/tmpl/*.tmpl 4 | 5 | package http 6 | 7 | import ( 8 | "context" 9 | "time" 10 | 11 | "github.com/go-kit/kit/endpoint" 12 | 13 | {{range .ImportsWithoutTime}}{{.}} 14 | {{end}} 15 | {{range .ExtraImports}} 16 | {{.}}{{end}} 17 | 18 | {{.BasePackageImport}} 19 | ) 20 | 21 | var _ {{.BasePackageName}}.{{.InterfaceName}} 22 | 23 | // DefaultRequestTimeout represents an overwritable Request timeout. 24 | var DefaultRequestTimeout = time.Second 25 | 26 | type client{{.InterfaceName}} struct { 27 | {{range .ExtraInterfaces}}{{.Type}} 28 | {{end}} 29 | {{range .Methods}}{{.MethodNameLcase}}Endpoint endpoint.Endpoint 30 | {{end}} 31 | } 32 | 33 | {{range .Methods}} 34 | // {{.MethodName}} implements {{.BasePackage}}.{{.InterfaceName}} 35 | func ({{.LocalName}} client{{.InterfaceName}}) {{.MethodName}}({{.MethodArguments}}) ({{.MethodResults}}) { 36 | {{if .HasContextParam}} 37 | if _, ok := {{.ContextParamName}}.Deadline(); !ok { 38 | _tmpCtx, _ctxCancelFunc := context.WithTimeout({{.ContextParamName}}, DefaultRequestTimeout) 39 | {{.ContextParamName}} = _tmpCtx 40 | defer _ctxCancelFunc() 41 | } 42 | {{else}} 43 | {{.ContextParamName}} := context.Background() 44 | {{.ContextParamName}}, _ctxCancelFunc := context.WithTimeout({{.ContextParamName}}, DefaultRequestTimeout) 45 | defer _ctxCancelFunc() 46 | {{end}} 47 | 48 | _request := {{.MethodNameLcase}}Request{ 49 | {{range .Params}}{{if .IsContext}}{{else}}{{template "param" .}}{{end}} 50 | {{end}} 51 | } 52 | 53 | _request.embedMime = new(embedMime) 54 | 55 | _response, err := {{.LocalName}}.{{.MethodNameLcase}}Endpoint( 56 | {{.ContextParamName}}, 57 | &_request, 58 | ) 59 | 60 | if err != nil { 61 | return 62 | } 63 | 64 | var ok bool 65 | err, ok = _response.(error) 66 | if ok { 67 | // we received an error from the server, so we'll return here. 68 | // if the Method has a return type named err, this will come through 69 | // it. 70 | return 71 | } 72 | 73 | {{if .Results}} 74 | _resp := _response.(*{{.MethodNameLcase}}Response) 75 | 76 | {{range .Results}}{{.Name}} = _resp.{{.PublicName}} 77 | {{end}} 78 | {{else}} 79 | _ = _response.(*{{.MethodNameLcase}}Response) 80 | {{end}} 81 | 82 | return 83 | } 84 | {{end}} 85 | 86 | {{define "param"}}{{.PublicName}}: {{.Name}},{{end}} -------------------------------------------------------------------------------- /tmpl/transport-http-client.tmpl: -------------------------------------------------------------------------------- 1 | // Autogenerated code, do not change directly. 2 | // To make changes to this file, please modify the templates at 3 | // go-kit-middlewarer/tmpl/*.tmpl 4 | 5 | package http 6 | 7 | import ( 8 | "io" 9 | "net/http" 10 | "net/url" 11 | stdlibpath "path" 12 | "strings" 13 | 14 | {{range .ExtraImports}} 15 | {{.}}{{end}} 16 | 17 | kitendpoint "github.com/go-kit/kit/endpoint" 18 | httptransport "github.com/go-kit/kit/transport/http" 19 | kitsd "github.com/go-kit/kit/sd" 20 | 21 | "{{.EndpointPackage}}" 22 | {{.BasePackageImport}} 23 | ) 24 | 25 | // ClientLayer is a function that takes an address and path string, so you 26 | // can have extra information, then it should return a 27 | // github.com/go-kit/kit/transport/http.ClientOption to wrap around the 28 | // endpoint. 29 | type ClientLayer func( addr, path string ) kitendpoint.Middleware 30 | 31 | // clientFactory will take a path, encoding function, decoding function, and a 32 | // slice of github.com/go-kit/kit/transport/http.ClientOption(s) 33 | func clientFactory( path string, enc httptransport.EncodeRequestFunc, dec httptransport.DecodeResponseFunc, config ClientConfig ) kitsd.Factory { 34 | return func(addr string) (kitendpoint.Endpoint, io.Closer, error) { 35 | // first we need to ensure that the address given (addr) is valid. 36 | if !strings.HasPrefix(addr, "http") { 37 | addr = "http://" + addr 38 | } 39 | uri, err := url.Parse(addr) 40 | if err != nil { 41 | panic(err) 42 | } 43 | 44 | if config.PathPrefix != "" { 45 | path = stdlibpath.Join("/", config.PathPrefix, path) 46 | } 47 | 48 | p, err := url.Parse(path) 49 | if err != nil { 50 | panic(err) 51 | } 52 | 53 | uri = uri.ResolveReference(p) 54 | 55 | var options []httptransport.ClientOption 56 | if config.Client != nil { 57 | options = append(options, httptransport.SetClient(config.Client)) 58 | } 59 | 60 | options = append(options, httptransport.BufferedStream(config.BufferedStream)) 61 | options = append(options, httptransport.ClientBefore(config.RequestFuncs...)) 62 | options = append(options, httptransport.ClientAfter(config.ClientResponseFuncs...)) 63 | options = append(options, config.Options...) 64 | 65 | cli := httptransport.NewClient( 66 | config.Method, uri, enc, dec, options... 67 | ) 68 | 69 | var middlewares []kitendpoint.Middleware 70 | for _, cl := range config.ClientLayers { 71 | middlewares = append(middlewares, cl(addr, path)) 72 | } 73 | 74 | mw := kitendpoint.Chain(epID, middlewares...) 75 | mw = kitendpoint.Chain(mw, config.Middlewares...) 76 | 77 | ep := cli.Endpoint() 78 | return mw(ep), nil, nil 79 | } 80 | } 81 | 82 | // NewClient creates a new {{.InterfaceName}} that will call methods at 83 | // the given address provided by the addr string. This function takes a series 84 | // of ClientLayer(s) that will be applied to the client before the 85 | // subsequent method call. 86 | func NewClient( addr string, {{range .ExtraInterfaces}}{{.Name}} {{.Type}}, {{end}}wrappers ...ClientLayer ) {{.BasePackageName}}.{{.InterfaceName}} { 87 | return NewClientWithConfig(addr,{{range .ExtraInterfaces}}{{.Name}}, {{end}}ClientConfig{ClientLayers: wrappers}) 88 | } 89 | 90 | // NewClientWithOptions creates a new {{.InterfaceName}} that will call methods at 91 | // the given address provided by the addr string. This function takes a series 92 | // of ClientLayer(s) that will be applied to the client before the 93 | // subsequent method call. 94 | func NewClientWithOptions( addr string, {{range .ExtraInterfaces}}{{.Name}} {{.Type}}, {{end}}wrappers []ClientLayer, options []httptransport.ClientOption ) {{.BasePackageName}}.{{.InterfaceName}} { 95 | return NewClientWithConfig(addr,{{range .ExtraInterfaces}}{{.Name}}, {{end}}ClientConfig{ClientLayers: wrappers, Options: options}) 96 | } 97 | 98 | // NewClientWithConfig creates a new {{.InterfaceName}} that will call methods 99 | // at the given address provided by the addr string. This function takes a 100 | // ClientConfig that specifies underlying options that will be applied to 101 | // every Endpoint. 102 | func NewClientWithConfig( addr string,{{range .ExtraInterfaces}}{{.Name}} {{.Type}}, {{end}}config ClientConfig) {{.BasePackageName}}.{{.InterfaceName}} { 103 | if config.Method == "" { 104 | config.Method = "GET" 105 | } 106 | 107 | var ( 108 | {{range .Methods}} 109 | {{.MethodNameLcase}}Endpoint, _, _ = clientFactory( {{.EndpointPackageName}}.Path{{.MethodName}}, encode{{.MethodName}}Request, decode{{.MethodName}}Response, config)(addr){{end}} 110 | ) 111 | 112 | return &client{{.InterfaceName}} { 113 | {{range .ExtraInterfaces}}{{.PublicName}}: {{.Name}}, 114 | {{end}} 115 | {{range .Methods}} 116 | {{.MethodNameLcase}}Endpoint: {{.MethodNameLcase}}Endpoint,{{end}} 117 | } 118 | } 119 | 120 | // ClientConfig represents a set of various options that can be used to 121 | // configure as many options as one would like for a Client. The fields here 122 | // correspond to the fields and options contained within the Client from 123 | // go-kit's http transport package. 124 | // 125 | // You only need to specify what you'd like to override. In this case the 126 | // zero values are all useful. 127 | type ClientConfig struct { 128 | 129 | // Client represents a net/http.Client to use. If nil, this will end up 130 | // using net/http.DefaultClient 131 | Client *http.Client 132 | 133 | // Method specifies which request method to use. If empty, then this will 134 | // default to "GET" 135 | Method string 136 | 137 | // PathPrefix allows for you to specify an optional path prefix to use when 138 | // making the request. This is useful for a custom Mux being used on the 139 | // server. 140 | // 141 | // Please note that the PathPrefix should be preceeded with a '/'. If one 142 | // is not set, one will be added. 143 | // 144 | // At the moment, parsing the path will panic on an error, so please ensure 145 | // that the PathPrefix is url-safe. 146 | PathPrefix string 147 | 148 | // ClientLayers represents a list of ClientLayers to apply to the Client. 149 | // ClientLayers can be useful, as they are Middlewares that have access to 150 | // the Service being invoked, as well as the address being dialed. 151 | // 152 | // The ClientLayers are applied before the Middlewares. 153 | ClientLayers []ClientLayer 154 | 155 | // Middlewares are a potential list of Middlewares to wrap the generated 156 | // endpoint. 157 | Middlewares []kitendpoint.Middleware 158 | 159 | // Options are a list of potential options to apply to the generated 160 | // github.com/go-kit/kit/transport/http.Client. 161 | // 162 | // Options are the last things to be applied, so they are capable of 163 | // overwriting the specified RequestFuncs, ClientResponseFuncs, and 164 | // BufferedStream ClientConfig Options. 165 | Options []httptransport.ClientOption 166 | 167 | // RequestFuncs represetns a list of potential RequestFunc(s) to be applied to 168 | // the Client. These will be applied to the Request before the Request 169 | // leaves. 170 | RequestFuncs []httptransport.RequestFunc 171 | 172 | // ClientResponseFuncs represents a list of potential ClientResponseFunc(s). 173 | // These Response Funcs are capable of touching the Response before being 174 | // returned to the Endpoint. 175 | ClientResponseFuncs []httptransport.ClientResponseFunc 176 | 177 | // BufferedStream allows for the specification of whether to automatically 178 | // close the Body of the Response or not. 179 | BufferedStream bool 180 | } 181 | -------------------------------------------------------------------------------- /tmpl/transport-http-loadbalanced.tmpl: -------------------------------------------------------------------------------- 1 | // Autogenerated code, do not change directly. 2 | // To make changes to this file, please modify the templates at 3 | // go-kit-middlewarer/tmpl/*.tmpl 4 | 5 | package http 6 | 7 | import ( 8 | "context" 9 | 10 | {{range .ExtraImports}} 11 | {{.}}{{end}} 12 | 13 | kitsd "github.com/go-kit/kit/sd" 14 | kitloadbalancer "github.com/go-kit/kit/sd/lb" 15 | httptransport "github.com/go-kit/kit/transport/http" 16 | kitendpoint "github.com/go-kit/kit/endpoint" 17 | 18 | "{{.EndpointPackage}}" 19 | {{.BasePackageImport}} 20 | ) 21 | 22 | // LoadBalancerRetryCount refers to the number of times the Loadbalanced backed 23 | // client will attempt to get an endpoint. 24 | var LoadBalancerRetryCount = 3 25 | 26 | // endpointFromLoadBalancer is a nice helper function that will pull an endpoint 27 | // off of a load balancer and initiate the request. However, if an error is 28 | // encountered, it will handle it instead. 29 | func endpointFromLoadBalancer( lb kitloadbalancer.Balancer ) kitendpoint.Endpoint { 30 | return func(ctx context.Context, request interface{}) (interface{}, error) { 31 | var ep kitendpoint.Endpoint 32 | var err error 33 | for i := 0; i < LoadBalancerRetryCount; i++ { 34 | ep, err = lb.Endpoint() 35 | if err != nil && err != kitloadbalancer.ErrNoEndpoints { 36 | continue 37 | } 38 | break 39 | } 40 | 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | return ep(ctx, request) 46 | } 47 | } 48 | 49 | // GetLoadBalancerFunc describes a function which takes a 50 | // github.com/go-kit/kit/loadbalancer.LoadBalancer and returns a 51 | // github.com/go-kit/kit/loadbalancer.LoadBalancer 52 | type GetLoadBalancerFunc func( kitsd.Factory ) kitloadbalancer.Balancer 53 | 54 | // NewLoadBalancedClient is a function that will return a Load balanced 55 | // client based on the load balancing conversion function provided. 56 | func NewLoadBalancedClient( get GetLoadBalancerFunc, {{range .ExtraInterfaces}}{{.Name}} {{.Type}}, {{end}}wrappers ...ClientLayer ) {{.BasePackageName}}.{{.InterfaceName}} { 57 | return NewLoadBalancedClientWithConfig( get, {{range .ExtraInterfaces}}{{.Name}}, {{end}}ClientConfig{ ClientLayers: wrappers}) 58 | } 59 | 60 | // NewLoadBalancedClientWithOptions is a function that will return a Load balanced 61 | // client based on the load balancing conversion function provided. 62 | func NewLoadBalancedClientWithOptions( get GetLoadBalancerFunc, {{range .ExtraInterfaces}}{{.Name}} {{.Type}}, {{end}}wrappers []ClientLayer, options []httptransport.ClientOption ) {{.BasePackageName}}.{{.InterfaceName}} { 63 | return NewLoadBalancedClientWithConfig( get, {{range .ExtraInterfaces}}{{.Name}}, {{end}}ClientConfig{ ClientLayers: wrappers, Options: options}) 64 | } 65 | 66 | func NewLoadBalancedClientWithConfig(get GetLoadBalancerFunc, {{range .ExtraInterfaces}}{{.Name}} {{.Type}}, {{end}}config ClientConfig) {{.BasePackageName}}.{{.InterfaceName}} { 67 | if config.Method == "" { 68 | config.Method = "GET" 69 | } 70 | 71 | return &client{{.InterfaceName}} { 72 | {{range .ExtraInterfaces}}{{.PublicName}}: {{.Name}}, 73 | {{end}} 74 | {{range .Methods}}{{.MethodNameLcase}}Endpoint: endpointFromLoadBalancer(get( clientFactory({{.EndpointPackageName}}.Path{{.MethodName}}, encode{{.MethodName}}Request, decode{{.MethodName}}Response, config))), 75 | {{end}} 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /tmpl/transport-http-server.tmpl: -------------------------------------------------------------------------------- 1 | // Autogenerated code, do not change directly. 2 | // To make changes to this file, please modify the templates at 3 | // go-kit-middlewarer/tmpl/*.tmpl 4 | 5 | package http 6 | 7 | import( 8 | "net/http" 9 | 10 | ep "github.com/go-kit/kit/endpoint" 11 | httptransport "github.com/go-kit/kit/transport/http" 12 | 13 | {{.BasePackageImport}} 14 | "{{.EndpointPackage}}" 15 | ) 16 | 17 | type toEndpoint func({{.BasePackageName}}.{{.InterfaceName}}) ep.Endpoint 18 | 19 | // ServerLayer is a wrapper for {{.BasePackage}}.{{.InterfaceName}} which returns a 20 | // github.com/go-kit/kit/endpoint.Middleware. This allows you to specify 21 | // Middleware while creating HTTP Servers. 22 | type ServerLayer func( base {{.BasePackageName}}.{{.InterfaceName}}, path string ) ep.Middleware 23 | 24 | func epID( ep ep.Endpoint ) ep.Endpoint { 25 | return ep 26 | } 27 | 28 | func serverFactory( {{.InterfaceNameLcase}} {{.BasePackageName}}.{{.InterfaceName}}, config ServerConfig, path string, endp toEndpoint, dec httptransport.DecodeRequestFunc, enc httptransport.EncodeResponseFunc) *httptransport.Server { 29 | var middlewares []ep.Middleware 30 | for _, w := range config.ServerLayers { 31 | middlewares = append( middlewares, w( {{.InterfaceNameLcase}}, path )) 32 | } 33 | 34 | middlewares = append( middlewares, config.Middlewares...) 35 | 36 | var options []httptransport.ServerOption 37 | if config.ErrorEncoder != nil { 38 | options = append(options, httptransport.ServerErrorEncoder(config.ErrorEncoder)) 39 | } 40 | options = append(options, httptransport.ServerBefore(config.RequestFuncs...)) 41 | options = append(options, httptransport.ServerAfter(config.ServerReponseFuncs...)) 42 | options = append(options, config.Options...) 43 | 44 | server := httptransport.NewServer( 45 | ep.Chain(epID, middlewares...)(endp({{.InterfaceNameLcase}})), 46 | dec, 47 | enc, 48 | options... 49 | ) 50 | config.Mux.Handle(path,server) 51 | return server 52 | } 53 | 54 | // ServersForEndpoints will take the given arguments, associate all of 55 | // the proper endpoints together, and register itself as an HTTP handler for 56 | // {{.BasePackage}}.{{.InterfaceName}}. 57 | func ServersForEndpoints( {{.InterfaceNameLcase}} {{.BasePackageName}}.{{.InterfaceName}}, wrappers ...ServerLayer ) (servers map[string]*httptransport.Server) { 58 | return ServersForEndpointsWithConfig({{.InterfaceNameLcase}}, ServerConfig{ServerLayers: wrappers}) 59 | } 60 | 61 | // ServersForEndpointsWithOptions will take the given arguments, associate all of 62 | // the proper endpoints together, and register itself as an HTTP handler for 63 | // {{.BasePackage}}.{{.InterfaceName}}. 64 | func ServersForEndpointsWithOptions( {{.InterfaceNameLcase}} {{.BasePackageName}}.{{.InterfaceName}}, wrappers []ServerLayer, options []httptransport.ServerOption ) (servers map[string]*httptransport.Server) { 65 | return ServersForEndpointsWithConfig({{.InterfaceNameLcase}}, ServerConfig{ServerLayers: wrappers, Options: options}) 66 | } 67 | 68 | // ServersForEndpointsWithConfig will take the given arguments, associate 69 | // all of the endpoints togher, and register itself as an HTTP handler for 70 | // {{.BasePackage}}.{{.InterfaceName}}. 71 | // 72 | // The function uses the ServerConfig specification to be setup. Any properties 73 | // can be specified within the ServerConfig structure. 74 | func ServersForEndpointsWithConfig( {{.InterfaceNameLcase}} {{.BasePackageName}}.{{.InterfaceName}}, config ServerConfig) (servers map[string]*httptransport.Server) { 75 | if config.Mux == nil { 76 | config.Mux = http.DefaultServeMux 77 | } 78 | 79 | return map[string]*httptransport.Server{ 80 | {{range .Methods}} 81 | endpoint.Path{{.MethodName}}: serverFactory( {{.InterfaceNameLcase}}, config, {{.EndpointPackageName}}.Path{{.MethodName}}, make{{.MethodName}}Endpoint, decode{{.MethodName}}Request, encode{{.MethodName}}Response),{{end}} 82 | } 83 | } 84 | 85 | // Mux represents an interface abstration for a Mux. This is useful when 86 | // wanting to use something other than the ServeMux in the default http package. 87 | // However, due to the signature restriction on the functions, adapaters will 88 | // likely be required for other implementations. 89 | type Mux interface { 90 | // Handle registers the handler for the given pattern. 91 | // According to net/http.ServeMux If a handler already exists for pattern, 92 | // the Handle invocation panics. 93 | Handle(pattern string, handler http.Handler) 94 | 95 | // HandleFunc registers the handler function for the given pattern. 96 | HandleFunc(pattern string, handler func(http.ResponseWriter,*http.Request)) 97 | 98 | // Any Mux must also implement net/http.Handler. 99 | http.Handler 100 | } 101 | 102 | // ServerConfig represents a set of configuation options that can be passed 103 | // and overwritten when instanciating the Handlers. This allows for maximum 104 | // configuration and cutomization. If nothing is provided, then defaults will 105 | // be used. 106 | type ServerConfig struct { 107 | // Mux represents the default Mux to use. Defaults to 108 | // net/http.DefaultServeMux 109 | Mux Mux 110 | 111 | // Options represents a list of potential 112 | // github.com/go-kit/kit/transport/http.ServerOption(s). These options 113 | // allow for direct manipulation of the 114 | // github.com/go-kit/kit/transport/http.Server, if desired. 115 | // These Options will be applied after the supplied ErrorEncoder, if it is 116 | // provided. 117 | Options []httptransport.ServerOption 118 | 119 | // ServerLayers represents a list of potential ServerLayers. Since a 120 | // ServerLayer generates an Endpoint, the provided ServerLayers will be 121 | // invoked as a chain of middlewares, in the order provided, to the 122 | // generated Endpoint. 123 | ServerLayers []ServerLayer 124 | 125 | // Middlewares represetns a list of potential 126 | // github.com/go-kit/kit/endpoint.Middleware(s). These Middlewares will be 127 | // applied after any supplied ServerLayers. 128 | Middlewares []ep.Middleware 129 | 130 | // RequestFuncs represents a list of potential 131 | // github.com/go-kit/kit/transport/http.RequestFunc(s) that will be invoked 132 | // before the processing of the Endpoint. 133 | RequestFuncs []httptransport.RequestFunc 134 | 135 | // ServerResponseFuncts represetns a list of potential 136 | // github.com/go-kit/kit/transport/http.ServerResponseFunc(s) that will be 137 | // invoked before the flush of the response generated by the Endpoint. 138 | ServerReponseFuncs []httptransport.ServerResponseFunc 139 | 140 | // ErrorEncoder allows for you to overwrite the ErrorEncoder. If nothing 141 | // is specified, the Default from go-kit will be used. 142 | // 143 | // If a different ErrorEncoder is needed for different endpoints, then it 144 | // is recommended that the returned Servers be modified with go-kit's 145 | // ServerOptions, externally. 146 | ErrorEncoder httptransport.ErrorEncoder 147 | } 148 | -------------------------------------------------------------------------------- /tmpl/transport-make-endpoint.tmpl: -------------------------------------------------------------------------------- 1 | // Autogenerated code, do not change directly. 2 | // To make changes to this file, please modify the templates at 3 | // go-kit-middlewarer/tmpl/*.tmpl 4 | 5 | package http 6 | 7 | import( 8 | "context" 9 | 10 | "github.com/go-kit/kit/endpoint" 11 | 12 | {{.BasePackageImport}} 13 | ) 14 | 15 | {{define "make-endpoint"}} 16 | // make{{.MethodName}}Endpoint creates a github.com/go-kit/kit/endpoint.Endpoint for {{.BasePackage}}.{{.InterfaceName}}.{{.MethodName}}. 17 | // It will automatically wrap and unwrap the arguments and results of the method. 18 | func make{{.MethodName}}Endpoint({{.InterfaceNameLcase}} {{.BasePackageName}}.{{.InterfaceName}}) endpoint.Endpoint { 19 | return func({{.ContextParamName}} context.Context, request interface{}) (resp interface{}, {{.ErrorResultName}} error) { 20 | req := request.(*{{.MethodNameLcase}}Request) 21 | var _resp {{.MethodNameLcase}}Response 22 | _resp.embedMime = new(embedMime) 23 | 24 | {{if .Params}} 25 | {{range .Params}}{{if .IsContext}}{{else}}{{.Name}} := req.{{.PublicName}}{{end}} 26 | {{end}}{{end}} 27 | 28 | {{if .Results}} 29 | {{if .HasMoreThanOneResult}} 30 | {{.MethodResultNamesStr}} := {{.InterfaceNameLcase}}.{{.MethodName}}({{.MethodArgumentNamesStr}}) 31 | {{else if .HasErrorResult}} 32 | {{.MethodResultNamesStr}} = {{.InterfaceNameLcase}}.{{.MethodName}}({{.MethodArgumentNamesStr}}) 33 | {{else}} 34 | {{.MethodResultNamesStr}} := {{.InterfaceNameLcase}}.{{.MethodName}}({{.MethodArgumentNamesStr}}) 35 | {{end}} 36 | {{range .Results}}_resp.{{.PublicName}} = {{.Name}} 37 | {{end}} 38 | {{else}} 39 | {{.InterfaceNameLcase}}.{{.MethodName}}({{.MethodArgumentNamesStr}}) 40 | {{end}} 41 | 42 | if mime := req.GetMime(); mime != "" { 43 | _resp.SetMime( mime ) 44 | } 45 | resp = _resp 46 | 47 | return 48 | } 49 | } 50 | {{end}} 51 | {{range .Methods}}{{template "make-endpoint" .}}{{end}} 52 | -------------------------------------------------------------------------------- /tmpl/transport-request-response.tmpl: -------------------------------------------------------------------------------- 1 | // Autogenerated code, do not change directly. 2 | // To make changes to this file, please modify the templates at 3 | // go-kit-middlewarer/tmpl/*.tmpl 4 | 5 | 6 | package http 7 | 8 | import ( 9 | "context" 10 | "net/http" 11 | 12 | "github.com/ayiga/go-kit-middlewarer/encoding" 13 | 14 | {{range .Imports}}{{.}} 15 | {{end}} 16 | 17 | {{.BasePackageImport}} 18 | ) 19 | 20 | var _ {{.BasePackageName}}.{{.InterfaceName}} 21 | 22 | type embedMime struct { 23 | mime string 24 | } 25 | 26 | func (em *embedMime) GetMime() string { 27 | if em == nil || em.mime == "" { 28 | return "application/json" 29 | } 30 | 31 | return em.mime 32 | } 33 | 34 | func (em *embedMime) SetMime( mime string ) { 35 | em.mime = mime 36 | } 37 | 38 | {{define "request-response"}} 39 | // {{.MethodNameLcase}}Request defines a Request structure for the Method {{.BasePackage}}.{{.InterfaceName}}.{{.MethodName}} 40 | type {{.MethodNameLcase}}Request struct { 41 | *embedMime 42 | {{range .Params}}{{template "param" .}} 43 | {{end}} 44 | } 45 | 46 | // {{.MethodNameLcase}}Response defines a Response structure for the Method {{.BasePackage}}.{{.InterfaceName}}.{{.MethodName}} 47 | type {{.MethodNameLcase}}Response struct { 48 | *embedMime 49 | {{range .Results}}{{template "param" .}} 50 | {{end}} 51 | } 52 | 53 | // decode{{.MethodName}}Request creates a decoder for {{.BasePackage}}.{{.InterfaceName}}.{{.MethodName}} 54 | func decode{{.MethodName}}Request(ctx context.Context, r *http.Request) (interface{}, error) { 55 | req := new({{.MethodNameLcase}}Request) 56 | req.embedMime = new(embedMime) 57 | return encoding.Default().DecodeRequest(req)(ctx, r) 58 | } 59 | 60 | // decode{{.MethodName}}Response creates a decoder for {{.BasePackage}}.{{.InterfaceName}}.{{.MethodName}} 61 | func decode{{.MethodName}}Response(ctx context.Context, r *http.Response) (interface{}, error) { 62 | req := new({{.MethodNameLcase}}Response) 63 | req.embedMime = new(embedMime) 64 | return encoding.Default().DecodeResponse(req)(ctx, r) 65 | } 66 | 67 | // encode{{.MethodName}}Request creates an encoder for {{.BasePackage}}.{{.InterfaceName}}.{{.MethodName}} 68 | func encode{{.MethodName}}Request (ctx context.Context, r *http.Request, request interface{}) error { 69 | return encoding.Default().EncodeRequest()(ctx, r, request) 70 | } 71 | 72 | // encode{{.MethodName}}Response creates an encoder for {{.BasePackage}}.{{.InterfaceName}}.{{.MethodName}} 73 | func encode{{.MethodName}}Response (ctx context.Context, w http.ResponseWriter, response interface{}) error { 74 | return encoding.Default().EncodeResponse()(ctx, w, response) 75 | } 76 | 77 | {{end}} 78 | {{define "param"}} {{if .IsContext}}{{else}}{{.PublicName}} {{.Type}} `json:"{{.Name}}" xml:"{{.Name}}"`{{end}}{{end}} 79 | {{range .Methods}}{{template "request-response" .}}{{end}} 80 | -------------------------------------------------------------------------------- /type.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "go/ast" 6 | "strings" 7 | ) 8 | 9 | type Type struct { 10 | typ ast.Expr 11 | requiredImport *Import // required import to use, can be nil? 12 | pkg *Package 13 | 14 | isBasePackage bool 15 | } 16 | 17 | func createType(typ ast.Expr, pkg *Package) Type { 18 | t := Type{ 19 | typ: typ, 20 | pkg: pkg, 21 | } 22 | 23 | if !strings.Contains(t.String(), ".") && 24 | strings.ToLower(t.String()) != t.String() { // exported. 25 | t.isBasePackage = true 26 | } 27 | 28 | return t 29 | } 30 | 31 | func (t Type) TypeName() string { 32 | return "" 33 | } 34 | 35 | func (t Type) Equal(o Type) bool { 36 | return t.String() == o.String() 37 | } 38 | 39 | func (t Type) String() string { 40 | if t.isBasePackage { 41 | return resolveFieldTypes(t.typ, t.pkg.name) 42 | } 43 | return resolveFieldTypes(t.typ, "") 44 | } 45 | 46 | func resolveFieldTypes(t ast.Expr, pkgName string) string { 47 | switch t1 := t.(type) { 48 | case *ast.StructType: 49 | return "struct{}" 50 | case *ast.InterfaceType: 51 | return "interface{}" 52 | case *ast.SelectorExpr: 53 | // we have to override this crap... 54 | return fmt.Sprintf("%s.%s", t1.X, resolveFieldTypes(t1.Sel, "")) 55 | case *ast.StarExpr: 56 | return fmt.Sprintf("*%s", resolveFieldTypes(t1.X, pkgName)) 57 | case *ast.Ident: 58 | if pkgName != "" { 59 | return fmt.Sprintf("%s.%s", pkgName, t1) 60 | } 61 | return fmt.Sprintf("%s", t1) 62 | case *ast.MapType: 63 | return fmt.Sprintf("map[%s]%s", resolveFieldTypes(t1.Key, pkgName), resolveFieldTypes(t1.Value, pkgName)) 64 | case *ast.ArrayType: 65 | l := "" 66 | if t1.Len != nil { 67 | // we have an array, not a slice.. pity... 68 | l = fmt.Sprintf("%s", t1.Len) 69 | } 70 | return fmt.Sprintf("[%s]%s", l, resolveFieldTypes(t1.Elt, pkgName)) 71 | default: 72 | return fmt.Sprintf("UKNOWN: +V", t) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "go/format" 6 | "os" 7 | "path/filepath" 8 | ) 9 | 10 | func sliceContains(slice []string, entry string) bool { 11 | for _, s := range slice { 12 | if entry == s { 13 | return true 14 | } 15 | } 16 | return false 17 | } 18 | 19 | func determineLocalName(suggestedName string, currentNames []string) string { 20 | if !sliceContains(currentNames, suggestedName) { 21 | return suggestedName 22 | } 23 | 24 | f := func(prefix string) string { 25 | p := []byte(prefix) 26 | for i := 97; i < 97+26; i++ { 27 | b := append([]byte{}, p...) 28 | b = append(b, byte(i)) 29 | if !sliceContains(currentNames, string(b)) { 30 | return string(b) 31 | } 32 | } 33 | return "" 34 | } 35 | 36 | for sliceContains(currentNames, suggestedName) { 37 | suggestedName = suggestedName + "a" 38 | 39 | suggestedName = f(suggestedName) 40 | } 41 | 42 | return suggestedName 43 | } 44 | 45 | func formatBuffer(buf bytes.Buffer, filename string) []byte { 46 | src, err := format.Source(buf.Bytes()) 47 | if err != nil { 48 | log.Printf("Warning: internal Error: invalid go generated in file %s: %s", filename, err) 49 | log.Printf("Warning: compile the package to analyze the error: %s", err) 50 | return buf.Bytes() 51 | } 52 | return src 53 | } 54 | 55 | func openFile(dirname, filename string) *os.File { 56 | _, err := os.Stat(dirname) 57 | err = os.MkdirAll(dirname, 0744) 58 | if err != nil && !os.IsExist(err) { 59 | log.Fatalf("Unable to Make Directory: %s: %s", dirname, err) 60 | } 61 | fname := filepath.Join(dirname, filename) 62 | 63 | file, err := os.Create(fname) 64 | if err != nil && os.IsExist(err) { 65 | file, err = os.Open(fname) 66 | } 67 | 68 | if err != nil { 69 | log.Fatalf("Unable to open or create file %s: %s", fname, err) 70 | } 71 | return file 72 | } 73 | -------------------------------------------------------------------------------- /variable.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type Variable struct { 4 | name string 5 | typ *Type // nillable 6 | value string 7 | } 8 | 9 | func createVariable(name, value string, typ *Type) Variable { 10 | return Variable{ 11 | name: name, 12 | typ: typ, 13 | value: value, 14 | } 15 | } 16 | --------------------------------------------------------------------------------