anp 0.7.0

Rust SDK for Agent Network Protocol (ANP)
Documentation
use std::collections::BTreeMap;

use anp::authentication::{
    create_did_wba_document, generate_auth_header, generate_http_signature_headers,
    DidDocumentOptions, DidProfile,
};
use serde_json::json;

fn main() {
    let args = std::env::args().skip(1).collect::<Vec<String>>();
    if args.is_empty() {
        eprintln!("Usage: cargo run --example interop_cli -- <did-fixture|auth-fixture> [options]");
        std::process::exit(1);
    }

    match args[0].as_str() {
        "did-fixture" => run_did_fixture(&args[1..]),
        "auth-fixture" => run_auth_fixture(&args[1..]),
        other => {
            eprintln!("Unsupported subcommand: {}", other);
            std::process::exit(1);
        }
    }
}

fn run_did_fixture(args: &[String]) {
    let profile = read_option(args, "--profile").unwrap_or_else(|| "e1".to_string());
    let hostname = read_option(args, "--hostname").unwrap_or_else(|| "example.com".to_string());
    let bundle = create_bundle(&hostname, &profile);
    println!(
        "{}",
        serde_json::to_string(&json!({
            "profile": profile,
            "did_document": bundle.did_document,
            "keys": bundle.keys,
        }))
        .expect("fixture should serialize")
    );
}

fn run_auth_fixture(args: &[String]) {
    let profile = read_option(args, "--profile").unwrap_or_else(|| "e1".to_string());
    let hostname = read_option(args, "--hostname").unwrap_or_else(|| "example.com".to_string());
    let scheme = read_option(args, "--scheme").unwrap_or_else(|| "http".to_string());
    let service_domain = read_option(args, "--service-domain")
        .unwrap_or_else(|| "api.example.com".to_string());
    let request_url = read_option(args, "--url")
        .unwrap_or_else(|| format!("https://{}/orders", service_domain));
    let request_method = read_option(args, "--method").unwrap_or_else(|| "GET".to_string());
    let body = read_option(args, "--body").unwrap_or_default();

    let bundle = create_bundle(&hostname, &profile);
    let private_key = bundle
        .load_private_key("key-1")
        .expect("private key should load");

    let output = match scheme.as_str() {
        "legacy" => {
            let header = generate_auth_header(
                &bundle.did_document,
                &service_domain,
                &private_key,
                "1.1",
            )
            .expect("legacy auth header should generate");
            json!({
                "profile": profile,
                "scheme": scheme,
                "service_domain": service_domain,
                "did_document": bundle.did_document,
                "keys": bundle.keys,
                "headers": {"Authorization": header},
            })
        }
        "http" => {
            let body_bytes = if body.is_empty() { None } else { Some(body.as_bytes()) };
            let headers = generate_http_signature_headers(
                &bundle.did_document,
                &request_url,
                &request_method,
                &private_key,
                Some(&BTreeMap::new()),
                body_bytes,
                Default::default(),
            )
            .expect("HTTP signature headers should generate");
            json!({
                "profile": profile,
                "scheme": scheme,
                "service_domain": service_domain,
                "request_url": request_url,
                "request_method": request_method,
                "body": body,
                "did_document": bundle.did_document,
                "keys": bundle.keys,
                "headers": headers,
            })
        }
        other => panic!("Unsupported scheme: {}", other),
    };

    println!("{}", serde_json::to_string(&output).expect("fixture should serialize"));
}

fn create_bundle(hostname: &str, profile: &str) -> anp::authentication::DidDocumentBundle {
    let did_profile = DidProfile::from_str(profile).expect("profile must be one of: e1, k1, plain_legacy");
    create_did_wba_document(
        hostname,
        DidDocumentOptions::default()
            .with_profile(did_profile)
            .with_path_segments(["user", "interop"]),
    )
    .expect("DID fixture should be created")
}

fn read_option(args: &[String], flag: &str) -> Option<String> {
    args.windows(2)
        .find(|window| window[0] == flag)
        .map(|window| window[1].clone())
}