ruby_robot 0.1.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +1 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +133 -0
- data/Guardfile +77 -0
- data/LICENSE.txt +21 -0
- data/README.md +50 -0
- data/Rakefile +24 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/ruby_robot +34 -0
- data/exe/ruby_robot_grpc_client +53 -0
- data/exe/ruby_robot_grpc_server +46 -0
- data/exe/ruby_robot_web +19 -0
- data/json_schema/request.schema.json +37 -0
- data/json_schema/response.schema.json +24 -0
- data/lib/public/favicon-16x16.png +0 -0
- data/lib/public/favicon-32x32.png +0 -0
- data/lib/public/index.html +95 -0
- data/lib/public/swagger-ui-bundle.js +93 -0
- data/lib/public/swagger-ui-standalone-preset.js +13 -0
- data/lib/public/swagger-ui.css +3 -0
- data/lib/public/swagger-ui.js +8 -0
- data/lib/public/swagger.json +179 -0
- data/lib/ruby_robot.rb +16 -0
- data/lib/ruby_robot/cacerts.crt +92 -0
- data/lib/ruby_robot/construction_error.rb +4 -0
- data/lib/ruby_robot/grpc/robot_service.rb +102 -0
- data/lib/ruby_robot/grpc/ruby_robot_services_pb.rb +34 -0
- data/lib/ruby_robot/grpc_helper.rb +15 -0
- data/lib/ruby_robot/grpc_ruby/ruby_robot_pb.rb +36 -0
- data/lib/ruby_robot/grpc_shell.rb +85 -0
- data/lib/ruby_robot/netflix_tabletop.rb +14 -0
- data/lib/ruby_robot/placement_error.rb +4 -0
- data/lib/ruby_robot/robot.rb +71 -0
- data/lib/ruby_robot/ruby_robot.proto +53 -0
- data/lib/ruby_robot/schema_loader.rb +10 -0
- data/lib/ruby_robot/shell.rb +204 -0
- data/lib/ruby_robot/tabletop.rb +120 -0
- data/lib/ruby_robot/version.rb +3 -0
- data/lib/ruby_robot/webapp.rb +263 -0
- data/ruby_robot.gemspec +48 -0
- metadata +258 -0
@@ -0,0 +1,53 @@
|
|
1
|
+
//
|
2
|
+
// gRPC service definition for the RubyRobot.
|
3
|
+
//
|
4
|
+
syntax = "proto3";
|
5
|
+
|
6
|
+
import "google/protobuf/empty.proto";
|
7
|
+
|
8
|
+
option java_multiple_files = true;
|
9
|
+
option java_package = "net.avilla.netflix.studio.robot";
|
10
|
+
option java_outer_classname = "RobotProto";
|
11
|
+
option objc_class_prefix = "RBT";
|
12
|
+
|
13
|
+
package RubyRobot;
|
14
|
+
|
15
|
+
//
|
16
|
+
// Service definitions
|
17
|
+
//
|
18
|
+
service RubyRobot {
|
19
|
+
rpc Left(google.protobuf.Empty) returns (RubyRobotResponse) {}
|
20
|
+
rpc Move(google.protobuf.Empty) returns (RubyRobotResponse) {}
|
21
|
+
rpc Place(RubyRobotRequest) returns (RubyRobotResponse) {}
|
22
|
+
rpc Remove(google.protobuf.Empty) returns (google.protobuf.Empty) {}
|
23
|
+
rpc Report(google.protobuf.Empty) returns (RubyRobotResponse) {}
|
24
|
+
rpc Right(google.protobuf.Empty) returns (RubyRobotResponse) {}
|
25
|
+
}
|
26
|
+
|
27
|
+
//
|
28
|
+
// Message type definitions
|
29
|
+
//
|
30
|
+
message RubyRobotRequest {
|
31
|
+
int32 x = 1;
|
32
|
+
int32 y = 2;
|
33
|
+
enum Direction {
|
34
|
+
// Clockwise from NORTH
|
35
|
+
NORTH=0;
|
36
|
+
EAST =1;
|
37
|
+
SOUTH=2;
|
38
|
+
WEST =3;
|
39
|
+
}
|
40
|
+
Direction direction = 3;
|
41
|
+
}
|
42
|
+
|
43
|
+
message RubyRobotError {
|
44
|
+
int32 error = 1;
|
45
|
+
string message = 2;
|
46
|
+
}
|
47
|
+
|
48
|
+
message RubyRobotResponse {
|
49
|
+
oneof response {
|
50
|
+
RubyRobotRequest current_state = 1;
|
51
|
+
RubyRobotError error = 2;
|
52
|
+
}
|
53
|
+
}
|
@@ -0,0 +1,204 @@
|
|
1
|
+
#
|
2
|
+
# Behold: I stand on the shoulders of giants...rossmeissl@github.com
|
3
|
+
# is the _(wo?)man_
|
4
|
+
#
|
5
|
+
require 'bombshell'
|
6
|
+
require 'logger'
|
7
|
+
|
8
|
+
module RubyRobot
|
9
|
+
class Shell < ::Bombshell::Environment
|
10
|
+
include ::Bombshell::Shell
|
11
|
+
|
12
|
+
prompt_with 'ILoveNetflixStudio'
|
13
|
+
|
14
|
+
attr_reader :logger
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@logger = Logger.new(STDOUT)
|
18
|
+
@logger.formatter = proc { |severity, datetime, progname, msg|
|
19
|
+
"#{msg}\n"
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
#
|
24
|
+
# Place a robot
|
25
|
+
#
|
26
|
+
def PLACE(x, y, direction)
|
27
|
+
# Save state in case place is called w/ invalid coords
|
28
|
+
orig_robot = @robot
|
29
|
+
orig_tabletop = @tabletop
|
30
|
+
# TODO: What happens when place is called > 1x per session?
|
31
|
+
# Answer under time crunch: just replace the Robot and Tabletop
|
32
|
+
@robot = Robot.new(direction)
|
33
|
+
@tabletop = NetflixTabletop.new
|
34
|
+
begin
|
35
|
+
@tabletop.place(@robot, x: x, y: y)
|
36
|
+
true
|
37
|
+
rescue
|
38
|
+
@robot = orig_robot
|
39
|
+
@tabletop = orig_tabletop
|
40
|
+
@logger.info $!
|
41
|
+
false
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def MOVE
|
46
|
+
return if @robot.nil?
|
47
|
+
@robot.move
|
48
|
+
end
|
49
|
+
|
50
|
+
def LEFT
|
51
|
+
return if @robot.nil?
|
52
|
+
@robot.left
|
53
|
+
end
|
54
|
+
|
55
|
+
def RIGHT
|
56
|
+
return if @robot.nil?
|
57
|
+
@robot.right
|
58
|
+
end
|
59
|
+
|
60
|
+
def REPORT(to_stderr=true)
|
61
|
+
return nil if @robot.nil?
|
62
|
+
@logger.info(@robot.report) if to_stderr
|
63
|
+
@robot.report
|
64
|
+
end
|
65
|
+
|
66
|
+
# Exit Bombshell
|
67
|
+
alias :QUIT :quit
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
#
|
72
|
+
# Monkeypatch Irb to allow no-arg uppercase methods
|
73
|
+
#
|
74
|
+
module IRB
|
75
|
+
class Irb
|
76
|
+
# Evaluates input for this session.
|
77
|
+
def eval_input
|
78
|
+
@scanner.set_prompt do
|
79
|
+
|ltype, indent, continue, line_no|
|
80
|
+
if ltype
|
81
|
+
f = @context.prompt_s
|
82
|
+
elsif continue
|
83
|
+
f = @context.prompt_c
|
84
|
+
elsif indent > 0
|
85
|
+
f = @context.prompt_n
|
86
|
+
else
|
87
|
+
f = @context.prompt_i
|
88
|
+
end
|
89
|
+
f = "" unless f
|
90
|
+
if @context.prompting?
|
91
|
+
@context.io.prompt = p = prompt(f, ltype, indent, line_no)
|
92
|
+
else
|
93
|
+
@context.io.prompt = p = ""
|
94
|
+
end
|
95
|
+
if @context.auto_indent_mode
|
96
|
+
unless ltype
|
97
|
+
ind = prompt(@context.prompt_i, ltype, indent, line_no)[/.*\z/].size +
|
98
|
+
indent * 2 - p.size
|
99
|
+
ind += 2 if continue
|
100
|
+
@context.io.prompt = p + " " * ind if ind > 0
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
@scanner.set_input(@context.io) do
|
106
|
+
signal_status(:IN_INPUT) do
|
107
|
+
if l = @context.io.gets
|
108
|
+
#
|
109
|
+
# Begin monkeypatch: turn uppercase constants into
|
110
|
+
# method calls
|
111
|
+
#
|
112
|
+
if [:MOVE,:LEFT,:RIGHT,:REPORT,:REMOVE,:QUIT].include?(l.strip.to_sym)
|
113
|
+
l = "#{l.strip}()\n"
|
114
|
+
end
|
115
|
+
#
|
116
|
+
# End monkeypatch
|
117
|
+
#
|
118
|
+
print l if @context.verbose?
|
119
|
+
else
|
120
|
+
if @context.ignore_eof? and @context.io.readable_after_eof?
|
121
|
+
l = "\n"
|
122
|
+
if @context.verbose?
|
123
|
+
printf "Use \"exit\" to leave %s\n", @context.ap_name
|
124
|
+
end
|
125
|
+
else
|
126
|
+
print "\n"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
l
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
@scanner.each_top_level_statement do |line, line_no|
|
134
|
+
signal_status(:IN_EVAL) do
|
135
|
+
begin
|
136
|
+
line.untaint
|
137
|
+
@context.evaluate(line, line_no)
|
138
|
+
output_value if @context.echo?
|
139
|
+
exc = nil
|
140
|
+
rescue Interrupt => exc
|
141
|
+
rescue SystemExit, SignalException
|
142
|
+
raise
|
143
|
+
rescue Exception => exc
|
144
|
+
end
|
145
|
+
if exc
|
146
|
+
print exc.class, ": ", exc, "\n"
|
147
|
+
if exc.backtrace && exc.backtrace[0] =~ /irb(2)?(\/.*|-.*|\.rb)?:/ && exc.class.to_s !~ /^IRB/ &&
|
148
|
+
!(SyntaxError === exc)
|
149
|
+
irb_bug = true
|
150
|
+
else
|
151
|
+
irb_bug = false
|
152
|
+
end
|
153
|
+
|
154
|
+
messages = []
|
155
|
+
lasts = []
|
156
|
+
levels = 0
|
157
|
+
if exc.backtrace
|
158
|
+
for m in exc.backtrace
|
159
|
+
m = @context.workspace.filter_backtrace(m) unless irb_bug
|
160
|
+
if m
|
161
|
+
if messages.size < @context.back_trace_limit
|
162
|
+
messages.push "\tfrom "+m
|
163
|
+
else
|
164
|
+
lasts.push "\tfrom "+m
|
165
|
+
if lasts.size > @context.back_trace_limit
|
166
|
+
lasts.shift
|
167
|
+
levels += 1
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
print messages.join("\n"), "\n"
|
174
|
+
unless lasts.empty?
|
175
|
+
printf "... %d levels...\n", levels if levels > 0
|
176
|
+
print lasts.join("\n"), "\n"
|
177
|
+
end
|
178
|
+
print "Maybe IRB bug!\n" if irb_bug
|
179
|
+
end
|
180
|
+
if $SAFE > 2
|
181
|
+
abort "Error: irb does not work for $SAFE level higher than 2"
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
#
|
190
|
+
# Map these constants to symbols so the shell
|
191
|
+
# will pass them through
|
192
|
+
#
|
193
|
+
module Bombshell
|
194
|
+
module Shell
|
195
|
+
NORTH=:north
|
196
|
+
SOUTH=:south
|
197
|
+
EAST=:east
|
198
|
+
WEST=:west
|
199
|
+
end
|
200
|
+
end
|
201
|
+
# Tell shell not to show lower-case 'quit' method; it is aliased
|
202
|
+
# to #QUIT along w/ all the other upper-case methods.
|
203
|
+
::Bombshell::Shell::Commands::HIDE << :quit
|
204
|
+
::Bombshell::Shell::Commands::HIDE << :logger
|
@@ -0,0 +1,120 @@
|
|
1
|
+
#
|
2
|
+
# This class is a tabletop which is essentially a 2D
|
3
|
+
# array even though Ruby doesn't support 2D arrays
|
4
|
+
# like in other languages.
|
5
|
+
#
|
6
|
+
# As per the API instructions, (0,0) is considered
|
7
|
+
# to be the SOUTH WEST most corner.
|
8
|
+
#
|
9
|
+
module RubyRobot
|
10
|
+
class Tabletop
|
11
|
+
attr_reader :width, :height
|
12
|
+
|
13
|
+
def initialize(width, height)
|
14
|
+
# Store position of each piece
|
15
|
+
@width = width
|
16
|
+
@height = height
|
17
|
+
# Actually, this probably isn't necessary
|
18
|
+
# @playing_field = Array.new(@width) { Array.new(@height) }
|
19
|
+
# Hash with keys as the robot object and values are x/y coords
|
20
|
+
@robots = {}
|
21
|
+
end
|
22
|
+
|
23
|
+
def width_range
|
24
|
+
# Exclude width since positions are 0-based
|
25
|
+
(0...@width)
|
26
|
+
end
|
27
|
+
|
28
|
+
def height_range
|
29
|
+
# Exclude height since positions are 0-based
|
30
|
+
(0...@height)
|
31
|
+
end
|
32
|
+
|
33
|
+
def calculate_position(orig_position, direction_sym)
|
34
|
+
case direction_sym
|
35
|
+
# These are clockwise from north
|
36
|
+
when :north then orig_position[:y] += 1
|
37
|
+
when :east then orig_position[:x] += 1
|
38
|
+
when :south then orig_position[:y] -= 1
|
39
|
+
when :west then orig_position[:x] -= 1
|
40
|
+
end
|
41
|
+
orig_position
|
42
|
+
end
|
43
|
+
|
44
|
+
#
|
45
|
+
# Return hash of x/y coords for a placed
|
46
|
+
# Robot, else raise PlacementError. Position state doesn't
|
47
|
+
# mean much to a Robot, by itself: only in
|
48
|
+
# relation to a Tabletop.
|
49
|
+
#
|
50
|
+
def position(robot)
|
51
|
+
raise PlacementError.new "Robot is not on this table" unless placed?(robot)
|
52
|
+
@robots[robot]
|
53
|
+
end
|
54
|
+
|
55
|
+
#
|
56
|
+
# Can the Robot move in the specified direction
|
57
|
+
# w/o falling off? Even though direction is a
|
58
|
+
# property of a specific Robot, pass it in here.
|
59
|
+
#
|
60
|
+
# Later this could be extended to support multiple
|
61
|
+
# Robots on a Tabletop. In that case, this and
|
62
|
+
# #move would need to be synchronized...
|
63
|
+
#
|
64
|
+
def move?(robot, direction_sym)
|
65
|
+
raise PlacementError.new "Robot is not on this table" unless placed?(robot)
|
66
|
+
possible_position = calculate_position(@robots[robot].clone, direction_sym)
|
67
|
+
|
68
|
+
# Is current_position on the board?
|
69
|
+
# Check in range (0..width).include?(x) and (0..height).include?(y)
|
70
|
+
return false if
|
71
|
+
!width_range.include?(possible_position[:x]) or
|
72
|
+
!height_range.include?(possible_position[:y])
|
73
|
+
true
|
74
|
+
end
|
75
|
+
|
76
|
+
#
|
77
|
+
# Move the robot in the specified direction.
|
78
|
+
#
|
79
|
+
def move(robot, direction_sym)
|
80
|
+
raise PlacementError.new "Robot is not on this table" unless placed?(robot)
|
81
|
+
new_position = calculate_position(@robots[robot].clone, direction_sym)
|
82
|
+
# Move the robot by placing it at its new location
|
83
|
+
place(robot, **new_position)
|
84
|
+
end
|
85
|
+
|
86
|
+
#
|
87
|
+
# Is this robot on this Tabletop?
|
88
|
+
#
|
89
|
+
def placed?(robot)
|
90
|
+
@robots.keys.include?(robot)
|
91
|
+
end
|
92
|
+
|
93
|
+
#
|
94
|
+
# NYI: Implement for multiple Robots per Tabletop
|
95
|
+
#
|
96
|
+
def place?(robot, direction_sym)
|
97
|
+
true
|
98
|
+
end
|
99
|
+
|
100
|
+
#
|
101
|
+
# Place a robot on this board
|
102
|
+
#
|
103
|
+
def place(robot, x:0, y:0)
|
104
|
+
raise PlacementError.new "Coordinates (#{x},#{y}) are not on this board" if
|
105
|
+
!width_range.include?(x) || !height_range.include?(y)
|
106
|
+
# @playing_field[x][y] = robot
|
107
|
+
@robots[robot] = {x: x, y: y}
|
108
|
+
robot.place(self)
|
109
|
+
end
|
110
|
+
|
111
|
+
#
|
112
|
+
# Human-readable dump of Tabletop, with
|
113
|
+
# 2D array index {x:0,y:0} in the lower-left
|
114
|
+
# corner of the output.
|
115
|
+
#
|
116
|
+
def inspect
|
117
|
+
@playing_field
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,263 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'sinatra/swagger-exposer/swagger-exposer'
|
3
|
+
require 'ruby_robot'
|
4
|
+
require 'json-schema'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
module RubyRobot
|
8
|
+
|
9
|
+
#
|
10
|
+
# SwaggerExposer turns out to have a few drawbacks; it doesn't support
|
11
|
+
# specifying enumerated string types.
|
12
|
+
#
|
13
|
+
USE_SWAGGER_EXPOSER=false
|
14
|
+
|
15
|
+
#
|
16
|
+
# Simple Sinatra webapp that supports:
|
17
|
+
#
|
18
|
+
# Run HTTP GET on /swagger.json to fetch a static JSON OpenAPI description
|
19
|
+
# for this webapp (suitable for use with swagger.io's GUI).
|
20
|
+
#
|
21
|
+
class Webapp < Sinatra::Base
|
22
|
+
include ::RubyRobot::SchemaLoader
|
23
|
+
|
24
|
+
set :public_folder, File.expand_path(File.join(File.dirname(__FILE__), '..', 'public'))
|
25
|
+
enable :static
|
26
|
+
|
27
|
+
# Tell sinatra to listen on all interfaces if it detects
|
28
|
+
# it is running on a docker container...otherwise it
|
29
|
+
# will just bind to the loopback interface which can't be
|
30
|
+
# exposed from the container.
|
31
|
+
set :bind, '0.0.0.0' if File.exist?('/.dockerenv')
|
32
|
+
|
33
|
+
REPORT_EXAMPLE_OBJ = {x:1,y:1,direction: :NORTH}
|
34
|
+
REPORT_EXAMPLE = REPORT_EXAMPLE_OBJ.to_json
|
35
|
+
|
36
|
+
ERR_PLACEMENT_MSG = 'Robot is not currently placed'
|
37
|
+
|
38
|
+
def request_schema
|
39
|
+
@@request_schema ||= schema = load_schema('request')
|
40
|
+
end
|
41
|
+
|
42
|
+
def response_schema
|
43
|
+
@@response_schema ||= schema = load_schema('response')
|
44
|
+
end
|
45
|
+
|
46
|
+
def max_error_message_length
|
47
|
+
# Grab from the schema
|
48
|
+
(response_schema['definitions']['Error']['properties']['message']['maxLength'] || 1024)
|
49
|
+
end
|
50
|
+
|
51
|
+
if USE_SWAGGER_EXPOSER
|
52
|
+
register Sinatra::SwaggerExposer
|
53
|
+
#
|
54
|
+
# Swagger general info
|
55
|
+
#
|
56
|
+
general_info(
|
57
|
+
{
|
58
|
+
version: ::RubyRobot::VERSION,
|
59
|
+
title: 'RubyRobot',
|
60
|
+
description: 'Web interface to RubyRobot API',
|
61
|
+
}
|
62
|
+
)
|
63
|
+
|
64
|
+
#
|
65
|
+
# Swagger types
|
66
|
+
#
|
67
|
+
type 'Error', {
|
68
|
+
:required => [:code, :message],
|
69
|
+
:properties => {
|
70
|
+
:code => {
|
71
|
+
:type => Integer,
|
72
|
+
:example => 400,
|
73
|
+
:description => 'The error code',
|
74
|
+
},
|
75
|
+
:message => {
|
76
|
+
:type => String,
|
77
|
+
:example => ERR_PLACEMENT_MSG,
|
78
|
+
:description => 'The error message',
|
79
|
+
}
|
80
|
+
}
|
81
|
+
}
|
82
|
+
|
83
|
+
type 'Report', {
|
84
|
+
required: [:x, :y, :direction],
|
85
|
+
properties: {
|
86
|
+
x: {
|
87
|
+
type: Integer,
|
88
|
+
example: 0
|
89
|
+
},
|
90
|
+
y: {
|
91
|
+
type: Integer,
|
92
|
+
example: 0
|
93
|
+
},
|
94
|
+
direction: {
|
95
|
+
type: String,
|
96
|
+
example: "NORTH"
|
97
|
+
}
|
98
|
+
}
|
99
|
+
}
|
100
|
+
end # if USE_SWAGGER_EXPOSER
|
101
|
+
|
102
|
+
#
|
103
|
+
# "Normal" sinatra/ruby code.
|
104
|
+
#
|
105
|
+
def robot
|
106
|
+
@@robot ||= proc {
|
107
|
+
rr = ::RubyRobot::Shell.new
|
108
|
+
# In the webapp, don't log the REPORT messages to stdout
|
109
|
+
rr.logger.formatter = proc { |severity, datetime, progname, msg| "" }
|
110
|
+
rr
|
111
|
+
}.call
|
112
|
+
end
|
113
|
+
|
114
|
+
def not_placed_error
|
115
|
+
[400, {code: 400, message: ERR_PLACEMENT_MSG}.to_json]
|
116
|
+
end
|
117
|
+
|
118
|
+
def formatted_report
|
119
|
+
r = robot.REPORT(false)
|
120
|
+
if !r.nil?
|
121
|
+
r[:direction] = r[:direction].upcase unless r[:direction].nil?
|
122
|
+
end
|
123
|
+
r
|
124
|
+
end
|
125
|
+
|
126
|
+
def position_report
|
127
|
+
# Pass along the report, but the direction needs to be upcased to
|
128
|
+
# comply w/ the JSON schema for the web API
|
129
|
+
[200, formatted_report.to_json]
|
130
|
+
end
|
131
|
+
|
132
|
+
if USE_SWAGGER_EXPOSER
|
133
|
+
endpoint_description 'Place the robot'
|
134
|
+
endpoint_parameter :body, "Robot placement specification object", :body, true, 'Report', {
|
135
|
+
example: REPORT_EXAMPLE_OBJ
|
136
|
+
}
|
137
|
+
endpoint_response 200, 'Report', 'Successful placement'
|
138
|
+
endpoint_response 400, 'Error', ERR_PLACEMENT_MSG
|
139
|
+
endpoint_tags 'Robot'
|
140
|
+
end # if USE_SWAGGER_EXPOSER
|
141
|
+
post '/place' do
|
142
|
+
content_type :json
|
143
|
+
request_params = nil
|
144
|
+
result = nil
|
145
|
+
begin
|
146
|
+
# Parse JSON args
|
147
|
+
request.body.rewind
|
148
|
+
request_params = JSON.parse(request.body.read)
|
149
|
+
# Validate input against JSON schema
|
150
|
+
json_schema_errors = JSON::Validator.fully_validate(request_schema, request_params)
|
151
|
+
body_json = {code: 400, message: "Bad request: #{json_schema_errors.join('; ')}"[0...max_error_message_length] }.to_json
|
152
|
+
body body_json
|
153
|
+
return [400, body_json] unless json_schema_errors.empty?
|
154
|
+
# Call robot#PLACE: inputs have already been validated by the JSON schema
|
155
|
+
robot.PLACE(request_params['x'], request_params['y'], (request_params['direction']))
|
156
|
+
body_json = formatted_report.to_json
|
157
|
+
body body_json
|
158
|
+
[200, body_json]
|
159
|
+
rescue
|
160
|
+
# TODO: log failing request details to enterprise logging...
|
161
|
+
# STDERR.puts $!
|
162
|
+
body_json = {code: 400, message: "Bad request (#{$!})"[0...max_error_message_length]}.to_json
|
163
|
+
body body_json
|
164
|
+
[400, body_json]
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
if USE_SWAGGER_EXPOSER
|
169
|
+
endpoint_description 'Move the robot'
|
170
|
+
endpoint_response 200, 'Report', REPORT_EXAMPLE
|
171
|
+
endpoint_response 400, 'Error', ERR_PLACEMENT_MSG
|
172
|
+
endpoint_tags 'Robot'
|
173
|
+
end # if USE_SWAGGER_EXPOSER
|
174
|
+
post '/move' do
|
175
|
+
content_type :json
|
176
|
+
if robot.REPORT.nil?
|
177
|
+
not_placed_error
|
178
|
+
else
|
179
|
+
robot.MOVE
|
180
|
+
position_report
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
if USE_SWAGGER_EXPOSER
|
185
|
+
endpoint_description 'Turn the robot left'
|
186
|
+
endpoint_response 200, 'Report', REPORT_EXAMPLE
|
187
|
+
endpoint_response 400, 'Error', ERR_PLACEMENT_MSG
|
188
|
+
endpoint_tags 'Robot'
|
189
|
+
end # if USE_SWAGGER_EXPOSER
|
190
|
+
post '/left' do
|
191
|
+
content_type :json
|
192
|
+
if robot.REPORT.nil?
|
193
|
+
not_placed_error
|
194
|
+
else
|
195
|
+
robot.LEFT
|
196
|
+
position_report
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
if USE_SWAGGER_EXPOSER
|
201
|
+
endpoint_description 'Turn the robot right'
|
202
|
+
endpoint_response 200, 'Report', REPORT_EXAMPLE
|
203
|
+
endpoint_response 400, 'Error', ERR_PLACEMENT_MSG
|
204
|
+
endpoint_tags 'Robot'
|
205
|
+
end # if USE_SWAGGER_EXPOSER
|
206
|
+
post '/right' do
|
207
|
+
content_type :json
|
208
|
+
if robot.REPORT.nil?
|
209
|
+
not_placed_error
|
210
|
+
else
|
211
|
+
robot.RIGHT
|
212
|
+
position_report
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
if USE_SWAGGER_EXPOSER
|
217
|
+
endpoint_description "Report the robot's position and orientation"
|
218
|
+
endpoint_response 200, 'Report', REPORT_EXAMPLE
|
219
|
+
endpoint_response 400, 'Error', ERR_PLACEMENT_MSG
|
220
|
+
endpoint_tags 'Robot'
|
221
|
+
end # if USE_SWAGGER_EXPOSER
|
222
|
+
get '/report' do
|
223
|
+
content_type :json
|
224
|
+
if robot.REPORT.nil?
|
225
|
+
not_placed_error
|
226
|
+
else
|
227
|
+
position_report
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
if !USE_SWAGGER_EXPOSER
|
232
|
+
get '/' do
|
233
|
+
redirect '/index.html'
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
post '/remove' do
|
238
|
+
@@robot = nil
|
239
|
+
[200]
|
240
|
+
end
|
241
|
+
|
242
|
+
#
|
243
|
+
# For now, just validate the response for POST /place to show
|
244
|
+
# responses can be validated within a Sinatra URL handler.
|
245
|
+
#
|
246
|
+
after '/place' do
|
247
|
+
# Validate response
|
248
|
+
begin
|
249
|
+
obj = JSON.parse(body.first)
|
250
|
+
unless JSON::Validator.validate(response_schema, obj)
|
251
|
+
# TODO: Enteprise logging
|
252
|
+
# Print out the failing constraints
|
253
|
+
STDERR.puts JSON::Validator.fully_validate(response_schema, obj)
|
254
|
+
STDERR.puts "Return value doesn't match response.schema.json: #{body.first}"
|
255
|
+
end
|
256
|
+
rescue
|
257
|
+
# TODO: Enterprise logging here...
|
258
|
+
STDERR.puts "Error (#{$!}) parsing JSON: '#{body}'"
|
259
|
+
end
|
260
|
+
# STDERR.puts "Failed validation" if JSON::Validator.validate(@response_schema, obj)
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|