zy 0.0.1.alfa
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +55 -0
- data/Rakefile.rb +7 -0
- data/lib/zy.rb +40 -0
- data/lib/zy/natra.rb +50 -0
- data/lib/zy/reply.rb +30 -0
- data/lib/zy/request.rb +71 -0
- data/lib/zy/server.rb +101 -0
- data/lib/zy/version.rb +3 -0
- data/test/basic_interaction_test.rb +130 -0
- data/test/helper.rb +10 -0
- data/zy.gemspec +25 -0
- metadata +116 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a13b474a2570c278d2cd7e81a9ae5837f760ac47
|
4
|
+
data.tar.gz: df95600d55da2e70d543dc414107616a7340d697
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 13ee3b9b683efe52dcbc9a15f6a7d256876b8b3edc076080551b3f4689f3d2a1aaf2c443ff69327a4cffa1ee8146a74a8096d492a2e64cb157cbb5cf92ec61e4
|
7
|
+
data.tar.gz: f15c7ab8febd69ffa1f031f32080306e5893fd3b3e849e003bbecf47ff14946734501392c73509a8ebc3dcd61905a295ed90497eec91761f6f51136bd3bc2b6a
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Ethan
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
# Zy
|
2
|
+
|
3
|
+
Zy is an application level protocol, defining a structure for requests and replies to a service. It draws from concepts of REST over HTTP.
|
4
|
+
|
5
|
+
It encodes its structure as JSON, and is intended to be transported over ØMQ (ZeroMQ).
|
6
|
+
|
7
|
+
A request from a client consists of a message with two frames, and an example looks like:
|
8
|
+
|
9
|
+
```
|
10
|
+
zy 0.0 json
|
11
|
+
```
|
12
|
+
|
13
|
+
```json
|
14
|
+
{
|
15
|
+
"resource": "events",
|
16
|
+
"action": "update",
|
17
|
+
"params": {
|
18
|
+
"name": "2004 summer olympics"
|
19
|
+
},
|
20
|
+
"body": {
|
21
|
+
"location": "Athens, Greece"
|
22
|
+
}
|
23
|
+
}
|
24
|
+
```
|
25
|
+
|
26
|
+
The first frame is called the protocol frame and sets up the basics, telling the server that the client is speaking the zy protocol, version 0.0. It indicates that the following message is formatted as JSON (JSON is currently the only supported format).
|
27
|
+
|
28
|
+
The next frame is the JSON-formatted request. It includes these fields:
|
29
|
+
|
30
|
+
- resource: the name of the resource. this is analagous to the request path or URI in HTTP.
|
31
|
+
- action: the action to be performed. this is analagous to the request method in HTTP.
|
32
|
+
- params: parameters of the request. in this case specifying the identity of the resource in question.
|
33
|
+
- body: the main message which the server will act on. may not always be specified.
|
34
|
+
|
35
|
+
A reply to this also consists of two frames and might look like:
|
36
|
+
|
37
|
+
```
|
38
|
+
zy 0.0 json
|
39
|
+
```
|
40
|
+
|
41
|
+
```json
|
42
|
+
{
|
43
|
+
"status": ["success", "update"],
|
44
|
+
"body": {
|
45
|
+
"name": "2004 summer olympics",
|
46
|
+
"location": "Athens, Greece",
|
47
|
+
"year": "2004"
|
48
|
+
}
|
49
|
+
}
|
50
|
+
```
|
51
|
+
|
52
|
+
The protocol frame is the same. The reply frame has these fields:
|
53
|
+
|
54
|
+
- status: an array of strings, each being a short word to identify aspects of the status, in decreasing order of specificity. this is conceptually similar to an HTTP status code, but is human-readable and allows for more precision.
|
55
|
+
- body: the main message of the response to be conveyed back to the requester. may not always be specified - in this case, for example, it may have been omitted on the assumption that the client knows the identified resource and does not need a representation of it in the reply after the update.
|
data/Rakefile.rb
ADDED
data/lib/zy.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
proc { |p| $:.unshift(p) unless $:.any? { |lp| File.expand_path(lp) == p } }.call(File.expand_path(File.dirname(__FILE__)))
|
2
|
+
|
3
|
+
require 'zy/version'
|
4
|
+
|
5
|
+
require 'ffi-rzmq'
|
6
|
+
require 'json'
|
7
|
+
|
8
|
+
module Zy
|
9
|
+
class Error < StandardError
|
10
|
+
end
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def zmq_context
|
14
|
+
@zmq_context ||= begin
|
15
|
+
ZMQ::Context.new.tap do |zmq_context|
|
16
|
+
trap("INT") do
|
17
|
+
STDERR.puts "goodbye!"
|
18
|
+
zmq_context.terminate
|
19
|
+
STDERR.puts "ok exiting"
|
20
|
+
exit 0
|
21
|
+
STDERR.puts "done"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def zmq_context=(zmq_context)
|
28
|
+
if @zmq_context
|
29
|
+
raise Zy::Error, "zmq_context is already set"
|
30
|
+
else
|
31
|
+
@zmq_context = zmq_context
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
autoload :Server, 'zy/server'
|
37
|
+
autoload :Request, 'zy/request'
|
38
|
+
autoload :Reply, 'zy/reply'
|
39
|
+
autoload :Natra, 'zy/natra'
|
40
|
+
end
|
data/lib/zy/natra.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
module Zy
|
2
|
+
class Natra
|
3
|
+
def self.inherited(klass)
|
4
|
+
klass.instance_variable_set(:@constraints, {})
|
5
|
+
klass.instance_variable_set(:@handlers, [])
|
6
|
+
end
|
7
|
+
|
8
|
+
module HandlerContext
|
9
|
+
def with_constraints(constraints, &block)
|
10
|
+
# keys and values: symbols become strings
|
11
|
+
constraints = constraints.map { |k,v| {k.is_a?(Symbol) ? k.to_s : k => v.is_a?(Symbol) ? v.to_s : v} }.inject({}, &:update)
|
12
|
+
begin
|
13
|
+
original_constraints = @constraints
|
14
|
+
@constraints = @constraints.merge(constraints)
|
15
|
+
yield
|
16
|
+
ensure
|
17
|
+
@constraints = original_constraints
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def resource(name, &block)
|
22
|
+
with_constraints(:resource => name, &block)
|
23
|
+
end
|
24
|
+
|
25
|
+
def action(name = nil, &block)
|
26
|
+
if name
|
27
|
+
with_constraints(:action => name) { action(&block) }
|
28
|
+
else
|
29
|
+
@handlers << [@constraints, block]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
extend HandlerContext
|
35
|
+
|
36
|
+
def self.call(request)
|
37
|
+
_, handler = @handlers.detect do |(constraints, action_block)|
|
38
|
+
constraints.all? do |key, value|
|
39
|
+
request[key] == value
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
if handler
|
44
|
+
handler.call(request)
|
45
|
+
else
|
46
|
+
{'status' => ['error', 'request', 'unroutable']}
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/zy/reply.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
module Zy
|
2
|
+
class Reply
|
3
|
+
class << self
|
4
|
+
def from(reply_object)
|
5
|
+
reply_object.is_a?(self) ? reply_object : new(reply_object)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(object)
|
10
|
+
@object = object
|
11
|
+
end
|
12
|
+
|
13
|
+
def reply_strings
|
14
|
+
@reply_strings ||= [protocol_string, reply_string]
|
15
|
+
end
|
16
|
+
|
17
|
+
def protocol_string
|
18
|
+
'zy 0.0 json'
|
19
|
+
end
|
20
|
+
|
21
|
+
def reply_string
|
22
|
+
begin
|
23
|
+
JSON.generate(@object)
|
24
|
+
rescue JSON::GeneratorError
|
25
|
+
# TODO log debug
|
26
|
+
JSON.generate({"status" => ["error", "server", "internal_error"]})
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/zy/request.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Zy
|
4
|
+
class Request
|
5
|
+
def initialize(request_strings)
|
6
|
+
@request_strings = request_strings
|
7
|
+
@error_status = catch(:error) do
|
8
|
+
# this should not happen; it's not possible to get a 0-frame message in zmq
|
9
|
+
throw(:error, ['error', 'request', 'no_frames']) if @request_strings.size < 1
|
10
|
+
|
11
|
+
@protocol_string = @request_strings[0]
|
12
|
+
@protocol_parts = @protocol_string.strip.split(/ +/)
|
13
|
+
|
14
|
+
# protocol part 0: protocol name (zy)
|
15
|
+
part = 0
|
16
|
+
if @protocol_parts.size <= part
|
17
|
+
throw(:error, ['error', 'protocol', 'protocol_name_not_specified'])
|
18
|
+
elsif @protocol_parts[part] != 'zy'
|
19
|
+
throw(:error, ['error', 'protocol', 'protocol_not_supported'])
|
20
|
+
end
|
21
|
+
|
22
|
+
# protocol part 1: version
|
23
|
+
part = 1
|
24
|
+
if @protocol_parts.size <= part
|
25
|
+
throw(:error, ['error', 'protocol', 'version_not_specified'])
|
26
|
+
elsif @protocol_parts[part] != '0.0'
|
27
|
+
throw(:error, ['error', 'protocol', 'version_not_supported'])
|
28
|
+
end
|
29
|
+
|
30
|
+
# protocol part 2: format
|
31
|
+
part = 2
|
32
|
+
if @protocol_parts.size <= part
|
33
|
+
throw(:error, ['error', 'protocol', 'format_not_specified'])
|
34
|
+
elsif @protocol_parts[part] != 'json'
|
35
|
+
throw(:error, ['error', 'protocol', 'format_not_supported'])
|
36
|
+
end
|
37
|
+
|
38
|
+
# protocol part(s) we don't recognize
|
39
|
+
part = 3
|
40
|
+
if @protocol_parts.size > part
|
41
|
+
throw(:error, ['error', 'protocol', 'too_many_parts'])
|
42
|
+
end
|
43
|
+
|
44
|
+
if @request_strings.size < 2
|
45
|
+
throw(:error, ['error', 'request', 'request_not_specified'])
|
46
|
+
end
|
47
|
+
|
48
|
+
@request_string = @request_strings[1]
|
49
|
+
|
50
|
+
if @request_strings.size > 2
|
51
|
+
throw(:error, ['error', 'request', 'too_many_frames'])
|
52
|
+
end
|
53
|
+
|
54
|
+
begin
|
55
|
+
@object = JSON.parse(@request_string)
|
56
|
+
rescue JSON::ParserError
|
57
|
+
throw(:error, ['error', 'request', 'not_in_specified_format'])
|
58
|
+
end
|
59
|
+
|
60
|
+
throw(:error, ['error', 'request', 'not_object']) unless @object.is_a?(Hash)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
attr_reader :error_status
|
65
|
+
attr_reader :object
|
66
|
+
|
67
|
+
def [](key)
|
68
|
+
@object[key]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/lib/zy/server.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
module Zy
|
2
|
+
class ServerError < Error
|
3
|
+
end
|
4
|
+
|
5
|
+
class Server
|
6
|
+
# options may include:
|
7
|
+
#
|
8
|
+
# - app
|
9
|
+
# - bind
|
10
|
+
# - connect
|
11
|
+
# - server_private_key
|
12
|
+
def initialize(options)
|
13
|
+
# stringify symbol keys
|
14
|
+
@options = options.map { |k,v| {k.is_a?(Symbol) ? k.to_s : k => v} }.inject({}, &:update)
|
15
|
+
end
|
16
|
+
|
17
|
+
def app
|
18
|
+
@options['app']
|
19
|
+
end
|
20
|
+
|
21
|
+
def start
|
22
|
+
debug({:server_socket => 'creating'})
|
23
|
+
server_socket = Zy.zmq_context.socket(ZMQ::REP)
|
24
|
+
debug({:server_socket => 'created (ZMQ::REP)'})
|
25
|
+
raise(ServerError, "failed to create socket") unless server_socket
|
26
|
+
|
27
|
+
if @options['server_private_key']
|
28
|
+
debug({:server_socket => {:curve => 'setting to server'}})
|
29
|
+
rc = server_socket.setsockopt(ZMQ::CURVE_SERVER, 1)
|
30
|
+
debug({:server_socket => {:curve => 'set to server'}})
|
31
|
+
raise(ServerError, "failed to set server socket to curve server (errno = #{ZMQ::Util.errno})") if rc < 0
|
32
|
+
debug({:server_socket => {:curve => 'setting private key'}})
|
33
|
+
rc = server_socket.setsockopt(ZMQ::CURVE_SECRETKEY, @options['server_private_key'])
|
34
|
+
debug({:server_socket => {:curve => 'set private key'}})
|
35
|
+
raise(ServerError, "failed to set server socket curve secret key (errno = #{ZMQ::Util.errno})") if rc < 0
|
36
|
+
else
|
37
|
+
debug({:server_socket => {:curve => 'private key not specified'}})
|
38
|
+
end
|
39
|
+
|
40
|
+
if @options['bind']
|
41
|
+
debug({:server_socket => "binding #{@options['bind']}"})
|
42
|
+
bind_rc = server_socket.bind(@options['bind'])
|
43
|
+
debug({:server_socket => "bound #{@options['bind']}"})
|
44
|
+
raise(ServerError, "failed to bind server socket to #{@options['bind']} (errno = #{ZMQ::Util.errno})") if bind_rc < 0
|
45
|
+
elsif @options['connect']
|
46
|
+
debug({:server_socket => "connecting #{@options['connect']}"})
|
47
|
+
connect_rc = server_socket.connect(@options['connect'])
|
48
|
+
debug({:server_socket => "connected #{@options['connect']}"})
|
49
|
+
raise(ServerError, "failed to connect server socket to #{@options['connect']} (errno = #{ZMQ::Util.errno})") if connect_rc < 0
|
50
|
+
else
|
51
|
+
raise(ServerError, "must specify bind or connect address")
|
52
|
+
end
|
53
|
+
|
54
|
+
loop do
|
55
|
+
debug({:server_socket => "ready to recv"})
|
56
|
+
request_strings = []
|
57
|
+
more = true
|
58
|
+
while more
|
59
|
+
request_message = ZMQ::Message.create || raise(ServerError, "failed to create message (errno = #{ZMQ::Util.errno})")
|
60
|
+
recv_rc = server_socket.recvmsg(request_message)
|
61
|
+
debug({:server_socket => "recvd (#{recv_rc})"})
|
62
|
+
raise(ServerError, "server socket failed to recv (errno = #{ZMQ::Util.errno})") if recv_rc < 0
|
63
|
+
request_strings << request_message.copy_out_string
|
64
|
+
debug({:server_socket => "copied #{request_strings.last}"})
|
65
|
+
request_message.close
|
66
|
+
more = server_socket.more_parts?
|
67
|
+
end
|
68
|
+
request = Request.new(request_strings)
|
69
|
+
if request.error_status
|
70
|
+
reply_obj = {'status' => request.error_status}
|
71
|
+
else
|
72
|
+
begin
|
73
|
+
reply_obj = app.call(request)
|
74
|
+
rescue Exception => e
|
75
|
+
debug({exception: {class: e.class, message: e.message, backtrace: e.backtrace}})
|
76
|
+
reply_obj = {"status" => ["error", "server", "internal_error"]}
|
77
|
+
end
|
78
|
+
end
|
79
|
+
reply = Reply.from(reply_obj)
|
80
|
+
reply.reply_strings.each_with_index do |reply_s, i|
|
81
|
+
flags = i < reply.reply_strings.size - 1 ? ZMQ::SNDMORE : 0
|
82
|
+
debug({:server_socket => "sending #{reply_s} (flags=#{flags})"})
|
83
|
+
send_rc = server_socket.send_string(reply_s, flags)
|
84
|
+
debug({:server_socket => "sent (#{send_rc})"})
|
85
|
+
raise(ServerError, "server socket failed to send (errno = #{ZMQ::Util.errno})") if send_rc < 0
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def logger
|
91
|
+
@logger ||= @options['logger'] || begin
|
92
|
+
require 'logger'
|
93
|
+
::Logger.new(STDOUT)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def debug(message)
|
98
|
+
logger.debug JSON.generate message
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
data/lib/zy/version.rb
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
proc { |p| $:.unshift(p) unless $:.any? { |lp| File.expand_path(lp) == p } }.call(File.expand_path('.', File.dirname(__FILE__)))
|
2
|
+
require 'helper'
|
3
|
+
|
4
|
+
describe(Zy::Server) do
|
5
|
+
before do
|
6
|
+
server = TCPServer.new('127.0.0.1', 0)
|
7
|
+
@server_port = server_port = server.addr[1]
|
8
|
+
server.close
|
9
|
+
|
10
|
+
STDERR.puts "firing server"
|
11
|
+
Thread.abort_on_exception = true
|
12
|
+
@server_thread = Thread.new do
|
13
|
+
Zy::Server.new(
|
14
|
+
:app => app,
|
15
|
+
:bind => "tcp://*:#{server_port}",
|
16
|
+
).start
|
17
|
+
end
|
18
|
+
STDERR.puts "fired server"
|
19
|
+
end
|
20
|
+
after do
|
21
|
+
@server_thread.kill
|
22
|
+
end
|
23
|
+
|
24
|
+
let(:app) do
|
25
|
+
proc { |request| {} }
|
26
|
+
end
|
27
|
+
|
28
|
+
let(:client_socket) do
|
29
|
+
Zy.zmq_context.socket(ZMQ::REQ).tap do |client_socket|
|
30
|
+
client_socket.connect("tcp://localhost:#{@server_port}")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def request_s(*request_strings)
|
35
|
+
request_strings.each_with_index do |request_s, i|
|
36
|
+
flags = i < request_strings.size - 1 ? ZMQ::SNDMORE : 0
|
37
|
+
send_rc = client_socket.send_string(request_s, flags)
|
38
|
+
assert(send_rc >= 0)
|
39
|
+
end
|
40
|
+
reply_strings = []
|
41
|
+
more = true
|
42
|
+
while more
|
43
|
+
reply_message = ZMQ::Message.create || raise(ServerError, "failed to create message (errno = #{ZMQ::Util.errno})")
|
44
|
+
recv_rc = client_socket.recvmsg(reply_message)
|
45
|
+
assert(recv_rc >= 0)
|
46
|
+
reply_strings << reply_message.copy_out_string
|
47
|
+
reply_message.close
|
48
|
+
more = client_socket.more_parts?
|
49
|
+
end
|
50
|
+
assert_equal(2, reply_strings.size)
|
51
|
+
assert_equal('zy 0.0 json', reply_strings[0])
|
52
|
+
JSON.parse(reply_strings[1])
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'requests and replies' do
|
56
|
+
reply_s = request_s('zy 0.0 json', '{}')
|
57
|
+
end
|
58
|
+
|
59
|
+
describe 'internal server error' do
|
60
|
+
let(:app) do
|
61
|
+
proc { |request| raise }
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'internal server errors' do
|
65
|
+
reply = request_s('zy 0.0 json', '{}')
|
66
|
+
assert_equal({'status' => ['error', 'server', 'internal_error']}, reply)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe 'reply object not jsonable' do
|
71
|
+
let(:app) do
|
72
|
+
proc { |request| Object.new }
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'internal server errors' do
|
76
|
+
reply = request_s('zy 0.0 json', '{}')
|
77
|
+
assert_equal({'status' => ['error', 'server', 'internal_error']}, reply)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'rejects non-json' do
|
82
|
+
reply = request_s('zy 0.0 json', 'hello!')
|
83
|
+
assert_equal({'status' => ['error', 'request', 'not_in_specified_format']}, reply)
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'rejects non-object in json' do
|
87
|
+
reply = request_s('zy 0.0 json', '["a request"]')
|
88
|
+
assert_equal({'status' => ['error', 'request', 'not_object']}, reply)
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'rejects missing request frame' do
|
92
|
+
reply = request_s('zy 0.0 json')
|
93
|
+
assert_equal({'status' => ['error', 'request', 'request_not_specified']}, reply)
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'rejects too many frames' do
|
97
|
+
reply = request_s('zy 0.0 json', '{}', '{}')
|
98
|
+
assert_equal({'status' => ['error', 'request', 'too_many_frames']}, reply)
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'rejects missing format' do
|
102
|
+
reply = request_s('zy 0.0', '{}')
|
103
|
+
assert_equal({'status' => ['error', 'protocol', 'format_not_specified']}, reply)
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'rejects missing version' do
|
107
|
+
reply = request_s('zy', '{}')
|
108
|
+
assert_equal({'status' => ['error', 'protocol', 'version_not_specified']}, reply)
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'rejects missing protocol' do
|
112
|
+
reply = request_s('', '{}')
|
113
|
+
assert_equal({'status' => ['error', 'protocol', 'protocol_name_not_specified']}, reply)
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'rejects unrecognized format' do
|
117
|
+
reply = request_s('zy 0.0 xml', '{}')
|
118
|
+
assert_equal({'status' => ['error', 'protocol', 'format_not_supported']}, reply)
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'rejects unsupported version' do
|
122
|
+
reply = request_s('zy 9.0 json', '{}')
|
123
|
+
assert_equal({'status' => ['error', 'protocol', 'version_not_supported']}, reply)
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'rejects wrong protocol' do
|
127
|
+
reply = request_s('http 0.0 json', '{}')
|
128
|
+
assert_equal({'status' => ['error', 'protocol', 'protocol_not_supported']}, reply)
|
129
|
+
end
|
130
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
proc { |p| $:.unshift(p) unless $:.any? { |lp| File.expand_path(lp) == p } }.call(File.expand_path('../lib', File.dirname(__FILE__)))
|
2
|
+
|
3
|
+
# NO EXPECTATIONS
|
4
|
+
ENV["MT_NO_EXPECTATIONS"] = ''
|
5
|
+
|
6
|
+
require 'minitest/autorun'
|
7
|
+
require 'minitest/reporters'
|
8
|
+
Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new
|
9
|
+
|
10
|
+
require 'zy'
|
data/zy.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.any? { |lp| File.expand_path(lp) == File.expand_path(lib) }
|
4
|
+
require 'zy/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'zy'
|
8
|
+
spec.version = Zy::VERSION
|
9
|
+
spec.authors = ['Ethan']
|
10
|
+
spec.email = ['ethan@unth']
|
11
|
+
spec.summary = 'zy'
|
12
|
+
spec.description = 'zy'
|
13
|
+
spec.homepage = ''
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0") - ['.gitignore']
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_dependency 'ffi-rzmq', '~> 2.0'
|
22
|
+
spec.add_development_dependency 'rake'
|
23
|
+
spec.add_development_dependency 'minitest'
|
24
|
+
spec.add_development_dependency 'minitest-reporters'
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: zy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1.alfa
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ethan
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-02-07 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: ffi-rzmq
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: minitest-reporters
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: zy
|
70
|
+
email:
|
71
|
+
- ethan@unth
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- Gemfile
|
77
|
+
- LICENSE.txt
|
78
|
+
- README.md
|
79
|
+
- Rakefile.rb
|
80
|
+
- lib/zy.rb
|
81
|
+
- lib/zy/natra.rb
|
82
|
+
- lib/zy/reply.rb
|
83
|
+
- lib/zy/request.rb
|
84
|
+
- lib/zy/server.rb
|
85
|
+
- lib/zy/version.rb
|
86
|
+
- test/basic_interaction_test.rb
|
87
|
+
- test/helper.rb
|
88
|
+
- zy.gemspec
|
89
|
+
homepage: ''
|
90
|
+
licenses:
|
91
|
+
- MIT
|
92
|
+
metadata: {}
|
93
|
+
post_install_message:
|
94
|
+
rdoc_options: []
|
95
|
+
require_paths:
|
96
|
+
- lib
|
97
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - '>'
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: 1.3.1
|
107
|
+
requirements: []
|
108
|
+
rubyforge_project:
|
109
|
+
rubygems_version: 2.2.2
|
110
|
+
signing_key:
|
111
|
+
specification_version: 4
|
112
|
+
summary: zy
|
113
|
+
test_files:
|
114
|
+
- test/basic_interaction_test.rb
|
115
|
+
- test/helper.rb
|
116
|
+
has_rdoc:
|