Skip to main content

dynoxide/schema/
mod.rs

1//! Application-level data model definitions for MCP agent context.
2//!
3//! Parses schema files (e.g., OneTable) into a [`DataModel`] that can be
4//! exposed to MCP-connected agents via instructions and `get_database_info`.
5
6pub mod onetable;
7
8use serde::Serialize;
9
10/// Application-level data model parsed from a schema file (e.g., OneTable).
11/// Designed for agent consumption — serializes to JSON for MCP responses.
12#[derive(Debug, Clone, Serialize)]
13pub struct DataModel {
14    /// Schema format identifier, e.g. "onetable:1.1.0"
15    pub schema_format: String,
16    /// The attribute name used to discriminate entity types (e.g. "_type")
17    pub type_attribute: String,
18    /// Entity definitions with key templates and GSI mappings
19    pub entities: Vec<EntityDefinition>,
20}
21
22/// A single entity type within the data model.
23#[derive(Debug, Clone, Serialize)]
24pub struct EntityDefinition {
25    /// Entity name, e.g. "Account"
26    pub name: String,
27    /// Primary key partition template, e.g. "account#${id}"
28    pub pk_template: String,
29    /// Primary key sort template, e.g. "account#"
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub sk_template: Option<String>,
32    /// Entity-level type attribute override (usually same as DataModel.type_attribute)
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub type_attribute: Option<String>,
35    /// Which GSIs this entity participates in, with key templates
36    pub gsi_mappings: Vec<GsiMapping>,
37    /// Human-readable description from schema (if present)
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub description: Option<String>,
40}
41
42/// A GSI mapping for an entity, describing the key templates used.
43#[derive(Debug, Clone, Serialize)]
44pub struct GsiMapping {
45    /// DynamoDB index name resolved from the schema's index definitions.
46    ///
47    /// For OneTable schemas, this is resolved from the `name` field in the
48    /// index definition if present, otherwise falls back to the OneTable key
49    /// (e.g. "gs1"). Must match the name from CreateTable / describe_table
50    /// so agents can pass it directly to query's `index_name` parameter.
51    pub index_name: String,
52    /// GSI partition key template
53    pub pk_template: String,
54    /// GSI sort key template (if the index has a sort key)
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub sk_template: Option<String>,
57}
58
59impl DataModel {
60    /// Generate a compact summary for MCP instructions.
61    ///
62    /// Returns entity names with their GSI participation, truncated to `limit`.
63    /// If `limit` is 0, returns `None` (summary suppressed).
64    pub fn instructions_summary(&self, limit: usize) -> Option<String> {
65        if limit == 0 {
66            return None;
67        }
68
69        let entity_count = self.entities.len();
70        let shown = self.entities.iter().take(limit);
71
72        let entity_parts: Vec<String> = shown
73            .map(|e| {
74                if e.gsi_mappings.is_empty() {
75                    e.name.clone()
76                } else {
77                    let gsis: Vec<&str> = e
78                        .gsi_mappings
79                        .iter()
80                        .map(|g| g.index_name.as_str())
81                        .collect();
82                    format!("{} ({})", e.name, gsis.join(", "))
83                }
84            })
85            .collect();
86
87        let mut summary = format!(
88            "## Data model\n\n\
89             Schema: {} ({} entities, type attribute: \"{}\")\n\
90             Entities: {}",
91            self.schema_format,
92            entity_count,
93            self.type_attribute,
94            entity_parts.join(", "),
95        );
96
97        if entity_count > limit {
98            summary.push_str(&format!("...and {} more", entity_count - limit));
99        }
100
101        summary.push_str(
102            "\n\nCall get_database_info for full entity definitions with key templates.\n\
103             Note: Data model definitions describe the intended schema but are not enforced. \
104             Actual database contents may differ.",
105        );
106
107        Some(summary)
108    }
109}