Skip to main content

wowlab_common/
node_http.rs

1//! HTTP wire contracts shared by simulation nodes and Sentinel.
2
3use serde::{Deserialize, Serialize};
4
5use crate::WorkContextHash;
6
7/// One registration-time access rule reported by a node.
8#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
9#[serde(rename_all = "camelCase")]
10pub struct NodeRegistrationAccessRule {
11    pub access_type: String,
12    #[serde(skip_serializing_if = "Option::is_none")]
13    pub target_id: Option<String>,
14}
15
16/// Registration details sent to `POST /nodes/register`.
17#[derive(Clone, Deserialize, Eq, PartialEq, Serialize)]
18#[serde(rename_all = "camelCase")]
19pub struct NodeRegistrationRequest {
20    pub token_claim: String,
21    #[serde(skip_serializing_if = "Option::is_none")]
22    pub hostname: Option<String>,
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub total_cores: Option<i32>,
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub enabled_cores: Option<i32>,
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub platform: Option<String>,
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub version: Option<String>,
31    #[serde(default, skip_serializing_if = "Vec::is_empty")]
32    pub access_rules: Vec<NodeRegistrationAccessRule>,
33}
34
35impl std::fmt::Debug for NodeRegistrationRequest {
36    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37        f.debug_struct("NodeRegistrationRequest")
38            .field("token_claim", &"[REDACTED]")
39            .field("hostname", &self.hostname)
40            .field("total_cores", &self.total_cores)
41            .field("enabled_cores", &self.enabled_cores)
42            .field("platform", &self.platform)
43            .field("version", &self.version)
44            .field("access_rules", &self.access_rules)
45            .finish()
46    }
47}
48
49/// Registration response from `POST /nodes/register`.
50#[derive(Clone, Deserialize, Eq, PartialEq, Serialize)]
51#[serde(rename_all = "camelCase")]
52pub struct NodeRegistrationResponse {
53    #[serde(deserialize_with = "deserialize_nullable_string")]
54    pub beacon_token: Option<String>,
55}
56
57impl std::fmt::Debug for NodeRegistrationResponse {
58    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59        f.debug_struct("NodeRegistrationResponse")
60            .field(
61                "beacon_token",
62                &self.beacon_token.as_ref().map(|_| "[REDACTED]"),
63            )
64            .finish()
65    }
66}
67
68/// Beacon-token response from `POST /nodes/token`.
69#[derive(Clone, Deserialize, Eq, PartialEq, Serialize)]
70#[serde(rename_all = "camelCase")]
71pub struct NodeTokenResponse {
72    beacon_token: String,
73}
74
75impl NodeTokenResponse {
76    #[must_use]
77    pub fn new(beacon_token: impl Into<String>) -> Self {
78        Self {
79            beacon_token: beacon_token.into(),
80        }
81    }
82
83    #[must_use]
84    pub fn into_beacon_token(self) -> String {
85        self.beacon_token
86    }
87}
88
89impl std::fmt::Debug for NodeTokenResponse {
90    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91        f.debug_struct("NodeTokenResponse")
92            .field("beacon_token", &"[REDACTED]")
93            .finish()
94    }
95}
96
97/// Success response from `POST /nodes/unlink`.
98#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
99#[serde(rename_all = "camelCase")]
100pub struct NodeUnlinkResponse {
101    success: bool,
102}
103
104impl NodeUnlinkResponse {
105    #[must_use]
106    pub const fn success() -> Self {
107        Self { success: true }
108    }
109
110    #[must_use]
111    pub const fn is_success(self) -> bool {
112        self.success
113    }
114}
115
116/// Immutable work context returned by `GET /jobs/{job_id}/work_context`.
117#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
118#[serde(rename_all = "camelCase")]
119pub struct NodeWorkContextResponse {
120    pub job_id: String,
121    pub work_context_hash: WorkContextHash,
122    pub base_sim_config: String,
123    pub tournament_payload_bytes: String,
124    pub sentinel_config: String,
125}
126
127/// Success response from `POST /chunks/complete`.
128#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
129#[serde(rename_all = "camelCase")]
130pub struct NodeChunkCompletionResponse {
131    pub success: bool,
132    #[serde(skip_serializing_if = "Option::is_none")]
133    pub already_completed: Option<bool>,
134    pub job_complete: bool,
135}
136
137fn deserialize_nullable_string<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
138where
139    D: serde::Deserializer<'de>,
140{
141    Option::<String>::deserialize(deserializer)
142}
143
144#[cfg(test)]
145mod tests;