Skip to content

Commit

Permalink
fix: fix compatibilities issues w/ api and docs
Browse files Browse the repository at this point in the history
* fix: Implement new communication protocol
* docs: internal_api & main
* fix: Add carriage return on send
* chore: renamed crate's models
* docs: documented api module
* fix: parse json with serialize_optionnal_string & docs
* fix: serde mapping & fix reading

Signed-off-by: Alexis-Bernard <[email protected]>
Signed-off-by: WoodenMaiden <[email protected]>
  • Loading branch information
alexis-ascoz authored and WoodenMaiden committed Apr 17, 2023
1 parent eee63df commit 2ee2be9
Show file tree
Hide file tree
Showing 12 changed files with 459 additions and 229 deletions.
3 changes: 3 additions & 0 deletions agent/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,6 @@ thiserror = "1.0.32"
[lib]
name = "agent_lib"
path = "lib/src/lib.rs"

[dev-dependencies]
rand = "0.8.5"
67 changes: 67 additions & 0 deletions agent/lib/src/api/comms.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// This message is sent to the API server to indicate wether
// the agent is ready or not to receive messages.

pub const MESSAGE_SIZE_NB_BYTES: usize = 8;

/// Represents a message sent by the agent
pub struct Message {
/// These are characters e.g. 00002048
pub message_size: [u8; MESSAGE_SIZE_NB_BYTES],
/// stringified json, vec because size is unknown
pub message: Vec<u8>
}

impl Message {
pub fn new(message_to_send: String) -> Self {
let mut message_size = [0; MESSAGE_SIZE_NB_BYTES];
let message = message_to_send.as_bytes().to_vec();

let string_size = format!("{:0>8}", message.len());
//We can't call directly as bytes as both &str and String sizes are not known at
//compile time unlike message_size

for (i, c) in string_size.chars().enumerate() {
message_size[i] = c as u8;
}

Self {
message_size,
message
}
}

pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
bytes.extend_from_slice(&self.message_size);
bytes.extend_from_slice(&self.message);
bytes
}


}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn new_message_well_encoded() {
let message_data = "Hello world".to_string();
let message = Message::new(message_data);
assert_eq!(message.message, [72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]);
assert_eq!(message.message_size, [48, 48, 48, 48, 48, 48, 49, 49]);

assert_eq!(message.to_bytes(), [48, 48, 48, 48, 48, 48, 49, 49, 72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]);
}


#[test]
fn message_size_badly_encoded() {
let message_data = "Hello world".to_string();
let message = Message::new(message_data);
assert_eq!(message.message, [72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]);
assert_ne!(message.message_size, [48, 48, 48, 48, 48, 48, 49, 50]); // should be 11, is 12
}

}

File renamed without changes.
188 changes: 188 additions & 0 deletions agent/lib/src/api/model.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
use serde::{Deserialize, Serialize};


/// Represents a file to be included in the workspace
#[derive(Deserialize, Serialize, Debug, PartialEq, Eq)]
pub struct FileModel {
/// Name of the file, paths relative to the workspace
pub filename: String,
/// Content of the file
pub content: String,
}

/// Identifies the type of the message
#[derive(Deserialize, Serialize, Debug, PartialEq, Eq)]
pub enum Type {
/// Status message to indicate that the agent is ready
#[serde(rename = "status")]
Status,
/// Request message
#[serde(rename = "request")]
Request,
/// Response message answering to a request message
#[serde(rename = "response")]
Response,
}

/// Code to tell what the Request/Response message is about
#[derive(Deserialize, Serialize, Debug, PartialEq, Eq)]
pub enum Code {
/// Represents a request to run the code or a response to such request
#[serde(rename = "run")]
Run,
/// Agent is ready to communicate
#[serde(rename = "ready")]
Ready,
}

/// Represents a Status message
#[derive(Deserialize, Serialize, Debug)]
pub struct StatusMessage {
/// Type of the message
pub r#type: Type,
/// Code of the message
pub code: Code,
}

impl StatusMessage {
pub fn new(code: Code) -> StatusMessage {
StatusMessage {
// r#type is a reserved keyword in Rust, so we need to use the raw identifier syntax
r#type: Type::Status,
code
}
}
}

impl Default for StatusMessage {
fn default() -> Self {
Self::new(Code::Ready)
}
}

/// Serializes an Option<String> as a String by returning an empty string if the Option is None
fn serialize_optionnal_string<S>(value: &Option<String>, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match value {
Some(v) => serializer.serialize_str(v),
None => serializer.serialize_str(""),
}
}

/// Represents the output of a step
#[derive(Deserialize, Serialize, Debug)]
pub struct ResponseStep {
/// Command that was run
pub command: String,
/// Exit code of the command
#[serde(alias = "exitCode")]
pub exit_code: i32,
/// Stdout of the command. If it is None, it will be serialized as an empty string
/// to avoid api crashes
#[serde(serialize_with = "serialize_optionnal_string")]
pub stdout: Option<String>,
/// Stderr of the command
pub stderr: String,
}

impl ResponseStep {
pub fn new(
command: String,
exit_code: i32,
stdout: Option<String>,
stderr: String,
) -> ResponseStep {
ResponseStep {
command,
exit_code,
stdout,
stderr,
}
}
}

/// Contains the id of the request and the result of all steps
#[derive(Deserialize, Serialize, Debug)]
pub struct ResponseData {
/// Id of the request (UUID)
pub id: String,
/// Result of all steps
pub steps: Vec<ResponseStep>,
}

impl ResponseData {
pub fn new(id: String, steps: Vec<ResponseStep>) -> ResponseData {
ResponseData { id, steps }
}
}

/// Represents a Response message with code Type::Run, meaning that it is a response to a run code request
#[derive(Deserialize, Serialize, Debug)]
pub struct ResponseMessage {
/// Type of the message
pub r#type: Type,
/// Code of the message
pub code: Code,
/// Data of the message
pub data: ResponseData,
}

impl ResponseMessage {
pub fn new(data: ResponseData) -> ResponseMessage {
ResponseMessage {
r#type: Type::Response,
code: Code::Run,
data,
}
}
}

/// Represent a step in the request with type Type::Run
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
pub struct RequestStep {
/// Command to run
pub command: String,
/// Whether the stdout should be returned or not (stderr will alaways be)
#[serde(alias = "enableOutput")]
pub enable_output: bool,
}

/// Represents the data of a request message with type Type::Run
#[derive(Deserialize, Serialize, Debug)]
pub struct RequestData {
/// Id of the request (UUID)
pub id: String,
/// Files to be included in the workspace, paths relative to the workspace
pub files: Vec<FileModel>,
/// Steps to be executed
pub steps: Vec<RequestStep>,
}

impl RequestData {
pub fn new(id: String, files: Vec<FileModel>, steps: Vec<RequestStep>) -> RequestData {
RequestData { id, files, steps }
}
}

/// Represents a Request message with type Type::Run
#[derive(Deserialize, Serialize, Debug)]
pub struct RequestMessage {
/// Type of the message
pub r#type: Type,
/// Code of the message
pub code: Code,
/// Data of the message
pub data: RequestData,
}

impl RequestMessage {
pub fn new(data: RequestData) -> RequestMessage {
RequestMessage {
r#type: Type::Request,
code: Code::Run,
data,
}
}
}
Loading

0 comments on commit 2ee2be9

Please sign in to comment.