Skip to main content

ograf_core/
models.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use serde_json::{json, Value};
4use uuid::Uuid;
5
6pub type RendererId = Uuid;
7
8// Re-export protocol types for backward compatibility
9pub use crate::protocol::{
10    InstanceId, InstanceSnapshot, RenderTarget, RendererMessage, ServerMessage,
11};
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct Graphic {
15    pub id: String,
16    pub name: String,
17    pub version: Option<String>,
18    pub description: Option<String>,
19    pub manifest: Value,
20    pub storage_path: String,
21    pub uploaded_at: String,
22}
23
24impl Graphic {
25    /// The public, spec-shaped `GraphicListInfo` view of this graphic —
26    /// deliberately omits internal fields like `storage_path`.
27    pub fn list_info(&self) -> Value {
28        let mut info = json!({
29            "id": self.id,
30            "name": self.name,
31        });
32        if let Some(description) = &self.description {
33            info["description"] = json!(description);
34        }
35        if let Some(thumbnails) = self.manifest.get("thumbnails") {
36            info["thumbnails"] = thumbnails.clone();
37        }
38        info
39    }
40}
41
42/// Metrics for a renderer connection (extension, not part of OGraf spec).
43/// Added to RendererInfo response to provide observability without requiring
44/// a separate metrics endpoint.
45#[derive(Debug, Clone, Serialize, Deserialize)]
46#[serde(rename_all = "camelCase")]
47pub struct RendererMetrics {
48    pub pending_requests: usize,
49    pub messages_sent: u64,
50    pub messages_received: u64,
51    pub uptime_seconds: u64,
52}
53
54/// Not part of the OGraf spec itself (the spec has no instance state
55/// machine) — this is Core's own bookkeeping, inferred from which
56/// action last succeeded, purely so dashboards/UIs can show more than
57/// "a graphic is loaded here" (see `store::renderers::apply_result`).
58#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
59#[serde(rename_all = "camelCase")]
60pub enum InstanceState {
61    Loaded,
62    Playing,
63    Stopped,
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct GraphicInstance {
68    pub instance_id: InstanceId,
69    pub graphic_id: String,
70    pub data: Option<Value>,
71    pub loaded_at: DateTime<Utc>,
72    pub state: InstanceState,
73    /// The last `currentStep` a playAction() reply reported — `None` until
74    /// the first PlayAction (freshly loaded instances have no step yet;
75    /// StopAction doesn't report one either, per the spec, so it just keeps
76    /// whatever it was last).
77    pub current_step: Option<f64>,
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct RendererInfo {
82    pub id: RendererId,
83    pub name: String,
84    pub connected_at: DateTime<Utc>,
85    pub render_target: RenderTarget,
86    /// The renderer's own declared schema for `render_target`, from
87    /// `hello.capabilities.renderTargetSchema` — `None` if it didn't send
88    /// one, in which case callers fall back to a generic placeholder.
89    pub render_target_schema: Option<Value>,
90    pub instances: Vec<GraphicInstance>,
91    /// Optional metrics for observability (extension, not part of OGraf spec).
92    /// Only populated if metrics tracking is enabled.
93    #[serde(skip_serializing_if = "Option::is_none")]
94    pub metrics: Option<RendererMetrics>,
95}