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.
96#[derive(Debug, Clone)]
97pub struct BuilderSdk {
98    /// The board name.
99    board_name: String,
100    /// The board configuration name.
101    board_config_name: String,
102    /// The path to the config.toml file.
103    config_path: String,
104    /// The action the script should take.
105    action: Action,
106}
107
108impl BuilderSdk {
109    /// Initialize the builder SDK and start event processing.
110    ///
111    /// Sets up Unix socket communication with the dispatcher and starts
112    /// an async event loop to handle incoming events.
113    ///
114    /// # Arguments
115    ///
116    /// * `event_callback` - Function called when events are received
117    ///
118    /// # Examples
119    ///
120    /// ```rust,no_run
121    /// use ej_builder_sdk::{BuilderSdk, BuilderEvent};
122    /// # tokio_test::block_on(async {
123    /// let sdk = BuilderSdk::init(|event| {
124    ///     match event {
125    ///         BuilderEvent::Exit => std::process::exit(0),
126    ///     }
127    /// }).await.unwrap();
128    /// # });
129    /// ```
130    pub async fn init<F>(event_callback: F) -> Result<Self>
131    where
132        F: Fn(&Self, BuilderEvent) + Send + Sync + 'static,
133    {
134        let args: Vec<String> = std::env::args().into_iter().collect();
135        if args.len() < 6 {
136            return Err(Error::MissingArgs(6, args.len()));
137        }
138
139        let action: Action = TryFrom::<&str>::try_from(&args[1])?;
140
141        let stream = UnixStream::connect(&args[5]).await?;
142        let sdk = Self {
143            config_path: args[2].clone(),
144            board_name: args[3].clone(),
145            board_config_name: args[4].clone(),
146            action,
147        };
148        let sdk_loop = sdk.clone();
149
150        tokio::spawn(async move { sdk_loop.start_event_loop(stream, event_callback) });
151        Ok(sdk)
152    }
153    /// Get the action this script should take
154    pub fn action(&self) -> Action {
155        self.action
156    }
157    /// Get the path to the config.toml file.
158    pub fn config_path(&self) -> PathBuf {
159        PathBuf::from(&self.config_path)
160    }
161    /// Get the board name.
162    pub fn board_name(&self) -> &str {
163        &self.board_name
164    }
165    /// Get the board configuration name.
166    pub fn board_config_name(&self) -> &str {
167        &self.board_config_name
168    }
169    /// Parse event data from JSON string.
170    fn parse_event(payload: &str) -> Result<BuilderEvent> {
171        Ok(serde_json::from_str(payload)?)
172    }
173    /// Start the event loop for processing dispatcher messages.
174    async fn start_event_loop<F>(self, stream: UnixStream, cb: F) -> Result<()>
175    where
176        F: Fn(&BuilderSdk, BuilderEvent) + Send + Sync + 'static,
177    {
178        let mut payload = String::new();
179        let (mut rx, mut tx) = stream.into_split();
180
181        loop {
182            match rx.read_to_string(&mut payload).await {
183                Ok(0) => break,
184                Ok(n) => {
185                    let event = BuilderSdk::parse_event(&payload)?;
186                    info!("Received event from builder {:?}", event);
187                    cb(&self, event);
188                    info!("Acking event to builder");
189                    let response = serde_json::to_string(&BuilderResponse::Ack)?;
190                    tx.write_all(response.as_bytes()).await;
191                    tx.write_all(b"\n").await;
192                    tx.flush().await;
193                }
194                Err(e) => return Err(Error::from(e)),
195            }
196        }
197        Ok(())
198    }
199}