Skip to content

Commit

Permalink
Merge pull request #284 from ahx/2.1.0
Browse files Browse the repository at this point in the history
2.1.0
  • Loading branch information
ahx committed Jul 18, 2024
2 parents 9a86626 + 63900aa commit 7d4e5d1
Show file tree
Hide file tree
Showing 13 changed files with 102 additions and 36 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

## 2.1.0

- Added `OpenapiFirst::Definition#[]` to access the raw Hash representation of the OAS document. Example: `api['components'].fetch('schemas', 'Stations')`

## 2.0.4

- Fix issue with parsing reponse body when using Rails https://github.com/ahx/openapi_first/issues/281
Expand Down
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
openapi_first (2.0.4)
openapi_first (2.1.0)
hana (~> 1.3)
json_schemer (>= 2.1, < 3.0)
multi_json (~> 1.15)
Expand Down
2 changes: 1 addition & 1 deletion Gemfile.rack2.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
openapi_first (2.0.4)
openapi_first (2.1.0)
hana (~> 1.3)
json_schemer (>= 2.1, < 3.0)
multi_json (~> 1.15)
Expand Down
2 changes: 1 addition & 1 deletion Gemfile.rack30.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
openapi_first (2.0.4)
openapi_first (2.1.0)
hana (~> 1.3)
json_schemer (>= 2.1, < 3.0)
multi_json (~> 1.15)
Expand Down
2 changes: 1 addition & 1 deletion Gemfile.rails6.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
openapi_first (2.0.4)
openapi_first (2.1.0)
hana (~> 1.3)
json_schemer (>= 2.1, < 3.0)
multi_json (~> 1.15)
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: ..
specs:
openapi_first (2.0.4)
openapi_first (2.1.0)
hana (~> 1.3)
json_schemer (>= 2.1, < 3.0)
multi_json (~> 1.15)
Expand Down
10 changes: 10 additions & 0 deletions bin/update
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

`bundle update`
Dir.glob('Gemfile*')
.reject { _1.end_with?('.lock') }
.each do |filename|
puts `BUNDLE_GEMFILE=#{filename} bundle update`
end
puts `cd benchmarks && bundle update && cd ..`
22 changes: 20 additions & 2 deletions lib/openapi_first/definition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,22 @@
require_relative 'request'
require_relative 'response'
require_relative 'builder'
require 'forwardable'

module OpenapiFirst
# Represents an OpenAPI API Description document
# This is returned by OpenapiFirst.load.
class Definition
attr_reader :filepath, :config, :paths, :router
extend Forwardable

# @return [String,nil]
attr_reader :filepath
# @return [Configuration]
attr_reader :config
# @return [Enumerable[String]]
attr_reader :paths
# @return [Router]
attr_reader :router

# @param resolved [Hash] The resolved OpenAPI document.
# @param filepath [String] The file path of the OpenAPI document.
Expand All @@ -20,15 +30,23 @@ def initialize(resolved, filepath = nil)
yield @config if block_given?
@config.freeze
@router = Builder.build_router(resolved, @config)
@resolved = resolved
@paths = resolved['paths'].keys # TODO: Move into builder as well
end

# Gives access to the raw resolved Hash. Like `mydefinition['components'].dig('schemas', 'Stations')`
# @!method [](key)
# @return [Hash]
def_delegators :@resolved, :[]

# Returns an Enumerable of available Routes for this API description.
# @return [Enumerable[Router::Route]]
def routes
@router.routes
end

# Validates the request against the API description.
# @param [Rack::Request] rack_request The Rack request object.
# @param [Rack::Request] request The Rack request object.
# @param [Boolean] raise_error Whether to raise an error if validation fails.
# @return [ValidatedRequest] The validated request object.
def validate_request(request, raise_error: false)
Expand Down
20 changes: 11 additions & 9 deletions lib/openapi_first/errors.rb
Original file line number Diff line number Diff line change
@@ -1,38 +1,40 @@
# frozen_string_literal: true

module OpenapiFirst
# @!visibility private
# Base class for all errors
class Error < StandardError; end
# @!visibility private

# Raised if YAML/JSON file was not found
class FileNotFoundError < Error; end
# @!visibility private

# Raised if response body could not be parsed
class ParseError < Error; end

# @!visibility private
# Raised during request validation if request was invalid
class RequestInvalidError < Error
def initialize(message, validated_request)
super(message)
@request = validated_request
end

# @attr_reader [OpenapiFirst::ValidatedRequest] request The validated request
# @return [ValidatedRequest] The validated request
attr_reader :request
end

# @!visibility private
# Raised during request validation if request was not defined in the API description
class NotFoundError < RequestInvalidError; end

# @!visibility private
# Raised during response validation if request was invalid
class ResponseInvalidError < Error
def initialize(message, validated_response)
super(message)
@response = validated_response
end

