microsoft_graph のコードを読む
Fukuoka.rb とかでちまちまと開発している msgraph であるが、ここ最近大きく実装の方針を変えようと思っている。
が、変えるためには既存の microsoft_graph の理解が不可欠だと判断した。そして、一人でメモっても気が滅入るので晒してツッコミを待つことにする。
主な目的は未来の自分へのメモ。
きっかけ
Go Conference 2019 Autumn で以下のセッションがありました。
www.slideshare.net
俺が Ruby でやりたいことを Go で一人(?)で実現された方がいて、「負けてらんねぇ!」とモチベーションが上がったのが大きいです。また、このセッションで Microsoft Graph SDK Code Generator という存在を知った。
平たく言うと「OData Protocol を定義しているメタデータ(XML ファイル)からコードを生成するもの」と解釈しているが、残念ながら Ruby は対応されていない。しかし、Go も同じ状況でこの方が OData Protocol のメタデータファイルからコード生成をしていそうなので、自分も踏襲することにした。
元々は「既にエンドポイントが存在するのでそれを愚直にラッパーモジュールを作成する」「OpenAPI 3.0 の定義をしてコードを自動生成する」の二通りを考えて、前者を採択していたが正直しんどい。そんな状況での先述のセッションを知ったのである。
また既存の microsoft_graph もメタデータファイルをパースして API インタフェース部分を実現していそうなので、車輪の再発明になるがその方が早くて確実な気がしてきたので方針を変更することにした。
microsoft_graph
「そもそも microsoft_graph って何?」と問われれば、Microsoft Graph Client Library for Ruby である、が回答である。平たくいえば API Client です。
Microsoft Graph API そのものの仕様はリファレンスを参照してもらうとして、このブログでも度々言及しているが microsoft_graph 自体はメンテナンスされていない。
GitHub に載っている usage example は動かないので、以下のサンプルコードで話をすすめる。
require 'httpclient' require 'microsoft_graph' require 'json' require 'nokogiri' require 'net/http' MS_BASE_URL = "https://login.microsoftonline.com".freeze TOKEN_REQUEST_PATH = "oauth2/v2.0/token".freeze # Your configuration client_id = 'xxxxx-xxxx-xxx-xxxxxx-xxxxxxx' client_secret = 'xxxXXXxxXXXxxxXXXxxXXXXXXXXxxxxxx=' tenant_id = 'xxxxx-xxxx-xxx-xxxxxx-xxxxxxx' client = HTTPClient.new response = client.post( "#{MS_BASE_URL}/#{tenant_id}/#{TOKEN_REQUEST_PATH}", { body: { client_id: client_id, client_secret: client_secret, scope: 'https://graph.microsoft.com/.default', grant_type: 'client_credentials', }, 'Content-Type' => 'application/json', multipart: true, } ) raise "#{response.message}" unless response.code == 200 token = JSON.parse(response.body)['access_token'] # add the access token to the request header callback = Proc.new { |r| r.headers["Authorization"] = "Bearer #{token}" } graph = MicrosoftGraph.new(base_url: MicrosoftGraph::BASE_URL, cached_metadata_file: File.join(MicrosoftGraph::CACHED_METADATA_DIRECTORY, "metadata_v1.0.xml"), &callback ) users = graph.users users.each do |user| puts "Hello, I am #{user.display_name}" end
MicrosoftGraph.new
トークンの発行は実は Microsoft Graph の責務ではないので割愛する。
「MicrosoftGraph.new は何をしているか」を読み解く。
require "odata" Dir[ File.join( File.dirname(__FILE__), 'microsoft_graph', '*' ) ].sort.each { |f| require f } class MicrosoftGraph attr_reader :service BASE_URL = "https://graph.microsoft.com/v1.0/" def initialize(options = {}, &auth_callback) @service = OData::Service.new( api_version: options[:api_version], auth_callback: auth_callback, base_url: BASE_URL, metadata_file: options[:cached_metadata_file] ) @association_collections = {} unless MicrosoftGraph::ClassBuilder.loaded? MicrosoftGraph::ClassBuilder.load!(service) end end def containing_navigation_property(type_name) navigation_properties.values.find do |navigation_property| navigation_property.collection? && navigation_property.type.name == "Collection(#{type_name})" end end def path; end end
l.1 で自前の OData モジュールを require
してます。microsoft_graph の肝の機能だと思うけど、今回は深堀りしない。
ll. 3 - 9 でやっているのは micorosoft_graph ディレクトリ配下のファイルを全て require
している。途中で sort
を使っているのでどうやら require
する順序は気にしなくていいものらしい。
ll. 11 - 36 で MicrosoftGraph クラスを定義しています。「gem って Module じゃなくてもいいんだ」と思ったのが最初の印象です。実際どうなのかは詳しくは知らないので誰か教えて下さい。
アトリビュートとして service
を、定数 BASE_URL
を定義しています。 BASE_URL
には Microsoft Graph API エンドポイントの共通 URI が定義されています。
そして initialize
メソッドが options というハッシュ値の引数と、auth_callback というブロック変数が定義されていて、内部では OData::Service
というクラスの初期化をしています。この OData::Service
クラスの初期化に options[:api_ver]
auth_callback
BASE_URL
options[:cache_metadata_file]
が必要そうだと分かります。
その後、インスタンス変数 associateion_collections
を空のハッシュで初期化し、MicrosoftGraph::ClassBuilder
がビルドされていなければビルドをする、という流れのようです。
うん、さっぱり分からないですね。肝心なのは OData を扱っているモジュールのようなきがする。
(続く)