swagger-core 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,9 +1,10 @@
1
1
  module Swagger
2
+ # Class representing a URI Template. Backed by Addressable::Template.
3
+ # @see http://tools.ietf.org/html/rfc6570
2
4
  class URITemplate < String
3
5
  attr_reader :uri_template
4
6
  def initialize(string)
5
- # FIXME: Is it possible to initialize with heuristic parse once?
6
- @uri_template = Addressable::Template.new string
7
+ @uri_template = Addressable::Template.new(string)
7
8
  super
8
9
  end
9
10
  end
@@ -0,0 +1,89 @@
1
+ require 'swagger/swagger_object'
2
+ require 'swagger/v2/info'
3
+ require 'swagger/v2/path'
4
+ require 'swagger/v2/tag'
5
+ require 'json-schema'
6
+
7
+ module Swagger
8
+ # Module containing classes that handle version 2 of the Swagger specification.
9
+ # @see https://github.com/wordnik/swagger-spec/blob/master/versions/2.0.md Swagger Specification 2.0
10
+ module V2
11
+ SWAGGER_SCHEMA = File.expand_path 'schemas/swagger/v2.0/schema.json', Swagger::RESOURCES_DIR
12
+ JSON_SCHEMA = File.expand_path 'schemas/json_schema/draft-04.json', Swagger::RESOURCES_DIR
13
+
14
+ # Class representing the top level "Swagger Object"
15
+ # @see https://github.com/wordnik/swagger-spec/blob/master/versions/2.0.md#swagger-object- Swagger Object
16
+ class API < Swagger::API
17
+ # @group Swagger Fields
18
+ # HACK: Swagger-Spec: This is correct for now, but schema will likely be changed to String
19
+ required_field :swagger, Float
20
+ required_field :info, Info
21
+ field :host, Swagger::URITemplate
22
+ field :basePath, Swagger::URITemplate
23
+ field :schemes, Array[String]
24
+ field :consumes, Array[String]
25
+ field :produces, Array[String]
26
+ required_field :paths, Hash[String => Path]
27
+ field :definitions, Hash[String => Schema]
28
+ field :parameters, Hash[String => Parameter]
29
+ field :responses, Hash[String => Response]
30
+ # HACK: Swagger-Spec: Not documented/defined yet. Implement once spec is complete.
31
+ field :security, Object
32
+ # TODO: This is actually an array of tag names, not Tag objects, need to handle relation
33
+ field :tag, Array[String]
34
+ # TODO: externalDocs - Documentable Module
35
+ # @endgroup
36
+
37
+ alias_method :base_path, :basePath
38
+
39
+ # All operations under all paths
40
+ # @return [Array<Operation>]
41
+ def operations
42
+ # Perhaps not the best way...
43
+ paths.values.map do | path |
44
+ path.operations.values
45
+ end.flatten
46
+ end
47
+
48
+ # A complete (including host) URI Template for the basePath.
49
+ # @return [Swagger::URITemplate]
50
+ def uri_template
51
+ Swagger::URITemplate.new("#{host}#{basePath}")
52
+ end
53
+
54
+ # Validates this object against the Swagger specification and returns all detected errors.
55
+ # Slower than {#validate}.
56
+ # @return [true] if the object fully complies with the Swagger specification.
57
+ # @raise [Swagger::InvalidDefinition] if any errors are found.
58
+ def fully_validate
59
+ # NOTE: fully_validate is ideal, but very slow with the current schema/validator
60
+ errors = JSON::Validator.fully_validate(swagger_schema, to_json)
61
+ fail Swagger::InvalidDefinition, errors unless errors.empty?
62
+ true
63
+ end
64
+
65
+ # Validates this object against the Swagger specification and returns the first detected error.
66
+ # Faster than {#fully_validate}.
67
+ # @return [true] if the object fully complies with the Swagger specification.
68
+ # @raise [Swagger::InvalidDefinition] if an error is found.
69
+ def validate
70
+ JSON::Validator.validate!(swagger_schema, to_json)
71
+ rescue JSON::Schema::ValidationError => e
72
+ raise Swagger::InvalidDefinition, e.message
73
+ end
74
+
75
+ private
76
+
77
+ def swagger_schema
78
+ @swagger_schema ||= JSON.parse(File.read(SWAGGER_SCHEMA))
79
+
80
+ # FIXME: Swagger should be able to parse offline. Blocked by json-schema.
81
+ # Offline workaround
82
+ # @swagger_schema = JSON.parse(File.read(SWAGGER_SCHEMA)
83
+ # .gsub('http://json-schema.org/draft-04/schema', "file://#{SWAGGER_SCHEMA}"))
84
+ # @swagger_schema['$schema'] = 'http://json-schema.org/draft-04/schema#'
85
+ # @swagger_schema
86
+ end
87
+ end
88
+ end
89
+ end
@@ -1,19 +1,27 @@
1
1
  module Swagger
