swaggable 0.4.0 → 0.5.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2e59f7b20f75ff1ecfcf7d6b3533163481aad335
4
- data.tar.gz: bf2b29d46c656ab268dab1b577bea82aa00d5ef6
3
+ metadata.gz: 6f8d7d4f89aa6aa7a1e59f301405bc5b59243bc1
4
+ data.tar.gz: ee74ad314bbcfa0d5348a38545524cac1630456a
5
5
  SHA512:
6
- metadata.gz: d38ea707dab9b2ab8040a32ae9c4fd718d3b3e0a7e128c54897659052a629719c779dab658ed6a4259fab6b81bd4ea18937791e77c10364af59ce63bda86b0d7
7
- data.tar.gz: 49008f292dae3193e1bdbc8ec492e775fc633f89587a28a171f6ac56379a391f4a4dd55ea70d4b49c6768e9813dd690238ae779ed74b3fa78c7e078b714a01fc
6
+ metadata.gz: 9333877ec1e0a9f1e0c65cb5c5ecd28005ca454a807ff071747df5a143bb1fdfa8591d9dd82afc48f96fabd45d24232b7de06f4ca3d9d1f3d3869f45945fcad6
7
+ data.tar.gz: e21ae9c163049d6cd6da2e95f9c1d9629b847df05f27b3adf81159e40b398428c47be7cb24b1c81bb7cdd7a21a4602a26b92d83ab636635764b4a09bd9e0276b
data/.gitignore CHANGED
@@ -1,2 +1,5 @@
1
1
  Gemfile.lock
2
2
  .yardoc
3
+ *.gem
4
+ coverage
5
+
@@ -0,0 +1,13 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.2
4
+ - 1.9.3
5
+ - jruby-19mode
6
+ - 2.0.0
7
+ - 2.1.6
8
+ - 2.2.2
9
+ - rbx-2
10
+ cache: bundler
11
+ addons:
12
+ code_climate:
13
+ repo_token: 53088c47bea470645e73f5c6cae91d526ac087add64ed14d8444696eb125f788
data/Gemfile CHANGED
@@ -4,11 +4,17 @@ gemspec
4
4
 
5
5
  group :test, :development do
6
6
  gem 'gem-release'
7
- gem 'rspec'
8
7
  gem 'pry'
9
8
  gem 'rerun'
10
- gem 'grape', '~> 0.11.0'
11
- gem 'rack-test'
12
9
  gem 'yard'
13
10
  gem 'webmock'
14
11
  end
