Torihaji's Growth Diary

Little by little, no hurry.

Ruby で access_tokenも含めて、Google Drive APIとSheet APIを触り尽くす - 1章

はじめに

みなさん、こんちは torihaziです。

最近、RubyGoogle API(google drive APIgoogle sheet api)を触っているのですが

あんまりドキュメントなくて困ったのでこれを機にまとめてみようかと

追記 まだまとまっていないです。1章ではなぜ drive.authorizationには何を設定できるのか

そこを詳しく突き詰めるとこまでと list_filesが実際には何をしているかというところまでです。

使用するgemというか大元

github.com

The client gems are named according to the pattern google-apis-<servicename>_<serviceversion>. For example, the client for the Google Drive V3 API is google-apis-drive_v3.

gemに命名規則があるそうなので調べてみましょう。

右上の Search に "drive"と入力してenter

左下に "google-apis-drive_v3"とかありますね。多分これです。

それか gemの一覧とかで調べられないのかな?

ruby でほしいgemがあるか調べたい! - Torihaji's Growth Diary

ということで "google-apis-drive_v3"を入れる。


入れました。

Installing faraday-follow_redirects 0.4.0
Installing google-cloud-env 2.3.1
Installing representable 3.2.0
Installing addressable 2.8.7
Fetching signet 0.21.0
Installing signet 0.21.0
Fetching googleauth 1.16.0
Installing googleauth 1.16.0
Fetching google-apis-core 1.0.2
Installing google-apis-core 1.0.2
Fetching google-apis-drive_v3 0.73.0
Installing google-apis-drive_v3 0.73.0

色々依存で入った。

  • faraday-follow_redirects 0.4.0 HTTPリクエストライブラリ「Faraday」の拡張 リダイレクト(301/302など)を自動で追従
  • google-cloud-env 2.3.1 Google Cloud環境の検出と設定 実行環境(GCP、ローカルなど)の判定と環境変数の取得
  • representable 3.2.0 オブジェクトのシリアライズ/デシリアライズ JSON/XMLなどの変換を定義可能にする
  • addressable 2.8.7 URI/URLの処理 パース、正規化、エンコード/デコード
  • signet 0.21.0 OAuth署名の実装 Google APIの認証で使用
  • googleauth 1.16.0 Google API認証 OAuth 2.0、サービスアカウント、アプリケーション認証情報の処理
  • google-apis-core 1.0.2 Google APIクライアントの共通基盤 HTTP通信、エラーハンドリング、リトライなどの共通機能
  • google-apis-drive_v3 0.73.0 Google Drive API v3のクライアント ファイルの取得・作成・更新・削除など

やっていき

では使っていきましょう。

それぞれがなんで必要かは使い慣れてからで。

さぁ rails cで色々触っていきましょう。

irb との違いは 以下。irbrubyの純粋な対話型実行環境、rails cはRailsの機能を使える対話型実行環境

IRBとRails Consoleの違いを理解しよう

で以下とあるので使ってみましょう。

Then, to use it, require the file and instantiate the service. For example to use the Drive API:

app(dev)> client = Google::Apis::DriveV3::DriveService.new
=>
#<Google::Apis::DriveV3::DriveService:0x0000ffffa1f83670

はいできました。

authorizationが何か知りませんが一旦やってみましょう。

files = client.list_files(q: "title contains 'finances'")
app(dev)* files.items.each do |file|
app(dev)*   puts file.title
app(dev)> end
Sending HTTP get https://www.googleapis.com/drive/v3/files?q=title%20contains%20%27finances%27
403

実際にはHTTPリクエストを飛ばす上で、クエリを構築して送ってるらしい。

HTTPステータスコード403 Forbiddenは、リクエストされたページにアクセスする権限がないために、サーバーがリクエストを拒否した状態