2
2
  module V2
3
+ # A class to represent example objects in the Swagger schema.
4
+ # Usually used to represent example request or responses.
5
+ # Provides access to both the raw example or a parsed representation.
3
6
  class Example
4
7
  extend Forwardable
5
8
  def_delegator :@raw, :to_s, :inspect
6
9
 
10
+ # The example as it appears in the Swagger document.
11
+ # @return Object the example
7
12
  attr_reader :raw
8
13
 
9
14
  def initialize(sample)
10
15
  @raw = sample
11
16
  end
12
17
 
13
- def parse(_format = :json)
18
+ # The example after it has been parsed to match the +media_type+.
19
+ # @param media_type [String] the target media_type
20
+ # @return [Object] an object according to the +media_type+
21
+ def parse(media_type = 'application/json')
14
22
  return @raw unless @raw.is_a? String
15
-
16
- JSON.parse(@raw)
23
+ parser = Swagger::MIMEType.parser_for(media_type)
24
+ parser.parse(@raw)
17
25
  end
18
26
 
19
27
  def inspect
@@ -1,23 +1,33 @@
1
+ require 'swagger/swagger_object'
2
+
1
3
  module Swagger
2
4
  module V2
3
- class Info < DefinitionSection
4
- class Contact < DefinitionSection
5
- section :name, String
6
- section :url, Swagger::URI
7
- section :email, String
5
+ # Class representing a Swagger "Info Object".
6
+ # @see https://github.com/wordnik/swagger-spec/blob/master/versions/2.0.md#infoObject Info Object
7
+ class Info < SwaggerObject
8
+ # Class representing a Swagger "Contact Object".
9
+ # @see https://github.com/wordnik/swagger-spec/blob/master/versions/2.0.md#contactObject Contact Object
10
+ class Contact < SwaggerObject
11
+ # @group Swagger Fields
12
+ field :name, String
13
+ field :url, Swagger::URI
14
+ field :email, String
15
+ # @endgroup
8
16
  end
9
17
 
10
- class License < DefinitionSection
11
- required_section :name, String
12
- section :url, Swagger::URI
18
+ # Class representing a Swagger "License Object".
19
+ # @see https://github.com/wordnik/swagger-spec/blob/master/versions/2.0.md#licenseObject License Object
20
+ class License < SwaggerObject
21
+ required_field :name, String
22
+ field :url, Swagger::URI
13
23
  end
14
24
 
15
- required_section :version, String
16
- required_section :title, String
17
- section :description, String
18
- section :termsOfService, String
19
- section :contact, Contact
20
- section :license, License
25
+ required_field :title, String
26
+ field :description, String
27
+ field :termsOfService, String
28
+ field :contact, Contact
29
+ field :license, License
30
+ required_field :version, String
21
31
  end
22
32
  end
23
33
  end
@@ -0,0 +1,52 @@
1
+ require 'swagger/swagger_object'
2
+ require 'swagger/v2/parameter'
3
+ require 'swagger/v2/response'
4
+
5
+ module Swagger
6
+ module V2
7
+ # Class representing a Swagger "Operation Object".
8
+ # @see https://github.com/wordnik/swagger-spec/blob/master/versions/2.0.md#operationObject Operation Object
9
+ class Operation < SwaggerObject
10
+ extend Forwardable
11
+ def_delegators :parent, :uri_template, :path, :host
12
+
13
+ # required_field :verb, Symbol
14
+ field :summary, String
15
+ field :description, String
16
+ field :operationId, String
17
+ alias_method :operation_id, :operationId
18
+ field :produces, Array[String]
19
+ field :consumes, Array[String]
20
+ field :tags, Array[String]
21
+ field :parameters, Array[Parameter]
22
+ field :responses, Hash[String => Response]
23
+ field :schemes, Array[String]
24
+
25
+ # TODO: Add externalDocs
26
+
27
+ def api_title
28
+ root.info.title
29
+ end
30
+
31
+ def full_name
32
+ "#{api_title} - #{summary}"
33
+ end
34
+
35
+ def verb
36
+ parent.operations.key self
37
+ end
38
+
39
+ def signature
40
+ "#{verb.to_s.upcase} #{parent.uri_template}"
41
+ end
42
+
43
+ def default_response
44
+ return nil if responses.values.nil?
45
+
46
+ # FIXME: Swagger isn't very clear on "normal response codes"
47
+ # In the examples, default is actually an error
48
+ responses['200'] || responses['201'] || responses['default'] || responses.values.first
49
+ end
50
+ end
51
+ end
52
+ end
@@ -1,6 +1,45 @@
1
+ require 'swagger/swagger_object'
2
+
1
3
  module Swagger