12
+
13
+ group :test do
14
+ gem 'rspec'
15
+ gem 'rack-test'
16
+ gem 'codeclimate-test-reporter'
17
+ gem 'grape', '~> 0.11.0'
18
+ gem 'grape-entity', '~> 0.4.5'
19
+ end
20
+
data/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Swaggable
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/swaggable.svg)](http://badge.fury.io/rb/swaggable)
4
+ [![Build Status](https://travis-ci.org/workshare/swaggable.svg)](https://travis-ci.org/workshare/swaggable)
5
+ [![Code Climate](https://codeclimate.com/github/workshare/swaggable/badges/gpa.svg)](https://codeclimate.com/github/workshare/swaggable)
6
+
3
7
  Flexible swagger documentation generation tool.
4
8
  Allows building a Rack application that
5
9
  serves [Swagger 2](http://swagger.io/) documentation
@@ -16,6 +20,11 @@ api_def = Swaggable::ApiDefinition.from_grape_api(UsersApi)
16
20
  rack_app = Swaggable::RackApp.new(api_definition: api_def)
17
21
  ```
18
22
 
23
+ Mount your rack app as `swagger.json` and you can point Swagger UI to it.
24
+
25
+
26
+ ## Working with Grape
27
+
19
28
  You can import several Grape APIs:
20
29
 
21
30
  ```ruby
@@ -34,18 +43,70 @@ api_def.endpoints['GET /users/{id}'].parameters['filter'].description = 'Allows
34
43
  api_def.endpoints['GET /users/{id}'].responses[403].description = 'Forbidden'
35
44
  ```
36
45
 
46
+ It supports status codes and entities. A more complex Grape example here:
47
+
48
+ ```ruby
49
+ class TeamCreateEntity < Grape::Entity
50
+ def self.name
51
+ 'create team payload'
52
+ end
53
+
54
+ expose :name, documentation: {
55
+ type: 'String',
56
+ desc: 'Name for the Team',
57
+ required: true,
58
+ }
59
+ end
60
+
61
+ class TeamsApi < Grape::API
62
+ format :json
63
+ content_type :json, 'application/json'
64
+ version 'v1.0', using: :path, vendor: :workshare
65
+ prefix "api"
66
+
67
+ route_param :account_uuid do
68
+ resource :teams do
69
+ desc "Creates a new team for such account"
70
+
71
+ params do
72
+ requires :account_uuid, type: String, desc: 'UUID of the account to be associated with the new team'
73
+ end
74
+
75
+ codes = default_codes + [
76
+ [201, 'Created'],
77
+ [422, 'Validations failed'],
78
+ [404, 'Account not found']
79
+ ]
80
+
81
+ post(http_codes: codes, entity: TeamCreateEntity) do
82
+ ## Some action here...
83
+ end
84
+ end
85
+ end
86
+ end
87
+
88
+ api_def = Swaggable::ApiDefinition.from_grape_api(TeamsApi)
89
+ rack_app = Swaggable::RackApp.new(api_definition: api_def)
90
+ ```
91
+
92
+
93
+ ## Validating the resultant Swagger JSON
94
+
37
95
  Validate the results against the corresponding schema in your tests:
38
96
 
39
97
  ```ruby
40
98
  it "validates" do
41
- expect(rack_app.validate!).to be true
99
+ expect(rack_app.validate).to eq []
42
100
  end
43
101
  ```
44
102
 
103
+
104
+ ## Working directly with the DSL
105
+
45
106
  Define the API without Grape:
46
107
 
47
108
  ```ruby
48
- api = Swaggable::ApiDefinition.new do
109
+ Swaggable::ApiDefinition.new do
49
110
  version '1.0'
50
111
  title 'My API'
51
112
  description 'A test API'
@@ -53,9 +114,9 @@ api = Swaggable::ApiDefinition.new do
53
114
 
54
115
  endpoints.add_new do
55
116
  path '/users/{id}'
56
- verb :get
57
- description 'Shows an user'
58
- summary 'Returns the JSON representation of such user'
117
+ verb :put
118
+ description 'Updates an user'
119
+ summary 'Updates attributes of such user'
59
120
 
60
121
  tags.add_new do
61
122
  name 'Users'
@@ -70,6 +131,24 @@ api = Swaggable::ApiDefinition.new do
70
131
  type :boolean # [:string, :number, :integer, :boolean, :array, :file, nil]
71
132
  end
72
133
 
134
+ parameters.add_new do
135
+ name 'user'
136
+ description 'The new attributes for the user'
137
+ location :body
138
+ required true
139
+
140
+ schema do
141
+ name :user
142
+
143
+ attributes do
144
+ add_new do
145
+ name :first_name
146
+ type :string
147
+ end
148
+ end
149
+ end
150
+ end
151
+
73
152
  responses.add_new do
74
153
  status 200
75
154
  description 'Success'
@@ -88,21 +167,17 @@ end
88
167
 
89
168
  ## TODO
90
169
 
91
- * Support response specs.
92
170
  * Document classes.
93
171
  * Include Redirector.
94
- * DSL.
95
- * Swagger validation.
96
172
  * Request & response validations.
97
- * Entities & schemas.
98
-
173
+ * Response body schemas.
99
174
 
100
175
  ## Contributing
101
176
 
102
177
  Do not forget to run the tests with:
103
178
 
104
179
  ```bash
105
- rake
180
+ bundle exec rake
106
181
  ```
107
182
 
108
183
 
@@ -8,7 +8,12 @@ module Swaggable
8
8
  autoload :ResponseDefinition, 'swaggable/response_definition'
9
9
  autoload :RackApp, 'swaggable/rack_app'
10
10
  autoload :GrapeAdapter, 'swaggable/grape_adapter'
11
+ autoload :GrapeEntityTranslator, 'swaggable/grape_entity_translator'
11
12
  autoload :Swagger2Serializer, 'swaggable/swagger_2_serializer'
12
13
  autoload :Swagger2Validator, 'swaggable/swagger_2_validator'
13
14
  autoload :EndpointIndex, 'swaggable/endpoint_index'
15
+ autoload :DefinitionBase, 'swaggable/definition_base'
16
+ autoload :EnumerableAttributes, 'swaggable/enumerable_attributes'
17
+ autoload :SchemaDefinition, 'swaggable/schema_definition'
18
+ autoload :AttributeDefinition, 'swaggable/attribute_definition'
14
19
  end
@@ -26,6 +26,16 @@ module Swaggable
26
26
  def tags
27
27
  (endpoints.map(&:tags).reduce(:merge) || []).dup.freeze
28
28
  end
29
+
30
+ def used_schemas
31
+ endpoints.inject([]) do |acc, endpoint|
32
+ endpoint.parameters.each do |parameter|
33
+ acc << parameter.schema unless parameter.schema.empty?
34
+ end
35
+
36
+ acc.uniq
37
+ end.freeze
38
+ end
29
39
 
30
40
  def self.from_grape_api grape
31
41
  grape_adapter.import(grape, new)
@@ -0,0 +1,72 @@
1
+ module Swaggable
2
+ class AttributeDefinition
3
+ include DefinitionBase
4
+
5
+ getsetter(
6
+ :name,
7
+ :description,
8
+ :required,
9
+ )
10
+
11
+ attr_enum :type, [
12
+ :integer,
13
+ :long,
14
+ :float,
15
+ :double,
16
+ :string,
17
+ :byte,
18
+ :boolean,
19
+ :date,
20
+ :date_time,
21
+ :password,
22
+ ]
23
+
24
+ def json_type
25
+ json_type_hash.fetch(type)
26
+ end
27
+
28
+ def json_format
29
+ json_format_hash.fetch(type)
30
+ end
31
+
32
+ def required?
33
+ !!required
34
+ end
35
+
36
+ def optional?
37
+ !required
38
+ end
39
+
40
+ private
41
+
42
+ def json_type_hash
43
+ {
44
+ integer: :integer,
45
+ long: :integer,
46
+ float: :number,
47
+ double: :number,
48
+ string: :string,
49
+ byte: :string,
50
+ boolean: :boolean,
51
+ date: :string,
52
+ date_time: :string,
53
+ password: :string,
54
+ }
55
+ end
56
+
57
+ def json_format_hash
58
+ {
59
+ integer: :int32,
60
+ long: :int64,
61
+ float: :float,
62
+ double: :double,
63
+ string: nil,
64
+ byte: :byte,
65
+ boolean: nil,
66
+ date: :date,
67
+ date_time: :"date-time",
68
+ password: :password,
69
+ }
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,15 @@
1
+ require 'forwarding_dsl'
2
+
3
+ module Swaggable
4
+ module DefinitionBase
5
+ def self.included klass
6
+ klass.send :include, ForwardingDsl::Getsetter
7
+ klass.send :include, EnumerableAttributes
8
+ end
9
+
10
+ def initialize args = {}, &block
11
+ args.each {|k, v| self.send("#{k}=", v) }
12
+ ForwardingDsl.run(self, &block)
13
+ end
14
+ end
15
+ end
@@ -3,7 +3,7 @@ require 'mini_object'
3
3
 
4
4
  module Swaggable
5
5
  class EndpointDefinition
6
- include ForwardingDsl::Getsetter
6
+ include DefinitionBase
7
7
 
8
8
  getsetter(
9
9
  :path,
@@ -12,11 +12,6 @@ module Swaggable
12
12
  :summary,
13
13
  )
14
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
15
  def tags
21
16
  @tags ||= MiniObject::IndexedList.new.tap do |l|
22
17
  l.build { TagDefinition.new }
@@ -0,0 +1,29 @@
1
+ require 'forwarding_dsl'
2
+
3
+ module Swaggable
4
+ module EnumerableAttributes
5
+ def self.included klass
6
+ klass.send :include, ForwardingDsl::Getsetter
7
+ klass.send :extend, ClassMethods
8
+ end
9
+
10
+ module ClassMethods
11
+ def attr_enum name, choices
12
+ getsetter name
13
+
14
+ class_variable_set :"@@valid_#{name}_list", choices
15
+
16
+ define_method "#{name}=" do |value|
17
+ valid_values = self.class.class_variable_get :"@@valid_#{name}_list"
18
+
19
+ unless valid_values.include? value
20
+ raise ArgumentError.new("#{value.inspect} is not one a valid #{name}: #{valid_values.map(&:inspect).join(", ")}")
21
+ end
22
+
23
+ instance_variable_set(:"@#{name}", value)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+
@@ -73,6 +73,10 @@ module Swaggable
73
73
  api_endpoint.parameters << parameter_from(name, options, grape_endpoint)
74
74
  end
75
75
 
76
+ if entity = grape_endpoint.route_entity
77
+ api_endpoint.parameters << GrapeEntityTranslator.parameter_from(entity)
78
+ end
79
+
76
80
  (grape_endpoint.route_http_codes || []).each do |status, desc, entity|
77
81
  api_endpoint.responses.add_new do |r|
78
82
  r.status = status
@@ -0,0 +1,38 @@
1
+ module Swaggable
2
+ module GrapeEntityTranslator
3
+ def self.parameter_from entity
4
+ ParameterDefinition.new do
5
+ location :body
6
+ name entity.name
7
+ schema.name entity.name
8
+
9
+ entity.exposures.each do |name, opts|
10
+ schema.attributes.add_new do
11
+ this.name name
12
+ type type_from_options(opts)
13
+ description description_from_options(opts)
14
+ required required_from_options(opts)
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def self.type_from_options opts
23
+ documentation = opts[:documentation] || {}
24
+ type = documentation[:type] || 'string'
25
+ type.downcase.to_sym
26
+ end
27
+
28
+ def self.description_from_options opts
29
+ documentation = opts[:documentation] || {}
30
+ documentation[:desc]
31
+ end
32
+
33
+ def self.required_from_options opts
34
+ documentation = opts[:documentation] || {}
35
+ documentation[:required]
36
+ end
37
+ end
38
+ end
@@ -2,49 +2,34 @@ require 'mini_object'
2
2
 
3
3
  module Swaggable
4
4
  class ParameterDefinition
5
- include ForwardingDsl::Getsetter
5
+ include DefinitionBase
6
6
 
7
7
  getsetter(
8
8
  :name,
9
9
  :description,
10
- :location,
11
10
  :required,
12
11
  :type,
12
+ :schema,
13
13
  )
14
14
 
15
- def initialize args = {}
16
- args.each {|k, v| self.send("#{k}=", v) }
17
- yield self if block_given?
18
- end
15
+ attr_enum :location, [:path, :query, :header, :body, :form, nil]
16
+ attr_enum :type, [:string, :number, :integer, :boolean, :array, :file, nil]
19
17
 
20
18
  def required?
21
19
  !!required
22
20
  end
23
21
 
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
22
+ def schema &block
23
+ ForwardingDsl.run(
24
+ @schema ||= build_schema,
25
+ &block
26
+ )
38
27
  end
39
28
 
40
29
  private
41
30
 
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]
31
+ def build_schema
32
+ SchemaDefinition.new
48
33
  end
49
34
  end
50
35
  end