lazy_graph 0.1.6 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rack'
4
+
5
+ module LazyGraph
6
+ class RackApp
7
+ ALLOWED_VALUES_VALIDATE = [true, false, nil, 'input', 'context'].to_set.freeze
8
+ ALLOWED_VALUES_DEBUG = [true, false, nil].to_set.freeze
9
+
10
+ attr_reader :routes
11
+
12
+ def initialize(routes: {})
13
+ @routes = routes.transform_keys(&:to_sym).compare_by_identity
14
+ end
15
+
16
+ def call(env)
17
+ env[:X_REQUEST_TIME_START] = Process.clock_gettime(Process::CLOCK_MONOTONIC)
18
+ request = Rack::Request.new(env)
19
+
20
+ return routes!(request) if (request.path == '/routes' || request.path == '/') && request.get?
21
+ return health!(request) if request.path == '/health' && request.get?
22
+ return not_found!(request) unless (graph_module = @routes[request.path.to_sym])
23
+ return success!(request, graph_module.usage) if request.get?
24
+ return not_found!("#{request.request_method} #{request.path}") unless request.post?
25
+
26
+ query_lazy_graph!(request, graph_module)
27
+ end
28
+
29
+ def query_lazy_graph!(request, graph_module)
30
+ body = begin
31
+ JSON.parse(request.body.read, symbolize_names: true)
32
+ rescue JSON::ParserError => e
33
+ return not_acceptable!(request, 'Invalid JSON', e.message)
34
+ end
35
+ context, modules, validate, debug, query = body.values_at(:context, :modules, :validate, :debug, :query)
36
+ unless context.is_a?(Hash) && !context.empty?
37
+ return not_acceptable!(request, "Invalid 'context' Parameter", 'Should be a non-empty object.')
38
+ end
39
+
40
+ unless (modules.is_a?(Hash) && !modules.empty?) || modules.is_a?(String) || (modules.is_a?(Array) && modules.all? do |m|
41
+ m.is_a?(String)
42
+ end)
43
+ return not_acceptable!(request, "Invalid 'modules' Parameter",
44
+ 'Should be a string, string-array or non-empty object.')
45
+ end
46
+
47
+ modules = Array(modules).map { |m| [m, {}] }.to_h unless modules.is_a?(Hash)
48
+
49
+ unless ALLOWED_VALUES_VALIDATE.include?(validate)
50
+ return not_acceptable!(
51
+ request, "Invalid 'validate' Parameter", "Should be nil, bool, or one of 'input', 'context'"
52
+ )
53
+ end
54
+
55
+ unless ALLOWED_VALUES_DEBUG.include?(debug) || debug.is_a?(String)
56
+ return not_acceptable!(request, "Invalid 'debug' Parameter", 'Should be nil or bool')
57
+ end
58
+
59
+ debug = Regexp.new(Regexp.escape(debug)) if debug.is_a?(String) && debug != 'exceptions'
60
+
61
+ unless query.nil? || query.is_a?(String) || (query.is_a?(Array) && query.all? do |q|
62
+ q.is_a?(String)
63
+ end)
64
+ return not_acceptable!(request, "Invalid 'query' Parameter", 'Should be nil, array or string array')
65
+ end
66
+
67
+ begin
68
+ result = graph_module.eval!(
69
+ modules: modules,
70
+ context: context,
71
+ validate: validate.nil? ? true : validate,
72
+ debug: if !debug.nil?
73
+ debug
74
+ else
75
+ LazyGraph::Environment.development? ? 'exceptions' : debug
76
+ end,
77
+ query: query
78
+ )
79
+ return not_acceptable!(request, result[:message], result[:detail]) if result[:type] == :error
80
+
81
+ success!(request, result)
82
+ rescue AbortError, ValidationError => e
83
+ LazyGraph.logger.error(e.message)
84
+ LazyGraph.logger.error(e.backtrace.join("\n"))
85
+ error!(request, 400, 'Bad Request', e.message)
86
+ rescue StandardError => e
87
+ LazyGraph.logger.error(e.message)
88
+ LazyGraph.logger.error(e.backtrace.join("\n"))
89
+
90
+ error!(request, 500, 'Internal Server Error', e.message)
91
+ end
92
+ end
93
+
94
+ def routes!(request)
95
+ success!(request, @routes.keys.map { |k, _v| { route: k.to_s, methods: %i[GET POST] } })
96
+ end
97
+
98
+ def health!(request)
99
+ success!(request, { status: 'ok' })
100
+ end
101
+
102
+ def not_acceptable!(request, message, details = '')
103
+ error!(request, 406, message, details)
104
+ end
105
+
106
+ def not_found!(request, details = '')
107
+ error!(request, 404, 'Not Found', details)
108
+ end
109
+
110
+ def request_ms(request)
111
+ ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - request.env[:X_REQUEST_TIME_START]) * 1000.0).round(3)
112
+ end
113
+
114
+ def success!(request, result, status: 200)
115
+ req_ms = request_ms(request)
116
+
117
+ LazyGraph.logger.info(
118
+ Logger.build_color_string do
119
+ "#{bold(request.request_method)}: #{dim(request.path)} => #{green(status)} #{light_gray("#{req_ms}ms")}"
120
+ end
121
+ )
122
+
123
+ [status, { 'content-type' => 'application/json' }, [JSON.fast_generate(result)]]
124
+ end
125
+
126
+ def error!(request, status, message, details = '')
127
+ req_ms = request_ms(request)
128
+
129
+ LazyGraph.logger.error(
130
+ Logger.build_color_string do
131
+ "#{bold(request.request_method)}: #{dim(request.path)} => #{status.to_i >= 500 ? red(status) : orange(status)} #{light_gray("#{req_ms}ms")}"
132
+ end
133
+ )
134
+
135
+ [status, { 'content-type' => 'application/json' }, [JSON.fast_generate({ 'error': message, 'details': details })]]
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,199 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'socket'
4
+
5
+ module LazyGraph
6
+ class RackServer
7
+ def initialize(app)
8
+ @app = app
9
+ @running = false
10
+ @threads = [] #
11
+ @threads_m = Mutex.new
12
+ end
13
+
14
+ def start(port: 9292, workers: 4)
15
+ trap_signals
16
+
17
+ @port = port
18
+ @workers = workers
19
+
20
+ if workers > 1
21
+ puts "Starting Raxx server with #{workers} processes on port #{port}..."
22
+ @server = TCPServer.new(@port)
23
+ @server.listen(1024)
24
+
25
+ workers.times do
26
+ fork(&method(:run_accept_loop))
27
+ end
28
+ Process.waitall
29
+ else
30
+ puts "Starting single-process server on port #{port}..."
31
+ @server = TCPServer.new(@port)
32
+ @server.listen(1024)
33
+ run_accept_loop
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ #
40
+ # Main accept loop
41
+ #
42
+ def run_accept_loop
43
+ enable_reuse(@server)
44
+ @running = true
45
+ puts "[PID #{Process.pid}] Listening on port #{@port}..."
46
+
47
+ while @running
48
+ begin
49
+ client = @server.accept_nonblock if @running
50
+ rescue IO::WaitReadable, Errno::EINTR
51
+ IO.select([@server], nil, nil, 0.1) if @running
52
+ retry
53
+ end
54
+
55
+ # Handle connection in a new thread
56
+ next unless client
57
+
58
+ thr = Thread.start(client) do |socket|
59
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
60
+ handle_request(socket)
61
+ socket.close
62
+ rescue Errno::ECONNRESET, Errno::EPIPE, IOError, Errno::EINVAL
63
+ # Connection reset by peer - ignore
64
+ ensure
65
+ remove_thread(Thread.current)
66
+ end
67
+
68
+ add_thread(thr)
69
+ end
70
+ ensure
71
+ if @server
72
+ puts "[PID #{Process.pid}] Shutting down server socket..."
73
+ @server.close
74
+ end
75
+ end
76
+
77
+ #
78
+ # Actually handle requests in a keep-alive loop
79
+ #
80
+ def handle_request(socket)
81
+ while @running
82
+
83
+ begin
84
+ request_line = socket.gets if @running
85
+ rescue IO::WaitReadable, Errno::EINTR, Errno::EPIPE
86
+ IO.select([socket], nil, nil, 0.5) if @running
87
+ retry
88
+ end
89
+
90
+ break if request_line.nil? || request_line.strip.empty?
91
+
92
+ method, path, http_version = request_line.split
93
+
94
+ # Parse headers
95
+ headers = {}
96
+ content_length = 0
97
+ while (line = socket.gets) && line != "\r\n"
98
+ key, value = line.split(': ', 2)
99
+ headers[key.downcase] = value.strip
100
+ content_length = value.to_i if key == 'Content-Length'
101
+ end
102
+
103
+ body = content_length.positive? ? socket.read(content_length) : (+'').force_encoding('ASCII-8BIT')
104
+
105
+ # Build Rack environment
106
+ env = {
107
+ 'REQUEST_METHOD' => method,
108
+ 'PATH_INFO' => path,
109
+ 'HTTP_VERSION' => http_version,
110
+ 'QUERY_STRING' => path[/\?.*/].to_s,
111
+ 'SERVER_PROTOCOL' => 'HTTP/1.1',
112
+ 'SERVER_NAME' => 'localhost',
113
+ 'rack.url_scheme' => 'http',
114
+ 'rack.request.headers' => headers,
115
+ 'rack.input' => StringIO.new(body),
116
+ 'rack.errors' => StringIO.new('')
117
+ }
118
+
119
+ # Call the Rack app
120
+ status, response_headers, body_enum = @app.call(env)
121
+
122
+ content = body_enum.to_enum(:each).map(&:to_s).join
123
+
124
+ response_headers['Connection'] = 'keep-alive'
125
+ response_headers['Content-Length'] = content.bytesize.to_s
126
+
127
+ # Write the response
128
+ socket.print "HTTP/1.1 #{status} #{http_status_message(status)}\r\n"
129
+ response_headers.each { |k, v| socket.print "#{k}: #{v}\r\n" }
130
+ socket.print "\r\n"
131
+ socket.print content
132
+ end
133
+ end
134
+
135
+ #
136
+ # Thread management helpers
137
+ #
138
+ def add_thread(thr)
139
+ @threads_m.synchronize { @threads << thr }
140
+ end
141
+
142
+ def remove_thread(thr)
143
+ @threads_m.synchronize { @threads.delete(thr) }
144
+ end
145
+
146
+ #
147
+ # Trap signals, set @running = false, then force-kill leftover threads
148
+ #
149
+ def trap_signals
150
+ %w[INT TERM].each do |signal|
151
+ trap(signal) do
152
+ @running = false
153
+ # Give threads a grace period to finish
154
+ graceful_shutdown_threads(timeout: 5)
155
+ end
156
+ end
157
+ end
158
+
159
+ #
160
+ # Gracefully wait for each thread to finish; then force-kill if still alive
161
+ #
162
+ def graceful_shutdown_threads(timeout:)
163
+ @threads.each do |thr|
164
+ next unless thr.alive?
165
+
166
+ # Try a graceful join
167
+ thr.join(timeout)
168
+ # If still running after timeout, forcibly kill
169
+ if thr.alive?
170
+ puts "[PID #{Process.pid}] Force-killing thread #{thr.object_id}"
171
+ thr.kill
172
+ end
173
+ end
174
+ end
175
+
176
+ #
177
+ # Reuse port if supported
178
+ #
179
+ def enable_reuse(server)
180
+ server.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true)
181
+ begin
182
+ server.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEPORT, true)
183
+ rescue StandardError
184
+ # Not supported on all systems
185
+ nil
186
+ end
187
+ end
188
+
189
+ #
190
+ # Utility: Convert a status code to reason phrase
191
+ #
192
+ def http_status_message(status)
193
+ {
194
+ 200 => 'OK',
195
+ 404 => 'Not Found'
196
+ }[status] || 'Unknown'
197
+ end
198
+ end
199
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LazyGraph
4
- VERSION = '0.1.6'
4
+ VERSION = '0.2.0'
5
5
  end