2
4
  module V2
3
- class Parameter < Hashie::Mash
5
+ # Class representing a Swagger "Parameter Object".
6
+ # @see https://github.com/wordnik/swagger-spec/blob/master/versions/2.0.md#parameterObject Parameter Object
7
+ class Parameter < SwaggerObject
8
+ # @!group Fixed Fields
9
+ required_field :name, String
10
+ # required_field :in, String
11
+ field :in, String
12
+ field :description, String
13
+ field :required, Object # FIXME: Should be a boolean
14
+ # @!endgroup
15
+
16
+ # @!group Body Fields
17
+ field :schema, Schema
18
+ # @!endgroup
19
+
20
+ # @!group Non-Body Fields
21
+ field :type, String
22
+ field :format, String
23
+ field :items, Hash # TODO: Items Object
24
+ field :collectionFormat, String
25
+ field :default, Object
26
+ # @!endgroup
27
+
28
+ # @!group Deterministic JSON Schema
29
+ field :default, Object
30
+ field :maximum, Numeric
31
+ field :exclusiveMaximum, String # FIXME: boolean
32
+ field :minimum, Numeric
33
+ field :exclusiveMinimum, String # FIXME: boolean
34
+ field :maxLength, Integer
35
+ field :minLength, Integer
36
+ field :pattern, String
37
+ field :maxItems, Integer
38
+ field :minItems, Integer
39
+ field :uniqueItems, String # FIXME: boolean
40
+ field :enum, Array[Object]
41
+ field :multipleOf, Numeric
42
+ # @!endgroup
4
43
  end
5
44
  end
6
45
  end
@@ -1,18 +1,19 @@
1
- require 'swagger/v2/api_operation'
1
+ require 'swagger/swagger_object'
2
+ require 'swagger/v2/operation'
2
3
 
3
4
  module Swagger
4
5
  module V2
5
- class Path < DefinitionSection
6
+ # Class representing a Swagger "Path Item Object".
7
+ # @see https://github.com/wordnik/swagger-spec/blob/master/versions/2.0.md#pathItemObject Path Item Object
8
+ class Path < SwaggerObject
6
9
  extend Forwardable
7
10
  def_delegator :parent, :host
8
11
 
9
12
  VERBS = [:get, :put, :post, :delete, :options, :head, :patch]
10
-
11
- section :parameters, Array[Parameter]
12
-
13
13
  VERBS.each do | verb |
14
- section verb, APIOperation
14
+ field verb, Operation
15
15
  end
16
+ field :parameters, Array[Parameter]
16
17
 
17
18
  def initialize(hash)
18
19
  hash[:parameters] ||= []
@@ -1,14 +1,18 @@
1
+ require 'swagger/swagger_object'
1
2
  require 'swagger/v2/example'
2
3
 
3
4
  module Swagger
4
5
  module V2
5
- class Response < DefinitionSection
6
- section :description, String
7
- section :schema, Swagger::Schema
8
- section :headers, Array # [String => String] # TODO: Headers
9
- section :examples, Hash[Swagger::MimeType => Example]
6
+ # Class representing a Swagger "Response Object".
7
+ # @see https://github.com/wordnik/swagger-spec/blob/master/versions/2.0.md#responseObject Response Object
8
+ class Response < SwaggerObject
9
+ field :description, String
10
+ field :schema, Swagger::Schema
11
+ field :headers, Array # [String => String] # TODO: Headers
12
+ field :examples, Hash[Swagger::MimeType => Example]
10
13
 
11
14
  def status_code
15
+ # FIXME: swagger-spec needs a defined way to define codes
12
16
  code = parent.responses.key self
13
17
  code = '200' if code == 'default'
14
18
  code.to_i
