JMAPアクション
JMAPアクションは、JMAP (RFC 8620 、RFC 8621 )のメソッドを呼びます。セッションを取得し、各呼び出しのアカウントを補い、呼び出しを1回のリクエストで送ります。レスポンスは呼び出しのidごとに返し、メソッドのエラーは1つのリストにまとめます。
外部アクションとしてmozership/probe-jmap で公開しています。ワークフローが初めて使うときにProbeがダウンロードし、固定したコミットのaction.ymlが示すSHA-256と一致する実行ファイルだけを実行します。Probe v1.21.0以降が必要です。このバージョンから、action.ymlが申告するガードとパラメータを読みます。
基本的な構文
ステップでは40文字のコミットSHAでアクションを固定します。各リリース のノートの先頭に、コピーして使うusesの行があります。
steps:
- name: Read the latest message
uses: github.com/mozership/probe-jmap@52000030f1bd839677830d1ab07a024183f581b7 # v0.2.0
with:
url: https://jmap.example.com
basic_auth:
username: "{{vars.user}}"
password: "{{vars.pass}}"
calls:
- method: Email/query
id: latest
args:
sort: [{property: receivedAt, isAscending: false}]
limit: 1
- method: Email/get
id: email
args:
"#ids": {resultOf: latest, path: /ids}
properties: [subject, from, receivedAt]
test: status == 0 && len(res.results.email.list) == 1
echo: "Latest: {{res.results.email.list[0].subject}}"パラメータ
| パラメータ | 型 | 必須 | デフォルト | 説明 |
|---|---|---|---|---|
url | String | はい | - | サーバー。httpまたはhttps。パスのないURLはサーバーとして扱い、セッションを/.well-known/jmapから取得します。パスのあるURLはセッションのURLそのものとして扱います |
calls | Array | はい | - | メソッドの呼び出し。この順に1回のリクエストで送ります |
using | Array | いいえ | 推定 | リクエストが使うcapability。既定ではcoreと、callsのメソッドを定義するcapabilityです |
account_id | String | いいえ | プライマリアカウント | argsにaccountIdがない呼び出しのアカウント |
basic_auth | Object | いいえ | - | HTTP Basic認証のusernameとpassword |
headers | Object | いいえ | - | Authorization: Bearer <token>などのリクエストヘッダー |
timeout | Duration | いいえ | 30s | セッションとAPIのリクエストを合わせた制限時間。10sのような形式か秒数で指定します。0を指定すると制限しません |
callsの各呼び出しには次を書きます。
| フィールド | 必須 | 説明 |
|---|---|---|
method | はい | Email/getのようなメソッド名 |
args | いいえ | メソッドの引数 |
id | いいえ | 呼び出しのid。back-referenceとres.resultsで使います。既定はcと呼び出しの位置(c0から)です |
argsにaccountIdも#accountIdもない呼び出しは、account_idのアカウントで行います。account_idもなければ、セッションがメソッドのcapabilityに示すプライマリアカウントで行います。Core/echoはアカウントなしで行います。
名前が#で始まる引数(back-reference)では、nameを省略できます。省略すると、resultOfが指す呼び出しのメソッドを補います。back-referenceは引数全体を置き換えるため、filterの中のように引数の内側には書けません。
capability
usingはメソッド名の型から推定します。
| 型 | capability |
|---|---|
Core | urn:ietf:params:jmap:core |
Mailbox、Thread、Email、SearchSnippet | urn:ietf:params:jmap:mail |
Identity、EmailSubmission | urn:ietf:params:jmap:submission |
VacationResponse | urn:ietf:params:jmap:vacationresponse |
MDN | urn:ietf:params:jmap:mdn |
Blob | urn:ietf:params:jmap:blob |
Quota | urn:ietf:params:jmap:quota |
SieveScript | urn:ietf:params:jmap:sieve |
AddressBook、ContactCard | urn:ietf:params:jmap:contacts |
これ以外の型のメソッドにはusingが必要です。usingを指定したときは、書いたとおりに送ります。
上記のパラメータ以外のキーをwithに書くと、何も送る前にステップが失敗します。action.ymlはパラメータをparamsとして申告しているため、probe checkがそのキーを行番号とともに報告します。
レスポンスオブジェクト
| プロパティ | 型 | 説明 |
|---|---|---|
res.code | Integer | HTTPステータスコード |
res.status | String | "200 OK"のようなHTTPステータス行 |
res.headers | Object | 正規化した名前をキーとするレスポンスヘッダー |
res.session | Object | サーバーが返したセッションオブジェクト。ないときはnull |
res.results | Object | 各メソッドのレスポンスの引数。呼び出しのidをキーにします。メソッドのエラーもエラーオブジェクトとしてここに入ります |
res.responses | Array | すべてのメソッドのレスポンス。順にname、args、idを持ちます |
res.errors | Array | 失敗したもの。内容は下記のとおりです。何も失敗しなければ空です |
res.body | Any | レスポンスボディ全体。JSONなら解析した値、それ以外は文字列 |
res.rawbody | String | 解析前のボディ。ボディがJSONのときに入ります |
req | Object | session_url、APIのurl、using、送ったcalls、headers |
rt | Duration | セッションとAPIのリクエストを合わせた時間 |
status | Integer | APIが2xxでメソッドのレスポンスを返し、res.errorsが空なら0。それ以外は1 |
JMAPのサーバーは、実行できなかったメソッドにもHTTP 200で応答します。そのため、res.codeだけでは呼び出しが成功したかわかりません。res.errorsはすべての失敗を集めます。各要素は、サーバーが返したエラーオブジェクトに次のフィールドを加えたものです。
kind | 失敗したもの | 加えるフィールド |
|---|---|---|
method | errorのレスポンスが返ったメソッドの呼び出し | id、method |
notCreated、notUpdated、notDestroyed | /setの呼び出しのレコード | id、method、key(作成のidかレコードのid) |
request | problem detailsのオブジェクトが返ったリクエスト全体 | - |
session | サーバーが返さなかったセッション | description |
サーバーがセッションを返さなかったときは、メソッドの呼び出しを送りません。このときresはセッションのリクエストへのレスポンスです。
サーバーが返したレスポンスはすべて結果として扱います。そのため、メソッドのエラーや401もテストで確かめられます。接続の拒否やタイムアウトのように、レスポンスを得られなかったリクエストだけが、ステップをエラーとして失敗させます。
使用例
メッセージを送り、届くのを待つ
Email/setでメッセージを作り、EmailSubmission/setで送ります。送るメッセージは作成のid #msgで指します。続くステップではretryで届くのを待ちます。
steps:
- name: Send a message to bob
uses: github.com/mozership/probe-jmap@52000030f1bd839677830d1ab07a024183f581b7 # v0.2.0
with:
url: "{{vars.url}}"
basic_auth: {username: "{{vars.alice}}", password: "{{vars.alice_pass}}"}
calls:
- method: Email/set
id: create
args:
create:
msg:
mailboxIds:
"{{outputs.alice.sent}}": true
from: [{email: "{{vars.alice}}"}]
to: [{email: "{{vars.bob}}"}]
subject: "{{vars.subject}}"
bodyValues: {body: {value: Sent by probe.}}
textBody: [{partId: body, type: text/plain}]
- method: EmailSubmission/set
id: submit
args:
create:
sub:
identityId: "{{outputs.alice.identity}}"
emailId: "#msg"
test: status == 0 && res.results.submit.created.sub.id != nil
- name: Wait for it in bob's mailbox
uses: github.com/mozership/probe-jmap@52000030f1bd839677830d1ab07a024183f581b7 # v0.2.0
with:
url: "{{vars.url}}"
basic_auth: {username: "{{vars.bob}}", password: "{{vars.bob_pass}}"}
calls:
- method: Email/query
id: query
args:
filter: {subject: "{{vars.subject}}"}
- method: Email/get
id: get
args:
"#ids": {resultOf: query, path: /ids}
properties: [subject, receivedAt]
retry:
max_attempts: 30
interval: 1s
test: status == 0 && len(res.results.get.list) == 1失敗を確かめる
steps:
- name: A record that cannot be created
uses: github.com/mozership/probe-jmap@52000030f1bd839677830d1ab07a024183f581b7 # v0.2.0
with:
url: "{{vars.url}}"
basic_auth: {username: "{{vars.user}}", password: "{{vars.pass}}"}
calls:
- method: Email/set
args:
create:
bad:
mailboxIds: {no-such-mailbox: true}
test: |
status == 1 &&
res.errors[0].kind == "notCreated" &&
res.errors[0].key == "bad"ガード
--read-onlyを指定した実行では、/get、/query、/changes、/queryChanges、/lookup、/echo以外のメソッドを含むステップを、何も送る前に拒否します。--allow-hostが許可しないホストは、セッション、APIのURL、リダイレクト先のいずれでも拒否します。action.ymlはguard: [read-only, allow-host]を申告しているため、どちらのガードの下でも--allow-actionなしで実行します。ガードを参照してください。