Rust crateの公開API情報を取得する
TL;DR
ライブラリを作りましたのでそちらを使ってください。
コマンドとして一覧だけ欲しい場合はcargo-public-api
を使うと良いと思います。
はじめに
とある事情でcrateの公開API情報を取得する必要に迫られたので、その方法をまとめます。
crates.ioで公開されているcrateの公開APIは全てdoc.rsで確認することができますが、これの型情報などをJSON形式で取得したいものです。
docs.rsにそれ用のAPIなどがあれば良いなと思ったのですが、パッと調べた感じ、なさそうです。
また手元のpublishしていないcrateの情報も調べたいときがあるので、Rustツールチェインの中にそれらしいことができるコマンドオプションがないか探してみました。
結論としては、rustdoc
のjson出力オプションが使えます。
cargo +nightly rustdoc -- -Z unstable-options --output-format json
またこれ相当のことをしてくれるrustdoc-json
というライブラリもあります。
結果はtarget/doc
に出力されます。中身を見てみると......
{"root":"0:0:1862","crate_version":"0.1.0","includes_private":false,"index":{"0:485":{"id":"0:485","crate_id":0,"name":null,"span":{"filename":"src/lib.rs","begin":[588,23],"end":[588,32]},"visibility":"default","docs":null,"links":{},"attrs":["#[automatically_derived]"],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":["ne"],"trait":{"name":"PartialEq","id":"2:2324:203","args":{"angle_bracketed":{"args":[{"type":{"resolved_path":{"name":"Crate","id":"0:477:1773","args":{"angle_bracketed":{"args":[],"bindings":[]}}}}}],"bindings":[]}}},"for":{"resolved_path":{"name":"Crate","id":"0:477:1773","args":{"angle_bracketed":{"args":[],"bindings":[]}}}},"items":["0:486:652"],"negative":false,"synthetic":false,"blanket_impl":null}}},"0:342:1805":{"id":"0:342:1805","crate_id":0, ...}, ...}, ...}
以下の規則に従ってシリアライズ[1]されているようです。
- 結果は平坦化されている(木構造ではない)
- itemが
0:0:1862
などのようにユニークなIDで識別されている - トップレベルには以下のキーがある:
-
root
: rootモジュールのID -
crate_version
: 対象crateのバージョン -
includes_private
: 非公開APIの情報も含めているかどうか -
index
: 公開API情報の本体(key: ID, value: item) -
paths
: IDと(一意に特定できる)パスの対応 -
external_crates
: 外部crateの番号と名前・URLの対応 -
format_version
: このフォーマットのバージョン。本記事執筆時点の最新版であるrustdoc 1.73.0では27
-
-
pub use foo::{bar, baz};
などのようなimportは1つずつitemとして登録される -
pub use foo::*;
などのようなglob importも1つのitemとして登録される
厄介なのが木構造ではないという点で、例えばあるstructのフィールドを見るにしてもindexの中から探す必要があります。
そこでこのJSONをdeserializeしてASTのようにvisitできるcrateを作りました。
使い方
CrateBuilder
でJSONに入っていた情報を保持するCrate
を作成します。
use crate_inspector::CrateBuilder;
let builder = CrateBuilder::default()
.toolchain("nightly")
.manifest_path("Cargo.toml");
let krate = builder.build().unwrap();
モジュールやブロックなど中にアイテムを持てるアイテムはitems
メソッドでイテレータを取得できます。
さらにその中からstructだけを取り出したい場合はstructs
メソッド、enum
を取り出したい場合はenums
メソッド、といった具合にメソッドが定義されています。
for item in krate.items() {
println!("item: {:?}", item.name);
}
for strc in krate.structs() {
println!("struct: {}", strc.name());
println!("#impls: {}", strc.impls().count());
}
for enm in krate.enums() {
println!("enum: {}", enm.name());
println!("variants: {:?}", enm.variants().collect::<Vec<_>>());
println!("#methods: {}", enm.impls().fold(0, |acc, i| acc + i.functions().count()));
}
structのitemはStructItem<'_>
などといった風に、itemのサブタイプのように振舞います。
***Item<'_>
はCrate
の参照を持っており、フィールドやメソッドの列挙などができます。
itemのダウンキャストはCrate.downcast
メソッドで行えます。
for item in krate.items() {
if let Some(strc) = krate.downcast::<StructItem>(item) {
println!("struct: {}", strc.name());
}
}
また、特定のitemを取得したい場合はCrate.get_*
メソッドが使えます。
if let Some(item) = krate.get_item("format") {
println!("item: {:?}", item.id);
}
if let Some(strc) = krate.get_struct("Crate") {
println!("struct: {}", strc.name());
}
使用可能なメソッドの一覧はAPIドキュメントを参照してください。
Acknowledgement
crate-inspector
は2023年度未踏IT人材発掘・育成事業に採択されたプロジェクト「Pythonにトランスパイル可能な静的型付けプログラミング言語の開発」の開発の一環として作成し、独立したcrateとして公開したものです。
プロジェクトの成果は2024年2月17/18日に発表される予定です(案内ページ、2022年度の様子)。
プロジェクトの内容やcrate-inspector
がどう使われたのか気になった方はぜひ発表をご覧ください。
Discussion