data/lib/lazy_graph.rb CHANGED
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'logger'
4
-
5
3
  # LazyGraph is a library which allows you to define a strictly typed hierarchical Graph structure.
6
4
  # Within this graph you can annotate certain nodes as derived nodes, which compute their outputs based
7
5
  # on input dependencies on other nodes in the graph structure (can be relative and absolute references).
@@ -12,11 +10,7 @@ require 'logger'
12
10
  #
13
11
  module LazyGraph
14
12
  class AbortError < StandardError; end
15
- class << self
16
- attr_accessor :logger
17
- end
18
-
19
- self.logger = Logger.new($stdout)
13
+ class ValidationError < StandardError; end
20
14
  end
21
15
 
22
16
  require_relative 'lazy_graph/context'
@@ -29,4 +23,8 @@ require_relative 'lazy_graph/hash_utils'
29
23
  require_relative 'lazy_graph/builder'
30
24
  require_relative 'lazy_graph/builder_group'
31
25
  require_relative 'lazy_graph/stack_pointer'
32
- require_relative 'lazy_graph/server'
26
+ require_relative 'lazy_graph/cli'
27
+ require_relative 'lazy_graph/rack_server'
28
+ require_relative 'lazy_graph/rack_app'
29
+ require_relative 'lazy_graph/logger'
30
+ require_relative 'lazy_graph/environment'
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lazy_graph
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wouter Coppieters
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2024-12-29 00:00:00.000000000 Z
10
+ date: 2025-01-07 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: json-schema
@@ -52,13 +52,13 @@ dependencies:
52
52
  - !ruby/object:Gem::Version
