Skip to content

Commit

Permalink
JSON::Schema::Loader
Browse files Browse the repository at this point in the history
  • Loading branch information
pd committed Nov 3, 2014
1 parent 74e021b commit c123bb2
Show file tree
Hide file tree
Showing 6 changed files with 220 additions and 0 deletions.
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ gem "json", :platforms => :mri_18
group :development do
gem "rake"
gem "minitest", '~> 5.0'
gem "pry"
end
1 change: 1 addition & 0 deletions json-schema.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Gem::Specification.new do |s|
s.license = "MIT"
s.required_rubygems_version = ">= 1.8"

s.add_runtime_dependency "addressable"
s.add_development_dependency "webmock"
s.add_runtime_dependency "addressable", '~> 2.3'
end
1 change: 1 addition & 0 deletions lib/json-schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

require 'json-schema/util/array_set'
require 'json-schema/schema'
require 'json-schema/schema/loader'
require 'json-schema/validator'
Dir[File.join(File.dirname(__FILE__), "json-schema/attributes/*.rb")].each {|file| require file }
Dir[File.join(File.dirname(__FILE__), "json-schema/attributes/formats/*.rb")].each {|file| require file }
Expand Down
113 changes: 113 additions & 0 deletions lib/json-schema/schema/loader.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
require 'open-uri'
require 'addressable/uri'
require 'pathname'

module JSON
class Schema
# Raised by {JSON::Schema::Loader} when one of its settings indicate
# a schema should not be loaded.
class LoadRefused < StandardError
# @return [String] the requested schema location which was refused
attr_reader :location

# @return [Symbol] either +:uri+ or +:file+
attr_reader :type

def initialize(location, type)
@location = location
@type = type
super("Load of #{type == :uri ? 'URI' : type} at #{location} refused!")
end
end

# When an unregistered schema is encountered, the {JSON::Schema::Loader} is
# used to fetch its contents and register it with the {JSON::Validator}.
#
# This default loader will load schemas from the filesystem or from a URI.
class Loader
# The behavior of the schema loader can be controlled by providing
# callbacks to determine whether to permit loading referenced schemas.
# The options +accept_uri+ and +accept_file+ should be procs which
# accept a +URI+ or +Pathname+ object, and return a boolean value
# indicating whether to load the referenced schema.
#
# URIs using the +file+ scheme will be normalized into +Pathname+ objects
# and passed to the +accept_file+ callback.
#
# @param options [Hash]
# @option options [Boolean, #call] accept_uri (true)
# @option options [Boolean, #call] accept_file (true)
#
# @example Reject all unregistered schemas
# JSON::Validator.schema_loader = JSON::Schema::Loader.new(
# :accept_uri => false,
# :accept_file => false
# )
#
# @example Only permit URIs from certain hosts
# JSON::Validator.schema_loader = JSON::Schema::Loader.new(
# :accept_file => false,
# :accept_uri => proc { |uri| ['mycompany.com', 'json-schema.org'].include?(uri.host) }
# )
def initialize(options = {})
@accept_uri = options.fetch(:accept_uri, true)
@accept_file = options.fetch(:accept_file, true)
end

# @param location [#to_s] The location from which to load the schema
# @return [JSON::Schema]
# @raise [JSON::Schema::LoadRefused] if +accept_uri+ or +accept_file+
# indicated the schema should not be loaded
# @raise [JSON::ParserError] if the schema was not a valid JSON object
def load(location)
uri = Addressable::URI.parse(location.to_s)
body = if uri.scheme.nil? || uri.scheme == 'file'
uri = Addressable::URI.convert_path(uri.path)
read_file(Pathname.new(uri.path).expand_path)
else
read_uri(uri)
end

JSON::Schema.new(JSON::Validator.parse(body), uri)
end

# @param uri [Addressable::URI]
# @return [Boolean]
def accept_uri?(uri)
if @accept_uri.respond_to?(:call)
@accept_uri.call(uri)
else
@accept_uri
end
end

