swagger_yard 0.0.5 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +126 -99
  3. data/Rakefile +6 -22
  4. data/lib/swagger_yard/api.rb +26 -109
  5. data/lib/swagger_yard/api_declaration.rb +89 -23
  6. data/lib/swagger_yard/authorization.rb +36 -0
  7. data/lib/swagger_yard/configuration.rb +14 -0
  8. data/lib/swagger_yard/listing_info.rb +15 -0
  9. data/lib/swagger_yard/model.rb +69 -0
  10. data/lib/swagger_yard/operation.rb +149 -0
  11. data/lib/swagger_yard/parameter.rb +62 -3
  12. data/lib/swagger_yard/property.rb +36 -0
  13. data/lib/swagger_yard/resource_listing.rb +57 -18
  14. data/lib/swagger_yard/type.rb +34 -0
  15. data/lib/swagger_yard/version.rb +1 -1
  16. data/lib/swagger_yard.rb +35 -78
  17. metadata +70 -48
  18. data/app/controllers/swagger_yard/application_controller.rb +0 -4
  19. data/app/controllers/swagger_yard/swagger_controller.rb +0 -21
  20. data/app/views/swagger_yard/swagger/doc.html.erb +0 -80
  21. data/lib/generators/swagger_yard/doc_generator.rb +0 -11
  22. data/lib/generators/swagger_yard/js_generator.rb +0 -13
  23. data/lib/swagger_yard/cache.rb +0 -50
  24. data/lib/swagger_yard/engine.rb +0 -18
  25. data/lib/swagger_yard/local_dispatcher.rb +0 -51
  26. data/lib/swagger_yard/parser.rb +0 -35
  27. data/public/swagger-ui/css/hightlight.default.css +0 -135
  28. data/public/swagger-ui/css/screen.css +0 -1759
  29. data/public/swagger-ui/images/logo_small.png +0 -0
  30. data/public/swagger-ui/images/pet_store_api.png +0 -0
  31. data/public/swagger-ui/images/throbber.gif +0 -0
  32. data/public/swagger-ui/images/wordnik_api.png +0 -0
  33. data/public/swagger-ui/lib/MD5.js +0 -319
  34. data/public/swagger-ui/lib/backbone-min.js +0 -38
  35. data/public/swagger-ui/lib/handlebars-1.0.rc.1.js +0 -1920
  36. data/public/swagger-ui/lib/highlight.7.3.pack.js +0 -1
  37. data/public/swagger-ui/lib/jquery-1.8.0.min.js +0 -2
  38. data/public/swagger-ui/lib/jquery.ba-bbq.min.js +0 -18
  39. data/public/swagger-ui/lib/jquery.slideto.min.js +0 -1
  40. data/public/swagger-ui/lib/jquery.wiggle.min.js +0 -8
  41. data/public/swagger-ui/lib/swagger.js +0 -794
  42. data/public/swagger-ui/lib/underscore-min.js +0 -32
  43. data/public/swagger-ui/swagger-ui.js +0 -2090
  44. data/public/swagger-ui/swagger-ui_org.js +0 -2005
