subako_function_sdk/
lib.rs1#![cfg_attr(
47 not(test),
48 deny(
49 clippy::unwrap_used,
50 clippy::expect_used,
51 clippy::panic,
52 clippy::unreachable,
53 clippy::todo,
54 clippy::unimplemented,
55 clippy::indexing_slicing,
56 )
57)]
58
59pub mod http;
60mod package;
61
62pub use package::{Function, MANIFEST_FLAG, Manifest, ManifestFunction, Package};
63pub use subako_function_sdk_macros::{function, package};
64
65use std::io::{Read as _, Write as _};
66use std::process::ExitCode;
67
68use serde::Serialize;
69use serde::de::DeserializeOwned;
70
71pub struct Input(serde_json::Value);
73
74impl Input {
75 pub fn parse<T: DeserializeOwned>(self) -> Result<T, ToolError> {
76 serde_json::from_value(self.0).map_err(|e| ToolError::new(format!("invalid input: {e}")))
77 }
78}
79
80pub struct Output(serde_json::Value);
82
83impl Output {
84 pub fn json<T: Serialize>(value: &T) -> Result<Output, ToolError> {
85 serde_json::to_value(value)
86 .map(Output)
87 .map_err(|e| ToolError::new(format!("output is not JSON: {e}")))
88 }
89}
90
91#[derive(Debug, Clone, PartialEq, Eq)]
93pub struct ToolError(String);
94
95impl ToolError {
96 pub fn new(message: impl Into<String>) -> Self {
97 ToolError(message.into())
98 }
99
100 pub fn unknown_tool(tool: &str) -> Self {
101 ToolError(format!("unknown tool: {tool}"))
102 }
103
104 pub fn message(&self) -> &str {
105 &self.0
106 }
107}
108
109impl From<String> for ToolError {
110 fn from(message: String) -> Self {
111 ToolError(message)
112 }
113}
114
115impl From<&str> for ToolError {
116 fn from(message: &str) -> Self {
117 ToolError(message.to_string())
118 }
119}
120
121impl From<std::io::Error> for ToolError {
122 fn from(error: std::io::Error) -> Self {
123 ToolError(error.to_string())
124 }
125}
126
127pub fn execute(
130 args: &[String],
131 stdin: &[u8],
132 route: impl FnOnce(&str, Input) -> Result<Output, ToolError>,
133) -> (Vec<u8>, u8) {
134 let result = match (args.get(1), serde_json::from_slice(stdin)) {
135 (None, _) => Err(ToolError::new("no tool named on the command line")),
136 (Some(_), Err(e)) => Err(ToolError::new(format!("input is not JSON: {e}"))),
137 (Some(tool), Ok(value)) => route(tool, Input(value)),
138 };
139 match result {
140 Ok(Output(value)) => (encode(&value), 0),
141 Err(ToolError(message)) => (encode(&serde_json::json!({ "error": message })), 1),
142 }
143}
144
145pub fn run(route: impl FnOnce(&str, Input) -> Result<Output, ToolError>) -> ExitCode {
148 let args: Vec<String> = std::env::args().collect();
149 let mut stdin = Vec::new();
150 if let Err(e) = std::io::stdin().read_to_end(&mut stdin) {
151 let (out, code) = (
152 encode(&serde_json::json!({ "error": format!("stdin unreadable: {e}") })),
153 1,
154 );
155 return finish(&out, code);
156 }
157 let (out, code) = execute(&args, &stdin, route);
158 finish(&out, code)
159}
160
161fn finish(out: &[u8], code: u8) -> ExitCode {
163 let mut stdout = std::io::stdout().lock();
164 match stdout.write_all(out).and_then(|()| stdout.flush()) {
165 Ok(()) => ExitCode::from(code),
166 Err(_) => ExitCode::from(2),
168 }
169}
170
171fn encode(value: &serde_json::Value) -> Vec<u8> {
172 serde_json::to_vec(value).unwrap_or_default()
174}