53
53
  version: '0'
54
54
  - !ruby/object:Gem::Dependency
55
- name: rack
55
+ name: benchmark-ips
56
56
  requirement: !ruby/object:Gem::Requirement
57
57
  requirements:
58
58
  - - ">="
59
59
  - !ruby/object:Gem::Version
60
60
  version: '0'
61
- type: :runtime
61
+ type: :development
62
62
  prerelease: false
63
63
  version_requirements: !ruby/object:Gem::Requirement
64
64
  requirements:
@@ -66,7 +66,21 @@ dependencies:
66
66
  - !ruby/object:Gem::Version
67
67
  version: '0'
68
68
  - !ruby/object:Gem::Dependency
69
- name: ostruct
69
+ name: debug
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '1.0'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '1.0'
82
+ - !ruby/object:Gem::Dependency
83
+ name: fiddle
70
84
  requirement: !ruby/object:Gem::Requirement
71
85
  requirements:
72
86
  - - ">="
@@ -80,7 +94,7 @@ dependencies:
80
94
  - !ruby/object:Gem::Version
81
95
  version: '0'
82
96
  - !ruby/object:Gem::Dependency
83
- name: rubocop
97
+ name: listen
84
98
  requirement: !ruby/object:Gem::Requirement
85
99
  requirements:
