1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
|
require 'open-uri'
require 'pathname'
module JSON
class Schema
# Base for any reading exceptions encountered by {JSON::Schema::Reader}
class ReadError < 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(error_message)
end
private
def type_string
type == :uri ? 'URI' : type.to_s
end
end
# Raised by {JSON::Schema::Reader} when one of its settings indicate
# a schema should not be read.
class ReadRefused < ReadError
private
def error_message
"Read of #{type_string} at #{location} refused"
end
end
# Raised by {JSON::Schema::Reader} when an attempt to read a schema fails
class ReadFailed < ReadError
private
def error_message
"Read of #{type_string} at #{location} failed"
end
end
# When an unregistered schema is encountered, the {JSON::Schema::Reader} is
# used to fetch its contents and register it with the {JSON::Validator}.
#
# This default reader will read schemas from the filesystem or from a URI.
class Reader
# The behavior of the schema reader can be controlled by providing
# callbacks to determine whether to permit reading 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 read 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_reader = JSON::Schema::Reader.new(
# :accept_uri => false,
# :accept_file => false
# )
#
# @example Only permit URIs from certain hosts
# JSON::Validator.schema_reader = JSON::Schema::Reader.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 read the schema
# @return [JSON::Schema]
# @raise [JSON::Schema::ReadRefused] if +accept_uri+ or +accept_file+
# indicated the schema could not be read
# @raise [JSON::Schema::ParseError] if the schema was not a valid JSON object
# @raise [JSON::Schema::ReadFailed] if reading the location was acceptable but the
# attempt to retrieve it failed
def read(location)
uri = JSON::Util::URI.parse(location.to_s)
body = if uri.scheme.nil? || uri.scheme == 'file'
uri = JSON::Util::URI.file_uri(uri)
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::ReadRefused.new(uri.to_s, :uri)
end
rescue OpenURI::HTTPError, SocketError
raise JSON::Schema::ReadFailed.new(uri.to_s, :uri)
end
def read_file(pathname)
if accept_file?(pathname)
File.read(JSON::Util::URI.unescaped_path(pathname.to_s))
else
raise JSON::Schema::ReadRefused.new(pathname.to_s, :file)
end
rescue Errno::ENOENT
raise JSON::Schema::ReadFailed.new(pathname.to_s, :file)
end
end
end
end
|