@@ -0,0 +1,69 @@
1
+ module SwaggerYard
2
+ #
3
+ # Carries id (the class name) and properties for a referenced
4
+ # complex model object as defined by swagger schema
5
+ #
6
+ class Model
7
+ attr_reader :id
8
+
9
+ def self.from_yard_objects(yard_objects)
10
+ from_yard_object(yard_objects.detect {|o| o.type == :class })
11
+ end
12
+
13
+ def self.from_yard_object(yard_object)
14
+ from_tags(yard_object.tags) if yard_object
15
+ end
16
+
17
+ def self.from_tags(tags)
18
+ new.tap do |model|
19
+ model.parse_tags(tags)
20
+ end
21
+ end
22
+
23
+ def initialize
24
+ @properties = []
25
+ end
26
+
27
+ def valid?
28
+ !id.nil?
29
+ end
30
+
31
+ def parse_tags(tags)
32
+ tags.each do |tag|
33
+ case tag.tag_name
34
+ when "model"
35
+ @id = tag.text
36
+ when "property"
37
+ @properties << Property.from_tag(tag)
38
+ end
39
+ end
40
+
41
+ self
42
+ end
43
+
44
+ def properties_model_names
45
+ @properties.map(&:model_name).compact
46
+ end
47
+
48
+ def recursive_properties_model_names(model_list)
49
+ properties_model_names + properties_model_names.map do |model_name|
50
+ child_model = model_from_model_list(model_list, model_name)
51
+ child_model.recursive_properties_model_names(model_list) if child_model
52
+ end.compact
53
+ end
54
+
55
+ def model_from_model_list(model_list, model_name)
56
+ model_list.find{|model| model.id == model_name}
57
+ end
58
+
59
+ def to_h
60
+ raise "Model is missing @model tag" if id.nil?
61
+
62
+ {
63
+ "id" => id,
64
+ "properties" => Hash[@properties.map {|property| [property.name, property.to_h]}],
65
+ "required" => @properties.select(&:required?).map(&:name)
66
+ }
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,149 @@
1
+ module SwaggerYard
2
+ class Operation
3
+ attr_accessor :summary, :notes
4
+ attr_reader :path, :http_method, :error_messages, :response_type
5
+ attr_reader :parameters, :model_names
6
+
7
+ PARAMETER_LIST_REGEX = /\A\[(\w*)\]\s*(\w*)(\(required\))?\s*(.*)\n([.\s\S]*)\Z/
8
+
9
+ # TODO: extract to operation builder?
10
+ def self.from_yard_object(yard_object, api)
11
+ new(api).tap do |operation|
12
+ yard_object.tags.each do |tag|
13
+ case tag.tag_name
14
+ when "path"
15
+ operation.add_path_params_and_method(tag)
16
+ when "parameter"
17
+ operation.add_parameter(tag)
18
+ when "parameter_list"
19
+ operation.add_parameter_list(tag)
20
+ when "response_type"
21
+ operation.add_response_type(Type.from_type_list(tag.types))
22
+ when "error_message"
23
+ operation.add_error_message(tag)
24
+ when "summary"
25
+ operation.summary = tag.text
26
+ when "notes"
27
+ operation.notes = tag.text.gsub("\n", "<br\>")
28
+ end
29
+ end
30
+
31
+ operation.sort_parameters
32
+ operation.append_format_parameter
33
+ end
34
+ end
35
+
36
+ def initialize(api)
37
+ @api = api
38
+ @parameters = []
39
+ @model_names = []
40
+ @error_messages = []
41
+ end
42
+
43
+ def nickname
44
+ @path[1..-1].gsub(/[^a-zA-Z\d:]/, '-').squeeze("-") + http_method.downcase
45
+ end
46
+
47
+ def to_h
48
+ {
49
+ "httpMethod" => http_method,
50
+ "nickname" => nickname,
51
+ "type" => "void",
52
+ "produces" => ["application/json", "application/xml"],
53
+ "parameters" => parameters.map(&:to_h),
54
+ "summary" => summary || @api.description,
55
+ "notes" => notes,
56
+ "responseMessages" => error_messages
57
+ }.tap do |h|
58
+ h.merge!(response_type.to_h) if response_type
59
+ end
60
+ end
61
+
62
+ ##
63
+ # Example: [GET] /api/v2/ownerships.{format_type}
64
+ # Example: [PUT] /api/v1/accounts/{account_id}.{format_type}
65
+ def add_path_params_and_method(tag)
66
+ @path = tag.text
67
+ @http_method = tag.types.first
68
+
69
+ parse_path_params(tag.text).each do |name|
70
+ @parameters << Parameter.from_path_param(name)
71
+ end
72
+ end
73
+
74
+ ##
75
+ # Example: [Array] status Filter by status. (e.g. status[]=1&status[]=2&status[]=3)
76
+ # Example: [Array] status(required) Filter by status. (e.g. status[]=1&status[]=2&status[]=3)
77
+ # Example: [Array] status(required, body) Filter by status. (e.g. status[]=1&status[]=2&status[]=3)
78
+ # Example: [Integer] media[media_type_id] ID of the desired media type.
79
+ def add_parameter(tag)
80
+ @parameters << Parameter.from_yard_tag(tag, self)
81
+ end
82
+
83
+ ##
84
+ # Example: [String] sort_order Orders ownerships by fields. (e.g. sort_order=created_at)
85
+ # [List] id
86
+ # [List] begin_at
87
+ # [List] end_at
88
+ # [List] created_at
89
+ def add_parameter_list(tag)
90
+ # TODO: switch to using Parameter.from_yard_tag
91
+ data_type, name, required, description, list_string = parse_parameter_list(tag)
92
+ allowable_values = parse_list_values(list_string)
93
+
94
+ @parameters << Parameter.new(name, Type.new(data_type.downcase), description, {
95
+ required: !!required,
96
+ param_type: "query",
97
+ allow_multiple: false,
98
+ allowable_values: allowable_values
99
+ })
100
+ end
101
+
102
+ def add_response_type(type)
103
+ model_names << type.model_name
104
+ @response_type = type
105
+ end
106
+
107
+ def add_error_message(tag)
108
+ @error_messages << {
109
+ "code" => Integer(tag.name),
110
+ "message" => tag.text,
111
+ "responseModel" => Array(tag.types).first
112
+ }.reject {|_,v| v.nil?}
113
+ end
114
+
115
+ def sort_parameters
116
+ @parameters.sort_by! {|p| p.name}
117
+ end
118
+
119
+ def append_format_parameter
120
+ @parameters << format_parameter
121
+ end
122
+
123
+ def ref?(data_type)
124
+ @api.ref?(data_type)
125
+ end
126
+
127
+ private
128
+ def parse_path_params(path)
129
+ path.scan(/\{([^\}]+)\}/).flatten.reject { |value| value == "format_type" }
130
+ end
131
+
132
+ def parse_parameter_list(tag)
133
+ tag.text.match(PARAMETER_LIST_REGEX).captures
134
+ end
135
+
136
+ def parse_list_values(list_string)
137
+ list_string.split("[List]").map(&:strip).reject { |string| string.empty? }
138
+ end
139
+
140
+ def format_parameter
141
+ Parameter.new("format_type", Type.new("string"), "Response format either JSON or XML", {
142
+ required: true,
143
+ param_type: "path",
144
+ allow_multiple: false,
145
+ allowable_values: ["json", "xml"]
146
+ })
147
+ end
148
+ end
149
+ end
@@ -1,9 +1,68 @@
1
1
  module SwaggerYard
