swaggable 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/lib/swaggable.rb +32 -7
  3. data/lib/swaggable/api_validator.rb +42 -0
  4. data/lib/swaggable/check_body_schema.rb +72 -0
  5. data/lib/swaggable/check_expected_parameters.rb +26 -0
  6. data/lib/swaggable/check_mandatory_parameters.rb +26 -0
  7. data/lib/swaggable/check_request_content_type.rb +26 -0
  8. data/lib/swaggable/check_response_code.rb +24 -0
  9. data/lib/swaggable/check_response_content_type.rb +26 -0
  10. data/lib/swaggable/endpoint_definition.rb +30 -2
  11. data/lib/swaggable/endpoint_validator.rb +27 -0
  12. data/lib/swaggable/errors.rb +9 -0
  13. data/lib/swaggable/errors/validations_collection.rb +44 -0
  14. data/lib/swaggable/grape_adapter.rb +2 -2
  15. data/lib/swaggable/mime_type_definition.rb +73 -0
  16. data/lib/swaggable/mime_types_collection.rb +60 -0
  17. data/lib/swaggable/parameter_definition.rb +19 -0
  18. data/lib/swaggable/query_params.rb +50 -0
  19. data/lib/swaggable/rack_request_adapter.rb +88 -0
  20. data/lib/swaggable/rack_response_adapter.rb +49 -0
  21. data/lib/swaggable/swagger_2_serializer.rb +3 -3
  22. data/lib/swaggable/validating_rack_app.rb +30 -0
  23. data/lib/swaggable/version.rb +1 -1
  24. data/spec/{swaggable/integration_spec.rb → integration/dsl_spec.rb} +0 -40
  25. data/spec/integration/rack_app_spec.rb +44 -0
  26. data/spec/integration/validating_rack_app_spec.rb +50 -0
  27. data/spec/spec_helper.rb +9 -5
  28. data/spec/swaggable/api_validator_spec.rb +61 -0
  29. data/spec/swaggable/check_body_schema_spec.rb +94 -0
  30. data/spec/swaggable/check_expected_parameters_spec.rb +110 -0
  31. data/spec/swaggable/check_mandatory_parameters_spec.rb +110 -0
  32. data/spec/swaggable/check_request_content_type_spec.rb +51 -0
  33. data/spec/swaggable/check_response_code_spec.rb +37 -0
  34. data/spec/swaggable/check_response_content_type_spec.rb +51 -0
  35. data/spec/swaggable/endpoint_definition_spec.rb +47 -0
  36. data/spec/swaggable/endpoint_validator_spec.rb +102 -0
  37. data/spec/swaggable/errors/validations_collection_spec.rb +78 -0
  38. data/spec/swaggable/grape_adapter_spec.rb +2 -2
  39. data/spec/swaggable/mime_type_definition_spec.rb +96 -0
  40. data/spec/swaggable/mime_types_collection_spec.rb +83 -0
  41. data/spec/swaggable/parameter_definition_spec.rb +23 -0
  42. data/spec/swaggable/query_params_spec.rb +37 -0
  43. data/spec/swaggable/rack_request_adapter_spec.rb +89 -0
  44. data/spec/swaggable/rack_response_adapter_spec.rb +46 -0
  45. data/spec/swaggable/swagger_2_serializer_spec.rb +4 -2
  46. data/spec/swaggable/swagger_2_validator_spec.rb +2 -2
  47. data/spec/swaggable/validating_rack_app_spec.rb +91 -0
  48. data/swaggable.gemspec +1 -0
  49. metadata +68 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bf18bc08944dd205e9633c7e85a69ac1e0fe9908
4
- data.tar.gz: 10a0e3fb8a88842000cd28b29838609106b7c7a4
3
+ metadata.gz: 808c9b125e0ebb98bf9307f3922375344f073e2d
4
+ data.tar.gz: e50cd856a8362d1f15e7e95cf70c5307b31c264b
5
5
  SHA512:
6
- metadata.gz: d5e0e8fdd32d892ffe06e0b5d801db1d7286a9e67dd64d3edbbc339636b8e3ab652d8cbf5b12ace2e9e9587feb288bbe93dee5089a4e7590939cb79514c68a03
7
- data.tar.gz: 42f21abe51eb383ff37bbdc8c018d19f2246a2f78d59a9b66fb045d9a48c3d6f93283a1e5799c2c0d55aae4fd413179c41e47de9083ed201a2b8c49a60c3ef56
6
+ metadata.gz: eeb0d8a2698ff01a16bd477f7a43d7ad6d52dabb191cd8f366e341bd7ba46cd444fa58bdcaf221962f3fd95174d6d44cff5eff7d67222b0795371f748f9b21d3
7
+ data.tar.gz: 8f5d5d91cb25766ebe780ee784b9a49599439b5eddda1343fe507d22f42972715bd6c91c1c892ea3e3663230335fe99100480f468a1734b0cc374259e72edb79
@@ -1,20 +1,45 @@
1
1
  require 'swaggable/version'