{
  "error": {
    "code": 403,
    "message": "Method doesn't allow unregistered callers (callers without established identity). Please use API Key or other form of API consumer identity to call this API.",
    "errors": [
      {
        "message": "Method doesn't allow unregistered callers (callers without established identity). Please use API Key or other form of API consumer identity to call this API.",
        "domain": "global",
        "reason": "forbidden"
      }
    ],
    "status": "PERMISSION_DENIED"
  }
}

claudeに聞いてみると

Google Drive APIへのリクエストに認証情報(APIキーまたはOAuthトークン)が含まれていない

まぁそうかと。

ではその認証情報をどうセットするのか。

drive.authorization = ... # See Googleauth or Signet libraries

これっぽい。

何をしているのか。元をのぞいてみる。

探求の旅

https://github.com/googleapis/google-api-ruby-client/blob/6220f97dbc9da5bd2a7d80a654709aadbd39d637/generated/google-apis-drive_v3/lib/google/apis/drive_v3/service.rb#L47

        def initialize
          super(DEFAULT_ENDPOINT_TEMPLATE, 'drive/v3/',
                client_name: 'google-apis-drive_v3',
                client_version: Google::Apis::DriveV3::GEM_VERSION)
          @batch_path = 'batch/drive/v3'
        end

近くにこんな記述が。

        # @return [String]
        #  API key. Your API key identifies your project and provides you with API access,
        #  quota, and reports. Required unless you provide an OAuth 2.0 token.
        attr_accessor :key

なるほど。API keyを与えるか OAuth 2.0 tokenを与える必要があるそうな。

今回はAPI keyを使わないので OAuth 2.0 tokenの路線で行く。

もし keyを使うなら

client = Google::Apis::DriveV3::DriveService.new
client.key = your_api_key

みたいな感じにしたら使えるとみた。

はい、でこのtokenはどうやってやるのかな。

docに書いてない。わかりやすいとこにはないな。

drive.authorization = ... # See Googleauth or Signet libraries

とあるから多分これなんだろうけど。

書いておいてほしいな。

usage みたいな感じでデカデカと。

初学者にはきつい。

ということでこのauthorizationがなんなのか、掘ってみる。

先ほどと同じく画面右上の searchでauthorizationとすると

それだと

google-api-ruby-client/google-apis-core/lib/google/apis/core/base_service.rb at 6220f97dbc9da5bd2a7d80a654709aadbd39d637 · googleapis/google-api-ruby-client · GitHub

        # @!attribute [rw] authorization
        # @return [Signet::OAuth2::Client]
        #  OAuth2 credentials
        def authorization=(authorization)
          request_options.authorization = authorization
        end

        def authorization
          request_options.authorization
        end

これっぽい??

DriveはGoogle::Apis::Core::BaseServiceを継承していたし。

いやだとしても ???だな。

とりあえずrequest_options.authorizationに入れている。

ちなみにそれは親元のBaseServiceの

self.request_options = Google::Apis::RequestOptions.default.dup

を入れているらしい。

これも合わせて流し読み

github.com

1個ずつ倒していこう。

