swaggable 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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
+