swaggard 2.0.0 → 4.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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +84 -132
  3. data/app/views/swaggard/swagger/index.html.erb +51 -53
  4. data/lib/swaggard/api_definition.rb +42 -28
  5. data/lib/swaggard/configuration.rb +1 -5
  6. data/lib/swaggard/engine.rb +0 -32
  7. data/lib/swaggard/swagger/operation.rb +32 -12
  8. data/lib/swaggard/swagger/parameters/base.rb +7 -6
  9. data/lib/swaggard/swagger/parameters/body.rb +9 -9
  10. data/lib/swaggard/swagger/parameters/form.rb +10 -1
  11. data/lib/swaggard/swagger/parameters/list.rb +6 -8
  12. data/lib/swaggard/swagger/parameters/query.rb +9 -9
  13. data/lib/swaggard/swagger/response.rb +15 -8
  14. data/lib/swaggard/swagger/response_header.rb +1 -1
  15. data/lib/swaggard/swagger/type.rb +2 -2
  16. data/lib/swaggard/version.rb +1 -1
  17. data/lib/swaggard.rb +9 -1
  18. data/spec/fixtures/api.json +1 -1
  19. data/spec/fixtures/dummy/config/application.rb +0 -1
  20. data/spec/fixtures/openapi_3_1_schema.json +1440 -0
  21. data/spec/integration/openapi_spec.rb +28 -0
  22. metadata +8 -43
  23. data/app/assets/fonts/swaggard/DroidSans-Bold.ttf +0 -0
  24. data/app/assets/fonts/swaggard/DroidSans.ttf +0 -0
  25. data/app/assets/javascripts/swaggard/application.js +0 -28
  26. data/app/assets/javascripts/swaggard/lang/ca.js +0 -53
  27. data/app/assets/javascripts/swaggard/lang/el.js +0 -56
  28. data/app/assets/javascripts/swaggard/lang/en.js +0 -56
  29. data/app/assets/javascripts/swaggard/lang/es.js +0 -53
  30. data/app/assets/javascripts/swaggard/lang/fr.js +0 -54
  31. data/app/assets/javascripts/swaggard/lang/geo.js +0 -56
  32. data/app/assets/javascripts/swaggard/lang/it.js +0 -52
  33. data/app/assets/javascripts/swaggard/lang/ja.js +0 -56
  34. data/app/assets/javascripts/swaggard/lang/ko-kr.js +0 -53
  35. data/app/assets/javascripts/swaggard/lang/pl.js +0 -53
  36. data/app/assets/javascripts/swaggard/lang/pt.js +0 -53
  37. data/app/assets/javascripts/swaggard/lang/ru.js +0 -56
  38. data/app/assets/javascripts/swaggard/lang/tr.js +0 -53
  39. data/app/assets/javascripts/swaggard/lang/zh-cn.js +0 -56
  40. data/app/assets/javascripts/swaggard/lib/backbone-min.js +0 -1
  41. data/app/assets/javascripts/swaggard/lib/handlebars-4.0.5.js +0 -3
  42. data/app/assets/javascripts/swaggard/lib/highlight.9.1.0.pack.js +0 -1
  43. data/app/assets/javascripts/swaggard/lib/highlight.9.1.0.pack_extended.js +0 -1
  44. data/app/assets/javascripts/swaggard/lib/jquery-1.8.0.min.js +0 -3
  45. data/app/assets/javascripts/swaggard/lib/jquery.ba-bbq.min.js +0 -1
  46. data/app/assets/javascripts/swaggard/lib/jquery.slideto.min.js +0 -1
  47. data/app/assets/javascripts/swaggard/lib/jquery.wiggle.min.js +0 -1
  48. data/app/assets/javascripts/swaggard/lib/jsoneditor.min.js +0 -5
  49. data/app/assets/javascripts/swaggard/lib/lodash.min.js +0 -2
  50. data/app/assets/javascripts/swaggard/lib/marked.js +0 -1
  51. data/app/assets/javascripts/swaggard/lib/object-assign-pollyfill.js +0 -1
  52. data/app/assets/javascripts/swaggard/lib/swagger-oauth.js +0 -1
  53. data/app/assets/javascripts/swaggard/swaggard.js +0 -72
  54. data/app/assets/javascripts/swaggard/swagger-ui.js +0 -25378
  55. data/app/assets/javascripts/swaggard/translator.js +0 -39
  56. data/app/assets/stylesheets/swaggard/application.css +0 -16
  57. data/app/assets/stylesheets/swaggard/application_print.css +0 -15
  58. data/app/assets/stylesheets/swaggard/print.css.scss +0 -4
  59. data/app/assets/stylesheets/swaggard/reset.css +0 -1
  60. data/app/assets/stylesheets/swaggard/screen.css.scss +0 -4
  61. data/app/assets/stylesheets/swaggard/typography.css.scss +0 -0
