1#![deny(warnings)]
2#![deny(clippy::all)]
3
4use failure::{Error, ResultExt};
5use log::*;
6use serde::de::DeserializeOwned;
7use tonic::transport::Endpoint;
8use tower::service_fn;
9
10mod bridge;
11mod error;
12mod stdio;
13mod utils;
14
15pub mod oci;
16pub mod options;
17
18use oci::ImageSpecification;
19
20pub use self::bridge::Bridge;
21pub use self::error::ErrorCode;
22pub use self::options::Options;
23pub use self::stdio::{stdio_connector, StdioSocket};
24pub use self::utils::{ErrorWithCauses, OutputRef};
25
26#[tonic::async_trait]
27pub trait Frontend<O = Options>
28where
29 O: DeserializeOwned,
30{
31 async fn run(self, bridge: Bridge, options: O) -> Result<FrontendOutput, Error>;
32}
33
34pub struct FrontendOutput {
35 output: OutputRef,
36 image_spec: Option<ImageSpecification>,
37}
38
39impl FrontendOutput {
40 pub fn with_ref(output: OutputRef) -> Self {
41 Self {
42 output,
43 image_spec: None,
44 }
45 }
46
47 pub fn with_spec_and_ref(spec: ImageSpecification, output: OutputRef) -> Self {
48 Self {
49 output,
50 image_spec: Some(spec),
51 }
52 }
53}
54
55pub async fn run_frontend<F, O>(frontend: F) -> Result<(), Error>
56where
57 F: Frontend<O>,
58 O: DeserializeOwned,
59{
60 let channel = {
61 Endpoint::from_static("http://[::]:50051")
62 .connect_with_connector(service_fn(stdio_connector))
63 .await?
64 };
65
66 let bridge = Bridge::new(channel);
67
68 match frontend_entrypoint(&bridge, frontend).await {
69 Ok(output) => {
70 bridge
71 .finish_with_success(output.output, output.image_spec)
72 .await
73 .context("Unable to send a success result")?;
74 }
75
76 Err(error) => {
77 let error = ErrorWithCauses::multi_line(error);
78
79 error!("Frontend entrypoint failed: {}", error);
80
81 bridge
83 .finish_with_error(
84 ErrorCode::Unknown,
85 ErrorWithCauses::single_line(error.into_inner()).to_string(),
86 )
87 .await
88 .context("Unable to send an error result")?;
89 }
90 }
91
92 Ok(())
95}
96
97async fn frontend_entrypoint<F, O>(bridge: &Bridge, frontend: F) -> Result<FrontendOutput, Error>
98where
99 F: Frontend<O>,
100 O: DeserializeOwned,
101{
102 let options = options::from_env(std::env::vars()).context("Unable to parse options")?;
103
104 debug!("running a frontend entrypoint");
105 frontend.run(bridge.clone(), options).await
106}