mod build_image;
mod exec;
mod init;
mod list_images;
mod read;
mod remove_image;
mod snapshot;
mod version;
mod write;
use std::fmt;
use bson::{Bson, Document, doc};
pub use build_image::{BuildImageCall, BuildImageResp};
pub use exec::{ExecCall, ExecResp};
pub use init::{InitCall, InitResp, InvalidMount, InvalidPort, MountSpec, Port};
pub use list_images::{ListImagesCall, ListImagesResp};
pub use read::{ReadCall, ReadResp};
pub use remove_image::{RemoveImageCall, RemoveImageResp};
use serde::{
Deserialize, Deserializer, Serialize, Serializer,
de::{self, DeserializeOwned, MapAccess, Visitor},
ser::SerializeMap,
};
pub use snapshot::{SnapshotCall, SnapshotResp};
pub use version::{VersionCall, VersionResp};
pub use write::{WriteCall, WriteResp};
use super::Method;
use crate::protocol::Error;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "method", content = "params", rename_all = "snake_case")]
pub enum Call {
Version(VersionCall),
BuildImage(BuildImageCall),
RemoveImage(RemoveImageCall),
ListImages(ListImagesCall),
Init(InitCall),
Exec(ExecCall),
Read(ReadCall),
Write(WriteCall),
Snapshot(SnapshotCall),
}
impl Call {
pub fn method(&self) -> Method {
match self {
Call::Version(_) => Method::Version,
Call::BuildImage(_) => Method::BuildImage,
Call::RemoveImage(_) => Method::RemoveImage,
Call::ListImages(_) => Method::ListImages,
Call::Init(_) => Method::Init,
Call::Exec(_) => Method::Exec,
Call::Read(_) => Method::Read,
Call::Write(_) => Method::Write,
Call::Snapshot(_) => Method::Snapshot,
}
}
pub(super) fn from_params<E: de::Error>(method: Method, params: Bson) -> Result<Self, E> {
if method.is_notification() {
return Err(E::custom(format!(
"{method} is a notification and cannot carry an id"
)));
}
let mut object = doc! { "method": method.as_str() };
object.insert(
"params",
match params {
Bson::Null => Bson::Document(bson::Document::new()),
params => params,
},
);
bson::deserialize_from_bson(Bson::Document(object))
.map_err(|e| E::custom(format!("{method} params: {e}")))
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Response {
Version(VersionResp),
BuildImage(BuildImageResp),
RemoveImage(RemoveImageResp),
ListImages(ListImagesResp),
Init(InitResp),
Exec(ExecResp),
Read(ReadResp),
Write(WriteResp),
Snapshot(SnapshotResp),
Error(Error),
}
impl Response {
pub fn method(&self) -> Option<Method> {
Some(match self {
Response::Version(_) => Method::Version,
Response::BuildImage(_) => Method::BuildImage,
Response::RemoveImage(_) => Method::RemoveImage,
Response::ListImages(_) => Method::ListImages,
Response::Snapshot(_) => Method::Snapshot,
Response::Init(_) => Method::Init,
Response::Exec(_) => Method::Exec,
Response::Read(_) => Method::Read,
Response::Write(_) => Method::Write,
Response::Error(_) => return None,
})
}
pub fn error(&self) -> Option<&Error> {
match self {
Response::Error(error) => Some(error),
_ => None,
}
}
pub(super) fn from_members<E: de::Error>(
method: Option<Method>,
result: Option<Bson>,
error: Option<Bson>,
) -> Result<Self, E> {
let mut object = Document::new();
if let Some(method) = method {
object.insert("method", method.as_str());
}
if let Some(result) = result {
object.insert("result", result);
}
if let Some(error) = error {
object.insert("error", error);
}
bson::deserialize_from_bson(Bson::Document(object)).map_err(E::custom)
}
}
impl Serialize for Response {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
let mut map = s.serialize_map(None)?;
if let Some(method) = self.method() {
map.serialize_entry("method", method.as_str())?;
}
match self {
Response::Version(version) => map.serialize_entry("result", version)?,
Response::BuildImage(build) => map.serialize_entry("result", build)?,
Response::RemoveImage(remove) => map.serialize_entry("result", remove)?,
Response::ListImages(list) => map.serialize_entry("result", list)?,
Response::Init(init) => map.serialize_entry("result", init)?,
Response::Exec(exec) => map.serialize_entry("result", exec)?,
Response::Read(read) => map.serialize_entry("result", read)?,
Response::Write(write) => map.serialize_entry("result", write)?,
Response::Snapshot(snapshot) => map.serialize_entry("result", snapshot)?,
Response::Error(error) => map.serialize_entry("error", error)?,
}
map.end()
}
}
impl<'de> Deserialize<'de> for Response {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Response, D::Error> {
d.deserialize_map(ResponseVisitor)
}
}
struct ResponseVisitor;
impl<'de> Visitor<'de> for ResponseVisitor {
type Value = Response;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("a JSON-RPC 2.0 response's result or error")
}
fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Response, A::Error> {
let mut method: Option<Method> = None;
let mut result: Option<Bson> = None;
let mut error: Option<Error> = None;
while let Some(key) = map.next_key::<String>()? {
match key.as_str() {
"method" => {
let name = map.next_value::<String>()?;
method =
Some(Method::parse(&name).ok_or_else(|| {
de::Error::custom(format!("unknown method {name:?}"))
})?);
}
"result" => result = Some(map.next_value()?),
"error" => error = Some(map.next_value()?),
_ => {
map.next_value::<de::IgnoredAny>()?;
}
}
}
match (result, error) {
(Some(result), None) => {
let method = method
.ok_or_else(|| de::Error::custom("a result needs the method it answers"))?;
typed_result(method, result)
}
(None, Some(error)) => Ok(Response::Error(error)),
(Some(_), Some(_)) => Err(de::Error::custom("both a result and an error")),
(None, None) => Err(de::Error::custom("neither a result nor an error")),
}
}
}
fn typed_result<E: de::Error>(method: Method, result: Bson) -> Result<Response, E> {
Ok(match method {
Method::Version => Response::Version(payload(method, result)?),
Method::BuildImage => Response::BuildImage(payload(method, result)?),
Method::RemoveImage => Response::RemoveImage(payload(method, result)?),
Method::ListImages => Response::ListImages(payload(method, result)?),
Method::Snapshot => Response::Snapshot(payload(method, result)?),
Method::Init => Response::Init(payload(method, result)?),
Method::Exec => Response::Exec(payload(method, result)?),
Method::Read => Response::Read(payload(method, result)?),
Method::Write => Response::Write(payload(method, result)?),
Method::Start | Method::Stop | Method::Quit => {
return Err(E::custom(format!(
"{method} is a notification and is not answered"
)));
}
})
}
fn payload<T: DeserializeOwned, E: de::Error>(method: Method, result: Bson) -> Result<T, E> {
bson::deserialize_from_bson(result).map_err(|e| E::custom(format!("{method} result: {e}")))
}