scorpio 0.4.6 → 0.5.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +3 -0
- data/README.md +1 -1
- data/lib/scorpio/google_api_document.rb +16 -9
- data/lib/scorpio/openapi.rb +125 -131
- data/lib/scorpio/openapi/document.rb +4 -4
- data/lib/scorpio/openapi/operation.rb +2 -2
- data/lib/scorpio/openapi/operations_scope.rb +1 -1
- data/lib/scorpio/openapi/v3/server.rb +1 -1
- data/lib/scorpio/request.rb +13 -9
- data/lib/scorpio/resource_base.rb +9 -2
- data/lib/scorpio/response.rb +3 -1
- data/lib/scorpio/ur.rb +12 -15
- data/lib/scorpio/version.rb +1 -1
- data/scorpio.gemspec +4 -3
- data/test/blog.openapi2.yml +113 -0
- data/test/blog.openapi3.yml +131 -0
- data/test/blog.rb +117 -0
- data/test/blog.rest_description.yml +67 -0
- data/test/blog_scorpio_models.rb +49 -0
- data/test/scorpio_test.rb +105 -0
- data/test/test_helper.rb +86 -0
- metadata +25 -11
@@ -423,7 +423,14 @@ module Scorpio
|
|
423
423
|
|
424
424
|
def response_object_to_instances(object, initialize_options = {})
|
425
425
|
if object.is_a?(JSI::Base)
|
426
|
-
|
426
|
+
models = object.jsi_schemas.map { |schema| models_by_schema[schema] }
|
427
|
+
if models.size == 0
|
428
|
+
model = nil
|
429
|
+
elsif models.size == 1
|
430
|
+
model = models.first
|
431
|
+
else
|
432
|
+
raise(Scorpio::OpenAPI::Error, "multiple models indicated by response JSI. models: #{models.inspect}; jsi: #{jsi.pretty_inspect.chomp}")
|
433
|
+
end
|
427
434
|
end
|
428
435
|
|
429
436
|
if object.respond_to?(:to_hash)
|
@@ -522,6 +529,6 @@ module Scorpio
|
|
522
529
|
def jsi_fingerprint
|
523
530
|
{class: self.class, attributes: JSI::Typelike.as_json(@attributes)}
|
524
531
|
end
|
525
|
-
include JSI::FingerprintHash
|
532
|
+
include JSI::Util::FingerprintHash
|
526
533
|
end
|
527
534
|
end
|
data/lib/scorpio/response.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
module Scorpio
|
2
|
-
|
2
|
+
Response = Scorpio::Ur.properties['response']
|
3
|
+
|
4
|
+
module Response
|
3
5
|
# @return [::JSI::Schema] the schema for this response according to its OpenAPI doc
|
4
6
|
def response_schema
|
5
7
|
ur.scorpio_request.operation.response_schema(status: status, media_type: media_type)
|
data/lib/scorpio/ur.rb
CHANGED
@@ -1,5 +1,16 @@
|
|
1
1
|
module Scorpio
|
2
|
-
|
2
|
+
# Scorpio::Ur is a JSI Schema module with which scorpio extends the ::Ur schema module
|
3
|
+
Ur = JSI::Schema.new({
|
4
|
+
'$id' => 'https://schemas.jsi.unth.net/ur',
|
5
|
+
'properties' => {
|
6
|
+
'request' => {},
|
7
|
+
'response' => {},
|
8
|
+
}
|
9
|
+
}).jsi_schema_module
|
10
|
+
|
11
|
+
-> { Scorpio::Response }.() # invoke autoload
|
12
|
+
|
13
|
+
module Ur
|
3
14
|
attr_accessor :scorpio_request
|
4
15
|
|
5
16
|
# raises a subclass of Scorpio::HTTPError if the response has an error status.
|
@@ -28,19 +39,5 @@ module Scorpio
|
|
28
39
|
end
|
29
40
|
nil
|
30
41
|
end
|
31
|
-
|
32
|
-
private
|
33
|
-
# overrides JSI::Base#class_for_schema to use Scorpio::Response instead of ::Ur::Response.
|
34
|
-
# maybe a Scorpio::Ur::Request in the future if I need to extend that ... or Scorpio::Request
|
35
|
-
# if I decide to make that subclass ::Ur::Request. not sure if that's a good idea or a terrible
|
36
|
-
# idea.
|
37
|
-
def class_for_schema(schema)
|
38
|
-
jsi_class_for_schema = super
|
39
|
-
if jsi_class_for_schema == ::Ur::Response
|
40
|
-
Scorpio::Response
|
41
|
-
else
|
42
|
-
jsi_class_for_schema
|
43
|
-
end
|
44
|
-
end
|
45
42
|
end
|
46
43
|
end
|
data/lib/scorpio/version.rb
CHANGED
data/scorpio.gemspec
CHANGED
@@ -16,14 +16,15 @@ Gem::Specification.new do |spec|
|
|
16
16
|
ignore_files = %w(.gitignore .travis.yml Gemfile test)
|
17
17
|
ignore_files_re = %r{\A(#{ignore_files.map { |f| Regexp.escape(f) }.join('|')})(/|\z)}
|
18
18
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(ignore_files_re) }
|
19
|
+
spec.test_files = `git ls-files -z test`.split("\x0")
|
19
20
|
spec.bindir = "exe"
|
20
21
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
22
|
spec.require_paths = ["lib"]
|
22
23
|
|
23
|
-
spec.add_dependency "jsi", "~> 0.
|
24
|
-
spec.add_dependency "ur", "~> 0.
|
24
|
+
spec.add_dependency "jsi", "~> 0.4.0"
|
25
|
+
spec.add_dependency "ur", "~> 0.2.0"
|
25
26
|
spec.add_dependency "faraday"
|
26
|
-
spec.add_development_dependency "rake"
|
27
|
+
spec.add_development_dependency "rake"
|
27
28
|
spec.add_development_dependency "minitest", "~> 5.0"
|
28
29
|
spec.add_development_dependency "minitest-around"
|
29
30
|
spec.add_development_dependency "minitest-reporters"
|
@@ -0,0 +1,113 @@
|
|
1
|
+
---
|
2
|
+
swagger: '2.0'
|
3
|
+
info:
|
4
|
+
title: Scorpio Blog
|
5
|
+
description: REST service for the Scorpio Blog
|
6
|
+
version: ''
|
7
|
+
contact: {}
|
8
|
+
host: blog
|
9
|
+
basePath: "/v1"
|
10
|
+
schemes: ['https']
|
11
|
+
consumes:
|
12
|
+
- application/json
|
13
|
+
produces:
|
14
|
+
- application/json
|
15
|
+
paths:
|
16
|
+
"/articles":
|
17
|
+
get:
|
18
|
+
tags:
|
19
|
+
- articles
|
20
|
+
operationId: articles.index
|
21
|
+
responses:
|
22
|
+
default:
|
23
|
+
description: default response
|
24
|
+
schema:
|
25
|
+
type: array
|
26
|
+
items:
|
27
|
+
"$ref": "#/definitions/articles"
|
28
|
+
post:
|
29
|
+
tags:
|
30
|
+
- articles
|
31
|
+
operationId: articles.post
|
32
|
+
parameters:
|
33
|
+
- name: body
|
34
|
+
in: body
|
35
|
+
required: true
|
36
|
+
schema:
|
37
|
+
"$ref": "#/definitions/articles"
|
38
|
+
responses:
|
39
|
+
default:
|
40
|
+
description: default response
|
41
|
+
schema:
|
42
|
+
"$ref": "#/definitions/articles"
|
43
|
+
"/articles_with_root":
|
44
|
+
get:
|
45
|
+
tags:
|
46
|
+
- articles
|
47
|
+
operationId: articles.index_with_root
|
48
|
+
responses:
|
49
|
+
default:
|
50
|
+
description: default response
|
51
|
+
schema:
|
52
|
+
type: object
|
53
|
+
properties:
|
54
|
+
articles:
|
55
|
+
type: array
|
56
|
+
items:
|
57
|
+
"$ref": "#/definitions/articles"
|
58
|
+
best_article:
|
59
|
+
"$ref": "#/definitions/articles"
|
60
|
+
version:
|
61
|
+
type: string
|
62
|
+
"/articles/{id}":
|
63
|
+
get:
|
64
|
+
tags:
|
65
|
+
- articles
|
66
|
+
operationId: articles.read
|
67
|
+
parameters:
|
68
|
+
- name: id
|
69
|
+
in: path
|
70
|
+
required: true
|
71
|
+
type: string
|
72
|
+
responses:
|
73
|
+
default:
|
74
|
+
description: default response
|
75
|
+
schema:
|
76
|
+
"$ref": "#/definitions/articles"
|
77
|
+
patch:
|
78
|
+
tags:
|
79
|
+
- articles
|
80
|
+
operationId: articles.patch
|
81
|
+
parameters:
|
82
|
+
- name: id
|
83
|
+
in: path
|
84
|
+
required: true
|
85
|
+
type: string
|
86
|
+
- name: body
|
87
|
+
in: body
|
88
|
+
required: true
|
89
|
+
schema:
|
90
|
+
"$ref": "#/definitions/articles"
|
91
|
+
responses:
|
92
|
+
default:
|
93
|
+
description: default response
|
94
|
+
schema:
|
95
|
+
"$ref": "#/definitions/articles"
|
96
|
+
"/clean":
|
97
|
+
post:
|
98
|
+
tags:
|
99
|
+
- clean
|
100
|
+
operationId: clean
|
101
|
+
responses:
|
102
|
+
default:
|
103
|
+
description: default response
|
104
|
+
definitions:
|
105
|
+
articles:
|
106
|
+
type: object
|
107
|
+
properties:
|
108
|
+
id:
|
109
|
+
type: integer
|
110
|
+
title:
|
111
|
+
type: string
|
112
|
+
author_id:
|
113
|
+
type: integer
|
@@ -0,0 +1,131 @@
|
|
1
|
+
openapi: 3.0.0
|
2
|
+
servers:
|
3
|
+
- url: "{scheme}://{host}:{port}/{basePath}"
|
4
|
+
variables:
|
5
|
+
scheme:
|
6
|
+
default: https
|
7
|
+
host:
|
8
|
+
default: blog.scorpio
|
9
|
+
port:
|
10
|
+
default: '443'
|
11
|
+
basePath:
|
12
|
+
enum:
|
13
|
+
- v1
|
14
|
+
default: v1
|
15
|
+
info:
|
16
|
+
title: Scorpio Blog
|
17
|
+
description: REST service for the Scorpio Blog
|
18
|
+
version: ''
|
19
|
+
contact: {}
|
20
|
+
paths:
|
21
|
+
/articles:
|
22
|
+
get:
|
23
|
+
tags:
|
24
|
+
- articles
|
25
|
+
operationId: articles.index
|
26
|
+
responses:
|
27
|
+
default:
|
28
|
+
description: default response
|
29
|
+
content:
|
30
|
+
application/json:
|
31
|
+
schema:
|
32
|
+
type: array
|
33
|
+
items:
|
34
|
+
$ref: '#/components/schemas/articles'
|
35
|
+
post:
|
36
|
+
tags:
|
37
|
+
- articles
|
38
|
+
operationId: articles.post
|
39
|
+
responses:
|
40
|
+
default:
|
41
|
+
description: default response
|
42
|
+
content:
|
43
|
+
application/json:
|
44
|
+
schema:
|
45
|
+
$ref: '#/components/schemas/articles'
|
46
|
+
requestBody:
|
47
|
+
$ref: '#/components/requestBodies/articles'
|
48
|
+
/articles_with_root:
|
49
|
+
get:
|
50
|
+
tags:
|
51
|
+
- articles
|
52
|
+
operationId: articles.index_with_root
|
53
|
+
responses:
|
54
|
+
default:
|
55
|
+
description: default response
|
56
|
+
content:
|
57
|
+
application/json:
|
58
|
+
schema:
|
59
|
+
type: object
|
60
|
+
properties:
|
61
|
+
articles:
|
62
|
+
type: array
|
63
|
+
items:
|
64
|
+
$ref: '#/components/schemas/articles'
|
65
|
+
best_article:
|
66
|
+
$ref: '#/components/schemas/articles'
|
67
|
+
version:
|
68
|
+
type: string
|
69
|
+
'/articles/{id}':
|
70
|
+
get:
|
71
|
+
tags:
|
72
|
+
- articles
|
73
|
+
operationId: articles.read
|
74
|
+
parameters:
|
75
|
+
- name: id
|
76
|
+
in: path
|
77
|
+
required: true
|
78
|
+
schema:
|
79
|
+
type: string
|
80
|
+
responses:
|
81
|
+
default:
|
82
|
+
description: default response
|
83
|
+
content:
|
84
|
+
application/json:
|
85
|
+
schema:
|
86
|
+
$ref: '#/components/schemas/articles'
|
87
|
+
patch:
|
88
|
+
tags:
|
89
|
+
- articles
|
90
|
+
operationId: articles.patch
|
91
|
+
parameters:
|
92
|
+
- name: id
|
93
|
+
in: path
|
94
|
+
required: true
|
95
|
+
schema:
|
96
|
+
type: string
|
97
|
+
responses:
|
98
|
+
default:
|
99
|
+
description: default response
|
100
|
+
content:
|
101
|
+
application/json:
|
102
|
+
schema:
|
103
|
+
$ref: '#/components/schemas/articles'
|
104
|
+
requestBody:
|
105
|
+
$ref: '#/components/requestBodies/articles'
|
106
|
+
/clean:
|
107
|
+
post:
|
108
|
+
tags:
|
109
|
+
- clean
|
110
|
+
operationId: clean
|
111
|
+
responses:
|
112
|
+
default:
|
113
|
+
description: default response
|
114
|
+
components:
|
115
|
+
requestBodies:
|
116
|
+
articles:
|
117
|
+
content:
|
118
|
+
application/json:
|
119
|
+
schema:
|
120
|
+
$ref: '#/components/schemas/articles'
|
121
|
+
required: true
|
122
|
+
schemas:
|
123
|
+
articles:
|
124
|
+
type: object
|
125
|
+
properties:
|
126
|
+
id:
|
127
|
+
type: integer
|
128
|
+
title:
|
129
|
+
type: string
|
130
|
+
author_id:
|
131
|
+
type: integer
|
data/test/blog.rb
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'sinatra'
|
2
|
+
require 'api_hammer'
|
3
|
+
require 'rack/accept'
|
4
|
+
require 'logger'
|
5
|
+
|
6
|
+
# app
|
7
|
+
|
8
|
+
class Blog < Sinatra::Base
|
9
|
+
include ApiHammer::Sinatra
|
10
|
+
self.supported_media_types = ['application/json']
|
11
|
+
set :static, false
|
12
|
+
disable :protection
|
13
|
+
logpath = Pathname.new('log/test.log')
|
14
|
+
FileUtils.mkdir_p(logpath.dirname)
|
15
|
+
set :logger, ::Logger.new(logpath)
|
16
|
+
logger.level = ::Logger::INFO
|
17
|
+
define_method(:logger) { self.class.logger }
|
18
|
+
use_with_lint ApiHammer::RequestLogger, logger
|
19
|
+
|
20
|
+
# prevent sinatra from using Sinatra::ShowExceptions so we can use ShowTextExceptions instead
|
21
|
+
set :show_exceptions, false
|
22
|
+
# allow errors to bubble past sinatra up to ShowTextExceptions
|
23
|
+
set :raise_errors, true
|
24
|
+
# ShowTextExceptions rescues ruby exceptions and gives a response of 500 with text/plain
|
25
|
+
use_with_lint ApiHammer::ShowTextExceptions, :full_error => true, :logger => logger
|
26
|
+
end
|
27
|
+
|
28
|
+
# models
|
29
|
+
|
30
|
+
require 'active_record'
|
31
|
+
ActiveRecord::Base.logger = Blog.logger
|
32
|
+
dbpath = Pathname.new('tmp/blog.sqlite3')
|
33
|
+
FileUtils.mkdir_p(dbpath.dirname)
|
34
|
+
dbpath.unlink if dbpath.exist?
|
35
|
+
ActiveRecord::Base.establish_connection(
|
36
|
+
:adapter => "sqlite3",
|
37
|
+
:database => dbpath
|
38
|
+
)
|
39
|
+
|
40
|
+
ActiveRecord::Schema.define do
|
41
|
+
create_table :articles do |table|
|
42
|
+
table.column :title, :string
|
43
|
+
table.column :author_id, :integer
|
44
|
+
end
|
45
|
+
|
46
|
+
create_table :authors do |table|
|
47
|
+
table.column :name, :string
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# we will namespace the models under Blog so that the top-level namespace
|
52
|
+
# can be used by the scorpio model classes
|
53
|
+
class Blog
|
54
|
+
class Article < ActiveRecord::Base
|
55
|
+
# validates_enthusiasm_of :title
|
56
|
+
validate { errors.add(:title, "with gusto!") if title && !title[/!\z/] }
|
57
|
+
end
|
58
|
+
class Author < ActiveRecord::Base
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# controllers
|
63
|
+
|
64
|
+
class Blog
|
65
|
+
get '/v1/articles' do
|
66
|
+
check_accept
|
67
|
+
|
68
|
+
articles = Blog::Article.all
|
69
|
+
format_response(200, articles.map(&:serializable_hash))
|
70
|
+
end
|
71
|
+
get '/v1/articles_with_root' do
|
72
|
+
check_accept
|
73
|
+
|
74
|
+
articles = Blog::Article.all
|
75
|
+
body = {
|
76
|
+
# this is on the response schema, an array with items whose id indicates they are articles
|
77
|
+
'articles' => articles.map(&:serializable_hash),
|
78
|
+
# in the response schema, a single article
|
79
|
+
'best_article' => articles.last.serializable_hash,
|
80
|
+
# this is on the response schema, not indicating it is an article
|
81
|
+
'version' => 'v1',
|
82
|
+
# this is not in the response schema at all
|
83
|
+
'note' => 'hi!',
|
84
|
+
}
|
85
|
+
format_response(200, body)
|
86
|
+
end
|
87
|
+
get '/v1/articles/:id' do |id|
|
88
|
+
article = find_or_halt(Blog::Article, id: id)
|
89
|
+
format_response(200, article.serializable_hash)
|
90
|
+
end
|
91
|
+
post '/v1/articles' do
|
92
|
+
article = Blog::Article.create(parsed_body)
|
93
|
+
if article.persisted?
|
94
|
+
format_response(200, article.serializable_hash)
|
95
|
+
else
|
96
|
+
halt_unprocessable_entity(article.errors.messages)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
patch '/v1/articles/:id' do |id|
|
100
|
+
article_attrs = parsed_body
|
101
|
+
article = find_or_halt(Blog::Article, id: id)
|
102
|
+
|
103
|
+
article.assign_attributes(article_attrs)
|
104
|
+
saved = article.save
|
105
|
+
if saved
|
106
|
+
format_response(200, article.serializable_hash)
|
107
|
+
else
|
108
|
+
halt_unprocessable_entity(article.errors.messages)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
require 'database_cleaner'
|
113
|
+
post '/v1/clean' do
|
114
|
+
DatabaseCleaner.clean_with(:truncation)
|
115
|
+
format_response(200, nil)
|
116
|
+
end
|
117
|
+
end
|