86
100
  - - ">="
@@ -94,7 +108,7 @@ dependencies:
94
108
  - !ruby/object:Gem::Version
95
109
  version: '0'
96
110
  - !ruby/object:Gem::Dependency
97
- name: fiddle
111
+ name: memory_profiler
98
112
  requirement: !ruby/object:Gem::Requirement
99
113
  requirements:
100
114
  - - ">="
@@ -108,19 +122,61 @@ dependencies:
108
122
  - !ruby/object:Gem::Version
109
123
  version: '0'
110
124
  - !ruby/object:Gem::Dependency
111
- name: debug
125
+ name: observer
126
+ requirement: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ type: :development
132
+ prerelease: false
133
+ version_requirements: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ - !ruby/object:Gem::Dependency
139
+ name: ostruct
140
+ requirement: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ type: :development
146
+ prerelease: false
147
+ version_requirements: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ - !ruby/object:Gem::Dependency
153
+ name: rack
154
+ requirement: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
159
+ type: :development
160
+ prerelease: false
161
+ version_requirements: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ - !ruby/object:Gem::Dependency
167
+ name: rake
112
168
  requirement: !ruby/object:Gem::Requirement
113
169
  requirements:
114
170
  - - "~>"
115
171
  - !ruby/object:Gem::Version