2
2
  class Parameter
3
- attr_accessor :param_type, :name, :description, :data_type, :required, :allow_multiple, :allowable_values
3
+ attr_accessor :name, :description
4
+ attr_reader :param_type, :required, :allow_multiple, :allowable_values
4
5
 
5
- def initialize(yard_object)
6
-
6
+ def self.from_yard_tag(tag, operation)
7
+ description = tag.text
8
+ name, options_string = tag.name.split(/[\(\)]/)
9
+ type = Type.from_type_list(tag.types)
10
+
11
+ options = {}
12
+
13
+ operation.model_names << type.name if type.ref?
14
+
15
+ unless options_string.nil?
16
+ options_string.split(',').map(&:strip).tap do |arr|
17
+ options[:required] = !arr.delete('required').nil?
18
+ options[:allow_multiple] = !arr.delete('multiple').nil?
19
+ options[:param_type] = arr.last
20
+ end
21
+ end
22
+
23
+ new(name, type, description, options)
24
+ end
25
+
26
+ # TODO: support more variation in scope types
27
+ def self.from_path_param(name)
28
+ new(name, Type.new("string"), "Scope response to #{name}", {
29
+ required: true,
30
+ allow_multiple: false,
31
+ param_type: "path"
32
+ })
33
+ end
34
+
35
+ def initialize(name, type, description, options={})
36
+ @name, @type, @description = name, type, description
37
+
38
+ @required = options[:required] || false
39
+ @param_type = options[:param_type] || 'query'
40
+ @allow_multiple = options[:allow_multiple] || false
41
+ @allowable_values = options[:allowable_values] || []
42
+ end
43
+
44
+ def type
45
+ @type.name
46
+ end
47
+
48
+ def allowable_values_hash
49
+ return nil if allowable_values.empty?
50
+
51
+ {
52
+ "valueType" => "LIST",
53
+ "values" => allowable_values
54
+ }
55
+ end
56
+
57
+ def to_h
58
+ {
59
+ "paramType" => param_type,
60
+ "name" => name,
61
+ "description" => description,
62
+ "required" => required,
63
+ "allowMultiple" => !!allow_multiple,
64
+ "allowableValues" => allowable_values_hash
65
+ }.merge(@type.to_h).reject {|k,v| v.nil?}
7
66
  end