まず Google::Apis::RequestOptions.default.dup

    # Request options
    class RequestOptions
      # @!attribute [rw] authorization
      #   @return [Signet::OAuth2::Client, #apply(Hash)] OAuth2 credentials.
      # @!attribute [rw] retries
      #   @return [Integer] Number of times to retry requests on server error.
      # @!attribute [rw] max_elapsed_time
      #   @return [Integer] Total time in seconds that requests are allowed to keep being retried.
      # @!attribute [rw] base_interval
      #   @return [Float] The initial interval in seconds between tries.
      # @!attribute [rw] max_interval
      #   @return [Integer] The maximum interval in seconds that any individual retry can reach.
      # @!attribute [rw] multiplier
      #   @return [Numeric] Each successive interval grows by this factor. A multipler of 1.5 means the next interval 
      #              will be 1.5x the current interval.
      # @!attribute [rw] header
      #   @return [Hash<String,String>] Additional HTTP headers to include in requests.
      # @!attribute [rw] normalize_unicode
      #   @return [Boolean] True if unicode strings should be normalized in path parameters.
      # @!attribute [rw] skip_serialization
      #   @return [Boolean] True if body object should be treated as raw text instead of an object.
      # @!attribute [rw] skip_deserialization
      #   @return [Boolean] True if response should be returned in raw form instead of deserialized.
      # @!attribute [rw] api_format_version
      #   @return [Integer] Version of the error format to request/expect.
      # @!attribute [rw] use_opencensus
      #   @return [Boolean] Whether OpenCensus spans should be generated for requests. Default is true.
      # @!attribute [rw] quota_project
      #   @return [String] Project ID to charge quota, or `nil` to default to the credentials-specified project.
      # @!attribute [rw] query
      #   @return [Hash<String,String>] Additional HTTP URL query parameters to include in requests.
      # @!attribute [rw] add_invocation_id_header
      #   @return [Boolean] True if the header gccl-invocation-id need to be set
      # @!attribute [rw] upload_chunk_size
      #   @return [Integer] The chunk size of storage upload. The default value is 100 MB.

      # Get the default options
      # @return [Google::Apis::RequestOptions]
      def self.default
        @options ||= RequestOptions.new
      end

      def merge(options)
        return self if options.nil?

        new_options = dup
        members.each do |opt|
          opt = opt.to_sym
          new_options[opt] = options[opt] unless options[opt].nil?
        end
        new_options
      end
    end

dupは元のオブジェクトのコピーだから、良い。

とにかく、RequestOptionsとかいうものを作っているっぽい。

その中にいた

      # @!attribute [rw] authorization
      #   @return [Signet::OAuth2::Client, #apply(Hash)] OAuth2 credentials.

そもそもこの書き方はなんだ

YARDというらしい。

magazine.techacademy.jp

これはこれでまた記事が書けそう

書き方が二つあって、こっちがdirectiveという書き方。属性とか実際のメソッドに与える引数とか書く時に使うものとして理解。

claudeった。

authorizationという読み書き可能な属性があり、それはOAuth2認証情報を保持するもので、Signet::OAuth2::Clientオブジェクトか、apply(Hash)メソッドを持つオブジェクトを返せるし、
authorizationに設定もできる

らしい。

あ、でもBaseServiceの方はSignet::OAuth2::Clientオブジェクト だけだな。

まぁいっか。

でこいつを入れて、どのようにhttpリクエスト時に使うんだろう。

とりあえずauthorizationというものを keyを設定しない限りは使う必要がありそうという事は覚えておこう。

今の結論

authorization: Signet::OAuth2::Client or apply(Hash)というメソッドがあるオブジェクト

で次だ。

さっき試しに使った list_filesが実際は何をしているのか・

これを突き詰めていけば、さっきのauthorizationの正体とかなぜ必要なのかもわかるはず。

list_filesが何をしているのか

さてまた検索する

正体はこれ。