116
- version: '1.0'
172
+ version: '13.0'
117
173
  type: :development
118
174
  prerelease: false
119
175
  version_requirements: !ruby/object:Gem::Requirement
120
176
  requirements:
121
177
  - - "~>"
122
178
  - !ruby/object:Gem::Version
123
- version: '1.0'
179
+ version: '13.0'
124
180
  - !ruby/object:Gem::Dependency
125
181
  name: rdoc
126
182
  requirement: !ruby/object:Gem::Requirement
@@ -135,6 +191,48 @@ dependencies:
135
191
  - - ">="
136
192
  - !ruby/object:Gem::Version
137
193
  version: '0'
194
+ - !ruby/object:Gem::Dependency
195
+ name: rubocop
196
+ requirement: !ruby/object:Gem::Requirement
197
+ requirements:
198
+ - - ">="
199
+ - !ruby/object:Gem::Version
200
+ version: '0'
201
+ type: :development
202
+ prerelease: false
203
+ version_requirements: !ruby/object:Gem::Requirement
204
+ requirements:
205
+ - - ">="
206
+ - !ruby/object:Gem::Version
207
+ version: '0'
208
+ - !ruby/object:Gem::Dependency
209
+ name: solargraph
210
+ requirement: !ruby/object:Gem::Requirement
211
+ requirements:
212
+ - - ">="
213
+ - !ruby/object:Gem::Version
214
+ version: '0'
215
+ type: :development
216
+ prerelease: false
217
+ version_requirements: !ruby/object:Gem::Requirement
218
+ requirements:
219
+ - - ">="
220
+ - !ruby/object:Gem::Version
221
+ version: '0'
222
+ - !ruby/object:Gem::Dependency
223
+ name: vernier
224
+ requirement: !ruby/object:Gem::Requirement
225
+ requirements:
226
+ - - ">="
227
+ - !ruby/object:Gem::Version
228
+ version: '0'
229
+ type: :development
230
+ prerelease: false
231
+ version_requirements: !ruby/object:Gem::Requirement
232
+ requirements:
233
+ - - ">="
234
+ - !ruby/object:Gem::Version
235
+ version: '0'
138
236
  description: JSON Driven, Stateless Rules Engine for JIT and efficient evaluation
139
237
  of complex rules and computation graphs.
140
238
  email:
@@ -148,15 +246,20 @@ files:
148
246
  - LICENSE.txt
149
247
  - README.md
150
248
  - Rakefile
249
+ - examples/converter.rb
151
250
  - examples/performance_tests.rb
251
+ - examples/shopping_cart_totals.rb
152
252
  - lib/lazy_graph.rb
153
253
  - lib/lazy_graph/builder.rb
154
254
  - lib/lazy_graph/builder/dsl.rb
155
255
  - lib/lazy_graph/builder_group.rb
256
+ - lib/lazy_graph/cli.rb
156
257
  - lib/lazy_graph/context.rb
258
+ - lib/lazy_graph/environment.rb
157
259
  - lib/lazy_graph/graph.rb
158
260
  - lib/lazy_graph/hash_utils.rb
159
261
  - lib/lazy_graph/lazy-graph.json
262
+ - lib/lazy_graph/logger.rb
160
263
  - lib/lazy_graph/missing_value.rb
161
264
  - lib/lazy_graph/node.rb
162
265
  - lib/lazy_graph/node/array_node.rb
@@ -168,7 +271,8 @@ files:
168
271
  - lib/lazy_graph/path_parser/path.rb
169
272
  - lib/lazy_graph/path_parser/path_group.rb
170
273
  - lib/lazy_graph/path_parser/path_part.rb