@@ -14,14 +14,13 @@ module Swaggard
14
14
  :error_responses, :tag
15
15
 
16
16
  def initialize(yard_object, tag, path, verb, path_params)
17
- @name = yard_object.name
17
+ @name = yard_object.name.to_s
18
18
  @tag = tag
19
19
  @summary = (yard_object.docstring.lines.first || '').chomp
20
20
  @parameters = []
21
21
  @responses = []
22
22
 
23
23
  @description = (yard_object.docstring.lines[1..-1] || []).map(&:chomp).reject(&:empty?).compact.join("\n")
24
- @formats = Swaggard.configuration.api_formats
25
24
  @http_method = verb
26
25
  @path = path
27
26
 
@@ -78,17 +77,26 @@ module Swaggard
78
77
  end
79
78
 
80
79
  def to_doc
81
- {
82
- 'tags' => [@tag.name],
83
- 'operationId' => @operation_id || @name,
84
- 'summary' => @summary,
85
- 'description' => @description,
86
- 'produces' => @formats.map { |format| "application/#{format}" },
87
- }.tap do |doc|
88
- doc['consumes'] = @formats.map { |format| "application/#{format}" } if @body_parameter
89
- doc['parameters'] = @parameters.map(&:to_doc)
90
- doc['responses'] = Hash[@responses.map { |response| [response.status_code, response.to_doc] }]
80
+ regular_params = @parameters.reject { |p| p.is_a?(Parameters::Body) || p.is_a?(Parameters::Form) }
81
+ body_param = @parameters.find { |p| p.is_a?(Parameters::Body) }
82
+ form_params = @parameters.select { |p| p.is_a?(Parameters::Form) }
83
+
84
+ doc = {
85
+ 'tags' => [@tag.name],
86
+ 'operationId' => @operation_id || @name,
87
+ 'summary' => @summary,
88
+ 'description' => @description,
89
+ 'parameters' => regular_params.map(&:to_doc),
90
+ 'responses' => Hash[@responses.map { |response| [response.status_code, response.to_doc] }]
91
+ }
92
+
93
+ if body_param && !body_param.empty?
94
+ doc['requestBody'] = body_param.to_request_body
95
+ elsif form_params.any?
96
+ doc['requestBody'] = build_form_request_body(form_params)
91
97
  end
98
+
99
+ doc
92
100
  end
93
101
 
94
102
  def definitions
@@ -117,6 +125,18 @@ module Swaggard
117
125
  @body_parameter
118
126
  end
119
127
 
128
+ def build_form_request_body(form_params)
129
+ properties = form_params.each_with_object({}) do |param, props|
130
+ props[param.name] = param.form_property_doc
131
+ end
132
+ required = form_params.select(&:is_required?).map(&:name)
133
+
134
+ schema = { 'type' => 'object', 'properties' => properties }
135
+ schema['required'] = required if required.any?
136
+
137
+ { 'content' => { 'application/x-www-form-urlencoded' => { 'schema' => schema } } }
138
+ end
139
+
120
140
  def build_path_parameters(path_params)
121
141
  return unless @tag.controller_class
122
142
 
@@ -7,13 +7,14 @@ module Swaggard
7
7
  attr_writer :is_required
8
8
 
9
9
  def to_doc