2
2
 
3
3
  module Swaggable
4
+ # Rack
5
+ autoload :RackApp, 'swaggable/rack_app'
6
+ autoload :RackRedirect, 'swaggable/rack_redirect'
7
+ autoload :ValidatingRackApp, 'swaggable/validating_rack_app'
8
+ autoload :RackRequestAdapter, 'swaggable/rack_request_adapter'
9
+ autoload :RackResponseAdapter, 'swaggable/rack_response_adapter'
10
+
11
+ # Definitions
12
+ autoload :DefinitionBase, 'swaggable/definition_base'
4
13
  autoload :ApiDefinition, 'swaggable/api_definition'
5
14
  autoload :EndpointDefinition, 'swaggable/endpoint_definition'
6
- autoload :ParameterDefinition, 'swaggable/parameter_definition'
7
15
  autoload :TagDefinition, 'swaggable/tag_definition'
16
+ autoload :ParameterDefinition, 'swaggable/parameter_definition'
8
17
  autoload :ResponseDefinition, 'swaggable/response_definition'
9
- autoload :RackApp, 'swaggable/rack_app'
18
+ autoload :MimeTypeDefinition, 'swaggable/mime_type_definition'
19
+ autoload :MimeTypesCollection, 'swaggable/mime_types_collection'
20
+ autoload :SchemaDefinition, 'swaggable/schema_definition'
21
+ autoload :AttributeDefinition, 'swaggable/attribute_definition'
22
+
23
+ # Grape
10
24
  autoload :GrapeAdapter, 'swaggable/grape_adapter'
11
25
  autoload :GrapeEntityTranslator, 'swaggable/grape_entity_translator'
26
+
27
+ # Swagger
12
28
  autoload :Swagger2Serializer, 'swaggable/swagger_2_serializer'
13
29
  autoload :Swagger2Validator, 'swaggable/swagger_2_validator'
14
- autoload :EndpointIndex, 'swaggable/endpoint_index'
15
- autoload :DefinitionBase, 'swaggable/definition_base'
30
+
31
+ # Validators
32
+ autoload :ApiValidator, 'swaggable/api_validator'
33
+ autoload :EndpointValidator, 'swaggable/endpoint_validator'
34
+ autoload :CheckRequestContentType, 'swaggable/check_request_content_type'
35
+ autoload :CheckMandatoryParameters, 'swaggable/check_mandatory_parameters'
36
+ autoload :CheckExpectedParameters, 'swaggable/check_expected_parameters'
37
+ autoload :CheckBodySchema, 'swaggable/check_body_schema'
38
+ autoload :CheckResponseContentType, 'swaggable/check_response_content_type'
39
+ autoload :CheckResponseCode, 'swaggable/check_response_code'
40
+
41
+ # Others
42
+ autoload :Errors, 'swaggable/errors'
16
43
  autoload :EnumerableAttributes, 'swaggable/enumerable_attributes'
17
- autoload :SchemaDefinition, 'swaggable/schema_definition'
18
- autoload :AttributeDefinition, 'swaggable/attribute_definition'
19
- autoload :RackRedirect, 'swaggable/rack_redirect'
44
+ autoload :QueryParams, 'swaggable/query_params'
20
45
  end
