01 何が問題だったのか
SE-0292 で、Swift のエコシステムにパッケージレジストリのサービスが導入されました。しかし現状では、レジストリ上のパッケージを利用するには、その正確な識別子(scope.name の形式)をあらかじめ知っている必要があります。レジストリ内のパッケージを名前や作者などから探すための、標準化された仕組みは存在しませんでした。
これは他のパッケージエコシステムと比べたときの欠落でした。npm には npm search、Cargo には cargo search、NuGet には dotnet package search があり、パッケージの発見はパッケージ管理ワークフローの基本的な要素です。この機能をレジストリのプロトコルレベルで用意すれば、Swift Package Manager(SwiftPM)の利用者だけでなく、サードパーティ製のクライアントにも恩恵があります。
SE-0391 で swift package-registry publish サブコマンドと標準化されたメタデータスキーマが導入されたことにより、レジストリは検索を実現するのに十分な構造化メタデータ(説明や作者など)を持つようになりました。SE-0536 はこの基盤の上に、検索の仕組みを追加します。
02 どのように解決されるのか
レジストリのサービス仕様に検索用の GET /search エンドポイントを追加し、あわせて SwiftPM に swift package-registry search サブコマンドを追加します。利用者はコマンドラインからレジストリ上のパッケージを検索できるようになります。
$ swift package-registry search LinkedList
mona.LinkedList - One thing links to another. (v1.1.1)
検索エンドポイントは追加的な機能であり、実装は任意です。レジストリは実装を強制されず、クライアント側は検索に対応していないレジストリも扱えるようにしておく必要があります。検索に対応しないレジストリは、/search への要求に 404 Not Found を返します。
検索対応の通知(capability advertisement)
検索が任意のエンドポイントであるため、対応しているレジストリはそのことをクライアントに知らせる手段が必要です。SwiftPM はレジストリと通信する前に GET /availability で到達性を確認しており、この提案ではこの /availability を正式に仕様化し、対応機能を通知する仕組みへ拡張します。
検索に対応するレジストリは、/availability の応答本文(JSON)に capabilities オブジェクトを含めて通知します。
{
"capabilities": {
"search": {}
}
}
search キーが存在すれば検索対応を意味します。本文を返さない従来のレジストリはこれまでどおり動作し、capabilities がなければ任意機能を通知していないものとして扱われます。
検索クエリの構文
GET /search?q={query} の q パラメータに検索クエリを渡します。クエリはフリーテキスト検索と、フィールドを指定する qualifier を組み合わせられます。フリーテキストはパッケージ名・スコープ・説明に対して照合され、qualifier は特定のメタデータフィールドで結果を絞り込みます。単語はスペースで区切り、複数語からなる値はダブルクォートで囲みます。
qualifier は field:value の形式で、次のものが定義されています。
| qualifier | 対象フィールド | 説明 |
|---|---|---|
scope: |
scope | パッケージのスコープで絞り込む |
name: |
name | パッケージ名で絞り込む |
description: |
description | 説明で絞り込む |
author: |
author.name | 作者名で絞り込む |
pkg: |
― | purl(package url)で特定のパッケージを検索する |
qualifier の値は大文字・小文字を区別せず、部分一致で照合されます。
論理演算子として AND(スペース区切りで暗黙的に適用)、OR、NOT(- も可)が使えます。OR は AND より優先順位が高く、たとえば networking scope:apple OR scope:vapor は networking AND (scope:apple OR scope:vapor) と等価です。この構文は API のコレクションを絞り込む標準である AIP-160 の小さなサブセットを採用しています。
$ swift package-registry search author:"Mona Lisa Octocat"
mona.LinkedList - One thing links to another. (v1.1.1)
mona.RegEx - Expressions on the reg. (v2.0.0)
$ swift package-registry search "networking scope:apple OR scope:vapor"
apple.swift-nio - Event-driven network application framework. (v2.60.0)
vapor.vapor - A server-side Swift HTTP framework. (v4.92.0)
結果とページング
サーバは一致したパッケージの一覧を JSON で返します。各結果には identity(scope.name 形式の識別子)が必須で、summary・latestVersion・author・licenseURL・url などが任意で含まれます。応答には total(一致した総数)・offset・limit が付き、offset += limit を繰り返してページングできます。クエリが指定された場合、サーバは関連度順に結果を並べることが推奨されます(具体的なランキングの指標はレジストリの実装に委ねられます)。
同一のクエリと同一のデータに対しては、offset や limit に依存しない決定的な並び順を返すことがレジストリに求められます(関連度が同点の場合は識別子順などの安定した tiebreaker を適用します)。ただし、レジストリは結果のスナップショットを保持する必要はありません。検索インデックスは時間とともに変化するため、ページングの途中でデータが更新されると、同じパッケージが複数のページに現れたり、現れなくなったりすることがあります。クライアントは、同じ識別子が複数のページにまたがって現れうることを前提にしておく必要があります。
SwiftPM サブコマンドの挙動
swift package-registry search は、既定では設定済みのすべてのレジストリを検索して結果を集約します。公開レジストリと社内の私的レジストリを併用する構成などで、どのレジストリにあるかを意識せず一度に検索できます。--registry <url> を付けると、単一のレジストリに絞り込めます。複数のレジストリを横断した場合は、同じ識別子を区別できるよう各結果にレジストリの URL が併記されます。
$ swift package-registry search Logger
acme.Logger - Internal logging framework. (v3.2.0) [https://packages.acme.internal]
mona.Logger - A tiny logger. (v1.0.1) [https://packages.example.com]
このほか、--limit / --offset でページングを、--json で機械可読な JSON 出力を指定できます。検索に対応していないレジストリを対象にした場合は、非対応である旨がエラーとして表示されます。
この変更は完全に追加的なもので、既存のエンドポイントやパッケージ形式には変更を加えません。/search を実装しないレジストリは単に 404 Not Found を返すだけで、既存のレジストリは影響を受けません。
03 今後の見通し
SE-0536 では、将来的な拡張としていくつかの方向性が挙げられています。いずれも構想段階であり、実現を約束するものではありません。
- 並び順の指定: 既定の関連度順に加えて、
sort=recent(新しい順)やsort=name(名前順)のようなsortクエリパラメータで並び順を選べるようにする案です。 - Package Collections との連携: 検索結果を SE-0291 の Package Collections の生成に活用し、検索クエリに基づいてコレクションをキュレーションできるようにする案です。
- サジェスト(suggested packages): 入力に応じて候補を返す、オートコンプリート的なサジェスト用エンドポイントを設ける案です。
- プラットフォーム・Swift バージョンでの絞り込み:
platform:linuxやswift:6.0のような qualifier で、対応プラットフォームや Swift バージョンによる絞り込みを可能にする案です。これにはパッケージ側が対応状況を機械可読な形で宣言する必要があります。 - publish 時のライセンス判定: 本提案では
licenseURLを検索対象から外していますが、URL だけではライセンスの種類を確実に判別できないためです。代わりにswift package-registry publishの時点で SwiftPM が SPDX 識別子などからライセンスを判定し、その結果を構造化フィールドとしてpackage-metadata.jsonに含めれば、レジストリ間で一貫したlicense:MITのような絞り込みを実現できるという構想です。 swift package info: 検索結果は意図的に軽量にしているため、単一パッケージの詳細(説明・作者・README の URL・リポジトリの URL・バージョンなど)を表示するswift package info <identity>サブコマンドを別途設ける案も挙げられています。