File: response.rb

package info (click to toggle)
ruby-oauth2 2.0.9-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 644 kB
  • sloc: ruby: 3,763; makefile: 4; sh: 4
file content (148 lines) | stat: -rw-r--r-- 4,727 bytes parent folder | download
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
141
142
143
144
145
146
147
148
# frozen_string_literal: true

require 'json'
require 'multi_xml'
require 'rack'

module OAuth2
  # OAuth2::Response class
  class Response
    DEFAULT_OPTIONS = {
      parse: :automatic,
      snaky: true,
    }.freeze
    attr_reader :response
    attr_accessor :options

    # Procs that, when called, will parse a response body according
    # to the specified format.
    @@parsers = {
      query: ->(body) { Rack::Utils.parse_query(body) },
      text: ->(body) { body },
    }

    # Content type assignments for various potential HTTP content types.
    @@content_types = {
      'application/x-www-form-urlencoded' => :query,
      'text/plain' => :text,
    }

    # Adds a new content type parser.
    #
    # @param [Symbol] key A descriptive symbol key such as :json or :query.
    # @param [Array] mime_types One or more mime types to which this parser applies.
    # @yield [String] A block returning parsed content.
    def self.register_parser(key, mime_types, &block)
      key = key.to_sym
      @@parsers[key] = block
      Array(mime_types).each do |mime_type|
        @@content_types[mime_type] = key
      end
    end

    # Initializes a Response instance
    #
    # @param [Faraday::Response] response The Faraday response instance
    # @param [Symbol] parse (:automatic) how to parse the response body.  one of :query (for x-www-form-urlencoded),
    #   :json, or :automatic (determined by Content-Type response header)
    # @param [true, false] snaky (true) Convert @parsed to a snake-case,
    #   indifferent-access SnakyHash::StringKeyed, which is a subclass of Hashie::Mash (from hashie gem)?
    # @param [Hash] options all other options for initializing the instance
    def initialize(response, parse: :automatic, snaky: true, **options)
      @response = response
      @options = {
        parse: parse,
        snaky: snaky,
      }.merge(options)
    end

    # The HTTP response headers
    def headers
      response.headers
    end

    # The HTTP response status code
    def status
      response.status
    end

    # The HTTP response body
    def body
      response.body || ''
    end

    # The {#response} {#body} as parsed by {#parser}.
    #
    # @return [Object] As returned by {#parser} if it is #call-able.
    # @return [nil] If the {#parser} is not #call-able.
    def parsed
      return @parsed if defined?(@parsed)

      @parsed =
        if parser.respond_to?(:call)
          case parser.arity
          when 0
            parser.call
          when 1
            parser.call(body)
          else
            parser.call(body, response)
          end
        end

      @parsed = SnakyHash::StringKeyed.new(@parsed) if options[:snaky] && @parsed.is_a?(Hash)

      @parsed
    end

    # Attempts to determine the content type of the response.
    def content_type
      return nil unless response.headers

      ((response.headers.values_at('content-type', 'Content-Type').compact.first || '').split(';').first || '').strip.downcase
    end

    # Determines the parser (a Proc or other Object which responds to #call)
    # that will be passed the {#body} (and optional {#response}) to supply
    # {#parsed}.
    #
    # The parser can be supplied as the +:parse+ option in the form of a Proc
    # (or other Object responding to #call) or a Symbol. In the latter case,
    # the actual parser will be looked up in {@@parsers} by the supplied Symbol.
    #
    # If no +:parse+ option is supplied, the lookup Symbol will be determined
    # by looking up {#content_type} in {@@content_types}.
    #
    # If {#parser} is a Proc, it will be called with no arguments, just
    # {#body}, or {#body} and {#response}, depending on the Proc's arity.
    #
    # @return [Proc, #call] If a parser was found.
    # @return [nil] If no parser was found.
    def parser
      return @parser if defined?(@parser)

      @parser =
        if options[:parse].respond_to?(:call)
          options[:parse]
        elsif options[:parse]
          @@parsers[options[:parse].to_sym]
        end

      @parser ||= @@parsers[@@content_types[content_type]]
    end
  end
end

OAuth2::Response.register_parser(:xml, ['text/xml', 'application/rss+xml', 'application/rdf+xml', 'application/atom+xml', 'application/xml']) do |body|
  next body unless body.respond_to?(:to_str)

  MultiXml.parse(body)
end

OAuth2::Response.register_parser(:json, ['application/json', 'text/javascript', 'application/hal+json', 'application/vnd.collection+json', 'application/vnd.api+json', 'application/problem+json']) do |body|
  next body unless body.respond_to?(:to_str)

  body = body.dup.force_encoding(::Encoding::ASCII_8BIT) if body.respond_to?(:force_encoding)

  ::JSON.parse(body)
end