github.com

        def list_files(corpora: nil, corpus: nil, drive_id: nil, include_items_from_all_drives: nil, include_labels: nil, include_permissions_for_view: nil, include_team_drive_items: nil, order_by: nil, page_size: nil, page_token: nil, q: nil, spaces: nil, supports_all_drives: nil, supports_team_drives: nil, team_drive_id: nil, fields: nil, quota_user: nil, options: nil, &block)
          command = make_simple_command(:get, 'files', options)
          command.response_representation = Google::Apis::DriveV3::FileList::Representation
          command.response_class = Google::Apis::DriveV3::FileList
          command.query['corpora'] = corpora unless corpora.nil?
          command.query['corpus'] = corpus unless corpus.nil?
          command.query['driveId'] = drive_id unless drive_id.nil?
          command.query['includeItemsFromAllDrives'] = include_items_from_all_drives unless include_items_from_all_drives.nil?
          command.query['includeLabels'] = include_labels unless include_labels.nil?
          command.query['includePermissionsForView'] = include_permissions_for_view unless include_permissions_for_view.nil?
          command.query['includeTeamDriveItems'] = include_team_drive_items unless include_team_drive_items.nil?
          command.query['orderBy'] = order_by unless order_by.nil?
          command.query['pageSize'] = page_size unless page_size.nil?
          command.query['pageToken'] = page_token unless page_token.nil?
          command.query['q'] = q unless q.nil?
          command.query['spaces'] = spaces unless spaces.nil?
          command.query['supportsAllDrives'] = supports_all_drives unless supports_all_drives.nil?
          command.query['supportsTeamDrives'] = supports_team_drives unless supports_team_drives.nil?
          command.query['teamDriveId'] = team_drive_id unless team_drive_id.nil?
          command.query['fields'] = fields unless fields.nil?
          command.query['quotaUser'] = quota_user unless quota_user.nil?
          execute_or_queue_command(command, &block)
        end

なんか色々指定できるそうな。値設定したらそれをqueryに添えてやるらしい。

queryはその名の通り、?hoge=1&huga=1みたいに設定していく感じなのかな?

command = make_simple_command(:get, 'files', options)
~~~
execute_or_queue_command(command, &block)

これが何してるかだ。

まずはmake_simple_command

github.com

これが実態。

        # Create a new command.
        #
        # @param [symbol] method
        #   HTTP method (:get, :post, :delete, etc...)
        # @param [String] path
        #  Additional path, appended to API base path
        # @param [Hash, Google::Apis::RequestOptions] options
        #  Request-specific options
        # @return [Google::Apis::Core::DownloadCommand]
        def make_simple_command(method, path, options)
          verify_universe_domain!
          full_path =
            if path.start_with? "/"
              path[1..-1]
            else
              base_path + path
            end
          template = Addressable::Template.new(root_url + full_path)
          command = ApiCommand.new(method, template, client_version: client_version)
          command.options = request_options.merge(options)
          apply_command_defaults(command)
          command
        end

verify_universe_domain! 何してるか。

検索の画像載せるのがめんどくなってきた。

        # Verify that the universe domain setting matches the universe domain
        # in the credentials, if present.
        #
        # @raise [Google::Apis::UniverseDomainError] if there is a mismatch
        def verify_universe_domain!
          auth = authorization
          auth_universe_domain = auth.universe_domain if auth.respond_to? :universe_domain
          if auth_universe_domain && auth_universe_domain != universe_domain
            raise UniverseDomainError,
                  "Universe domain is #{universe_domain} but credentials are in #{auth_universe_domain}"
          end
          true
        end

authorizationはさっきのrequest_optionsで入れたやつ。

そこから そのauthに universe_domainメソッドがあったらなんかチェックして例外を投げて、

それ以外はtrueを返す。

まぁ多分そんな気にしなくて良いと思う。とりあえずtrue返すっていう事で次。

fullpath

full_path =
            if path.start_with? "/"
              path[1..-1]
            else
              base_path + path
            end

pathっていうのは command = make_simple_command(:get, 'files', options)

なので 'files'。

という事でここでいうfull_pathは base_path + 'files'

base_pathがなんかはわからないけど、このgemはGoogle 公式が提供しているREST APIへの

アクセスをより簡素にしてくれてると思うから

Method: files.list  |  Google Drive  |  Google for Developers

結論、ここに飛ばしてくれてるはず。

なのでbase_pathは "https://www.googleapis.com/drive/v3" だと思う。

これに今回の 'files'をつけたら、それっぽくなるし。

Templateって何。

template = Addressable::Template.new(root_url + full_path)

これはなんだ。

Addressableというものをリポジトリ内でも検索してもなし。

外部のgemか?

なんかURLを構築してるのかな?