@@ -0,0 +1,11 @@
1
+ module Swagger
2
+ module V2
3
+ # Class representing a Swagger "Tag Object".
4
+ # @see https://github.com/wordnik/swagger-spec/blob/master/versions/2.0.md#tagObject Tag Object
5
+ class Tag < SwaggerObject
6
+ required_field :name, String
7
+ field :description, String
8
+ # TODO: Documentable
9
+ end
10
+ end
11
+ end
@@ -1,3 +1,3 @@
1
1
  module Swagger
2
- VERSION = '0.2.0'
2
+ VERSION = '0.2.1'
3
3
  end
@@ -62,7 +62,7 @@
62
62
  "^x-": {
63
63
  "$ref": "#/definitions/vendorExtension"
64
64
  },
65
- "^/.*[^\/]$": {
65
+ "^\/$|^\/.*[^\/]$": {
66
66
  "$ref": "#/definitions/pathItem"
67
67
  }
68
68
  },
@@ -523,7 +523,7 @@
523
523
  "^x-": {
524
524
  "$ref": "#/definitions/vendorExtension"
525
525
  },
526
- "^/.*[^\/]$": {
526
+ "^\/$|^\/.*[^\/]$": {
527
527
  "type": "string"
528
528
  }
529
529
  }
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  module Swagger
4
4
  module V2
5
- describe APIDeclaration do
5
+ describe API do
6
6
  let(:swagger_file) { 'spec/fixtures/petstore-full.yaml' }
7
7
  let(:swagger) { Swagger.load swagger_file }
8
8
  let(:expected_host) { 'petstore.swagger.wordnik.com' }
@@ -0,0 +1,52 @@
1
+ require 'spec_helper'
2
+
3
+ module Swagger
4
+ describe Builder do
5
+ subject(:builder) { described_class.builder }
6
+ describe 'setting fields' do
7
+ it 'raises an error if the field does not exist' do
8
+ expect { builder.xxx = 'foo' }.to raise_error(NoMethodError)
9
+ end
10
+
11
+ it 'raises if the value cannot be coerced to match the field' do
12
+ expect { builder.swagger = :foo }.to raise_error(Hashie::CoercionError)
13
+ end
14
+
15
+ it 'sets the field' do
16
+ expect { builder.swagger = 2.0 }.to change { builder.swagger }.from(nil).to(2.0)
17
+ end
18
+
19
+ it 'allows complex types to be set via a block' do
20
+ builder.info do |info|
21
+ info.title = 'Sample Swagger API'
22
+ info.version = '1.0'
23
+ end
24
+
25
+ expect(builder.info).to be_an_kind_of(Swagger::Bash)
26
+ expect(builder.info.title).to eq('Sample Swagger API')
27
+ end
28
+ end
29
+
30
+ describe '#build' do
31
+ it 'raises an error if a required field is omitted' do
32
+ expect { builder.build }.to raise_error(ArgumentError, /property '\w+' is required/)
33
+ end
34
+
35
+ it 'returns the built Swagger API object if all required fields are present' do
36
+ builder.swagger = 2.0
37
+ builder.info do |info|
38
+ info.title = 'Sample Swagger API'
39
+ info.version = '1.0'
40
+ end
41
+ builder.paths = {
42
+ '/foo' => {}
43
+ }
44
+ builder.paths['/foo'].get do |get|
45
+ get.description = 'Testing...'
46
+ get.tags = %w(foo bar)
47
+ end
48
+ expect(builder.build).to be_an_instance_of(Swagger::V2::API)
49
+ end
50
+ end
51
+ end
52
+ end
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  module Swagger
4
4
  module V2
5
- describe APIOperation do
5
+ describe Operation do
6
6
  context 'Sample petstore API' do
7
7
  let(:swagger_file) { 'spec/fixtures/petstore-full.yaml' }
8
8
  let(:swagger) { Swagger.load swagger_file }
@@ -85,6 +85,14 @@ module Swagger
85
85
  expect(subject.description).to eq('pet response')
86
86
  end
87
87
  end
88
+
89
+ describe '#signature' do
90
+ subject { swagger.paths['/pets'].get }
91
+ it 'is a signature similar to what would appear in logs or HTTP dumps' do
92
+ # FIXME: It's a bit weird to have host but not scheme... but Swagger allows multiple schemes
93
+ expect(subject.signature).to eq('GET petstore.swagger.wordnik.com/api/pets')
94
+ end
95
+ end
88
96
  end
89
97
  end
90
98
  end