10
- {
11
- 'name' => @name,
12
- 'in' => @in,
13
- 'required' => @is_required,
14
- 'type' => @data_type,
15
- 'description' => description
10
+ doc = {
11
+ 'name' => @name,
12
+ 'in' => @in,
13
+ 'required' => @is_required,
14
+ 'description' => description
16
15
  }
16
+ doc['schema'] = { 'type' => @data_type } if @data_type
17
+ doc
17
18
  end
18
19
 
19
20
  end
@@ -25,15 +25,15 @@ module Swaggard
25
25
  @definition.empty?
26
26
  end
27
27
 
28
- def to_doc
29
- doc = super
30
-
31
- doc.delete('type')
32
- doc.delete('description')
33
-
34
- doc['schema'] = { '$ref' => "#/definitions/#{@definition_id}" }
35
-
36
- doc
28
+ def to_request_body
29
+ {
30
+ 'required' => @is_required,
31
+ 'content' => {
32
+ 'application/json' => {
33
+ 'schema' => { '$ref' => "#/components/schemas/#{Swaggard.ref_name(@definition_id)}" }
34
+ }
35
+ }
36
+ }
37
37
  end
38
38
 
39
39
  def description=(description)
@@ -6,10 +6,19 @@ module Swaggard
6
6
  class Form < Base
7
7
 
8
8
  def initialize(string)
9
- @in = 'formData'
10
9
  parse(string)
11
10
  end
12
11
 
12
+ def is_required?
13
+ @is_required
14
+ end
15
+
16
+ def form_property_doc
17
+ doc = { 'type' => @data_type }
18
+ doc['description'] = @description if @description.present?
19
+ doc
20
+ end
21
+
13
22
  private
14
23
 
15
24
  # Example: [Array] status Filter by status. (e.g. status[]=1&status[]=2&status[]=3)
@@ -11,14 +11,12 @@ module Swaggard
11
11
 
12
12
  def to_doc
13
13
  doc = super
14
-
15
- doc.merge(
16
- {
17
- 'type' => 'array',
18
- 'items' => { 'type' => @data_type },
19
- 'enum' => @list_values
20
- }
21
- )
14
+ doc['schema'] = {
15
+ 'type' => 'array',
16
+ 'items' => { 'type' => @data_type },
17
+ 'enum' => @list_values
18
+ }
19
+ doc
22
20
  end
23
21
 
24
22
  private
@@ -11,16 +11,16 @@ module Swaggard
11
11
  end
12
12
 
13
13
  def to_doc
14
- {
15
- 'name' => @name,
16
- 'in' => @in,
17
- 'required' => @is_required,
18
- }.tap do |doc|
19
- doc.merge!(@type.to_doc)
14
+ schema = @type.to_doc
15
+ schema['enum'] = @options if @options.any?
20
16
 
21
- doc.merge!('enum' => @options) if @options.any?
22
- doc.merge!('description' => description)
23
- end
17
+ {
18
+ 'name' => @name,
19
+ 'in' => @in,
20
+ 'required' => @is_required,
21
+ 'description' => description,
22
+ 'schema' => schema
23
+ }
24
24
  end
25
25
 
26
26
  private
@@ -50,13 +50,20 @@ module Swaggard
50
50
  def to_doc
51
51
  { 'description' => description }.tap do |doc|
52
52
  schema = if @response_root.present?
53
- { '$ref' => "#/definitions/#{definition.id}" }
53
+ { '$ref' => "#/components/schemas/#{Swaggard.ref_name(definition.id)}" }
54
54
  elsif @response_model.response_class.present?
55
55
  @response_model.to_doc
56
56
  end
57
57
 
58
- doc.merge!('schema' => schema) if schema
59
- doc.merge!('examples' => example_to_doc) if @example
58
+ if schema || @example
59
+ media_type_object = {}
60
+ media_type_object['schema'] = schema if schema
61
+ media_type_object['example'] = example_to_doc if @example
62
+ doc['content'] = Swaggard.configuration.api_formats.each_with_object({}) do |format, content|
63
+ content["application/#{format}"] = media_type_object
64
+ end
65
+ end
66
+
60
67
  if @headers.any?
61
68
  doc['headers'] = Hash[@headers.map { |header| [header.name, header.to_doc] }]
62
69
  end
@@ -64,10 +71,10 @@ module Swaggard
64
71
  end
65
72
 
66
73
  def example_to_doc
67
- Swaggard.configuration.api_formats.inject({}) do |examples, format|
68
- examples.merge!("application/#{format}" => { '$ref' => @example })
69
-
70
- examples
74
+ if File.exist?(@example)
75
+ JSON.parse(File.read(@example))
76
+ else
77
+ { '$ref' => @example }
71
78
  end
72
79
  end
73
80
 
@@ -100,7 +107,7 @@ module Swaggard
100
107
  if PRIMITIVE_TYPES.include?(@response_class)
101
108
  { 'type' => @response_class }
102
109
  else
103
- { '$ref' => "#/definitions/#@response_class" }
110
+ { '$ref' => "#/components/schemas/#{Swaggard.ref_name(@response_class)}" }
104
111
  end
105
112
  end
106
113
  end
@@ -10,7 +10,7 @@ module Swaggard
10
10
  def to_doc
11
11
  {
12
12
  'description' => @description,
13
- 'type' => @type,
13
+ 'schema' => { 'type' => @type },
14
14
  }
15
15
  end
16
16
 
@@ -46,9 +46,9 @@ module Swaggard
46
46
  if basic_type?
47
47
  BASIC_TYPES[@name.downcase].dup
48
48
  elsif custom_type?
49
- Swaggard.configuration.custom_types[@name].dup
49
+ { '$ref' => "#/components/schemas/#{Swaggard.ref_name(@name)}" }
50
50
  else
51
- { '$ref' => "#/definitions/#{name}" }
51
+ { '$ref' => "#/components/schemas/#{Swaggard.ref_name(name)}" }
52
52
  end
53
53
  end
54
54
  end
@@ -1,3 +1,3 @@
1
1
  module Swaggard
2
- VERSION = '2.0.0'
2
+ VERSION = '4.0.0'
3
3
  end
data/lib/swaggard.rb CHANGED
@@ -41,12 +41,20 @@ module Swaggard
41
41
  ::YARD::Tags::Library.define_tag('Ignore inherited attributes', :ignore_inherited)
42
42
  end
43
43
 
44
+ # Sanitize a name for use as an OpenAPI component key and $ref fragment.
45
+ # Keys must match ^[a-zA-Z0-9\.\-_]+$ per the OAS 3.1 spec.
46
+ def ref_name(name)
47
+ name.to_s.gsub('::', '.').gsub('#', '_')
48
+ end
49
+
44
50
  def get_doc(host = nil)
45
51
  load!
46
52
 
47
53
  doc = @api.to_doc
48
54
 
49
- doc['host'] = host if doc['host'].blank? && host
55
+ if host && configuration.host.blank?
56
+ doc['servers'] = [{ 'url' => "#{configuration.schemes.first}://#{host}#{configuration.api_base_path}" }]
57
+ end
50
58
 
51
59
  doc
52
60
  end
@@ -1 +1 @@
1
- {"swagger":"2.0","info":{"version":"0.1","title":"","description":"","termsOfService":"","contact":{"name":""},"license":{"name":""}},"host":"localhost:3000","basePath":"/","schemes":["https","http"],"consumes":["application/xml","application/json"],"produces":["application/xml","application/json"],"tags":[{"name":"admin/pets","description":"This document describes the API for interacting with Pet resources"},{"name":"pets","description":"This document describes the API for interacting with Pet resources"}],"paths":{"/admin/pets":{"get":{"tags":["admin/pets"],"operationId":"index","summary":"return a list of Pets","description":"","produces":["application/xml","application/json"],"parameters":[],"responses":{"default":{"description":"successful operation"}}}},"/pets":{"get":{"tags":["pets"],"operationId":"index","summary":"return a list of Pets","description":"","produces":["application/xml","application/json"],"parameters":[],"responses":{"default":{"description":"successful operation"}}}},"/pets/{id}":{"get":{"tags":["pets"],"operationId":"show","summary":"return a Pet","description":"","produces":["application/xml","application/json"],"parameters":[{"name":"id","in":"path","required":true,"type":"string","description":"Scope response to id"},{"name":"id","in":"query","required":false,"type":"integer","format":"int32","description":"The ID for the Pet"}],"responses":{"default":{"description":"successful operation"}}}}},"definitions":{}}
1
+ {"openapi":"3.1.0","info":{"version":"0.1","title":"","description":"","contact":{"name":""},"license":{"name":""}},"servers":[{"url":"https://localhost:3000/"}],"tags":[{"name":"admin/pets","description":"This document describes the API for interacting with Pet resources"},{"name":"pets","description":"This document describes the API for interacting with Pet resources"}],"paths":{"/admin/pets":{"get":{"tags":["admin/pets"],"operationId":"index","summary":"return a list of Pets","description":"","parameters":[],"responses":{"default":{"description":"successful operation"}}}},"/pets":{"get":{"tags":["pets"],"operationId":"index","summary":"return a list of Pets","description":"","parameters":[],"responses":{"default":{"description":"successful operation"}}}},"/pets/{id}":{"get":{"tags":["pets"],"operationId":"show","summary":"return a Pet","description":"","parameters":[{"name":"id","in":"path","required":true,"description":"Scope response to id","schema":{"type":"string"}},{"name":"id","in":"query","required":false,"description":"The ID for the Pet","schema":{"type":"integer","format":"int32"}}],"responses":{"default":{"description":"successful operation"}}}}}}
@@ -1,5 +1,4 @@
1
1
  require 'action_controller/railtie'
2
- require 'sprockets/railtie'
3
2
 
4
3
  # Load Swaggard
5
4
  require File.expand_path('../../../../../lib/swaggard', __FILE__)