├── .gitignore ├── .gitattributes ├── examples ├── scribe_client │ └── main.go ├── scribe_server │ └── main.go └── scribe │ └── thrift.go ├── thrift ├── util.go ├── tags_test.go ├── thrift_test.go ├── transport.go ├── tags.go ├── helpers.go ├── framed_test.go ├── server_test.go ├── protocol.go ├── util_test.go ├── framed.go ├── server.go ├── client.go ├── encoder.go ├── protocol_text.go ├── client_test.go ├── decoder.go ├── protocol_compact_test.go ├── encoder_test.go ├── protocol_binary_test.go ├── protocol_binary.go ├── thrift.go └── protocol_compact.go ├── .travis.yml ├── LICENSE ├── parser ├── types.go ├── parser.go ├── parser_test.go └── grammar.peg ├── generator ├── go_test.go ├── main.go └── go.go ├── README.md └── testfiles └── Hbase.thrift /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.peg.go binary 2 | -------------------------------------------------------------------------------- /examples/scribe_client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | 7 | "github.com/samuel/go-thrift/examples/scribe" 8 | "github.com/samuel/go-thrift/thrift" 9 | ) 10 | 11 | func main() { 12 | conn, err := net.Dial("tcp", "127.0.0.1:1463") 13 | if err != nil { 14 | panic(err) 15 | } 16 | 17 | t := thrift.NewTransport(thrift.NewFramedReadWriteCloser(conn, 0), thrift.BinaryProtocol) 18 | client := thrift.NewClient(t, false) 19 | scr := scribe.ScribeClient{Client: client} 20 | res, err := scr.Log([]*scribe.LogEntry{{"category", "message"}}) 21 | if err != nil { 22 | panic(err) 23 | } 24 | 25 | fmt.Printf("Response: %+v\n", res) 26 | } 27 | -------------------------------------------------------------------------------- /thrift/util.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2015 Samuel Stauffer. All rights reserved. 2 | // Use of this source code is governed by a 3-clause BSD 3 | // license that can be found in the LICENSE file. 4 | 5 | package thrift 6 | 7 | import ( 8 | "strings" 9 | "unicode" 10 | ) 11 | 12 | // CamelCase returns the string converted to camel case (e.g. some_name to SomeName) 13 | func CamelCase(s string) string { 14 | prev := '_' 15 | return strings.Map( 16 | func(r rune) rune { 17 | if r == '_' { 18 | prev = r 19 | return -1 20 | } 21 | if prev == '_' { 22 | prev = r 23 | return unicode.ToUpper(r) 24 | } 25 | prev = r 26 | return r 27 | }, s) 28 | } 29 | -------------------------------------------------------------------------------- /thrift/tags_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package thrift 6 | 7 | import ( 8 | "testing" 9 | ) 10 | 11 | func TestTagParsing(t *testing.T) { 12 | id, opts := parseTag("1,foobar,foo") 13 | if id != 1 { 14 | t.Fatalf("id = %d, want 1", id) 15 | } 16 | for _, tt := range []struct { 17 | opt string 18 | want bool 19 | }{ 20 | {"foobar", true}, 21 | {"foo", true}, 22 | {"bar", false}, 23 | } { 24 | if opts.Contains(tt.opt) != tt.want { 25 | t.Errorf("Contains(%q) = %v", tt.opt, !tt.want) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /thrift/thrift_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2015 Samuel Stauffer. All rights reserved. 2 | // Use of this source code is governed by a 3-clause BSD 3 | // license that can be found in the LICENSE file. 4 | 5 | package thrift 6 | 7 | import ( 8 | "reflect" 9 | "testing" 10 | ) 11 | 12 | type EncodeFieldsTestStruct struct { 13 | F1 string `json:"f1" thrift:"1"` 14 | F2 string `thrift:"2" json:"f2"` 15 | Set map[string]struct{} `thrift:"3"` 16 | } 17 | 18 | func TestEncodeFields(t *testing.T) { 19 | s := EncodeFieldsTestStruct{} 20 | m := encodeFields(reflect.TypeOf(s)) 21 | if len(m.fields) != 3 { 22 | t.Fatalf("Did not find all fields. %d fields, expected 3 fields", len(m.fields)) 23 | } 24 | if m.fields[3].fieldType != TypeSet { 25 | t.Fatalf("Type map[...]struct{} not handled as a Set") 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/scribe_server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "net/rpc" 7 | 8 | "github.com/samuel/go-thrift/examples/scribe" 9 | "github.com/samuel/go-thrift/thrift" 10 | ) 11 | 12 | // implementation 13 | 14 | type scribeServiceImplementation int 15 | 16 | func (s *scribeServiceImplementation) Log(messages []*scribe.LogEntry) (scribe.ResultCode, error) { 17 | for _, m := range messages { 18 | fmt.Printf("MSG: %+v\n", m) 19 | } 20 | return scribe.ResultCodeOk, nil 21 | } 22 | 23 | func main() { 24 | scribeService := new(scribeServiceImplementation) 25 | rpc.RegisterName("Thrift", &scribe.ScribeServer{Implementation: scribeService}) 26 | 27 | ln, err := net.Listen("tcp", ":1463") 28 | if err != nil { 29 | panic(err) 30 | } 31 | for { 32 | conn, err := ln.Accept() 33 | if err != nil { 34 | fmt.Printf("ERROR: %+v\n", err) 35 | continue 36 | } 37 | fmt.Printf("New connection %+v\n", conn) 38 | t := thrift.NewTransport(thrift.NewFramedReadWriteCloser(conn, 0), thrift.BinaryProtocol) 39 | go rpc.ServeCodec(thrift.NewServerCodec(t)) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /thrift/transport.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2015 Samuel Stauffer. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package thrift 6 | 7 | import ( 8 | "bufio" 9 | "io" 10 | ) 11 | 12 | type Transport interface { 13 | ProtocolReader 14 | ProtocolWriter 15 | io.Closer 16 | Flusher 17 | } 18 | 19 | type transport struct { 20 | ProtocolReader 21 | ProtocolWriter 22 | io.Closer 23 | f Flusher 24 | } 25 | 26 | func NewTransport(rwc io.ReadWriteCloser, p ProtocolBuilder) Transport { 27 | t := &transport{ 28 | Closer: rwc, 29 | } 30 | if _, ok := rwc.(*FramedReadWriteCloser); ok { 31 | t.ProtocolReader = p.NewProtocolReader(rwc) 32 | t.ProtocolWriter = p.NewProtocolWriter(rwc) 33 | if f, ok := rwc.(Flusher); ok { 34 | t.f = f 35 | } 36 | } else { 37 | w := bufio.NewWriter(rwc) 38 | t.ProtocolWriter = p.NewProtocolWriter(w) 39 | t.ProtocolReader = p.NewProtocolReader(bufio.NewReader(rwc)) 40 | t.f = w 41 | } 42 | return t 43 | } 44 | 45 | func (t *transport) Flush() error { 46 | if t.f != nil { 47 | return t.f.Flush() 48 | } 49 | return nil 50 | } 51 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.4.2 4 | - tip 5 | 6 | branches: 7 | only: 8 | - master 9 | 10 | script: 11 | - go build ./... 12 | - go fmt ./... 13 | - go get golang.org/x/tools/cmd/vet 14 | - go vet ./... 15 | - go test -i -race -cover ./... 16 | - go test -v -race -cover -coverprofile cover.out ./thrift 17 | - go tool cover -func=cover.out 18 | - go test -v -race -cover -coverprofile cover.out ./parser 19 | - go tool cover -func=cover.out 20 | - go test -v -race -cover -coverprofile cover.out ./generator 21 | - go tool cover -func=cover.out 22 | - go build -o gen ./generator/ 23 | - mkdir tmp 24 | # cassandra.thrift 25 | - ./gen testfiles/cassandra.thrift tmp/ 26 | - go build ./tmp/cassandra/ 27 | - go vet ./tmp/cassandra/ 28 | - rm -rf ./tmp/cassandra 29 | - ./gen -go.pointers=true testfiles/cassandra.thrift tmp/ 30 | - go build ./tmp/cassandra/ 31 | - go vet ./tmp/cassandra 32 | # Hbase.thrift 33 | - ./gen testfiles/Hbase.thrift tmp/ 34 | - go build ./tmp/hbase/ 35 | - go vet ./tmp/hbase/ 36 | - rm -rf ./tmp/hbase 37 | - ./gen -go.pointers=true testfiles/Hbase.thrift tmp/ 38 | - go build ./tmp/hbase/ 39 | - go vet ./tmp/hbase 40 | -------------------------------------------------------------------------------- /thrift/tags.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2015 Samuel Stauffer. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package thrift 6 | 7 | import ( 8 | "strconv" 9 | "strings" 10 | ) 11 | 12 | // tagOptions is the string following a comma in a struct field's "thrift" 13 | // tag, or the empty string. It does not include the leading comma. 14 | type tagOptions string 15 | 16 | // parseTag splits a struct field's thrift tag into its id and 17 | // comma-separated options. 18 | func parseTag(tag string) (int, tagOptions) { 19 | if idx := strings.Index(tag, ","); idx != -1 { 20 | id, _ := strconv.Atoi(tag[:idx]) 21 | return id, tagOptions(tag[idx+1:]) 22 | } 23 | id, _ := strconv.Atoi(tag) 24 | return id, tagOptions("") 25 | } 26 | 27 | // Contains returns whether checks that a comma-separated list of options 28 | // contains a particular substr flag. substr must be surrounded by a 29 | // string boundary or commas. 30 | func (o tagOptions) Contains(optionName string) bool { 31 | if len(o) == 0 { 32 | return false 33 | } 34 | s := string(o) 35 | for s != "" { 36 | var next string 37 | i := strings.Index(s, ",") 38 | if i >= 0 { 39 | s, next = s[:i], s[i+1:] 40 | } 41 | if s == optionName { 42 | return true 43 | } 44 | s = next 45 | } 46 | return false 47 | } 48 | -------------------------------------------------------------------------------- /thrift/helpers.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2015 Samuel Stauffer. All rights reserved. 2 | // Use of this source code is governed by a 3-clause BSD 3 | // license that can be found in the LICENSE file. 4 | 5 | package thrift 6 | 7 | // Bool is a helper routine that allocates a new bool value to store v and returns a pointer to it. 8 | func Bool(v bool) *bool { 9 | return &v 10 | } 11 | 12 | // Float32 is a helper routine that allocates a new float32 value to store v and returns a pointer to it. 13 | func Float32(v float32) *float32 { 14 | return &v 15 | } 16 | 17 | // Float64 is a helper routine that allocates a new float64 value to store v and returns a pointer to it. 18 | func Float64(v float64) *float64 { 19 | return &v 20 | } 21 | 22 | // Byte is a helper routine that allocates a new byte value to store v and returns a pointer to it. 23 | func Byte(v byte) *byte { 24 | return &v 25 | } 26 | 27 | // Int16 is a helper routine that allocates a new int16 value to store v and returns a pointer to it. 28 | func Int16(v int16) *int16 { 29 | return &v 30 | } 31 | 32 | // Int32 is a helper routine that allocates a new int32 value to store v and returns a pointer to it. 33 | func Int32(v int32) *int32 { 34 | return &v 35 | } 36 | 37 | // Int64 is a helper routine that allocates a new int64 value to store v and returns a pointer to it. 38 | func Int64(v int64) *int64 { 39 | return &v 40 | } 41 | 42 | // String is a helper routine that allocates a new string value to store v and returns a pointer to it. 43 | func String(v string) *string { 44 | return &v 45 | } 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012, Samuel Stauffer 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * Neither the name of the author nor the 13 | names of its contributors may be used to endorse or promote products 14 | derived from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /thrift/framed_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2015 Samuel Stauffer. All rights reserved. 2 | // Use of this source code is governed by a 3-clause BSD 3 | // license that can be found in the LICENSE file. 4 | 5 | package thrift 6 | 7 | import ( 8 | "bytes" 9 | "testing" 10 | ) 11 | 12 | type ClosingBuffer struct { 13 | *bytes.Buffer 14 | } 15 | 16 | func (c *ClosingBuffer) Close() error { 17 | return nil 18 | } 19 | 20 | func TestFramed(t *testing.T) { 21 | buf := &ClosingBuffer{&bytes.Buffer{}} 22 | 23 | framed := NewFramedReadWriteCloser(buf, 1024) 24 | if _, err := framed.Write([]byte{1, 2, 3, 4}); err != nil { 25 | t.Fatalf("Framed: error on Write %s", err) 26 | } 27 | if buf.Len() != 0 { 28 | t.Fatalf("Framed: wrote %d bytes before flush", buf.Len()) 29 | } 30 | if err := framed.Flush(); err != nil { 31 | t.Fatalf("Framed: error on Flush %s", err) 32 | } 33 | if buf.Len() != 8 { 34 | t.Fatalf("Framed: wrote (%d) other than 8 bytes after flush", buf.Len()) 35 | } 36 | if err := framed.Flush(); err != nil { 37 | t.Fatalf("Framed: error on Flush %s", err) 38 | } 39 | if buf.Len() != 8 { 40 | t.Fatalf("Framed: flush didn't clear write buffer") 41 | } 42 | 43 | out := buf.Bytes() 44 | expected := []byte{0, 0, 0, 4, 1, 2, 3, 4} 45 | if bytes.Compare(out, expected) != 0 { 46 | t.Fatalf("Framed: expected output %+v but got %+v", expected, out) 47 | } 48 | 49 | buf = &ClosingBuffer{bytes.NewBuffer([]byte{0, 0, 0, 2, 5, 6})} 50 | framed = NewFramedReadWriteCloser(buf, 1024) 51 | out = make([]byte, 4) 52 | n, err := framed.Read(out[:4]) 53 | if err != nil { 54 | t.Fatalf("Framed: error from Read %s", err) 55 | } 56 | if n != 2 { 57 | t.Fatalf("Framed: expected read count of 2 instead %d", n) 58 | } 59 | if out[0] != 5 || out[1] != 6 { 60 | t.Fatalf("Framed: expected {5,6} from Read instead %+v", out[:2]) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /parser/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2015 Samuel Stauffer. All rights reserved. 2 | // Use of this source code is governed by a 3-clause BSD 3 | // license that can be found in the LICENSE file. 4 | 5 | package parser 6 | 7 | import "fmt" 8 | 9 | type Type struct { 10 | Name string 11 | KeyType *Type // If map 12 | ValueType *Type // If map, list, or set 13 | } 14 | 15 | type EnumValue struct { 16 | Name string 17 | Value int 18 | } 19 | 20 | type Enum struct { 21 | Name string 22 | Values map[string]*EnumValue 23 | } 24 | 25 | type Constant struct { 26 | Name string 27 | Type *Type 28 | Value interface{} 29 | } 30 | 31 | type Field struct { 32 | ID int 33 | Name string 34 | Optional bool 35 | Type *Type 36 | Default interface{} 37 | } 38 | 39 | type Struct struct { 40 | Name string 41 | Fields []*Field 42 | } 43 | 44 | type Method struct { 45 | Comment string 46 | Name string 47 | Oneway bool 48 | ReturnType *Type 49 | Arguments []*Field 50 | Exceptions []*Field 51 | } 52 | 53 | type Service struct { 54 | Name string 55 | Extends string 56 | Methods map[string]*Method 57 | } 58 | 59 | type Thrift struct { 60 | Includes map[string]string // name -> unique identifier (absolute path generally) 61 | Typedefs map[string]*Type 62 | Namespaces map[string]string 63 | Constants map[string]*Constant 64 | Enums map[string]*Enum 65 | Structs map[string]*Struct 66 | Exceptions map[string]*Struct 67 | Services map[string]*Service 68 | } 69 | 70 | type Identifier string 71 | 72 | type KeyValue struct { 73 | Key, Value interface{} 74 | } 75 | 76 | func (t *Type) String() string { 77 | switch t.Name { 78 | case "map": 79 | return fmt.Sprintf("map<%s,%s>", t.KeyType.String(), t.ValueType.String()) 80 | case "list": 81 | return fmt.Sprintf("list<%s>", t.ValueType.String()) 82 | case "set": 83 | return fmt.Sprintf("set<%s>", t.ValueType.String()) 84 | } 85 | return t.Name 86 | } 87 | -------------------------------------------------------------------------------- /thrift/server_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2015 Samuel Stauffer. All rights reserved. 2 | // Use of this source code is governed by a 3-clause BSD 3 | // license that can be found in the LICENSE file. 4 | 5 | package thrift 6 | 7 | import ( 8 | "bytes" 9 | "net/rpc" 10 | "testing" 11 | ) 12 | 13 | // Make sure the ServerCodec returns the same method name 14 | // in the response as was in the request. 15 | func TestServerMethodName(t *testing.T) { 16 | buf := &ClosingBuffer{&bytes.Buffer{}} 17 | clientCodec := NewClientCodec(NewTransport(buf, BinaryProtocol), false) 18 | defer clientCodec.Close() 19 | serverCodec := NewServerCodec(NewTransport(buf, BinaryProtocol)) 20 | defer serverCodec.Close() 21 | req := &rpc.Request{ 22 | ServiceMethod: "some_method", 23 | Seq: 3, 24 | } 25 | empty := &struct{}{} 26 | if err := clientCodec.WriteRequest(req, empty); err != nil { 27 | t.Fatal(err) 28 | } 29 | var req2 rpc.Request 30 | if err := serverCodec.ReadRequestHeader(&req2); err != nil { 31 | t.Fatal(err) 32 | } 33 | if req.Seq != req2.Seq { 34 | t.Fatalf("Expected seq %d, got %d", req.Seq, req2.Seq) 35 | } 36 | t.Logf("Mangled method name: %s", req2.ServiceMethod) 37 | if err := serverCodec.ReadRequestBody(empty); err != nil { 38 | t.Fatal(err) 39 | } 40 | res := &rpc.Response{ 41 | ServiceMethod: req2.ServiceMethod, 42 | Seq: req2.Seq, 43 | } 44 | if err := serverCodec.WriteResponse(res, empty); err != nil { 45 | t.Fatal(err) 46 | } 47 | var res2 rpc.Response 48 | if err := clientCodec.ReadResponseHeader(&res2); err != nil { 49 | t.Fatal(err) 50 | } 51 | if res2.Seq != req.Seq { 52 | t.Fatalf("Expected seq %d, got %d", req.Seq, res2.Seq) 53 | } 54 | if res2.Error != "" { 55 | t.Fatalf("Expected error of '' instead of '%s'", res2.Error) 56 | } 57 | if res2.ServiceMethod != req.ServiceMethod { 58 | t.Fatalf("Expected ServiceMethod of '%s' instead of '%s'", req.ServiceMethod, res2.ServiceMethod) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /examples/scribe/thrift.go: -------------------------------------------------------------------------------- 1 | // This file is automatically generated. Do not modify. 2 | 3 | package scribe 4 | 5 | import ( 6 | "fmt" 7 | "strconv" 8 | ) 9 | 10 | type ResultCode int32 11 | 12 | var ( 13 | ResultCodeOk = ResultCode(0) 14 | ResultCodeTryLater = ResultCode(1) 15 | ResultCodeByName = map[string]ResultCode{ 16 | "ResultCode.OK": ResultCodeOk, 17 | "ResultCode.TRY_LATER": ResultCodeTryLater, 18 | } 19 | ResultCodeByValue = map[ResultCode]string{ 20 | ResultCodeOk: "ResultCode.OK", 21 | ResultCodeTryLater: "ResultCode.TRY_LATER", 22 | } 23 | ) 24 | 25 | func (e ResultCode) String() string { 26 | name := ResultCodeByValue[e] 27 | if name == "" { 28 | name = fmt.Sprintf("Unknown enum value ResultCode(%d)", e) 29 | } 30 | return name 31 | } 32 | 33 | func (e *ResultCode) UnmarshalJSON(b []byte) error { 34 | st := string(b) 35 | if st[0] == '"' { 36 | *e = ResultCode(ResultCodeByName[st[1:len(st)-1]]) 37 | return nil 38 | } 39 | i, err := strconv.Atoi(st) 40 | *e = ResultCode(i) 41 | return err 42 | } 43 | 44 | type LogEntry struct { 45 | Category string `thrift:"1,required" json:"category"` 46 | Message string `thrift:"2,required" json:"message"` 47 | } 48 | 49 | type RPCClient interface { 50 | Call(method string, request interface{}, response interface{}) error 51 | } 52 | 53 | type Scribe interface { 54 | Log(Messages []*LogEntry) (ResultCode, error) 55 | } 56 | 57 | type ScribeServer struct { 58 | Implementation Scribe 59 | } 60 | 61 | func (s *ScribeServer) Log(req *ScribeLogRequest, res *ScribeLogResponse) error { 62 | val, err := s.Implementation.Log(req.Messages) 63 | switch e := err.(type) { 64 | } 65 | res.Value = val 66 | return err 67 | } 68 | 69 | type ScribeLogRequest struct { 70 | Messages []*LogEntry `thrift:"1,required" json:"messages"` 71 | } 72 | 73 | type ScribeLogResponse struct { 74 | Value ResultCode `thrift:"0,required" json:"value"` 75 | } 76 | 77 | type ScribeClient struct { 78 | Client RPCClient 79 | } 80 | 81 | func (s *ScribeClient) Log(Messages []*LogEntry) (ResultCode, error) { 82 | req := &ScribeLogRequest{ 83 | Messages: Messages, 84 | } 85 | res := &ScribeLogResponse{} 86 | err := s.Client.Call("Log", req, res) 87 | return res.Value, err 88 | } 89 | -------------------------------------------------------------------------------- /parser/parser.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2015 Samuel Stauffer. All rights reserved. 2 | // Use of this source code is governed by a 3-clause BSD 3 | // license that can be found in the LICENSE file. 4 | 5 | package parser 6 | 7 | //go:generate pigeon -o grammar.peg.go ./grammar.peg 8 | //go:generate goimports -w ./grammar.peg.go 9 | 10 | import ( 11 | "io" 12 | "io/ioutil" 13 | "os" 14 | "path/filepath" 15 | ) 16 | 17 | type Filesystem interface { 18 | Open(filename string) (io.ReadCloser, error) 19 | Abs(path string) (string, error) 20 | } 21 | 22 | type Parser struct { 23 | Filesystem Filesystem // For handling includes. Can be set to nil to fall back to os package. 24 | } 25 | 26 | func (p *Parser) Parse(r io.Reader, opts ...Option) (*Thrift, error) { 27 | b, err := ioutil.ReadAll(r) 28 | if err != nil { 29 | return nil, err 30 | } 31 | t, err := Parse("", b, opts...) 32 | if err != nil { 33 | return nil, err 34 | } 35 | return t.(*Thrift), nil 36 | } 37 | 38 | func (p *Parser) ParseFile(filename string) (map[string]*Thrift, string, error) { 39 | files := make(map[string]*Thrift) 40 | 41 | absPath, err := p.abs(filename) 42 | if err != nil { 43 | return nil, "", err 44 | } 45 | basePath := filepath.Dir(absPath) 46 | 47 | path := absPath 48 | for path != "" { 49 | rd, err := p.open(path) 50 | if err != nil { 51 | return nil, "", err 52 | } 53 | thrift, err := p.Parse(rd) 54 | if err != nil { 55 | return nil, "", err 56 | } 57 | files[path] = thrift 58 | 59 | for incName, incPath := range thrift.Includes { 60 | p, err := p.abs(filepath.Join(basePath, incPath)) 61 | if err != nil { 62 | return nil, "", err 63 | } 64 | thrift.Includes[incName] = p 65 | } 66 | 67 | // Find path for next unparsed include 68 | path = "" 69 | for _, th := range files { 70 | for _, incPath := range th.Includes { 71 | if files[incPath] == nil { 72 | path = incPath 73 | break 74 | } 75 | } 76 | } 77 | } 78 | 79 | return files, absPath, nil 80 | } 81 | 82 | func (p *Parser) open(path string) (io.ReadCloser, error) { 83 | if p.Filesystem == nil { 84 | return os.Open(path) 85 | } 86 | return p.Filesystem.Open(path) 87 | } 88 | 89 | func (p *Parser) abs(path string) (string, error) { 90 | if p.Filesystem == nil { 91 | absPath, err := filepath.Abs(path) 92 | if err != nil { 93 | return "", err 94 | } 95 | return filepath.Clean(absPath), nil 96 | } 97 | return p.Filesystem.Abs(path) 98 | } 99 | -------------------------------------------------------------------------------- /generator/go_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2015 Samuel Stauffer. All rights reserved. 2 | // Use of this source code is governed by a 3-clause BSD 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | // import ( 8 | // "bytes" 9 | // "github.com/samuel/go-thrift/parser" 10 | // "io" 11 | // "regexp" 12 | // "testing" 13 | // ) 14 | 15 | // // Regular expressions 16 | // const ( 17 | // // http://golang.org/ref/spec#identifier 18 | // GO_IDENTIFIER = "[\\pL_][\\pL\\pN_]*" 19 | // ) 20 | 21 | // // Thrift constants 22 | // const ( 23 | // THRIFT_SIMPLE = `struct UserProfile { 24 | // 1: i32 uid, 25 | // 2: string name, 26 | // 3: string blurb 27 | // }` 28 | // ) 29 | 30 | // func GenerateThrift(name string, in io.Reader) (out string, err error) { 31 | // var ( 32 | // p *parser.Parser 33 | // th *parser.Thrift 34 | // g *GoGenerator 35 | // b *bytes.Buffer 36 | // ) 37 | // if th, err = p.Parse(in); err != nil { 38 | // return 39 | // } 40 | // g = &GoGenerator{ThriftFiles: th} 41 | // b = new(bytes.Buffer) 42 | // if err = g.Generate(name, b); err != nil { 43 | // return 44 | // } 45 | // out = b.String() 46 | // return 47 | // } 48 | 49 | // func Includes(pattern string, in string) bool { 50 | // matched, err := regexp.MatchString(pattern, in) 51 | // return matched == true && err == nil 52 | // } 53 | 54 | // // Generated package names should be valid identifiers. 55 | // // Per: http://golang.org/ref/spec#Package_clause 56 | // func TestGeneratesValidPackageNames(t *testing.T) { 57 | // var ( 58 | // in *bytes.Buffer 59 | // out string 60 | // err error 61 | // tests map[string]string 62 | // is_err bool 63 | // ) 64 | // in = bytes.NewBufferString(THRIFT_SIMPLE) 65 | // tests = map[string]string{ 66 | // "foo-bar": "foo_bar", 67 | // "_foo": "_foo", 68 | // "fooαβ": "fooαβ", 69 | // "0foo": "_0foo", 70 | // } 71 | // for test, expected := range tests { 72 | // if out, err = GenerateThrift(test, in); err != nil { 73 | // t.Fatalf("Could not generate Thrift: %v", err) 74 | // } 75 | // if !Includes("package "+GO_IDENTIFIER+"\n", out) { 76 | // t.Errorf("Couldn't find valid package for test %v", test) 77 | // is_err = true 78 | // } 79 | // if !Includes("package "+expected+"\n", out) { 80 | // t.Errorf("Couldn't find expected package '%v' for test %v", expected, test) 81 | // is_err = true 82 | // } 83 | // if is_err { 84 | // t.Logf("Problem with generated Thrift:\n%v\n", out) 85 | // is_err = false 86 | // } 87 | // } 88 | // } 89 | -------------------------------------------------------------------------------- /generator/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2015 Samuel Stauffer. All rights reserved. 2 | // Use of this source code is governed by a 3-clause BSD 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "flag" 9 | "fmt" 10 | "os" 11 | "reflect" 12 | "regexp" 13 | "sort" 14 | "strings" 15 | 16 | "github.com/samuel/go-thrift/parser" 17 | "github.com/samuel/go-thrift/thrift" 18 | ) 19 | 20 | func camelCase(st string) string { 21 | if strings.ToUpper(st) == st { 22 | st = strings.ToLower(st) 23 | } 24 | return thrift.CamelCase(st) 25 | } 26 | 27 | func lowerCamelCase(st string) string { 28 | if len(st) <= 1 { 29 | return strings.ToLower(st) 30 | } 31 | st = thrift.CamelCase(st) 32 | return strings.ToLower(st[:1]) + st[1:] 33 | } 34 | 35 | // Converts a string to a valid Golang identifier, as defined in 36 | // http://golang.org/ref/spec#identifier 37 | // by converting invalid characters to the value of replace. 38 | // If the first character is a Unicode digit, then replace is 39 | // prepended to the string. 40 | func validIdentifier(st string, replace string) string { 41 | var ( 42 | invalid_rune = regexp.MustCompile("[^\\pL\\pN_]") 43 | invalid_start = regexp.MustCompile("^\\pN") 44 | out string 45 | ) 46 | out = invalid_rune.ReplaceAllString(st, "_") 47 | if invalid_start.MatchString(out) { 48 | out = fmt.Sprintf("%v%v", replace, out) 49 | } 50 | return out 51 | } 52 | 53 | // Given a map with string keys, return a sorted list of keys. 54 | // If m is not a map or doesn't have string keys then return nil. 55 | func sortedKeys(m interface{}) []string { 56 | value := reflect.ValueOf(m) 57 | if value.Kind() != reflect.Map || value.Type().Key().Kind() != reflect.String { 58 | return nil 59 | } 60 | 61 | valueKeys := value.MapKeys() 62 | keys := make([]string, len(valueKeys)) 63 | for i, k := range valueKeys { 64 | keys[i] = k.String() 65 | } 66 | sort.Strings(keys) 67 | return keys 68 | } 69 | 70 | func main() { 71 | flag.Parse() 72 | 73 | if flag.NArg() < 2 { 74 | fmt.Fprintf(os.Stderr, "Usage of %s: [options] inputfile outputpath\n", os.Args[0]) 75 | flag.PrintDefaults() 76 | os.Exit(1) 77 | } 78 | 79 | filename := flag.Arg(0) 80 | outpath := flag.Arg(1) 81 | 82 | p := &parser.Parser{} 83 | parsedThrift, _, err := p.ParseFile(filename) 84 | if err != nil { 85 | fmt.Fprintf(os.Stderr, "%s\n", err.Error()) 86 | os.Exit(2) 87 | } 88 | 89 | generator := &GoGenerator{ 90 | ThriftFiles: parsedThrift, 91 | Format: true, 92 | } 93 | err = generator.Generate(outpath) 94 | if err != nil { 95 | fmt.Fprintf(os.Stderr, "%s\n", err.Error()) 96 | os.Exit(2) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /thrift/protocol.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2015 Samuel Stauffer. All rights reserved. 2 | // Use of this source code is governed by a 3-clause BSD 3 | // license that can be found in the LICENSE file. 4 | 5 | package thrift 6 | 7 | import ( 8 | "fmt" 9 | "io" 10 | ) 11 | 12 | type ProtocolError struct { 13 | Protocol string 14 | Message string 15 | } 16 | 17 | func (e ProtocolError) Error() string { 18 | return fmt.Sprintf("thrift: [%s] %s", e.Protocol, e.Message) 19 | } 20 | 21 | type ProtocolBuilder interface { 22 | NewProtocolReader(io.Reader) ProtocolReader 23 | NewProtocolWriter(io.Writer) ProtocolWriter 24 | } 25 | 26 | type protocolBuilder struct { 27 | reader func(io.Reader) ProtocolReader 28 | writer func(io.Writer) ProtocolWriter 29 | } 30 | 31 | func NewProtocolBuilder(r func(io.Reader) ProtocolReader, w func(io.Writer) ProtocolWriter) ProtocolBuilder { 32 | return &protocolBuilder{ 33 | reader: r, 34 | writer: w, 35 | } 36 | } 37 | 38 | func (p *protocolBuilder) NewProtocolReader(r io.Reader) ProtocolReader { 39 | return p.reader(r) 40 | } 41 | 42 | func (p *protocolBuilder) NewProtocolWriter(w io.Writer) ProtocolWriter { 43 | return p.writer(w) 44 | } 45 | 46 | type ProtocolReadWriter interface { 47 | ProtocolReader 48 | ProtocolWriter 49 | } 50 | 51 | type ProtocolWriter interface { 52 | WriteMessageBegin(name string, messageType byte, seqid int32) error 53 | WriteMessageEnd() error 54 | WriteStructBegin(name string) error 55 | WriteStructEnd() error 56 | WriteFieldBegin(name string, fieldType byte, id int16) error 57 | WriteFieldEnd() error 58 | WriteFieldStop() error 59 | WriteMapBegin(keyType byte, valueType byte, size int) error 60 | WriteMapEnd() error 61 | WriteListBegin(elementType byte, size int) error 62 | WriteListEnd() error 63 | WriteSetBegin(elementType byte, size int) error 64 | WriteSetEnd() error 65 | WriteBool(value bool) error 66 | WriteByte(value byte) error 67 | WriteI16(value int16) error 68 | WriteI32(value int32) error 69 | WriteI64(value int64) error 70 | WriteDouble(value float64) error 71 | WriteString(value string) error 72 | WriteBytes(value []byte) error 73 | } 74 | 75 | type ProtocolReader interface { 76 | ReadMessageBegin() (name string, messageType byte, seqid int32, err error) 77 | ReadMessageEnd() error 78 | ReadStructBegin() error 79 | ReadStructEnd() error 80 | ReadFieldBegin() (fieldType byte, id int16, err error) 81 | ReadFieldEnd() error 82 | ReadMapBegin() (keyType byte, valueType byte, size int, err error) 83 | ReadMapEnd() error 84 | ReadListBegin() (elementType byte, size int, err error) 85 | ReadListEnd() error 86 | ReadSetBegin() (elementType byte, size int, err error) 87 | ReadSetEnd() error 88 | ReadBool() (bool, error) 89 | ReadByte() (byte, error) 90 | ReadI16() (int16, error) 91 | ReadI32() (int32, error) 92 | ReadI64() (int64, error) 93 | ReadDouble() (float64, error) 94 | ReadString() (string, error) 95 | ReadBytes() ([]byte, error) 96 | } 97 | -------------------------------------------------------------------------------- /thrift/util_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2015 Samuel Stauffer. All rights reserved. 2 | // Use of this source code is governed by a 3-clause BSD 3 | // license that can be found in the LICENSE file. 4 | 5 | package thrift 6 | 7 | import ( 8 | "bytes" 9 | "testing" 10 | ) 11 | 12 | type loopingReader struct { 13 | bytes []byte 14 | offset int 15 | } 16 | 17 | func (r *loopingReader) Write(b []byte) (int, error) { 18 | r.bytes = append(r.bytes, b...) 19 | return len(b), nil 20 | } 21 | 22 | func (r *loopingReader) Read(b []byte) (int, error) { 23 | n := len(r.bytes) 24 | end := r.offset + len(b) 25 | if end > n { 26 | oldOffset := r.offset 27 | r.offset = 0 28 | copy(b, r.bytes[oldOffset:]) 29 | return n - oldOffset, nil 30 | } 31 | copy(b, r.bytes[r.offset:end]) 32 | if end == n { 33 | r.offset = 0 34 | } else { 35 | r.offset = end 36 | } 37 | return len(b), nil 38 | } 39 | 40 | type nullWriter int 41 | 42 | func (w nullWriter) Write(b []byte) (int, error) { 43 | return len(b), nil 44 | } 45 | 46 | func TestCamelCase(t *testing.T) { 47 | cases := map[string]string{ 48 | "test": "Test", 49 | "Foo": "Foo", 50 | "foo_bar": "FooBar", 51 | "FooBar": "FooBar", 52 | "test__ing": "TestIng", 53 | "three_part_word": "ThreePartWord", 54 | "FOOBAR": "FOOBAR", 55 | "TESTing": "TESTing", 56 | } 57 | for k, v := range cases { 58 | if CamelCase(k) != v { 59 | t.Fatalf("%s did not properly CamelCase: %s", k, CamelCase(k)) 60 | } 61 | } 62 | } 63 | 64 | func BenchmarkCamelCase(b *testing.B) { 65 | for i := 0; i < b.N; i++ { 66 | CamelCase("foo_bar") 67 | } 68 | } 69 | 70 | func TestLoopingReader(t *testing.T) { 71 | b := make([]byte, 16) 72 | r := &loopingReader{[]byte{}, 0} 73 | if n, _ := r.Read(b); n != 0 { 74 | t.Fatalf("Empty loopingReader should return 0 bytes on Read instead of %d", n) 75 | } 76 | r.Write([]byte{1, 2}) 77 | if n, _ := r.Read(b); n != 2 { 78 | t.Fatalf("loopingReader should return all bytes") 79 | } else if bytes.Compare(b[:2], []byte{1, 2}) != 0 { 80 | t.Fatalf("loopingReader output didn't match for full read") 81 | } 82 | b[0] = 0 83 | b[1] = 0 84 | if n, _ := r.Read(b[:1]); n != 1 { 85 | t.Fatalf("loopingReader should return 1 byte for non-full read instead of %d", n) 86 | } else if bytes.Compare(b[:2], []byte{1, 0}) != 0 { 87 | t.Fatalf("loopingReader output didn't match for non-full read") 88 | } 89 | if n, _ := r.Read(b[:1]); n != 1 { 90 | t.Fatalf("loopingReader should return 1 byte for non-full read2 instead of %d", n) 91 | } else if bytes.Compare(b[:2], []byte{2, 0}) != 0 { 92 | t.Fatalf("loopingReader output didn't match for non-full read2, returned %+v instead pf %+v", b[:2], []byte{2, 0}) 93 | } 94 | if n, _ := r.Read(b[:1]); n != 1 { 95 | t.Fatalf("loopingReader should return 1 byte for non-full read3 instead of %d", n) 96 | } else if bytes.Compare(b[:2], []byte{1, 0}) != 0 { 97 | t.Fatalf("loopingReader output didn't match for non-full read3") 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Thrift Package for Go 2 | ===================== 3 | 4 | [![Build Status](https://travis-ci.org/samuel/go-thrift.png)](https://travis-ci.org/samuel/go-thrift) 5 | 6 | API Documentation: 7 | 8 | License 9 | ------- 10 | 11 | 3-clause BSD. See LICENSE file. 12 | 13 | Overview 14 | -------- 15 | 16 | Thrift is an IDL that can be used to generate RPC client and server 17 | bindings for a variety of languages. This package includes client and server 18 | codecs, serialization, and code generation for Go. It tries to be a more 19 | natural mapping to the language compared to other implementations. For instance, 20 | Go already has the idea of a thrift transport in the ReadWriteCloser interfaces. 21 | 22 | Types 23 | ----- 24 | 25 | Most types map directly to the native Go types, but there are some 26 | quirks and limitations. 27 | 28 | * Go supports a more limited set of types for map keys than Thrift 29 | * To use a set define the field as []type and provide a tag of "set": 30 | 31 | StringSet []string `thrift:"1,set"` 32 | 33 | * []byte get encoded/decoded as a string because the Thrift binary type 34 | is the same as string on the wire. 35 | 36 | RPC 37 | --- 38 | 39 | The standard Go net/rpc package is used to provide RPC. Although, one 40 | incompatibility is the net/rpc's use of ServiceName.Method for naming 41 | RPC methods. To get around this the Thrift ServerCodec prefixes method 42 | names with "Thrift". 43 | 44 | ### Transport 45 | 46 | There are no specific transport "classes" as there are in most Thrift 47 | libraries. Instead, the standard `io.ReadWriteCloser` is used as the 48 | interface. If the value also implements the thrift.Flusher interface 49 | then `Flush() error` is called after `protocol.WriteMessageEnd`. 50 | 51 | _Framed transport_ is supported by wrapping a value implementing 52 | `io.ReadWriteCloser` with `thrift.NewFramedReadWriteCloser(value)` 53 | 54 | ### One-way requests 55 | 56 | #### Client 57 | 58 | One-way request support needs to be enabled on the RPC codec explicitly. 59 | The reason they're not allowed by default is because the Go RPC package 60 | doesn't actually support one-way requests. To get around this requires 61 | a rather janky hack of using channels to track pending requests in the 62 | codec and faking responses. 63 | 64 | #### Server 65 | 66 | One-way requests aren't yet implemented on the server side. 67 | 68 | Parser & Code Generator 69 | ----------------------- 70 | 71 | The "parser" subdirectory contains a Thrift IDL parser, and "generator" 72 | contains a Go code generator. It could be extended to include other 73 | languages. 74 | 75 | How to use the generator: 76 | 77 | $ go install github.com/samuel/go-thrift/generator 78 | 79 | $ generator --help 80 | Usage of parsimony: [options] inputfile outputpath 81 | -go.binarystring=false: Always use string for binary instead of []byte 82 | -go.json.enumnum=false: For JSON marshal enums by number instead of name 83 | -go.pointers=false: Make all fields pointers 84 | 85 | $ generator cassandra.thrift $GOPATH/src/ 86 | 87 | TODO 88 | ---- 89 | 90 | * default values 91 | * oneway requests on the server 92 | -------------------------------------------------------------------------------- /thrift/framed.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2015 Samuel Stauffer. All rights reserved. 2 | // Use of this source code is governed by a 3-clause BSD 3 | // license that can be found in the LICENSE file. 4 | 5 | package thrift 6 | 7 | import ( 8 | "bytes" 9 | "encoding/binary" 10 | "fmt" 11 | "io" 12 | ) 13 | 14 | const ( 15 | // DefaultMaxFrameSize is the default max size for frames when using the FramedReadWriteCloser 16 | DefaultMaxFrameSize = 1024 * 1024 17 | ) 18 | 19 | type ErrFrameTooBig struct { 20 | Size, MaxSize int64 21 | } 22 | 23 | func (e ErrFrameTooBig) Error() string { 24 | return fmt.Sprintf("thrift: frame size while reading over allowed size (%d > %d)", e.Size, e.MaxSize) 25 | } 26 | 27 | type Flusher interface { 28 | Flush() error 29 | } 30 | 31 | type FramedReadWriteCloser struct { 32 | wrapped io.ReadWriteCloser 33 | limitedReader *io.LimitedReader 34 | maxFrameSize int64 35 | rtmp []byte 36 | wtmp []byte 37 | rbuf *bytes.Buffer 38 | wbuf *bytes.Buffer 39 | } 40 | 41 | func NewFramedReadWriteCloser(wrapped io.ReadWriteCloser, maxFrameSize int) *FramedReadWriteCloser { 42 | if maxFrameSize == 0 { 43 | maxFrameSize = DefaultMaxFrameSize 44 | } 45 | return &FramedReadWriteCloser{ 46 | wrapped: wrapped, 47 | limitedReader: &io.LimitedReader{R: wrapped, N: 0}, 48 | maxFrameSize: int64(maxFrameSize), 49 | rtmp: make([]byte, 4), 50 | wtmp: make([]byte, 4), 51 | rbuf: &bytes.Buffer{}, 52 | wbuf: &bytes.Buffer{}, 53 | } 54 | } 55 | 56 | func (f *FramedReadWriteCloser) Read(p []byte) (int, error) { 57 | if err := f.fillBuffer(); err != nil { 58 | return 0, err 59 | } 60 | return f.rbuf.Read(p) 61 | } 62 | 63 | func (f *FramedReadWriteCloser) ReadByte() (byte, error) { 64 | if err := f.fillBuffer(); err != nil { 65 | return 0, err 66 | } 67 | return f.rbuf.ReadByte() 68 | } 69 | 70 | func (f *FramedReadWriteCloser) fillBuffer() error { 71 | if f.rbuf.Len() > 0 { 72 | return nil 73 | } 74 | 75 | f.rbuf.Reset() 76 | if _, err := io.ReadFull(f.wrapped, f.rtmp); err != nil { 77 | return err 78 | } 79 | frameSize := int64(binary.BigEndian.Uint32(f.rtmp)) 80 | if frameSize > f.maxFrameSize { 81 | return ErrFrameTooBig{frameSize, f.maxFrameSize} 82 | } 83 | f.limitedReader.N = frameSize 84 | written, err := io.Copy(f.rbuf, f.limitedReader) 85 | if err != nil { 86 | return err 87 | } 88 | if written < frameSize { 89 | return io.EOF 90 | } 91 | return nil 92 | } 93 | 94 | func (f *FramedReadWriteCloser) Write(p []byte) (int, error) { 95 | n, err := f.wbuf.Write(p) 96 | if err != nil { 97 | return n, err 98 | } 99 | if ln := int64(f.wbuf.Len()); ln > f.maxFrameSize { 100 | return n, &ErrFrameTooBig{ln, f.maxFrameSize} 101 | } 102 | return n, nil 103 | } 104 | 105 | func (f *FramedReadWriteCloser) Close() error { 106 | return f.wrapped.Close() 107 | } 108 | 109 | func (f *FramedReadWriteCloser) Flush() error { 110 | frameSize := uint32(f.wbuf.Len()) 111 | if frameSize > 0 { 112 | binary.BigEndian.PutUint32(f.wtmp, frameSize) 113 | if _, err := f.wrapped.Write(f.wtmp); err != nil { 114 | return err 115 | } 116 | _, err := io.Copy(f.wrapped, f.wbuf) 117 | f.wbuf.Reset() 118 | return err 119 | } 120 | return nil 121 | } 122 | -------------------------------------------------------------------------------- /thrift/server.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2015 Samuel Stauffer. All rights reserved. 2 | // Use of this source code is governed by a 3-clause BSD 3 | // license that can be found in the LICENSE file. 4 | 5 | package thrift 6 | 7 | import ( 8 | "errors" 9 | "io" 10 | "net/rpc" 11 | "strings" 12 | "sync" 13 | ) 14 | 15 | type serverCodec struct { 16 | conn Transport 17 | nameCache map[string]string // incoming name -> registered name 18 | methodName map[uint64]string // sequence ID -> method name 19 | mu sync.Mutex 20 | } 21 | 22 | // ServeConn runs the Thrift RPC server on a single connection. ServeConn blocks, 23 | // serving the connection until the client hangs up. The caller typically invokes 24 | // ServeConn in a go statement. 25 | func ServeConn(conn Transport) { 26 | rpc.ServeCodec(NewServerCodec(conn)) 27 | } 28 | 29 | // NewServerCodec returns a new rpc.ServerCodec using Thrift RPC on conn using the specified protocol. 30 | func NewServerCodec(conn Transport) rpc.ServerCodec { 31 | return &serverCodec{ 32 | conn: conn, 33 | nameCache: make(map[string]string, 8), 34 | methodName: make(map[uint64]string, 8), 35 | } 36 | } 37 | 38 | func (c *serverCodec) ReadRequestHeader(request *rpc.Request) error { 39 | name, messageType, seq, err := c.conn.ReadMessageBegin() 40 | if err != nil { 41 | return err 42 | } 43 | if messageType != MessageTypeCall { // Currently don't support one way 44 | return errors.New("thrift: expected Call message type") 45 | } 46 | 47 | // TODO: should use a limited size cache for the nameCache to avoid a possible 48 | // memory overflow from nefarious or broken clients 49 | newName := c.nameCache[name] 50 | if newName == "" { 51 | newName = CamelCase(name) 52 | if !strings.ContainsRune(newName, '.') { 53 | newName = "Thrift." + newName 54 | } 55 | c.nameCache[name] = newName 56 | } 57 | 58 | c.mu.Lock() 59 | c.methodName[uint64(seq)] = name 60 | c.mu.Unlock() 61 | 62 | request.ServiceMethod = newName 63 | request.Seq = uint64(seq) 64 | 65 | return nil 66 | } 67 | 68 | func (c *serverCodec) ReadRequestBody(thriftStruct interface{}) error { 69 | if thriftStruct == nil { 70 | if err := SkipValue(c.conn, TypeStruct); err != nil { 71 | return err 72 | } 73 | } else { 74 | if err := DecodeStruct(c.conn, thriftStruct); err != nil { 75 | return err 76 | } 77 | } 78 | return c.conn.ReadMessageEnd() 79 | } 80 | 81 | func (c *serverCodec) WriteResponse(response *rpc.Response, thriftStruct interface{}) error { 82 | c.mu.Lock() 83 | methodName := c.methodName[response.Seq] 84 | delete(c.methodName, response.Seq) 85 | c.mu.Unlock() 86 | response.ServiceMethod = methodName 87 | 88 | mtype := byte(MessageTypeReply) 89 | if response.Error != "" { 90 | mtype = MessageTypeException 91 | etype := int32(ExceptionInternalError) 92 | if strings.HasPrefix(response.Error, "rpc: can't find") { 93 | etype = ExceptionUnknownMethod 94 | } 95 | thriftStruct = &ApplicationException{response.Error, etype} 96 | } 97 | if err := c.conn.WriteMessageBegin(response.ServiceMethod, mtype, int32(response.Seq)); err != nil { 98 | return err 99 | } 100 | if err := EncodeStruct(c.conn, thriftStruct); err != nil { 101 | return err 102 | } 103 | if err := c.conn.WriteMessageEnd(); err != nil { 104 | return err 105 | } 106 | return c.conn.Flush() 107 | } 108 | 109 | func (c *serverCodec) Close() error { 110 | if cl, ok := c.conn.(io.Closer); ok { 111 | return cl.Close() 112 | } 113 | return nil 114 | } 115 | -------------------------------------------------------------------------------- /thrift/client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2015 Samuel Stauffer. All rights reserved. 2 | // Use of this source code is governed by a 3-clause BSD 3 | // license that can be found in the LICENSE file. 4 | 5 | package thrift 6 | 7 | import ( 8 | "errors" 9 | "io" 10 | "net" 11 | "net/rpc" 12 | ) 13 | 14 | // Implements rpc.ClientCodec 15 | type clientCodec struct { 16 | conn Transport 17 | onewayRequests chan pendingRequest 18 | twowayRequests chan pendingRequest 19 | enableOneway bool 20 | } 21 | 22 | type pendingRequest struct { 23 | method string 24 | seq uint64 25 | } 26 | 27 | type oneway interface { 28 | Oneway() bool 29 | } 30 | 31 | var ( 32 | // ErrTooManyPendingRequests is the error when there's too many requests that have been 33 | // sent that have not yet received responses. 34 | ErrTooManyPendingRequests = errors.New("thrift.client: too many pending requests") 35 | // ErrOnewayNotEnabled is the error when trying to make a one-way RPC call but the 36 | // client was not created with one-way support enabled. 37 | ErrOnewayNotEnabled = errors.New("thrift.client: one way support not enabled on codec") 38 | ) 39 | 40 | const maxPendingRequests = 1000 41 | 42 | // Dial connects to a Thrift RPC server at the specified network address using the specified protocol. 43 | func Dial(network, address string, framed bool, protocol ProtocolBuilder, supportOnewayRequests bool) (*rpc.Client, error) { 44 | conn, err := net.Dial(network, address) 45 | if err != nil { 46 | return nil, err 47 | } 48 | var c io.ReadWriteCloser = conn 49 | if framed { 50 | c = NewFramedReadWriteCloser(conn, DefaultMaxFrameSize) 51 | } 52 | codec := &clientCodec{ 53 | conn: NewTransport(c, protocol), 54 | } 55 | if supportOnewayRequests { 56 | codec.enableOneway = true 57 | codec.onewayRequests = make(chan pendingRequest, maxPendingRequests) 58 | codec.twowayRequests = make(chan pendingRequest, maxPendingRequests) 59 | } 60 | return rpc.NewClientWithCodec(codec), nil 61 | } 62 | 63 | // NewClient returns a new rpc.Client to handle requests to the set of 64 | // services at the other end of the connection. 65 | func NewClient(conn Transport, supportOnewayRequests bool) *rpc.Client { 66 | return rpc.NewClientWithCodec(NewClientCodec(conn, supportOnewayRequests)) 67 | } 68 | 69 | // NewClientCodec returns a new rpc.ClientCodec using Thrift RPC on conn using the specified protocol. 70 | func NewClientCodec(conn Transport, supportOnewayRequests bool) rpc.ClientCodec { 71 | c := &clientCodec{ 72 | conn: conn, 73 | } 74 | if supportOnewayRequests { 75 | c.enableOneway = true 76 | c.onewayRequests = make(chan pendingRequest, maxPendingRequests) 77 | c.twowayRequests = make(chan pendingRequest, maxPendingRequests) 78 | } 79 | return c 80 | } 81 | 82 | func (c *clientCodec) WriteRequest(request *rpc.Request, thriftStruct interface{}) error { 83 | if err := c.conn.WriteMessageBegin(request.ServiceMethod, MessageTypeCall, int32(request.Seq)); err != nil { 84 | return err 85 | } 86 | if err := EncodeStruct(c.conn, thriftStruct); err != nil { 87 | return err 88 | } 89 | if err := c.conn.WriteMessageEnd(); err != nil { 90 | return err 91 | } 92 | if err := c.conn.Flush(); err != nil { 93 | return err 94 | } 95 | ow := false 96 | if o, ok := thriftStruct.(oneway); ok { 97 | ow = o.Oneway() 98 | } 99 | if c.enableOneway { 100 | var err error 101 | if ow { 102 | select { 103 | case c.onewayRequests <- pendingRequest{request.ServiceMethod, request.Seq}: 104 | default: 105 | err = ErrTooManyPendingRequests 106 | } 107 | } else { 108 | select { 109 | case c.twowayRequests <- pendingRequest{request.ServiceMethod, request.Seq}: 110 | default: 111 | err = ErrTooManyPendingRequests 112 | } 113 | } 114 | if err != nil { 115 | return err 116 | } 117 | } else if ow { 118 | return ErrOnewayNotEnabled 119 | } 120 | 121 | return nil 122 | } 123 | 124 | func (c *clientCodec) ReadResponseHeader(response *rpc.Response) error { 125 | if c.enableOneway { 126 | select { 127 | case ow := <-c.onewayRequests: 128 | response.ServiceMethod = ow.method 129 | response.Seq = ow.seq 130 | return nil 131 | case _ = <-c.twowayRequests: 132 | } 133 | } 134 | 135 | name, messageType, seq, err := c.conn.ReadMessageBegin() 136 | if err != nil { 137 | return err 138 | } 139 | response.ServiceMethod = name 140 | response.Seq = uint64(seq) 141 | if messageType == MessageTypeException { 142 | exception := &ApplicationException{} 143 | if err := DecodeStruct(c.conn, exception); err != nil { 144 | return err 145 | } 146 | response.Error = exception.String() 147 | return c.conn.ReadMessageEnd() 148 | } 149 | return nil 150 | } 151 | 152 | func (c *clientCodec) ReadResponseBody(thriftStruct interface{}) error { 153 | if thriftStruct == nil { 154 | // Should only get called if ReadResponseHeader set the Error value in 155 | // which case we've already read the body (ApplicationException) 156 | return nil 157 | } 158 | 159 | if err := DecodeStruct(c.conn, thriftStruct); err != nil { 160 | return err 161 | } 162 | 163 | return c.conn.ReadMessageEnd() 164 | } 165 | 166 | func (c *clientCodec) Close() error { 167 | if cl, ok := c.conn.(io.Closer); ok { 168 | return cl.Close() 169 | } 170 | return nil 171 | } 172 | -------------------------------------------------------------------------------- /parser/parser_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2015 Samuel Stauffer. All rights reserved. 2 | // Use of this source code is governed by a 3-clause BSD 3 | // license that can be found in the LICENSE file. 4 | 5 | package parser 6 | 7 | import ( 8 | "bytes" 9 | "encoding/json" 10 | "reflect" 11 | "strconv" 12 | "testing" 13 | ) 14 | 15 | func TestServiceParsing(t *testing.T) { 16 | parser := &Parser{} 17 | thrift, err := parser.Parse(bytes.NewBuffer([]byte(` 18 | include "other.thrift" 19 | 20 | namespace go somepkg 21 | namespace python some.module123 22 | namespace python.py-twisted another 23 | 24 | const map M1 = {"hello": "world", "goodnight": "moon"} 25 | const string S1 = "foo\"\tbar" 26 | const string S2 = 'foo\'\tbar' 27 | const list L = [1, 2, 3]; 28 | 29 | service ServiceNAME extends SomeBase { 30 | # authenticate method 31 | // comment2 32 | /* some other 33 | comments */ 34 | string login(1:string password) throws (1:AuthenticationException authex), 35 | oneway void explode(); 36 | blah something() 37 | } 38 | 39 | struct SomeStruct { 40 | 1: double dbl = 1.2, 41 | 2: optional string abc 42 | }`))) 43 | if err != nil { 44 | t.Fatalf("Service parsing failed with error %s", err.Error()) 45 | } 46 | 47 | if thrift.Includes["other"] != "other.thrift" { 48 | t.Errorf("Include not parsed: %+v", thrift.Includes) 49 | } 50 | 51 | if c := thrift.Constants["M1"]; c == nil { 52 | t.Errorf("M1 constant missing") 53 | } else if c.Name != "M1" { 54 | t.Errorf("M1 name not M1, got '%s'", c.Name) 55 | } else if v, e := c.Type.String(), "map"; v != e { 56 | t.Errorf("Expected type '%s' for M1, got '%s'", e, v) 57 | } else if _, ok := c.Value.([]KeyValue); !ok { 58 | t.Errorf("Expected []KeyValue value for M1, got %T", c.Value) 59 | } 60 | 61 | if c := thrift.Constants["S1"]; c == nil { 62 | t.Errorf("S1 constant missing") 63 | } else if v, e := c.Value.(string), "foo\"\tbar"; e != v { 64 | t.Errorf("Excepted %s for constnat S1, got %s", strconv.Quote(e), strconv.Quote(v)) 65 | } 66 | if c := thrift.Constants["S2"]; c == nil { 67 | t.Errorf("S2 constant missing") 68 | } else if v, e := c.Value.(string), "foo'\tbar"; e != v { 69 | t.Errorf("Excepted %s for constnat S2, got %s", strconv.Quote(e), strconv.Quote(v)) 70 | } 71 | 72 | expConst := &Constant{ 73 | Name: "L", 74 | Type: &Type{ 75 | Name: "list", 76 | ValueType: &Type{Name: "i64"}, 77 | }, 78 | Value: []interface{}{int64(1), int64(2), int64(3)}, 79 | } 80 | if c := thrift.Constants["L"]; c == nil { 81 | t.Errorf("L constant missing") 82 | } else if !reflect.DeepEqual(c, expConst) { 83 | t.Errorf("Expected for L:\n%s\ngot\n%s", pprint(expConst), pprint(c)) 84 | } 85 | 86 | expectedStruct := &Struct{ 87 | Name: "SomeStruct", 88 | Fields: []*Field{ 89 | { 90 | ID: 1, 91 | Name: "dbl", 92 | Default: 1.2, 93 | Type: &Type{ 94 | Name: "double", 95 | }, 96 | }, 97 | { 98 | ID: 2, 99 | Name: "abc", 100 | Optional: true, 101 | Type: &Type{ 102 | Name: "string", 103 | }, 104 | }, 105 | }, 106 | } 107 | if s := thrift.Structs["SomeStruct"]; s == nil { 108 | t.Errorf("SomeStruct missing") 109 | } else if !reflect.DeepEqual(s, expectedStruct) { 110 | t.Errorf("Expected\n%s\ngot\n%s", pprint(expectedStruct), pprint(s)) 111 | } 112 | 113 | if len(thrift.Services) != 1 { 114 | t.Fatalf("Parsing service returned %d services rather than 1 as expected", len(thrift.Services)) 115 | } 116 | svc := thrift.Services["ServiceNAME"] 117 | if svc == nil || svc.Name != "ServiceNAME" { 118 | t.Fatalf("Parsing service expected to find 'ServiceNAME' rather than '%+v'", thrift.Services) 119 | } else if svc.Extends != "SomeBase" { 120 | t.Errorf("Expected extends 'SomeBase' got '%s'", svc.Extends) 121 | } 122 | 123 | expected := map[string]*Service{ 124 | "ServiceNAME": &Service{ 125 | Name: "ServiceNAME", 126 | Extends: "SomeBase", 127 | Methods: map[string]*Method{ 128 | "login": &Method{ 129 | Name: "login", 130 | ReturnType: &Type{ 131 | Name: "string", 132 | }, 133 | Arguments: []*Field{ 134 | &Field{ 135 | ID: 1, 136 | Name: "password", 137 | Optional: false, 138 | Type: &Type{ 139 | Name: "string", 140 | }, 141 | }, 142 | }, 143 | Exceptions: []*Field{ 144 | &Field{ 145 | ID: 1, 146 | Name: "authex", 147 | Optional: true, 148 | Type: &Type{ 149 | Name: "AuthenticationException", 150 | }, 151 | }, 152 | }, 153 | }, 154 | "explode": &Method{ 155 | Name: "explode", 156 | ReturnType: nil, 157 | Oneway: true, 158 | Arguments: []*Field{}, 159 | }, 160 | }, 161 | }, 162 | } 163 | for n, m := range expected["ServiceNAME"].Methods { 164 | if !reflect.DeepEqual(svc.Methods[n], m) { 165 | t.Fatalf("Parsing service returned method\n%s\ninstead of\n%s", pprint(svc.Methods[n]), pprint(m)) 166 | } 167 | } 168 | } 169 | 170 | // func TestParseFile(t *testing.T) { 171 | // th, err := ParseFile("../testfiles/full.thrift") 172 | // if err != nil { 173 | // t.Fatal(err) 174 | // } 175 | // b, err := json.MarshalIndent(th, "", " ") 176 | // if err != nil { 177 | // t.Fatal(err) 178 | // } 179 | // _ = b 180 | // } 181 | 182 | func pprint(v interface{}) string { 183 | b, err := json.MarshalIndent(v, "", " ") 184 | if err != nil { 185 | panic(err) 186 | } 187 | return string(b) 188 | } 189 | -------------------------------------------------------------------------------- /thrift/encoder.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2015 Samuel Stauffer. All rights reserved. 2 | // Use of this source code is governed by a 3-clause BSD 3 | // license that can be found in the LICENSE file. 4 | 5 | package thrift 6 | 7 | import ( 8 | "reflect" 9 | "runtime" 10 | ) 11 | 12 | // Encoder is the interface that allows types to serialize themselves to a Thrift stream 13 | type Encoder interface { 14 | EncodeThrift(ProtocolWriter) error 15 | } 16 | 17 | type encoder struct { 18 | w ProtocolWriter 19 | } 20 | 21 | // EncodeStruct tries to serialize a struct to a Thrift stream 22 | func EncodeStruct(w ProtocolWriter, v interface{}) (err error) { 23 | if en, ok := v.(Encoder); ok { 24 | return en.EncodeThrift(w) 25 | } 26 | 27 | defer func() { 28 | if r := recover(); r != nil { 29 | if _, ok := r.(runtime.Error); ok { 30 | panic(r) 31 | } 32 | err = r.(error) 33 | } 34 | }() 35 | e := &encoder{w} 36 | vo := reflect.ValueOf(v) 37 | e.writeStruct(vo) 38 | return nil 39 | } 40 | 41 | func (e *encoder) error(err interface{}) { 42 | panic(err) 43 | } 44 | 45 | func (e *encoder) writeStruct(v reflect.Value) { 46 | for v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface { 47 | v = v.Elem() 48 | } 49 | if v.Kind() != reflect.Struct { 50 | e.error(&UnsupportedValueError{Value: v, Str: "expected a struct"}) 51 | } 52 | if err := e.w.WriteStructBegin(v.Type().Name()); err != nil { 53 | e.error(err) 54 | } 55 | for _, ef := range encodeFields(v.Type()).fields { 56 | structField := v.Type().Field(ef.i) 57 | fieldValue := v.Field(ef.i) 58 | 59 | if !ef.required && !ef.keepEmpty && isEmptyValue(fieldValue) { 60 | continue 61 | } 62 | 63 | if fieldValue.Kind() == reflect.Ptr { 64 | if ef.required && fieldValue.IsNil() { 65 | e.error(&MissingRequiredField{v.Type().Name(), structField.Name}) 66 | } 67 | } 68 | 69 | ftype := ef.fieldType 70 | 71 | if err := e.w.WriteFieldBegin(structField.Name, ftype, int16(ef.id)); err != nil { 72 | e.error(err) 73 | } 74 | e.writeValue(fieldValue, ftype) 75 | if err := e.w.WriteFieldEnd(); err != nil { 76 | e.error(err) 77 | } 78 | } 79 | if err := e.w.WriteFieldStop(); err != nil { 80 | e.error(err) 81 | } 82 | if err := e.w.WriteStructEnd(); err != nil { 83 | e.error(err) 84 | } 85 | } 86 | 87 | func (e *encoder) writeValue(v reflect.Value, thriftType byte) { 88 | if en, ok := v.Interface().(Encoder); ok { 89 | if err := en.EncodeThrift(e.w); err != nil { 90 | e.error(err) 91 | } 92 | return 93 | } 94 | 95 | kind := v.Kind() 96 | if kind == reflect.Ptr || kind == reflect.Interface { 97 | v = v.Elem() 98 | kind = v.Kind() 99 | } 100 | 101 | var err error 102 | switch thriftType { 103 | case TypeBool: 104 | err = e.w.WriteBool(v.Bool()) 105 | case TypeByte: 106 | if kind == reflect.Uint8 { 107 | err = e.w.WriteByte(byte(v.Uint())) 108 | } else { 109 | err = e.w.WriteByte(byte(v.Int())) 110 | } 111 | case TypeI16: 112 | err = e.w.WriteI16(int16(v.Int())) 113 | case TypeI32: 114 | if kind == reflect.Uint32 { 115 | err = e.w.WriteI32(int32(v.Uint())) 116 | } else { 117 | err = e.w.WriteI32(int32(v.Int())) 118 | } 119 | case TypeI64: 120 | if kind == reflect.Uint64 { 121 | err = e.w.WriteI64(int64(v.Uint())) 122 | } else { 123 | err = e.w.WriteI64(v.Int()) 124 | } 125 | case TypeDouble: 126 | err = e.w.WriteDouble(v.Float()) 127 | case TypeString: 128 | if kind == reflect.Slice { 129 | elemType := v.Type().Elem() 130 | if elemType.Kind() == reflect.Uint8 { 131 | err = e.w.WriteBytes(v.Bytes()) 132 | } else { 133 | err = &UnsupportedValueError{Value: v, Str: "encoder expected a byte array"} 134 | } 135 | } else { 136 | err = e.w.WriteString(v.String()) 137 | } 138 | case TypeStruct: 139 | e.writeStruct(v) 140 | case TypeMap: 141 | keyType := v.Type().Key() 142 | valueType := v.Type().Elem() 143 | keyThriftType := fieldType(keyType) 144 | valueThriftType := fieldType(valueType) 145 | if er := e.w.WriteMapBegin(keyThriftType, valueThriftType, v.Len()); er != nil { 146 | e.error(er) 147 | } 148 | for _, k := range v.MapKeys() { 149 | e.writeValue(k, keyThriftType) 150 | e.writeValue(v.MapIndex(k), valueThriftType) 151 | } 152 | err = e.w.WriteMapEnd() 153 | case TypeList: 154 | elemType := v.Type().Elem() 155 | if elemType.Kind() == reflect.Uint8 { 156 | err = e.w.WriteBytes(v.Bytes()) 157 | } else { 158 | elemThriftType := fieldType(elemType) 159 | if er := e.w.WriteListBegin(elemThriftType, v.Len()); er != nil { 160 | e.error(er) 161 | } 162 | n := v.Len() 163 | for i := 0; i < n; i++ { 164 | e.writeValue(v.Index(i), elemThriftType) 165 | } 166 | err = e.w.WriteListEnd() 167 | } 168 | case TypeSet: 169 | if v.Type().Kind() == reflect.Slice { 170 | elemType := v.Type().Elem() 171 | elemThriftType := fieldType(elemType) 172 | if er := e.w.WriteSetBegin(elemThriftType, v.Len()); er != nil { 173 | e.error(er) 174 | } 175 | n := v.Len() 176 | for i := 0; i < n; i++ { 177 | e.writeValue(v.Index(i), elemThriftType) 178 | } 179 | err = e.w.WriteSetEnd() 180 | } else if v.Type().Kind() == reflect.Map { 181 | elemType := v.Type().Key() 182 | valueType := v.Type().Elem() 183 | elemThriftType := fieldType(elemType) 184 | if valueType.Kind() == reflect.Bool { 185 | n := 0 186 | for _, k := range v.MapKeys() { 187 | if v.MapIndex(k).Bool() { 188 | n++ 189 | } 190 | } 191 | if er := e.w.WriteSetBegin(elemThriftType, n); er != nil { 192 | e.error(er) 193 | } 194 | for _, k := range v.MapKeys() { 195 | if v.MapIndex(k).Bool() { 196 | e.writeValue(k, elemThriftType) 197 | } 198 | } 199 | } else { 200 | if er := e.w.WriteSetBegin(elemThriftType, v.Len()); er != nil { 201 | e.error(er) 202 | } 203 | for _, k := range v.MapKeys() { 204 | e.writeValue(k, elemThriftType) 205 | } 206 | } 207 | err = e.w.WriteSetEnd() 208 | } else { 209 | e.error(&UnsupportedTypeError{v.Type()}) 210 | } 211 | default: 212 | e.error(&UnsupportedTypeError{v.Type()}) 213 | } 214 | 215 | if err != nil { 216 | e.error(err) 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /thrift/protocol_text.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2015 Samuel Stauffer. All rights reserved. 2 | // Use of this source code is governed by a 3-clause BSD 3 | // license that can be found in the LICENSE file. 4 | 5 | package thrift 6 | 7 | import ( 8 | "errors" 9 | "fmt" 10 | "io" 11 | ) 12 | 13 | var ( 14 | ErrUnimplemented = errors.New("thrift: unimplemented") 15 | ) 16 | 17 | type textProtocolWriter struct { 18 | w io.Writer 19 | indentation string 20 | } 21 | 22 | func NewTextProtocolWriter(w io.Writer) ProtocolWriter { 23 | return &textProtocolWriter{w: w} 24 | } 25 | 26 | func (p *textProtocolWriter) indent() { 27 | p.indentation += "\t" 28 | } 29 | 30 | func (p *textProtocolWriter) unindent() { 31 | p.indentation = p.indentation[:len(p.indentation)-1] 32 | } 33 | 34 | func (p *textProtocolWriter) WriteMessageBegin(name string, messageType byte, seqid int32) error { 35 | fmt.Fprintf(p.w, "%sMessageBegin(%s, %d, %.8x)\n", p.indentation, name, messageType, seqid) 36 | p.indent() 37 | return nil 38 | } 39 | 40 | func (p *textProtocolWriter) WriteMessageEnd() error { 41 | p.unindent() 42 | fmt.Fprintf(p.w, "%sMessageEnd()\n", p.indentation) 43 | return nil 44 | } 45 | 46 | func (p *textProtocolWriter) WriteStructBegin(name string) error { 47 | fmt.Fprintf(p.w, "%sStructBegin(%s)\n", p.indentation, name) 48 | p.indent() 49 | return nil 50 | } 51 | 52 | func (p *textProtocolWriter) WriteStructEnd() error { 53 | p.unindent() 54 | fmt.Fprintf(p.w, "%sStructEnd()\n", p.indentation) 55 | return nil 56 | } 57 | 58 | func (p *textProtocolWriter) WriteFieldBegin(name string, fieldType byte, id int16) error { 59 | fmt.Fprintf(p.w, "%sFieldBegin(%s, %d, %d)\n", p.indentation, name, fieldType, id) 60 | p.indent() 61 | return nil 62 | } 63 | 64 | func (p *textProtocolWriter) WriteFieldEnd() error { 65 | p.unindent() 66 | fmt.Fprintf(p.w, "%sFieldEnd()\n", p.indentation) 67 | return nil 68 | } 69 | 70 | func (p *textProtocolWriter) WriteFieldStop() error { 71 | fmt.Fprintf(p.w, "%sFieldStop()\n", p.indentation) 72 | return nil 73 | } 74 | 75 | func (p *textProtocolWriter) WriteMapBegin(keyType byte, valueType byte, size int) error { 76 | fmt.Fprintf(p.w, "%sMapBegin(%d, %d, %d)\n", p.indentation, keyType, valueType, size) 77 | p.indent() 78 | return nil 79 | } 80 | 81 | func (p *textProtocolWriter) WriteMapEnd() error { 82 | p.unindent() 83 | fmt.Fprintf(p.w, "%sMapEnd()\n", p.indentation) 84 | return nil 85 | } 86 | 87 | func (p *textProtocolWriter) WriteListBegin(elementType byte, size int) error { 88 | fmt.Fprintf(p.w, "%sListBegin(%d, %d)\n", p.indentation, elementType, size) 89 | p.indent() 90 | return nil 91 | } 92 | 93 | func (p *textProtocolWriter) WriteListEnd() error { 94 | p.unindent() 95 | fmt.Fprintf(p.w, "%sListEnd()\n", p.indentation) 96 | return nil 97 | } 98 | 99 | func (p *textProtocolWriter) WriteSetBegin(elementType byte, size int) error { 100 | fmt.Fprintf(p.w, "%sSetBegin(%d, %d)\n", p.indentation, elementType, size) 101 | p.indent() 102 | return nil 103 | } 104 | 105 | func (p *textProtocolWriter) WriteSetEnd() error { 106 | p.unindent() 107 | fmt.Fprintf(p.w, "%sSetEnd()\n", p.indentation) 108 | return nil 109 | } 110 | 111 | func (p *textProtocolWriter) WriteBool(value bool) error { 112 | fmt.Fprintf(p.w, "%sBool(%+v)\n", p.indentation, value) 113 | return nil 114 | } 115 | 116 | func (p *textProtocolWriter) WriteByte(value byte) error { 117 | fmt.Fprintf(p.w, "%sByte(%d)\n", p.indentation, value) 118 | return nil 119 | } 120 | 121 | func (p *textProtocolWriter) WriteI16(value int16) error { 122 | fmt.Fprintf(p.w, "%sI16(%d)\n", p.indentation, value) 123 | return nil 124 | } 125 | 126 | func (p *textProtocolWriter) WriteI32(value int32) error { 127 | fmt.Fprintf(p.w, "%sI32(%d)\n", p.indentation, value) 128 | return nil 129 | } 130 | 131 | func (p *textProtocolWriter) WriteI64(value int64) error { 132 | fmt.Fprintf(p.w, "%sI64(%d)\n", p.indentation, value) 133 | return nil 134 | } 135 | 136 | func (p *textProtocolWriter) WriteDouble(value float64) error { 137 | fmt.Fprintf(p.w, "%sDouble(%f)\n", p.indentation, value) 138 | return nil 139 | } 140 | 141 | func (p *textProtocolWriter) WriteString(value string) error { 142 | fmt.Fprintf(p.w, "%sString(%s)\n", p.indentation, value) 143 | return nil 144 | } 145 | 146 | func (p *textProtocolWriter) WriteBytes(value []byte) error { 147 | fmt.Fprintf(p.w, "%sBytes(%+v)\n", p.indentation, value) 148 | return nil 149 | } 150 | 151 | func (p *textProtocolWriter) ReadMessageBegin() (name string, messageType byte, seqid int32, err error) { 152 | return "", 0, 0, ErrUnimplemented 153 | } 154 | 155 | func (p *textProtocolWriter) ReadMessageEnd() error { 156 | return ErrUnimplemented 157 | } 158 | 159 | func (p *textProtocolWriter) ReadStructBegin() error { 160 | return ErrUnimplemented 161 | } 162 | 163 | func (p *textProtocolWriter) ReadStructEnd() error { 164 | return ErrUnimplemented 165 | } 166 | 167 | func (p *textProtocolWriter) ReadFieldBegin() (fieldType byte, id int16, err error) { 168 | return 0, 0, ErrUnimplemented 169 | } 170 | 171 | func (p *textProtocolWriter) ReadFieldEnd() error { 172 | return ErrUnimplemented 173 | } 174 | 175 | func (p *textProtocolWriter) ReadMapBegin() (keyType byte, valueType byte, size int, err error) { 176 | return 0, 0, 0, ErrUnimplemented 177 | } 178 | 179 | func (p *textProtocolWriter) ReadMapEnd() error { 180 | return ErrUnimplemented 181 | } 182 | 183 | func (p *textProtocolWriter) ReadListBegin() (elementType byte, size int, err error) { 184 | return 0, 0, ErrUnimplemented 185 | } 186 | 187 | func (p *textProtocolWriter) ReadListEnd() error { 188 | return ErrUnimplemented 189 | } 190 | 191 | func (p *textProtocolWriter) ReadSetBegin() (elementType byte, size int, err error) { 192 | return 0, 0, ErrUnimplemented 193 | } 194 | 195 | func (p *textProtocolWriter) ReadSetEnd() error { 196 | return ErrUnimplemented 197 | } 198 | 199 | func (p *textProtocolWriter) ReadBool() (bool, error) { 200 | return false, ErrUnimplemented 201 | } 202 | 203 | func (p *textProtocolWriter) ReadByte() (byte, error) { 204 | return 0, ErrUnimplemented 205 | } 206 | 207 | func (p *textProtocolWriter) ReadI16() (int16, error) { 208 | return 0, ErrUnimplemented 209 | } 210 | 211 | func (p *textProtocolWriter) ReadI32() (int32, error) { 212 | return 0, ErrUnimplemented 213 | } 214 | 215 | func (p *textProtocolWriter) ReadI64() (int64, error) { 216 | return 0, ErrUnimplemented 217 | } 218 | 219 | func (p *textProtocolWriter) ReadDouble() (float64, error) { 220 | return 0.0, ErrUnimplemented 221 | } 222 | 223 | func (p *textProtocolWriter) ReadString() (string, error) { 224 | return "", ErrUnimplemented 225 | } 226 | 227 | func (p *textProtocolWriter) ReadBytes() ([]byte, error) { 228 | return nil, ErrUnimplemented 229 | } 230 | -------------------------------------------------------------------------------- /thrift/client_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2015 Samuel Stauffer. All rights reserved. 2 | // Use of this source code is governed by a 3-clause BSD 3 | // license that can be found in the LICENSE file. 4 | 5 | package thrift 6 | 7 | import ( 8 | "errors" 9 | "fmt" 10 | "log" 11 | "net" 12 | "net/rpc" 13 | "runtime" 14 | "sync" 15 | "testing" 16 | ) 17 | 18 | var ( 19 | serverAddr, newServerAddr string 20 | once sync.Once 21 | ) 22 | 23 | type TestRequest struct { 24 | Value int32 `thrift:"1,required"` 25 | } 26 | 27 | type TestOneWayRequest struct { 28 | Value int32 `thrift:"1,required"` 29 | } 30 | 31 | func (req *TestOneWayRequest) Oneway() bool { 32 | return true 33 | } 34 | 35 | func (tr *TestRequest) EncodeThrift(w ProtocolWriter) error { 36 | if err := w.WriteStructBegin("TestRequest"); err != nil { 37 | return err 38 | } 39 | if err := w.WriteFieldBegin("Value", TypeI32, 1); err != nil { 40 | return err 41 | } 42 | if err := w.WriteI32(tr.Value); err != nil { 43 | return err 44 | } 45 | if err := w.WriteFieldEnd(); err != nil { 46 | return err 47 | } 48 | if err := w.WriteFieldStop(); err != nil { 49 | return err 50 | } 51 | return w.WriteStructEnd() 52 | } 53 | 54 | func (tr *TestRequest) DecodeThrift(r ProtocolReader) error { 55 | if err := r.ReadStructBegin(); err != nil { 56 | return err 57 | } 58 | ftype, id, err := r.ReadFieldBegin() 59 | if err != nil { 60 | return err 61 | } 62 | if id != 1 || ftype != TypeI32 { 63 | return &MissingRequiredField{ 64 | StructName: "TestRequest", 65 | FieldName: "Value", 66 | } 67 | } 68 | if tr.Value, err = r.ReadI32(); err != nil { 69 | return err 70 | } 71 | if err := r.ReadFieldEnd(); err != nil { 72 | return err 73 | } 74 | if ftype, _, err := r.ReadFieldBegin(); err != nil { 75 | return err 76 | } else if ftype != TypeStop { 77 | return errors.New("expected field stop") 78 | } 79 | return r.ReadStructEnd() 80 | } 81 | 82 | type TestResponse struct { 83 | Value int32 `thrift:"0,required"` 84 | } 85 | 86 | func (tr *TestResponse) EncodeThrift(w ProtocolWriter) error { 87 | if err := w.WriteStructBegin("TestResponse"); err != nil { 88 | return err 89 | } 90 | if err := w.WriteFieldBegin("Value", TypeI32, 0); err != nil { 91 | return err 92 | } 93 | if err := w.WriteI32(tr.Value); err != nil { 94 | return err 95 | } 96 | if err := w.WriteFieldEnd(); err != nil { 97 | return err 98 | } 99 | if err := w.WriteFieldStop(); err != nil { 100 | return err 101 | } 102 | return w.WriteStructEnd() 103 | } 104 | 105 | func (tr *TestResponse) DecodeThrift(r ProtocolReader) error { 106 | if err := r.ReadStructBegin(); err != nil { 107 | return err 108 | } 109 | ftype, id, err := r.ReadFieldBegin() 110 | if err != nil { 111 | return err 112 | } 113 | if id != 0 || ftype != TypeI32 { 114 | return &MissingRequiredField{ 115 | StructName: "TestResponse", 116 | FieldName: "Value", 117 | } 118 | } 119 | if tr.Value, err = r.ReadI32(); err != nil { 120 | return err 121 | } 122 | if err := r.ReadFieldEnd(); err != nil { 123 | return err 124 | } 125 | if ftype, _, err := r.ReadFieldBegin(); err != nil { 126 | return err 127 | } else if ftype != TypeStop { 128 | return errors.New("expected field stop") 129 | } 130 | return r.ReadStructEnd() 131 | } 132 | 133 | type TestService int 134 | 135 | func (s *TestService) Success(req *TestRequest, res *TestResponse) error { 136 | res.Value = req.Value 137 | return nil 138 | } 139 | 140 | func (s *TestService) Fail(req *TestRequest, res *TestResponse) error { 141 | res.Value = req.Value 142 | return errors.New("fail") 143 | } 144 | 145 | func listenTCP() (net.Listener, string) { 146 | l, e := net.Listen("tcp", "127.0.0.1:0") // any available address 147 | if e != nil { 148 | log.Fatalf("net.Listen tcp :0: %v", e) 149 | } 150 | return l, l.Addr().String() 151 | } 152 | 153 | func startServer() { 154 | rpc.RegisterName("Thrift", new(TestService)) 155 | 156 | var l net.Listener 157 | l, serverAddr = listenTCP() 158 | log.Println("Test RPC server listening on", serverAddr) 159 | go func() { 160 | for { 161 | conn, err := l.Accept() 162 | if err != nil { 163 | panic(err) 164 | } 165 | c := NewFramedReadWriteCloser(conn, 0) 166 | t := NewTransport(c, BinaryProtocol) 167 | go rpc.ServeCodec(NewServerCodec(t)) 168 | } 169 | }() 170 | } 171 | 172 | func TestRPCClientSuccess(t *testing.T) { 173 | once.Do(startServer) 174 | 175 | c, err := Dial("tcp", serverAddr, true, BinaryProtocol, false) 176 | if err != nil { 177 | t.Fatalf("NewClient returned error: %+v", err) 178 | } 179 | req := &TestRequest{123} 180 | res := &TestResponse{789} 181 | if err := c.Call("Success", req, res); err != nil { 182 | t.Fatalf("Client.Call returned error: %+v", err) 183 | } 184 | if res.Value != req.Value { 185 | t.Fatalf("Response value wrong: %d != %d", res.Value, req.Value) 186 | } 187 | } 188 | 189 | func TestRPCClientOneWay(t *testing.T) { 190 | once.Do(startServer) 191 | 192 | c, err := Dial("tcp", serverAddr, true, BinaryProtocol, true) 193 | if err != nil { 194 | t.Fatalf("NewClient returned error: %+v", err) 195 | } 196 | req := &TestOneWayRequest{123} 197 | if err := c.Call("Success", req, nil); err != nil { 198 | t.Fatalf("Client.Call returned error: %+v", err) 199 | } 200 | } 201 | 202 | func TestRPCClientFail(t *testing.T) { 203 | once.Do(startServer) 204 | 205 | c, err := Dial("tcp", serverAddr, true, BinaryProtocol, false) 206 | if err != nil { 207 | t.Fatalf("NewClient returned error: %+v", err) 208 | } 209 | req := &TestRequest{123} 210 | res := &TestResponse{789} 211 | if err := c.Call("Fail", req, res); err == nil { 212 | t.Fatalf("Client.Call didn't return an error as it should") 213 | } else if err.Error() != "Internal Error: fail" { 214 | t.Fatalf("Expected 'fail' error but got '%s'", err) 215 | } 216 | 217 | // Make sure an exception doesn't cause future requests to fail 218 | 219 | if err := c.Call("Success", req, res); err != nil { 220 | t.Fatalf("Client.Call returned error: %+v", err) 221 | } 222 | if res.Value != req.Value { 223 | t.Fatalf("Response value wrong: %d != %d", res.Value, req.Value) 224 | } 225 | } 226 | 227 | func TestRPCMallocCount(t *testing.T) { 228 | once.Do(startServer) 229 | 230 | c, err := Dial("tcp", serverAddr, true, BinaryProtocol, false) 231 | if err != nil { 232 | t.Fatalf("NewClient returned error: %+v", err) 233 | } 234 | req := &TestRequest{123} 235 | res := &TestResponse{789} 236 | allocs := testing.AllocsPerRun(100, func() { 237 | if err := c.Call("Success", req, res); err != nil { 238 | t.Fatalf("Client.Call returned error: %+v", err) 239 | } 240 | }) 241 | fmt.Printf("mallocs per thrift.rpc round trip: %d\n", int(allocs)) 242 | runtime.GC() 243 | } 244 | -------------------------------------------------------------------------------- /thrift/decoder.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2015 Samuel Stauffer. All rights reserved. 2 | // Use of this source code is governed by a 3-clause BSD 3 | // license that can be found in the LICENSE file. 4 | 5 | package thrift 6 | 7 | import ( 8 | "reflect" 9 | "runtime" 10 | ) 11 | 12 | // Decoder is the interface that allows types to deserialize themselves from a Thrift stream 13 | type Decoder interface { 14 | DecodeThrift(ProtocolReader) error 15 | } 16 | 17 | type decoder struct { 18 | r ProtocolReader 19 | } 20 | 21 | // DecodeStruct tries to deserialize a struct from a Thrift stream 22 | func DecodeStruct(r ProtocolReader, v interface{}) (err error) { 23 | if de, ok := v.(Decoder); ok { 24 | return de.DecodeThrift(r) 25 | } 26 | 27 | defer func() { 28 | if r := recover(); r != nil { 29 | if _, ok := r.(runtime.Error); ok { 30 | panic(r) 31 | } 32 | err = r.(error) 33 | } 34 | }() 35 | d := &decoder{r} 36 | vo := reflect.ValueOf(v) 37 | for vo.Kind() != reflect.Ptr { 38 | d.error(&UnsupportedValueError{Value: vo, Str: "pointer to struct expected"}) 39 | } 40 | if vo.Elem().Kind() != reflect.Struct { 41 | d.error(&UnsupportedValueError{Value: vo, Str: "expected a struct"}) 42 | } 43 | d.readValue(TypeStruct, vo.Elem()) 44 | return nil 45 | } 46 | 47 | func (d *decoder) error(err interface{}) { 48 | panic(err) 49 | } 50 | 51 | func (d *decoder) readValue(thriftType byte, rf reflect.Value) { 52 | v := rf 53 | kind := rf.Kind() 54 | if kind == reflect.Ptr { 55 | if rf.IsNil() { 56 | rf.Set(reflect.New(rf.Type().Elem())) 57 | } 58 | v = rf.Elem() 59 | kind = v.Kind() 60 | } 61 | 62 | if de, ok := rf.Interface().(Decoder); ok { 63 | if err := de.DecodeThrift(d.r); err != nil { 64 | d.error(err) 65 | } 66 | return 67 | } 68 | 69 | var err error 70 | switch thriftType { 71 | case TypeBool: 72 | if val, err := d.r.ReadBool(); err != nil { 73 | d.error(err) 74 | } else { 75 | v.SetBool(val) 76 | } 77 | case TypeByte: 78 | if val, err := d.r.ReadByte(); err != nil { 79 | d.error(err) 80 | } else { 81 | if kind == reflect.Uint8 { 82 | v.SetUint(uint64(val)) 83 | } else { 84 | v.SetInt(int64(val)) 85 | } 86 | } 87 | case TypeI16: 88 | if val, err := d.r.ReadI16(); err != nil { 89 | d.error(err) 90 | } else { 91 | v.SetInt(int64(val)) 92 | } 93 | case TypeI32: 94 | if val, err := d.r.ReadI32(); err != nil { 95 | d.error(err) 96 | } else { 97 | if kind == reflect.Uint32 { 98 | v.SetUint(uint64(val)) 99 | } else { 100 | v.SetInt(int64(val)) 101 | } 102 | } 103 | case TypeI64: 104 | if val, err := d.r.ReadI64(); err != nil { 105 | d.error(err) 106 | } else { 107 | if kind == reflect.Uint64 { 108 | v.SetUint(uint64(val)) 109 | } else { 110 | v.SetInt(val) 111 | } 112 | } 113 | case TypeDouble: 114 | if val, err := d.r.ReadDouble(); err != nil { 115 | d.error(err) 116 | } else { 117 | v.SetFloat(val) 118 | } 119 | case TypeString: 120 | if kind == reflect.Slice { 121 | elemType := v.Type().Elem() 122 | elemTypeName := elemType.Name() 123 | if elemType.Kind() == reflect.Uint8 && (elemTypeName == "byte" || elemTypeName == "uint8") { 124 | if val, err := d.r.ReadBytes(); err != nil { 125 | d.error(err) 126 | } else { 127 | v.SetBytes(val) 128 | } 129 | } else { 130 | err = &UnsupportedValueError{Value: v, Str: "decoder expected a byte array"} 131 | } 132 | } else { 133 | if val, err := d.r.ReadString(); err != nil { 134 | d.error(err) 135 | } else { 136 | v.SetString(val) 137 | } 138 | } 139 | case TypeStruct: 140 | if err := d.r.ReadStructBegin(); err != nil { 141 | d.error(err) 142 | } 143 | 144 | meta := encodeFields(v.Type()) 145 | req := meta.required 146 | for { 147 | ftype, id, err := d.r.ReadFieldBegin() 148 | if err != nil { 149 | d.error(err) 150 | } 151 | if ftype == TypeStop { 152 | break 153 | } 154 | 155 | ef, ok := meta.fields[int(id)] 156 | if !ok { 157 | SkipValue(d.r, ftype) 158 | } else { 159 | req &= ^(uint64(1) << uint64(id)) 160 | fieldValue := v.Field(ef.i) 161 | if ftype != ef.fieldType { 162 | d.error(&UnsupportedValueError{Value: fieldValue, Str: "type mismatch"}) 163 | } 164 | d.readValue(ftype, fieldValue) 165 | } 166 | 167 | if err = d.r.ReadFieldEnd(); err != nil { 168 | d.error(err) 169 | } 170 | } 171 | 172 | if err := d.r.ReadStructEnd(); err != nil { 173 | d.error(err) 174 | } 175 | 176 | if req != 0 { 177 | for i := 0; req != 0; i, req = i+1, req>>1 { 178 | if req&1 != 0 { 179 | d.error(&MissingRequiredField{ 180 | StructName: v.Type().Name(), 181 | FieldName: meta.fields[i].name, 182 | }) 183 | } 184 | } 185 | } 186 | case TypeMap: 187 | keyType := v.Type().Key() 188 | valueType := v.Type().Elem() 189 | ktype, vtype, n, err := d.r.ReadMapBegin() 190 | if err != nil { 191 | d.error(err) 192 | } 193 | v.Set(reflect.MakeMap(v.Type())) 194 | for i := 0; i < n; i++ { 195 | key := reflect.New(keyType).Elem() 196 | val := reflect.New(valueType).Elem() 197 | d.readValue(ktype, key) 198 | d.readValue(vtype, val) 199 | v.SetMapIndex(key, val) 200 | } 201 | if err := d.r.ReadMapEnd(); err != nil { 202 | d.error(err) 203 | } 204 | case TypeList: 205 | elemType := v.Type().Elem() 206 | et, n, err := d.r.ReadListBegin() 207 | if err != nil { 208 | d.error(err) 209 | } 210 | for i := 0; i < n; i++ { 211 | val := reflect.New(elemType) 212 | d.readValue(et, val.Elem()) 213 | v.Set(reflect.Append(v, val.Elem())) 214 | } 215 | if err := d.r.ReadListEnd(); err != nil { 216 | d.error(err) 217 | } 218 | case TypeSet: 219 | if v.Type().Kind() == reflect.Slice { 220 | elemType := v.Type().Elem() 221 | et, n, err := d.r.ReadSetBegin() 222 | if err != nil { 223 | d.error(err) 224 | } 225 | for i := 0; i < n; i++ { 226 | val := reflect.New(elemType) 227 | d.readValue(et, val.Elem()) 228 | v.Set(reflect.Append(v, val.Elem())) 229 | } 230 | if err := d.r.ReadSetEnd(); err != nil { 231 | d.error(err) 232 | } 233 | } else if v.Type().Kind() == reflect.Map { 234 | elemType := v.Type().Key() 235 | valueType := v.Type().Elem() 236 | et, n, err := d.r.ReadSetBegin() 237 | if err != nil { 238 | d.error(err) 239 | } 240 | v.Set(reflect.MakeMap(v.Type())) 241 | for i := 0; i < n; i++ { 242 | key := reflect.New(elemType).Elem() 243 | d.readValue(et, key) 244 | switch valueType.Kind() { 245 | case reflect.Bool: 246 | v.SetMapIndex(key, reflect.ValueOf(true)) 247 | default: 248 | v.SetMapIndex(key, reflect.Zero(valueType)) 249 | } 250 | } 251 | if err := d.r.ReadSetEnd(); err != nil { 252 | d.error(err) 253 | } 254 | } else { 255 | d.error(&UnsupportedTypeError{v.Type()}) 256 | } 257 | default: 258 | d.error(&UnsupportedTypeError{v.Type()}) 259 | } 260 | 261 | if err != nil { 262 | d.error(err) 263 | } 264 | 265 | return 266 | } 267 | -------------------------------------------------------------------------------- /thrift/protocol_compact_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2015 Samuel Stauffer. All rights reserved. 2 | // Use of this source code is governed by a 3-clause BSD 3 | // license that can be found in the LICENSE file. 4 | 5 | package thrift 6 | 7 | import ( 8 | "bytes" 9 | "testing" 10 | ) 11 | 12 | func TestCompactProtocol(t *testing.T) { 13 | b := &bytes.Buffer{} 14 | testProtocol(t, NewCompactProtocolReader(b), NewCompactProtocolWriter(b)) 15 | } 16 | 17 | func TestCompactList(t *testing.T) { 18 | tests := []struct { 19 | values []byte 20 | bytes []byte 21 | }{ 22 | {[]byte{}, []byte{3}}, 23 | {[]byte{64}, []byte{19, 64}}, 24 | {[]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, 25 | []byte{243, 17, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}}, 26 | } 27 | 28 | for _, exp := range tests { 29 | expValue := exp.values 30 | expBytes := exp.bytes 31 | 32 | b := &bytes.Buffer{} 33 | w := NewCompactProtocolWriter(b) 34 | if err := w.WriteListBegin(TypeByte, len(expValue)); err != nil { 35 | t.Fatalf("WriteListBegin returned an error: %+v", err) 36 | } 37 | for _, v := range expValue { 38 | if err := w.WriteByte(v); err != nil { 39 | t.Fatalf("WriteByte returned an error: %+v", err) 40 | } 41 | } 42 | if err := w.WriteListEnd(); err != nil { 43 | t.Fatalf("WriteListEnd returned an error: %+v", err) 44 | } 45 | out := b.Bytes() 46 | if bytes.Compare(out, expBytes) != 0 { 47 | t.Fatalf("WriteListBegin wrote %+v which did not match expected %+v", out, expBytes) 48 | } 49 | 50 | b = bytes.NewBuffer(expBytes) 51 | r := NewCompactProtocolReader(b) 52 | etype, size, err := r.ReadListBegin() 53 | if err != nil { 54 | t.Fatalf("ReadListBegin returned an error: %+v", err) 55 | } else if etype != TypeByte { 56 | t.Fatalf("ReadListBegin returned wrong type %d instead of %d", etype, TypeByte) 57 | } else if size != len(expValue) { 58 | t.Fatalf("ReadListBegin returned wrong size %d insted of %d", size, len(expValue)) 59 | } 60 | for i := 0; i < size; i++ { 61 | if v, err := r.ReadByte(); err != nil { 62 | t.Fatalf("ReadByte returned an error: %+v", err) 63 | } else if v != expValue[i] { 64 | t.Fatalf("ReadByte returned wrong value %d insted of %d", v, expBytes[i]) 65 | } 66 | } 67 | if err := r.ReadListEnd(); err != nil { 68 | t.Fatalf("ReadListEnd returned an error: %+v", err) 69 | } 70 | } 71 | } 72 | 73 | func TestCompactString(t *testing.T) { 74 | expStrings := map[string][]byte{ 75 | "": {0}, 76 | "foo": {3, 102, 111, 111}, 77 | } 78 | 79 | for expValue, expBytes := range expStrings { 80 | b := &bytes.Buffer{} 81 | w := NewCompactProtocolWriter(b) 82 | err := w.WriteString(expValue) 83 | if err != nil { 84 | t.Fatalf("WriteString returned an error: %+v", err) 85 | } 86 | out := b.Bytes() 87 | if bytes.Compare(out, expBytes) != 0 { 88 | t.Fatalf("WriteString wrote %+v which did not match expected %+v", out, expBytes) 89 | } 90 | 91 | b = bytes.NewBuffer(expBytes) 92 | r := NewCompactProtocolReader(b) 93 | v, err := r.ReadString() 94 | if err != nil { 95 | t.Fatalf("ReadString returned an error: %+v", err) 96 | } 97 | if v != expValue { 98 | t.Fatalf("ReadString returned the wrong value %s instead of %s", v, expValue) 99 | } 100 | } 101 | } 102 | 103 | func TestCompactI16(t *testing.T) { 104 | exp := map[int16][]byte{ 105 | 0: {0}, 106 | -1: {1}, 107 | 1: {2}, 108 | 12345: {242, 192, 1}, 109 | } 110 | 111 | for expValue, expBytes := range exp { 112 | b := &bytes.Buffer{} 113 | w := NewCompactProtocolWriter(b) 114 | err := w.WriteI16(expValue) 115 | if err != nil { 116 | t.Fatalf("WriteI16 returned an error: %+v", err) 117 | } 118 | out := b.Bytes() 119 | if bytes.Compare(out, expBytes) != 0 { 120 | t.Fatalf("WriteI16 wrote %+v which did not match expected %+v", out, expBytes) 121 | } 122 | 123 | b = bytes.NewBuffer(expBytes) 124 | r := NewCompactProtocolReader(b) 125 | v, err := r.ReadI16() 126 | if err != nil { 127 | t.Fatalf("ReadI16 returned an error: %+v", err) 128 | } 129 | if v != expValue { 130 | t.Fatalf("ReadI16 returned the wrong value %d instead of %d", v, expValue) 131 | } 132 | } 133 | } 134 | 135 | func TestCompactI32(t *testing.T) { 136 | exp := map[int32][]byte{ 137 | 0: {0}, 138 | -1: {1}, 139 | 1: {2}, 140 | 1234567890: {164, 139, 176, 153, 9}, 141 | } 142 | 143 | for expValue, expBytes := range exp { 144 | b := &bytes.Buffer{} 145 | w := NewCompactProtocolWriter(b) 146 | err := w.WriteI32(expValue) 147 | if err != nil { 148 | t.Fatalf("WriteI32 returned an error: %+v", err) 149 | } 150 | out := b.Bytes() 151 | if bytes.Compare(out, expBytes) != 0 { 152 | t.Fatalf("WriteI32 wrote %+v which did not match expected %+v", out, expBytes) 153 | } 154 | 155 | b = bytes.NewBuffer(expBytes) 156 | r := NewCompactProtocolReader(b) 157 | v, err := r.ReadI32() 158 | if err != nil { 159 | t.Fatalf("Read32 returned an error: %+v", err) 160 | } 161 | if v != expValue { 162 | t.Fatalf("Read32 returned the wrong value %d instead of %d", v, expValue) 163 | } 164 | } 165 | } 166 | 167 | func BenchmarkCompactProtocolReadByte(b *testing.B) { 168 | buf := &loopingReader{} 169 | w := NewCompactProtocolWriter(buf) 170 | r := NewCompactProtocolReader(buf) 171 | w.WriteByte(123) 172 | for i := 0; i < b.N; i++ { 173 | r.ReadByte() 174 | } 175 | } 176 | 177 | func BenchmarkCompactProtocolReadI32Small(b *testing.B) { 178 | buf := &loopingReader{} 179 | w := NewCompactProtocolWriter(buf) 180 | r := NewCompactProtocolReader(buf) 181 | w.WriteI32(1) 182 | for i := 0; i < b.N; i++ { 183 | r.ReadI32() 184 | } 185 | } 186 | 187 | func BenchmarkCompactProtocolReadI32Large(b *testing.B) { 188 | buf := &loopingReader{} 189 | w := NewCompactProtocolWriter(buf) 190 | r := NewCompactProtocolReader(buf) 191 | w.WriteI32(1234567890) 192 | for i := 0; i < b.N; i++ { 193 | r.ReadI32() 194 | } 195 | } 196 | 197 | func BenchmarkCompactProtocolWriteByte(b *testing.B) { 198 | buf := nullWriter(0) 199 | w := NewCompactProtocolWriter(buf) 200 | for i := 0; i < b.N; i++ { 201 | w.WriteByte(1) 202 | } 203 | } 204 | 205 | func BenchmarkCompactProtocolWriteI32(b *testing.B) { 206 | buf := nullWriter(0) 207 | w := NewCompactProtocolWriter(buf) 208 | for i := 0; i < b.N; i++ { 209 | w.WriteI32(1) 210 | } 211 | } 212 | 213 | func BenchmarkCompactProtocolWriteString4(b *testing.B) { 214 | buf := nullWriter(0) 215 | w := NewCompactProtocolWriter(buf) 216 | for i := 0; i < b.N; i++ { 217 | w.WriteString("test") 218 | } 219 | } 220 | 221 | func BenchmarkCompactProtocolWriteFullMessage(b *testing.B) { 222 | buf := nullWriter(0) 223 | w := NewCompactProtocolWriter(buf) 224 | for i := 0; i < b.N; i++ { 225 | w.WriteMessageBegin("", 2, 123) 226 | w.WriteStructBegin("") 227 | w.WriteFieldBegin("", TypeBool, 1) 228 | w.WriteBool(true) 229 | w.WriteFieldEnd() 230 | w.WriteFieldBegin("", TypeBool, 3) 231 | w.WriteBool(false) 232 | w.WriteFieldEnd() 233 | w.WriteFieldBegin("", TypeString, 2) 234 | w.WriteString("foo") 235 | w.WriteFieldEnd() 236 | w.WriteFieldStop() 237 | w.WriteStructEnd() 238 | w.WriteMessageEnd() 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /thrift/encoder_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2015 Samuel Stauffer. All rights reserved. 2 | // Use of this source code is governed by a 3-clause BSD 3 | // license that can be found in the LICENSE file. 4 | 5 | package thrift 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "reflect" 11 | "testing" 12 | "time" 13 | ) 14 | 15 | type TestStruct2 struct { 16 | Str string `thrift:"1"` 17 | Binary []byte `thrift:"2"` 18 | } 19 | 20 | func (t *TestStruct2) String() string { 21 | return fmt.Sprintf("{Str:%s Binary:%+v}", t.Str, t.Binary) 22 | } 23 | 24 | type IntSet []int32 25 | 26 | func (s *IntSet) EncodeThrift(w ProtocolWriter) error { 27 | if err := w.WriteByte(byte(len(*s))); err != nil { 28 | return err 29 | } 30 | for _, v := range *s { 31 | if err := w.WriteI32(v); err != nil { 32 | return err 33 | } 34 | } 35 | return nil 36 | } 37 | 38 | func (s *IntSet) DecodeThrift(r ProtocolReader) error { 39 | l, err := r.ReadByte() 40 | if err != nil { 41 | return err 42 | } 43 | sl := (*s)[:0] 44 | for i := byte(0); i < l; i++ { 45 | v, err := r.ReadI32() 46 | if err != nil { 47 | return err 48 | } 49 | sl = append(sl, v*10) 50 | } 51 | *s = sl 52 | return nil 53 | } 54 | 55 | type TestStruct struct { 56 | String string `thrift:"1"` 57 | Int *int `thrift:"2"` 58 | List []string `thrift:"3"` 59 | Map map[string]string `thrift:"4"` 60 | Struct *TestStruct2 `thrift:"5"` 61 | List2 []*string `thrift:"6"` 62 | Struct2 TestStruct2 `thrift:"7"` 63 | Binary []byte `thrift:"8"` 64 | Set []string `thrift:"9,set"` 65 | Set2 map[string]struct{} `thrift:"10"` 66 | Set3 map[string]bool `thrift:"11,set"` 67 | Uint32 uint32 `thrift:"12"` 68 | Uint64 uint64 `thrift:"13"` 69 | Duration time.Duration `thrift:"14"` 70 | } 71 | 72 | type TestStructRequiredOptional struct { 73 | RequiredPtr *string `thrift:"1,required"` 74 | Required string `thrift:"2,required"` 75 | OptionalPtr *string `thrift:"3"` 76 | Optional string `thrift:"4"` 77 | } 78 | 79 | type TestEmptyStruct struct{} 80 | 81 | type testCustomStruct struct { 82 | Custom *IntSet `thrift:"1"` 83 | } 84 | 85 | func TestKeepEmpty(t *testing.T) { 86 | buf := &bytes.Buffer{} 87 | 88 | s := struct { 89 | Str1 string `thrift:"1"` 90 | }{} 91 | err := EncodeStruct(NewBinaryProtocolWriter(buf, true), s) 92 | if err != nil { 93 | t.Fatal(err) 94 | } 95 | if buf.Len() != 1 || buf.Bytes()[0] != 0 { 96 | t.Fatal("missing keepempty should mean empty fields are not serialized") 97 | } 98 | 99 | buf.Reset() 100 | s2 := struct { 101 | Str1 string `thrift:"1,keepempty"` 102 | }{} 103 | err = EncodeStruct(NewBinaryProtocolWriter(buf, true), s2) 104 | if err != nil { 105 | t.Fatal(err) 106 | } 107 | if buf.Len() != 8 { 108 | t.Fatal("keepempty should cause empty fields to be serialized") 109 | } 110 | } 111 | 112 | func TestEncodeRequired(t *testing.T) { 113 | buf := &bytes.Buffer{} 114 | 115 | s := struct { 116 | Str1 string `thrift:"1,required"` 117 | }{} 118 | err := EncodeStruct(NewBinaryProtocolWriter(buf, true), s) 119 | if err != nil { 120 | t.Fatal(err) 121 | } 122 | if buf.Len() != 8 { 123 | t.Fatal("Non-pointer required fields that aren't 'keepempty' should be serialized empty") 124 | } 125 | 126 | buf.Reset() 127 | s2 := struct { 128 | Str1 *string `thrift:"1,required"` 129 | }{} 130 | err = EncodeStruct(NewBinaryProtocolWriter(buf, true), s2) 131 | _, ok := err.(*MissingRequiredField) 132 | if !ok { 133 | t.Fatalf("Missing required field should throw MissingRequiredField instead of %+v", err) 134 | } 135 | } 136 | 137 | func TestBasics(t *testing.T) { 138 | i := 123 139 | str := "bar" 140 | ts2 := TestStruct2{"qwerty", []byte{1, 2, 3}} 141 | s := &TestStruct{ 142 | "test", 143 | &i, 144 | []string{"a", "b"}, 145 | map[string]string{"a": "b", "1": "2"}, 146 | &ts2, 147 | []*string{&str}, 148 | ts2, 149 | []byte{1, 2, 3}, 150 | []string{"a", "b"}, 151 | map[string]struct{}{"i": struct{}{}, "o": struct{}{}}, 152 | map[string]bool{"q": true, "p": false}, 153 | 1<<31 + 2, 154 | 1<<63 + 2, 155 | time.Second, 156 | } 157 | buf := &bytes.Buffer{} 158 | 159 | err := EncodeStruct(NewBinaryProtocolWriter(buf, true), s) 160 | if err != nil { 161 | t.Fatal(err) 162 | } 163 | 164 | s2 := &TestStruct{} 165 | err = DecodeStruct(NewBinaryProtocolReader(buf, false), s2) 166 | if err != nil { 167 | t.Fatal(err) 168 | } 169 | 170 | // Make sure map[string]bool regards a false value as not-belonging to the set 171 | delete(s.Set3, "p") 172 | 173 | if !reflect.DeepEqual(s, s2) { 174 | t.Fatalf("encdec doesn't match: %+v != %+v", s, s2) 175 | } 176 | } 177 | 178 | func TestEncodeRequiredFields(t *testing.T) { 179 | buf := &bytes.Buffer{} 180 | 181 | // encode nil pointer required field 182 | 183 | s := &TestStructRequiredOptional{nil, "", nil, ""} 184 | err := EncodeStruct(NewBinaryProtocolWriter(buf, true), s) 185 | if err == nil { 186 | t.Fatal("Expected MissingRequiredField exception") 187 | } 188 | e, ok := err.(*MissingRequiredField) 189 | if !ok { 190 | t.Fatalf("Expected MissingRequiredField exception instead %+v", err) 191 | } 192 | if e.StructName != "TestStructRequiredOptional" || e.FieldName != "RequiredPtr" { 193 | t.Fatalf("Expected MissingRequiredField{'TestStructRequiredOptional', 'RequiredPtr'} instead %+v", e) 194 | } 195 | 196 | // encode empty non-pointer required field 197 | 198 | str := "foo" 199 | s = &TestStructRequiredOptional{&str, "", nil, ""} 200 | err = EncodeStruct(NewBinaryProtocolWriter(buf, true), s) 201 | if err != nil { 202 | t.Fatal("Empty non-pointer required fields shouldn't return an error") 203 | } 204 | } 205 | 206 | func TestDecodeRequiredFields(t *testing.T) { 207 | buf := &bytes.Buffer{} 208 | 209 | s := &TestEmptyStruct{} 210 | err := EncodeStruct(NewBinaryProtocolWriter(buf, true), s) 211 | if err != nil { 212 | t.Fatal("Failed to encode empty struct") 213 | } 214 | 215 | s2 := &TestStructRequiredOptional{} 216 | err = DecodeStruct(NewBinaryProtocolReader(buf, false), s2) 217 | if err == nil { 218 | t.Fatal("Expected MissingRequiredField exception") 219 | } 220 | e, ok := err.(*MissingRequiredField) 221 | if !ok { 222 | t.Fatalf("Expected MissingRequiredField exception instead %+v", err) 223 | } 224 | if e.StructName != "TestStructRequiredOptional" || e.FieldName != "RequiredPtr" { 225 | t.Fatalf("Expected MissingRequiredField{'TestStructRequiredOptional', 'RequiredPtr'} instead %+v", e) 226 | } 227 | } 228 | 229 | func TestDecodeUnknownFields(t *testing.T) { 230 | buf := &bytes.Buffer{} 231 | 232 | str := "foo" 233 | s := &TestStructRequiredOptional{&str, str, &str, str} 234 | err := EncodeStruct(NewBinaryProtocolWriter(buf, true), s) 235 | if err != nil { 236 | t.Fatal("Failed to encode TestStructRequiredOptional struct") 237 | } 238 | 239 | s2 := &TestEmptyStruct{} 240 | err = DecodeStruct(NewBinaryProtocolReader(buf, false), s2) 241 | if err != nil { 242 | t.Fatalf("Unknown fields during decode weren't ignored: %+v", err) 243 | } 244 | } 245 | 246 | func TestDecodeCustom(t *testing.T) { 247 | is := IntSet([]int32{1, 2, 3}) 248 | st := &testCustomStruct{ 249 | Custom: &is, 250 | } 251 | 252 | buf := &bytes.Buffer{} 253 | err := EncodeStruct(NewBinaryProtocolWriter(buf, true), st) 254 | if err != nil { 255 | t.Fatal("Failed to encode custom struct") 256 | } 257 | 258 | st2 := &testCustomStruct{} 259 | err = DecodeStruct(NewBinaryProtocolReader(buf, false), st2) 260 | if err != nil { 261 | t.Fatalf("Custom fields during decode failed: %+v", err) 262 | } 263 | expected := IntSet([]int32{10, 20, 30}) 264 | if !reflect.DeepEqual(expected, *st2.Custom) { 265 | t.Fatalf("Custom decode failed expected %+v instead %+v", expected, *st2.Custom) 266 | } 267 | } 268 | 269 | // Benchmarks 270 | 271 | func BenchmarkEncodeEmptyStruct(b *testing.B) { 272 | buf := nullWriter(0) 273 | st := &struct{}{} 274 | for i := 0; i < b.N; i++ { 275 | EncodeStruct(NewBinaryProtocolWriter(buf, true), st) 276 | } 277 | } 278 | 279 | func BenchmarkDecodeEmptyStruct(b *testing.B) { 280 | b.StopTimer() 281 | buf1 := &bytes.Buffer{} 282 | st := &struct{}{} 283 | EncodeStruct(NewBinaryProtocolWriter(buf1, true), st) 284 | buf := bytes.NewBuffer(bytes.Repeat(buf1.Bytes(), b.N)) 285 | b.StartTimer() 286 | for i := 0; i < b.N; i++ { 287 | DecodeStruct(NewBinaryProtocolReader(buf, false), st) 288 | } 289 | } 290 | 291 | func BenchmarkEncodeSimpleStruct(b *testing.B) { 292 | buf := nullWriter(0) 293 | st := &struct { 294 | Str string `thrift:"1,required"` 295 | Int int32 `thrift:"2,required"` 296 | }{ 297 | Str: "test", 298 | Int: 123, 299 | } 300 | for i := 0; i < b.N; i++ { 301 | EncodeStruct(NewBinaryProtocolWriter(buf, true), st) 302 | } 303 | } 304 | 305 | func BenchmarkDecodeSimpleStruct(b *testing.B) { 306 | b.StopTimer() 307 | buf1 := &bytes.Buffer{} 308 | st := &struct { 309 | Str string `thrift:"1,required"` 310 | Int int32 `thrift:"2,required"` 311 | }{ 312 | Str: "test", 313 | Int: 123, 314 | } 315 | buf := bytes.NewBuffer(bytes.Repeat(buf1.Bytes(), b.N)) 316 | b.StartTimer() 317 | for i := 0; i < b.N; i++ { 318 | DecodeStruct(NewBinaryProtocolReader(buf, false), st) 319 | } 320 | } 321 | -------------------------------------------------------------------------------- /parser/grammar.peg: -------------------------------------------------------------------------------- 1 | { 2 | package parser 3 | 4 | import ( 5 | "bytes" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | type namespace struct { 11 | scope string 12 | namespace string 13 | } 14 | 15 | type typeDef struct { 16 | name string 17 | typ *Type 18 | } 19 | 20 | type exception *Struct 21 | 22 | type include string 23 | 24 | func toIfaceSlice(v interface{}) []interface{} { 25 | if v == nil { 26 | return nil 27 | } 28 | return v.([]interface{}) 29 | } 30 | 31 | func ifaceSliceToString(v interface{}) string { 32 | ifs := toIfaceSlice(v) 33 | b := make([]byte, len(ifs)) 34 | for i, v := range ifs { 35 | b[i] = v.([]uint8)[0] 36 | } 37 | return string(b) 38 | } 39 | } 40 | 41 | Grammar ← __ statements:( Statement __ )* (EOF / SyntaxError) { 42 | thrift := &Thrift{ 43 | Includes: make(map[string]string), 44 | Namespaces: make(map[string]string), 45 | Typedefs: make(map[string]*Type), 46 | Constants: make(map[string]*Constant), 47 | Enums: make(map[string]*Enum), 48 | Structs: make(map[string]*Struct), 49 | Exceptions: make(map[string]*Struct), 50 | Services: make(map[string]*Service), 51 | } 52 | stmts := toIfaceSlice(statements) 53 | for _, st := range stmts { 54 | switch v := st.([]interface{})[0].(type) { 55 | case *namespace: 56 | thrift.Namespaces[v.scope] = v.namespace 57 | case *Constant: 58 | thrift.Constants[v.Name] = v 59 | case *Enum: 60 | thrift.Enums[v.Name] = v 61 | case *typeDef: 62 | thrift.Typedefs[v.name] = v.typ 63 | case *Struct: 64 | thrift.Structs[v.Name] = v 65 | case exception: 66 | thrift.Exceptions[v.Name] = (*Struct)(v) 67 | case *Service: 68 | thrift.Services[v.Name] = v 69 | case include: 70 | name := string(v) 71 | if ix := strings.LastIndex(name, "."); ix > 0 { 72 | name = name[:ix] 73 | } 74 | thrift.Includes[name] = string(v) 75 | default: 76 | return nil, fmt.Errorf("parser: unknown value %#v", v) 77 | } 78 | } 79 | return thrift, nil 80 | } 81 | 82 | SyntaxError ← . { 83 | return nil, errors.New("parser: syntax error") 84 | } 85 | 86 | Include ← "include" _ file:Literal EOS { 87 | return include(file.(string)), nil 88 | } 89 | 90 | Statement ← Include / Namespace / Const / Enum / TypeDef / Struct / Exception / Service 91 | 92 | Namespace ← "namespace" _ scope:[a-z.-]+ _ ns:Identifier EOS { 93 | return &namespace{ 94 | scope: ifaceSliceToString(scope), 95 | namespace: string(ns.(Identifier)), 96 | }, nil 97 | } 98 | 99 | Const ← "const" _ typ:FieldType _ name:Identifier _ "=" _ value:ConstValue EOS { 100 | return &Constant{ 101 | Name: string(name.(Identifier)), 102 | Type: typ.(*Type), 103 | Value: value, 104 | }, nil 105 | } 106 | 107 | Enum ← "enum" _ name:Identifier _ '{' __ values:(EnumValue __)* '}' EOS { 108 | vs := toIfaceSlice(values) 109 | en := &Enum{ 110 | Name: string(name.(Identifier)), 111 | Values: make(map[string]*EnumValue, len(vs)), 112 | } 113 | // Assigns numbers in order. This will behave badly if some values are 114 | // defined and other are not, but I think that's ok since that's a silly 115 | // thing to do. 116 | next := 0 117 | for _, v := range vs { 118 | ev := v.([]interface{})[0].(*EnumValue) 119 | if ev.Value < 0 { 120 | ev.Value = next 121 | } 122 | if ev.Value >= next { 123 | next = ev.Value + 1 124 | } 125 | en.Values[ev.Name] = ev 126 | } 127 | return en, nil 128 | } 129 | 130 | EnumValue ← name:Identifier _ value:('=' _ IntConstant)? ListSeparator? { 131 | ev := &EnumValue{ 132 | Name: string(name.(Identifier)), 133 | Value: -1, 134 | } 135 | if value != nil { 136 | ev.Value = int(value.([]interface{})[2].(int64)) 137 | } 138 | return ev, nil 139 | } 140 | 141 | TypeDef ← "typedef" _ typ:FieldType _ name:Identifier EOS { 142 | return &typeDef{ 143 | name: string(name.(Identifier)), 144 | typ: typ.(*Type), 145 | }, nil 146 | } 147 | 148 | Struct ← "struct" _ st:StructLike { return st.(*Struct), nil } 149 | Exception ← "exception" _ st:StructLike { return exception(st.(*Struct)), nil } 150 | StructLike ← name:Identifier _ '{' __ fields:FieldList '}' EOS { 151 | st := &Struct{ 152 | Name: string(name.(Identifier)), 153 | } 154 | if fields != nil { 155 | st.Fields = fields.([]*Field) 156 | } 157 | return st, nil 158 | } 159 | 160 | FieldList ← fields:(Field __)* { 161 | fs := fields.([]interface{}) 162 | flds := make([]*Field, len(fs)) 163 | for i, f := range fs { 164 | flds[i] = f.([]interface{})[0].(*Field) 165 | } 166 | return flds, nil 167 | } 168 | 169 | Field ← id:IntConstant _ ':' _ req:FieldReq? _ typ:FieldType _ name:Identifier _ def:('=' _ ConstValue)? ListSeparator? { 170 | f := &Field{ 171 | ID : int(id.(int64)), 172 | Name : string(name.(Identifier)), 173 | Type : typ.(*Type), 174 | } 175 | if req != nil && !req.(bool) { 176 | f.Optional = true 177 | } 178 | if def != nil { 179 | f.Default = def.([]interface{})[2] 180 | } 181 | return f, nil 182 | } 183 | 184 | FieldReq ← ("required" / "optional") { 185 | return !bytes.Equal(c.text, []byte("optional")), nil 186 | } 187 | 188 | Service ← "service" _ name:Identifier _ extends:("extends" __ Identifier __)? __ '{' __ methods:(Function __)* ('}' / EndOfServiceError) EOS { 189 | ms := methods.([]interface{}) 190 | svc := &Service{ 191 | Name: string(name.(Identifier)), 192 | Methods: make(map[string]*Method, len(ms)), 193 | } 194 | if extends != nil { 195 | svc.Extends = string(extends.([]interface{})[2].(Identifier)) 196 | } 197 | for _, m := range ms { 198 | mt := m.([]interface{})[0].(*Method) 199 | svc.Methods[mt.Name] = mt 200 | } 201 | return svc, nil 202 | } 203 | EndOfServiceError ← . { 204 | return nil, errors.New("parser: expected end of service") 205 | } 206 | 207 | Function ← oneway:("oneway" __)? typ:FunctionType __ name:Identifier _ '(' __ arguments:FieldList ')' __ exceptions:Throws? ListSeparator? { 208 | m := &Method{ 209 | Name: string(name.(Identifier)), 210 | } 211 | t := typ.(*Type) 212 | if t.Name != "void" { 213 | m.ReturnType = t 214 | } 215 | if oneway != nil { 216 | m.Oneway = true 217 | } 218 | if arguments != nil { 219 | m.Arguments = arguments.([]*Field) 220 | } 221 | if exceptions != nil { 222 | m.Exceptions = exceptions.([]*Field) 223 | for _, e := range m.Exceptions { 224 | e.Optional = true 225 | } 226 | } 227 | return m, nil 228 | } 229 | 230 | FunctionType ← typ:("void" / FieldType) { 231 | if t, ok := typ.(*Type); ok { 232 | return t, nil 233 | } 234 | return &Type{Name: string(c.text)}, nil 235 | } 236 | 237 | Throws ← "throws" __ '(' __ exceptions:FieldList ')' { 238 | return exceptions, nil 239 | } 240 | 241 | FieldType ← typ:(BaseType / ContainerType / Identifier) { 242 | if t, ok := typ.(Identifier); ok { 243 | return &Type{Name: string(t)}, nil 244 | } 245 | return typ, nil 246 | } 247 | 248 | DefinitionType ← typ:(BaseType / ContainerType) { 249 | return typ, nil 250 | } 251 | 252 | BaseType ← ("bool" / "byte" / "i16" / "i32" / "i64" / "double" / "string" / "binary" ) { 253 | return &Type{Name: string(c.text)}, nil 254 | } 255 | 256 | ContainerType ← typ:(MapType / SetType / ListType) { 257 | return typ, nil 258 | } 259 | 260 | MapType ← CppType? "map<" WS key:FieldType WS "," WS value:FieldType WS ">" { 261 | return &Type{ 262 | Name: "map", 263 | KeyType: key.(*Type), 264 | ValueType: value.(*Type), 265 | }, nil 266 | } 267 | 268 | SetType ← CppType? "set<" WS typ:FieldType WS ">" { 269 | return &Type{ 270 | Name: "set", 271 | ValueType: typ.(*Type), 272 | }, nil 273 | } 274 | 275 | ListType ← "list<" WS typ:FieldType WS ">" { 276 | return &Type{ 277 | Name: "list", 278 | ValueType: typ.(*Type), 279 | }, nil 280 | } 281 | 282 | CppType ← "cpp_type" cppType:Literal { 283 | return cppType, nil 284 | } 285 | 286 | ConstValue ← Literal / DoubleConstant / IntConstant / ConstMap / ConstList / Identifier 287 | 288 | IntConstant ← [-+]? Digit+ { 289 | return strconv.ParseInt(string(c.text), 10, 64) 290 | } 291 | 292 | DoubleConstant ← [+-]? Digit* '.' Digit* ( ['Ee'] IntConstant )? { 293 | return strconv.ParseFloat(string(c.text), 64) 294 | } 295 | 296 | ConstList ← '[' __ values:(ConstValue __ ListSeparator? __)* __ ']' { 297 | valueSlice := values.([]interface{}) 298 | vs := make([]interface{}, len(valueSlice)) 299 | for i, v := range valueSlice { 300 | vs[i] = v.([]interface{})[0] 301 | } 302 | return vs, nil 303 | } 304 | 305 | ConstMap ← '{' __ values:(ConstValue __ ':' __ ConstValue __ (',' / &'}') __)* '}' { 306 | if values == nil { 307 | return nil, nil 308 | } 309 | vals := values.([]interface{}) 310 | kvs := make([]KeyValue, len(vals)) 311 | for i, kv := range vals { 312 | v := kv.([]interface{}) 313 | kvs[i] = KeyValue{ 314 | Key: v[0], 315 | Value: v[4], 316 | } 317 | } 318 | return kvs, nil 319 | } 320 | 321 | Literal ← (('"' (`\"` / [^"])* '"') / ('\'' (`\'` / [^'])* '\'')) { 322 | if len(c.text) != 0 && c.text[0] == '\'' { 323 | return strconv.Unquote(`"` + strings.Replace(string(c.text[1:len(c.text)-1]), `\'`, `'`, -1) + `"`) 324 | } 325 | return strconv.Unquote(string(c.text)) 326 | } 327 | 328 | Identifier ← (Letter / '_')+ (Letter / Digit / [._])* { 329 | return Identifier(string(c.text)), nil 330 | } 331 | 332 | ListSeparator ← [,;] 333 | Letter ← [A-Za-z] 334 | Digit ← [0-9] 335 | 336 | // 337 | 338 | SourceChar ← . 339 | Comment ← MultiLineComment / SingleLineComment 340 | MultiLineComment ← "/*" ( !"*/" SourceChar )* "*/" 341 | MultiLineCommentNoLineTerminator ← "/*" ( !( "*/" / EOL ) SourceChar )* "*/" 342 | SingleLineComment ← ("//" ( !EOL SourceChar )*) / ("#" ( !EOL SourceChar )*) 343 | 344 | __ ← ( Whitespace / EOL / Comment )* 345 | _ ← ( Whitespace / MultiLineCommentNoLineTerminator )* 346 | WS ← Whitespace* 347 | 348 | Whitespace ← [ \t\r] 349 | EOL ← '\n' 350 | EOS ← __ ';' / _ SingleLineComment? EOL / __ EOF 351 | 352 | EOF ← !. 353 | -------------------------------------------------------------------------------- /thrift/protocol_binary_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2015 Samuel Stauffer. All rights reserved. 2 | // Use of this source code is governed by a 3-clause BSD 3 | // license that can be found in the LICENSE file. 4 | 5 | package thrift 6 | 7 | import ( 8 | "bytes" 9 | "testing" 10 | ) 11 | 12 | func testProtocol(t *testing.T, r ProtocolReader, w ProtocolWriter) { 13 | // b := &bytes.Buffer{} 14 | // r := pr.NewProtocolReader(b) 15 | // w := pr.NewProtocolWriter(b) 16 | 17 | if err := w.WriteBool(true); err != nil { 18 | t.Fatalf("write bool true failed: %+v", err) 19 | } 20 | if b, err := r.ReadBool(); err != nil { 21 | t.Fatalf("read bool true failed: %+v", err) 22 | } else if !b { 23 | t.Fatal("read bool true returned false") 24 | } 25 | 26 | if err := w.WriteBool(false); err != nil { 27 | t.Fatalf("write bool false failed: %+v", err) 28 | } 29 | if b, err := r.ReadBool(); err != nil { 30 | t.Fatalf("read bool false failed: %+v", err) 31 | } else if b { 32 | t.Fatal("read bool false returned true") 33 | } 34 | 35 | if err := w.WriteI16(1234); err != nil { 36 | t.Fatalf("write i16 failed: %+v", err) 37 | } 38 | if v, err := r.ReadI16(); err != nil { 39 | t.Fatalf("read i16 failed: %+v", err) 40 | } else if v != 1234 { 41 | t.Fatalf("read i16 returned %d expected 1234", v) 42 | } 43 | 44 | if err := w.WriteI32(-1234); err != nil { 45 | t.Fatalf("write i32 failed: %+v", err) 46 | } 47 | if v, err := r.ReadI32(); err != nil { 48 | t.Fatalf("read i32 failed: %+v", err) 49 | } else if v != -1234 { 50 | t.Fatalf("read i32 returned %d expected -1234", v) 51 | } 52 | 53 | if err := w.WriteI64(-1234); err != nil { 54 | t.Fatalf("write i64 failed: %+v", err) 55 | } 56 | if v, err := r.ReadI64(); err != nil { 57 | t.Fatalf("read i64 failed: %+v", err) 58 | } else if v != -1234 { 59 | t.Fatalf("read i64 returned %d expected -1234", v) 60 | } 61 | 62 | if err := w.WriteDouble(-0.1234); err != nil { 63 | t.Fatalf("write double failed: %+v", err) 64 | } 65 | if v, err := r.ReadDouble(); err != nil { 66 | t.Fatalf("read double failed: %+v", err) 67 | } else if v != -0.1234 { 68 | t.Fatalf("read double returned %.4f expected -0.1234", v) 69 | } 70 | 71 | testString := "012345" 72 | for i := 0; i < 2; i++ { 73 | if err := w.WriteString(testString); err != nil { 74 | t.Fatalf("write string failed: %+v", err) 75 | } 76 | if v, err := r.ReadString(); err != nil { 77 | t.Fatalf("read string failed: %+v", err) 78 | } else if v != testString { 79 | t.Fatalf("read string returned %s expected '%s'", v, testString) 80 | } 81 | testString += "012345" 82 | } 83 | 84 | // Write a message 85 | 86 | if err := w.WriteMessageBegin("msgName", 2, 123); err != nil { 87 | t.Fatalf("WriteMessageBegin failed: %+v", err) 88 | } 89 | if err := w.WriteStructBegin("struct"); err != nil { 90 | t.Fatalf("WriteStructBegin failed: %+v", err) 91 | } 92 | 93 | if err := w.WriteFieldBegin("boolTrue", TypeBool, 1); err != nil { 94 | t.Fatalf("WriteFieldBegin failed: %+v", err) 95 | } 96 | if err := w.WriteBool(true); err != nil { 97 | t.Fatalf("WriteBool(true) failed: %+v", err) 98 | } 99 | if err := w.WriteFieldEnd(); err != nil { 100 | t.Fatalf("WriteFieldEnd failed: %+v", err) 101 | } 102 | 103 | if err := w.WriteFieldBegin("boolFalse", TypeBool, 3); err != nil { 104 | t.Fatalf("WriteFieldBegin failed: %+v", err) 105 | } 106 | if err := w.WriteBool(false); err != nil { 107 | t.Fatalf("WriteBool(false) failed: %+v", err) 108 | } 109 | if err := w.WriteFieldEnd(); err != nil { 110 | t.Fatalf("WriteFieldEnd failed: %+v", err) 111 | } 112 | 113 | if err := w.WriteFieldBegin("str", TypeString, 2); err != nil { 114 | t.Fatalf("WriteFieldBegin failed: %+v", err) 115 | } 116 | if err := w.WriteString("foo"); err != nil { 117 | t.Fatalf("WriteString failed: %+v", err) 118 | } 119 | if err := w.WriteFieldEnd(); err != nil { 120 | t.Fatalf("WriteFieldEnd failed: %+v", err) 121 | } 122 | 123 | if err := w.WriteFieldStop(); err != nil { 124 | t.Fatalf("WriteStructEnd failed: %+v", err) 125 | } 126 | if err := w.WriteStructEnd(); err != nil { 127 | t.Fatalf("WriteStructEnd failed: %+v", err) 128 | } 129 | if err := w.WriteMessageEnd(); err != nil { 130 | t.Fatalf("WriteMessageEnd failed: %+v", err) 131 | } 132 | 133 | // Read the message 134 | 135 | if name, mtype, seqID, err := r.ReadMessageBegin(); err != nil { 136 | t.Fatalf("ReadMessageBegin failed: %+v", err) 137 | } else if name != "msgName" { 138 | t.Fatalf("ReadMessageBegin name mismatch: %s != %s", name, "msgName") 139 | } else if mtype != 2 { 140 | t.Fatalf("ReadMessageBegin type mismatch: %d != %d", mtype, 2) 141 | } else if seqID != 123 { 142 | t.Fatalf("ReadMessageBegin seqID mismatch: %d != %d", seqID, 123) 143 | } 144 | if err := r.ReadStructBegin(); err != nil { 145 | t.Fatalf("ReadStructBegin failed: %+v", err) 146 | } 147 | 148 | if fieldType, id, err := r.ReadFieldBegin(); err != nil { 149 | t.Fatalf("ReadFieldBegin failed: %+v", err) 150 | } else if fieldType != TypeBool { 151 | t.Fatalf("ReadFieldBegin type mismatch: %d != %d", fieldType, TypeBool) 152 | } else if id != 1 { 153 | t.Fatalf("ReadFieldBegin id mismatch: %d != %d", id, 1) 154 | } 155 | if v, err := r.ReadBool(); err != nil { 156 | t.Fatalf("ReaBool failed: %+v", err) 157 | } else if !v { 158 | t.Fatalf("ReadBool value mistmatch %+v != %+v", v, true) 159 | } 160 | if err := r.ReadFieldEnd(); err != nil { 161 | t.Fatalf("ReadFieldEnd failed: %+v", err) 162 | } 163 | 164 | if fieldType, id, err := r.ReadFieldBegin(); err != nil { 165 | t.Fatalf("ReadFieldBegin failed: %+v", err) 166 | } else if fieldType != TypeBool { 167 | t.Fatalf("ReadFieldBegin type mismatch: %d != %d", fieldType, TypeBool) 168 | } else if id != 3 { 169 | t.Fatalf("ReadFieldBegin id mismatch: %d != %d", id, 3) 170 | } 171 | if v, err := r.ReadBool(); err != nil { 172 | t.Fatalf("ReaBool failed: %+v", err) 173 | } else if v { 174 | t.Fatalf("ReadBool value mistmatch %+v != %+v", v, false) 175 | } 176 | if err := r.ReadFieldEnd(); err != nil { 177 | t.Fatalf("ReadFieldEnd failed: %+v", err) 178 | } 179 | 180 | if fieldType, id, err := r.ReadFieldBegin(); err != nil { 181 | t.Fatalf("ReadFieldBegin failed: %+v", err) 182 | } else if fieldType != TypeString { 183 | t.Fatalf("ReadFieldBegin type mismatch: %d != %d", fieldType, TypeString) 184 | } else if id != 2 { 185 | t.Fatalf("ReadFieldBegin id mismatch: %d != %d", id, 2) 186 | } 187 | if v, err := r.ReadString(); err != nil { 188 | t.Fatalf("ReadString failed: %+v", err) 189 | } else if v != "foo" { 190 | t.Fatalf("ReadString value mistmatch %s != %s", v, "foo") 191 | } 192 | if err := r.ReadFieldEnd(); err != nil { 193 | t.Fatalf("ReadFieldEnd failed: %+v", err) 194 | } 195 | 196 | if err := r.ReadStructEnd(); err != nil { 197 | t.Fatalf("ReadStructEnd failed: %+v", err) 198 | } 199 | if err := r.ReadMessageEnd(); err != nil { 200 | t.Fatalf("ReadMessageEnd failed: %+v", err) 201 | } 202 | } 203 | 204 | func TestBinaryProtocolBadStringLength(t *testing.T) { 205 | b := &bytes.Buffer{} 206 | w := NewBinaryProtocolWriter(b, true) 207 | r := NewBinaryProtocolReader(b, false) 208 | 209 | // zero string length 210 | if err := w.WriteI32(0); err != nil { 211 | t.Fatal(err) 212 | } 213 | if st, err := r.ReadString(); err != nil { 214 | t.Fatal(err) 215 | } else if st != "" { 216 | t.Fatal("BinaryProtocol.ReadString didn't return an empty string given a length of 0") 217 | } 218 | 219 | // negative string length 220 | if err := w.WriteI32(-1); err != nil { 221 | t.Fatal(err) 222 | } 223 | if _, err := r.ReadString(); err == nil { 224 | t.Fatal("BinaryProtocol.ReadString didn't return an error given a negative length") 225 | } 226 | } 227 | 228 | func TestBinaryProtocol(t *testing.T) { 229 | b := &bytes.Buffer{} 230 | testProtocol(t, NewBinaryProtocolReader(b, false), NewBinaryProtocolWriter(b, true)) 231 | b.Reset() 232 | testProtocol(t, NewBinaryProtocolReader(b, false), NewBinaryProtocolWriter(b, false)) 233 | b.Reset() 234 | testProtocol(t, NewBinaryProtocolReader(b, true), NewBinaryProtocolWriter(b, true)) 235 | } 236 | 237 | func BenchmarkBinaryProtocolReadByte(b *testing.B) { 238 | buf := &loopingReader{} 239 | w := NewBinaryProtocolWriter(buf, true) 240 | r := NewBinaryProtocolReader(buf, false) 241 | w.WriteByte(123) 242 | for i := 0; i < b.N; i++ { 243 | r.ReadByte() 244 | } 245 | } 246 | 247 | func BenchmarkBinaryProtocolReadI32(b *testing.B) { 248 | buf := &loopingReader{} 249 | w := NewBinaryProtocolWriter(buf, true) 250 | r := NewBinaryProtocolReader(buf, false) 251 | w.WriteI32(1234567890) 252 | for i := 0; i < b.N; i++ { 253 | r.ReadI32() 254 | } 255 | } 256 | 257 | func BenchmarkBinaryProtocolWriteByte(b *testing.B) { 258 | buf := nullWriter(0) 259 | w := NewBinaryProtocolWriter(buf, true) 260 | for i := 0; i < b.N; i++ { 261 | w.WriteByte(1) 262 | } 263 | } 264 | 265 | func BenchmarkBinaryProtocolWriteI32(b *testing.B) { 266 | buf := nullWriter(0) 267 | w := NewBinaryProtocolWriter(buf, true) 268 | for i := 0; i < b.N; i++ { 269 | w.WriteI32(1) 270 | } 271 | } 272 | 273 | func BenchmarkBinaryProtocolWriteString4(b *testing.B) { 274 | buf := nullWriter(0) 275 | w := NewBinaryProtocolWriter(buf, true) 276 | for i := 0; i < b.N; i++ { 277 | w.WriteString("test") 278 | } 279 | } 280 | 281 | func BenchmarkBinaryProtocolWriteFullMessage(b *testing.B) { 282 | buf := nullWriter(0) 283 | w := NewBinaryProtocolWriter(buf, true) 284 | for i := 0; i < b.N; i++ { 285 | w.WriteMessageBegin("", 2, 123) 286 | w.WriteStructBegin("") 287 | w.WriteFieldBegin("", TypeBool, 1) 288 | w.WriteBool(true) 289 | w.WriteFieldEnd() 290 | w.WriteFieldBegin("", TypeBool, 3) 291 | w.WriteBool(false) 292 | w.WriteFieldEnd() 293 | w.WriteFieldBegin("", TypeString, 2) 294 | w.WriteString("foo") 295 | w.WriteFieldEnd() 296 | w.WriteFieldStop() 297 | w.WriteStructEnd() 298 | w.WriteMessageEnd() 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /thrift/protocol_binary.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2015 Samuel Stauffer. All rights reserved. 2 | // Use of this source code is governed by a 3-clause BSD 3 | // license that can be found in the LICENSE file. 4 | 5 | package thrift 6 | 7 | import ( 8 | "encoding/binary" 9 | "io" 10 | "math" 11 | ) 12 | 13 | const ( 14 | versionMask uint32 = 0xffff0000 15 | version1 uint32 = 0x80010000 16 | typeMask uint32 = 0x000000ff 17 | ) 18 | 19 | const ( 20 | maxMessageNameSize = 128 21 | ) 22 | 23 | type binaryProtocolWriter struct { 24 | w io.Writer 25 | strict bool 26 | buf []byte 27 | } 28 | 29 | type binaryProtocolReader struct { 30 | r io.Reader 31 | strict bool 32 | buf []byte 33 | } 34 | 35 | var BinaryProtocol = NewProtocolBuilder( 36 | func(r io.Reader) ProtocolReader { return NewBinaryProtocolReader(r, false) }, 37 | func(w io.Writer) ProtocolWriter { return NewBinaryProtocolWriter(w, true) }, 38 | ) 39 | 40 | func NewBinaryProtocolWriter(w io.Writer, strict bool) ProtocolWriter { 41 | p := &binaryProtocolWriter{ 42 | w: w, 43 | strict: strict, 44 | buf: make([]byte, 32), 45 | } 46 | return p 47 | } 48 | 49 | func NewBinaryProtocolReader(r io.Reader, strict bool) ProtocolReader { 50 | p := &binaryProtocolReader{ 51 | r: r, 52 | strict: strict, 53 | buf: make([]byte, 32), 54 | } 55 | return p 56 | } 57 | 58 | func (p *binaryProtocolWriter) WriteMessageBegin(name string, messageType byte, seqid int32) error { 59 | if p.strict { 60 | if err := p.WriteI32(int32(version1 | uint32(messageType))); err != nil { 61 | return err 62 | } 63 | if err := p.WriteString(name); err != nil { 64 | return err 65 | } 66 | } else { 67 | if err := p.WriteString(name); err != nil { 68 | return err 69 | } 70 | if err := p.WriteByte(messageType); err != nil { 71 | return err 72 | } 73 | } 74 | return p.WriteI32(seqid) 75 | } 76 | 77 | func (p *binaryProtocolWriter) WriteMessageEnd() error { 78 | return nil 79 | } 80 | 81 | func (p *binaryProtocolWriter) WriteStructBegin(name string) error { 82 | return nil 83 | } 84 | 85 | func (p *binaryProtocolWriter) WriteStructEnd() error { 86 | return nil 87 | } 88 | 89 | func (p *binaryProtocolWriter) WriteFieldBegin(name string, fieldType byte, id int16) error { 90 | if err := p.WriteByte(fieldType); err != nil { 91 | return err 92 | } 93 | return p.WriteI16(id) 94 | } 95 | 96 | func (p *binaryProtocolWriter) WriteFieldEnd() error { 97 | return nil 98 | } 99 | 100 | func (p *binaryProtocolWriter) WriteFieldStop() error { 101 | return p.WriteByte(TypeStop) 102 | } 103 | 104 | func (p *binaryProtocolWriter) WriteMapBegin(keyType byte, valueType byte, size int) error { 105 | if err := p.WriteByte(keyType); err != nil { 106 | return err 107 | } 108 | if err := p.WriteByte(valueType); err != nil { 109 | return err 110 | } 111 | return p.WriteI32(int32(size)) 112 | } 113 | 114 | func (p *binaryProtocolWriter) WriteMapEnd() error { 115 | return nil 116 | } 117 | 118 | func (p *binaryProtocolWriter) WriteListBegin(elementType byte, size int) error { 119 | if err := p.WriteByte(elementType); err != nil { 120 | return err 121 | } 122 | return p.WriteI32(int32(size)) 123 | } 124 | 125 | func (p *binaryProtocolWriter) WriteListEnd() error { 126 | return nil 127 | } 128 | 129 | func (p *binaryProtocolWriter) WriteSetBegin(elementType byte, size int) error { 130 | if err := p.WriteByte(elementType); err != nil { 131 | return err 132 | } 133 | return p.WriteI32(int32(size)) 134 | } 135 | 136 | func (p *binaryProtocolWriter) WriteSetEnd() error { 137 | return nil 138 | } 139 | 140 | func (p *binaryProtocolWriter) WriteBool(value bool) error { 141 | if value { 142 | return p.WriteByte(1) 143 | } 144 | return p.WriteByte(0) 145 | } 146 | 147 | func (p *binaryProtocolWriter) WriteByte(value byte) error { 148 | b := p.buf 149 | if b == nil { 150 | b = []byte{value} 151 | } else { 152 | b[0] = value 153 | } 154 | _, err := p.w.Write(b[:1]) 155 | return err 156 | } 157 | 158 | func (p *binaryProtocolWriter) WriteI16(value int16) error { 159 | b := p.buf 160 | if b == nil { 161 | b = []byte{0, 0} 162 | } 163 | binary.BigEndian.PutUint16(b, uint16(value)) 164 | _, err := p.w.Write(b[:2]) 165 | return err 166 | } 167 | 168 | func (p *binaryProtocolWriter) WriteI32(value int32) error { 169 | b := p.buf 170 | if b == nil { 171 | b = []byte{0, 0, 0, 0} 172 | } 173 | binary.BigEndian.PutUint32(b, uint32(value)) 174 | _, err := p.w.Write(b[:4]) 175 | return err 176 | } 177 | 178 | func (p *binaryProtocolWriter) WriteI64(value int64) error { 179 | b := p.buf 180 | if b == nil { 181 | b = []byte{0, 0, 0, 0, 0, 0, 0, 0} 182 | } 183 | binary.BigEndian.PutUint64(b, uint64(value)) 184 | _, err := p.w.Write(b[:8]) 185 | return err 186 | } 187 | 188 | func (p *binaryProtocolWriter) WriteDouble(value float64) error { 189 | b := p.buf 190 | if b == nil { 191 | b = []byte{0, 0, 0, 0, 0, 0, 0, 0} 192 | } 193 | binary.BigEndian.PutUint64(b, math.Float64bits(value)) 194 | _, err := p.w.Write(b[:8]) 195 | return err 196 | } 197 | 198 | func (p *binaryProtocolWriter) WriteString(value string) error { 199 | if len(value) <= len(p.buf) { 200 | if err := p.WriteI32(int32(len(value))); err != nil { 201 | return err 202 | } 203 | n := copy(p.buf, value) 204 | _, err := p.w.Write(p.buf[:n]) 205 | return err 206 | } 207 | return p.WriteBytes([]byte(value)) 208 | } 209 | 210 | func (p *binaryProtocolWriter) WriteBytes(value []byte) error { 211 | if err := p.WriteI32(int32(len(value))); err != nil { 212 | return err 213 | } 214 | _, err := p.w.Write(value) 215 | return err 216 | } 217 | 218 | func (p *binaryProtocolReader) ReadMessageBegin() (name string, messageType byte, seqid int32, err error) { 219 | size, e := p.ReadI32() 220 | if e != nil { 221 | err = e 222 | return 223 | } 224 | if size < 0 { 225 | version := uint32(size) & versionMask 226 | if version != version1 { 227 | err = ProtocolError{"BinaryProtocol", "bad version in ReadMessageBegin"} 228 | return 229 | } 230 | messageType = byte(uint32(size) & typeMask) 231 | if name, err = p.ReadString(); err != nil { 232 | return 233 | } 234 | } else { 235 | if p.strict { 236 | err = ProtocolError{"BinaryProtocol", "no protocol version header"} 237 | return 238 | } 239 | if size > maxMessageNameSize { 240 | err = ProtocolError{"BinaryProtocol", "message name exceeds max size"} 241 | return 242 | } 243 | nameBytes := make([]byte, size) 244 | if _, err = p.r.Read(nameBytes); err != nil { 245 | return 246 | } 247 | name = string(nameBytes) 248 | if messageType, err = p.ReadByte(); err != nil { 249 | return 250 | } 251 | } 252 | seqid, err = p.ReadI32() 253 | return 254 | } 255 | 256 | func (p *binaryProtocolReader) ReadMessageEnd() error { 257 | return nil 258 | } 259 | 260 | func (p *binaryProtocolReader) ReadStructBegin() error { 261 | return nil 262 | } 263 | 264 | func (p *binaryProtocolReader) ReadStructEnd() error { 265 | return nil 266 | } 267 | 268 | func (p *binaryProtocolReader) ReadFieldBegin() (fieldType byte, id int16, err error) { 269 | if fieldType, err = p.ReadByte(); err != nil || fieldType == TypeStop { 270 | return 271 | } 272 | id, err = p.ReadI16() 273 | return 274 | } 275 | 276 | func (p *binaryProtocolReader) ReadFieldEnd() error { 277 | return nil 278 | } 279 | 280 | func (p *binaryProtocolReader) ReadMapBegin() (keyType byte, valueType byte, size int, err error) { 281 | if keyType, err = p.ReadByte(); err != nil { 282 | return 283 | } 284 | if valueType, err = p.ReadByte(); err != nil { 285 | return 286 | } 287 | var sz int32 288 | sz, err = p.ReadI32() 289 | size = int(sz) 290 | return 291 | } 292 | 293 | func (p *binaryProtocolReader) ReadMapEnd() error { 294 | return nil 295 | } 296 | 297 | func (p *binaryProtocolReader) ReadListBegin() (elementType byte, size int, err error) { 298 | if elementType, err = p.ReadByte(); err != nil { 299 | return 300 | } 301 | var sz int32 302 | sz, err = p.ReadI32() 303 | size = int(sz) 304 | return 305 | } 306 | 307 | func (p *binaryProtocolReader) ReadListEnd() error { 308 | return nil 309 | } 310 | 311 | func (p *binaryProtocolReader) ReadSetBegin() (elementType byte, size int, err error) { 312 | if elementType, err = p.ReadByte(); err != nil { 313 | return 314 | } 315 | var sz int32 316 | sz, err = p.ReadI32() 317 | size = int(sz) 318 | return 319 | } 320 | 321 | func (p *binaryProtocolReader) ReadSetEnd() error { 322 | return nil 323 | } 324 | 325 | func (p *binaryProtocolReader) ReadBool() (bool, error) { 326 | if b, e := p.ReadByte(); e != nil { 327 | return false, e 328 | } else if b != 0 { 329 | return true, nil 330 | } 331 | return false, nil 332 | } 333 | 334 | func (p *binaryProtocolReader) ReadByte() (value byte, err error) { 335 | _, err = io.ReadFull(p.r, p.buf[:1]) 336 | value = p.buf[0] 337 | return 338 | } 339 | 340 | func (p *binaryProtocolReader) ReadI16() (value int16, err error) { 341 | _, err = io.ReadFull(p.r, p.buf[:2]) 342 | value = int16(binary.BigEndian.Uint16(p.buf)) 343 | return 344 | } 345 | 346 | func (p *binaryProtocolReader) ReadI32() (value int32, err error) { 347 | _, err = io.ReadFull(p.r, p.buf[:4]) 348 | value = int32(binary.BigEndian.Uint32(p.buf)) 349 | return 350 | } 351 | 352 | func (p *binaryProtocolReader) ReadI64() (value int64, err error) { 353 | _, err = io.ReadFull(p.r, p.buf[:8]) 354 | value = int64(binary.BigEndian.Uint64(p.buf)) 355 | return 356 | } 357 | 358 | func (p *binaryProtocolReader) ReadDouble() (value float64, err error) { 359 | _, err = io.ReadFull(p.r, p.buf[:8]) 360 | value = math.Float64frombits(binary.BigEndian.Uint64(p.buf)) 361 | return 362 | } 363 | 364 | func (p *binaryProtocolReader) ReadString() (string, error) { 365 | ln, err := p.ReadI32() 366 | if err != nil || ln == 0 { 367 | return "", err 368 | } 369 | if ln < 0 { 370 | return "", ProtocolError{"BinaryProtocol", "negative length while reading string"} 371 | } 372 | b := p.buf 373 | if int(ln) > len(b) { 374 | b = make([]byte, ln) 375 | } else { 376 | b = b[:ln] 377 | } 378 | if _, err := io.ReadFull(p.r, b); err != nil { 379 | return "", err 380 | } 381 | return string(b), nil 382 | } 383 | 384 | func (p *binaryProtocolReader) ReadBytes() ([]byte, error) { 385 | ln, err := p.ReadI32() 386 | if err != nil || ln == 0 { 387 | return nil, err 388 | } 389 | if ln < 0 { 390 | return nil, ProtocolError{"BinaryProtocol", "negative length while reading bytes"} 391 | } 392 | b := make([]byte, ln) 393 | if _, err := io.ReadFull(p.r, b); err != nil { 394 | return nil, err 395 | } 396 | return b, nil 397 | } 398 | -------------------------------------------------------------------------------- /thrift/thrift.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2015 Samuel Stauffer. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package thrift 6 | 7 | import ( 8 | "errors" 9 | "fmt" 10 | "reflect" 11 | "sync" 12 | ) 13 | 14 | // Type identifiers for serialized Thrift 15 | const ( 16 | TypeStop = 0 17 | TypeVoid = 1 18 | TypeBool = 2 19 | TypeByte = 3 20 | TypeI08 = 3 21 | TypeDouble = 4 22 | TypeI16 = 6 23 | TypeI32 = 8 24 | TypeI64 = 10 25 | TypeString = 11 26 | TypeUtf7 = 11 27 | TypeStruct = 12 28 | TypeMap = 13 29 | TypeSet = 14 30 | TypeList = 15 31 | TypeUtf8 = 16 32 | TypeUtf16 = 17 33 | ) 34 | 35 | var TypeNames = map[int]string{ 36 | TypeStop: "stop", 37 | TypeVoid: "void", 38 | TypeBool: "bool", 39 | TypeByte: "byte", 40 | TypeDouble: "double", 41 | TypeI16: "i16", 42 | TypeI32: "i32", 43 | TypeI64: "i64", 44 | TypeString: "string", 45 | TypeStruct: "struct", 46 | TypeMap: "map", 47 | TypeSet: "set", 48 | TypeList: "list", 49 | TypeUtf8: "utf8", 50 | TypeUtf16: "utf16", 51 | } 52 | 53 | // Message types for RPC 54 | const ( 55 | MessageTypeCall = 1 56 | MessageTypeReply = 2 57 | MessageTypeException = 3 58 | MessageTypeOneway = 4 59 | ) 60 | 61 | // Exception types for RPC responses 62 | const ( 63 | ExceptionUnknown = 0 64 | ExceptionUnknownMethod = 1 65 | ExceptionInvalidMessageType = 2 66 | ExceptionWrongMethodName = 3 67 | ExceptionBadSequenceID = 4 68 | ExceptionMissingResult = 5 69 | ExceptionInternalError = 6 70 | ExceptionProtocolError = 7 71 | ) 72 | 73 | type MissingRequiredField struct { 74 | StructName string 75 | FieldName string 76 | } 77 | 78 | func (e *MissingRequiredField) Error() string { 79 | return "thrift: missing required field: " + e.StructName + "." + e.FieldName 80 | } 81 | 82 | type UnsupportedTypeError struct { 83 | Type reflect.Type 84 | } 85 | 86 | func (e *UnsupportedTypeError) Error() string { 87 | return "thrift: unsupported type: " + e.Type.String() 88 | } 89 | 90 | type UnsupportedValueError struct { 91 | Value reflect.Value 92 | Str string 93 | } 94 | 95 | func (e *UnsupportedValueError) Error() string { 96 | return fmt.Sprintf("thrift: unsupported value (%+v): %s", e.Value, e.Str) 97 | } 98 | 99 | // ApplicationException is an application level thrift exception 100 | type ApplicationException struct { 101 | Message string `thrift:"1"` 102 | Type int32 `thrift:"2"` 103 | } 104 | 105 | func (e *ApplicationException) String() string { 106 | typeStr := "Unknown Exception" 107 | switch e.Type { 108 | case ExceptionUnknownMethod: 109 | typeStr = "Unknown Method" 110 | case ExceptionInvalidMessageType: 111 | typeStr = "Invalid Message Type" 112 | case ExceptionWrongMethodName: 113 | typeStr = "Wrong Method Name" 114 | case ExceptionBadSequenceID: 115 | typeStr = "Bad Sequence ID" 116 | case ExceptionMissingResult: 117 | typeStr = "Missing Result" 118 | case ExceptionInternalError: 119 | typeStr = "Internal Error" 120 | case ExceptionProtocolError: 121 | typeStr = "Protocol Error" 122 | } 123 | return fmt.Sprintf("%s: %s", typeStr, e.Message) 124 | } 125 | 126 | func fieldType(t reflect.Type) byte { 127 | switch t.Kind() { 128 | case reflect.Bool: 129 | return TypeBool 130 | case reflect.Int8, reflect.Uint8: 131 | return TypeByte 132 | case reflect.Int16: 133 | return TypeI16 134 | case reflect.Int32, reflect.Uint32, reflect.Int: 135 | return TypeI32 136 | case reflect.Int64, reflect.Uint64: 137 | return TypeI64 138 | case reflect.Float64: 139 | return TypeDouble 140 | case reflect.Map: 141 | valueType := t.Elem() 142 | if valueType.Kind() == reflect.Struct && valueType.Name() == "" && valueType.NumField() == 0 { 143 | return TypeSet 144 | } 145 | return TypeMap 146 | case reflect.Slice: 147 | elemType := t.Elem() 148 | if elemType.Kind() == reflect.Uint8 { 149 | return TypeString 150 | } 151 | return TypeList 152 | case reflect.Struct: 153 | return TypeStruct 154 | case reflect.String: 155 | return TypeString 156 | case reflect.Interface, reflect.Ptr: 157 | return fieldType(t.Elem()) 158 | } 159 | panic(&UnsupportedTypeError{t}) 160 | } 161 | 162 | func isEmptyValue(v reflect.Value) bool { 163 | switch v.Kind() { 164 | case reflect.Array, reflect.Map, reflect.Slice: 165 | return v.IsNil() 166 | case reflect.String: 167 | return v.Len() == 0 168 | case reflect.Bool: 169 | return !v.Bool() 170 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 171 | return v.Int() == 0 172 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 173 | return v.Uint() == 0 174 | case reflect.Float32, reflect.Float64: 175 | return v.Float() == 0 176 | case reflect.Interface, reflect.Ptr: 177 | return v.IsNil() 178 | } 179 | return false 180 | } 181 | 182 | // encodeField contains information about how to encode a field of a 183 | // struct. 184 | type encodeField struct { 185 | i int // field index in struct 186 | id int 187 | required bool 188 | keepEmpty bool 189 | fieldType byte 190 | name string 191 | } 192 | 193 | type structMeta struct { 194 | required uint64 // bitmap of required fields 195 | fields map[int]encodeField 196 | } 197 | 198 | var ( 199 | typeCacheLock sync.RWMutex 200 | encodeFieldsCache = make(map[reflect.Type]structMeta) 201 | ) 202 | 203 | // encodeFields returns a slice of encodeField for a given 204 | // struct type. 205 | func encodeFields(t reflect.Type) structMeta { 206 | typeCacheLock.RLock() 207 | m, ok := encodeFieldsCache[t] 208 | typeCacheLock.RUnlock() 209 | if ok { 210 | return m 211 | } 212 | 213 | typeCacheLock.Lock() 214 | defer typeCacheLock.Unlock() 215 | m, ok = encodeFieldsCache[t] 216 | if ok { 217 | return m 218 | } 219 | 220 | fs := make(map[int]encodeField) 221 | m = structMeta{fields: fs} 222 | v := reflect.Zero(t) 223 | n := v.NumField() 224 | for i := 0; i < n; i++ { 225 | f := t.Field(i) 226 | if f.PkgPath != "" { 227 | continue 228 | } 229 | if f.Anonymous { 230 | // We want to do a better job with these later, 231 | // so for now pretend they don't exist. 232 | continue 233 | } 234 | tv := f.Tag.Get("thrift") 235 | if tv != "" { 236 | var ef encodeField 237 | ef.i = i 238 | ef.id = 0 239 | 240 | if tv == "-" { 241 | continue 242 | } 243 | id, opts := parseTag(tv) 244 | if id >= 64 { 245 | // TODO: figure out a better way to deal with this 246 | panic("thrift: field id must be < 64") 247 | } 248 | ef.id = id 249 | ef.name = f.Name 250 | ef.required = opts.Contains("required") 251 | if ef.required { 252 | m.required |= 1 << byte(id) 253 | } 254 | ef.keepEmpty = opts.Contains("keepempty") 255 | if opts.Contains("set") { 256 | ef.fieldType = TypeSet 257 | } else { 258 | ef.fieldType = fieldType(f.Type) 259 | } 260 | 261 | fs[ef.id] = ef 262 | } 263 | } 264 | encodeFieldsCache[t] = m 265 | return m 266 | } 267 | 268 | func SkipValue(r ProtocolReader, thriftType byte) error { 269 | var err error 270 | switch thriftType { 271 | case TypeBool: 272 | _, err = r.ReadBool() 273 | case TypeByte: 274 | _, err = r.ReadByte() 275 | case TypeI16: 276 | _, err = r.ReadI16() 277 | case TypeI32: 278 | _, err = r.ReadI32() 279 | case TypeI64: 280 | _, err = r.ReadI64() 281 | case TypeDouble: 282 | _, err = r.ReadDouble() 283 | case TypeString: 284 | _, err = r.ReadBytes() 285 | case TypeStruct: 286 | if err := r.ReadStructBegin(); err != nil { 287 | return err 288 | } 289 | for { 290 | ftype, _, err := r.ReadFieldBegin() 291 | if err != nil { 292 | return err 293 | } 294 | if ftype == TypeStop { 295 | break 296 | } 297 | if err = SkipValue(r, ftype); err != nil { 298 | return err 299 | } 300 | if err = r.ReadFieldEnd(); err != nil { 301 | return err 302 | } 303 | } 304 | return r.ReadStructEnd() 305 | case TypeMap: 306 | keyType, valueType, n, err := r.ReadMapBegin() 307 | if err != nil { 308 | return err 309 | } 310 | 311 | for i := 0; i < n; i++ { 312 | if err = SkipValue(r, keyType); err != nil { 313 | return err 314 | } 315 | if err = SkipValue(r, valueType); err != nil { 316 | return err 317 | } 318 | } 319 | 320 | return r.ReadMapEnd() 321 | case TypeList: 322 | valueType, n, err := r.ReadListBegin() 323 | if err != nil { 324 | return err 325 | } 326 | for i := 0; i < n; i++ { 327 | if err = SkipValue(r, valueType); err != nil { 328 | return err 329 | } 330 | } 331 | return r.ReadListEnd() 332 | case TypeSet: 333 | valueType, n, err := r.ReadSetBegin() 334 | if err != nil { 335 | return err 336 | } 337 | for i := 0; i < n; i++ { 338 | if err = SkipValue(r, valueType); err != nil { 339 | return err 340 | } 341 | } 342 | return r.ReadSetEnd() 343 | } 344 | return err 345 | } 346 | 347 | func ReadValue(r ProtocolReader, thriftType byte) (interface{}, error) { 348 | switch thriftType { 349 | case TypeBool: 350 | return r.ReadBool() 351 | case TypeByte: 352 | return r.ReadByte() 353 | case TypeI16: 354 | return r.ReadI16() 355 | case TypeI32: 356 | return r.ReadI32() 357 | case TypeI64: 358 | return r.ReadI64() 359 | case TypeDouble: 360 | return r.ReadDouble() 361 | case TypeString: 362 | return r.ReadString() 363 | case TypeStruct: 364 | if err := r.ReadStructBegin(); err != nil { 365 | return nil, err 366 | } 367 | st := make(map[int]interface{}) 368 | for { 369 | ftype, id, err := r.ReadFieldBegin() 370 | if err != nil { 371 | return st, err 372 | } 373 | if ftype == TypeStop { 374 | break 375 | } 376 | v, err := ReadValue(r, ftype) 377 | if err != nil { 378 | return st, err 379 | } 380 | st[int(id)] = v 381 | if err = r.ReadFieldEnd(); err != nil { 382 | return st, err 383 | } 384 | } 385 | return st, r.ReadStructEnd() 386 | case TypeMap: 387 | keyType, valueType, n, err := r.ReadMapBegin() 388 | if err != nil { 389 | return nil, err 390 | } 391 | 392 | mp := make(map[interface{}]interface{}) 393 | for i := 0; i < n; i++ { 394 | k, err := ReadValue(r, keyType) 395 | if err != nil { 396 | return mp, err 397 | } 398 | v, err := ReadValue(r, valueType) 399 | if err != nil { 400 | return mp, err 401 | } 402 | mp[k] = v 403 | } 404 | 405 | return mp, r.ReadMapEnd() 406 | case TypeList: 407 | valueType, n, err := r.ReadListBegin() 408 | if err != nil { 409 | return nil, err 410 | } 411 | lst := make([]interface{}, 0) 412 | for i := 0; i < n; i++ { 413 | v, err := ReadValue(r, valueType) 414 | if err != nil { 415 | return lst, err 416 | } 417 | lst = append(lst, v) 418 | } 419 | return lst, r.ReadListEnd() 420 | case TypeSet: 421 | valueType, n, err := r.ReadSetBegin() 422 | if err != nil { 423 | return nil, err 424 | } 425 | set := make([]interface{}, 0) 426 | for i := 0; i < n; i++ { 427 | v, err := ReadValue(r, valueType) 428 | if err != nil { 429 | return set, err 430 | } 431 | set = append(set, v) 432 | } 433 | return set, r.ReadSetEnd() 434 | } 435 | return nil, errors.New("thrift: unknown type") 436 | } 437 | -------------------------------------------------------------------------------- /thrift/protocol_compact.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2015 Samuel Stauffer. All rights reserved. 2 | // Use of this source code is governed by a 3-clause BSD 3 | // license that can be found in the LICENSE file. 4 | 5 | package thrift 6 | 7 | import ( 8 | "encoding/binary" 9 | "io" 10 | "math" 11 | ) 12 | 13 | const ( 14 | compactProtocolID = 0x82 15 | compactVersion = 1 16 | compactVersionMask = 0x1f 17 | compactTypeMask = 0xe0 18 | compactTypeShiftAmount = 5 19 | ) 20 | 21 | const ( 22 | ctStop = iota 23 | ctTrue 24 | ctFalse 25 | ctByte 26 | ctI16 27 | ctI32 28 | ctI64 29 | ctDouble 30 | ctBinary 31 | ctList 32 | ctSet 33 | ctMap 34 | ctStruct 35 | ) 36 | 37 | var thriftTypeToCompactType []byte 38 | var compactTypeToThriftType []byte 39 | 40 | func init() { 41 | thriftTypeToCompactType = make([]byte, 16) 42 | thriftTypeToCompactType[TypeStop] = ctStop 43 | thriftTypeToCompactType[TypeBool] = ctTrue 44 | thriftTypeToCompactType[TypeByte] = ctByte 45 | thriftTypeToCompactType[TypeI16] = ctI16 46 | thriftTypeToCompactType[TypeI32] = ctI32 47 | thriftTypeToCompactType[TypeI64] = ctI64 48 | thriftTypeToCompactType[TypeDouble] = ctDouble 49 | thriftTypeToCompactType[TypeString] = ctBinary 50 | thriftTypeToCompactType[TypeStruct] = ctStruct 51 | thriftTypeToCompactType[TypeList] = ctList 52 | thriftTypeToCompactType[TypeSet] = ctSet 53 | thriftTypeToCompactType[TypeMap] = ctMap 54 | compactTypeToThriftType = make([]byte, 16) 55 | compactTypeToThriftType[ctStop] = TypeStop 56 | compactTypeToThriftType[ctTrue] = TypeBool 57 | compactTypeToThriftType[ctFalse] = TypeBool 58 | compactTypeToThriftType[ctByte] = TypeByte 59 | compactTypeToThriftType[ctI16] = TypeI16 60 | compactTypeToThriftType[ctI32] = TypeI32 61 | compactTypeToThriftType[ctI64] = TypeI64 62 | compactTypeToThriftType[ctDouble] = TypeDouble 63 | compactTypeToThriftType[ctBinary] = TypeString 64 | compactTypeToThriftType[ctStruct] = TypeStruct 65 | compactTypeToThriftType[ctList] = TypeList 66 | compactTypeToThriftType[ctSet] = TypeSet 67 | compactTypeToThriftType[ctMap] = TypeMap 68 | } 69 | 70 | type compactProtocolWriter struct { 71 | w io.Writer 72 | lastFieldID int16 73 | boolFid int16 74 | boolValue bool 75 | structs []int16 76 | container []int 77 | buf []byte 78 | } 79 | 80 | type compactProtocolReader struct { 81 | r io.Reader 82 | lastFieldID int16 83 | boolFid int16 84 | boolValue bool 85 | structs []int16 86 | container []int 87 | buf []byte 88 | } 89 | 90 | var CompactProtocol = NewProtocolBuilder(NewCompactProtocolReader, NewCompactProtocolWriter) 91 | 92 | func NewCompactProtocolWriter(w io.Writer) ProtocolWriter { 93 | return &compactProtocolWriter{ 94 | w: w, 95 | lastFieldID: 0, 96 | boolFid: -1, 97 | boolValue: false, 98 | structs: make([]int16, 0, 8), 99 | container: make([]int, 0, 8), 100 | buf: make([]byte, 64), 101 | } 102 | } 103 | 104 | func NewCompactProtocolReader(r io.Reader) ProtocolReader { 105 | return &compactProtocolReader{ 106 | r: r, 107 | lastFieldID: 0, 108 | boolFid: -1, 109 | boolValue: false, 110 | structs: make([]int16, 0, 8), 111 | container: make([]int, 0, 8), 112 | buf: make([]byte, 64), 113 | } 114 | } 115 | 116 | func (p *compactProtocolWriter) writeVarint(value int64) (err error) { 117 | n := binary.PutVarint(p.buf, value) 118 | _, err = p.w.Write(p.buf[:n]) 119 | return 120 | } 121 | 122 | func (p *compactProtocolWriter) writeUvarint(value uint64) (err error) { 123 | n := binary.PutUvarint(p.buf, value) 124 | _, err = p.w.Write(p.buf[:n]) 125 | return 126 | } 127 | 128 | // Write a message header to the wire. Compact Protocol messages contain the 129 | // protocol version so we can migrate forwards in the future if need be. 130 | func (p *compactProtocolWriter) WriteMessageBegin(name string, messageType byte, seqid int32) (err error) { 131 | if err = p.writeByteDirect(compactProtocolID); err != nil { 132 | return 133 | } 134 | if err = p.writeByteDirect(compactVersion | (messageType << compactTypeShiftAmount)); err != nil { 135 | return 136 | } 137 | if err = p.writeUvarint(uint64(seqid)); err != nil { 138 | return 139 | } 140 | err = p.WriteString(name) 141 | return 142 | } 143 | 144 | // Write a struct begin. This doesn't actually put anything on the wire. We 145 | // use it as an opportunity to put special placeholder markers on the field 146 | // stack so we can get the field id deltas correct. 147 | func (p *compactProtocolWriter) WriteStructBegin(name string) error { 148 | p.structs = append(p.structs, p.lastFieldID) 149 | p.lastFieldID = 0 150 | return nil 151 | } 152 | 153 | // Write a struct end. This doesn't actually put anything on the wire. We use 154 | // this as an opportunity to pop the last field from the current struct off 155 | // of the field stack. 156 | func (p *compactProtocolWriter) WriteStructEnd() error { 157 | if len(p.structs) == 0 { 158 | return ProtocolError{"CompactProtocol", "Struct end without matching begin"} 159 | } 160 | fid := p.structs[len(p.structs)-1] 161 | p.structs = p.structs[:len(p.structs)-1] 162 | p.lastFieldID = fid 163 | return nil 164 | } 165 | 166 | // Write a field header containing the field id and field type. If the 167 | // difference between the current field id and the last one is small (< 15), 168 | // then the field id will be encoded in the 4 MSB as a delta. Otherwise, the 169 | // field id will follow the type header as a zigzag varint. 170 | func (p *compactProtocolWriter) WriteFieldBegin(name string, fieldType byte, id int16) error { 171 | if fieldType == TypeBool { 172 | // we want to possibly include the value, so we'll wait. 173 | p.boolFid = id 174 | return nil 175 | } 176 | return p.writeFieldBeginInternal(name, fieldType, id, 0xff) 177 | } 178 | 179 | // The workhorse of writeFieldBegin. It has the option of doing a 180 | // 'type override' of the type header. This is used specifically in the 181 | // boolean field case. 182 | func (p *compactProtocolWriter) writeFieldBeginInternal(name string, fieldType byte, id int16, typeOverride byte) error { 183 | // if there's a type override, use that. 184 | typeToWrite := typeOverride 185 | if typeToWrite == 0xff { 186 | typeToWrite = thriftTypeToCompactType[fieldType] 187 | } 188 | 189 | // check if we can use delta encoding for the field id 190 | if id > p.lastFieldID && id-p.lastFieldID <= 15 { 191 | // write them together 192 | if err := p.writeByteDirect(byte((id-p.lastFieldID)<<4 | int16(typeToWrite))); err != nil { 193 | return err 194 | } 195 | } else { 196 | // write them separate 197 | if err := p.writeByteDirect(byte(typeToWrite)); err != nil { 198 | return err 199 | } 200 | if err := p.WriteI16(id); err != nil { 201 | return err 202 | } 203 | } 204 | 205 | p.lastFieldID = id 206 | return nil 207 | } 208 | 209 | // Write the STOP symbol so we know there are no more fields in this struct. 210 | func (p *compactProtocolWriter) WriteFieldStop() error { 211 | return p.writeByteDirect(TypeStop) 212 | } 213 | 214 | // Write a map header. If the map is empty, omit the key and value type 215 | // headers, as we don't need any additional information to skip it. 216 | func (p *compactProtocolWriter) WriteMapBegin(keyType byte, valueType byte, size int) error { 217 | if size == 0 { 218 | return p.writeByteDirect(0) 219 | } 220 | if err := p.writeUvarint(uint64(size)); err != nil { 221 | return err 222 | } 223 | return p.writeByteDirect(byte(thriftTypeToCompactType[keyType]<<4 | thriftTypeToCompactType[valueType])) 224 | } 225 | 226 | // Write a list header. 227 | func (p *compactProtocolWriter) WriteListBegin(elementType byte, size int) error { 228 | return p.writeCollectionBegin(elementType, size) 229 | } 230 | 231 | // Write a set header. 232 | func (p *compactProtocolWriter) WriteSetBegin(elementType byte, size int) error { 233 | return p.writeCollectionBegin(elementType, size) 234 | } 235 | 236 | // Write a boolean value. Potentially, this could be a boolean field, in 237 | // which case the field header info isn't written yet. If so, decide what the 238 | // right type header is for the value and then write the field header. 239 | // Otherwise, write a single byte. 240 | func (p *compactProtocolWriter) WriteBool(value bool) error { 241 | fieldType := byte(ctFalse) 242 | if value { 243 | fieldType = ctTrue 244 | } 245 | if p.boolFid >= 0 { 246 | // we haven't written the field header yet 247 | return p.writeFieldBeginInternal("bool", TypeBool, p.boolFid, fieldType) 248 | } 249 | return p.writeByteDirect(fieldType) 250 | } 251 | 252 | func (p *compactProtocolWriter) WriteByte(value byte) error { 253 | return p.writeByteDirect(value) 254 | } 255 | 256 | func (p *compactProtocolWriter) WriteI16(value int16) error { 257 | return p.writeVarint(int64(value)) 258 | } 259 | 260 | func (p *compactProtocolWriter) WriteI32(value int32) error { 261 | return p.writeVarint(int64(value)) 262 | } 263 | 264 | func (p *compactProtocolWriter) WriteI64(value int64) error { 265 | return p.writeVarint(value) 266 | } 267 | 268 | func (p *compactProtocolWriter) WriteDouble(value float64) (err error) { 269 | b := p.buf 270 | binary.BigEndian.PutUint64(b, math.Float64bits(value)) 271 | _, err = p.w.Write(b[:8]) 272 | return 273 | } 274 | 275 | // Write a string to the wire with a varint size preceeding. 276 | func (p *compactProtocolWriter) WriteString(value string) error { 277 | return p.WriteBytes([]byte(value)) 278 | } 279 | 280 | // Write a byte array, using a varint for the size. 281 | func (p *compactProtocolWriter) WriteBytes(value []byte) error { 282 | if err := p.writeUvarint(uint64(len(value))); err != nil { 283 | return err 284 | } 285 | _, err := p.w.Write(value) 286 | return err 287 | } 288 | 289 | func (p *compactProtocolWriter) WriteMessageEnd() error { 290 | return nil 291 | } 292 | 293 | func (p *compactProtocolWriter) WriteMapEnd() error { 294 | return nil 295 | } 296 | 297 | func (p *compactProtocolWriter) WriteListEnd() error { 298 | return nil 299 | } 300 | 301 | func (p *compactProtocolWriter) WriteSetEnd() error { 302 | return nil 303 | } 304 | 305 | func (p *compactProtocolWriter) WriteFieldEnd() error { 306 | return nil 307 | } 308 | 309 | // Abstract method for writing the start of lists and sets. List and sets on 310 | // the wire differ only by the type indicator. 311 | func (p *compactProtocolWriter) writeCollectionBegin(elemType byte, size int) error { 312 | if size <= 14 { 313 | return p.writeByteDirect(byte(size)<<4 | thriftTypeToCompactType[elemType]) 314 | } 315 | if err := p.writeByteDirect(0xf0 | thriftTypeToCompactType[elemType]); err != nil { 316 | return err 317 | } 318 | return p.writeUvarint(uint64(size)) 319 | } 320 | 321 | // Writes a byte without any possiblity of all that field header nonsense. 322 | // Used internally by other writing methods that know they need to write a byte. 323 | func (p *compactProtocolWriter) writeByteDirect(value byte) error { 324 | p.buf[0] = value 325 | _, err := p.w.Write(p.buf[:1]) 326 | return err 327 | } 328 | 329 | func (p *compactProtocolReader) readVarint() (int64, error) { 330 | if br, ok := p.r.(io.ByteReader); ok { 331 | return binary.ReadVarint(br) 332 | } 333 | // TODO: Make this more efficient 334 | n := 0 335 | b := p.buf 336 | for { 337 | if _, err := p.r.Read(b[n : n+1]); err != nil { 338 | return 0, err 339 | } 340 | n++ 341 | // n == 0: buf too small 342 | // n < 0: value larger than 64-bits 343 | if val, n := binary.Varint(b[:n]); n > 0 { 344 | return val, nil 345 | } else if n < 0 { 346 | return val, ProtocolError{"CompactProtocol", "varint overflow on read"} 347 | } 348 | } 349 | } 350 | 351 | func (p *compactProtocolReader) readUvarint() (uint64, error) { 352 | if br, ok := p.r.(io.ByteReader); ok { 353 | return binary.ReadUvarint(br) 354 | } 355 | // TODO: Make this more efficient 356 | n := 0 357 | b := p.buf 358 | for { 359 | if _, err := p.r.Read(b[n : n+1]); err != nil { 360 | return 0, err 361 | } 362 | n++ 363 | // n == 0: buf too small 364 | // n < 0: value larger than 64-bits 365 | if val, n := binary.Uvarint(b[:n]); n > 0 { 366 | return val, nil 367 | } else if n < 0 { 368 | return val, ProtocolError{"CompactProtocol", "varint overflow on read"} 369 | } 370 | } 371 | } 372 | 373 | func (p *compactProtocolReader) ReadMessageBegin() (string, byte, int32, error) { 374 | protocolID, err := p.ReadByte() 375 | if err != nil { 376 | return "", 0, -1, err 377 | } 378 | if protocolID != compactProtocolID { 379 | return "", 0, -1, ProtocolError{"CompactProtocol", "invalid compact protocol ID"} 380 | } 381 | versionAndType, err := p.ReadByte() 382 | if err != nil { 383 | return "", 0, -1, err 384 | } 385 | version := versionAndType & compactVersionMask 386 | if version != compactVersion { 387 | return "", 0, -1, ProtocolError{"CompactProtocol", "invalid compact protocol version"} 388 | } 389 | msgType := (versionAndType >> compactTypeShiftAmount) & 0x03 390 | seqID, err := p.readUvarint() 391 | if err != nil { 392 | return "", 0, -1, err 393 | } 394 | msgName, err := p.ReadString() 395 | if err != nil { 396 | return "", 0, -1, err 397 | } 398 | return msgName, msgType, int32(seqID), nil 399 | } 400 | 401 | // Read a struct begin. There's nothing on the wire for this, but it is our 402 | // opportunity to push a new struct begin marker onto the field stack. 403 | func (p *compactProtocolReader) ReadStructBegin() error { 404 | p.structs = append(p.structs, p.lastFieldID) 405 | p.lastFieldID = 0 406 | return nil 407 | } 408 | 409 | // Doesn't actually consume any wire data, just removes the last field for 410 | // this struct from the field stack. 411 | func (p *compactProtocolReader) ReadStructEnd() error { 412 | // consume the last field we read off the wire 413 | p.lastFieldID = p.structs[len(p.structs)-1] 414 | p.structs = p.structs[:len(p.structs)-1] 415 | return nil 416 | } 417 | 418 | // Read a field header off the wire. 419 | func (p *compactProtocolReader) ReadFieldBegin() (byte, int16, error) { 420 | compactType, err := p.ReadByte() 421 | if err != nil { 422 | return 0, -1, err 423 | } 424 | 425 | // if it's a stop, then we can return immediately, as the struct is over 426 | if (compactType & 0x0f) == ctStop { 427 | return TypeStop, -1, nil 428 | } 429 | 430 | // mask off the 4 MSB of the type header. it could contain a field id delta. 431 | var fieldID int16 432 | modifier := int16((compactType & 0xf0) >> 4) 433 | if modifier == 0 { 434 | // not a delta. look ahead for the zigzag varint field id. 435 | fieldID, err = p.ReadI16() 436 | if err != nil { 437 | return 0, fieldID, err 438 | } 439 | } else { 440 | // has a delta. add the delta to the last read field id 441 | fieldID = p.lastFieldID + modifier 442 | } 443 | 444 | fieldType := compactTypeToThriftType[compactType&0x0f] 445 | 446 | // if this happens to be a boolean field, the value is encoded in the type 447 | if fieldType == TypeBool { 448 | // save the boolean value in a special instance variable. 449 | p.boolValue = (compactType & 0x0f) == ctTrue 450 | p.boolFid = fieldID 451 | } 452 | 453 | // push the new field onto the field stack so we can keep the deltas going. 454 | p.lastFieldID = fieldID 455 | return fieldType, fieldID, nil 456 | } 457 | 458 | // Read a map header off the wire. If the size is zero, skip reading the key 459 | // and value type. This means that 0-length maps will yield TMaps without the 460 | // "correct" types. 461 | func (p *compactProtocolReader) ReadMapBegin() (byte, byte, int, error) { 462 | size, err := p.readUvarint() 463 | if err != nil { 464 | return 0, 0, -1, err 465 | } 466 | keyAndValueType := byte(0) 467 | if size > 0 { 468 | keyAndValueType, err = p.ReadByte() 469 | if err != nil { 470 | return 0, 0, -1, err 471 | } 472 | } 473 | return compactTypeToThriftType[keyAndValueType>>4], compactTypeToThriftType[keyAndValueType&0x0f], int(size), nil 474 | } 475 | 476 | // Read a list header off the wire. If the list size is 0-14, the size will 477 | // be packed into the element type header. If it's a longer list, the 4 MSB 478 | // of the element type header will be 0xF, and a varint will follow with the 479 | // true size. 480 | func (p *compactProtocolReader) ReadListBegin() (byte, int, error) { 481 | sizeAndType, err := p.ReadByte() 482 | if err != nil { 483 | return 0, -1, err 484 | } 485 | size := int((sizeAndType >> 4) & 0x0f) 486 | if size == 15 { 487 | s, err := p.readUvarint() 488 | if err != nil { 489 | return 0, -1, err 490 | } 491 | size = int(s) 492 | } 493 | return compactTypeToThriftType[sizeAndType&0x0f], size, nil 494 | } 495 | 496 | // Read a set header off the wire. If the set size is 0-14, the size will 497 | // be packed into the element type header. If it's a longer set, the 4 MSB 498 | // of the element type header will be 0xF, and a varint will follow with the 499 | // true size. 500 | func (p *compactProtocolReader) ReadSetBegin() (byte, int, error) { 501 | return p.ReadListBegin() 502 | } 503 | 504 | // Read a boolean off the wire. If this is a boolean field, the value should 505 | // already have been read during readFieldBegin, so we'll just consume the 506 | // pre-stored value. Otherwise, read a byte. 507 | func (p *compactProtocolReader) ReadBool() (bool, error) { 508 | if p.boolFid < 0 { 509 | v, err := p.ReadByte() 510 | return v == ctTrue, err 511 | } 512 | 513 | res := p.boolValue 514 | p.boolFid = -1 515 | return res, nil 516 | } 517 | 518 | // Read a single byte off the wire. Nothing interesting here. 519 | func (p *compactProtocolReader) ReadByte() (byte, error) { 520 | b := p.buf 521 | _, err := io.ReadFull(p.r, b[:1]) 522 | return b[0], err 523 | } 524 | 525 | func (p *compactProtocolReader) ReadI16() (int16, error) { 526 | v, err := p.readVarint() 527 | return int16(v), err 528 | } 529 | 530 | func (p *compactProtocolReader) ReadI32() (int32, error) { 531 | v, err := p.readVarint() 532 | return int32(v), err 533 | } 534 | 535 | func (p *compactProtocolReader) ReadI64() (int64, error) { 536 | v, err := p.readVarint() 537 | return v, err 538 | } 539 | 540 | func (p *compactProtocolReader) ReadDouble() (float64, error) { 541 | b := p.buf 542 | _, err := io.ReadFull(p.r, b[:8]) 543 | value := math.Float64frombits(binary.BigEndian.Uint64(b)) 544 | return value, err 545 | } 546 | 547 | func (p *compactProtocolReader) ReadString() (string, error) { 548 | ln, err := p.readUvarint() 549 | if err != nil || ln == 0 { 550 | return "", err 551 | } else if ln < 0 { 552 | return "", ProtocolError{"CompactProtocol", "negative length in CompactProtocol.ReadString"} 553 | } 554 | b := p.buf 555 | if int(ln) > len(b) { 556 | b = make([]byte, ln) 557 | } else { 558 | b = b[:ln] 559 | } 560 | if _, err := io.ReadFull(p.r, b); err != nil { 561 | return "", err 562 | } 563 | return string(b), nil 564 | } 565 | 566 | func (p *compactProtocolReader) ReadBytes() ([]byte, error) { 567 | ln, err := p.readUvarint() 568 | if err != nil || ln == 0 { 569 | return nil, err 570 | } else if ln < 0 { 571 | return nil, ProtocolError{"CompactProtocol", "negative length in CompactProtocol.ReadBytes"} 572 | } 573 | b := make([]byte, ln) 574 | if _, err := io.ReadFull(p.r, b); err != nil { 575 | return nil, err 576 | } 577 | return b, nil 578 | } 579 | 580 | func (p *compactProtocolReader) ReadMessageEnd() error { 581 | return nil 582 | } 583 | 584 | func (p *compactProtocolReader) ReadFieldEnd() error { 585 | return nil 586 | } 587 | 588 | func (p *compactProtocolReader) ReadMapEnd() error { 589 | return nil 590 | } 591 | 592 | func (p *compactProtocolReader) ReadListEnd() error { 593 | return nil 594 | } 595 | 596 | func (p *compactProtocolReader) ReadSetEnd() error { 597 | return nil 598 | } 599 | -------------------------------------------------------------------------------- /generator/go.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2015 Samuel Stauffer. All rights reserved. 2 | // Use of this source code is governed by a 3-clause BSD 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | // TODO: 8 | // - Default arguments. Possibly don't bother... 9 | 10 | import ( 11 | "bytes" 12 | "flag" 13 | "fmt" 14 | "go/format" 15 | "io" 16 | "os" 17 | "path/filepath" 18 | "runtime" 19 | "strconv" 20 | "strings" 21 | 22 | "github.com/samuel/go-thrift/parser" 23 | ) 24 | 25 | var ( 26 | flagGoBinarystring = flag.Bool("go.binarystring", false, "Always use string for binary instead of []byte") 27 | flagGoJsonEnumnum = flag.Bool("go.json.enumnum", false, "For JSON marshal enums by number instead of name") 28 | flagGoPointers = flag.Bool("go.pointers", false, "Make all fields pointers") 29 | ) 30 | 31 | var ( 32 | goNamespaceOrder = []string{"go", "perl", "py", "cpp", "rb", "java"} 33 | ) 34 | 35 | type ErrUnknownType string 36 | 37 | func (e ErrUnknownType) Error() string { 38 | return fmt.Sprintf("Unknown type %s", string(e)) 39 | } 40 | 41 | type ErrMissingInclude string 42 | 43 | func (e ErrMissingInclude) Error() string { 44 | return fmt.Sprintf("Missing include %s", string(e)) 45 | } 46 | 47 | type GoPackage struct { 48 | Path string 49 | Name string 50 | } 51 | 52 | type GoGenerator struct { 53 | thrift *parser.Thrift 54 | pkg string 55 | 56 | ThriftFiles map[string]*parser.Thrift 57 | Packages map[string]GoPackage 58 | Format bool 59 | } 60 | 61 | var goKeywords = map[string]bool{ 62 | "break": true, 63 | "default": true, 64 | "func": true, 65 | "interface": true, 66 | "select": true, 67 | "case": true, 68 | "defer": true, 69 | "go": true, 70 | "map": true, 71 | "struct": true, 72 | "chan": true, 73 | "else": true, 74 | "goto": true, 75 | "package": true, 76 | "switch": true, 77 | "const": true, 78 | "fallthrough": true, 79 | "if": true, 80 | "range": true, 81 | "type": true, 82 | "continue": true, 83 | "for": true, 84 | "import": true, 85 | "return": true, 86 | "var": true, 87 | } 88 | 89 | var basicTypes = map[string]bool{ 90 | "byte": true, 91 | "bool": true, 92 | "string": true, 93 | "i16": true, 94 | "i32": true, 95 | "i64": true, 96 | "double": true, 97 | } 98 | 99 | func init() { 100 | if *flagGoBinarystring { 101 | basicTypes["binary"] = true 102 | } 103 | } 104 | 105 | func validGoIdent(id string) string { 106 | if goKeywords[id] { 107 | return "_" + id 108 | } 109 | return id 110 | } 111 | 112 | func (g *GoGenerator) error(err error) { 113 | panic(err) 114 | } 115 | 116 | func (g *GoGenerator) write(w io.Writer, f string, a ...interface{}) error { 117 | if _, err := io.WriteString(w, fmt.Sprintf(f, a...)); err != nil { 118 | g.error(err) 119 | return err 120 | } 121 | return nil 122 | } 123 | 124 | func (g *GoGenerator) formatType(pkg string, thrift *parser.Thrift, typ *parser.Type, optional bool) string { 125 | // Follow includes 126 | if strings.Contains(typ.Name, ".") { 127 | // . 128 | parts := strings.SplitN(typ.Name, ".", 2) 129 | // Get Thrift struct for the given include 130 | thriftFilename := thrift.Includes[parts[0]] 131 | if thriftFilename == "" { 132 | g.error(ErrMissingInclude(parts[0])) 133 | } 134 | thrift = g.ThriftFiles[thriftFilename] 135 | if thrift == nil { 136 | g.error(ErrMissingInclude(thriftFilename)) 137 | } 138 | pkg = g.Packages[thriftFilename].Name 139 | typ = &parser.Type{ 140 | Name: parts[1], 141 | KeyType: typ.KeyType, 142 | ValueType: typ.ValueType, 143 | } 144 | } 145 | 146 | ptr := "" 147 | if *flagGoPointers || optional { 148 | ptr = "*" 149 | } 150 | switch typ.Name { 151 | case "byte", "bool", "string": 152 | return ptr + typ.Name 153 | case "binary": 154 | if *flagGoBinarystring { 155 | return ptr + "string" 156 | } 157 | return "[]byte" 158 | case "i16": 159 | return ptr + "int16" 160 | case "i32": 161 | return ptr + "int32" 162 | case "i64": 163 | return ptr + "int64" 164 | case "double": 165 | return ptr + "float64" 166 | case "set": 167 | valueType := g.formatType(pkg, thrift, typ.ValueType, false) 168 | if valueType == "[]byte" { 169 | valueType = "string" 170 | } 171 | return "map[" + valueType + "]struct{}" 172 | case "list": 173 | return "[]" + g.formatType(pkg, thrift, typ.ValueType, false) 174 | case "map": 175 | keyType := g.formatType(pkg, thrift, typ.KeyType, false) 176 | if keyType == "[]byte" { 177 | // TODO: Log, warn, do something! 178 | // println("key type of []byte not supported for maps") 179 | keyType = "string" 180 | } 181 | return "map[" + keyType + "]" + g.formatType(pkg, thrift, typ.ValueType, false) 182 | } 183 | 184 | if t := thrift.Typedefs[typ.Name]; t != nil { 185 | return g.formatType(pkg, thrift, t, optional) 186 | } 187 | if e := thrift.Enums[typ.Name]; e != nil { 188 | name := e.Name 189 | if pkg != g.pkg { 190 | name = pkg + "." + name 191 | } 192 | return ptr + name 193 | } 194 | if s := thrift.Structs[typ.Name]; s != nil { 195 | name := s.Name 196 | if pkg != g.pkg { 197 | name = pkg + "." + name 198 | } 199 | return "*" + name 200 | } 201 | if e := thrift.Exceptions[typ.Name]; e != nil { 202 | name := e.Name 203 | if pkg != g.pkg { 204 | name = pkg + "." + name 205 | } 206 | return "*" + name 207 | } 208 | 209 | g.error(ErrUnknownType(typ.Name)) 210 | return "" 211 | } 212 | 213 | // Follow typedefs to the actual type 214 | func (g *GoGenerator) resolveType(typ *parser.Type) string { 215 | if t := g.thrift.Typedefs[typ.Name]; t != nil { 216 | return g.resolveType(t) 217 | } 218 | return typ.Name 219 | } 220 | 221 | func (g *GoGenerator) formatField(field *parser.Field) string { 222 | tags := "" 223 | jsonTags := "" 224 | if !field.Optional { 225 | tags = ",required" 226 | } else { 227 | jsonTags = ",omitempty" 228 | } 229 | return fmt.Sprintf( 230 | "%s %s `thrift:\"%d%s\" json:\"%s%s\"`", 231 | camelCase(field.Name), g.formatType(g.pkg, g.thrift, field.Type, field.Optional), field.ID, tags, field.Name, jsonTags) 232 | } 233 | 234 | func (g *GoGenerator) formatArguments(arguments []*parser.Field) string { 235 | args := make([]string, len(arguments)) 236 | for i, arg := range arguments { 237 | args[i] = fmt.Sprintf("%s %s", validGoIdent(lowerCamelCase(arg.Name)), g.formatType(g.pkg, g.thrift, arg.Type, arg.Optional)) 238 | } 239 | return strings.Join(args, ", ") 240 | } 241 | 242 | func (g *GoGenerator) formatReturnType(typ *parser.Type, named bool) string { 243 | if typ == nil || typ.Name == "void" { 244 | if named { 245 | return "(err error)" 246 | } 247 | return "error" 248 | } 249 | if named { 250 | return fmt.Sprintf("(ret %s, err error)", g.formatType(g.pkg, g.thrift, typ, false)) 251 | } 252 | return fmt.Sprintf("(%s, error)", g.formatType(g.pkg, g.thrift, typ, false)) 253 | } 254 | 255 | func (g *GoGenerator) formatValue(v interface{}, t *parser.Type) (string, error) { 256 | switch v2 := v.(type) { 257 | case string: 258 | return strconv.Quote(v2), nil 259 | case int: 260 | return strconv.Itoa(v2), nil 261 | case int64: 262 | return strconv.FormatInt(v2, 10), nil 263 | case float64: 264 | return strconv.FormatFloat(v2, 'f', -1, 64), nil 265 | case []interface{}: 266 | buf := &bytes.Buffer{} 267 | buf.WriteString(g.formatType(g.pkg, g.thrift, t, false)) 268 | buf.WriteString("{\n") 269 | for _, v := range v2 { 270 | buf.WriteString("\t\t") 271 | s, err := g.formatValue(v, t.ValueType) 272 | if err != nil { 273 | return "", err 274 | } 275 | buf.WriteString(s) 276 | if t.Name == "set" { 277 | buf.WriteString(": struct{}{}") 278 | } 279 | buf.WriteString(",\n") 280 | } 281 | buf.WriteString("\t}") 282 | return buf.String(), nil 283 | case []parser.KeyValue: 284 | buf := &bytes.Buffer{} 285 | buf.WriteString(g.formatType(g.pkg, g.thrift, t, false)) 286 | buf.WriteString("{\n") 287 | for _, kv := range v2 { 288 | buf.WriteString("\t\t") 289 | s, err := g.formatValue(kv.Key, t.KeyType) 290 | if err != nil { 291 | return "", err 292 | } 293 | buf.WriteString(s) 294 | buf.WriteString(": ") 295 | s, err = g.formatValue(kv.Value, t.ValueType) 296 | if err != nil { 297 | return "", err 298 | } 299 | buf.WriteString(s) 300 | buf.WriteString(",\n") 301 | } 302 | buf.WriteString("\t}") 303 | return buf.String(), nil 304 | case parser.Identifier: 305 | return string(v2), nil 306 | } 307 | return "", fmt.Errorf("unsupported value type %T", v) 308 | } 309 | 310 | func (g *GoGenerator) writeEnum(out io.Writer, enum *parser.Enum) error { 311 | enumName := camelCase(enum.Name) 312 | 313 | g.write(out, "\ntype %s int32\n", enumName) 314 | 315 | valueNames := sortedKeys(enum.Values) 316 | g.write(out, "\nconst (\n") 317 | for _, name := range valueNames { 318 | val := enum.Values[name] 319 | g.write(out, "\t%s%s %s = %d\n", enumName, camelCase(name), enumName, val.Value) 320 | } 321 | g.write(out, ")\n") 322 | 323 | // begin var 324 | g.write(out, "\nvar (\n") 325 | 326 | // EnumByName 327 | g.write(out, "\t%sByName = map[string]%s{\n", enumName, enumName) 328 | for _, name := range valueNames { 329 | realName := enum.Name + "." + name 330 | fullName := enumName + camelCase(name) 331 | g.write(out, "\t\t\"%s\": %s,\n", realName, fullName) 332 | } 333 | g.write(out, "\t}\n") 334 | 335 | // EnumByValue 336 | g.write(out, "\t%sByValue = map[%s]string{\n", enumName, enumName) 337 | for _, name := range valueNames { 338 | realName := enum.Name + "." + name 339 | fullName := enumName + camelCase(name) 340 | g.write(out, "\t\t%s: \"%s\",\n", fullName, realName) 341 | } 342 | g.write(out, "\t}\n") 343 | 344 | // end var 345 | g.write(out, ")\n") 346 | 347 | g.write(out, ` 348 | func (e %s) String() string { 349 | name := %sByValue[e] 350 | if name == "" { 351 | name = fmt.Sprintf("Unknown enum value %s(%%d)", e) 352 | } 353 | return name 354 | } 355 | `, enumName, enumName, enumName) 356 | 357 | if !*flagGoJsonEnumnum { 358 | g.write(out, ` 359 | func (e %s) MarshalJSON() ([]byte, error) { 360 | name := %sByValue[e] 361 | if name == "" { 362 | name = strconv.Itoa(int(e)) 363 | } 364 | return []byte("\""+name+"\""), nil 365 | } 366 | `, enumName, enumName) 367 | } 368 | 369 | g.write(out, ` 370 | func (e *%s) UnmarshalJSON(b []byte) error { 371 | st := string(b) 372 | if st[0] == '"' { 373 | *e = %s(%sByName[st[1:len(st)-1]]) 374 | return nil 375 | } 376 | i, err := strconv.Atoi(st) 377 | *e = %s(i) 378 | return err 379 | } 380 | `, enumName, enumName, enumName, enumName) 381 | 382 | return nil 383 | } 384 | 385 | func (g *GoGenerator) writeStruct(out io.Writer, st *parser.Struct) error { 386 | structName := camelCase(st.Name) 387 | 388 | g.write(out, "\ntype %s struct {\n", structName) 389 | for _, field := range st.Fields { 390 | g.write(out, "\t%s\n", g.formatField(field)) 391 | } 392 | return g.write(out, "}\n") 393 | } 394 | 395 | func (g *GoGenerator) writeException(out io.Writer, ex *parser.Struct) error { 396 | if err := g.writeStruct(out, ex); err != nil { 397 | return err 398 | } 399 | 400 | exName := camelCase(ex.Name) 401 | 402 | g.write(out, "\nfunc (e *%s) Error() string {\n", exName) 403 | if len(ex.Fields) == 0 { 404 | g.write(out, "\treturn \"%s{}\"\n", exName) 405 | } else { 406 | fieldNames := make([]string, len(ex.Fields)) 407 | fieldVars := make([]string, len(ex.Fields)) 408 | for i, field := range ex.Fields { 409 | fieldNames[i] = camelCase(field.Name) + ": %+v" 410 | fieldVars[i] = "e." + camelCase(field.Name) 411 | } 412 | g.write(out, "\treturn fmt.Sprintf(\"%s{%s}\", %s)\n", 413 | exName, strings.Join(fieldNames, ", "), strings.Join(fieldVars, ", ")) 414 | } 415 | return g.write(out, "}\n") 416 | } 417 | 418 | func (g *GoGenerator) writeService(out io.Writer, svc *parser.Service) error { 419 | svcName := camelCase(svc.Name) 420 | 421 | // Service interface 422 | 423 | g.write(out, "\ntype %s interface {\n", svcName) 424 | if svc.Extends != "" { 425 | g.write(out, "\t%s\n", camelCase(svc.Extends)) 426 | } 427 | methodNames := sortedKeys(svc.Methods) 428 | for _, k := range methodNames { 429 | method := svc.Methods[k] 430 | g.write(out, 431 | "\t%s(%s) %s\n", 432 | camelCase(method.Name), g.formatArguments(method.Arguments), 433 | g.formatReturnType(method.ReturnType, false)) 434 | } 435 | g.write(out, "}\n") 436 | 437 | // Server 438 | 439 | if svc.Extends == "" { 440 | g.write(out, "\ntype %sServer struct {\n\tImplementation %s\n}\n", svcName, svcName) 441 | } else { 442 | g.write(out, "\ntype %sServer struct {\n\t%sServer\n\tImplementation %s\n}\n", svcName, camelCase(svc.Extends), svcName) 443 | } 444 | 445 | // Server method wrappers 446 | 447 | for _, k := range methodNames { 448 | method := svc.Methods[k] 449 | mName := camelCase(method.Name) 450 | resArg := "" 451 | if !method.Oneway { 452 | resArg = fmt.Sprintf(", res *%s%sResponse", svcName, mName) 453 | } 454 | g.write(out, "\nfunc (s *%sServer) %s(req *%s%sRequest%s) error {\n", svcName, mName, svcName, mName, resArg) 455 | args := make([]string, 0) 456 | for _, arg := range method.Arguments { 457 | aName := camelCase(arg.Name) 458 | args = append(args, "req."+aName) 459 | } 460 | isVoid := method.ReturnType == nil || method.ReturnType.Name == "void" 461 | val := "" 462 | if !isVoid { 463 | val = "val, " 464 | } 465 | g.write(out, "\t%serr := s.Implementation.%s(%s)\n", val, mName, strings.Join(args, ", ")) 466 | if len(method.Exceptions) > 0 { 467 | g.write(out, "\tswitch e := err.(type) {\n") 468 | for _, ex := range method.Exceptions { 469 | g.write(out, "\tcase %s:\n\t\tres.%s = e\n\t\terr = nil\n", g.formatType(g.pkg, g.thrift, ex.Type, false), camelCase(ex.Name)) 470 | } 471 | g.write(out, "\t}\n") 472 | } 473 | if !isVoid { 474 | if !*flagGoPointers && basicTypes[g.resolveType(method.ReturnType)] { 475 | g.write(out, "\tres.Value = &val\n") 476 | } else { 477 | g.write(out, "\tres.Value = val\n") 478 | } 479 | } 480 | g.write(out, "\treturn err\n}\n") 481 | } 482 | 483 | for _, k := range methodNames { 484 | // Request struct 485 | method := svc.Methods[k] 486 | reqStructName := svcName + camelCase(method.Name) + "Request" 487 | if err := g.writeStruct(out, &parser.Struct{Name: reqStructName, Fields: method.Arguments}); err != nil { 488 | return err 489 | } 490 | 491 | if method.Oneway { 492 | g.write(out, "\nfunc (r *%s) Oneway() bool {\n\treturn true\n}\n", reqStructName) 493 | } else { 494 | // Response struct 495 | args := make([]*parser.Field, 0, len(method.Exceptions)) 496 | if method.ReturnType != nil && method.ReturnType.Name != "void" { 497 | args = append(args, &parser.Field{ID: 0, Name: "value", Optional: true /*len(method.Exceptions) != 0*/, Type: method.ReturnType, Default: nil}) 498 | } 499 | for _, ex := range method.Exceptions { 500 | args = append(args, ex) 501 | } 502 | res := &parser.Struct{Name: svcName + camelCase(method.Name) + "Response", Fields: args} 503 | if err := g.writeStruct(out, res); err != nil { 504 | return err 505 | } 506 | } 507 | } 508 | 509 | if svc.Extends == "" { 510 | g.write(out, "\ntype %sClient struct {\n\tClient RPCClient\n}\n", svcName) 511 | } else { 512 | g.write(out, "\ntype %sClient struct {\n\t%sClient\n}\n", svcName, camelCase(svc.Extends)) 513 | } 514 | 515 | for _, k := range methodNames { 516 | method := svc.Methods[k] 517 | methodName := camelCase(method.Name) 518 | returnType := "err error" 519 | if !method.Oneway { 520 | returnType = g.formatReturnType(method.ReturnType, true) 521 | } 522 | g.write(out, "\nfunc (s *%sClient) %s(%s) %s {\n", 523 | svcName, methodName, 524 | g.formatArguments(method.Arguments), 525 | returnType) 526 | 527 | // Request 528 | g.write(out, "\treq := &%s%sRequest{\n", svcName, methodName) 529 | for _, arg := range method.Arguments { 530 | g.write(out, "\t\t%s: %s,\n", camelCase(arg.Name), validGoIdent(lowerCamelCase(arg.Name))) 531 | } 532 | g.write(out, "\t}\n") 533 | 534 | // Response 535 | if method.Oneway { 536 | // g.write(out, "\tvar res *%s%sResponse = nil\n", svcName, methodName) 537 | g.write(out, "\tvar res interface{} = nil\n") 538 | } else { 539 | g.write(out, "\tres := &%s%sResponse{}\n", svcName, methodName) 540 | } 541 | 542 | // Call 543 | g.write(out, "\terr = s.Client.Call(\"%s\", req, res)\n", method.Name) 544 | 545 | // Exceptions 546 | if len(method.Exceptions) > 0 { 547 | g.write(out, "\tif err == nil {\n\t\tswitch {\n") 548 | for _, ex := range method.Exceptions { 549 | exName := camelCase(ex.Name) 550 | g.write(out, "\t\tcase res.%s != nil:\n\t\t\terr = res.%s\n", exName, exName) 551 | } 552 | g.write(out, "\t\t}\n\t}\n") 553 | } 554 | 555 | if method.ReturnType != nil && method.ReturnType.Name != "void" { 556 | if !*flagGoPointers && basicTypes[g.resolveType(method.ReturnType)] { 557 | g.write(out, "\tif err == nil && res.Value != nil {\n\t ret = *res.Value\n}\n") 558 | } else { 559 | g.write(out, "\tif err == nil {\n\tret = res.Value\n}\n") 560 | } 561 | } 562 | g.write(out, "\treturn\n") 563 | g.write(out, "}\n") 564 | } 565 | 566 | return nil 567 | } 568 | 569 | func (g *GoGenerator) generateSingle(out io.Writer, thriftPath string, thrift *parser.Thrift) { 570 | packageName := g.Packages[thriftPath].Name 571 | g.thrift = thrift 572 | g.pkg = packageName 573 | 574 | g.write(out, "// This file is automatically generated. Do not modify.\n") 575 | g.write(out, "\npackage %s\n", packageName) 576 | 577 | // Imports 578 | imports := []string{"fmt"} 579 | if len(thrift.Enums) > 0 { 580 | imports = append(imports, "strconv") 581 | } 582 | if len(thrift.Includes) > 0 { 583 | for _, path := range thrift.Includes { 584 | pkg := g.Packages[path].Name 585 | if pkg != packageName { 586 | imports = append(imports, pkg) 587 | } 588 | } 589 | } 590 | if len(imports) > 0 { 591 | g.write(out, "\nimport (\n") 592 | for _, in := range imports { 593 | g.write(out, "\t\"%s\"\n", in) 594 | } 595 | g.write(out, ")\n") 596 | } 597 | 598 | g.write(out, "\nvar _ = fmt.Printf\n") 599 | 600 | // 601 | 602 | if len(thrift.Constants) > 0 { 603 | for _, k := range sortedKeys(thrift.Constants) { 604 | c := thrift.Constants[k] 605 | v, err := g.formatValue(c.Value, c.Type) 606 | if err != nil { 607 | g.error(err) 608 | } 609 | if c.Type.Name == "list" || c.Type.Name == "map" || c.Type.Name == "set" { 610 | g.write(out, "var ") 611 | } else { 612 | g.write(out, "const ") 613 | } 614 | g.write(out, "\t%s = %+v\n", camelCase(c.Name), v) 615 | } 616 | } 617 | 618 | for _, k := range sortedKeys(thrift.Enums) { 619 | enum := thrift.Enums[k] 620 | if err := g.writeEnum(out, enum); err != nil { 621 | g.error(err) 622 | } 623 | } 624 | 625 | for _, k := range sortedKeys(thrift.Structs) { 626 | st := thrift.Structs[k] 627 | if err := g.writeStruct(out, st); err != nil { 628 | g.error(err) 629 | } 630 | } 631 | 632 | for _, k := range sortedKeys(thrift.Exceptions) { 633 | ex := thrift.Exceptions[k] 634 | if err := g.writeException(out, ex); err != nil { 635 | g.error(err) 636 | } 637 | } 638 | 639 | for _, k := range sortedKeys(thrift.Services) { 640 | svc := thrift.Services[k] 641 | if err := g.writeService(out, svc); err != nil { 642 | g.error(err) 643 | } 644 | } 645 | } 646 | 647 | func (g *GoGenerator) Generate(outPath string) (err error) { 648 | defer func() { 649 | if r := recover(); r != nil { 650 | if _, ok := r.(runtime.Error); ok { 651 | panic(r) 652 | } 653 | err = r.(error) 654 | } 655 | }() 656 | 657 | // Generate package namespace mapping if necessary 658 | if g.Packages == nil { 659 | g.Packages = make(map[string]GoPackage) 660 | } 661 | for path, th := range g.ThriftFiles { 662 | if pkg, ok := g.Packages[path]; !ok || pkg.Name == "" { 663 | pkg := GoPackage{} 664 | for _, k := range goNamespaceOrder { 665 | pkg.Name = th.Namespaces[k] 666 | if pkg.Name != "" { 667 | parts := strings.Split(pkg.Name, ".") 668 | if len(parts) > 1 { 669 | pkg.Path = strings.Join(parts[:len(parts)-1], "/") 670 | pkg.Name = parts[len(parts)-1] 671 | } 672 | break 673 | } 674 | } 675 | if pkg.Name == "" { 676 | pkg.Name = filepath.Base(path) 677 | } 678 | pkg.Name = validIdentifier(strings.ToLower(pkg.Name), "_") 679 | g.Packages[path] = pkg 680 | } 681 | } 682 | 683 | rpcPackages := map[string]string{} 684 | 685 | for path, th := range g.ThriftFiles { 686 | pkg := g.Packages[path] 687 | filename := strings.ToLower(filepath.Base(path)) 688 | for i := len(filename) - 1; i >= 0; i-- { 689 | if filename[i] == '.' { 690 | filename = filename[:i] 691 | } 692 | } 693 | filename += ".go" 694 | pkgpath := filepath.Join(outPath, pkg.Path, pkg.Name) 695 | outfile := filepath.Join(pkgpath, filename) 696 | 697 | if err := os.MkdirAll(pkgpath, 0755); err != nil { 698 | g.error(err) 699 | } 700 | 701 | out := &bytes.Buffer{} 702 | g.generateSingle(out, path, th) 703 | 704 | if len(th.Services) > 0 { 705 | rpcPackages[pkgpath] = pkg.Name 706 | } 707 | 708 | outBytes := out.Bytes() 709 | if g.Format { 710 | outBytes, err = format.Source(outBytes) 711 | if err != nil { 712 | g.error(err) 713 | } 714 | } 715 | 716 | fi, err := os.OpenFile(outfile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) 717 | if err != nil { 718 | fmt.Fprintf(os.Stderr, "%s\n", err.Error()) 719 | os.Exit(2) 720 | } 721 | if _, err := fi.Write(outBytes); err != nil { 722 | fi.Close() 723 | g.error(err) 724 | } 725 | fi.Close() 726 | } 727 | 728 | for path, name := range rpcPackages { 729 | outfile := filepath.Join(path, "rpc_stub.go") 730 | 731 | fi, err := os.OpenFile(outfile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) 732 | if err != nil { 733 | fmt.Fprintf(os.Stderr, "%s\n", err.Error()) 734 | os.Exit(2) 735 | } 736 | _, err = fi.WriteString(fmt.Sprintf("package %s\n\ntype RPCClient interface {\n"+ 737 | "\tCall(method string, request interface{}, response interface{}) error\n"+ 738 | "}\n", name)) 739 | fi.Close() 740 | if err != nil { 741 | fmt.Fprintf(os.Stderr, "%s\n", err.Error()) 742 | os.Exit(2) 743 | } 744 | } 745 | 746 | return nil 747 | } 748 | -------------------------------------------------------------------------------- /testfiles/Hbase.thrift: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | // ---------------------------------------------------------------- 20 | // Hbase.thrift 21 | // 22 | // This is a Thrift interface definition file for the Hbase service. 23 | // Target language libraries for C++, Java, Ruby, PHP, (and more) are 24 | // generated by running this file through the Thrift compiler with the 25 | // appropriate flags. The Thrift compiler binary and runtime 26 | // libraries for various languages are available 27 | // from the Apache Incubator (http://incubator.apache.org/thrift/) 28 | // 29 | // See the package.html file for information on the version of Thrift 30 | // used to generate the *.java files checked into the Hbase project. 31 | // ---------------------------------------------------------------- 32 | 33 | namespace java org.apache.hadoop.hbase.thrift.generated 34 | namespace cpp apache.hadoop.hbase.thrift 35 | namespace rb Apache.Hadoop.Hbase.Thrift 36 | namespace py hbase 37 | namespace perl Hbase 38 | namespace php Hbase 39 | // 40 | // Types 41 | // 42 | 43 | // NOTE: all variables with the Text type are assumed to be correctly 44 | // formatted UTF-8 strings. This is a programming language and locale 45 | // dependent property that the client application is repsonsible for 46 | // maintaining. If strings with an invalid encoding are sent, an 47 | // IOError will be thrown. 48 | 49 | typedef binary Text 50 | typedef binary Bytes 51 | typedef i32 ScannerID 52 | 53 | /** 54 | * TCell - Used to transport a cell value (byte[]) and the timestamp it was 55 | * stored with together as a result for get and getRow methods. This promotes 56 | * the timestamp of a cell to a first-class value, making it easy to take 57 | * note of temporal data. Cell is used all the way from HStore up to HTable. 58 | */ 59 | struct TCell{ 60 | 1:Bytes value, 61 | 2:i64 timestamp 62 | } 63 | 64 | /** 65 | * An HColumnDescriptor contains information about a column family 66 | * such as the number of versions, compression settings, etc. It is 67 | * used as input when creating a table or adding a column. 68 | */ 69 | struct ColumnDescriptor { 70 | 1:Text name, 71 | 2:i32 maxVersions = 3, 72 | 3:string compression = "NONE", 73 | 4:bool inMemory = 0, 74 | 5:string bloomFilterType = "NONE", 75 | 6:i32 bloomFilterVectorSize = 0, 76 | 7:i32 bloomFilterNbHashes = 0, 77 | 8:bool blockCacheEnabled = 0, 78 | 9:i32 timeToLive = -1 79 | } 80 | 81 | /** 82 | * A TRegionInfo contains information about an HTable region. 83 | */ 84 | struct TRegionInfo { 85 | 1:Text startKey, 86 | 2:Text endKey, 87 | 3:i64 id, 88 | 4:Text name, 89 | 5:byte version, 90 | 6:Text serverName, 91 | 7:i32 port 92 | } 93 | 94 | /** 95 | * A Mutation object is used to either update or delete a column-value. 96 | */ 97 | struct Mutation { 98 | 1:bool isDelete = 0, 99 | 2:Text column, 100 | 3:Text value, 101 | 4:bool writeToWAL = 1 102 | } 103 | 104 | 105 | /** 106 | * A BatchMutation object is used to apply a number of Mutations to a single row. 107 | */ 108 | struct BatchMutation { 109 | 1:Text row, 110 | 2:list mutations 111 | } 112 | 113 | /** 114 | * For increments that are not incrementColumnValue 115 | * equivalents. 116 | */ 117 | struct TIncrement { 118 | 1:Text table, 119 | 2:Text row, 120 | 3:Text column, 121 | 4:i64 ammount 122 | } 123 | 124 | /** 125 | * Holds column name and the cell. 126 | */ 127 | struct TColumn { 128 | 1:Text columnName, 129 | 2:TCell cell 130 | } 131 | 132 | /** 133 | * Holds row name and then a map of columns to cells. 134 | */ 135 | struct TRowResult { 136 | 1:Text row, 137 | 2:optional map columns, 138 | 3:optional list sortedColumns 139 | } 140 | 141 | /** 142 | * A Scan object is used to specify scanner parameters when opening a scanner. 143 | */ 144 | struct TScan { 145 | 1:optional Text startRow, 146 | 2:optional Text stopRow, 147 | 3:optional i64 timestamp, 148 | 4:optional list columns, 149 | 5:optional i32 caching, 150 | 6:optional Text filterString, 151 | 7:optional i32 batchSize, 152 | 8:optional bool sortColumns 153 | } 154 | 155 | // 156 | // Exceptions 157 | // 158 | /** 159 | * An IOError exception signals that an error occurred communicating 160 | * to the Hbase master or an Hbase region server. Also used to return 161 | * more general Hbase error conditions. 162 | */ 163 | exception IOError { 164 | 1:string message 165 | } 166 | 167 | /** 168 | * An IllegalArgument exception indicates an illegal or invalid 169 | * argument was passed into a procedure. 170 | */ 171 | exception IllegalArgument { 172 | 1:string message 173 | } 174 | 175 | /** 176 | * An AlreadyExists exceptions signals that a table with the specified 177 | * name already exists 178 | */ 179 | exception AlreadyExists { 180 | 1:string message 181 | } 182 | 183 | // 184 | // Service 185 | // 186 | 187 | service Hbase { 188 | /** 189 | * Brings a table on-line (enables it) 190 | */ 191 | void enableTable( 192 | /** name of the table */ 193 | 1:Bytes tableName 194 | ) throws (1:IOError io) 195 | 196 | /** 197 | * Disables a table (takes it off-line) If it is being served, the master 198 | * will tell the servers to stop serving it. 199 | */ 200 | void disableTable( 201 | /** name of the table */ 202 | 1:Bytes tableName 203 | ) throws (1:IOError io) 204 | 205 | /** 206 | * @return true if table is on-line 207 | */ 208 | bool isTableEnabled( 209 | /** name of the table to check */ 210 | 1:Bytes tableName 211 | ) throws (1:IOError io) 212 | 213 | void compact(1:Bytes tableNameOrRegionName) 214 | throws (1:IOError io) 215 | 216 | void majorCompact(1:Bytes tableNameOrRegionName) 217 | throws (1:IOError io) 218 | 219 | /** 220 | * List all the userspace tables. 221 | * 222 | * @return returns a list of names 223 | */ 224 | list getTableNames() 225 | throws (1:IOError io) 226 | 227 | /** 228 | * List all the column families assoicated with a table. 229 | * 230 | * @return list of column family descriptors 231 | */ 232 | map getColumnDescriptors ( 233 | /** table name */ 234 | 1:Text tableName 235 | ) throws (1:IOError io) 236 | 237 | /** 238 | * List the regions associated with a table. 239 | * 240 | * @return list of region descriptors 241 | */ 242 | list getTableRegions( 243 | /** table name */ 244 | 1:Text tableName) 245 | throws (1:IOError io) 246 | 247 | /** 248 | * Create a table with the specified column families. The name 249 | * field for each ColumnDescriptor must be set and must end in a 250 | * colon (:). All other fields are optional and will get default 251 | * values if not explicitly specified. 252 | * 253 | * @throws IllegalArgument if an input parameter is invalid 254 | * 255 | * @throws AlreadyExists if the table name already exists 256 | */ 257 | void createTable( 258 | /** name of table to create */ 259 | 1:Text tableName, 260 | 261 | /** list of column family descriptors */ 262 | 2:list columnFamilies 263 | ) throws (1:IOError io, 2:IllegalArgument ia, 3:AlreadyExists exist) 264 | 265 | /** 266 | * Deletes a table 267 | * 268 | * @throws IOError if table doesn't exist on server or there was some other 269 | * problem 270 | */ 271 | void deleteTable( 272 | /** name of table to delete */ 273 | 1:Text tableName 274 | ) throws (1:IOError io) 275 | 276 | /** 277 | * Get a single TCell for the specified table, row, and column at the 278 | * latest timestamp. Returns an empty list if no such value exists. 279 | * 280 | * @return value for specified row/column 281 | */ 282 | list get( 283 | /** name of table */ 284 | 1:Text tableName, 285 | 286 | /** row key */ 287 | 2:Text row, 288 | 289 | /** column name */ 290 | 3:Text column, 291 | 292 | /** Get attributes */ 293 | 4:map attributes 294 | ) throws (1:IOError io) 295 | 296 | /** 297 | * Get the specified number of versions for the specified table, 298 | * row, and column. 299 | * 300 | * @return list of cells for specified row/column 301 | */ 302 | list getVer( 303 | /** name of table */ 304 | 1:Text tableName, 305 | 306 | /** row key */ 307 | 2:Text row, 308 | 309 | /** column name */ 310 | 3:Text column, 311 | 312 | /** number of versions to retrieve */ 313 | 4:i32 numVersions, 314 | 315 | /** Get attributes */ 316 | 5:map attributes 317 | ) throws (1:IOError io) 318 | 319 | /** 320 | * Get the specified number of versions for the specified table, 321 | * row, and column. Only versions less than or equal to the specified 322 | * timestamp will be returned. 323 | * 324 | * @return list of cells for specified row/column 325 | */ 326 | list getVerTs( 327 | /** name of table */ 328 | 1:Text tableName, 329 | 330 | /** row key */ 331 | 2:Text row, 332 | 333 | /** column name */ 334 | 3:Text column, 335 | 336 | /** timestamp */ 337 | 4:i64 timestamp, 338 | 339 | /** number of versions to retrieve */ 340 | 5:i32 numVersions, 341 | 342 | /** Get attributes */ 343 | 6:map attributes 344 | ) throws (1:IOError io) 345 | 346 | /** 347 | * Get all the data for the specified table and row at the latest 348 | * timestamp. Returns an empty list if the row does not exist. 349 | * 350 | * @return TRowResult containing the row and map of columns to TCells 351 | */ 352 | list getRow( 353 | /** name of table */ 354 | 1:Text tableName, 355 | 356 | /** row key */ 357 | 2:Text row, 358 | 359 | /** Get attributes */ 360 | 3:map attributes 361 | ) throws (1:IOError io) 362 | 363 | /** 364 | * Get the specified columns for the specified table and row at the latest 365 | * timestamp. Returns an empty list if the row does not exist. 366 | * 367 | * @return TRowResult containing the row and map of columns to TCells 368 | */ 369 | list getRowWithColumns( 370 | /** name of table */ 371 | 1:Text tableName, 372 | 373 | /** row key */ 374 | 2:Text row, 375 | 376 | /** List of columns to return, null for all columns */ 377 | 3:list columns, 378 | 379 | /** Get attributes */ 380 | 4:map attributes 381 | ) throws (1:IOError io) 382 | 383 | /** 384 | * Get all the data for the specified table and row at the specified 385 | * timestamp. Returns an empty list if the row does not exist. 386 | * 387 | * @return TRowResult containing the row and map of columns to TCells 388 | */ 389 | list getRowTs( 390 | /** name of the table */ 391 | 1:Text tableName, 392 | 393 | /** row key */ 394 | 2:Text row, 395 | 396 | /** timestamp */ 397 | 3:i64 timestamp, 398 | 399 | /** Get attributes */ 400 | 4:map attributes 401 | ) throws (1:IOError io) 402 | 403 | /** 404 | * Get the specified columns for the specified table and row at the specified 405 | * timestamp. Returns an empty list if the row does not exist. 406 | * 407 | * @return TRowResult containing the row and map of columns to TCells 408 | */ 409 | list getRowWithColumnsTs( 410 | /** name of table */ 411 | 1:Text tableName, 412 | 413 | /** row key */ 414 | 2:Text row, 415 | 416 | /** List of columns to return, null for all columns */ 417 | 3:list columns, 418 | 4:i64 timestamp, 419 | 420 | /** Get attributes */ 421 | 5:map attributes 422 | ) throws (1:IOError io) 423 | 424 | /** 425 | * Get all the data for the specified table and rows at the latest 426 | * timestamp. Returns an empty list if no rows exist. 427 | * 428 | * @return TRowResult containing the rows and map of columns to TCells 429 | */ 430 | list getRows( 431 | /** name of table */ 432 | 1:Text tableName, 433 | 434 | /** row keys */ 435 | 2:list rows 436 | 437 | /** Get attributes */ 438 | 3:map attributes 439 | ) throws (1:IOError io) 440 | 441 | /** 442 | * Get the specified columns for the specified table and rows at the latest 443 | * timestamp. Returns an empty list if no rows exist. 444 | * 445 | * @return TRowResult containing the rows and map of columns to TCells 446 | */ 447 | list getRowsWithColumns( 448 | /** name of table */ 449 | 1:Text tableName, 450 | 451 | /** row keys */ 452 | 2:list rows, 453 | 454 | /** List of columns to return, null for all columns */ 455 | 3:list columns, 456 | 457 | /** Get attributes */ 458 | 4:map attributes 459 | ) throws (1:IOError io) 460 | 461 | /** 462 | * Get all the data for the specified table and rows at the specified 463 | * timestamp. Returns an empty list if no rows exist. 464 | * 465 | * @return TRowResult containing the rows and map of columns to TCells 466 | */ 467 | list getRowsTs( 468 | /** name of the table */ 469 | 1:Text tableName, 470 | 471 | /** row keys */ 472 | 2:list rows 473 | 474 | /** timestamp */ 475 | 3:i64 timestamp, 476 | 477 | /** Get attributes */ 478 | 4:map attributes 479 | ) throws (1:IOError io) 480 | 481 | /** 482 | * Get the specified columns for the specified table and rows at the specified 483 | * timestamp. Returns an empty list if no rows exist. 484 | * 485 | * @return TRowResult containing the rows and map of columns to TCells 486 | */ 487 | list getRowsWithColumnsTs( 488 | /** name of table */ 489 | 1:Text tableName, 490 | 491 | /** row keys */ 492 | 2:list rows 493 | 494 | /** List of columns to return, null for all columns */ 495 | 3:list columns, 496 | 4:i64 timestamp, 497 | 498 | /** Get attributes */ 499 | 5:map attributes 500 | ) throws (1:IOError io) 501 | 502 | /** 503 | * Apply a series of mutations (updates/deletes) to a row in a 504 | * single transaction. If an exception is thrown, then the 505 | * transaction is aborted. Default current timestamp is used, and 506 | * all entries will have an identical timestamp. 507 | */ 508 | void mutateRow( 509 | /** name of table */ 510 | 1:Text tableName, 511 | 512 | /** row key */ 513 | 2:Text row, 514 | 515 | /** list of mutation commands */ 516 | 3:list mutations, 517 | 518 | /** Mutation attributes */ 519 | 4:map attributes 520 | ) throws (1:IOError io, 2:IllegalArgument ia) 521 | 522 | /** 523 | * Apply a series of mutations (updates/deletes) to a row in a 524 | * single transaction. If an exception is thrown, then the 525 | * transaction is aborted. The specified timestamp is used, and 526 | * all entries will have an identical timestamp. 527 | */ 528 | void mutateRowTs( 529 | /** name of table */ 530 | 1:Text tableName, 531 | 532 | /** row key */ 533 | 2:Text row, 534 | 535 | /** list of mutation commands */ 536 | 3:list mutations, 537 | 538 | /** timestamp */ 539 | 4:i64 timestamp, 540 | 541 | /** Mutation attributes */ 542 | 5:map attributes 543 | ) throws (1:IOError io, 2:IllegalArgument ia) 544 | 545 | /** 546 | * Apply a series of batches (each a series of mutations on a single row) 547 | * in a single transaction. If an exception is thrown, then the 548 | * transaction is aborted. Default current timestamp is used, and 549 | * all entries will have an identical timestamp. 550 | */ 551 | void mutateRows( 552 | /** name of table */ 553 | 1:Text tableName, 554 | 555 | /** list of row batches */ 556 | 2:list rowBatches, 557 | 558 | /** Mutation attributes */ 559 | 3:map attributes 560 | ) throws (1:IOError io, 2:IllegalArgument ia) 561 | 562 | /** 563 | * Apply a series of batches (each a series of mutations on a single row) 564 | * in a single transaction. If an exception is thrown, then the 565 | * transaction is aborted. The specified timestamp is used, and 566 | * all entries will have an identical timestamp. 567 | */ 568 | void mutateRowsTs( 569 | /** name of table */ 570 | 1:Text tableName, 571 | 572 | /** list of row batches */ 573 | 2:list rowBatches, 574 | 575 | /** timestamp */ 576 | 3:i64 timestamp, 577 | 578 | /** Mutation attributes */ 579 | 4:map attributes 580 | ) throws (1:IOError io, 2:IllegalArgument ia) 581 | 582 | /** 583 | * Atomically increment the column value specified. Returns the next value post increment. 584 | */ 585 | i64 atomicIncrement( 586 | /** name of table */ 587 | 1:Text tableName, 588 | 589 | /** row to increment */ 590 | 2:Text row, 591 | 592 | /** name of column */ 593 | 3:Text column, 594 | 595 | /** amount to increment by */ 596 | 4:i64 value 597 | ) throws (1:IOError io, 2:IllegalArgument ia) 598 | 599 | /** 600 | * Delete all cells that match the passed row and column. 601 | */ 602 | void deleteAll( 603 | /** name of table */ 604 | 1:Text tableName, 605 | 606 | /** Row to update */ 607 | 2:Text row, 608 | 609 | /** name of column whose value is to be deleted */ 610 | 3:Text column, 611 | 612 | /** Delete attributes */ 613 | 4:map attributes 614 | ) throws (1:IOError io) 615 | 616 | /** 617 | * Delete all cells that match the passed row and column and whose 618 | * timestamp is equal-to or older than the passed timestamp. 619 | */ 620 | void deleteAllTs( 621 | /** name of table */ 622 | 1:Text tableName, 623 | 624 | /** Row to update */ 625 | 2:Text row, 626 | 627 | /** name of column whose value is to be deleted */ 628 | 3:Text column, 629 | 630 | /** timestamp */ 631 | 4:i64 timestamp, 632 | 633 | /** Delete attributes */ 634 | 5:map attributes 635 | ) throws (1:IOError io) 636 | 637 | /** 638 | * Completely delete the row's cells. 639 | */ 640 | void deleteAllRow( 641 | /** name of table */ 642 | 1:Text tableName, 643 | 644 | /** key of the row to be completely deleted. */ 645 | 2:Text row, 646 | 647 | /** Delete attributes */ 648 | 3:map attributes 649 | ) throws (1:IOError io) 650 | 651 | /** 652 | * Increment a cell by the ammount. 653 | * Increments can be applied async if hbase.regionserver.thrift.coalesceIncrement is set to true. 654 | * False is the default. Turn to true if you need the extra performance and can accept some 655 | * data loss if a thrift server dies with increments still in the queue. 656 | */ 657 | void increment( 658 | /** The single increment to apply */ 659 | 1:TIncrement increment 660 | ) throws (1:IOError io) 661 | 662 | 663 | void incrementRows( 664 | /** The list of increments */ 665 | 1:list increments 666 | ) throws (1:IOError io) 667 | 668 | /** 669 | * Completely delete the row's cells marked with a timestamp 670 | * equal-to or older than the passed timestamp. 671 | */ 672 | void deleteAllRowTs( 673 | /** name of table */ 674 | 1:Text tableName, 675 | 676 | /** key of the row to be completely deleted. */ 677 | 2:Text row, 678 | 679 | /** timestamp */ 680 | 3:i64 timestamp, 681 | 682 | /** Delete attributes */ 683 | 4:map attributes 684 | ) throws (1:IOError io) 685 | 686 | /** 687 | * Get a scanner on the current table, using the Scan instance 688 | * for the scan parameters. 689 | */ 690 | ScannerID scannerOpenWithScan( 691 | /** name of table */ 692 | 1:Text tableName, 693 | 694 | /** Scan instance */ 695 | 2:TScan scan, 696 | 697 | /** Scan attributes */ 698 | 3:map attributes 699 | ) throws (1:IOError io) 700 | 701 | /** 702 | * Get a scanner on the current table starting at the specified row and 703 | * ending at the last row in the table. Return the specified columns. 704 | * 705 | * @return scanner id to be used with other scanner procedures 706 | */ 707 | ScannerID scannerOpen( 708 | /** name of table */ 709 | 1:Text tableName, 710 | 711 | /** 712 | * Starting row in table to scan. 713 | * Send "" (empty string) to start at the first row. 714 | */ 715 | 2:Text startRow, 716 | 717 | /** 718 | * columns to scan. If column name is a column family, all 719 | * columns of the specified column family are returned. It's also possible 720 | * to pass a regex in the column qualifier. 721 | */ 722 | 3:list columns, 723 | 724 | /** Scan attributes */ 725 | 4:map attributes 726 | ) throws (1:IOError io) 727 | 728 | /** 729 | * Get a scanner on the current table starting and stopping at the 730 | * specified rows. ending at the last row in the table. Return the 731 | * specified columns. 732 | * 733 | * @return scanner id to be used with other scanner procedures 734 | */ 735 | ScannerID scannerOpenWithStop( 736 | /** name of table */ 737 | 1:Text tableName, 738 | 739 | /** 740 | * Starting row in table to scan. 741 | * Send "" (empty string) to start at the first row. 742 | */ 743 | 2:Text startRow, 744 | 745 | /** 746 | * row to stop scanning on. This row is *not* included in the 747 | * scanner's results 748 | */ 749 | 3:Text stopRow, 750 | 751 | /** 752 | * columns to scan. If column name is a column family, all 753 | * columns of the specified column family are returned. It's also possible 754 | * to pass a regex in the column qualifier. 755 | */ 756 | 4:list columns, 757 | 758 | /** Scan attributes */ 759 | 5:map attributes 760 | ) throws (1:IOError io) 761 | 762 | /** 763 | * Open a scanner for a given prefix. That is all rows will have the specified 764 | * prefix. No other rows will be returned. 765 | * 766 | * @return scanner id to use with other scanner calls 767 | */ 768 | ScannerID scannerOpenWithPrefix( 769 | /** name of table */ 770 | 1:Text tableName, 771 | 772 | /** the prefix (and thus start row) of the keys you want */ 773 | 2:Text startAndPrefix, 774 | 775 | /** the columns you want returned */ 776 | 3:list columns, 777 | 778 | /** Scan attributes */ 779 | 4:map attributes 780 | ) throws (1:IOError io) 781 | 782 | /** 783 | * Get a scanner on the current table starting at the specified row and 784 | * ending at the last row in the table. Return the specified columns. 785 | * Only values with the specified timestamp are returned. 786 | * 787 | * @return scanner id to be used with other scanner procedures 788 | */ 789 | ScannerID scannerOpenTs( 790 | /** name of table */ 791 | 1:Text tableName, 792 | 793 | /** 794 | * Starting row in table to scan. 795 | * Send "" (empty string) to start at the first row. 796 | */ 797 | 2:Text startRow, 798 | 799 | /** 800 | * columns to scan. If column name is a column family, all 801 | * columns of the specified column family are returned. It's also possible 802 | * to pass a regex in the column qualifier. 803 | */ 804 | 3:list columns, 805 | 806 | /** timestamp */ 807 | 4:i64 timestamp, 808 | 809 | /** Scan attributes */ 810 | 5:map attributes 811 | ) throws (1:IOError io) 812 | 813 | /** 814 | * Get a scanner on the current table starting and stopping at the 815 | * specified rows. ending at the last row in the table. Return the 816 | * specified columns. Only values with the specified timestamp are 817 | * returned. 818 | * 819 | * @return scanner id to be used with other scanner procedures 820 | */ 821 | ScannerID scannerOpenWithStopTs( 822 | /** name of table */ 823 | 1:Text tableName, 824 | 825 | /** 826 | * Starting row in table to scan. 827 | * Send "" (empty string) to start at the first row. 828 | */ 829 | 2:Text startRow, 830 | 831 | /** 832 | * row to stop scanning on. This row is *not* included in the 833 | * scanner's results 834 | */ 835 | 3:Text stopRow, 836 | 837 | /** 838 | * columns to scan. If column name is a column family, all 839 | * columns of the specified column family are returned. It's also possible 840 | * to pass a regex in the column qualifier. 841 | */ 842 | 4:list columns, 843 | 844 | /** timestamp */ 845 | 5:i64 timestamp, 846 | 847 | /** Scan attributes */ 848 | 6:map attributes 849 | ) throws (1:IOError io) 850 | 851 | /** 852 | * Returns the scanner's current row value and advances to the next 853 | * row in the table. When there are no more rows in the table, or a key 854 | * greater-than-or-equal-to the scanner's specified stopRow is reached, 855 | * an empty list is returned. 856 | * 857 | * @return a TRowResult containing the current row and a map of the columns to TCells. 858 | * 859 | * @throws IllegalArgument if ScannerID is invalid 860 | * 861 | * @throws NotFound when the scanner reaches the end 862 | */ 863 | list scannerGet( 864 | /** id of a scanner returned by scannerOpen */ 865 | 1:ScannerID id 866 | ) throws (1:IOError io, 2:IllegalArgument ia) 867 | 868 | /** 869 | * Returns, starting at the scanner's current row value nbRows worth of 870 | * rows and advances to the next row in the table. When there are no more 871 | * rows in the table, or a key greater-than-or-equal-to the scanner's 872 | * specified stopRow is reached, an empty list is returned. 873 | * 874 | * @return a TRowResult containing the current row and a map of the columns to TCells. 875 | * 876 | * @throws IllegalArgument if ScannerID is invalid 877 | * 878 | * @throws NotFound when the scanner reaches the end 879 | */ 880 | list scannerGetList( 881 | /** id of a scanner returned by scannerOpen */ 882 | 1:ScannerID id, 883 | 884 | /** number of results to return */ 885 | 2:i32 nbRows 886 | ) throws (1:IOError io, 2:IllegalArgument ia) 887 | 888 | /** 889 | * Closes the server-state associated with an open scanner. 890 | * 891 | * @throws IllegalArgument if ScannerID is invalid 892 | */ 893 | void scannerClose( 894 | /** id of a scanner returned by scannerOpen */ 895 | 1:ScannerID id 896 | ) throws (1:IOError io, 2:IllegalArgument ia) 897 | 898 | /** 899 | * Get the row just before the specified one. 900 | * 901 | * @return value for specified row/column 902 | */ 903 | list getRowOrBefore( 904 | /** name of table */ 905 | 1:Text tableName, 906 | 907 | /** row key */ 908 | 2:Text row, 909 | 910 | /** column name */ 911 | 3:Text family 912 | ) throws (1:IOError io) 913 | 914 | /** 915 | * Get the regininfo for the specified row. It scans 916 | * the metatable to find region's start and end keys. 917 | * 918 | * @return value for specified row/column 919 | */ 920 | TRegionInfo getRegionInfo( 921 | /** row key */ 922 | 1:Text row, 923 | 924 | ) throws (1:IOError io) 925 | } 926 | --------------------------------------------------------------------------------