gemがあった。URL管理を便利にしてくれるやつっぽい。

次。

command

command = ApiCommand.new(method, template, client_version: client_version)

methodは httpメソッドがシンボルで渡される。templateは url、versoinは多分固定の文字列とか数値。

なので ApiComandが何してるかだけ気にすれば良い。

github.com

本体。

github.com

        # @param [symbol] method
        #   HTTP method
        # @param [String,Addressable::URI, Addressable::Template] url
        #   HTTP URL or template
        # @param [String, #read] body
        #   Request body
        def initialize(method, url, body: nil, client_version: nil)
          super(method, url, body: body)
          self.client_version = client_version || Core::VERSION
        end

これをさらにsuperして親のものを使うと。

これ作った人よくこんな抽象化できるな。

すごいなー。

でこのsuperは HttpCommandなのでそれをみにいく。

module Google
  module Apis
    module Core
      # Command for executing most basic API request with JSON requests/responses
      class ApiCommand < HttpCommand

HttpCommand

github.com

        # @param [symbol] method
        #   HTTP method
        # @param [String,Addressable::URI, Addressable::Template] url
        #   HTTP URL or template
        # @param [String, #read] body
        #   Request body
        def initialize(method, url, body: nil)
          self.options = Google::Apis::RequestOptions.default.dup
          self.url = url
          self.url = Addressable::Template.new(url) if url.is_a?(String)
          self.method = method
          self.header = Hash.new
          self.body = body
          self.query = {}
          self.params = {}
          @opencensus_span = nil
          if OPENCENSUS_AVAILABLE
            logger.warn  'OpenCensus support is now deprecated. ' +
                         'Please refer https://github.com/googleapis/google-api-ruby-client#tracing for migrating to use OpenTelemetry.' 
            
          end
        end

なんかよくわからないけど色々入れてるみたい。

とりあえずこれがmake_simple_commandで帰ってくると。

でlist_filesに戻ろう。なんかqueryにhashで色々入れられるらしいね。

ただいま、list_files

        def list_files(corpora: nil, corpus: nil, drive_id: nil, include_items_from_all_drives: nil, include_labels: nil, include_permissions_for_view: nil, include_team_drive_items: nil, order_by: nil, page_size: nil, page_token: nil, q: nil, spaces: nil, supports_all_drives: nil, supports_team_drives: nil, team_drive_id: nil, fields: nil, quota_user: nil, options: nil, &block)
          command = make_simple_command(:get, 'files', options)
          command.response_representation = Google::Apis::DriveV3::FileList::Representation
          command.response_class = Google::Apis::DriveV3::FileList
          command.query['corpora'] = corpora unless corpora.nil?
          command.query['corpus'] = corpus unless corpus.nil?
          command.query['driveId'] = drive_id unless drive_id.nil?
          command.query['includeItemsFromAllDrives'] = include_items_from_all_drives unless include_items_from_all_drives.nil?
          command.query['includeLabels'] = include_labels unless include_labels.nil?
          command.query['includePermissionsForView'] = include_permissions_for_view unless include_permissions_for_view.nil?
          command.query['includeTeamDriveItems'] = include_team_drive_items unless include_team_drive_items.nil?
          command.query['orderBy'] = order_by unless order_by.nil?
          command.query['pageSize'] = page_size unless page_size.nil?
          command.query['pageToken'] = page_token unless page_token.nil?
          command.query['q'] = q unless q.nil?
          command.query['spaces'] = spaces unless spaces.nil?
          command.query['supportsAllDrives'] = supports_all_drives unless supports_all_drives.nil?
          command.query['supportsTeamDrives'] = supports_team_drives unless supports_team_drives.nil?
          command.query['teamDriveId'] = team_drive_id unless team_drive_id.nil?
          command.query['fields'] = fields unless fields.nil?
          command.query['quotaUser'] = quota_user unless quota_user.nil?
          execute_or_queue_command(command, &block)
        end

