swaggable 0.4.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.
data/lib/swaggable.rb ADDED
@@ -0,0 +1,14 @@
1
+ require 'swaggable/version'
2
+
3
+ module Swaggable
4
+ autoload :ApiDefinition, 'swaggable/api_definition'
5
+ autoload :EndpointDefinition, 'swaggable/endpoint_definition'
6
+ autoload :ParameterDefinition, 'swaggable/parameter_definition'
7
+ autoload :TagDefinition, 'swaggable/tag_definition'
8
+ autoload :ResponseDefinition, 'swaggable/response_definition'
9
+ autoload :RackApp, 'swaggable/rack_app'
10
+ autoload :GrapeAdapter, 'swaggable/grape_adapter'
11
+ autoload :Swagger2Serializer, 'swaggable/swagger_2_serializer'
12
+ autoload :Swagger2Validator, 'swaggable/swagger_2_validator'
13
+ autoload :EndpointIndex, 'swaggable/endpoint_index'
14
+ end
@@ -0,0 +1,52 @@
1
+ require 'forwarding_dsl'
2
+ require 'mini_object'
3
+
4
+ module Swaggable
5
+ class ApiDefinition
6
+ include ForwardingDsl::Getsetter
7
+
8
+ getsetter(
9
+ :version,
10
+ :title,
11
+ :description,
12
+ :base_path,
13
+ )
14
+
15
+ def initialize &block
16
+ configure(&block) if block_given?
17
+ end
18
+
19
+ def endpoints &block
20
+ ForwardingDsl.run(
21
+ @endpoints ||= build_endpoints,
22
+ &block
23
+ )
24
+ end
25
+
26
+ def tags
27
+ (endpoints.map(&:tags).reduce(:merge) || []).dup.freeze
28
+ end
29
+
30
+ def self.from_grape_api grape
31
+ grape_adapter.import(grape, new)
32
+ end
33
+
34
+ def self.grape_adapter
35
+ @grape_adapter ||= Swaggable::GrapeAdapter.new
36
+ end
37
+
38
+ def configure &block
39
+ ForwardingDsl.run(self, &block)
40
+ self
41
+ end
42
+
43
+ private
44
+
45
+ def build_endpoints
46
+ MiniObject::IndexedList.new.tap do |l|
47
+ l.build { EndpointDefinition.new }
48
+ l.key {|e| "#{e.verb.to_s.upcase} #{e.path}" }
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,54 @@
1
+ require 'forwarding_dsl'
2
+ require 'mini_object'
3
+
4
+ module Swaggable
5
+ class EndpointDefinition
6
+ include ForwardingDsl::Getsetter
7
+
8
+ getsetter(
9
+ :path,
10
+ :verb,
11
+ :description,
12
+ :summary,
13
+ )
14
+
15
+ def initialize args = {}, &block
16
+ args.each {|k, v| self.send("#{k}=", v) }
17
+ configure(&block) if block_given?
18
+ end
19
+
20
+ def tags
21
+ @tags ||= MiniObject::IndexedList.new.tap do |l|
22
+ l.build { TagDefinition.new }
23
+ l.key {|e| e.name }
24
+ end
25
+ end
26
+
27
+ def parameters
28
+ @parameters ||= MiniObject::IndexedList.new.tap do |l|
29
+ l.build { ParameterDefinition.new }
30
+ l.key {|e| e.name }
31
+ end
32
+ end
33
+
34
+ def responses
35
+ @responses ||= MiniObject::IndexedList.new.tap do |l|
36
+ l.build { ResponseDefinition.new }
37
+ l.key {|e| e.status }
38
+ end
39
+ end
40
+
41
+ def consumes
42
+ @consumes ||= []
43
+ end
44
+
45
+ def produces
46
+ @produces ||= []
47
+ end
48
+
49
+ def configure &block
50
+ ForwardingDsl.run(self, &block)
51
+ self
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,84 @@
1
+ module Swaggable
2
+ class GrapeAdapter
3
+ def import grape, api
4
+ api.version = grape.version
5
+ api.title = grape.name
6
+ api.base_path = '/'
7
+
8
+ grape.routes.each do |grape_endpoint|
9
+ api.endpoints.add_new do |api_endpoint|
10
+ import_endpoint grape_endpoint, api_endpoint, grape
11
+ end
12
+ end
13
+
14
+ api
15
+ end
16
+
17
+ private
18
+
19
+ def extract_path grape_endpoint, grape
20
+ path = grape_endpoint.route_path
21
+ path = remove_format_from_path(path)
22
+ path = substitute_version_in_path(path, grape)
23
+ substitute_parameters_in_path(path, grape_endpoint)
24
+ end
25
+
26
+ def remove_format_from_path path
27
+ path.gsub(/\(\/{0,1}\.:format\)$/,'')
28
+ end
29
+
30
+ def substitute_version_in_path path, grape
31
+ prefix = "/#{grape.prefix.to_s}".gsub(/^\/\//,'/')
32
+ prefix = '' if prefix == '/'
33
+ path.gsub(/^#{Regexp.escape prefix}\/:version/,"#{prefix}/#{grape.version}")
34
+ end
35
+
36
+ def substitute_parameters_in_path path, grape_endpoint
37
+ path = path.dup
38
+
39
+ grape_endpoint.route_compiled.names.each do |name|
40
+ path.gsub!(/:#{name}/, "{#{name}}")
41
+ end
42
+
43
+ path
44
+ end
45
+
46
+ def parameter_from name, options, grape_endpoint
47
+ Swaggable::ParameterDefinition.new do |p|
48
+ options = {} if options == ''
49
+
50
+ p.name = name
51
+ p.type = options[:type].downcase.to_sym if options[:type]
52
+ p.required = options[:required]
53
+ p.description = options[:desc]
54
+ p.location = if grape_endpoint.route_compiled.names.include? name
55
+ :path
56
+ else
57
+ :query
58
+ end
59
+ end
60
+ end
61
+
62
+ def import_endpoint grape_endpoint, api_endpoint, grape
63
+ api_endpoint.verb = grape_endpoint.route_method.downcase
64
+ api_endpoint.summary = grape_endpoint.route_description
65
+ api_endpoint.produces << "application/#{grape.format}" if grape.format
66
+ api_endpoint.path = extract_path(grape_endpoint, grape)
67
+
68
+ api_endpoint.tags.add_new do |t|
69
+ t.name = grape.name
70
+ end
71
+
72
+ grape_endpoint.route_params.each do |name, options|
73
+ api_endpoint.parameters << parameter_from(name, options, grape_endpoint)
74
+ end
75
+
76
+ (grape_endpoint.route_http_codes || []).each do |status, desc, entity|
77
+ api_endpoint.responses.add_new do |r|
78
+ r.status = status
79
+ r.description = desc
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,50 @@
1
+ require 'mini_object'
2
+
3
+ module Swaggable
4
+ class ParameterDefinition
5
+ include ForwardingDsl::Getsetter
6
+
7
+ getsetter(
8
+ :name,
9
+ :description,
10
+ :location,
11
+ :required,
12
+ :type,
13
+ )
14
+
15
+ def initialize args = {}
16
+ args.each {|k, v| self.send("#{k}=", v) }
17
+ yield self if block_given?
18
+ end
19
+
20
+ def required?
21
+ !!required
22
+ end
23
+
24
+ def location= location
25
+ unless valid_locations.include? location
26
+ raise ArgumentError.new("#{location} is not one of the valid locations: #{valid_locations.join(", ")}")
27
+ end
28
+
29
+ @location = location
30
+ end
31
+
32
+ def type= type
33
+ unless valid_types.include? type
34
+ raise ArgumentError.new("#{type} is not one of the valid types: #{valid_types.join(", ")}")
35
+ end
36
+
37
+ @type = type
38
+ end
39
+
40
+ private
41
+
42
+ def valid_locations
43
+ [:path, :query, :header, :body, :form, nil]
44
+ end
45
+
46
+ def valid_types
47
+ [:string, :number, :integer, :boolean, :array, :file, nil]
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,30 @@
1
+ require 'json'
2
+
3
+ module Swaggable
4
+ class RackApp
5
+ attr_accessor(
6
+ :api_definition,
7
+ :serializer,
8
+ )
9
+
10
+ def initialize args = {}
11
+ args.each {|k,v| send "#{k}=", v }
12
+ end
13
+
14
+ def call env
15
+ [
16
+ 200,
17
+ {'Content-Type' => 'application/json'},
18
+ [serializer.serialize(api_definition).to_json]
19
+ ]
20
+ end
21
+
22
+ def serializer
23
+ @serializer ||= Swagger2Serializer.new
24
+ end
25
+
26
+ def validate!
27
+ serializer.validate! api_definition
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,16 @@
1
+ require 'forwarding_dsl'
2
+
3
+ module Swaggable
4
+ class ResponseDefinition
5
+ include ForwardingDsl::Getsetter
6
+
7
+ getsetter :status
8
+ getsetter :description
9
+
10
+ def initialize args = {}
11
+ args.each {|k, v| self.send("#{k}=", v) }
12
+ yield self if block_given?
13
+ end
14
+ end
15
+ end
16
+
@@ -0,0 +1,92 @@
1
+ module Swaggable
2
+ # Generates a Swagger 2 hash from an {ApiDefinition}.
3
+ #
4
+ # @example Basic usage
5
+ # serializer = Swagger2Serializer.new
6
+ # api_definition = ApiDefinition.new
7
+ # serializer.serialize(api_definition)
8
+ # # => {:swagger=>"2.0", :basePath=>nil, :info=>{:title=>nil, :description=>nil, :version=>nil}, :tags=>[], :paths=>{}}
9
+ #
10
+ class Swagger2Serializer
11
+ attr_accessor :tag_serializer
12
+
13
+ # Main method that given an {ApiDefinition} will return a hash to serialize
14
+ def serialize api
15
+ {
16
+ swagger: '2.0',
17
+ basePath: api.base_path,
18
+ info: serialize_info(api),
19
+ tags: api.tags.map{|t| serialize_tag t },
20
+ paths: serialize_endpoints(api.endpoints),
21
+ }
22
+ end
23
+
24
+ def serialize_info api
25
+ {
26
+ title: api.title.to_s,
27
+ version: (api.version || '0.0.0'),
28
+ }.
29
+ tap do |h|
30
+ h[:description] = api.description if api.description
31
+ end
32
+ end
33
+
34
+ def serialize_tag tag
35
+ {
36
+ name: tag.name,
37
+ }.
38
+ tap do |e|
39
+ e[:description] = tag.description if tag.description
40
+ end
41
+ end
42
+
43
+ def serialize_endpoints endpoints
44
+ endpoints.inject({}) do |out, endpoint|
45
+ out[endpoint.path] ||= {}
46
+ out[endpoint.path][endpoint.verb] = serialize_endpoint(endpoint)
47
+ out
48
+ end
49
+ end
50
+
51
+ def serialize_endpoint endpoint
52
+ {
53
+ tags: endpoint.tags.map(&:name),
54
+ consumes: endpoint.consumes,
55
+ produces: endpoint.produces,
56
+ parameters: endpoint.parameters.map{|p| serialize_parameter p },
57
+ responses: serialize_responses(endpoint.responses),
58
+ }.
59
+ tap do |e|
60
+ e[:summary] = endpoint.summary if endpoint.summary
61
+ e[:description] = endpoint.description if endpoint.description
62
+ end
63
+ end
64
+
65
+ def serialize_parameter parameter
66
+ p = {
67
+ in: parameter.location.to_s,
68
+ name: parameter.name,
69
+ required: parameter.required?,
70
+ }
71
+
72
+ p[:type] = parameter.type || 'string'
73
+ p[:description] = parameter.description if parameter.description
74
+ p
75
+ end
76
+
77
+ def serialize_responses responses
78
+ if responses.any?
79
+ responses.inject({}) do |acc, r|
80
+ acc[r.status] = {description: r.description}
81
+ acc
82
+ end
83
+ else
84
+ {200 => {description: 'Success'}}
85
+ end
86
+ end
87
+
88
+ def validate! api
89
+ Swagger2Validator.validate! serialize(api)
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,28 @@
1
+ require 'json-schema'
2
+
3
+ module Swaggable
4
+ class Swagger2Validator
5
+ def self.validate! swagger
6
+ preload_draft4
7
+ JSON::Validator.validate!(schema, swagger)
8
+ end
9
+
10
+ private
11
+
12
+ def self.schema
13
+ @schema = assets_dir + '/swagger-2.0-schema.json'
14
+ end
15
+
16
+ def self.draft4
17
+ @draft4 = assets_dir + '/json-schema-draft-04.json'
18
+ end
19
+
20
+ def self.assets_dir
21
+ File.dirname(__FILE__) + '/../../assets'
22
+ end
23
+
24
+ def self.preload_draft4
25
+ @draft4_preloaded ||= JSON::Validator.validate!(draft4, {})
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,27 @@
1
+ require 'forwarding_dsl'
2
+
3
+ module Swaggable
4
+ class TagDefinition
5
+ include ForwardingDsl::Getsetter
6
+
7
+ getsetter(
8
+ :name,
9
+ :description,
10
+ )
11
+
12
+ def initialize args = {}
13
+ args.each {|k, v| self.send("#{k}=", v) }
14
+ yield self if block_given?
15
+ end
16
+
17
+ def == other
18
+ self.name == other.name if other.respond_to?(:name)
19
+ end
20
+ alias eql? ==
21
+
22
+ def hash
23
+ name.hash
24
+ end
25
+ end
26
+ end
27
+