mumukit 0.3.0 → 2.4.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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/bin/limit +10 -0
  3. data/bin/mulang +0 -0
  4. data/lib/locales/en.yml +4 -0
  5. data/lib/locales/es.yml +4 -0
  6. data/lib/mumukit/defaults/default_expectations_hook.rb +9 -0
  7. data/lib/mumukit/defaults/default_feedback_hook.rb +5 -0
  8. data/lib/mumukit/defaults/default_metadata_hook.rb +5 -0
  9. data/lib/mumukit/defaults/default_query_hook.rb +9 -0
  10. data/lib/mumukit/defaults/default_test_hook.rb +9 -0
  11. data/lib/mumukit/defaults/default_validation_hook.rb +4 -0
  12. data/lib/mumukit/defaults.rb +11 -0
  13. data/lib/mumukit/env.rb +11 -0
  14. data/lib/mumukit/hook.rb +30 -0
  15. data/lib/mumukit/isolated_environment.rb +51 -0
  16. data/lib/mumukit/metatest/checker.rb +34 -0
  17. data/lib/mumukit/metatest/errors.rb +10 -0
  18. data/lib/mumukit/metatest/framework.rb +20 -0
  19. data/lib/mumukit/metatest/identity_runner.rb +7 -0
  20. data/lib/mumukit/metatest.rb +7 -0
  21. data/lib/mumukit/request_validation_error.rb +4 -0
  22. data/lib/mumukit/runner.rb +33 -0
  23. data/lib/mumukit/runtime/info.rb +30 -0
  24. data/lib/mumukit/runtime/runtime.rb +35 -0
  25. data/lib/mumukit/runtime/shortcuts.rb +12 -0
  26. data/lib/mumukit/runtime/symbol.rb +17 -0
  27. data/lib/mumukit/runtime.rb +4 -0
  28. data/lib/mumukit/server/app.rb +71 -0
  29. data/lib/mumukit/server/response_builder.rb +62 -0
  30. data/lib/mumukit/server/test_server.rb +118 -0
  31. data/lib/mumukit/server.rb +7 -0
  32. data/lib/mumukit/templates/file_hook.rb +49 -0
  33. data/lib/mumukit/templates/mulang_expectations_hook.rb +57 -0
  34. data/lib/mumukit/templates/with_code_smells.rb +7 -0
  35. data/lib/mumukit/templates/with_embedded_environment.rb +12 -0
  36. data/lib/mumukit/templates/with_isolated_environment.rb +11 -0
  37. data/lib/mumukit/templates/with_mashup_file_content.rb +11 -0
  38. data/lib/mumukit/templates/with_structured_results.rb +15 -0
  39. data/lib/mumukit/templates.rb +12 -0
  40. data/lib/mumukit/version.rb +1 -1
  41. data/lib/mumukit/with_command_line.rb +33 -1
  42. data/lib/mumukit/with_content_type.rb +5 -0
  43. data/lib/mumukit/with_tempfile.rb +13 -2
  44. data/lib/mumukit.rb +62 -8
  45. metadata +180 -21
  46. data/lib/mumukit/file_test_compiler.rb +0 -14
  47. data/lib/mumukit/file_test_runner.rb +0 -20
  48. data/lib/mumukit/stub.rb +0 -5
  49. data/lib/mumukit/test_server.rb +0 -42
  50. data/lib/mumukit/test_server_app.rb +0 -28
  51. data/lib/stubs/expectations_runner.rb +0 -5
  52. data/lib/stubs/test_runner.rb +0 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 96ca9d2847fa9cf5412c483dd9e7900b0c093549
4
- data.tar.gz: 07439f192678d0ac52cf8009ed2249a6955bcc88
3
+ metadata.gz: 3f8afb7358f144c2e25c356c70f0061144ff2f04
4
+ data.tar.gz: d41e834b6c92fea4897c87d46f5d3303983a8209
5
5
  SHA512:
6
- metadata.gz: 7fb5c9cde7fb1e16ff83232a7b43dd11511c6ab504b07a1a72984bf36055d872fc48b51435ec027ff81a0626b5324595620cb626e776f4c2c9f17ac29bf6993d
7
- data.tar.gz: f5eb3c788559964d2ac32ed272631ee3305cf17b293435d25499be1259b64122cc67c5812e76f426f2f9712cb27b3b3aa1ad8f52856d2b874a3e2fb7d8645c51
6
+ metadata.gz: b4830f051f2f4ede2c1a76f05306974ea4c8d05947b2a0b8e4d2f7c3b3ad5dbb32207045accb30c10815bd90dd2e6ccc8a9e982a947e48c0fd17df2582b67e63
7
+ data.tar.gz: d1c908995f05aeab6cd7bc6a47c4614a20ec242cb3e49fee4497060c03ebc87a3636578652a3fb3716016543861405db1c7a4cd94335772303ed5cc30a06d30a
data/bin/limit ADDED
@@ -0,0 +1,10 @@
1
+ #!/bin/bash
2
+
3
+ SIZE=$1
4
+ shift
5
+ TIME=$1
6
+ shift
7
+ COMMAND=$@
8
+
9
+ ulimit -Sv $(expr 1024 \* $SIZE) -St $TIME
10
+ sh -c "$COMMAND"
data/bin/mulang ADDED
Binary file
@@ -0,0 +1,4 @@
1
+ en:
2
+ mumukit:
3
+ memory_exceeded: Memory limit exceeded. Is your program trying to allocate to much memory?
4
+ time_exceeded: Execution time limit of %{limit}s exceeded. Is your program performing an infinite loop or recursion?
@@ -0,0 +1,4 @@
1
+ es:
2
+ mumukit:
3
+ memory_exceeded: Limite de memoria excedido. ¿Tu programa está alocando demasiada memoria?
4
+ time_exceeded: Limite de tiempo de ejecución de %{limit}s excedido. ¿Tu programa tiene recursión o bucle infinito?
@@ -0,0 +1,9 @@
1
+ class Mumukit::Defaults::ExpectationsHook < Mumukit::Hook
2
+ def compile(request)
3
+ request
4
+ end
5
+
6
+ def run!(request)
7
+ []
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ class Mumukit::Defaults::FeedbackHook < Mumukit::Hook
2
+ def run!(request, results)
3
+ ''
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Mumukit::Defaults::MetadataHook < Mumukit::Hook
2
+ def metadata
3
+ {}
4
+ end
5
+ end
@@ -0,0 +1,9 @@
1
+ class Mumukit::Defaults::QueryHook < Mumukit::Hook
2
+ def compile(request)
3
+ request
4
+ end
5
+
6
+ def run!(request)
7
+ ['unimplemented', :aborted]
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ class Mumukit::Defaults::TestHook < Mumukit::Hook
2
+ def compile(request)
3
+ request
4
+ end
5
+
6
+ def run!(compilation)
7
+ ['unimplemented', :aborted]
8
+ end
9
+ end
@@ -0,0 +1,4 @@
1
+ class Mumukit::Defaults::ValidationHook < Mumukit::Hook
2
+ def validate!(request)
3
+ end
4
+ end
@@ -0,0 +1,11 @@
1
+ module Mumukit
2
+ module Defaults
3
+ end
4
+ end
5
+
6
+ require_relative './defaults/default_expectations_hook'
7
+ require_relative './defaults/default_query_hook'
8
+ require_relative './defaults/default_feedback_hook'
9
+ require_relative './defaults/default_validation_hook'
10
+ require_relative './defaults/default_test_hook'
11
+ require_relative './defaults/default_metadata_hook'
@@ -0,0 +1,11 @@
1
+ module Mumukit
2
+ module Env
3
+ def self.env
4
+ Thread.current[:mumukit_env]
5
+ end
6
+
7
+ def self.env=(env)
8
+ Thread.current[:mumukit_env] = env
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,30 @@
1
+ class Mumukit::Hook
2
+ attr_reader :config
3
+
4
+ include Mumukit::WithContentType
5
+
6
+ def initialize(config=nil)
7
+ @config = (config||{}).with_indifferent_access
8
+ end
9
+
10
+ def t(*args)
11
+ I18n.t(*args)
12
+ end
13
+
14
+ def method_missing(name, *args, &block)
15
+ super unless should_forward_to_config?(args, name, &block)
16
+ @config[name]
17
+ end
18
+
19
+ def env
20
+ Mumukit::Env.env
21
+ end
22
+
23
+ def logger
24
+ env['rack.logger']
25
+ end
26
+
27
+ def should_forward_to_config?(args, name)
28
+ args.length == 0 && !block_given? && @config[name]
29
+ end
30
+ end
@@ -0,0 +1,51 @@
1
+ require 'docker'
2
+ require 'pathname'
3
+
4
+ module Mumukit
5
+ class IsolatedEnvironment
6
+
7
+ attr_accessor :container
8
+
9
+ def configure!(*files)
10
+
11
+ filenames = files.map { |it| File.absolute_path(it.path) }
12
+ dirnames = filenames.map { |it| Pathname.new(it).dirname }
13
+
14
+ binds = dirnames.map { |it| "#{it}:#{it}" }
15
+ volumes = Hash[[dirnames.map { |it| [it, {}] }]]
16
+
17
+ command = yield(*filenames).split
18
+
19
+ self.container = Docker::Container.create(
20
+ 'Image' => Mumukit.config.docker_image,
21
+ 'Cmd' => command,
22
+ 'NetworkDisabled' => true,
23
+ 'HostConfig' => {
24
+ 'Binds' => binds},
25
+ 'Volumes' => volumes)
26
+ end
27
+
28
+ def run!
29
+ container.start
30
+ container.wait(Mumukit.config.command_time_limit)
31
+
32
+ exit = container.json['State']['ExitCode']
33
+ out = container.streaming_logs(stdout: true, stderr: true)
34
+
35
+ if exit == 0
36
+ [out, :passed]
37
+ else
38
+ [out, :failed]
39
+ end
40
+ rescue Docker::Error::TimeoutError => e
41
+ [I18n.t('mumukit.time_exceeded', limit: Mumukit.config.command_time_limit), :aborted]
42
+ end
43
+
44
+ def destroy!
45
+ if container
46
+ container.stop
47
+ container.delete
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,34 @@
1
+ module Mumukit::Metatest
2
+ class Checker
3
+ def check(value, example)
4
+ example[:postconditions].each { |key, arg| check_assertion key, value, arg, example }
5
+ [example[:name], :passed, render_success_output(value)]
6
+ rescue => e
7
+ [example[:name], :failed, render_error_output(value, e.message)]
8
+ end
9
+
10
+ def render_success_output(value)
11
+ nil
12
+ end
13
+
14
+ def render_error_output(value, error)
15
+ error
16
+ end
17
+
18
+ def check_assertion(key, value, arg, example)
19
+ send "check_#{key}", value, arg
20
+ end
21
+
22
+ def fail(message)
23
+ raise Mumukit::Metatest::Failed, message
24
+ end
25
+
26
+ def abort(message)
27
+ raise Mumukit::Metatest::Aborted, message
28
+ end
29
+
30
+ def error(message)
31
+ raise Mumukit::Metatest::Errored, message
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,10 @@
1
+ module Mumukit::Metatest
2
+ class Aborted < StandardError
3
+ end
4
+
5
+ class Errored < StandardError
6
+ end
7
+
8
+ class Failed < StandardError
9
+ end
10
+ end
@@ -0,0 +1,20 @@
1
+ module Mumukit::Metatest
2
+ class Framework
3
+ def initialize(options={})
4
+ @runner = options[:runner]
5
+ @checker = options[:checker]
6
+ end
7
+
8
+ def test(compilation, examples)
9
+ [examples.map { |it| example(compilation, it) }]
10
+ rescue Aborted => e
11
+ [e.message, :aborted]
12
+ rescue Errored => e
13
+ [e.message, :errored]
14
+ end
15
+
16
+ def example(compilation, example)
17
+ @checker.check(@runner.run(compilation, example), example)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,7 @@
1
+ module Mumukit::Metatest
2
+ class IdentityRunner
3
+ def run(it, _example)
4
+ it
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Mumukit::Metatest
2
+ end
3
+
4
+ require_relative './metatest/errors'
5
+ require_relative './metatest/checker'
6
+ require_relative './metatest/framework'
7
+ require_relative './metatest/identity_runner'
@@ -0,0 +1,4 @@
1
+ module Mumukit
2
+ class RequestValidationError < StandardError
3
+ end
4
+ end
@@ -0,0 +1,33 @@
1
+ class Mumukit::Runner
2
+ attr_reader :name, :runtime
3
+
4
+ def initialize(name)
5
+ @name = name
6
+ end
7
+
8
+ def configure
9
+ @config ||= self.class.default_config.clone
10
+ yield @config
11
+ end
12
+
13
+ def configure_runtime(config)
14
+ @runtime = Mumukit::Runtime.new(config)
15
+ end
16
+
17
+ def config
18
+ @config or raise 'This runner has not being configured yet'
19
+ end
20
+
21
+ def prefix
22
+ name.camelize
23
+ end
24
+
25
+ def self.default_config
26
+ @default_config
27
+ end
28
+
29
+ def self.configure_defaults
30
+ @default_config ||= OpenStruct.new
31
+ yield @default_config
32
+ end
33
+ end
@@ -0,0 +1,30 @@
1
+ module Mumukit::RuntimeInfo
2
+ def info
3
+ {
4
+ name: Mumukit.runner_name,
5
+ version: (File.read('version') rescue 'master'),
6
+ escualo_base_version: ENV['ESCUALO_BASE_VERSION'],
7
+ escualo_service_version: ENV['ESCUALO_SERVICE_VERSION'],
8
+ mumukit_version: Mumukit::VERSION,
9
+ output_content_type: Mumukit.config.content_type,
10
+ comment_type: Mumukit.config.comment_type,
11
+ features: {
12
+ query: query_hook?,
13
+ expectations: expectations_hook?,
14
+ feedback: feedback_hook?,
15
+ secure: validation_hook?,
16
+ stateful: Mumukit.config.stateful,
17
+ preprocessor: Mumukit.config.preprocessor_enabled,
18
+
19
+ sandboxed: any_hook_include?([:test, :query], Mumukit::Templates::WithIsolatedEnvironment),
20
+ structured: any_hook_include?([:test], Mumukit::Templates::WithStructuredResults) || Mumukit.config.structured
21
+ }
22
+ }
23
+ end
24
+
25
+ private
26
+
27
+ def any_hook_include?(hooks, mixin)
28
+ hooks.any? { |it| hook_includes?(it, mixin) }
29
+ end
30
+ end
@@ -0,0 +1,35 @@
1
+ class Mumukit::Runtime
2
+ include Mumukit::RuntimeShortcuts
3
+ include Mumukit::RuntimeInfo
4
+
5
+ def initialize(config)
6
+ @config = config
7
+ @hook_classes = {}
8
+ end
9
+
10
+ def hook_defined?(hook_name)
11
+ hook_name.to_default_mumukit_hook_class rescue raise "Wrong hook #{hook_name}"
12
+
13
+ Kernel.const_defined? hook_name.to_mumukit_hook_class_name
14
+ end
15
+
16
+ def hook_includes?(hook_name, mixin)
17
+ hook_class(hook_name).included_modules.include?(mixin)
18
+ end
19
+
20
+ def new_hook(hook_name)
21
+ hook_class(hook_name).new(@config)
22
+ end
23
+
24
+ private
25
+
26
+ def hook_class(hook_name)
27
+ @hook_classes[hook_name] ||=
28
+ if hook_defined? hook_name
29
+ hook_name.to_mumukit_hook_class
30
+ else
31
+ hook_name.to_default_mumukit_hook_class
32
+ end
33
+ end
34
+ end
35
+
@@ -0,0 +1,12 @@
1
+ module Mumukit::RuntimeShortcuts
2
+ def method_missing(name, *args, &block)
3
+ n = name.to_s
4
+ if n =~ /(.*)_hook\?/
5
+ hook_defined? $1.to_sym
6
+ elsif n =~ /(.*)_hook/
7
+ new_hook $1.to_sym
8
+ else
9
+ super
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,17 @@
1
+ class Symbol
2
+ def to_mumukit_hook_class_name
3
+ "#{Mumukit.prefix}#{to_simple_mumukit_hook_class_name}"
4
+ end
5
+
6
+ def to_simple_mumukit_hook_class_name
7
+ "#{to_s.camelize.to_sym}Hook"
8
+ end
9
+
10
+ def to_mumukit_hook_class
11
+ Kernel.const_get to_mumukit_hook_class_name
12
+ end
13
+
14
+ def to_default_mumukit_hook_class
15
+ Kernel.const_get "Mumukit::Defaults::#{to_simple_mumukit_hook_class_name}"
16
+ end
17
+ end
@@ -0,0 +1,4 @@
1
+ require_relative './runtime/symbol'
2
+ require_relative './runtime/info'
3
+ require_relative './runtime/shortcuts'
4
+ require_relative './runtime/runtime'
@@ -0,0 +1,71 @@
1
+ require 'sinatra/base'
2
+ require 'yaml'
3
+ require 'json'
4
+
5
+
6
+ class Mumukit::Server::App < Sinatra::Base
7
+ configure do
8
+ set :mumuki_url, 'http://mumuki.io'
9
+ set :show_exceptions, :after_handler
10
+ enable :logging
11
+ end
12
+
13
+ configure :development do
14
+ set :config_filename, 'config/development.yml'
15
+ end
16
+
17
+ configure :production do
18
+ set :config_filename, 'config/production.yml'
19
+ end
20
+
21
+ runtime_config = YAML.load_file(settings.config_filename) rescue nil
22
+
23
+ Mumukit.configure_runtime(runtime_config)
24
+
25
+ set :server, Mumukit::Server::TestServer.new
26
+
27
+ before do
28
+ content_type 'application/json'
29
+ end
30
+
31
+ before do
32
+ Mumukit::Env.env = env
33
+ server.start_request!(parse_request)
34
+ end
35
+
36
+ helpers do
37
+ def server
38
+ settings.server
39
+ end
40
+
41
+ def parse_request
42
+ @parsed_request ||= server.parse_request(request)
43
+ end
44
+ end
45
+
46
+ get '/info' do
47
+ JSON.generate(server.info(request.url))
48
+ end
49
+
50
+ post '/test' do
51
+ JSON.generate(server.test!(parse_request))
52
+ end
53
+
54
+ post '/query' do
55
+ JSON.generate(server.query!(parse_request))
56
+ end
57
+
58
+ get '/*' do
59
+ redirect settings.mumuki_url
60
+ end
61
+
62
+ error StandardError do
63
+ content_type :json
64
+ status 200
65
+
66
+ message = Mumukit::ContentType::Plain.format_exception env['sinatra.error']
67
+ logger.error "Unhandled error #{message}"
68
+
69
+ {status: :errored, exit: message}.to_json
70
+ end
71
+ end
@@ -0,0 +1,62 @@
1
+ class Mumukit::Server::ResponseBuilder
2
+
3
+ def add_test_results(r)
4
+ @response = base_response(r)
5
+ end
6
+
7
+ def add_query_results(r)
8
+ @response = unstructured_base_response(r)
9
+ end
10
+
11
+ def add_expectation_results(r)
12
+ @response.merge!(expectationResults: r) if r.present?
13
+ end
14
+
15
+
16
+ def add_feedback(f)
17
+ @response.merge!(feedback: f) if f.present?
18
+ end
19
+
20
+ def build
21
+ @response
22
+ end
23
+
24
+ def self.build(&block)
25
+ builder = new
26
+ builder.instance_eval(&block)
27
+ builder.build
28
+ end
29
+
30
+ private
31
+
32
+ def base_response(test_results)
33
+ if structured_test_result?(test_results)
34
+ structured_base_response(test_results)
35
+ elsif unstructured_test_result?(test_results)
36
+ unstructured_base_response(test_results)
37
+ else
38
+ raise "Invalid test results format: #{test_results}. You must either return [results_array] or [results_string, status]"
39
+ end
40
+ end
41
+
42
+ def structured_test_result?(test_results)
43
+ test_results.size == 1 && test_results[0].is_a?(Array)
44
+ end
45
+
46
+ def unstructured_test_result?(test_results)
47
+ test_results.size == 2 && test_results[0].is_a?(String)
48
+ end
49
+
50
+ def structured_base_response(test_results)
51
+ {testResults: test_results[0].map { |title, status, result|
52
+ {title: title,
53
+ status: status,
54
+ result: result} }}
55
+ end
56
+
57
+ def unstructured_base_response(test_results)
58
+ {exit: test_results[1],
59
+ out: test_results[0]}
60
+ end
61
+
62
+ end
@@ -0,0 +1,118 @@
1
+ require 'yaml'
2
+ require 'ostruct'
3
+
4
+ class Mumukit::Server::TestServer
5
+
6
+ include Mumukit::WithContentType
7
+
8
+ def runtime
9
+ Mumukit.runtime
10
+ end
11
+
12
+ def info(url)
13
+ runtime.info.merge(runtime.metadata_hook.metadata).merge(url: url)
14
+ end
15
+
16
+ def start_request!(_request)
17
+ end
18
+
19
+ def parse_request(sinatra_request)
20
+ OpenStruct.new parse_request_headers(sinatra_request).merge(parse_request_body(sinatra_request))
21
+ end
22
+
23
+ def parse_request_headers(sinatra_request)
24
+ {}
25
+ end
26
+
27
+ def parse_request_body(sinatra_request)
28
+ JSON.parse(sinatra_request.body.read).tap do |it|
29
+ I18n.locale = it['locale'] || :en
30
+ end rescue {}
31
+ end
32
+
33
+ def test!(request)
34
+ respond_to(request) do |r|
35
+ test_results = run_tests! r
36
+ expectation_results = run_expectations! r
37
+
38
+ results = OpenStruct.new(test_results: test_results,
39
+ expectation_results: expectation_results)
40
+
41
+ feedback = run_feedback! r, results
42
+
43
+ Mumukit::Server::ResponseBuilder.build do
44
+ add_test_results(test_results)
45
+ add_expectation_results(expectation_results)
46
+ add_feedback(feedback)
47
+ end
48
+ end
49
+ end
50
+
51
+ def query!(request)
52
+ respond_to(request) do |r|
53
+ results = run_query!(r)
54
+ Mumukit::Server::ResponseBuilder.build do
55
+ add_query_results(results)
56
+ end
57
+ end
58
+ end
59
+
60
+ def run_query!(request)
61
+ compile_and_run runtime.query_hook, request
62
+ end
63
+
64
+ def run_tests!(request)
65
+ return ['', :passed] if request.test.blank?
66
+
67
+ compile_and_run runtime.test_hook, request
68
+ end
69
+
70
+ def run_expectations!(request)
71
+ if request.expectations
72
+ compile_and_run runtime.expectations_hook, request
73
+ else
74
+ []
75
+ end
76
+ end
77
+
78
+ def run_feedback!(request, results)
79
+ runtime.feedback_hook.run!(request, results)
80
+ end
81
+
82
+ private
83
+
84
+ def compile_and_run(hook, request)
85
+ compilation = hook.compile(preprocess request)
86
+ hook.run!(compilation)
87
+ end
88
+
89
+ def preprocess(request)
90
+ if Mumukit.config.preprocessor_enabled
91
+ directives_pipeline.transform(request)
92
+ else
93
+ request
94
+ end
95
+ end
96
+
97
+ def directives_pipeline
98
+ @pipeline ||= Mumukit::Directives::Pipeline.new(
99
+ [Mumukit::Directives::Sections.new,
100
+ Mumukit::Directives::Interpolations.new('test'),
101
+ Mumukit::Directives::Interpolations.new('extra'),
102
+ Mumukit::Directives::Flags.new],
103
+ Mumukit.config.comment_type)
104
+ end
105
+
106
+ def validate_request!(request)
107
+ runtime.validation_hook.validate! request
108
+ end
109
+
110
+ def respond_to(request)
111
+ yield request.tap { |r| validate_request! r }
112
+ rescue Mumukit::RequestValidationError => e
113
+ {exit: :aborted, out: e.message}
114
+ rescue Exception => e
115
+ {exit: :errored, out: content_type.format_exception(e)}
116
+ end
117
+
118
+ end