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.
@@ -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
- model = models_by_schema[object.schema]
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
@@ -1,5 +1,7 @@
1
1
  module Scorpio
2
- class Response < ::Ur::Response
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)
@@ -1,5 +1,16 @@
1
1
  module Scorpio
2
- class Ur < ::Ur
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
@@ -1,3 +1,3 @@
1
1
  module Scorpio
2
- VERSION = "0.4.6"
2
+ VERSION = "0.5.0"
3
3
  end
@@ -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.3.0"
24
- spec.add_dependency "ur", "~> 0.1.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", "~> 10.0"
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
@@ -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