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