Files
OpenFUT-Core/src/models/game_ext.rs
T

61 lines
2.5 KiB
Rust

//! Generic, game-scoped **opaque** extension state.
//!
//! Core stores and versions these bytes and associates them with a canonical
//! entity + a server-computed fingerprint, but never interprets them. A game
//! adapter owns the payload schema/meaning. This keeps game-only wire round-trip
//! state (e.g. a FIFA 17 squad's `custom[]`/`kicktakers`/`kitNumber`) durable and
//! atomic with its canonical entity without leaking game concepts into Core.
use serde::{Deserialize, Serialize};
/// Generic safety bounds Core enforces without interpreting the payload.
pub const MAX_EXT_PAYLOAD_BYTES: usize = 64 * 1024;
pub const MAX_EXT_NAMESPACE_LEN: usize = 64;
/// An opaque extension payload a game adapter asks Core to persist atomically
/// alongside a canonical entity. `payload` is uninterpreted bytes-as-text.
#[derive(Debug, Clone, Deserialize)]
pub struct OpaqueExtensionWrite {
/// Opaque adapter key, e.g. `"fifa17.squad.v1"`. Core treats it as a string.
pub namespace: String,
/// Adapter's payload schema version (distinct from the DB storage schema).
pub schema_version: i64,
/// Uninterpreted payload (the adapter's serialized game-only state).
pub payload: String,
}
impl OpaqueExtensionWrite {
/// Generic bounds check — namespace non-empty/length, payload size. Semantic
/// validation of the payload is the adapter's job; Core only guards size.
pub fn validate(&self) -> Result<(), String> {
if self.namespace.is_empty() || self.namespace.len() > MAX_EXT_NAMESPACE_LEN {
return Err(format!(
"namespace length {} out of bounds (1..={MAX_EXT_NAMESPACE_LEN})",
self.namespace.len()
));
}
if self.payload.len() > MAX_EXT_PAYLOAD_BYTES {
return Err(format!(
"extension payload {} bytes exceeds max {MAX_EXT_PAYLOAD_BYTES}",
self.payload.len()
));
}
Ok(())
}
}
/// A stored opaque extension row (read side). `canonical_fingerprint` is the
/// server-computed fingerprint of the canonical entity at write time; a reader
/// compares it against the entity's *current* fingerprint to detect staleness.
#[derive(Debug, Clone, Serialize, sqlx::FromRow)]
pub struct GameEntityExt {
pub game_id: String,
pub entity_kind: String,
pub entity_id: String,
pub namespace: String,
pub schema_version: i64,
pub canonical_fingerprint: String,
pub payload: String,
pub updated_at: String,
}