8
67
  end
9
68
  end
@@ -0,0 +1,36 @@
1
+ module SwaggerYard
2
+ #
3
+ # Holds the name and type for a single model property
4
+ #
5
+ class Property
6
+ attr_reader :name, :description
7
+
8
+ def self.from_tag(tag)
9
+ name, options_string = tag.name.split(/[\(\)]/)
10
+
11
+ required = options_string.to_s.split(',').map(&:strip).include?('required')
12
+
13
+ new(name, tag.types, tag.text, required)
14
+ end
15
+
16
+ def initialize(name, types, description, required)
17
+ @name, @description, @required = name, description, required
18
+
19
+ @type = Type.from_type_list(types)
20
+ end
21
+
22
+ def required?
23
+ @required
24
+ end
25
+
26
+ def model_name
27
+ @type.model_name
28
+ end
29
+
30
+ def to_h
31
+ result = @type.to_h
32
+ result["description"] = description if description
33
+ result
34
+ end
35
+ end
36
+ end
@@ -1,32 +1,71 @@
1
1
  module SwaggerYard
2
2
  class ResourceListing
3
- attr_reader :api_declarations
3
+ attr_reader :api_declarations, :resource_to_file_path
4
+ attr_accessor :authorizations
4
5
 
5
- def initialize
6
- @api_declarations = []
6
+ def initialize(controller_path, model_path)
7
+ @model_path = model_path
8
+ @controller_path = controller_path
9
+
10
+ @resource_to_file_path = {}
11
+ @authorizations = []
12
+ end
13
+
14
+ def models
15
+ @models ||= parse_models
16
+ end
17
+
18
+ def controllers
19
+ @controllers ||= parse_controllers
7
20
  end
8
21
 
9
- def add(api_declaration)
10
- @api_declarations << api_declaration
22
+ def declaration_for(resource_name)
23
+ controllers[resource_name]
11
24
  end
12
25
 
13
26
  def to_h