@@ -0,0 +1,42 @@
1
+ module Swaggable
2
+ class ApiValidator
3
+ attr_accessor(
4
+ :definition,
5
+ :request,
6
+ )
7
+
8
+ def initialize opts = {}
9
+ @definition, @request = opts.values_at(:definition, :request)
10
+ end
11
+
12
+ def errors_for_request
13
+ endpoint_validator.errors_for_request request
14
+ end
15
+
16
+ def errors_for_response response
17
+ endpoint_validator.errors_for_response response
18
+ end
19
+
20
+ def endpoint
21
+ definition.endpoints.detect do |e|
22
+ e.match? *request_signature
23
+ end || raise_endpoint_not_found
24
+ end
25
+
26
+ def endpoint_validator
27
+ @endpoint_validator ||= EndpointValidator.new(endpoint: endpoint)
28
+ end
29
+
30
+ private
31
+
32
+ def raise_endpoint_not_found
33
+ raise Errors::EndpointNotFound.new(
34
+ "No endpoint matched #{request_signature.join(" ")}"
35
+ )
36
+ end
37
+
38
+ def request_signature
39
+ [request['REQUEST_METHOD'], request['PATH_INFO']]
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,72 @@
1
+ module Swaggable
2
+ class CheckBodySchema
3
+ attr_reader :endpoint, :request
4
+
5
+ def initialize args
6
+ @endpoint = args.fetch(:endpoint)
7
+ @request = args.fetch(:request)
8
+ end
9
+
10
+ def self.call(*args)
11
+ new(*args).send :errors
12
+ end
13
+
14
+ private
15
+
16
+ def errors
17
+ Errors::ValidationsCollection.new.tap do |errors|
18
+ errors_for_required_parameters_in_body.each {|e| errors << e }
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def errors_for_required_parameters_in_body
25
+ if body_definition == nil
26
+ []
27
+ elsif !body_definition.required?
28
+ []
29
+ elsif !request.body
30
+ [Errors::Validation.new("Missing body")]
31
+ elsif body_definition.schema.empty?
32
+ []
33
+ else
34
+ missing_attribute_errors + unexpected_attribute_errors
35
+ end
36
+ end
37
+
38
+ def missing_attribute_errors
39
+ [].tap do |errors|
40
+ schema.attributes.select(&:required?).each do |attr|
41
+ unless body.has_key? attr.name
42
+ errors << Errors::Validation.new("Missing body parameter #{attr.inspect}")
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ def unexpected_attribute_errors
49
+ [].tap do |errors|
50
+ body.each do |key, value|
51
+ unless schema.attributes[key]
52
+ errors << Errors::Validation.new("Unexpected body parameter #{key.inspect}")
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ def body_definition
61
+ endpoint.body
62
+ end
63
+
64
+ def schema
65
+ body_definition.schema
66
+ end
67
+
68
+ def body
69
+ request.parsed_body
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,26 @@
1
+ module Swaggable
2
+ class CheckExpectedParameters
3
+ attr_reader :endpoint, :request
4
+
5
+ def initialize args
6
+ @endpoint = args.fetch(:endpoint)
7
+ @request = args.fetch(:request)
8
+ end
9
+
10
+ def self.call(*args)
11
+ new(*args).send :errors
12
+ end
13
+
14
+ private
15
+
16
+ def errors
17
+ Errors::ValidationsCollection.new.tap do |errors|
18
+ request.parameters(endpoint).each do |parameter|
19
+ unless endpoint.parameters.include? parameter
20
+ errors << Errors::Validation.new("Unexpected parameter #{parameter.inspect}")
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ module Swaggable
2
+ class CheckMandatoryParameters
3
+ attr_reader :endpoint, :request
4
+
5
+ def initialize args
6
+ @endpoint = args.fetch(:endpoint)
7
+ @request = args.fetch(:request)
8
+ end
9
+
10
+ def self.call(*args)
11
+ new(*args).send :errors
12
+ end
13
+
14
+ private
15
+
16
+ def errors
17
+ Errors::ValidationsCollection.new.tap do |errors|
18
+ endpoint.parameters.select(&:required).each do |parameter|
19
+ unless request.parameters(endpoint).include? parameter
20
+ errors << Errors::Validation.new("Missing parameter #{parameter.inspect}")
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ module Swaggable
2
+ class CheckRequestContentType
3
+ attr_reader :endpoint, :request
4
+
5
+ def initialize args
6
+ @endpoint = args.fetch(:endpoint)
7
+ @request = args.fetch(:request)
8
+ end
9
+
10
+ def self.call(*args)
11
+ new(*args).send :errors
12
+ end
13
+
14
+ private
15
+
16
+ def errors
17
+ Errors::ValidationsCollection.new.tap do |errors|
18
+ type = request.content_type
19
+
20
+ unless type.nil? || endpoint.consumes.include?(type)
21
+ errors << Errors::UnsupportedContentType.new("Content-Type #{type} not supported")
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,24 @@
1
+ module Swaggable
2
+ class CheckResponseCode
3
+ attr_reader :endpoint, :response
4
+
5
+ def initialize args
6
+ @endpoint = args.fetch(:endpoint)
7
+ @response = args.fetch(:response)
8
+ end
9
+
10
+ def self.call(*args)
11
+ new(*args).send :errors
12
+ end
13
+
14
+ private
15
+
16
+ def errors
17
+ Errors::ValidationsCollection.new.tap do |errors|
18
+ unless endpoint.responses[response.code]
19
+ errors << Errors::Validation.new("Status code #{response.code.inspect} not supported")
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,26 @@
1
+ module Swaggable
2
+ class CheckResponseContentType
3
+ attr_reader :endpoint, :response
4
+
5
+ def initialize args
6
+ @endpoint = args.fetch(:endpoint)
7
+ @response = args.fetch(:response)
8
+ end
9
+
10
+ def self.call(*args)
11
+ new(*args).send :errors
12
+ end
13
+
14
+ private
15
+
16
+ def errors
17
+ Errors::ValidationsCollection.new.tap do |errors|
18
+ type = response.content_type
19
+
20
+ unless type.nil? || endpoint.produces.include?(type)
21
+ errors << Errors::UnsupportedContentType.new("Content-Type #{type} not supported")
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,5 +1,6 @@
1
1
  require 'forwarding_dsl'
