Swift Digest
Blog | Swift.org Blog

SwiftNIO IMAP の発表

Announcing SwiftNIO IMAP

このダイジェストはClaude Opus 4.7 / 4.8によって生成されたものです(License)。原文はこちら

この記事の要点

何が発表されたのか

SwiftNIO IMAP は、IMAPv4 のパーサとエンコーダです。次の機能を実装します。

メール(Email)は 40 年以上にわたってインターネットに不可欠な存在であり、IMAP(Internet Message Access Protocol)はメールメッセージを取得するための最も広く使われているオープン標準です。長い歴史の中でさまざまな RFC を通じて大きく進化してきた一方、IMAP を正しくパース・エンコードするのは難しいことで知られています。SwiftNIO IMAP は、エンコードとパースの細かく込み入った部分を肩代わりすることでこの難しさを減らし、Swift on Server でリッチで強力なメール連携を書きやすくします。

このパッケージはパースとエンコードに焦点を当てつつ、IMAP の中核的な型に関するいくつかの便利なメソッドも提供します。IMAP に関わるビジネスロジックは実装しません。

何に使えるのか

SwiftNIO IMAP は RFC 3501(IMAP version 4rev1)を対象とし、加えて 20 以上の RFC(RFC 2087・2177・2342・2971・3502・4315・4959・5258・6154・7888 など)による拡張をサポートします。

IMAP は「人間が読める」テキストベースのワイヤーフォーマットを使いますが、SwiftNIO IMAP はこれをモダンな Swift のデータ構造による型安全な世界へ橋渡しします。プロトコルは「非対称」で、サーバーが送るメッセージとクライアントが送るメッセージは異なるパターンに従います。

やり取りの例

たとえば RFC 3501 section 8 のやり取りの一部は次のようになります(S: がサーバー、C: がクライアント)。

S:   * OK IMAP4rev1 Service Ready
C:   a001 login mrc secret
S:   a001 OK LOGIN completed

この 3 行は SwiftNIO IMAP では次のように表されます。

Response.untagged(.conditionalState(.ok(ResponseText(text: "IMAP4rev1 Service Ready"))))
CommandStreamPart.tagged(TaggedCommand(tag: "a001", command: .login(username: "mrc", password: "secret")))
Response.tagged(.init(tag: "a001", state: .ok(ResponseText(text: "LOGIN completed"))))

続く SELECT コマンドとその応答のように、より構造的なやり取りも型として表現されます。

CommandStreamPart.tagged(TaggedCommand(tag: "a002", command: .select(MailboxName("box1"), [])))
Response.untagged(.mailboxData(.exists(18)))
Response.untagged(.mailboxData(.flags([.answered, .flagged, .deleted, .seen, .draft])))
Response.untagged(.conditionalState(.ok(ResponseText(code: .unseen(17), text: "Message 17 is the first unseen message"))))
Response.tagged(.init(tag: "a002", state: .ok(ResponseText(code: .readWrite, text: "SELECT completed"))))

中核となる型

IMAP で頻繁に使われる値には UID・メッセージのシーケンス番号・メールボックス名があります。

リテラルの透過的なサポート

SwiftNIO IMAP は、RFC 3501 の同期(synchronizing)リテラルと、RFC 7888 拡張の非同期(non-synchronizing)リテラルの両方を、クライアント・サーバーのいずれについても透過的にエンコード・デコードできます。

たとえば次の 3 つの書き方は、いずれも透過的に扱われます。

RFC 3501 の「quoted」文字列:

C: A001 LOGIN "FRED FOOBAR" "fat man"
S: A001 OK LOGIN completed

RFC 3501 のコマンド継続(command continuation):

C: A001 LOGIN {11}
S: + Ready for additional command text
C: FRED FOOBAR {7}
S: + Ready for additional command text
C: fat man
S: A001 OK LOGIN completed

RFC 7888 の非同期リテラル:

C: A001 LOGIN {11+}
C: FRED FOOBAR {7+}
C: fat man
S: A001 OK LOGIN completed

いわゆる LITERAL+ / LITERAL- のサポートは、サーバーからの CAPABILITY 応答によって有効化するか、エンコードオプションを明示的に設定して有効化できます。

SwiftNIO との統合

SwiftNIO IMAP は、SwiftNIO の ChannelPipeline に組み込める 1 組の ChannelHandler を提供します。これにより、NIO の Channel を通じて IMAP コマンドを送受信できます。

提供されるハンドラは IMAPClientHandlerIMAPServerHandler の 2 つです。ChannelPipeline に挿入すると、メッセージのエンコード・デコードに使えます。

let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
let channel = try await ClientBootstrap(group).channelInitializer { channel in
    channel.pipeline.addHandler(IMAPClientHandler())
}.connect(host: example.com, port: 143).get()

try await channel.writeAndFlush(CommandStreamPart.tagged(TaggedCommand(tag: "a001", command: .login(username: "mrc", password: "secret"))), promise: nil)

これらの ChannelHandler は透過的なリテラル・IMAP のケイパビリティ(capability)など SwiftNIO IMAP の全機能をサポートし、サーバー側・クライアント側いずれの IMAP アプリケーションにとっても強力な構成要素になります。

導入・今後の位置づけ

発表時点で公開されている SwiftNIO IMAP はまだプロトタイプ段階で、コミュニティからのフィードバックを募っています。コードは入念にテストされており「production ready」に近いと考えられていますが、何が足りないか、どこを改善できるかについて、Swift フォーラムでの設計議論を歓迎するとされています。

SwiftNIO IMAP は GitHub 上で開発されているオープンソースプロジェクトです。議論や改善提案、質問は Swift フォーラムで行え、問題があれば issue を、修正があればプルリクエストを送れます。

関連リンク