use serde::{Deserialize, Serialize};
use ts_rs::TS;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
#[ts(export, export_to = "registrationTypes.ts")]
pub struct RegistrationState {
pub instance_id: String,
pub registered: bool,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub owner_account: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub owner_group: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub registry_endpoint: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub registered_at: Option<String>,
}
impl RegistrationState {
pub fn unregistered(instance_id: impl Into<String>) -> Self {
Self {
instance_id: instance_id.into(),
registered: false,
owner_account: None,
owner_group: None,
registry_endpoint: None,
registered_at: None,
}
}
pub fn registered(
instance_id: impl Into<String>,
owner_account: impl Into<String>,
owner_group: Option<String>,
registry_endpoint: Option<String>,
registered_at: Option<String>,
) -> Self {
Self {
instance_id: instance_id.into(),
registered: true,
owner_account: Some(owner_account.into()),
owner_group,
registry_endpoint,
registered_at,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn unregistered_serializes_to_exactly_two_keys() {
let state = RegistrationState::unregistered("scepter-example");
let json = serde_json::to_value(&state).expect("serialize");
assert_eq!(
json,
serde_json::json!({
"instance_id": "scepter-example",
"registered": false,
})
);
}
#[test]
fn full_state_round_trips() {
let state = RegistrationState::registered(
"arona-example",
"acct-123",
Some("group-42".to_string()),
Some("https://gateway.celestia.world".to_string()),
Some("2026-10-21T02:00:00Z".to_string()),
);
let json = serde_json::to_string(&state).expect("serialize");
let back: RegistrationState = serde_json::from_str(&json).expect("deserialize");
assert_eq!(back, state);
assert!(back.registered);
assert_eq!(back.owner_account.as_deref(), Some("acct-123"));
assert_eq!(back.owner_group.as_deref(), Some("group-42"));
}
#[test]
fn unknown_fields_are_ignored_for_forward_compatibility() {
let json = serde_json::json!({
"instance_id": "evernight-example",
"registered": true,
"owner_account": "acct-1",
"some_future_field": { "nested": true },
});
let state: RegistrationState =
serde_json::from_value(json).expect("forward-compatible parse");
assert!(state.registered);
assert_eq!(state.owner_account.as_deref(), Some("acct-1"));
assert_eq!(state.registry_endpoint, None);
}
#[test]
fn explicit_null_is_equivalent_to_absence() {
let json = serde_json::json!({
"instance_id": "x",
"registered": false,
"owner_account": null,
"owner_group": null,
});
let state: RegistrationState = serde_json::from_value(json).expect("null parse");
assert_eq!(state.owner_account, None);
assert_eq!(state.owner_group, None);
}
#[test]
fn registered_constructor_invariants_hold() {
let state = RegistrationState::registered("i", "acct", None, None, None);
assert!(state.registered);
assert!(state.owner_account.is_some());
let json = serde_json::to_value(&state).expect("serialize");
assert_eq!(
json,
serde_json::json!({
"instance_id": "i",
"registered": true,
"owner_account": "acct",
})
);
}
}