# @attr_reader [OpenapiFirst::ValidatedResponse] request The validated response
# @return [ValidatedResponse] The validated response
attr_reader :response
end

# @!visibility private
# Raised during request validation if response was not defined in the API description
class ResponseNotFoundError < ResponseInvalidError; end
end
40 changes: 24 additions & 16 deletions lib/openapi_first/validated_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,46 +15,53 @@ def initialize(original_request, error:, parsed_values: {}, request_definition:
@request_definition = request_definition
end

# @!method error
# @return [Failure, nil] The error that occurred during validation.
# @!method request_definition
# @return [Request, nil]
attr_reader :parsed_values, :error, :request_definition
# A Failure object if the request is invalid
# @return [Failure, nil]
attr_reader :error

# The request definition if this request is defined in the API description
# @return [Request, nil]
attr_reader :request_definition

# Openapi 3 specific
# @!method operation
# @return [Hash] The OpenAPI 3 operation object
# @!method operation_id
# @return [String, nil] The OpenAPI 3 operationId
def_delegators :request_definition, :operation_id, :operation
# @return [String, nil] The OpenAPI 3 operationId
def_delegator :request_definition, :operation_id

# @!method operation
# @return [Hash] The raw OpenAPI 3 operation object
def_delegator :request_definition, :operation

# Parsed path parameters
# @return [Hash] A string keyed hash of path parameters
# @return [Hash<String, anything>]
def parsed_path_parameters
parsed_values[:path]
@parsed_values[:path]
end

# Parsed query parameters. This only returns the query parameters that are defined in the OpenAPI spec.
# @return [Hash<String, anything>]
def parsed_query
parsed_values[:query]
@parsed_values[:query]
end

# Parsed headers. This only returns the query parameters that are defined in the OpenAPI spec.
# @return [Hash<String, anything>]
def parsed_headers
parsed_values[:headers]
@parsed_values[:headers]
end

# Parsed cookies. This only returns the query parameters that are defined in the OpenAPI spec.
# @return [Hash<String, anything>]
def parsed_cookies
parsed_values[:cookies]
@parsed_values[:cookies]
end

# Parsed body. This parses the body according to the content type.
# Note that this returns the hole body, not only the fields that are defined in the OpenAPI spec.
# You can use JSON Schemas `additionalProperties` or `unevaluatedProperties` to
# returns a validation error if the body contains unknown fields.
# @return [Hash<String, anything>]
def parsed_body
parsed_values[:body]
@parsed_values[:body]
end

# Checks if the request is valid.
Expand All @@ -74,6 +81,7 @@ def known?

# Merged path, query, body parameters.
# Here path has the highest precedence, then query, then body.
# @return [Hash<String, anything>]
def parsed_params
@parsed_params ||= parsed_body.merge(parsed_query, parsed_path_parameters)
end
Expand Down
21 changes: 18 additions & 3 deletions lib/openapi_first/validated_response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,32 @@ def initialize(original_response, error:, parsed_values: nil, response_definitio
@response_definition = response_definition
end

attr_reader :parsed_values, :error, :response_definition
# A Failure object if the response is invalid
# @return [Failure, nil]
attr_reader :error

def_delegator :parsed_values, :headers, :parsed_headers
def_delegator :parsed_values, :body, :parsed_body
# The response definition if this response is defined in the API description
# @return [Response, nil]
attr_reader :response_definition

# The parsed headers
# @!method parsed_headers
# @return [Hash<String,anything>]
def_delegator :@parsed_values, :headers, :parsed_headers

# The parsed body
# @!method parsed_body
# @return [Hash<String,anything>]
def_delegator :@parsed_values, :body, :parsed_body

# Checks if the response is valid.
# @return [Boolean] true if the response is valid, false otherwise.
def valid?
error.nil?
end

# Checks if the response is invalid.
# @return [Boolean]
def invalid?
!valid?
end
Expand Down
2 changes: 1 addition & 1 deletion lib/openapi_first/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true

module OpenapiFirst
VERSION = '2.0.4'
VERSION = '2.1.0'
end
9 changes: 9 additions & 0 deletions spec/definition_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@ def build_request(path, method: 'GET')
end
end

describe '#[]' do
it 'gives access to the raw hash' do
definition = OpenapiFirst.load('./spec/data/train-travel-api/openapi.yaml')
expect(definition['webhooks']).to be_a(Hash)
expect(definition['webhooks'].dig('newBooking', 'post', 'operationId')).to eq('new-booking')
expect(definition['components'].dig('schemas', 'Station', 'type')).to eq('object')
end
end

describe '#paths' do
it 'returns all paths' do
definition = OpenapiFirst.load('./spec/data/petstore.yaml')
Expand Down

0 comments on commit 7d4e5d1

Please sign in to comment.