swagger-core 0.2.0 → 0.2.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.
@@ -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