xript_runtime/
manifest.rs1use 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}