File: reader.rb

package info (click to toggle)
ruby-json-schema 2.7.0-1
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 748 kB
  • ctags: 656
  • sloc: ruby: 5,380; makefile: 6
file content (140 lines) | stat: -rw-r--r-- 4,537 bytes parent folder | download | duplicates (4)
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