171
- - lib/lazy_graph/server.rb
274
+ - lib/lazy_graph/rack_app.rb
275
+ - lib/lazy_graph/rack_server.rb
172
276
  - lib/lazy_graph/stack_pointer.rb
173
277
  - lib/lazy_graph/version.rb
174
278
  - logo.png
@@ -1,96 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'rack'
4
-
5
- module LazyGraph
6
- class Server
7
- ALLOWED_VALUES_VALIDATE = [true, false, nil, 'input', 'context'].to_set.freeze
8
- ALLOWED_VALUES_DEBUG = [true, false, nil].to_set.freeze
9
-
10
- attr_reader :routes
11
-
12
- def initialize(routes: {})
13
- @routes = routes.transform_keys(&:to_sym).compare_by_identity
14
- end
15
-
16
- def call(env)
17
- # Rack environment contains request details
18
- env[:X_REQUEST_TIME_START] = Process.clock_gettime(Process::CLOCK_MONOTONIC)
19
- request = Rack::Request.new(env)
20
-
21
- unless (graph_module = @routes[request.path.to_sym])
22
- return not_found!(request.path)
23
- end
24
-
25
- return success!(request, graph_module.usage) if request.get?
26
-
27
- if request.post?
28
- body = JSON.parse(request.body.read)
29
- context, modules, validate, debug, query = body.values_at('context', 'modules', 'validate', 'debug', 'query')
30
- unless context.is_a?(Hash) && !context.empty?
31
- return not_acceptable!(request, "Invalid 'context' Parameter", 'Should be a non-empty object.')
32
- end
33
-
34
- unless modules.is_a?(Hash) && !modules.empty?
35
- return not_acceptable!(request, "Invalid 'modules' Parameter", 'Should be a non-empty object.')
36
- end
37
-
38
- unless ALLOWED_VALUES_VALIDATE.include?(validate)
39
- return not_acceptable!(
40
- request, "Invalid 'validate' Parameter", "Should be nil, bool, or one of 'input', 'context'"
41
- )
42
- end
43
-
44
- unless ALLOWED_VALUES_DEBUG.include?(debug)
45
- return not_acceptable!(request, "Invalid 'debug' Parameter", 'Should be nil or bool')
46
- end
47
-
48
- unless query.nil? || query.is_a?(String) || (query.is_a?(Array) && query.all? do |q|
49
- q.is_a?(String)
50
- end)
51
- return not_acceptable!(request, "Invalid 'query' Parameter", 'Should be nil, array or string array')
52
- end
53
-
54
- begin
55
- result = graph_module.eval!(
56
- modules: modules, context: context,
57
- validate: validate, debug: debug,
58
- query: query
59
- )
60
- return not_acceptable!(request, result[:message], result[:detail]) if result[:type] == :error
61
-
62
- return success!(request, result)
63
- rescue StandardError => e
64
- LazyGraph.logger.error(e.message)
65
- LazyGraph.logger.error(e.backtrace.join("\n"))
66
-
67
- return error!(request, 500, 'Internal Server Error', e.message)
68
- end
69
- end
70
-
71
- not_found!("#{request.request_method} #{request.path}")
72
- end
73
-
74
- def not_acceptable!(request, message, details = '')
75
- error!(request, 406, message, details)
76
- end
77
-
78
- def not_found!(request, details = '')
79
- error!(request, 404, 'Not Found', details)
80
- end
81
-
82
- def request_ms(request)
83
- ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - request.env[:X_REQUEST_TIME_START]) * 1000.0).round(3)
84
- end
85
-
86
- def success!(request, result, status: 200)
87
- LazyGraph.logger.info("#{request.request_method}: #{request.path} => #{status} #{request_ms(request)}ms")
88
- [status, { 'Content-Type' => 'text/json' }, [result.to_json]]
89
- end
90
-
91
- def error!(request, status, message, details = '')
92
- LazyGraph.logger.info("#{request.request_method}: #{request.path} => #{status} #{request_ms(request)}ms")
93
- [status, { 'Content-Type' => 'text/json' }, [{ 'error': message, 'details': details }.to_json]]
94
- end
95
- end
96
- end