use crate::types::{JobClaim, LogEntry, Task, WorkerCapabilities};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct HelloFrame {
pub auth_token: String,
pub capabilities: WorkerCapabilities,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum WorkerInbound {
Hello(HelloFrame),
#[serde(rename_all = "camelCase")]
Heartbeat {
capabilities: WorkerCapabilities,
#[serde(default, skip_serializing_if = "Option::is_none")]
current_job_id: Option<String>,
},
#[serde(rename_all = "camelCase")]
Accept {
job_id: String,
},
#[serde(rename_all = "camelCase")]
Reject {
job_id: String,
reason: String,
},
#[serde(rename_all = "camelCase")]
CompleteJson {
job_id: String,
result: serde_json::Value,
#[serde(default, skip_serializing_if = "Option::is_none")]
prompt: Option<String>,
},
#[serde(rename_all = "camelCase")]
Fail {
job_id: String,
error: String,
retryable: bool,
},
LogBatch {
entries: Vec<LogEntry>,
},
ReadyForMore,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct JobOfferClaim {
pub job_id: String,
pub game_id: String,
pub asset_name: String,
pub model: String,
pub vram_gb_estimate: f32,
#[serde(default)]
pub prompt: String,
#[serde(default = "default_image_ext")]
pub ext: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub task: Option<Task>,
}
impl JobOfferClaim {
pub fn into_job_claim(self) -> JobClaim {
JobClaim {
job_id: self.job_id,
game_id: self.game_id,
asset_name: self.asset_name,
model: self.model,
vram_gb_estimate: self.vram_gb_estimate,
prompt: self.prompt,
ext: self.ext,
task: self.task,
}
}
}
fn default_image_ext() -> String {
"webp".into()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum WorkerErrorCode {
AuthFailed,
ProtocolViolation,
DuplicateWorker,
WorkerDeleted,
InternalError,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum WorkerOutbound {
#[serde(rename_all = "camelCase")]
Welcome {
worker_id: String,
server_time: String,
},
Offer {
claim: JobOfferClaim,
},
HeartbeatAck,
#[serde(rename_all = "camelCase")]
CompleteAck {
job_id: String,
},
#[serde(rename_all = "camelCase")]
FailAck {
job_id: String,
},
Error {
code: WorkerErrorCode,
message: String,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u16)]
pub enum WsCloseCode {
Normal = 1000,
AuthFailed = 4001,
ProtocolViolation = 4002,
DuplicateWorker = 4003,
WorkerDeleted = 4004,
}
impl WsCloseCode {
pub fn from_error_code(code: WorkerErrorCode) -> Self {
match code {
WorkerErrorCode::AuthFailed => Self::AuthFailed,
WorkerErrorCode::ProtocolViolation => Self::ProtocolViolation,
WorkerErrorCode::DuplicateWorker => Self::DuplicateWorker,
WorkerErrorCode::WorkerDeleted => Self::WorkerDeleted,
WorkerErrorCode::InternalError => Self::Normal,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn job_offer_claim_into_job_claim_preserves_fields() {
let offer = JobOfferClaim {
job_id: "j".into(),
game_id: "g".into(),
asset_name: "g/x/y".into(),
model: "m".into(),
vram_gb_estimate: 1.5,
prompt: "hi".into(),
ext: "png".into(),
task: None,
};
let claim = offer.into_job_claim();
assert_eq!(claim.job_id, "j");
assert_eq!(claim.model, "m");
assert_eq!(claim.vram_gb_estimate, 1.5);
assert_eq!(claim.ext, "png");
}
#[test]
fn job_offer_claim_uses_default_ext_when_missing() {
let json = json!({
"jobId": "j",
"gameId": "g",
"assetName": "g/x/y",
"model": "m",
"vramGbEstimate": 1.0,
});
let parsed: JobOfferClaim = serde_json::from_value(json).unwrap();
assert_eq!(parsed.ext, "webp");
assert_eq!(parsed.prompt, "");
}
}