Mountain/ApplicationState/DTO/ExtensionDescriptionStateDTO.rs
1//! # ExtensionDescriptionStateDTO
2//!
3//! # RESPONSIBILITY
4//! - Data transfer object for extension description/state
5//! - Serializable format for gRPC/IPC transmission
6//! - Used by Mountain to track extension metadata and capabilities
7//!
8//! # FIELDS
9//! - Identifier: Unique extension identifier
10//! - Name: Extension display name
11//! - Version: Semantic version string
12//! - Publisher: Publisher name
13//! - Engines: Engine compatibility requirements
14//! - Main: Main entry point path (Node.js)
15//! - Browser: Browser entry point path
16//! - ModuleType: Module type (commonjs/esm)
17//! - IsBuiltin: Built-in extension flag
18//! - IsUnderDevelopment: Development flag
19//! - ExtensionLocation: Installation location URI
20//! - ActivationEvents: Activation event triggers
21//! - Contributes: Extension contributions configuration
22
23use serde::{Deserialize, Serialize};
24use serde_json::Value;
25
26/// Maximum length for extension name
27const MAX_EXTENSION_NAME_LENGTH:usize = 128;
28
29/// Maximum length for version string
30const MAX_VERSION_LENGTH:usize = 64;
31
32/// Maximum length for publisher name
33const MAX_PUBLISHER_LENGTH:usize = 64;
34
35/// Maximum number of activation events
36const MAX_ACTIVATION_EVENTS:usize = 100;
37
38/// Represents the deserialized content of an extension's `package.json` file,
39/// augmented with location information and other metadata.
40///
41/// This is stored in `ApplicationState` to provide the extension host with the
42/// list of available extensions and their capabilities.
43/// VS Code extensions use camelCase in package.json. Serde renames from
44/// PascalCase Rust fields to camelCase JSON automatically. Fields that
45/// don't exist in package.json (Identifier, ExtensionLocation, IsBuiltin)
46/// default to their zero values on deserialization.
47#[derive(Serialize, Deserialize, Clone, Debug)]
48#[serde(rename_all = "camelCase")]
49pub struct ExtensionDescriptionStateDTO {
50 // --- Core Metadata ---
51 /// Extension identifier: { value: string, uuid?: string }
52 /// Not present in package.json — constructed from publisher.name after
53 /// parsing.
54 #[serde(default)]
55 pub Identifier:Value,
56
57 /// Extension name (from package.json "name")
58 #[serde(default, skip_serializing_if = "String::is_empty")]
59 pub Name:String,
60
61 /// Semantic version string (e.g., "1.0.0")
62 #[serde(default, skip_serializing_if = "String::is_empty")]
63 pub Version:String,
64
65 /// Publisher name or identifier
66 #[serde(default, skip_serializing_if = "String::is_empty")]
67 pub Publisher:String,
68
69 /// Engine compatibility requirements: { vscode: string }
70 #[serde(default)]
71 pub Engines:Value,
72
73 // --- Entry Points ---
74 /// Main entry point path (Node.js runtime)
75 #[serde(default, skip_serializing_if = "Option::is_none")]
76 pub Main:Option<String>,
77
78 /// Browser entry point path (web extension)
79 #[serde(default, skip_serializing_if = "Option::is_none")]
80 pub Browser:Option<String>,
81
82 // --- Type & Flags ---
83 /// Module type: commonjs or esm
84 #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
85 pub ModuleType:Option<String>,
86
87 /// Whether this is a built-in extension (not in package.json, set by
88 /// scanner)
89 #[serde(default)]
90 pub IsBuiltin:bool,
91
92 /// Whether extension is under active development
93 #[serde(default)]
94 pub IsUnderDevelopment:bool,
95
96 // --- Location & Activation ---
97 /// Installation location URI (set by scanner, not in package.json)
98 #[serde(default)]
99 pub ExtensionLocation:Value,
100
101 /// Activation event triggers (e.g., "onStartupFinished")
102 #[serde(default, skip_serializing_if = "Option::is_none")]
103 pub ActivationEvents:Option<Vec<String>>,
104
105 // --- Contributions ---
106 /// Extension contributions (commands, views, etc.)
107 #[serde(default, skip_serializing_if = "Option::is_none")]
108 pub Contributes:Option<Value>,
109}
110
111impl ExtensionDescriptionStateDTO {
112 /// Validates the extension description data.
113 ///
114 /// # Returns
115 /// Result indicating success or validation error with reason
116 pub fn Validate(&self) -> Result<(), String> {
117 // Validate Name length
118 if self.Name.len() > MAX_EXTENSION_NAME_LENGTH {
119 return Err(format!(
120 "Extension name exceeds maximum length of {} bytes",
121 MAX_EXTENSION_NAME_LENGTH
122 ));
123 }
124
125 // Validate Version length
126 if self.Version.len() > MAX_VERSION_LENGTH {
127 return Err(format!("Version string exceeds maximum length of {} bytes", MAX_VERSION_LENGTH));
128 }
129
130 // Validate Publisher length
131 if self.Publisher.len() > MAX_PUBLISHER_LENGTH {
132 return Err(format!("Publisher exceeds maximum length of {} bytes", MAX_PUBLISHER_LENGTH));
133 }
134
135 // Validate ActivationEvents count
136 if let Some(Events) = &self.ActivationEvents {
137 if Events.len() > MAX_ACTIVATION_EVENTS {
138 return Err(format!("Activation events exceed maximum count of {}", MAX_ACTIVATION_EVENTS));
139 }
140 }
141
142 Ok(())
143 }
144
145 /// Creates a minimal extension description for testing or placeholder use.
146 ///
147 /// # Arguments
148 /// * `Identifier` - Extension identifier value
149 /// * `Name` - Extension name
150 /// * `Version` - Extension version
151 /// * `Publisher` - Publisher name
152 ///
153 /// # Returns
154 /// A new ExtensionDescriptionStateDTO with minimal required fields
155 pub fn CreateMinimal(Identifier:Value, Name:String, Version:String, Publisher:String) -> Result<Self, String> {
156 let Description = Self {
157 Identifier,
158 Name:Name.clone(),
159 Version:Version.clone(),
160 Publisher:Publisher.clone(),
161 Engines:serde_json::json!({ "vscode": "*" }),
162 Main:None,
163 Browser:None,
164 ModuleType:None,
165 IsBuiltin:false,
166 IsUnderDevelopment:false,
167 ExtensionLocation:serde_json::json!(null),
168 ActivationEvents:None,
169 Contributes:None,
170 };
171
172 Description.Validate()?;
173 Ok(Description)
174 }
175}