//! Raw discovery and lifecycle guest for activation protocol tests. use serde_json::{Value, json}; use std::io::{self, BufRead, Write}; /// Emits one flushed JSON-RPC response. fn send(value: Value) { println!("{value}"); io::stdout().flush().unwrap(); } /// Calls a provider after activation releases an old consumer process. fn deferred(request: &Value, plugin: &str, lines: &mut impl Iterator>) { send( json!({"jsonrpc":"2.0","id":request["id"],"result":std::env::var("SAUSAGE_PID").unwrap()}), ); while !std::path::Path::new(&format!("/state/{plugin}/release-call")).exists() { std::thread::sleep(std::time::Duration::from_millis(1)); } send(json!({"jsonrpc":"2.0","id":"deferred","method":"notes.legacy","params":{}})); for line in lines { let response: Value = serde_json::from_str(&line.unwrap()).unwrap(); if response["id"] == "deferred" { std::fs::write(format!("/state/{plugin}/call-result"), response.to_string()).unwrap(); break; } } } /// Answers a method that version two removes from its interface. fn legacy(request: &Value, document: &Value, version: &str) { if document["methods"] .as_array() .unwrap() .iter() .any(|m| m["name"] == "legacy") { send(json!({"jsonrpc":"2.0","id":request["id"],"result":version})); } else { send( json!({"jsonrpc":"2.0","id":request["id"],"error":{"code":-32601,"message":"legacy removed","data":{"version":version}}}), ); } } /// Reads a version-specific discovery document without the SDK. fn main() { let version = env!("FIXTURE_VERSION"); let plugin = std::env::var("SAUSAGE_PLUGIN").unwrap(); let mut lines = io::stdin().lock().lines(); let request: Value = serde_json::from_str(&lines.next().unwrap().unwrap()).unwrap(); let document: Value = serde_json::from_slice(&std::fs::read(format!("/state/{plugin}/{version}.json")).unwrap()) .unwrap(); let result = match request["method"].as_str().unwrap() { "deferred" => { deferred(&request, &plugin, &mut lines); return; } "legacy" => { legacy(&request, &document, version); return; } "rpc.discover" => { if document["x-test-wait"] == true { std::fs::write(format!("/state/{plugin}/discover-started"), "ready").unwrap(); while !std::path::Path::new(&format!("/state/{plugin}/discover-release")).exists() { std::thread::sleep(std::time::Duration::from_millis(1)); } } document } "read" => { let text = std::fs::read_to_string(request["params"]["path"].as_str().unwrap()).unwrap(); serde_json::from_str(&text).unwrap_or(json!(text)) } "rpc.retired" => { std::fs::write( format!("/state/{plugin}/retired"), request["params"]["previous"].as_str().unwrap(), ) .unwrap(); json!({"retired":version}) } "serve" | "hold" => { send(json!({"jsonrpc":"2.0","id":request["id"],"result":{"version":version}})); if request["method"] == "serve" { let exclusive = format!("/run/{plugin}-service"); if std::fs::OpenOptions::new() .write(true) .create_new(true) .open(&exclusive) .is_err() { std::fs::write(format!("/state/{plugin}/duplicate-service"), "duplicate") .unwrap(); } std::fs::write(format!("/state/{plugin}/service"), version).unwrap(); } for _ in lines {} if request["method"] == "serve" { let _ = std::fs::remove_file(format!("/run/{plugin}-service")); } std::fs::write(format!("/state/{plugin}/ended-{version}"), "ended").unwrap(); return; } _ => json!({"version":version}), }; send(json!({"jsonrpc":"2.0","id":request["id"],"result":result})); if request["method"] == "rpc.discover" && result["x-test-linger"] == true { if result["x-test-stubborn"] == true { while !std::path::Path::new(&format!("/state/{plugin}/discovery-exit")).exists() { std::thread::sleep(std::time::Duration::from_millis(1)); } } else { for _ in lines {} } std::fs::write( format!("/state/{plugin}/discovery-ended-{version}"), "ended", ) .unwrap(); } }