ej_builder_sdk/
lib.rs

1//! Builder SDK for the EJ framework.
2//!
3//! Provides communication interface between builders and the EJ dispatcher.
4//!
5//! # Usage
6//!
7//! ```rust, no_run
8//! use ej_builder_sdk::{BuilderSdk, BuilderEvent};
9//!
10//! #[tokio::main]
11//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
12//!     let sdk = BuilderSdk::init(|event| {
13//!         match event {
14//!             BuilderEvent::Exit => {
15//!                 // Cleanup logic here
16//!                 println!("Received exit signal");
17//!                 std::process::exit(0);
18//!             }
19//!         }
20//!     }).await.unwrap();
21//!     
22//!     // Builder logic here
23//!     Ok(())
24//! }
25//! ```
26
27use std::{env::args, path::PathBuf};
28
29use serde::{Deserialize, Serialize};
30use tokio::{
31    io::{AsyncReadExt, AsyncWriteExt},
32    net::{
33        UnixStream,
34        unix::{OwnedReadHalf, OwnedWriteHalf},
35    },
36};
37use tracing::info;
38
39use crate::prelude::*;
40pub mod error;
41pub mod prelude;
42
43/// Events sent from the dispatcher to the builder.
44#[derive(Debug, Clone, Serialize, Deserialize)]
45pub enum BuilderEvent {
46    /// Request to exit the builder.
47    Exit,
48}
49
50/// Responses sent from the builder to the dispatcher.
51#[derive(Debug, Clone, Serialize, Deserialize)]
52pub enum BuilderResponse {
53    /// Acknowledge receipt of an event.
54    Ack,
55}
56#[derive(Debug, Clone, Copy)]
57pub enum Action {
58    Build,
59    Run,
60}
61
62impl TryFrom<&str> for Action {
63    type Error = Error;
64
65    fn try_from(value: &str) -> Result<Self> {
66        if value == "build" {
67            return Ok(Action::Build);
68        }
69        if value == "run" {
70            return Ok(Action::Run);
71        }
72        Err(Error::InvalidAction(String::from(value)))
73    }
74}
75
76impl From<Action> for &'static str {
77    fn from(value: Action) -> Self {
78        match value {
79            Action::Build => "build",
80            Action::Run => "run",
81        }
82    }
83}
84
85impl From<Action> for String {
86    fn from(value: Action) -> Self {
87        let value: &str = value.into();
88        Self::from(value)
89    }
90}
91
92/// Builder SDK for communicating with the EJ dispatcher.
93///
94/// Handles Unix socket communication and event processing between
95/// the builder and dispatcher.
96pub struct BuilderSdk {
97    /// The board configuration name.
98    board_config_name: String,
99    /// The path to the config.toml file.
100    config_path: String,
101    /// The action the script should take.
102    action: Action,
103}
104
105impl BuilderSdk {
106    /// Initialize the builder SDK and start event processing.
107    ///
108    /// Sets up Unix socket communication with the dispatcher and starts
109    /// an async event loop to handle incoming events.
110    ///
111    /// # Arguments
112    ///
113    /// * `event_callback` - Function called when events are received
114    ///
115    /// # Examples
116    ///
117    /// ```rust,no_run
118    /// use ej_builder_sdk::{BuilderSdk, BuilderEvent};
119    /// # tokio_test::block_on(async {
120    /// let sdk = BuilderSdk::init(|event| {
121    ///     match event {
122    ///         BuilderEvent::Exit => std::process::exit(0),
123    ///     }
124    /// }).await.unwrap();
125    /// # });
126    /// ```
127    pub async fn init<F>(event_callback: F) -> Result<Self>
128    where
129        F: Fn(BuilderEvent) + Send + Sync + 'static,
130    {
131        let args: Vec<String> = std::env::args().into_iter().collect();
132        if args.len() < 5 {
133            return Err(Error::MissingArgs(5, args.len()));
134        }
135
136        let stream = UnixStream::connect(&args[4]).await?;
137        tokio::spawn(async move { BuilderSdk::start_event_loop(stream, event_callback) });
138        let action: Action = TryFrom::<&str>::try_from(&args[1])?;
139
140        Ok(Self {
141            config_path: args[2].clone(),
142            board_config_name: args[3].clone(),
143            action,
144        })
145    }
146    /// Get the board configuration name.
147    pub fn board_config_name(&self) -> &str {
148        &self.board_config_name
149    }
150    /// Get the path to the config.toml file.
151    pub fn config_path(&self) -> PathBuf {
152        PathBuf::from(&self.config_path)
153    }
154    /// Get the action this script should take
155    pub fn action(&self) -> Action {
156        self.action
157    }
158    /// Parse event data from JSON string.
159    fn parse_event(payload: &str) -> Result<BuilderEvent> {
160        Ok(serde_json::from_str(payload)?)
161    }
162    /// Start the event loop for processing dispatcher messages.
163    async fn start_event_loop<F>(stream: UnixStream, cb: F) -> Result<()>
164    where
165        F: Fn(BuilderEvent) + Send + Sync + 'static,
166    {
167        let mut payload = String::new();
168        let (mut rx, mut tx) = stream.into_split();
169
170        loop {
171            match rx.read_to_string(&mut payload).await {
172                Ok(0) => break,
173                Ok(n) => {
174                    let event = BuilderSdk::parse_event(&payload)?;
175                    info!("Received event from builder {:?}", event);
176                    cb(event);
177                    info!("Acking event to builder");
178                    let response = serde_json::to_string(&BuilderResponse::Ack)?;
179                    tx.write_all(response.as_bytes()).await;
180                    tx.write_all(b"\n").await;
181                    tx.flush().await;
182                }
183                Err(e) => return Err(Error::from(e)),
184            }
185        }
186        Ok(())
187    }
188}