alembic_engine/
external.rs1use crate::{ApplyReport, BackendId, Op, ProvisionReport, StateData};
4use alembic_core::{JsonMap, Key, Schema, TypeName};
5use anyhow::Result;
6use serde::{Deserialize, Serialize};
7use std::io::{self, Read, Write};
8
9pub const EXTERNAL_PROTOCOL_VERSION: u8 = 1;
11
12#[derive(Debug, Serialize, Deserialize)]
14pub struct ExternalEnvelope {
15 pub version: u8,
17 #[serde(flatten)]
19 pub request: ExternalRequest,
20}
21
22#[derive(Debug, Serialize, Deserialize)]
24#[serde(tag = "method", rename_all = "snake_case")]
25pub enum ExternalRequest {
26 Read {
28 schema: Schema,
29 types: Vec<TypeName>,
30 state: StateData,
31 },
32 Write {
34 schema: Schema,
35 ops: Vec<Op>,
36 state: StateData,
37 },
38 EnsureSchema { schema: Schema },
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct ExternalObject {
45 pub type_name: TypeName,
47 pub key: Key,
49 pub attrs: JsonMap,
51 #[serde(skip_serializing_if = "Option::is_none")]
53 pub backend_id: Option<BackendId>,
54}
55
56#[derive(Debug, Serialize, Deserialize)]
58pub struct ExternalResponse<T> {
59 pub ok: bool,
61 #[serde(skip_serializing_if = "Option::is_none")]
63 pub result: Option<T>,
64 #[serde(skip_serializing_if = "Option::is_none")]
66 pub error: Option<String>,
67}
68
69impl<T> ExternalResponse<T> {
70 pub fn ok(result: T) -> Self {
72 Self {
73 ok: true,
74 result: Some(result),
75 error: None,
76 }
77 }
78
79 pub fn error(message: impl Into<String>) -> Self {
81 Self {
82 ok: false,
83 result: None,
84 error: Some(message.into()),
85 }
86 }
87
88 pub fn from_result(result: Result<T>) -> Self {
90 match result {
91 Ok(value) => Self::ok(value),
92 Err(err) => Self::error(err.to_string()),
93 }
94 }
95}
96
97pub trait ExternalAdapter {
99 fn read(
101 &mut self,
102 schema: &Schema,
103 types: &[TypeName],
104 state: &StateData,
105 ) -> Result<Vec<ExternalObject>>;
106
107 fn write(&mut self, schema: &Schema, ops: &[Op], state: &StateData) -> Result<ApplyReport>;
109
110 fn ensure_schema(&mut self, schema: &Schema) -> Result<ProvisionReport> {
112 let _ = schema;
113 Ok(ProvisionReport::default())
114 }
115}
116
117pub fn run_external_adapter<A: ExternalAdapter>(mut adapter: A) -> io::Result<()> {
119 let mut input = String::new();
120 io::stdin().read_to_string(&mut input)?;
121 if input.trim().is_empty() {
122 return Ok(());
123 }
124
125 let envelope: ExternalEnvelope = match serde_json::from_str(&input) {
126 Ok(envelope) => envelope,
127 Err(err) => return write_error(format!("invalid request: {err}")),
128 };
129
130 if envelope.version != EXTERNAL_PROTOCOL_VERSION {
131 return write_error(format!(
132 "unsupported protocol version {} (expected {})",
133 envelope.version, EXTERNAL_PROTOCOL_VERSION
134 ));
135 }
136
137 let mut stdout = io::BufWriter::new(io::stdout());
138 match envelope.request {
139 ExternalRequest::Read {
140 schema,
141 types,
142 state,
143 } => {
144 let response = ExternalResponse::from_result(adapter.read(&schema, &types, &state));
145 write_response(&mut stdout, response)
146 }
147 ExternalRequest::Write { schema, ops, state } => {
148 let response = ExternalResponse::from_result(adapter.write(&schema, &ops, &state));
149 write_response(&mut stdout, response)
150 }
151 ExternalRequest::EnsureSchema { schema } => {
152 let response = ExternalResponse::from_result(adapter.ensure_schema(&schema));
153 write_response(&mut stdout, response)
154 }
155 }
156}
157
158fn write_error(message: String) -> io::Result<()> {
159 let mut stdout = io::BufWriter::new(io::stdout());
160 let response = ExternalResponse::<serde_json::Value>::error(message);
161 write_response(&mut stdout, response)
162}
163
164fn write_response<T: Serialize>(
165 out: &mut impl Write,
166 response: ExternalResponse<T>,
167) -> io::Result<()> {
168 serde_json::to_writer(&mut *out, &response).map_err(io::Error::other)?;
169 out.write_all(b"\n")?;
170 out.flush()
171}
172
173#[macro_export]
175macro_rules! alembic_external_main {
176 ($adapter:expr) => {
177 fn main() -> std::io::Result<()> {
178 $crate::external::run_external_adapter($adapter)
179 }
180 };
181}
182
183#[cfg(test)]
184mod tests {
185 use super::ExternalResponse;
186 use serde_json::json;
187
188 #[test]
189 fn external_response_ok_serializes() {
190 let response = ExternalResponse::ok(vec!["one".to_string()]);
191 let value = serde_json::to_value(&response).unwrap();
192 assert_eq!(value, json!({"ok": true, "result": ["one"]}));
193 }
194
195 #[test]
196 fn external_response_error_serializes() {
197 let response: ExternalResponse<Vec<String>> = ExternalResponse::error("boom");
198 let value = serde_json::to_value(&response).unwrap();
199 assert_eq!(value, json!({"ok": false, "error": "boom"}));
200 }
201}