Skip to main content

xript_runtime/
manifest.rs

1use serde::Deserialize;
2use std::collections::HashMap;
3
4use crate::error::{ValidationIssue, XriptError};
5
6#[derive(Debug, Clone, Deserialize)]
7pub struct Manifest {
8    pub xript: String,
9    pub name: String,
10    pub version: Option<String>,
11    pub title: Option<String>,
12    pub description: Option<String>,
13    pub bindings: Option<HashMap<String, Binding>>,
14    pub hooks: Option<HashMap<String, HookDef>>,
15    pub capabilities: Option<HashMap<String, Capability>>,
16    pub limits: Option<Limits>,
17}
18
19#[derive(Debug, Clone, Deserialize)]
20#[serde(untagged)]
21pub enum Binding {
22    Namespace(NamespaceBinding),
23    Function(FunctionBinding),
24}
25
26#[derive(Debug, Clone, Deserialize)]
27pub struct FunctionBinding {
28    pub description: String,
29    pub params: Option<Vec<Parameter>>,
30    pub returns: Option<serde_json::Value>,
31    pub r#async: Option<bool>,
32    pub capability: Option<String>,
33    pub deprecated: Option<String>,
34}
35
36#[derive(Debug, Clone, Deserialize)]
37pub struct NamespaceBinding {
38    pub description: String,
39    pub members: HashMap<String, Binding>,
40}
41
42#[derive(Debug, Clone, Deserialize)]
43pub struct Parameter {
44    pub name: String,
45    pub r#type: serde_json::Value,
46    pub description: Option<String>,
47    pub default: Option<serde_json::Value>,
48    pub required: Option<bool>,
49}
50
51#[derive(Debug, Clone, Deserialize)]
52pub struct HookDef {
53    pub description: String,
54    pub phases: Option<Vec<String>>,
55    pub params: Option<Vec<Parameter>>,
56    pub capability: Option<String>,
57    pub r#async: Option<bool>,
58    pub deprecated: Option<String>,
59}
60
61#[derive(Debug, Clone, Deserialize)]
62pub struct Capability {
63    pub description: String,
64    pub risk: Option<String>,
65}
66
67#[derive(Debug, Clone, Deserialize)]
68pub struct Limits {
69    pub timeout_ms: Option<u64>,
70    pub memory_mb: Option<u64>,
71    pub max_stack_depth: Option<usize>,
72}
73
74impl Binding {
75    pub fn is_namespace(&self) -> bool {
76        matches!(self, Binding::Namespace(_))
77    }
78}
79
80pub fn validate_structure(manifest: &Manifest) -> crate::error::Result<()> {
81    let mut issues = Vec::new();
82
83    if manifest.xript.is_empty() {
84        issues.push(ValidationIssue {
85            path: "/xript".into(),
86            message: "required field 'xript' must be a non-empty string".into(),
87        });
88    }
89
90    if manifest.name.is_empty() {
91        issues.push(ValidationIssue {
92            path: "/name".into(),
93            message: "required field 'name' must be a non-empty string".into(),
94        });
95    }
96
97    if let Some(ref limits) = manifest.limits {
98        if let Some(timeout) = limits.timeout_ms {
99            if timeout == 0 {
100                issues.push(ValidationIssue {
101                    path: "/limits/timeout_ms".into(),
102                    message: "'timeout_ms' must be a positive number".into(),
103                });
104            }
105        }
106        if let Some(memory) = limits.memory_mb {
107            if memory == 0 {
108                issues.push(ValidationIssue {
109                    path: "/limits/memory_mb".into(),
110                    message: "'memory_mb' must be a positive number".into(),
111                });
112            }
113        }
114    }
115
116    if issues.is_empty() {
117        Ok(())
118    } else {
119        Err(XriptError::ManifestValidation { issues })
120    }
121}