swagger_yard 0.0.5 → 0.1.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.
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