で途中のcommandに設定するqueryは無視できるので、ここから次の相手。

execute_or_queue_command

execute_or_queue_command(command, &block)は何してるのか。

github.com

        # Execute the request. If a batch is in progress, the request is added to the batch instead.
        #
        # @param [Google::Apis::Core::HttpCommand] command
        #   Command to execute
        # @return [Object] response object if command executed and no callback supplied
        # @yield [result, err] Result & error if block supplied
        # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried
        # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification
        # @raise [Google::Apis::AuthorizationError] Authorization is required
        def execute_or_queue_command(command, &callback)
          batch_command = current_batch
          if batch_command
            fail "Can not combine services in a batch" if Thread.current[:google_api_batch_service] != self
            batch_command.add(command, &callback)
            nil
          else
            command.execute(client, &callback)
          end
        end

ようやくゴールが見えてきた気がする。

試しにさっきのlist_filesの例外を捕捉してなんのクラスだったかみてみよう。

app(dev)* begin
app(dev)*   files = client.list_files(q: "hoge")
app(dev)* rescue => e
app(dev)*   pp e.class
app(dev)*   pp e.message
app(dev)> end

Google::Apis::ClientError
"forbidden: Method doesn't allow unregistered callers (callers without established identity). Please use API Key or other form of API consumer identity to call this API."

およ、AuthorizationErrorじゃなかった。

まぁそもそもこのclientでどこにアクセスしたいのっていうのもわかりっこないからあれだけど

まぁいっか。先に進もう、いずれわかるでしょ。

batch_command

多分batchだったらみたいな感じかな。この場合は今回関係ないと思うから skip

command.execute

これが本体。

command.execute(client, &callback)

このコマンドというのはさっきのHttpCommandだから。

github.com

        # Execute the command, retrying as necessary
        #
        # @param [Faraday::Connection] client
        #   Faraday connection
        # @yield [result, err] Result or error if block supplied
        # @return [Object]
        # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried
        # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification
        # @raise [Google::Apis::AuthorizationError] Authorization is required
        def execute(client, &block)
          prepare!
          opencensus_begin_span
          do_retry :execute_once, client, &block
        ensure
          opencensus_end_span
          @http_res = nil
          release!
        end

clientというはFaradayとかいうrubyのhttpクライアント。日本語あっているか知らないけど。

