sinatra-swagger 0.0.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 ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MmJlMmY4Y2NiMTI5NTljNDAyNGIxODBkY2ZlZmI3OTFiYWU2ODNjMQ==
5
+ data.tar.gz: !binary |-
6
+ MTFiY2Q2NDRhYTJmMmU5ODAyYzgxMDg4OGJjYzRlNDg3NzZiMjgyZg==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ YjZmMDE4YTZiMDIzMTUyODAwZDhlYjM4YThkZjFkNjNhYTNjNjc5ZWQwM2M5
10
+ MTc3MzY4NjlmM2E3ZGMyYzExNWY3MjA0OGNiMDI1N2I2NjlkYjBlMTA2YmEx
11
+ NmVkMGUwZGJkZWU4ZDBhYjhiOWFmMjA0NmY1NTNjOWY0ZGVjZGI=
12
+ data.tar.gz: !binary |-
13
+ YWQ0NjIzOGJmZTA4NzU2YjIyNGMyN2IwZWJkYWQxMjU4Nzg0ZjA4OTY5ZTRj
14
+ ZmE2OTdlNzIyNzNiNzNhYTQ4YzIwMTdkMDkxNzgxZmZlMzJiOTg4MjZlY2I1
15
+ ZjdmYjZjMjhiNjJkYmQzNzg4OWQxYTI1NGZjZWNhOTc3ZGNiZjQ=
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ InstalledFiles
7
+ _yardoc
8
+ coverage
9
+ doc/
10
+ lib/bundler/man
11
+ pkg
12
+ rdoc
13
+ spec/reports
14
+ test/tmp
15
+ test/version_tmp
16
+ tmp
17
+ config/
18
+ bin/
19
+ vendor/
20
+ log/
21
+ Gemfile.lock
data/.travis.yml ADDED
@@ -0,0 +1,12 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ - 2.1.2
5
+ deploy:
6
+ provider: rubygems
7
+ api_key:
8
+ secure: f3c7U1z9hulitL8sjYztJmEEgGsCC/X/q/VybFYOC2wsp5D2WggYJ4CTaWcNjCSAWXgVl5L0kOkjKYyItdxVJoyhBa4Mc3MfZo8Dp2UCYpdA+FGd7UbOqbITkr9ZyD22oWk7PgfKeNKoLyffdG9HH/OYJ5ezSa7PB2XMN+Ae2W8=
9
+ gem: sinatra-swagger
10
+ on:
11
+ tags: true
12
+ repo: jphastings/sinatra-swagger
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
data/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # Sinatra::Swagger
2
+
3
+ Helper methods for accesing your swagger documentation from within your sinatra routes.
data/Rakefile ADDED
@@ -0,0 +1,23 @@
1
+ require "rake"
2
+ require "bundler/gem_tasks"
3
+ require "rspec/core/rake_task"
4
+
5
+ task :default => :build
6
+ task :build => :test
7
+
8
+ desc "Runs all tests"
9
+ task :test => :spec
10
+
11
+ desc "Run all rspec tests"
12
+ begin
13
+ require "rspec/core/rake_task"
14
+
15
+ RSpec::Core::RakeTask.new(:spec) do |t|
16
+ t.pattern = "spec/**/*_spec.rb"
17
+ end
18
+ rescue LoadError => e
19
+ raise e
20
+ task :spec do
21
+ $stderr.puts "Please install rspec: `gem install rspec`"
22
+ end
23
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,68 @@
1
+ require "yaml"
2
+ require "swagger/rack"
3
+
4
+ module Sinatra
5
+ module Swagger
6
+ module SpecEnforcer
7
+ def swagger(filepath)
8
+ set :swagger, ::Swagger::Base.new(filepath)
9
+ end
10
+
11
+ def self.registered(app)
12
+ app.helpers Helpers
13
+
14
+ app.before do
15
+ spec = swagger_spec
16
+ unless spec.nil?
17
+ _, captures, spec = spec.values
18
+
19
+ invalidities = Hash[(spec['parameters'] || []).map { |details|
20
+ param_name = details['name']
21
+
22
+ parameter = case details['in']
23
+ when "query"
24
+ params[param_name]
25
+ when "path"
26
+ captures[param_name]
27
+ else
28
+ raise NotImplementedError, "Can't cope with #{details['in']} parameters right now"
29
+ end
30
+
31
+ if !parameter
32
+ next [param_name, :missing] if details['required'] && details['required'] != "false"
33
+ next unless details['default']
34
+ parameter = details['default']
35
+ end
36
+
37
+ begin
38
+ parameter = ::Swagger.cast(parameter.to_s, details['type']) if parameter
39
+ rescue ArgumentError
40
+ next [param_name, :incorrect_type]
41
+ end
42
+
43
+ if %w{integer number}.include? details['type']
44
+ too_large = details['maximum'] && parameter > details['maximum']
45
+ too_small = details['minimum'] && parameter < details['minimum']
46
+ next [param_name, :out_of_range] if too_large || too_small
47
+ end
48
+
49
+ params[param_name] = parameter
50
+
51
+ nil
52
+ }.compact]
53
+
54
+ halt 400, invalidities.to_json if invalidities.any?
55
+ end
56
+ end
57
+ end
58
+
59
+ module Helpers
60
+ def swagger_spec
61
+ settings.swagger.request_spec(env: env)
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ register Swagger::SpecEnforcer
68
+ end
@@ -0,0 +1,5 @@
1
+ module Sinatra
2
+ module Swagger
3
+ VERSION = File.read(File.join(__dir__, "../../../VERSION")) rescue "0.0.0"
4
+ end
5
+ end
@@ -0,0 +1,2 @@
1
+ require "sinatra/swagger/version"
2
+ require "sinatra/swagger/spec_enforcer"
@@ -0,0 +1,33 @@
1
+ require "json-schema"
2
+
3
+ module Swagger
4
+ # Casts a string value to the ruby datatype as definied in the swagger spec
5
+ def self.cast(value, type = "string")
6
+ raise ArgumentError, "#{value} is not a string" unless value.is_a?(String)
7
+ case type
8
+ when "string"
9
+ value
10
+ when "integer"
11
+ raise ArgumentError, "#{value} is not an integer" unless value =~ /^-?\d+$/
12
+ value.to_i
13
+ when "number"
14
+ raise ArgumentError, "#{value} is not a float" unless value =~ /^-?\d+(?:\.\d+)?$/
15
+ value.to_f
16
+ else
17
+ raise NotImplementedError
18
+ end
19
+ end
20
+
21
+ class Base
22
+ # Pre-load JSON-schema files used for validation
23
+ Dir.glob(File.join(__dir__, "../../schema/*-schema.json")) do |schema|
24
+ data = JSON.parse(File.read(schema))
25
+ JSON::Validator.add_schema(JSON::Schema.new(data, data['id']))
26
+ end
27
+
28
+ def initialize(filepath)
29
+ @spec = YAML.load(open(filepath))
30
+ JSON::Validator.validate!({ "$ref" => "http://swagger.io/v2/schema.json#" }, @spec)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,42 @@
1
+ require "swagger/base"
2
+
3
+ module Swagger
4
+ module RackHelpers
5
+ def request_spec(env: nil)
6
+ path = env['REQUEST_PATH']
7
+ verb = env['REQUEST_METHOD'].downcase
8
+ matching_paths = (@spec['paths'] || {}).map { |spec_path, spec|
9
+ next unless spec[verb]
10
+ matches = match_string(spec_path, path)
11
+ next if matches.nil?
12
+ {
13
+ path: spec_path,
14
+ captures: matches,
15
+ spec: spec[verb]
16
+ }
17
+ }.compact
18
+
19
+ case matching_paths.size
20
+ when 0
21
+ return nil
22
+ when 1
23
+ return matching_paths.first
24
+ else
25
+ spec_paths = matching_paths.map { |p| p[:path] }
26
+ raise "Your API documentation is non-deterministic for the path: #{path} (#{spec_paths.join(', ')})"
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def match_string(bracketed_string, match_string)
33
+ re = bracketed_string.gsub(/\{([^}]+)\}/, "(?<\\1>.+?)")
34
+ matches = Regexp.new("^#{re}$").match(match_string)
35
+ return nil if matches.nil?
36
+ captures = matches.captures
37
+ Hash[matches.names.zip(captures)]
38
+ end
39
+ end
40
+
41
+ Base.include(RackHelpers)
42
+ end
data/schema/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # Schema
2
+
3
+ These schema are the ones needed to validate Swagger 2.0 documentation without requiring access to the internet.
4
+
5
+ If the Swagger 2.x schema updates then the files here should also be updated. Any file matching `*-schema.json` will be loaded as a JSON Schema file (and will cause errors if it does not validate itself)
@@ -0,0 +1,150 @@
1
+ {
2
+ "id": "http://json-schema.org/draft-04/schema#",
3
+ "$schema": "http://json-schema.org/draft-04/schema#",
4
+ "description": "Core schema meta-schema",
5
+ "definitions": {
6
+ "schemaArray": {
7
+ "type": "array",
8
+ "minItems": 1,
9
+ "items": { "$ref": "#" }
10
+ },
11
+ "positiveInteger": {
12
+ "type": "integer",
13
+ "minimum": 0
14
+ },
15
+ "positiveIntegerDefault0": {
16
+ "allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ]
17
+ },
18
+ "simpleTypes": {
19
+ "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ]
20
+ },
21
+ "stringArray": {
22
+ "type": "array",
23
+ "items": { "type": "string" },
24
+ "minItems": 1,
25
+ "uniqueItems": true
26
+ }
27
+ },
28
+ "type": "object",
29
+ "properties": {
30
+ "id": {
31
+ "type": "string",
32
+ "format": "uri"
33
+ },
34
+ "$schema": {
35
+ "type": "string",
36
+ "format": "uri"
37
+ },
38
+ "title": {
39
+ "type": "string"
40
+ },
41
+ "description": {
42
+ "type": "string"
43
+ },
44
+ "default": {},
45
+ "multipleOf": {
46
+ "type": "number",
47
+ "minimum": 0,
48
+ "exclusiveMinimum": true
49
+ },
50
+ "maximum": {
51
+ "type": "number"
52
+ },
53
+ "exclusiveMaximum": {
54
+ "type": "boolean",
55
+ "default": false
56
+ },
57
+ "minimum": {
58
+ "type": "number"
59
+ },
60
+ "exclusiveMinimum": {
61
+ "type": "boolean",
62
+ "default": false
63
+ },
64
+ "maxLength": { "$ref": "#/definitions/positiveInteger" },
65
+ "minLength": { "$ref": "#/definitions/positiveIntegerDefault0" },
66
+ "pattern": {
67
+ "type": "string",
68
+ "format": "regex"
69
+ },
70
+ "additionalItems": {
71
+ "anyOf": [
72
+ { "type": "boolean" },
73
+ { "$ref": "#" }
74
+ ],
75
+ "default": {}
76
+ },
77
+ "items": {
78
+ "anyOf": [
79
+ { "$ref": "#" },
80
+ { "$ref": "#/definitions/schemaArray" }
81
+ ],
82
+ "default": {}
83
+ },
84
+ "maxItems": { "$ref": "#/definitions/positiveInteger" },
85
+ "minItems": { "$ref": "#/definitions/positiveIntegerDefault0" },
86
+ "uniqueItems": {
87
+ "type": "boolean",
88
+ "default": false
89
+ },
90
+ "maxProperties": { "$ref": "#/definitions/positiveInteger" },
91
+ "minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" },
92
+ "required": { "$ref": "#/definitions/stringArray" },
93
+ "additionalProperties": {
94
+ "anyOf": [
95
+ { "type": "boolean" },
96
+ { "$ref": "#" }
97
+ ],
98
+ "default": {}
99
+ },
100
+ "definitions": {
101
+ "type": "object",
102
+ "additionalProperties": { "$ref": "#" },
103
+ "default": {}
104
+ },
105
+ "properties": {
106
+ "type": "object",
107
+ "additionalProperties": { "$ref": "#" },
108
+ "default": {}
109
+ },
110
+ "patternProperties": {
111
+ "type": "object",
112
+ "additionalProperties": { "$ref": "#" },
113
+ "default": {}
114
+ },
115
+ "dependencies": {
116
+ "type": "object",
117
+ "additionalProperties": {
118
+ "anyOf": [
119
+ { "$ref": "#" },
120
+ { "$ref": "#/definitions/stringArray" }
121
+ ]
122
+ }
123
+ },
124
+ "enum": {
125
+ "type": "array",
126
+ "minItems": 1,
127
+ "uniqueItems": true
128
+ },
129
+ "type": {
130
+ "anyOf": [
131
+ { "$ref": "#/definitions/simpleTypes" },
132
+ {
133
+ "type": "array",
134
+ "items": { "$ref": "#/definitions/simpleTypes" },
135
+ "minItems": 1,
136
+ "uniqueItems": true
137
+ }
138
+ ]
139
+ },
140
+ "allOf": { "$ref": "#/definitions/schemaArray" },
141
+ "anyOf": { "$ref": "#/definitions/schemaArray" },
142
+ "oneOf": { "$ref": "#/definitions/schemaArray" },
143
+ "not": { "$ref": "#" }
144
+ },
145
+ "dependencies": {
146
+ "exclusiveMaximum": [ "maximum" ],
147
+ "exclusiveMinimum": [ "minimum" ]
148
+ },
149
+ "default": {}
150
+ }