grape-swagger 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.rspec +2 -0
- data/.rvmrc +1 -1
- data/.travis.yml +2 -0
- data/CHANGELOG.markdown +9 -0
- data/Gemfile +2 -1
- data/Gemfile.lock +24 -10
- data/README.markdown +58 -6
- data/VERSION +1 -1
- data/grape-swagger.gemspec +14 -7
- data/lib/grape-swagger.rb +235 -93
- data/spec/api_models_spec.rb +132 -0
- data/spec/default_api_spec.rb +80 -10
- data/spec/form_params_spec.rb +83 -0
- data/spec/grape-swagger_helper_spec.rb +107 -0
- data/spec/grape-swagger_spec.rb +0 -2
- data/spec/hide_api_spec.rb +106 -0
- data/spec/non_default_api_spec.rb +318 -78
- data/spec/simple_mounted_api_spec.rb +139 -19
- data/spec/spec_helper.rb +2 -10
- metadata +26 -6
- data/spec/grape-swagger-helper_spec.rb +0 -88
@@ -0,0 +1,132 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "API Models" do
|
4
|
+
|
5
|
+
before :all do
|
6
|
+
module Entities
|
7
|
+
class Something < Grape::Entity
|
8
|
+
expose :text, :documentation => { :type => "string", :desc => "Content of something." }
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module Entities
|
13
|
+
module Some
|
14
|
+
class Thing < Grape::Entity
|
15
|
+
expose :text, :documentation => { :type => "string", :desc => "Content of something." }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class ModelsApi < Grape::API
|
21
|
+
format :json
|
22
|
+
desc 'This gets something.', {
|
23
|
+
entity: Entities::Something
|
24
|
+
}
|
25
|
+
get '/something' do
|
26
|
+
something = OpenStruct.new text: 'something'
|
27
|
+
present something, with: Entities::Something
|
28
|
+
end
|
29
|
+
|
30
|
+
desc 'This gets thing.', {
|
31
|
+
entity: Entities::Some::Thing
|
32
|
+
}
|
33
|
+
get "/thing" do
|
34
|
+
thing = OpenStruct.new text: 'thing'
|
35
|
+
present thing, with: Entities::Some::Thing
|
36
|
+
end
|
37
|
+
add_swagger_documentation
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def app; ModelsApi; end
|
42
|
+
|
43
|
+
it "should document specified models" do
|
44
|
+
get '/swagger_doc'
|
45
|
+
JSON.parse(last_response.body).should == {
|
46
|
+
"apiVersion" => "0.1",
|
47
|
+
"swaggerVersion" => "1.2",
|
48
|
+
"basePath" => "http://example.org",
|
49
|
+
"info" => {},
|
50
|
+
"produces" => ["application/json"],
|
51
|
+
"operations" => [],
|
52
|
+
"apis" => [
|
53
|
+
{ "path" => "/something.{format}" },
|
54
|
+
{ "path" => "/thing.{format}" },
|
55
|
+
{ "path" => "/swagger_doc.{format}" }
|
56
|
+
]
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should include type when specified" do
|
61
|
+
get '/swagger_doc/something.json'
|
62
|
+
JSON.parse(last_response.body).should == {
|
63
|
+
"apiVersion" => "0.1",
|
64
|
+
"swaggerVersion" => "1.2",
|
65
|
+
"basePath" => "http://example.org",
|
66
|
+
"resourcePath" => "",
|
67
|
+
"apis" => [{
|
68
|
+
"path" => "/something.{format}",
|
69
|
+
"operations" => [{
|
70
|
+
"produces" => [
|
71
|
+
"application/json"
|
72
|
+
],
|
73
|
+
"notes" => "",
|
74
|
+
"type" => "Something",
|
75
|
+
"summary" => "This gets something.",
|
76
|
+
"nickname" => "GET-something---format-",
|
77
|
+
"httpMethod" => "GET",
|
78
|
+
"parameters" => []
|
79
|
+
}]
|
80
|
+
}],
|
81
|
+
"models" => {
|
82
|
+
"Something" => {
|
83
|
+
"id" => "Something",
|
84
|
+
"name" => "Something",
|
85
|
+
"properties" => {
|
86
|
+
"text" => {
|
87
|
+
"type" => "string",
|
88
|
+
"description" => "Content of something."
|
89
|
+
}
|
90
|
+
}
|
91
|
+
}
|
92
|
+
}
|
93
|
+
}
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should include nested type when specified" do
|
97
|
+
get '/swagger_doc/thing.json'
|
98
|
+
JSON.parse(last_response.body).should == {
|
99
|
+
"apiVersion" => "0.1",
|
100
|
+
"swaggerVersion" => "1.2",
|
101
|
+
"basePath" => "http://example.org",
|
102
|
+
"resourcePath" => "",
|
103
|
+
"apis" => [{
|
104
|
+
"path" => "/thing.{format}",
|
105
|
+
"operations" => [{
|
106
|
+
"produces" => [
|
107
|
+
"application/json"
|
108
|
+
],
|
109
|
+
"notes" => "",
|
110
|
+
"type" => "Some::Thing",
|
111
|
+
"summary" => "This gets thing.",
|
112
|
+
"nickname" => "GET-thing---format-",
|
113
|
+
"httpMethod" => "GET",
|
114
|
+
"parameters" => []
|
115
|
+
}]
|
116
|
+
}],
|
117
|
+
"models" => {
|
118
|
+
"Some::Thing" => {
|
119
|
+
"id" => "Some::Thing",
|
120
|
+
"name" => "Some::Thing",
|
121
|
+
"properties" => {
|
122
|
+
"text" => {
|
123
|
+
"type" => "string",
|
124
|
+
"description" => "Content of something."
|
125
|
+
}
|
126
|
+
}
|
127
|
+
}
|
128
|
+
}
|
129
|
+
}
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
data/spec/default_api_spec.rb
CHANGED
@@ -2,21 +2,91 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe "Default API" do
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
context 'with no additional options' do
|
6
|
+
before :all do
|
7
|
+
class NotAMountedApi < Grape::API
|
8
|
+
format :json
|
9
|
+
desc 'This gets something.'
|
10
|
+
get '/something' do
|
11
|
+
{ bla: 'something' }
|
12
|
+
end
|
13
|
+
add_swagger_documentation
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def app; NotAMountedApi; end
|
18
|
+
|
19
|
+
it "should document something" do
|
20
|
+
get '/swagger_doc'
|
21
|
+
JSON.parse(last_response.body).should == {
|
22
|
+
"apiVersion" => "0.1",
|
23
|
+
"swaggerVersion" => "1.2",
|
24
|
+
"basePath" => "http://example.org",
|
25
|
+
"info" => {},
|
26
|
+
"produces" => ["application/json"],
|
27
|
+
"operations" => [],
|
28
|
+
"apis" => [
|
29
|
+
{ "path" => "/something.{format}" },
|
30
|
+
{ "path" => "/swagger_doc.{format}" }
|
31
|
+
]
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
context "path inside the apis array" do
|
36
|
+
it "should start with a forward slash" do
|
37
|
+
get '/swagger_doc'
|
38
|
+
JSON.parse(last_response.body)['apis'].each do |api|
|
39
|
+
api['path'].should start_with "/"
|
40
|
+
end
|
10
41
|
end
|
11
|
-
add_swagger_documentation
|
12
42
|
end
|
13
43
|
end
|
14
44
|
|
15
|
-
|
45
|
+
context 'with API info' do
|
46
|
+
before :all do
|
47
|
+
class ApiInfoTest < Grape::API
|
48
|
+
format :json
|
49
|
+
add_swagger_documentation info: {
|
50
|
+
title: 'My API Title',
|
51
|
+
description: 'A description of my API',
|
52
|
+
license: 'Apache 2',
|
53
|
+
license_url: 'http://test.com',
|
54
|
+
terms_of_service_url: 'http://terms.com',
|
55
|
+
contact: 'support@test.com'
|
56
|
+
}
|
57
|
+
end
|
58
|
+
get '/swagger_doc'
|
59
|
+
end
|
60
|
+
|
61
|
+
def app; ApiInfoTest; end
|
16
62
|
|
17
|
-
|
18
|
-
|
19
|
-
|
63
|
+
subject do
|
64
|
+
JSON.parse(last_response.body)['info']
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'should document API title' do
|
68
|
+
expect(subject['title']).to eql('My API Title')
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'should document API description' do
|
72
|
+
expect(subject['description']).to eql('A description of my API')
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'should document the license' do
|
76
|
+
expect(subject['license']).to eql('Apache 2')
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'should document the license url' do
|
80
|
+
expect(subject['licenseUrl']).to eql('http://test.com')
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'should document the terms of service url' do
|
84
|
+
expect(subject['termsOfServiceUrl']).to eql('http://terms.com')
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'should document the contact email' do
|
88
|
+
expect(subject['contact']).to eql('support@test.com')
|
89
|
+
end
|
20
90
|
end
|
21
91
|
|
22
92
|
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Form Params" do
|
4
|
+
|
5
|
+
before :all do
|
6
|
+
class FormParamApi < Grape::API
|
7
|
+
format :json
|
8
|
+
|
9
|
+
params do
|
10
|
+
requires :name, type: String, desc: "name of item"
|
11
|
+
end
|
12
|
+
post '/items' do
|
13
|
+
{}
|
14
|
+
end
|
15
|
+
|
16
|
+
params do
|
17
|
+
requires :id, type: Integer, desc: "id of item"
|
18
|
+
requires :name, type: String, desc: "name of item"
|
19
|
+
end
|
20
|
+
put '/items/:id' do
|
21
|
+
{}
|
22
|
+
end
|
23
|
+
|
24
|
+
params do
|
25
|
+
requires :id, type: Integer, desc: "id of item"
|
26
|
+
requires :name, type: String, desc: "name of item"
|
27
|
+
end
|
28
|
+
patch '/items/:id' do
|
29
|
+
{}
|
30
|
+
end
|
31
|
+
|
32
|
+
add_swagger_documentation
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def app; FormParamApi; end
|
37
|
+
|
38
|
+
it "retrieves the documentation form params" do
|
39
|
+
get '/swagger_doc/items.json'
|
40
|
+
|
41
|
+
JSON.parse(last_response.body).should == {
|
42
|
+
"apiVersion" => "0.1",
|
43
|
+
"swaggerVersion" => "1.2",
|
44
|
+
"resourcePath" => "",
|
45
|
+
"basePath"=>"http://example.org",
|
46
|
+
"apis" => [
|
47
|
+
{
|
48
|
+
"path" => "/items.{format}",
|
49
|
+
"operations" => [
|
50
|
+
{
|
51
|
+
"produces" => ["application/json"],
|
52
|
+
"notes" => "",
|
53
|
+
"summary" => "",
|
54
|
+
"nickname" => "POST-items---format-",
|
55
|
+
"httpMethod" => "POST",
|
56
|
+
"parameters" => [ { "paramType" => "form", "name" => "name", "description" => "name of item", "type" => "String", "dataType" => "String", "required" => true } ]
|
57
|
+
}
|
58
|
+
]
|
59
|
+
}, {
|
60
|
+
"path" => "/items/{id}.{format}",
|
61
|
+
"operations" => [
|
62
|
+
{
|
63
|
+
"produces" => ["application/json"],
|
64
|
+
"notes" => "",
|
65
|
+
"summary" => "",
|
66
|
+
"nickname" => "PUT-items--id---format-",
|
67
|
+
"httpMethod" => "PUT",
|
68
|
+
"parameters" => [ { "paramType" => "path", "name" => "id", "description" => "id of item", "type" => "Integer", "dataType" => "Integer", "required" => true }, { "paramType" => "form", "name" => "name", "description" => "name of item", "type" => "String", "dataType" => "String", "required" => true } ]
|
69
|
+
},
|
70
|
+
{
|
71
|
+
"produces" => ["application/json"],
|
72
|
+
"notes" => "",
|
73
|
+
"summary" => "",
|
74
|
+
"nickname" => "PATCH-items--id---format-",
|
75
|
+
"httpMethod" => "PATCH",
|
76
|
+
"parameters" => [ { "paramType" => "path", "name" => "id", "description" => "id of item", "type" => "Integer", "dataType" => "Integer", "required" => true }, { "paramType" => "form", "name" => "name", "description" => "name of item", "type" => "String", "dataType" => "String", "required" => true } ]
|
77
|
+
}
|
78
|
+
]
|
79
|
+
}
|
80
|
+
]
|
81
|
+
}
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "helpers" do
|
4
|
+
|
5
|
+
before :all do
|
6
|
+
class HelperTestAPI < Grape::API
|
7
|
+
add_swagger_documentation
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
before :each do
|
12
|
+
@api = Object.new
|
13
|
+
|
14
|
+
# after injecting grape-swagger into the Test API we get the helper methods
|
15
|
+
# back from the first endpoint's class (the API mounted by grape-swagger
|
16
|
+
# to serve the swagger_doc
|
17
|
+
|
18
|
+
@api.extend HelperTestAPI.endpoints.first.options[:app].helpers
|
19
|
+
end
|
20
|
+
|
21
|
+
context "parsing parameters" do
|
22
|
+
it "parses params as query strings for a GET" do
|
23
|
+
params = {
|
24
|
+
name: { type: 'String', desc: "A name", required: true, defaultValue: 'default' },
|
25
|
+
level: 'max'
|
26
|
+
}
|
27
|
+
path = "/coolness"
|
28
|
+
method = "GET"
|
29
|
+
@api.parse_params(params, path, method).should == [
|
30
|
+
{ paramType: "query", name: :name, description: "A name", type: "String", dataType: "String", required: true, defaultValue: 'default' },
|
31
|
+
{ paramType: "query", name: :level, description: "", type: "String", dataType: "String", required: false }
|
32
|
+
]
|
33
|
+
end
|
34
|
+
|
35
|
+
it "parses params as form for a POST" do
|
36
|
+
params = {
|
37
|
+
name: { type: 'String', :desc => "A name", required: true },
|
38
|
+
level: 'max'
|
39
|
+
}
|
40
|
+
path = "/coolness"
|
41
|
+
method = "POST"
|
42
|
+
@api.parse_params(params, path, method).should == [
|
43
|
+
{ paramType: "form", name: :name, description: "A name", type: "String", dataType: "String", required: true },
|
44
|
+
{ paramType: "form", name: :level, description: "", type: "String", dataType: "String", required: false }
|
45
|
+
]
|
46
|
+
end
|
47
|
+
|
48
|
+
context "custom type" do
|
49
|
+
before :all do
|
50
|
+
class CustomType
|
51
|
+
end
|
52
|
+
end
|
53
|
+
it "parses a custom parameters" do
|
54
|
+
params = {
|
55
|
+
option: { type: CustomType, desc: "Custom option" }
|
56
|
+
}
|
57
|
+
path = "/coolness"
|
58
|
+
method = "GET"
|
59
|
+
@api.parse_params(params, path, method).should == [
|
60
|
+
{ paramType: "query", name: :option, description: "Custom option", type: "CustomType", dataType: "CustomType", required: false }
|
61
|
+
]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
context "parsing the path" do
|
68
|
+
it "parses the path" do
|
69
|
+
path = ":abc/def(.:format)"
|
70
|
+
@api.parse_path(path, nil).should == "{abc}/def.{format}"
|
71
|
+
end
|
72
|
+
|
73
|
+
it "parses a path that has vars with underscores in the name" do
|
74
|
+
path = "abc/:def_g(.:format)"
|
75
|
+
@api.parse_path(path, nil).should == "abc/{def_g}.{format}"
|
76
|
+
end
|
77
|
+
|
78
|
+
it "parses a path that has vars with numbers in the name" do
|
79
|
+
path = "abc/:sha1(.:format)"
|
80
|
+
@api.parse_path(path, nil).should == "abc/{sha1}.{format}"
|
81
|
+
end
|
82
|
+
|
83
|
+
it "parses a path that has multiple variables" do
|
84
|
+
path1 = "abc/:def/:geh(.:format)"
|
85
|
+
path2 = "abc/:def:geh(.:format)"
|
86
|
+
@api.parse_path(path1, nil).should == "abc/{def}/{geh}.{format}"
|
87
|
+
@api.parse_path(path2, nil).should == "abc/{def}{geh}.{format}"
|
88
|
+
end
|
89
|
+
|
90
|
+
it "parses the path with a specified version" do
|
91
|
+
path = ":abc/{version}/def(.:format)"
|
92
|
+
@api.parse_path(path, 'v1').should == "{abc}/v1/def.{format}"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
context "parsing header parameters" do
|
97
|
+
it "parses params for the header" do
|
98
|
+
params = {
|
99
|
+
"XAuthToken" => { description: "A required header.", required: true, defaultValue: 'default' }
|
100
|
+
}
|
101
|
+
@api.parse_header_params(params).should == [
|
102
|
+
{ paramType: "header", name: "XAuthToken", description: "A required header.", type: "String", dataType: "String", required: true, defaultValue: 'default' }
|
103
|
+
]
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
data/spec/grape-swagger_spec.rb
CHANGED
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "a hide mounted api" do
|
4
|
+
before :all do
|
5
|
+
class HideMountedApi < Grape::API
|
6
|
+
desc 'Show this endpoint'
|
7
|
+
get '/simple' do
|
8
|
+
{ :foo => 'bar' }
|
9
|
+
end
|
10
|
+
|
11
|
+
desc 'Hide this endpoint', {
|
12
|
+
:hidden => true
|
13
|
+
}
|
14
|
+
get '/hide' do
|
15
|
+
{ :foo => 'bar' }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class HideApi < Grape::API
|
20
|
+
mount HideMountedApi
|
21
|
+
add_swagger_documentation
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def app; HideApi end
|
26
|
+
|
27
|
+
it "retrieves swagger-documentation that doesn't include hidden endpoints" do
|
28
|
+
get '/swagger_doc.json'
|
29
|
+
JSON.parse(last_response.body).should == {
|
30
|
+
"apiVersion" => "0.1",
|
31
|
+
"swaggerVersion" => "1.2",
|
32
|
+
"basePath" => "http://example.org",
|
33
|
+
"info" => {},
|
34
|
+
"produces" => ["application/xml", "application/json", "text/plain"],
|
35
|
+
"operations" => [],
|
36
|
+
"apis" => [
|
37
|
+
{ "path" => "/simple.{format}" },
|
38
|
+
{ "path" => "/swagger_doc.{format}" }
|
39
|
+
]
|
40
|
+
}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
describe "a hide mounted api with same namespace" do
|
46
|
+
before :all do
|
47
|
+
class HideNamespaceMountedApi < Grape::API
|
48
|
+
desc 'Show this endpoint'
|
49
|
+
get '/simple/show' do
|
50
|
+
{ :foo => 'bar' }
|
51
|
+
end
|
52
|
+
|
53
|
+
desc 'Hide this endpoint', {
|
54
|
+
:hidden => true
|
55
|
+
}
|
56
|
+
get '/simple/hide' do
|
57
|
+
{ :foo => 'bar' }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class HideNamespaceApi < Grape::API
|
62
|
+
mount HideNamespaceMountedApi
|
63
|
+
add_swagger_documentation
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def app; HideNamespaceApi end
|
68
|
+
|
69
|
+
it "retrieves swagger-documentation on /swagger_doc" do
|
70
|
+
get '/swagger_doc.json'
|
71
|
+
JSON.parse(last_response.body).should == {
|
72
|
+
"apiVersion" => "0.1",
|
73
|
+
"swaggerVersion" => "1.2",
|
74
|
+
"basePath" => "http://example.org",
|
75
|
+
"info" => {},
|
76
|
+
"produces" => ["application/xml", "application/json", "text/plain"],
|
77
|
+
"operations" => [],
|
78
|
+
"apis" => [
|
79
|
+
{ "path" => "/simple.{format}" },
|
80
|
+
{ "path" => "/swagger_doc.{format}" }
|
81
|
+
]
|
82
|
+
}
|
83
|
+
end
|
84
|
+
|
85
|
+
it "retrieves the documentation for mounted-api that doesn't include hidden endpoints" do
|
86
|
+
get '/swagger_doc/simple.json'
|
87
|
+
JSON.parse(last_response.body).should == {
|
88
|
+
"apiVersion" => "0.1",
|
89
|
+
"swaggerVersion" => "1.2",
|
90
|
+
"basePath" => "http://example.org",
|
91
|
+
"resourcePath" => "",
|
92
|
+
"apis" => [{
|
93
|
+
"path" => "/simple/show.{format}",
|
94
|
+
"operations" => [{
|
95
|
+
"produces" => ["application/xml", "application/json", "text/plain"],
|
96
|
+
"notes" => nil,
|
97
|
+
"notes" => "",
|
98
|
+
"summary" => "Show this endpoint",
|
99
|
+
"nickname" => "GET-simple-show---format-",
|
100
|
+
"httpMethod" => "GET",
|
101
|
+
"parameters" => []
|
102
|
+
}]
|
103
|
+
}]
|
104
|
+
}
|
105
|
+
end
|
106
|
+
end
|