sinatra-swagger-exposer 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b55ef98d692d8d3d3b6d27a92b7a30657d5729bb
4
+ data.tar.gz: a34f58471cf590358e549b51ac348829b269b1ae
5
+ SHA512:
6
+ metadata.gz: fe4a2405064efbde281a34810dc78680a3a4ba484cc281f3045ef12211cf8470baadb171db8bb6fdfd53faa11bbb178a3cd3d60a3deb21283bcac17cff3af910
7
+ data.tar.gz: 86f6a2d376446ed4a59e5d1049955f1cacc2eb0334895d116ab42fcdd891618863a30b5264ec81983d7ff3c453ad4e446dc9de4bb3c5bdb8cef4773c39d7eaca
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ sinatra-swagger-exposer-*.gem
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.0
4
+ addons:
5
+ code_climate:
6
+ repo_token: 9327b6a5a4de354ce0da555d4ae4ed262ee0fa2e4d210d8fb82511eee5a2dc2f
@@ -0,0 +1,13 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
4
+
5
+ We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion.
6
+
7
+ Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
8
+
9
+ Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
10
+
11
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
12
+
13
+ This Code of Conduct is adapted from the [Contributor Covenant](http:contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in sinatra-swagger-exposer.gemspec
4
+ gemspec
5
+
6
+ gem 'codeclimate-test-reporter', group: :test, require: nil
7
+ gem 'coveralls', require: false
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Julien Kirch
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.asciidoc ADDED
@@ -0,0 +1,75 @@
1
+ # Sinatra::SwaggerExposer
2
+
3
+ image:https://codeclimate.com/github/archiloque/sinatra-swagger-exposer/badges/gpa.svg["Code status", link=https://codeclimate.com/github/archiloque/sinatra-swagger-exposer]
4
+ image:https://travis-ci.org/archiloque/sinatra-swagger-exposer.svg?branch=master["Build Status", link="https://travis-ci.org/archiloque/sinatra-swagger-exposer"]
5
+ image:https://coveralls.io/repos/archiloque/sinatra-swagger-exposer/badge.svg?branch=master["Coverage Status", link="https://coveralls.io/r/archiloque/sinatra-swagger-exposer?branch=master"]
6
+
7
+ Create Swagger endpoint for your Sinatra application.
8
+
9
+ This Sinatra extension enable you to add metadata to your code and to expose your API as a Swagger endpoint.
10
+
11
+ I'm adding features as I need them and it currently doesn't use all the Swagger options, so if you need one that is missing please open an issue or contribute.
12
+
13
+ ## Design choices
14
+
15
+ - All the declarations are validated when the server is started
16
+ - The declarations are defined to look as ruby-ish as possible
17
+
18
+ To use it in your app :
19
+
20
+ [source,ruby]
21
+ ----
22
+ require 'sinatra/swagger-exposer/swagger-exposer'
23
+
24
+ class MyApp < Sinatra::Base
25
+
26
+ register Sinatra::SwaggerExposer
27
+
28
+ general_info(
29
+ {
30
+ version: '0.0.1',
31
+ title: 'May app',
32
+ description: 'My wonderful app',
33
+ license: {
34
+ name: 'MIT',
35
+ url: 'http://opensource.org/licenses/MIT'
36
+ }
37
+ }
38
+ )
39
+
40
+ type 'Status',
41
+ {
42
+ :properties => {
43
+ :status => {
44
+ :type => String,
45
+ :example => 'OK,
46
+ },
47
+ },
48
+ :required => [:status]
49
+ }
50
+
51
+ endpoint_description 'Base method to ping'
52
+ endpoint_response 200, 'Standard response', 'Status'
53
+ endpoint_tags 'Ping'
54
+ get '/' do
55
+ json({'status' => 'OK'})
56
+ end
57
+
58
+ end
59
+ ----
60
+
61
+ The swagger json endpoint will be exposed at `/swagger_doc.json`.
62
+
63
+ ## Detailed example
64
+
65
+ A more complete example is available link:https://github.com/archiloque/sinatra-swagger-exposer/tree/master/example[here].
66
+
67
+ ## Resources
68
+
69
+ - link:https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md[Swagger RESTful API Documentation Specification].
70
+ - link:https://github.com/swagger-api/swagger-spec/tree/master/examples/v2.0/json[Swagger json examples].
71
+ - link:https://raw.githubusercontent.com/swagger-api/swagger-spec/master/schemas/v2.0/schema.json[The swagger json schema].
72
+
73
+ ## License
74
+
75
+ This software is released under the MIT license.
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'test'
6
+ t.pattern = 'test/**/test-*.rb'
7
+ end
8
+
9
+ task :default => :test
data/example/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'sinatra', '~> 1.4.5'
4
+ gem 'sinatra-cross_origin', '~> 0.3.2'
@@ -0,0 +1,19 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ rack (1.6.0)
5
+ rack-protection (1.5.3)
6
+ rack
7
+ sinatra (1.4.6)
8
+ rack (~> 1.4)
9
+ rack-protection (~> 1.4)
10
+ tilt (>= 1.3, < 3)
11
+ sinatra-cross_origin (0.3.2)
12
+ tilt (2.0.1)
13
+
14
+ PLATFORMS
15
+ ruby
16
+
17
+ DEPENDENCIES
18
+ sinatra (~> 1.4.5)
19
+ sinatra-cross_origin (~> 0.3.2)
data/example/config.ru ADDED
@@ -0,0 +1,2 @@
1
+ require_relative 'petstore'
2
+ run Petstore
@@ -0,0 +1,102 @@
1
+ require 'sinatra/base'
2
+ require 'json'
3
+ require 'sinatra/cross_origin'
4
+
5
+ require_relative '../lib/sinatra/swagger-exposer/swagger-exposer'
6
+
7
+ class Petstore < Sinatra::Base
8
+
9
+ set :logging, true
10
+
11
+ register Sinatra::CrossOrigin
12
+ set :allow_origin, :any
13
+ enable :cross_origin
14
+
15
+ register Sinatra::SwaggerExposer
16
+
17
+ general_info(
18
+ {
19
+ :version => '1.0.0',
20
+ :title => 'Swagger Petstore',
21
+ :description => 'A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification',
22
+ :termsOfService => 'http://swagger.io/terms/',
23
+ :contact => {:name => 'Swagger API Team',
24
+ :email => 'apiteam@swagger.io',
25
+ :url => 'http://swagger.io'
26
+ },
27
+ :license => {
28
+ :name => 'Apache 2.0',
29
+ :url => 'http://github.com/gruntjs/grunt/blob/master/LICENSE-MIT'
30
+ }
31
+ }
32
+ )
33
+
34
+ type 'Error', {
35
+ :required => [:code, :message],
36
+ :properties => {
37
+ :code => {
38
+ :type => Integer,
39
+ :example => 404,
40
+ :description => 'The error code',
41
+ },
42
+ :message => {
43
+ :type => String,
44
+ :example => 'Pet not found',
45
+ :description => 'The error message',
46
+ },
47
+ },
48
+ }
49
+
50
+
51
+ type 'Pet', {
52
+ :required => [:id, :name],
53
+ :properties => {
54
+ :id => {
55
+ :type => Integer,
56
+ :format => 'int64',
57
+ },
58
+ :name => {
59
+ :type => String,
60
+ :example => 'doggie',
61
+ :description => 'The pet name',
62
+ },
63
+ :photoUrls => {
64
+ :type => [String],
65
+ },
66
+ :tags => {
67
+ :type => [String],
68
+ :description => 'The pet\'s tags',
69
+ },
70
+ :status => {
71
+ :type => String,
72
+ :description => 'pet status in the store',
73
+ :example => 'sleepy',
74
+ },
75
+ },
76
+ }
77
+
78
+ endpoint_summary 'Finds all the pets'
79
+ endpoint_description 'Returns all pets from the system that the user has access to'
80
+ endpoint_tags 'Pets'
81
+ endpoint_response 200, ['Pet'], 'Standard response'
82
+ get '/pets' do
83
+ content_type :json
84
+ [].to_json
85
+ end
86
+
87
+ endpoint_summary 'Finds a pet by its id'
88
+ endpoint_description 'Finds a pet by its id, or 404 if the user does not have access to the pet'
89
+ endpoint_tags 'Pets'
90
+ endpoint_response 200, 'Pet', 'Standard response'
91
+ endpoint_response 404, 'Error', 'Pet not found'
92
+ endpoint_parameter :id, 'The pet id', :path, true, String
93
+ {
94
+ :example => 'AMZ',
95
+ }
96
+ get '/pets/:id' do
97
+ content_type :json
98
+ [404, {:code => 404, :message => 'Pet not found'}]
99
+ end
100
+
101
+ end
102
+
@@ -0,0 +1,58 @@
1
+ require_relative 'swagger-utilities'
2
+
3
+ module Sinatra
4
+
5
+ module SwaggerExposer
6
+
7
+ # Create the swagger content
8
+ class SwaggerContentCreator
9
+
10
+ include SwaggerUtilities
11
+
12
+ def initialize(swagger_info, swagger_types, swagger_endpoints)
13
+ @swagger_info = swagger_info
14
+ @swagger_types = swagger_types
15
+ @swagger_endpoints = swagger_endpoints
16
+ end
17
+
18
+ def to_swagger
19
+ result = {
20
+ swagger: '2.0',
21
+ consumes: ['application/json'],
22
+ produces: ['application/json'],
23
+ }
24
+ if @swagger_info
25
+ result[:info] = @swagger_info.to_swagger
26
+ end
27
+
28
+ unless @swagger_types.empty?
29
+ result[:definitions] = hash_to_swagger(@swagger_types)
30
+ end
31
+
32
+ unless @swagger_endpoints.empty?
33
+ result_endpoints = {}
34
+
35
+ # swagger need the endpoints to be grouped by path
36
+ endpoints_by_path = @swagger_endpoints.group_by { |endpoint| endpoint.path }
37
+ endpoints_by_path.keys.sort.each do |path|
38
+ endpoints = endpoints_by_path[path]
39
+
40
+ result_endpoints_for_path = {}
41
+ endpoints.each do |endpoint|
42
+ result_endpoints_for_path[endpoint.type] = endpoint.to_swagger
43
+ end
44
+
45
+ result_endpoints[path] = result_endpoints_for_path
46
+
47
+ end
48
+ result[:paths] = result_endpoints
49
+ end
50
+
51
+ result
52
+ end
53
+
54
+ end
55
+
56
+ end
57
+
58
+ end
@@ -0,0 +1,105 @@
1
+ require_relative 'swagger-invalid-exception'
2
+ require_relative 'swagger-utilities'
3
+
4
+ module Sinatra
5
+
6
+ module SwaggerExposer
7
+
8
+ class SwaggerEndpointParameter
9
+
10
+ include SwaggerUtilities
11
+
12
+ HOW_TO_PASS_BODY = 'body'
13
+ HOW_TO_PASS= ['path', 'query', 'header', 'formData'] + [HOW_TO_PASS_BODY]
14
+ PRIMITIVE_TYPES_FOR_NON_BODY = ['string', 'number', 'integer', 'boolean']
15
+
16
+ def initialize(name, description, how_to_pass, required, type, params, known_types)
17
+ unless name.is_a?(String) || name.is_a?(Symbol)
18
+ raise SwaggerInvalidException.new("Name [#{name}] should be a string or a symbol")
19
+ end
20
+ name = name.to_s
21
+ if name.empty?
22
+ raise SwaggerInvalidException.new('Name should not be empty')
23
+ end
24
+ @name = name
25
+
26
+ if description
27
+ @description = description
28
+ end
29
+
30
+ how_to_pass = how_to_pass.to_s
31
+ unless HOW_TO_PASS.include? how_to_pass
32
+ raise SwaggerInvalidException.new("Unknown how to pass value [#{how_to_pass}], registered types are #{HOW_TO_PASS.join(', ')}")
33
+ end
34
+ @how_to_pass = how_to_pass
35
+
36
+ if @how_to_pass == HOW_TO_PASS_BODY
37
+ get_type(type, PRIMITIVE_TYPES + known_types)
38
+ else
39
+ get_type(type, PRIMITIVE_TYPES_FOR_NON_BODY)
40
+ end
41
+
42
+ unless [true, false].include? required
43
+ raise SwaggerInvalidException.new("Required should be a boolean instead of [#{required}]")
44
+ end
45
+ @required = required
46
+
47
+ if params
48
+ white_list_params(params, [:format])
49
+ end
50
+ @params = params
51
+
52
+ end
53
+
54
+ def to_swagger
55
+ result = {
56
+ :name => @name,
57
+ :in => @how_to_pass,
58
+ :required => @required
59
+ }
60
+
61
+ if @type
62
+ if @type == 'array'
63
+ result[:type] = 'array'
64
+ if @items
65
+ if PRIMITIVE_TYPES.include? @items
66
+ result[:items] = {:type => @items}
67
+ else
68
+ result[:schema] = {'$ref' => "#/definitions/#{@items}"}
69
+ end
70
+ end
71
+ else
72
+ if PRIMITIVE_TYPES.include? @type
73
+ result[:type] = @type
74
+ else
75
+ result[:schema] = {'$ref' => "#/definitions/#{@type}"}
76
+ end
77
+ end
78
+ end
79
+
80
+ if @description
81
+ result[:description] = @description
82
+ end
83
+ if @params
84
+ result.merge!(@params)
85
+ end
86
+
87
+ result
88
+ end
89
+
90
+ def to_s
91
+ {
92
+ :name => @name,
93
+ :in => @how_to_pass,
94
+ :required => @required,
95
+ :type => @type,
96
+ :items => @items,
97
+ :params => @params,
98
+ :description => @description,
99
+ }.to_json
100
+ end
101
+
102
+ end
103
+
104
+ end
105
+ end
@@ -0,0 +1,61 @@
1
+ require_relative 'swagger-utilities'
2
+ require_relative 'swagger-invalid-exception'
3
+
4
+ module Sinatra
5
+
6
+ module SwaggerExposer
7
+
8
+ class SwaggerEndpointResponse
9
+
10
+ include SwaggerUtilities
11
+
12
+ def initialize(type, description, known_types)
13
+ get_type(type, known_types + PRIMITIVE_TYPES)
14
+ if description
15
+ @description = description
16
+ end
17
+ end
18
+
19
+ def to_swagger
20
+ result = {}
21
+
22
+ if @type
23
+ if @type == 'array'
24
+ schema = {:type => 'array'}
25
+ if @items
26
+ if PRIMITIVE_TYPES.include? @items
27
+ schema[:items] = {:type => @items}
28
+ else
29
+ schema[:items] = {'$ref' => "#/definitions/#{@items}"}
30
+ end
31
+ end
32
+ result[:schema] = schema
33
+ else
34
+ if PRIMITIVE_TYPES.include? @type
35
+ result[:schema] = {:type => @type}
36
+ else
37
+ result[:schema] = {'$ref' => "#/definitions/#{@type}"}
38
+ end
39
+ end
40
+ end
41
+
42
+ if @description
43
+ result[:description] = @description
44
+ end
45
+
46
+ result
47
+ end
48
+
49
+ def to_s
50
+ {
51
+ :type => @type,
52
+ :items => @items,
53
+ :description => @description,
54
+ }.to_json
55
+ end
56
+
57
+
58
+ end
59
+
60
+ end
61
+ end
@@ -0,0 +1,75 @@
1
+ require_relative 'swagger-utilities'
2
+
3
+ module Sinatra
4
+
5
+ module SwaggerExposer
6
+
7
+ # An endpoint
8
+ class SwaggerEndpoint
9
+
10
+ include SwaggerUtilities
11
+
12
+ attr_reader :path, :type
13
+
14
+ def initialize(type, path, parameters, responses, summary, description, tags)
15
+ @type = type
16
+ @path = sinatra_path_to_swagger_path(path)
17
+
18
+ @parameters = parameters
19
+ @responses = responses
20
+
21
+ @attributes = {}
22
+ if summary
23
+ @attributes[:summary] = summary
24
+ end
25
+ if description
26
+ @attributes[:description] = description
27
+ end
28
+ if tags
29
+ @attributes[:tags] = tags
30
+ end
31
+ end
32
+
33
+ def to_swagger
34
+ result = {
35
+ produces: ['application/json'],
36
+ }.merge(@attributes)
37
+
38
+ unless @parameters.empty?
39
+ result[:parameters] = @parameters.collect { |parameter| parameter.to_swagger }
40
+ end
41
+
42
+ unless @responses.empty?
43
+ result[:responses] = hash_to_swagger(@responses)
44
+ end
45
+
46
+ result
47
+ end
48
+
49
+ REGEX_PATH_PARAM_MIDDLE = /\A(.*\/)\:([a-z]+)\/(.+)\z/
50
+ REGEX_PATH_PARAM_END = /\A(.*)\/:([a-z]+)\z/
51
+
52
+ def sinatra_path_to_swagger_path(path)
53
+ while (m = REGEX_PATH_PARAM_MIDDLE.match(path))
54
+ path = "#{m[1]}{#{m[2]}}/#{m[3]}"
55
+ end
56
+ if (m = REGEX_PATH_PARAM_END.match(path))
57
+ path = "#{m[1]}/{#{m[2]}}"
58
+ end
59
+ path
60
+ end
61
+
62
+ def to_s
63
+ {
64
+ :type => @type,
65
+ :path => @path,
66
+ :attributes => @attributes,
67
+ :parameters => @parameters,
68
+ :responses => @responses,
69
+ }.to_json
70
+ end
71
+
72
+ end
73
+
74
+ end
75
+ end
@@ -0,0 +1,182 @@
1
+ require 'sinatra/base'
2
+ require 'json'
3
+
4
+ require_relative 'swagger-endpoint'
5
+ require_relative 'swagger-endpoint-parameter'
6
+ require_relative 'swagger-endpoint-response'
7
+ require_relative 'swagger-info'
8
+ require_relative 'swagger-invalid-exception'
9
+ require_relative 'swagger-type'
10
+
11
+ require_relative 'swagger-content-creator'
12
+
13
+ module Sinatra
14
+
15
+ # Expose swagger API from your Sinatra app
16
+ module SwaggerExposer
17
+
18
+ def self.registered(app)
19
+ app.set :swagger_endpoints, []
20
+ app.set :swagger_current_endpoint_info, {}
21
+ app.set :swagger_current_endpoint_parameters, {}
22
+ app.set :swagger_current_endpoint_responses, {}
23
+ app.set :swagger_types, {}
24
+
25
+ # Declare the swagger endpoints
26
+ app.endpoint_summary 'The swagger endpoint'
27
+ app.endpoint_tags 'swagger'
28
+ app.get '/swagger_doc.json' do
29
+ swagger_content = ::Sinatra::SwaggerExposer::SwaggerContentCreator.new(
30
+ settings.respond_to?(:swagger_info) ? settings.swagger_info : nil ,
31
+ settings.swagger_types,
32
+ settings.swagger_endpoints
33
+ ).to_swagger
34
+ content_type :json
35
+ swagger_content.to_json
36
+ end
37
+
38
+ app.endpoint_summary 'Option method for the swagger endpoint, useful for some CORS stuff'
39
+ app.endpoint_tags 'swagger'
40
+ app.options '/swagger_doc.json' do
41
+ 200
42
+ end
43
+
44
+ end
45
+
46
+ # Provide a summary for the endpoint
47
+ def endpoint_summary(summary)
48
+ set_if_type_and_not_exist(summary, 'summary', String)
49
+ end
50
+
51
+ # Provide a description for the endpoint
52
+ def endpoint_description(description)
53
+ set_if_type_and_not_exist(description, 'description', String)
54
+ end
55
+
56
+ # Provide tags for the endpoint
57
+ def endpoint_tags(*tags)
58
+ set_if_type_and_not_exist(tags, 'tags', nil)
59
+ end
60
+
61
+ # Define parameter for the endpoint
62
+ def endpoint_parameter(name, description, how_to_pass, required, type, params = nil)
63
+ parameters = settings.swagger_current_endpoint_parameters
64
+ check_if_not_duplicate(name, parameters, 'Parameter')
65
+ parameters[name] = SwaggerEndpointParameter.new(
66
+ name,
67
+ description,
68
+ how_to_pass,
69
+ required,
70
+ type,
71
+ params,
72
+ settings.swagger_types.keys)
73
+ end
74
+
75
+ # General information
76
+ def general_info(params)
77
+ set :swagger_info, SwaggerInfo.new(params)
78
+ end
79
+
80
+ # Declare a type
81
+ def type(name, params)
82
+ types = settings.swagger_types
83
+ if types.key? name
84
+ raise SwaggerInvalidException.new("Type [#{name}] already exist with value #{types[name]}")
85
+ end
86
+ types[name] = SwaggerType.new(name, params, settings.swagger_types.keys)
87
+ end
88
+
89
+ # Declare a response
90
+ def endpoint_response(code, type = nil, description = nil)
91
+ responses = settings.swagger_current_endpoint_responses
92
+ check_if_not_duplicate(code, responses, 'Response')
93
+ responses[code] = SwaggerEndpointResponse.new(type, description, settings.swagger_types.keys)
94
+ end
95
+
96
+ def delete(*args, &block)
97
+ process_endpoint('delete', args)
98
+ super(*args, &block)
99
+ end
100
+
101
+ def get(*args, &block)
102
+ process_endpoint('get', args)
103
+ super(*args, &block)
104
+ end
105
+
106
+ def head(*args, &block)
107
+ process_endpoint('head', args)
108
+ super(*args, &block)
109
+ end
110
+
111
+ def link(*args, &block)
112
+ process_endpoint('link', args)
113
+ super(*args, &block)
114
+ end
115
+
116
+ def options(*args, &block)
117
+ process_endpoint('options', args)
118
+ super(*args, &block)
119
+ end
120
+
121
+ def patch(*args, &block)
122
+ process_endpoint('patch', args)
123
+ super(*args, &block)
124
+ end
125
+
126
+ def post(*args, &block)
127
+ process_endpoint('post', args)
128
+ super(*args, &block)
129
+ end
130
+
131
+ def put(*args, &block)
132
+ process_endpoint('put', args)
133
+ super(*args, &block)
134
+ end
135
+
136
+ def unlink(*args, &block)
137
+ process_endpoint('unlink', args)
138
+ super(*args, &block)
139
+ end
140
+
141
+ private
142
+
143
+ # Call for each endpoint declaration
144
+ def process_endpoint(type, args)
145
+ current_endpoint_info = settings.swagger_current_endpoint_info
146
+ current_endpoint_parameters = settings.swagger_current_endpoint_parameters
147
+ current_endpoint_responses = settings.swagger_current_endpoint_responses
148
+ endpoint_path = args[0]
149
+ settings.swagger_endpoints << SwaggerEndpoint.new(
150
+ type,
151
+ endpoint_path,
152
+ current_endpoint_parameters.values,
153
+ current_endpoint_responses.clone,
154
+ current_endpoint_info[:summary],
155
+ current_endpoint_info[:description],
156
+ current_endpoint_info[:tags])
157
+ current_endpoint_info.clear
158
+ current_endpoint_parameters.clear
159
+ current_endpoint_responses.clear
160
+ end
161
+
162
+ def set_if_type_and_not_exist(value, name, type)
163
+ if type
164
+ unless value.is_a? type
165
+ raise SwaggerInvalidException.new("#{name} [#{value}] should be a #{type.to_s.downcase}")
166
+ end
167
+ end
168
+ if settings.swagger_current_endpoint_info.key? name
169
+ raise SwaggerInvalidException.new("#{name} with value [#{value}] already defined: #{settings.swagger_current_endpoint_info[name]}")
170
+ end
171
+ settings.swagger_current_endpoint_info[name] = value
172
+ end
173
+
174
+ def check_if_not_duplicate(key, values, name)
175
+ if values.key? key
176
+ raise SwaggerInvalidException.new("#{name} already exist for #{key} with value #{values[key]}")
177
+ end
178
+ end
179
+
180
+ end
181
+
182
+ end
@@ -0,0 +1,67 @@
1
+ require_relative 'swagger-invalid-exception'
2
+
3
+ module Sinatra
4
+
5
+ module SwaggerExposer
6
+
7
+ # The info declaration
8
+ class SwaggerInfo
9
+
10
+ def initialize(values)
11
+ @values = process(values, 'info', INFO_FIELDS, values)
12
+ end
13
+
14
+ # Known fields for the info field
15
+ INFO_FIELDS = {
16
+ :version => String,
17
+ :title => String,
18
+ :description => String,
19
+ :termsOfService => String,
20
+ :contact => {:name => String, :email => String, :url => String},
21
+ :license => {:name => String, :url => String},
22
+ }
23
+
24
+ # Recursive function
25
+ def process(current_hash, current_field_name, current_fields, top_level_hash)
26
+ result = {}
27
+
28
+ current_hash.each_pair do |current_key, current_value|
29
+ key_sym = current_key.to_sym
30
+ if current_fields.key? key_sym
31
+
32
+ field_content = current_fields[key_sym]
33
+ if field_content == String
34
+ if current_value.is_a? String
35
+ result[key_sym] = current_value
36
+ else
37
+ raise SwaggerInvalidException.new("Property [#{current_key}] value [#{current_value}] should be a String for #{current_field_name}: #{top_level_hash}")
38
+ end
39
+ else
40
+ if current_value.is_a? Hash
41
+ sub_params = process(current_value, current_field_name, field_content, top_level_hash)
42
+ if sub_params
43
+ result[key_sym] = sub_params
44
+ end
45
+ else
46
+ raise SwaggerInvalidException.new("Property [#{current_key}] value [#{current_value}] should be a Hash for #{current_field_name}: #{top_level_hash}")
47
+ end
48
+ end
49
+ else
50
+ raise SwaggerInvalidException.new("Unknown property [#{current_key}] for #{current_field_name}, possible keys are: #{current_fields.keys.join(', ')}: #{top_level_hash}")
51
+ end
52
+ end
53
+ result.empty? ? nil : result
54
+ end
55
+
56
+ def to_swagger
57
+ @values
58
+ end
59
+
60
+ def to_s
61
+ @values.to_json
62
+ end
63
+
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,16 @@
1
+ module Sinatra
2
+
3
+ module SwaggerExposer
4
+
5
+ # When something is wrong
6
+ class SwaggerInvalidException < Exception
7
+
8
+ def initialize(message)
9
+ super(message)
10
+ end
11
+
12
+ end
13
+
14
+ end
15
+
16
+ end
@@ -0,0 +1,72 @@
1
+ require_relative 'swagger-utilities'
2
+ require_relative 'swagger-invalid-exception'
3
+
4
+ module Sinatra
5
+
6
+ module SwaggerExposer
7
+
8
+ # A property of a type
9
+ class SwaggerTypeProperty
10
+
11
+ include SwaggerUtilities
12
+
13
+ OTHER_PROPERTIES = [:example, :description, :format]
14
+ PROPERTIES = [:type] + OTHER_PROPERTIES
15
+
16
+ def initialize(type_name, property_name, property_properties, known_types)
17
+ @name = property_name
18
+
19
+ unless property_properties.is_a? Hash
20
+ raise SwaggerInvalidException.new("Property [#{property_name}] value [#{property_properties}] of [#{type_name}] should be a hash")
21
+ end
22
+
23
+ if property_properties.key? :type
24
+ get_type(property_properties[:type], PRIMITIVE_TYPES + known_types)
25
+ end
26
+
27
+ white_list_params(property_properties, PROPERTIES)
28
+
29
+ @other_properties = property_properties.select do |key, value|
30
+ OTHER_PROPERTIES.include? key
31
+ end
32
+
33
+ end
34
+
35
+ def to_swagger
36
+ result = @other_properties.clone
37
+
38
+ if @type
39
+ if @type == 'array'
40
+ result[:type] = 'array'
41
+ if @items
42
+ if PRIMITIVE_TYPES.include? @items
43
+ result[:items] = {:type => @items}
44
+ else
45
+ result[:items] = {'$ref' => "#/definitions/#{@items}"}
46
+ end
47
+ end
48
+ else
49
+ if PRIMITIVE_TYPES.include? @type
50
+ result[:type] = @type
51
+ else
52
+ result['$ref'] = "#/definitions/#{@type}"
53
+ end
54
+ end
55
+ end
56
+
57
+ result
58
+ end
59
+
60
+ def to_s
61
+ {
62
+ :name => @name,
63
+ :type => @type,
64
+ :items => @items,
65
+ :other_properties => @other_properties,
66
+ }.to_json
67
+ end
68
+
69
+ end
70
+
71
+ end
72
+ end
@@ -0,0 +1,101 @@
1
+ require_relative 'swagger-invalid-exception'
2
+ require_relative 'swagger-type-property'
3
+ require_relative 'swagger-utilities'
4
+
5
+ module Sinatra
6
+
7
+ module SwaggerExposer
8
+
9
+ # A type
10
+ class SwaggerType
11
+
12
+ include SwaggerUtilities
13
+
14
+ def initialize(type_name, type_content, known_types)
15
+ @properties = process_properties(type_name, type_content, known_types)
16
+ @required = process_required(type_name, type_content, @properties.keys)
17
+ @example = process_example(type_name, type_content, @properties.keys)
18
+ end
19
+
20
+ def process_properties(type_name, type_content, known_types)
21
+ possible_value = check_attribute_empty_or_bad(type_name, type_content, :properties, Hash)
22
+ if possible_value
23
+ possible_value
24
+ else
25
+ result = {}
26
+ type_content[:properties].each_pair do |property_name, property_properties|
27
+ result[property_name.to_s] = SwaggerTypeProperty.new(type_name, property_name, property_properties, known_types)
28
+ end
29
+ result
30
+ end
31
+ end
32
+
33
+ def process_required(type_name, type_content, properties_names)
34
+ possible_value = check_attribute_empty_or_bad(type_name, type_content, :required, Array)
35
+ if possible_value
36
+ possible_value
37
+ else
38
+ type_content[:required].each do |property_name|
39
+ property_name = property_name.to_s
40
+ unless properties_names.include? property_name
41
+ raise SwaggerInvalidException.new("Required property [#{property_name}] of [#{type_name}] is unknown, known properties: #{properties_names.join(', ')}")
42
+ end
43
+ end
44
+ type_content[:required]
45
+ end
46
+ end
47
+
48
+ def process_example(type_name, type_content, properties_names)
49
+ possible_value = check_attribute_empty_or_bad(type_name, type_content, :example, Hash)
50
+ if possible_value
51
+ possible_value
52
+ else
53
+ type_content[:example].each_pair do |property_name, property_value|
54
+ property_name = property_name.to_s
55
+ unless properties_names.include? property_name
56
+ raise SwaggerInvalidException.new("Example property [#{property_name}] with value [#{property_value}] of [#{type_name}] is unknown, known properties: #{properties_names.join(', ')}")
57
+ end
58
+ end
59
+ type_content[:example]
60
+ end
61
+ end
62
+
63
+ def check_attribute_empty_or_bad(type_name, type_content, attribute_name, attribute_class)
64
+ if !type_content.key?(attribute_name)
65
+ attribute_class.new
66
+ elsif !type_content[attribute_name].is_a? attribute_class
67
+ raise SwaggerInvalidException.new("Attribute [#{attribute_name}] of #{type_name} is not an hash: #{type_content[attribute_name]}")
68
+ end
69
+ end
70
+
71
+ def to_swagger
72
+ result = {}
73
+
74
+ unless @properties.empty?
75
+ result[:properties] = hash_to_swagger(@properties)
76
+ end
77
+
78
+ unless @required.empty?
79
+ result[:required] = @required
80
+ end
81
+
82
+ unless @example.empty?
83
+ result[:example] = @example
84
+ end
85
+
86
+ result
87
+ end
88
+
89
+ def to_s
90
+ {
91
+ :properties => @properties,
92
+ :required => @required,
93
+ :example => @example,
94
+ }.to_json
95
+ end
96
+
97
+
98
+ end
99
+
100
+ end
101
+ end
@@ -0,0 +1,85 @@
1
+ require_relative 'swagger-invalid-exception'
2
+
3
+ module Sinatra
4
+
5
+ module SwaggerExposer
6
+
7
+ module SwaggerUtilities
8
+
9
+ PRIMITIVE_TYPES = [
10
+ 'integer',
11
+ 'long',
12
+ 'float',
13
+ 'double',
14
+ 'string',
15
+ 'byte',
16
+ 'boolean',
17
+ 'date',
18
+ 'dateTime',
19
+ 'password'
20
+ ]
21
+
22
+ def hash_to_swagger(hash)
23
+ result = {}
24
+ hash.each_pair do |key, value|
25
+ result[key] = value.to_swagger
26
+ end
27
+ result
28
+ end
29
+
30
+ def type_to_s(value)
31
+ if value.is_a? Class
32
+ value.to_s.downcase
33
+ else
34
+ value
35
+ end
36
+ end
37
+
38
+ def get_type(type, possible_values)
39
+ @type = type
40
+ if type.nil?
41
+ raise SwaggerInvalidException.new('Type is nil')
42
+ elsif type.is_a?(String) || @type.is_a?(Class)
43
+ @type = type_to_s(@type)
44
+ check_type(@type, possible_values)
45
+ elsif @type.is_a? Array
46
+ @items = type_to_s(get_array_type(@type))
47
+ check_type(@items, possible_values)
48
+ @type = 'array'
49
+ else
50
+ raise SwaggerInvalidException.new("Type [#{@type}] of has an unknown type, should be a class, a string or an array")
51
+ end
52
+ end
53
+
54
+ def white_list_params(params, allowed_params)
55
+ params.each_pair do |key, value|
56
+ unless allowed_params.include? key
57
+ raise SwaggerInvalidException.new("Unknown property [#{key}] for with value [#{value}], known properties are #{allowed_params.join(', ')}")
58
+ end
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ def get_array_type(array)
65
+ if array.empty?
66
+ raise SwaggerInvalidException.new('Type is an empty array, you should specify a type as the array content')
67
+ elsif array.length > 1
68
+ raise SwaggerInvalidException.new("Type [#{array}] has more than one entry, it should only have one")
69
+ else
70
+ type_to_s(array[0])
71
+ end
72
+ end
73
+
74
+ def check_type(type, possible_values)
75
+ unless possible_values.include? type
76
+ raise SwaggerInvalidException.new("Unknown type [#{type}], possible types are #{possible_values.join(', ')}")
77
+ end
78
+ end
79
+
80
+ end
81
+
82
+ end
83
+
84
+ end
85
+
@@ -0,0 +1,5 @@
1
+ module Sinatra
2
+ module SwaggerExposer
3
+ VERSION = '0.0.1'
4
+ end
5
+ end
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'sinatra/swagger-exposer/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'sinatra-swagger-exposer'
8
+ spec.version = Sinatra::SwaggerExposer::VERSION
9
+ spec.authors = ['Julien Kirch']
10
+
11
+ spec.summary = %q{Expose swagger API from your Sinatra app}
12
+ spec.description = %q{This Sinatra extension enable you to add metadata to your code and to expose your API as a Swagger endpoint}
13
+ spec.homepage = 'https://github.com/archiloque/sinatra-swagger-exposer'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.require_paths = ['lib']
18
+
19
+ spec.add_dependency 'sinatra', '~> 1.4'
20
+
21
+ spec.add_development_dependency 'bundler'
22
+ spec.add_development_dependency 'rake', '~> 10.0'
23
+ spec.add_development_dependency 'minitest', '~> 5.5'
24
+ spec.add_development_dependency 'rack-test', '~> 0.6.3'
25
+ spec.add_development_dependency 'simplecov'
26
+
27
+ end
metadata ADDED
@@ -0,0 +1,151 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sinatra-swagger-exposer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Julien Kirch
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-04-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: sinatra
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.4'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '5.5'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '5.5'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rack-test
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.6.3
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.6.3
83
+ - !ruby/object:Gem::Dependency
84
+ name: simplecov
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: This Sinatra extension enable you to add metadata to your code and to
98
+ expose your API as a Swagger endpoint
99
+ email:
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".gitignore"
105
+ - ".travis.yml"
106
+ - CODE_OF_CONDUCT.md
107
+ - Gemfile
108
+ - LICENSE.txt
109
+ - README.asciidoc
110
+ - Rakefile
111
+ - example/Gemfile
112
+ - example/Gemfile.lock
113
+ - example/config.ru
114
+ - example/petstore.rb
115
+ - lib/sinatra/swagger-exposer/swagger-content-creator.rb
116
+ - lib/sinatra/swagger-exposer/swagger-endpoint-parameter.rb
117
+ - lib/sinatra/swagger-exposer/swagger-endpoint-response.rb
118
+ - lib/sinatra/swagger-exposer/swagger-endpoint.rb
119
+ - lib/sinatra/swagger-exposer/swagger-exposer.rb
120
+ - lib/sinatra/swagger-exposer/swagger-info.rb
121
+ - lib/sinatra/swagger-exposer/swagger-invalid-exception.rb
122
+ - lib/sinatra/swagger-exposer/swagger-type-property.rb
123
+ - lib/sinatra/swagger-exposer/swagger-type.rb
124
+ - lib/sinatra/swagger-exposer/swagger-utilities.rb
125
+ - lib/sinatra/swagger-exposer/version.rb
126
+ - sinatra-swagger-exposer.gemspec
127
+ homepage: https://github.com/archiloque/sinatra-swagger-exposer
128
+ licenses:
129
+ - MIT
130
+ metadata: {}
131
+ post_install_message:
132
+ rdoc_options: []
133
+ require_paths:
134
+ - lib
135
+ required_ruby_version: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
140
+ required_rubygems_version: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ requirements: []
146
+ rubyforge_project:
147
+ rubygems_version: 2.4.5
148
+ signing_key:
149
+ specification_version: 4
150
+ summary: Expose swagger API from your Sinatra app
151
+ test_files: []