Skip to content

Commit

Permalink
implement JSON schema generation and validation
Browse files Browse the repository at this point in the history
  • Loading branch information
titusfortner committed Mar 30, 2016
1 parent aa6939d commit a195978
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 6 deletions.
2 changes: 2 additions & 0 deletions airborne.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,7 @@ Gem::Specification.new do |s|
s.add_runtime_dependency 'rest-client', '~> 1.7', '>= 1.7.3' # version 1.7.3 fixes security vulnerability https://github.com/brooklynDev/airborne/issues/41
s.add_runtime_dependency 'rack-test', '~> 0.6', '>= 0.6.2'
s.add_runtime_dependency 'activesupport', '>= 3.0.0', '>= 3.0.0'
s.add_runtime_dependency 'json-schema'
s.add_runtime_dependency 'json-schema-generator'
s.add_development_dependency 'webmock', '~> 0'
end
4 changes: 4 additions & 0 deletions lib/airborne.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
config.add_setting :rack_app
config.add_setting :requester_type
config.add_setting :requester_module
config.add_setting :generate_schema, default: false
config.add_setting :schema_path, default: 'schema'
config.add_setting :validate_schema, default: false
config.add_setting :schema_version, default: 'draft4'
config.before do |example|
config.match_expected = example.metadata[:match_expected].nil? ?
Airborne.configuration.match_expected_default? : example.metadata[:match_expected]
Expand Down
8 changes: 7 additions & 1 deletion lib/airborne/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class InvalidJsonError < StandardError; end

include RequestExpectations

attr_reader :response, :headers, :body
attr_reader :response, :headers, :body, :rest_url, :rest_method

def self.configure
RSpec.configure do |config|
Expand All @@ -30,22 +30,27 @@ def self.configuration
end

def get(url, headers = nil)
@rest_method = :get
@response = make_request(:get, url, headers: headers)
end

def post(url, post_body = nil, headers = nil)
@rest_method = :post
@response = make_request(:post, url, body: post_body, headers: headers)
end

def patch(url, patch_body = nil, headers = nil)
@rest_method = :patch
@response = make_request(:patch, url, body: patch_body, headers: headers)
end

def put(url, put_body = nil, headers = nil)
@rest_method = :put
@response = make_request(:put, url, body: put_body, headers: headers)
end

def delete(url, delete_body = nil, headers = nil)
@rest_method = :delete
@response = make_request(:delete, url, body: delete_body, headers: headers)
end

Expand Down Expand Up @@ -76,6 +81,7 @@ def json_body
private

def get_url(url)
@rest_url = url
base = Airborne.configuration.base_url || ''
base + url
end
Expand Down
21 changes: 21 additions & 0 deletions lib/airborne/request_expectations.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require 'rspec'
require 'date'
require 'rack/utils'
require 'json-schema'

module Airborne
class ExpectationError < StandardError; end
Expand Down Expand Up @@ -44,6 +45,26 @@ def expect_header_contains(key, content)
expect_header_impl(key, content, true)
end

def expect_json_schema(path=nil)
path ||= Airborne.configuration.schema_path

file = "#{Dir.pwd}/#{path}#{rest_url}/#{rest_method}.schema"
file = file.tr('?', '/').tr('&', '/').tr('[', '-').tr(']', '')
file = file.gsub(/\/\d*\//, "/ID/").gsub(/\/\d*$/, "/ID").tr('=', '/')

schema = File.read(file)
schema = JSON.parse(schema)
schema = schema['items'] if schema['items']
schema = schema['properties']

expect { JSON::Validator.validate!(schema, json_body) }.not_to raise_error
rescue Errno::ENOENT => ex
warn "Can not verify schema; #{ex.message}"
ensure
json_file = file.gsub('.schema', '.json')
File.delete json_file if File.exist? json_file
end

def optional(hash)
OptionalHashTypeExpectations.new(hash)
end
Expand Down
35 changes: 30 additions & 5 deletions lib/airborne/rest_client_requester.rb
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
require 'rest_client'
require 'json-schema-generator'

module Airborne
module RestClientRequester
def make_request(method, url, options = {})
headers = base_headers.merge(options[:headers] || {})
res = if method == :post || method == :patch || method == :put
if method == :post || method == :patch || method == :put
begin
request_body = options[:body].nil? ? '' : options[:body]
request_body = request_body.to_json if options[:body].is_a?(Hash)
RestClient.send(method, get_url(url), request_body, headers)
res = RestClient.send(method, get_url(url), request_body, headers)
generate_json(method, url, res) if Airborne.configuration.validate_schema
rescue RestClient::Exception => e
e.response
res = e.response
end
else
begin
RestClient.send(method, get_url(url), headers)
res = RestClient.send(method, get_url(url), headers)
generate_json(method, url, res) if Airborne.configuration.validate_schema
rescue RestClient::Exception => e
e.response
res = e.response
end
end
res
Expand All @@ -27,5 +30,27 @@ def make_request(method, url, options = {})
def base_headers
{ content_type: :json }.merge(Airborne.configuration.headers || {})
end

def generate_json(method, url, res)
path ||= Airborne.configuration.schema_path
path = "#{Dir.pwd}/#{path}#{url}"

file = "#{path}/#{method}.json"
file = file.tr('?', '/').tr('&', '/').tr('[', '-').tr(']', '')
file = file.gsub(/\/\d*\//, "/ID/").gsub(/\/\d*$/, "/ID").tr('=', '/')

unless File.exist?(file.gsub('.json', '.schema'))
return unless Airborne.configuration.generate_schema # no schema to compare against
FileUtils.mkdir_p(path)
end

File.open(file, 'w') { |f| f.puts res }

schema_file = file.gsub(".json", ".schema")
return unless File.exist?(schema_file) || Airborne.configuration.generate_schema

schema = JSON::SchemaGenerator.generate file, File.read(file), {:schema_version => Airborne.configuration.schema_version}
File.open(schema_file, 'w') { |f| f.puts schema }
end
end
end

0 comments on commit a195978

Please sign in to comment.