Add message parser
This commit is contained in:
parent
dd0b83734d
commit
66044ddae1
@ -1,6 +1,11 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "json-schema"
|
||||||
|
|
||||||
require_relative "server/messages"
|
require_relative "server/messages"
|
||||||
|
|
||||||
|
require_relative "server/request_schemas"
|
||||||
|
require_relative "server/message_parser"
|
||||||
require_relative "server/message_broker"
|
require_relative "server/message_broker"
|
||||||
module Rex
|
module Rex
|
||||||
module Server
|
module Server
|
||||||
|
56
lib/rex/server/message_parser.rb
Normal file
56
lib/rex/server/message_parser.rb
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
require "json-schema"
|
||||||
|
module Rex
|
||||||
|
module Server
|
||||||
|
# Parses the messages from the wire and returns well formatted ruby objects
|
||||||
|
class MessageParser
|
||||||
|
def parse(raw_message)
|
||||||
|
parsed_message = begin
|
||||||
|
JSON.parse(raw_message)
|
||||||
|
rescue JSON::ParserError
|
||||||
|
return [false, [{error: :json_parse_error}]]
|
||||||
|
end
|
||||||
|
|
||||||
|
# check that it is one of the accepted requests
|
||||||
|
errors = JSON::Validator.fully_validate(
|
||||||
|
RequestSchemas::REQUEST_NAME,
|
||||||
|
parsed_message,
|
||||||
|
errors_as_objects: true
|
||||||
|
)
|
||||||
|
return [false, errors] unless errors.empty?
|
||||||
|
|
||||||
|
# Full validation
|
||||||
|
errors = JSON::Validator.fully_validate(
|
||||||
|
RequestSchemas::SCHEMA_REQUEST_NAME_MAPPING[parsed_message["name"]],
|
||||||
|
parsed_message,
|
||||||
|
errors_as_objects: true
|
||||||
|
)
|
||||||
|
return [false, errors] unless errors.empty?
|
||||||
|
|
||||||
|
case parsed_message["name"]
|
||||||
|
when "order.create"
|
||||||
|
Messages::CreateOrderRequest.new(
|
||||||
|
nil,
|
||||||
|
parsed_message["args"]["side"],
|
||||||
|
parsed_message["args"]["price"],
|
||||||
|
parsed_message["args"]["quantity"]
|
||||||
|
)
|
||||||
|
when "order.cancel"
|
||||||
|
Messages::CancelOrderRequest.new(
|
||||||
|
nil,
|
||||||
|
parsed_message["args"]["id"]
|
||||||
|
)
|
||||||
|
when "orderbook.fetch"
|
||||||
|
Messages::FetchOrderBookRequest.new(nil)
|
||||||
|
when "orders.fetch"
|
||||||
|
Messages::FetchOrdersRequest.new(nil)
|
||||||
|
when "trades.fetch"
|
||||||
|
Messages::FetchTradesRequest.new(nil)
|
||||||
|
when "authenticate"
|
||||||
|
Messages::AuthenticateRequest.new(
|
||||||
|
parsed_message["args"]["user_id"]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
120
lib/rex/server/request_schemas.rb
Normal file
120
lib/rex/server/request_schemas.rb
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
module Rex
|
||||||
|
module Server
|
||||||
|
module RequestSchemas
|
||||||
|
REQUEST_ENUM = {
|
||||||
|
type: :string,
|
||||||
|
description: "A valid request name",
|
||||||
|
enum: [
|
||||||
|
"order.create",
|
||||||
|
"order.cancel",
|
||||||
|
"orderbook.fetch",
|
||||||
|
"orders.fetch",
|
||||||
|
"authenticate",
|
||||||
|
"trades.fetch"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
REQUEST_NAME = {
|
||||||
|
type: :object,
|
||||||
|
required: [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
properties: {
|
||||||
|
name: REQUEST_ENUM
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def self.request(args)
|
||||||
|
{
|
||||||
|
type: :object,
|
||||||
|
required: [
|
||||||
|
"request_id",
|
||||||
|
"type",
|
||||||
|
"name",
|
||||||
|
"args"
|
||||||
|
],
|
||||||
|
properties: {
|
||||||
|
request_id: {
|
||||||
|
type: :integer
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: :string
|
||||||
|
},
|
||||||
|
name: REQUEST_ENUM,
|
||||||
|
args: args
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
CREATE_ORDER_ARGUMENTS = {
|
||||||
|
type: :object,
|
||||||
|
required: [
|
||||||
|
"quantity",
|
||||||
|
"price",
|
||||||
|
"side"
|
||||||
|
],
|
||||||
|
properties: {
|
||||||
|
quantity: {
|
||||||
|
type: :integer,
|
||||||
|
exclusiveMinimum: 0
|
||||||
|
},
|
||||||
|
price: {
|
||||||
|
type: :integer,
|
||||||
|
minimum: 0
|
||||||
|
},
|
||||||
|
side: {
|
||||||
|
type: :string,
|
||||||
|
enum: ["buy", "sell"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CREATE_ORDER = request(CREATE_ORDER_ARGUMENTS)
|
||||||
|
|
||||||
|
CANCEL_ORDER_ARGUMENTS = {
|
||||||
|
type: :object,
|
||||||
|
required: [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
properties: {
|
||||||
|
id: {
|
||||||
|
type: :integer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EMPTY_ARGUMENTS = {
|
||||||
|
type: :object,
|
||||||
|
properties: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
CANCEL_ORDER = request(CANCEL_ORDER_ARGUMENTS)
|
||||||
|
FETCH_ORDERBOOK = request(EMPTY_ARGUMENTS)
|
||||||
|
FETCH_ORDERS = request(EMPTY_ARGUMENTS)
|
||||||
|
FETCH_TRADES = request(EMPTY_ARGUMENTS)
|
||||||
|
|
||||||
|
AUTHENTICATE_ARGUMENTS = {
|
||||||
|
type: :object,
|
||||||
|
required: [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
properties: {
|
||||||
|
user_id: {
|
||||||
|
type: :string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AUTHENTICATE = request(AUTHENTICATE_ARGUMENTS)
|
||||||
|
|
||||||
|
SCHEMA_REQUEST_NAME_MAPPING = {
|
||||||
|
"order.create" => CREATE_ORDER,
|
||||||
|
"order.cancel" => CANCEL_ORDER,
|
||||||
|
"orders.fetch" => FETCH_ORDERS,
|
||||||
|
"trades.fetch" => FETCH_TRADES,
|
||||||
|
"orderbook.fetch" => FETCH_ORDERBOOK,
|
||||||
|
"authenticate" => AUTHENTICATE
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
154
spec/server/message_parser_spec.rb
Normal file
154
spec/server/message_parser_spec.rb
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
# require "eventmachine"
|
||||||
|
RSpec.describe Rex::Server::MessageParser do
|
||||||
|
subject(:instance) { described_class.new }
|
||||||
|
|
||||||
|
describe "#parse" do
|
||||||
|
subject { instance.parse(message) }
|
||||||
|
|
||||||
|
context "when message is not valid json" do
|
||||||
|
let(:message) do
|
||||||
|
"{{}}"
|
||||||
|
end
|
||||||
|
|
||||||
|
it { is_expected.to eq([false, [{error: :json_parse_error}]]) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when message is not a valid message type" do
|
||||||
|
let(:message) do
|
||||||
|
{
|
||||||
|
name: "unknown.request_type"
|
||||||
|
}.to_json
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns an error" do
|
||||||
|
expect(subject[0]).to be(false)
|
||||||
|
expect(subject.dig(1, 0, :fragment)).to eq("#/name")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when message does not match the schema" do
|
||||||
|
let(:message) do
|
||||||
|
{
|
||||||
|
request_id: "-1",
|
||||||
|
name: "order.create",
|
||||||
|
type: "request",
|
||||||
|
args: "test"
|
||||||
|
}.to_json
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns an error" do
|
||||||
|
expect(subject[0]).to be(false)
|
||||||
|
expect(subject[1].length).to eq(2)
|
||||||
|
expect(subject[1].map { _1[:fragment] }).to match_array(["#/request_id", "#/args"])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when message is a order create request" do
|
||||||
|
let(:message) do
|
||||||
|
{
|
||||||
|
request_id: 1,
|
||||||
|
name: "order.create",
|
||||||
|
type: "request",
|
||||||
|
args: {
|
||||||
|
quantity: 100,
|
||||||
|
price: 10,
|
||||||
|
side: :buy
|
||||||
|
}
|
||||||
|
}.to_json
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns a order create request" do
|
||||||
|
expect(subject).to be_instance_of(Rex::Server::Messages::CreateOrderRequest)
|
||||||
|
expect(subject.user_id).to eq(nil)
|
||||||
|
expect(subject.side).to eq("buy")
|
||||||
|
expect(subject.price).to eq(10)
|
||||||
|
expect(subject.quantity).to eq(100)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when message is a order cancel request" do
|
||||||
|
let(:message) do
|
||||||
|
{
|
||||||
|
request_id: 1,
|
||||||
|
name: "order.cancel",
|
||||||
|
type: "request",
|
||||||
|
args: {
|
||||||
|
id: 123
|
||||||
|
}
|
||||||
|
}.to_json
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns a order cancel request" do
|
||||||
|
expect(subject).to be_instance_of(Rex::Server::Messages::CancelOrderRequest)
|
||||||
|
expect(subject.user_id).to eq(nil)
|
||||||
|
expect(subject.order_id).to eq(123)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when message is a order book request" do
|
||||||
|
let(:message) do
|
||||||
|
{
|
||||||
|
request_id: 1,
|
||||||
|
name: "orderbook.fetch",
|
||||||
|
type: "request",
|
||||||
|
args: {}
|
||||||
|
}.to_json
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns a fetch order book request" do
|
||||||
|
expect(subject).to be_instance_of(Rex::Server::Messages::FetchOrderBookRequest)
|
||||||
|
expect(subject.user_id).to eq(nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when message is a fetch orders request" do
|
||||||
|
let(:message) do
|
||||||
|
{
|
||||||
|
request_id: 1,
|
||||||
|
name: "orders.fetch",
|
||||||
|
type: "request",
|
||||||
|
args: {}
|
||||||
|
}.to_json
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns a fetch orders request" do
|
||||||
|
expect(subject).to be_instance_of(Rex::Server::Messages::FetchOrdersRequest)
|
||||||
|
expect(subject.user_id).to eq(nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when message is a fetch trades request" do
|
||||||
|
let(:message) do
|
||||||
|
{
|
||||||
|
request_id: 1,
|
||||||
|
name: "trades.fetch",
|
||||||
|
type: "request",
|
||||||
|
args: {}
|
||||||
|
}.to_json
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns a fetch trades request" do
|
||||||
|
expect(subject).to be_instance_of(Rex::Server::Messages::FetchTradesRequest)
|
||||||
|
expect(subject.user_id).to eq(nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when message is an authenticate request" do
|
||||||
|
let(:message) do
|
||||||
|
{
|
||||||
|
request_id: 1,
|
||||||
|
name: "authenticate",
|
||||||
|
type: "request",
|
||||||
|
args: {
|
||||||
|
user_id: "1"
|
||||||
|
}
|
||||||
|
}.to_json
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns a authenticate request" do
|
||||||
|
expect(subject).to be_instance_of(Rex::Server::Messages::AuthenticateRequest)
|
||||||
|
expect(subject.user_id).to eq("1")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue
Block a user