2
2
  require 'mini_object'
3
+ require 'addressable/template'
3
4
 
4
5
  module Swaggable
5
6
  class EndpointDefinition
@@ -12,6 +13,11 @@ module Swaggable
12
13
  :summary,
13
14
  )
14
15
 
16
+ def initialize *args, &block
17
+ self.verb = 'GET'
18
+ super *args, &block
19
+ end
20
+
15
21
  def tags
16
22
  @tags ||= MiniObject::IndexedList.new.tap do |l|
17
23
  l.build { TagDefinition.new }
@@ -34,16 +40,38 @@ module Swaggable
34
40
  end
35
41
 
36
42
  def consumes
37
- @consumes ||= Set.new
43
+ @consumes ||= MimeTypesCollection.new
38
44
  end
39
45
 
40
46
  def produces
41
- @produces ||= Set.new
47
+ @produces ||= MimeTypesCollection.new
42
48
  end
43
49
 
44
50
  def configure &block
45
51
  ForwardingDsl.run(self, &block)
46
52
  self
47
53
  end
54
+
55
+ def match? v, p
56
+ v.to_s.upcase == verb.to_s.upcase && !!(path_template.match p)
57
+ end
58
+
59
+ def verb= value
60
+ @verb = value.to_s.upcase
61
+ end
62
+
63
+ def path_parameters_for path
64
+ path_template.extract(path) || {}
65
+ end
66
+
67
+ def body
68
+ parameters.detect {|p| p.location == :body }
69
+ end
70
+
71
+ private
72
+
73
+ def path_template
74
+ Addressable::Template.new path
75
+ end
48
76
  end
49
77
  end
@@ -0,0 +1,27 @@
1
+ module Swaggable
2
+ class EndpointValidator
3
+ attr_accessor(
4
+ :endpoint,
5
+ )
6
+
7
+ def initialize(args)
8
+ @endpoint = args.fetch(:endpoint)
9
+ end
10
+
11
+ def errors_for_request request
12
+ Errors::ValidationsCollection.new.tap do |errors|
13
+ errors.merge! CheckRequestContentType.(endpoint: endpoint, request: request)
14
+ errors.merge! CheckMandatoryParameters.(endpoint: endpoint, request: request)
15
+ errors.merge! CheckExpectedParameters.(endpoint: endpoint, request: request)
16
+ errors.merge! CheckBodySchema.(endpoint: endpoint, request: request)
17
+ end
18
+ end
19
+
20
+ def errors_for_response response
21
+ Errors::ValidationsCollection.new.tap do |errors|
22
+ errors.merge! CheckResponseContentType.(endpoint: endpoint, response: response)
23
+ errors.merge! CheckResponseCode.(endpoint: endpoint, response: response)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,9 @@
1
+ module Swaggable
2
+ module Errors
3
+ autoload :ValidationsCollection, 'swaggable/errors/validations_collection'
4
+
5
+ Validation = Class.new StandardError
6
+ EndpointNotFound = Class.new StandardError
7
+ UnsupportedContentType = Class.new Validation
8
+ end
9
+ end
@@ -0,0 +1,44 @@
1
+ module Swaggable
2
+ module Errors
3
+ class ValidationsCollection < Validation
4
+ include Enumerable
5
+ extend Forwardable
6
+
7
+ def_delegators(
8
+ :@list,
9
+ :last,
10
+ :first,
11
+ :count,
12
+ :empty?,
13
+ )
14
+
15
+ def initialize
16
+ @list = []
17
+ end
18
+
19
+ def << entry
20
+ list << entry
21
+ end
22
+
23
+ def each *args, &block
24
+ list.each(*args, &block)
25
+ end
26
+
27
+ def inspect
28
+ "#<#{self.class.name}: #{message}>"
29
+ end
30
+
31
+ def merge! other
32
+ other.each {|e| self << e }
33
+ end
34
+
35
+ def message
36
+ list.map(&:message).join('. ') + '.'
37
+ end
38
+
39
+ protected
40
+
41
+ attr_reader :list
42
+ end
43
+ end
44
+ end