swaggable 0.4.0 → 0.5.1

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