File: request.rb

package info (click to toggle)
ruby-gitlab 5.1.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,660 kB
  • sloc: ruby: 12,582; makefile: 7; sh: 4
file content (118 lines) | stat: -rw-r--r-- 3,809 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
# frozen_string_literal: true

require 'httparty'
require 'json'

module Gitlab
  # @private
  class Request
    include HTTParty
    format :json
    maintain_method_across_redirects true
    headers 'Accept' => 'application/json', 'Content-Type' => 'application/x-www-form-urlencoded'
    parser(proc { |body, _| parse(body) })

    attr_accessor :private_token, :endpoint, :pat_prefix

    # Converts the response body to an ObjectifiedHash.
    def self.parse(body)
      body = decode(body)

      if body.is_a? Hash
        ObjectifiedHash.new body
      elsif body.is_a? Array
        PaginatedResponse.new(body.collect! { |e| ObjectifiedHash.new(e) })
      elsif body
        true
      elsif !body
        false
      else
        raise Error::Parsing, "Couldn't parse a response body"
      end
    end

    # Decodes a JSON response into Ruby object.
    def self.decode(response)
      response ? JSON.load(response) : {}
    rescue JSON::ParserError
      raise Error::Parsing, 'The response is not a valid JSON'
    end

    %w[get post put patch delete].each do |method|
      define_method method do |path, options = {}|
        params = options.dup

        httparty_config(params)

        unless params[:unauthenticated]
          params[:headers] ||= {}
          params[:headers].merge!(authorization_header)
        end

        retries_left = params[:ratelimit_retries] || 3
        begin
          response = self.class.send(method, endpoint + path, params)
          validate response
        rescue Gitlab::Error::TooManyRequests => e
          retries_left -= 1
          raise e if retries_left.zero?

          wait_time = response.headers['Retry-After'] || 2
          sleep(wait_time.to_i)
          retry
        end
      end
    end

    # Checks the response code for common errors.
    # Returns parsed response for successful requests.
    def validate(response)
      error_klass = Error.klass(response)
      raise error_klass, response if error_klass

      parsed = response.parsed_response
      parsed.client = self if parsed.respond_to?(:client=)
      parsed.parse_headers!(response.headers) if parsed.respond_to?(:parse_headers!)
      parsed
    end

    # Sets a base_uri and default_params for requests.
    # @raise [Error::MissingCredentials] if endpoint not set.
    def request_defaults(sudo = nil)
      raise Error::MissingCredentials, 'Please set an endpoint to API' unless endpoint

      self.class.default_params sudo: sudo
      self.class.default_params.delete(:sudo) if sudo.nil?
    end

    private

    # Returns an Authorization header hash
    #
    # @raise [Error::MissingCredentials] if private_token and auth_token are not set.
    def authorization_header
      raise Error::MissingCredentials, 'Please provide a private_token or auth_token for user' unless private_token

      # The Personal Access Token prefix can be at most 20 characters, and the
      # generated part is of length 20 characters. Personal Access Tokens, thus
      # can have a maximum size of 40 characters. GitLab uses
      # `Doorkeeper::OAuth::Helpers::UniqueToken.generate` for generating
      # OAuth2 tokens, and specified `hex` as token generator method. Thus, the
      # OAuth2 tokens are of length more than 64. If the token length is below
      # that, it is probably a Personal Access Token or CI_JOB_TOKEN.
      if private_token.size >= 64
        { 'Authorization' => "Bearer #{private_token}" }
      elsif private_token.start_with?(pat_prefix.to_s)
        { 'PRIVATE-TOKEN' => private_token }
      else
        { 'JOB-TOKEN' => private_token }
      end
    end

    # Set HTTParty configuration
    # @see https://github.com/jnunemaker/httparty
    def httparty_config(options)
      options.merge!(httparty) if httparty
    end
  end
end