prepare!

        # Prepare the request (e.g. calculate headers, add query params, serialize data, etc) before sending
        #
        # @private
        # @return [void]
        def prepare!
          normalize_unicode = true
          if options
            header.update(options.header) if options.header
            query.update(options.query) if options.query
            normalize_unicode = options.normalize_unicode
          end
          self.url = url.expand(params, nil, normalize_unicode) if url.is_a?(Addressable::Template)
          url.query_values = normalize_query_values(query).merge(url.query_values || {})

          if allow_form_encoding?
            @form_encoded = true
            self.body = Addressable::URI.form_encode(url.query_values(Array))
            self.header['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'
          else
            @form_encoded = false
          end

          self.body = '' if self.body.nil? && [:post, :put, :patch].include?(method)
          if defined?(::Google::Apis::Core::CompositeIO) && body.is_a?(::Google::Apis::Core::CompositeIO)
            header["Content-Length"] ||= body.size.to_s
          end
        end

リクエストをこれまでの情報を元にセットするっぽい。

opencensus_begin_span

何してるかわからん。

do_retry

まだ認証情報セットしてるようには見えないな。

見逃したか??

github.com

        def do_retry func, client
          begin
            Retriable.retriable tries: options.retries + 1,
                                max_elapsed_time: options.max_elapsed_time,
                                base_interval: options.base_interval,
                                max_interval: options.max_interval,
                                multiplier: options.multiplier,
                                on: RETRIABLE_ERRORS do |try|
              # This 2nd level retriable only catches auth errors, and supports 1 retry, which allows
              # auth to be re-attempted without having to retry all sorts of other failures like
              # NotFound, etc
              auth_tries = (try == 1 && authorization_refreshable? ? 2 : 1)
              Retriable.retriable tries: auth_tries,
                                  on: [Google::Apis::AuthorizationError, Signet::AuthorizationError, Signet::RemoteServerError, Signet::UnexpectedStatusError],
                                  on_retry: proc { |*| refresh_authorization } do
                send(func, client).tap do |result|
                  if block_given?
                    yield result, nil
                  end
                end
              end
            end
          rescue => e
            if block_given?
              yield nil, e
            else
              raise e
            end
          end
        end

何してるの??

retryはしてそう。

けど やっているのは execute_onceだからそれをみることにする。

execute_once

github.com

        # Execute the command once.
        #
        # @private
        # @param [Faraday::Connection] client
        #   Faraday connection
        # @return [Object]
        # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried
        # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification
        # @raise [Google::Apis::AuthorizationError] Authorization is required
        def execute_once(client)
          body.rewind if body.respond_to?(:rewind)
          begin
            logger.debug { sprintf('Sending HTTP %s %s', method, url) }
            request_header = header.dup
            apply_request_options(request_header)

            @http_res = client.run_request(method, url.to_s, body, request_header)

            logger.debug { @http_res.status }
            logger.debug { safe_single_line_representation @http_res }
            response = process_response(@http_res.status.to_i, @http_res.headers, @http_res.body)
            success(response)
          rescue => e
            logger.debug { sprintf('Caught error %s', e) }
            error(e, rethrow: true)
          end
        end

あ、なんか見覚えあるlogger

logger.debug { sprintf('Sending HTTP %s %s', method, url) }

これで最初のログが出るのか。

app(dev)* begin
app(dev)*   files = client.list_files(q: "hoge")
app(dev)* rescue => e
app(dev)*   pp e.class
app(dev)*   pp e.message
app(dev)> end
Sending HTTP get https://www.googleapis.com/drive/v3/files?q=hoge <= これ。
403

次。

apply_request_options(request_header)

これが何してるか。

github.com

        # Update the request with any specified options.
        # @param [Hash] req_header
        #  HTTP headers
        # @return [void]
        def apply_request_options(req_header)
          if options.authorization.respond_to?(:apply!)
            options.authorization.apply!(req_header)
          elsif options.authorization.is_a?(String)
            req_header['Authorization'] = sprintf('Bearer %s', options.authorization)
          end
          req_header.update(header)
        end

あ、authorizationあったーーーーーーーー。

なるほど、ここで applyメソッドがあるかみてるな。

authorizationを見て、applyがあれば そのapply!メソッドを使って、多分 headerの値を書き換えて、

文字列であれば、AuthorizatoinヘッダーにBearerトークンを付与している。

あーだからこのままaccess_tokenとか付与してもいけるのか。

いけるのか??知らんけど。

とりあえずauthorizationに設定できるものは 2種類そう。

string(多分、token直渡し or apply!メソッドあるやつ)

あれでもSignetだっけ、あれはどこ行った??あれも apply!メソッdがあるのかな?

ていうかgoogle-apis-drive_v4 apiはhttpリクエスト自体にFaradayを使っているんだ。

Faraday使ったことないから詳しくは知らないけど。

なんかはてぶろが重くなってきたから次章へ。

結論

今回理解したのは

client = Google::Apis::DriveV3::DriveService.new
files = client.list_files(q: "title contains 'finances'")

とかやっているけど裏でやっているのは

httpリクエストに クエリパラメータつけたりしてるだけ。

そのリクエストはFaradayを使っている。

drive.authorizationで指定できるのは 2つ

文字列なら AuthorizationヘッダでBearerトーク

apply!メソッドがあるオブジェクトを渡したらそれ専用の処理をする。

では次へ。