# @param pathname [Pathname]
# @return [Boolean]
def accept_file?(pathname)
if @accept_file.respond_to?(:call)
@accept_file.call(pathname)
else
@accept_file
end
end

private

def read_uri(uri)
if accept_uri?(uri)
open(uri.to_s).read
else
raise JSON::Schema::LoadRefused.new(uri.to_s, :uri)
end
end

def read_file(pathname)
if accept_file?(pathname)
pathname.read
else
raise JSON::Schema::LoadRefused.new(pathname.to_s, :file)
end
end
end
end
end
18 changes: 18 additions & 0 deletions test/schemas/address_microformat.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"description": "An Address following the convention of http://microformats.org/wiki/hcard",
"type": "object",
"properties": {
"post-office-box": { "type": "string" },
"extended-address": { "type": "string" },
"street-address": { "type": "string" },
"locality":{ "type": "string" },
"region": { "type": "string" },
"postal-code": { "type": "string" },
"country-name": { "type": "string"}
},
"required": ["locality", "region", "country-name"],
"dependencies": {
"post-office-box": "street-address",
"extended-address": "street-address"
}
}
86 changes: 86 additions & 0 deletions test/test_schema_loader.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
require File.expand_path('../test_helper', __FILE__)
require 'webmock'

class TestSchemaLoader < Minitest::Test
ADDRESS_SCHEMA_URI = 'http://json-schema.org/address'
ADDRESS_SCHEMA_PATH = File.expand_path('../schemas/address_microformat.json', __FILE__)

include WebMock::API

def setup
WebMock.enable!
WebMock.disable_net_connect!
end

def teardown
WebMock.reset!
WebMock.disable!
end

def stub_address_request(body = File.read(ADDRESS_SCHEMA_PATH))
stub_request(:get, ADDRESS_SCHEMA_URI).
to_return(:body => body, :status => 200)
end

def test_accept_all_uris
stub_address_request

loader = JSON::Schema::Loader.new
schema = loader.load(ADDRESS_SCHEMA_URI)

assert_equal schema.uri, Addressable::URI.parse("#{ADDRESS_SCHEMA_URI}#")
end

def test_accept_all_files
loader = JSON::Schema::Loader.new
schema = loader.load(ADDRESS_SCHEMA_PATH)

assert_equal schema.uri, Addressable::URI.convert_path(ADDRESS_SCHEMA_PATH + '#')
end

def test_refuse_all_uris
loader = JSON::Schema::Loader.new(:accept_uri => false)
refute loader.accept_uri?(Addressable::URI.parse('http://foo.com'))
end

def test_refuse_all_files
loader = JSON::Schema::Loader.new(:accept_file => false)
refute loader.accept_file?(Pathname.new('/foo/bar/baz'))
end

def test_accept_uri_proc
loader = JSON::Schema::Loader.new(
:accept_uri => proc { |uri| uri.host == 'json-schema.org' }
)

assert loader.accept_uri?(Addressable::URI.parse('http://json-schema.org/address'))
refute loader.accept_uri?(Addressable::URI.parse('http://sub.json-schema.org/address'))
end

def test_accept_file_proc
test_root = Pathname.new(__FILE__).expand_path.dirname

loader = JSON::Schema::Loader.new(
:accept_file => proc { |path| path.to_s.start_with?(test_root.to_s) }
)

assert loader.accept_file?(test_root.join('anything.json'))
refute loader.accept_file?(test_root.join('..', 'anything.json'))
end

def test_file_scheme
loader = JSON::Schema::Loader.new(:accept_uri => true, :accept_file => false)
assert_raises(JSON::Schema::LoadRefused) do
loader.load('file://' + ADDRESS_SCHEMA_PATH)
end
end

def test_parse_error
stub_address_request('this is totally not valid JSON!')

loader = JSON::Schema::Loader.new
assert_raises(JSON::ParserError) do
loader.load(ADDRESS_SCHEMA_URI)
end
end
end

0 comments on commit c123bb2

Please sign in to comment.