14
- {
15
- "apiVersion" => SwaggerYard.api_version,
16
- "swaggerVersion" => SwaggerYard.swagger_version,
17
- "basePath" => SwaggerYard.doc_base_path,
18
- "apis" => list_api
27
+ {
28
+ "apiVersion" => SwaggerYard.config.api_version,
29
+ "swaggerVersion" => SwaggerYard.config.swagger_version,
30
+ "basePath" => SwaggerYard.config.swagger_spec_base_path,
31
+ "apis" => list_api_declarations,
32
+ "authorizations" => authorizations_hash
19
33
  }
20
34
  end
21
35
 
22
36
  private
23
- def list_api
24
- @api_declarations.map do |api_declaration|
25
- {
26
- "path" => api_declaration.resource_path,
27
- "description" => api_declaration.description
28
- }
29
- end.sort_by { |hsh| hsh["path"] }
37
+ def list_api_declarations
38
+ controllers.values.sort_by(&:resource_path).map(&:listing_hash)
39
+ end
40
+
41
+ def parse_models
42
+ return [] unless @model_path
43
+
44
+ Dir[@model_path].map do |file_path|
45
+ Model.from_yard_objects(SwaggerYard.yard_objects_from_file(file_path))
46
+ end.compact.select(&:valid?)
47
+ end
48
+
49
+ def parse_controllers
50
+ return {} unless @controller_path
51
+
52
+ Hash[Dir[@controller_path].map do |file_path|
53
+ declaration = create_api_declaration(file_path)
54
+
55
+ [declaration.resource_name, declaration] if declaration.valid?
56
+ end.compact]
57
+ end
58
+
59
+ def create_api_declaration(file_path)
60
+ yard_objects = SwaggerYard.yard_objects_from_file(file_path)
61
+
62
+ ApiDeclaration.new(self).add_yard_objects(yard_objects)
63
+ end
64
+
65
+ def authorizations_hash
66
+ Hash[
67
+ authorizations.map(&:name).zip(authorizations.map(&:to_h)) # ugh
68
+ ]
30
69
  end
31
70
  end
32
- end
71
+ end
@@ -0,0 +1,34 @@
1
+ module SwaggerYard
2
+ class Type
3
+ def self.from_type_list(types)
4
+ parts = types.first.split(/[<>]/)
5
+ new(parts.last, parts.grep(/array/i).any?)
6
+ end
7
+
8
+ attr_reader :name, :array
9
+
10
+ def initialize(name, array=false)
11
+ @name, @array = name, array
12
+ end
13
+
14
+ # TODO: have this look at resource listing?
15
+ def ref?
16
+ /[[:upper:]]/.match(name)
17
+ end
18
+
19
+ def model_name
20
+ ref? ? name : nil
21
+ end
22
+
23
+ alias :array? :array
24
+
25
+ def to_h
26
+ type_tag = ref? ? "$ref" : "type"
27
+ if array?
28
+ {"type"=>"array", "items"=> { type_tag => name }}
29
+ else
30
+ {"type"=>name}
31
+ end
32
+ end
33
+ end
34
+ end
@@ -1,3 +1,3 @@
1
1
  module SwaggerYard
2
- VERSION = "0.0.5"
2
+ VERSION = "0.1.0"
3
3
  end
data/lib/swagger_yard.rb CHANGED
@@ -1,7 +1,16 @@
1
1
  require "yard"
2
- require "swagger_yard/engine"
3
- require "swagger_yard/cache"
4
- require "swagger_yard/parser"
2
+ require "json"
3
+ require "swagger_yard/configuration"
4
+ require "swagger_yard/type"
5
+ require "swagger_yard/parameter"
6
+ require "swagger_yard/property"
7
+ require "swagger_yard/operation"
8
+ require "swagger_yard/authorization"
9
+ require "swagger_yard/resource_listing"
10
+ require "swagger_yard/api_declaration"
11
+ require "swagger_yard/model"
12
+ require "swagger_yard/api"
13
+ require "swagger_yard/listing_info"
5
14
 
6
15
  module SwaggerYard
7
16
  class << self
@@ -13,101 +22,49 @@ module SwaggerYard
13
22
  # config.api_version = "0.1"
14
23
  # config.doc_base_path = "http://swagger.example.com/doc"
15
24
  # config.api_base_path = "http://swagger.example.com/api"
25
+ # config.reload = true # Rails.env.development?
16
26
  # end
17
27
  def configure
18
- yield self
28
+ yield config
19
29
  end
20
30
 
21
- attr_accessor :doc_base_path, :api_base_path, :api_path
22
- attr_writer :swagger_version, :api_version, :cache_store, :cache_prefix, :enable, :reload
23
-
24
- def cache_store
25
- @cache_store ||= Rails.cache
26
- end
27
-
28
- def cache_prefix
29
- @cache_prefix ||= "swagger_yard/"
30
- end
31
-
32
- def swagger_version
33
- @swagger_version ||= "1.1"
31
+ def config
32
+ @configuration ||= Configuration.new
34
33
  end
35
34
 
36
- def api_version
37
- @api_version ||= "0.1"
38
- end
39
-
40
- def enable
41
- @enable ||= false
42
- end
43
-
44
- def reload
45
- @reload ||= false
46
- end
47
-
48
- def resource_to_file_path
49
- @resource_to_file_path ||= {}
50
- end
51
-
52
- def parse_file(file_path)
53
- ::YARD.parse(file_path)
54
- yard_objects = ::YARD::Registry.all
35
+ #
36
+ # Use YARD to parse object tags from a file
37
+ #
38
+ # @param file_path [string] The complete path to file
39
+ # @return [YARD] objects representing class/methods and tags from the file
40
+ #
41
+ def yard_objects_from_file(file_path)
55
42
  ::YARD::Registry.clear
56
- @parser.run(yard_objects)
57
- end
58
-
59
- def generate!(controller_path)
60
- register_custom_yard_tags!
61
- @controller_path = controller_path
62
- cache.fetch("listing_index") { parse_controllers }
63
- end
64
-
65
- def get_api(resource_name)
66
- if reload
67
- parse_file(resource_to_file_path[resource_name]).to_h
68
- else
69
- cache.fetch(resource_name) { parse_file(resource_to_file_path[resource_name]).to_h }
70
- end
43
+ ::YARD.parse(file_path)
44
+ ::YARD::Registry.all
71
45
  end
72
46
 
73
- def get_listing
74
- if reload
75
- parse_controllers
76
- else
77
- cache.fetch("listing_index") { parse_controllers }
78
- end
47
+ def yard_objects_from_resource(resource_name)
48
+ yard_objects_from_file(resource_to_file_path[resource_name])
79
49
  end
80
50
 
81
- private
82
51
  ##
83
52
  # Register some custom yard tags used by swagger-ui
84
53
  def register_custom_yard_tags!
85
54
  ::YARD::Tags::Library.define_tag("Api resource", :resource)
86
55
  ::YARD::Tags::Library.define_tag("Resource path", :resource_path)
87
- ::YARD::Tags::Library.define_tag("Api path", :path)
88
- ::YARD::Tags::Library.define_tag("Parameter", :parameter)
56
+ ::YARD::Tags::Library.define_tag("Api path", :path, :with_types)
57
+ ::YARD::Tags::Library.define_tag("Parameter", :parameter, :with_types_name_and_default)
89
58
  ::YARD::Tags::Library.define_tag("Parameter list", :parameter_list)
90
59
  ::YARD::Tags::Library.define_tag("Status code", :status_code)
91
60
  ::YARD::Tags::Library.define_tag("Implementation notes", :notes)
92
- ::YARD::Tags::Library.define_tag("Response class", :response_class)
61
+ ::YARD::Tags::Library.define_tag("Response type", :response_type, :with_types)
62
+ ::YARD::Tags::Library.define_tag("Error response message", :error_message, :with_types_and_name)
93
63
  ::YARD::Tags::Library.define_tag("Api Summary", :summary)
94
- end
95
-
96
- def cache
97
- @cache ||= Cache.new(cache_store, cache_prefix)
98
- end
99
-
100
- def parse_controllers
101
- @parser = Parser.new
102
-
103
- Dir[@controller_path].each do |file|
104
- if api_declaration = parse_file(file)
105
- resource_to_file_path[api_declaration.resource_name] = file
106
- cache[api_declaration.resource_name] = api_declaration.to_h
107
- end
108
- end
109
-
110
- @parser.listing.to_h
64
+ ::YARD::Tags::Library.define_tag("Model resource", :model)
65
+ ::YARD::Tags::Library.define_tag("Model property", :property, :with_types_name_and_default)
66
+ ::YARD::Tags::Library.define_tag("Authorization", :authorization, :with_types_and_name)
67
+ ::YARD::Tags::Library.define_tag("Authorization Use", :authorize_with)
111
68
  end
112
69
  end
113
70
  end