twirp 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +20 -0
- data/README.md +3 -0
- data/lib/twirp.rb +2 -0
- data/lib/twirp/error.rb +86 -0
- data/lib/twirp/server.rb +66 -0
- data/lib/twirp/version.rb +3 -0
- data/protoc-gen-twirp_ruby/main.go +185 -0
- data/test/error_test.rb +83 -0
- data/twirp.gemspec +25 -0
- metadata +86 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: cd5d364eb9a55aa4f9f80a217d0c25a9a3b5377d
|
4
|
+
data.tar.gz: add09813fd7965e1c510b5774a30445e66859b3e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1a0e61629575ec564449543c16b6c4842541abaea0c97e944318aea5ed385941b155371cbe2926faf2fe0c50824d46d1eb44302bed983749859599b6906d9063
|
7
|
+
data.tar.gz: f4113090daee32f775653c563fdb2ba60bad0d34ccc0b2287e8636b820288411933d20e41988d43ae17035db737e3e7cdce4079ad888fd16a4fc14a4eb8ee608
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
twirp (0.0.1)
|
5
|
+
google-protobuf (>= 3.0.0)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
google-protobuf (3.5.1.2)
|
11
|
+
|
12
|
+
PLATFORMS
|
13
|
+
ruby
|
14
|
+
|
15
|
+
DEPENDENCIES
|
16
|
+
bundler (>= 1)
|
17
|
+
twirp!
|
18
|
+
|
19
|
+
BUNDLED WITH
|
20
|
+
1.14.6
|
data/README.md
ADDED
data/lib/twirp.rb
ADDED
data/lib/twirp/error.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
module Twirp
|
2
|
+
|
3
|
+
# Valid Twirp error codes and their mapping to related HTTP status.
|
4
|
+
# This can also be used to check if a code is valid (check if not nil).
|
5
|
+
ERROR_CODES_TO_HTTP_STATUS = {
|
6
|
+
canceled: 408, # RequestTimeout
|
7
|
+
invalid_argument: 400, # BadRequest
|
8
|
+
deadline_exceeded: 408, # RequestTimeout
|
9
|
+
not_found: 404, # Not Found
|
10
|
+
bad_route: 404, # Not Found
|
11
|
+
already_exists: 409, # Conflict
|
12
|
+
permission_denied: 403, # Forbidden
|
13
|
+
unauthenticated: 401, # Unauthorized
|
14
|
+
resource_exhausted: 403, # Forbidden
|
15
|
+
failed_precondition: 412, # Precondition Failed
|
16
|
+
aborted: 409, # Conflict
|
17
|
+
out_of_range: 400, # Bad Request
|
18
|
+
|
19
|
+
internal: 500, # Internal Server Error
|
20
|
+
unknown: 500, # Internal Server Error
|
21
|
+
unimplemented: 501, # Not Implemented
|
22
|
+
unavailable: 503, # Service Unavailable
|
23
|
+
data_loss: 500, # Internal Server Error
|
24
|
+
}
|
25
|
+
|
26
|
+
# List of all valid error codes in Twirp
|
27
|
+
ERROR_CODES = ERROR_CODES_TO_HTTP_STATUS.keys
|
28
|
+
|
29
|
+
# Twirp::Error represents a valid error from a Twirp service
|
30
|
+
class Error
|
31
|
+
|
32
|
+
# Initialize a Twirp::Error
|
33
|
+
# The code MUST be one of the valid ERROR_CODES Symbols (e.g. :internal, :not_found, :permission_denied ...).
|
34
|
+
# The msg is a String with the error message.
|
35
|
+
# The meta is optional error metadata, if included it MUST be a Hash with String keys and values.
|
36
|
+
def initialize(code, msg, meta=nil)
|
37
|
+
@code = validate_code(code)
|
38
|
+
@msg = msg.to_s
|
39
|
+
@meta = validate_meta(meta)
|
40
|
+
end
|
41
|
+
|
42
|
+
attr_reader :code
|
43
|
+
attr_reader :msg
|
44
|
+
def meta; @meta || {}; end
|
45
|
+
|
46
|
+
def as_json
|
47
|
+
h = {
|
48
|
+
code: @code,
|
49
|
+
msg: @msg,
|
50
|
+
}
|
51
|
+
h[:meta] = @meta if @meta
|
52
|
+
h
|
53
|
+
end
|
54
|
+
|
55
|
+
def to_json
|
56
|
+
JSON.encode(as_json)
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def validate_code(code)
|
62
|
+
if !code.is_a? Symbol
|
63
|
+
raise ArgumentError.new("Twirp::Error code must be a Symbol, but it is a #{code.class.to_s}")
|
64
|
+
end
|
65
|
+
if !ERROR_CODES_TO_HTTP_STATUS.has_key? code
|
66
|
+
raise ArgumentError.new("Twirp::Error code :#{code} is invalid. Expected one of #{ERROR_CODES.inspect}")
|
67
|
+
end
|
68
|
+
code
|
69
|
+
end
|
70
|
+
|
71
|
+
def validate_meta(meta)
|
72
|
+
return nil if !meta
|
73
|
+
if !meta.is_a? Hash
|
74
|
+
raise ArgumentError.new("Twirp::Error meta must be a Hash, but it is a #{meta.class.to_s}")
|
75
|
+
end
|
76
|
+
meta.each do |k, v|
|
77
|
+
if !k.is_a?(String) || !v.is_a?(String)
|
78
|
+
raise ArgumentError.new("Twirp::Error meta must be a Hash with String keys and values")
|
79
|
+
end
|
80
|
+
end
|
81
|
+
meta
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
data/lib/twirp/server.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
module Twirp
|
2
|
+
class Server
|
3
|
+
@@rpcs = {}
|
4
|
+
|
5
|
+
def initialize(svc)
|
6
|
+
@svc = svc
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.rpc(name, request_class, response_class)
|
10
|
+
@@rpcs[name] = {
|
11
|
+
request_class: request_class,
|
12
|
+
response_class: response_class
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
def route_request(req)
|
17
|
+
# Parse url to get method names
|
18
|
+
method_name = req.path_info[1..-1]
|
19
|
+
|
20
|
+
# Get req/res types from @@rpcs
|
21
|
+
rpc = @@rpcs[method_name.to_sym]
|
22
|
+
request_class = rpc[:request_class]
|
23
|
+
response_class = rpc[:response_class]
|
24
|
+
|
25
|
+
case req.env["CONTENT_TYPE"]
|
26
|
+
when "application/json"
|
27
|
+
return self.serve_json(req, method_name, request_class, response_class)
|
28
|
+
when "application/protobuf"
|
29
|
+
return self.serve_proto(req, method_name, request_class, response_class)
|
30
|
+
else
|
31
|
+
return self.serve_error(Twerr.NotFound("unexpected Content-Type: #{req.env["CONTENT_TYPE"]}"))
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def serve_json(req, method_name, request_class, response_class)
|
36
|
+
params = request_class.decode_json(req.body.read)
|
37
|
+
resp = @svc.send(method_name.underscore, params)
|
38
|
+
self.serve_success_json(response_class.encode_json(resp))
|
39
|
+
end
|
40
|
+
|
41
|
+
def serve_proto(req, method_name, request_class, response_class)
|
42
|
+
params = request_type.decode(req.body.read)
|
43
|
+
resp = @svc.send(method_name.underscore, params)
|
44
|
+
self.serve_success_proto(response_class.encode(resp))
|
45
|
+
end
|
46
|
+
|
47
|
+
def serve_success_proto(resp)
|
48
|
+
return ['200', {'Content-Type' => 'application/protobuf'}, [resp]]
|
49
|
+
end
|
50
|
+
|
51
|
+
def serve_success_json(resp)
|
52
|
+
return ['200', {'Content-Type' => 'application/json'}, [resp]]
|
53
|
+
end
|
54
|
+
|
55
|
+
def serve_error(twerr)
|
56
|
+
return ['500', {'Content-Type' => 'application/json'}, []]
|
57
|
+
end
|
58
|
+
|
59
|
+
def handler
|
60
|
+
return Proc.new do |env|
|
61
|
+
req = Rack::Request.new(env)
|
62
|
+
self.route_request(req)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,185 @@
|
|
1
|
+
package main
|
2
|
+
|
3
|
+
import (
|
4
|
+
"bytes"
|
5
|
+
"flag"
|
6
|
+
"fmt"
|
7
|
+
"io"
|
8
|
+
"io/ioutil"
|
9
|
+
"log"
|
10
|
+
"os"
|
11
|
+
"path"
|
12
|
+
"strings"
|
13
|
+
"unicode"
|
14
|
+
|
15
|
+
"github.com/golang/protobuf/proto"
|
16
|
+
"github.com/golang/protobuf/protoc-gen-go/descriptor"
|
17
|
+
plugin "github.com/golang/protobuf/protoc-gen-go/plugin"
|
18
|
+
)
|
19
|
+
|
20
|
+
const Version = "v5.2.0"
|
21
|
+
|
22
|
+
func main() {
|
23
|
+
versionFlag := flag.Bool("version", false, "print version and exit")
|
24
|
+
flag.Parse()
|
25
|
+
if *versionFlag {
|
26
|
+
fmt.Println(Version)
|
27
|
+
os.Exit(0)
|
28
|
+
}
|
29
|
+
|
30
|
+
g := newGenerator()
|
31
|
+
Main(g)
|
32
|
+
}
|
33
|
+
|
34
|
+
type Generator interface {
|
35
|
+
Generate(in *plugin.CodeGeneratorRequest) *plugin.CodeGeneratorResponse
|
36
|
+
}
|
37
|
+
|
38
|
+
func Main(g Generator) {
|
39
|
+
req := readGenRequest(os.Stdin)
|
40
|
+
resp := g.Generate(req)
|
41
|
+
writeResponse(os.Stdout, resp)
|
42
|
+
}
|
43
|
+
|
44
|
+
type generator struct {
|
45
|
+
output *bytes.Buffer
|
46
|
+
}
|
47
|
+
|
48
|
+
func newGenerator() *generator {
|
49
|
+
return &generator{output: new(bytes.Buffer)}
|
50
|
+
}
|
51
|
+
|
52
|
+
func (g *generator) Generate(in *plugin.CodeGeneratorRequest) *plugin.CodeGeneratorResponse {
|
53
|
+
|
54
|
+
resp := new(plugin.CodeGeneratorResponse)
|
55
|
+
for _, name := range in.FileToGenerate {
|
56
|
+
for _, f := range in.ProtoFile {
|
57
|
+
if f.GetName() == name {
|
58
|
+
respFile := g.generateFile(f)
|
59
|
+
if respFile != nil {
|
60
|
+
resp.File = append(resp.File, respFile)
|
61
|
+
}
|
62
|
+
continue
|
63
|
+
}
|
64
|
+
}
|
65
|
+
}
|
66
|
+
|
67
|
+
return resp
|
68
|
+
}
|
69
|
+
|
70
|
+
func (g *generator) generateFile(file *descriptor.FileDescriptorProto) *plugin.CodeGeneratorResponse_File {
|
71
|
+
pkgName := pkgName(file)
|
72
|
+
g.P(`# Code generated by protoc-gen-twirp_ruby, DO NOT EDIT.`)
|
73
|
+
for _, service := range file.Service {
|
74
|
+
serviceName := serviceName(service)
|
75
|
+
g.P(``)
|
76
|
+
g.P(fmt.Sprintf("class %s < Twirp::Server", serviceName))
|
77
|
+
g.P(fmt.Sprintf(`PATH_PREFIX = "/twirp/%s.%s"`, pkgName, serviceName))
|
78
|
+
for _, method := range service.GetMethod() {
|
79
|
+
methName := methodName(method)
|
80
|
+
inputName := methodInputName(method)
|
81
|
+
outputName := methodOutputName(method)
|
82
|
+
g.P(fmt.Sprintf(" rpc :%s, %s, %s", methName, inputName, outputName))
|
83
|
+
}
|
84
|
+
g.P(`end`)
|
85
|
+
}
|
86
|
+
|
87
|
+
resp := new(plugin.CodeGeneratorResponse_File)
|
88
|
+
resp.Name = proto.String(rubyFileName(file))
|
89
|
+
resp.Content = proto.String(g.output.String())
|
90
|
+
g.output.Reset()
|
91
|
+
|
92
|
+
return resp
|
93
|
+
}
|
94
|
+
|
95
|
+
func (g *generator) P(args ...string) {
|
96
|
+
for _, v := range args {
|
97
|
+
g.output.WriteString(v)
|
98
|
+
}
|
99
|
+
g.output.WriteByte('\n')
|
100
|
+
}
|
101
|
+
|
102
|
+
func rubyFileName(f *descriptor.FileDescriptorProto) string {
|
103
|
+
name := *f.Name
|
104
|
+
if ext := path.Ext(name); ext == ".proto" || ext == ".protodevel" {
|
105
|
+
name = name[:len(name)-len(ext)]
|
106
|
+
}
|
107
|
+
name += "_twirp.rb"
|
108
|
+
return name
|
109
|
+
}
|
110
|
+
|
111
|
+
func pkgName(file *descriptor.FileDescriptorProto) string {
|
112
|
+
return file.GetPackage()
|
113
|
+
}
|
114
|
+
|
115
|
+
func serviceName(service *descriptor.ServiceDescriptorProto) string {
|
116
|
+
return service.GetName()
|
117
|
+
}
|
118
|
+
|
119
|
+
func methodName(method *descriptor.MethodDescriptorProto) string {
|
120
|
+
return method.GetName()
|
121
|
+
}
|
122
|
+
|
123
|
+
// methodInputName returns the basename of the input type of a method in snake
|
124
|
+
// case.
|
125
|
+
func methodInputName(meth *descriptor.MethodDescriptorProto) string {
|
126
|
+
fullName := meth.GetInputType()
|
127
|
+
split := strings.Split(fullName, ".")
|
128
|
+
return split[len(split)-1]
|
129
|
+
}
|
130
|
+
|
131
|
+
// methodInputName returns the basename of the input type of a method in snake
|
132
|
+
// case.
|
133
|
+
func methodOutputName(meth *descriptor.MethodDescriptorProto) string {
|
134
|
+
fullName := meth.GetOutputType()
|
135
|
+
split := strings.Split(fullName, ".")
|
136
|
+
return split[len(split)-1]
|
137
|
+
}
|
138
|
+
|
139
|
+
func Fail(msgs ...string) {
|
140
|
+
s := strings.Join(msgs, " ")
|
141
|
+
log.Print("error:", s)
|
142
|
+
os.Exit(1)
|
143
|
+
}
|
144
|
+
|
145
|
+
// SnakeCase converts a string from CamelCase to snake_case.
|
146
|
+
func SnakeCase(s string) string {
|
147
|
+
var buf bytes.Buffer
|
148
|
+
for i, r := range s {
|
149
|
+
if unicode.IsUpper(r) && i > 0 {
|
150
|
+
fmt.Fprintf(&buf, "_")
|
151
|
+
}
|
152
|
+
r = unicode.ToLower(r)
|
153
|
+
fmt.Fprintf(&buf, "%c", r)
|
154
|
+
}
|
155
|
+
return buf.String()
|
156
|
+
}
|
157
|
+
|
158
|
+
func readGenRequest(r io.Reader) *plugin.CodeGeneratorRequest {
|
159
|
+
data, err := ioutil.ReadAll(os.Stdin)
|
160
|
+
if err != nil {
|
161
|
+
Fail(err.Error(), "reading input")
|
162
|
+
}
|
163
|
+
|
164
|
+
req := new(plugin.CodeGeneratorRequest)
|
165
|
+
if err = proto.Unmarshal(data, req); err != nil {
|
166
|
+
Fail(err.Error(), "parsing input proto")
|
167
|
+
}
|
168
|
+
|
169
|
+
if len(req.FileToGenerate) == 0 {
|
170
|
+
Fail("no files to generate")
|
171
|
+
}
|
172
|
+
|
173
|
+
return req
|
174
|
+
}
|
175
|
+
|
176
|
+
func writeResponse(w io.Writer, resp *plugin.CodeGeneratorResponse) {
|
177
|
+
data, err := proto.Marshal(resp)
|
178
|
+
if err != nil {
|
179
|
+
Fail(err.Error(), "marshaling response")
|
180
|
+
}
|
181
|
+
_, err = w.Write(data)
|
182
|
+
if err != nil {
|
183
|
+
Fail(err.Error(), "writing response")
|
184
|
+
}
|
185
|
+
}
|
data/test/error_test.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
|
3
|
+
require_relative '../lib/twirp/error'
|
4
|
+
|
5
|
+
class TestErrorCodes < Minitest::Test
|
6
|
+
|
7
|
+
def test_error_codes
|
8
|
+
assert_equal 17, Twirp::ERROR_CODES.size
|
9
|
+
|
10
|
+
# all codes should be symbols
|
11
|
+
Twirp::ERROR_CODES.each do |code|
|
12
|
+
assert_instance_of Symbol, code
|
13
|
+
end
|
14
|
+
|
15
|
+
# check some codes
|
16
|
+
assert_includes Twirp::ERROR_CODES, :internal
|
17
|
+
assert_includes Twirp::ERROR_CODES, :not_found
|
18
|
+
assert_includes Twirp::ERROR_CODES, :invalid_argument
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_codes_to_http_status
|
22
|
+
assert_equal 17, Twirp::ERROR_CODES_TO_HTTP_STATUS.size
|
23
|
+
|
24
|
+
assert_equal 404, Twirp::ERROR_CODES_TO_HTTP_STATUS[:not_found]
|
25
|
+
assert_equal 500, Twirp::ERROR_CODES_TO_HTTP_STATUS[:internal]
|
26
|
+
|
27
|
+
# nil for invalid_codes
|
28
|
+
assert_nil Twirp::ERROR_CODES_TO_HTTP_STATUS[:invalid_fdsafda]
|
29
|
+
assert_nil Twirp::ERROR_CODES_TO_HTTP_STATUS[500]
|
30
|
+
assert_nil Twirp::ERROR_CODES_TO_HTTP_STATUS[nil]
|
31
|
+
assert_nil Twirp::ERROR_CODES_TO_HTTP_STATUS["not_found"] # string checks not supported, please use symbols
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class TestTwirpError < Minitest::Test
|
36
|
+
|
37
|
+
def test_new_with_valid_code_and_a_message
|
38
|
+
err = Twirp::Error.new(:internal, "woops")
|
39
|
+
assert_equal :internal, err.code
|
40
|
+
assert_equal "woops", err.msg
|
41
|
+
assert_equal({}, err.meta) # empty
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_new_with_valid_metadata
|
45
|
+
err = Twirp::Error.new(:internal, "woops", "meta" => "data", "for this" => "error")
|
46
|
+
assert_equal err.meta["meta"], "data"
|
47
|
+
assert_equal "error", err.meta["for this"]
|
48
|
+
assert_nil err.meta["something else"]
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_invalid_code
|
52
|
+
assert_raises ArgumentError do
|
53
|
+
Twirp::Error.new(:invalid_code, "woops")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_invalid_metadata
|
58
|
+
Twirp::Error.new(:internal, "woops") # ensure the base case doesn't error
|
59
|
+
|
60
|
+
assert_raises ArgumentError do
|
61
|
+
Twirp::Error.new(:internal, "woops", non_string: "metadata")
|
62
|
+
end
|
63
|
+
|
64
|
+
assert_raises ArgumentError do
|
65
|
+
Twirp::Error.new(:internal, "woops", "string key" => :non_string_value)
|
66
|
+
end
|
67
|
+
|
68
|
+
assert_raises ArgumentError do
|
69
|
+
Twirp::Error.new(:internal, "woops", "valid key" => "valid val", "bad_one" => 666)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_as_json
|
74
|
+
# returns a hash with attributes
|
75
|
+
err = Twirp::Error.new(:internal, "err msg", "key" => "val")
|
76
|
+
assert_equal({code: :internal, msg: "err msg", meta: {"key" => "val"}}, err.as_json)
|
77
|
+
|
78
|
+
# skips meta if not included
|
79
|
+
err = Twirp::Error.new(:internal, "err msg")
|
80
|
+
assert_equal({code: :internal, msg: "err msg"}, err.as_json)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
data/twirp.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
|
5
|
+
require 'twirp/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = "twirp"
|
9
|
+
spec.version = Twirp::VERSION
|
10
|
+
spec.authors = ["Cyrus A. Forbes", "Mario Izquierdo"]
|
11
|
+
spec.email = ["forbescyrus@gmail.com", "tothemario@gmail.com"]
|
12
|
+
spec.summary = %q{Twirp services in Ruby.}
|
13
|
+
spec.description = %q{Twirp is a simple RPC framework with protobuf service definitions. The Twirp gem provides support for Ruby.}
|
14
|
+
spec.homepage = "https://github.com/cyrusaf/ruby-twirp"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0")
|
18
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_runtime_dependency 'google-protobuf', '>= 3.0.0'
|
23
|
+
|
24
|
+
spec.add_development_dependency 'bundler', '>= 1'
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: twirp
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Cyrus A. Forbes
|
8
|
+
- Mario Izquierdo
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2018-02-06 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: google-protobuf
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: 3.0.0
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: 3.0.0
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: bundler
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '1'
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '1'
|
42
|
+
description: Twirp is a simple RPC framework with protobuf service definitions. The
|
43
|
+
Twirp gem provides support for Ruby.
|
44
|
+
email:
|
45
|
+
- forbescyrus@gmail.com
|
46
|
+
- tothemario@gmail.com
|
47
|
+
executables: []
|
48
|
+
extensions: []
|
49
|
+
extra_rdoc_files: []
|
50
|
+
files:
|
51
|
+
- Gemfile
|
52
|
+
- Gemfile.lock
|
53
|
+
- README.md
|
54
|
+
- lib/twirp.rb
|
55
|
+
- lib/twirp/error.rb
|
56
|
+
- lib/twirp/server.rb
|
57
|
+
- lib/twirp/version.rb
|
58
|
+
- protoc-gen-twirp_ruby/main.go
|
59
|
+
- test/error_test.rb
|
60
|
+
- twirp.gemspec
|
61
|
+
homepage: https://github.com/cyrusaf/ruby-twirp
|
62
|
+
licenses:
|
63
|
+
- MIT
|
64
|
+
metadata: {}
|
65
|
+
post_install_message:
|
66
|
+
rdoc_options: []
|
67
|
+
require_paths:
|
68
|
+
- lib
|
69
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
requirements: []
|
80
|
+
rubyforge_project:
|
81
|
+
rubygems_version: 2.6.14
|
82
|
+
signing_key:
|
83
|
+
specification_version: 4
|
84
|
+
summary: Twirp services in Ruby.
|
85
|
+
test_files:
|
86
|
+
- test/error_test.rb
|