Skip to main content

candid_core/model/
source_info.rs

1use super::contract::Contract;
2use super::type_graph::TypeRef;
3use super::validation_error::ContractValidationError;
4use crate::limits::Limits;
5use serde::{Deserialize, Serialize};
6
7pub const SOURCE_INFO_VERSION: u32 = 1;
8
9/// Optional source/provenance data returned alongside, but never embedded in,
10/// the canonical Contract. It has no effect on Contract or interface identity.
11#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
12#[serde(deny_unknown_fields)]
13pub struct SourceInfo {
14    pub(crate) source_info_version: u32,
15    pub(crate) contract_id: String,
16    pub(crate) source_bundle_id: String,
17    pub(crate) sources: Vec<SourceFileInfo>,
18    #[serde(default)]
19    pub(crate) imports: Vec<SourceImportInfo>,
20    #[serde(default)]
21    pub(crate) declarations: Vec<SourceDeclaration>,
22    #[serde(default)]
23    pub(crate) field_labels: Vec<FieldLabelProvenance>,
24    #[serde(default)]
25    pub(crate) methods: Vec<SourceMethodInfo>,
26    #[serde(default)]
27    pub(crate) function_arguments: Vec<SourceFunctionArgumentInfo>,
28    #[serde(default)]
29    pub(crate) actors: Vec<SourceActorInfo>,
30}
31
32#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
33#[serde(deny_unknown_fields)]
34/// Unvalidated source/provenance data.
35///
36/// Raw data cannot be converted infallibly into validated [`SourceInfo`]:
37///
38/// ```compile_fail
39/// use candid_core::{RawSourceInfo, SourceInfo};
40///
41/// fn bypass_validation(raw: RawSourceInfo) -> SourceInfo {
42///     raw.into()
43/// }
44/// ```
45pub struct RawSourceInfo {
46    pub source_info_version: u32,
47    pub contract_id: String,
48    pub source_bundle_id: String,
49    pub sources: Vec<SourceFileInfo>,
50    #[serde(default)]
51    pub imports: Vec<SourceImportInfo>,
52    #[serde(default)]
53    pub declarations: Vec<SourceDeclaration>,
54    #[serde(default)]
55    pub field_labels: Vec<FieldLabelProvenance>,
56    #[serde(default)]
57    pub methods: Vec<SourceMethodInfo>,
58    #[serde(default)]
59    pub function_arguments: Vec<SourceFunctionArgumentInfo>,
60    #[serde(default)]
61    pub actors: Vec<SourceActorInfo>,
62}
63
64impl SourceInfo {
65    pub(crate) fn from_raw_unchecked(raw: RawSourceInfo) -> Self {
66        Self {
67            source_info_version: raw.source_info_version,
68            contract_id: raw.contract_id,
69            source_bundle_id: raw.source_bundle_id,
70            sources: raw.sources,
71            imports: raw.imports,
72            declarations: raw.declarations,
73            field_labels: raw.field_labels,
74            methods: raw.methods,
75            function_arguments: raw.function_arguments,
76            actors: raw.actors,
77        }
78    }
79
80    /// Recompiles the embedded source bundle and validates that every presented
81    /// provenance field matches the compiler-derived sidecar for `contract`.
82    pub fn try_from_raw(
83        raw: RawSourceInfo,
84        contract: &Contract,
85        limits: &Limits,
86    ) -> Result<Self, ContractValidationError> {
87        let source_info = Self::from_raw_unchecked(raw);
88        source_info.validate(contract, limits)?;
89        Ok(source_info)
90    }
91
92    pub fn try_from_raw_with_context(
93        raw: RawSourceInfo,
94        contract: &Contract,
95        context: &crate::RuntimeContext,
96    ) -> Result<Self, ContractValidationError> {
97        let source_info = Self::from_raw_unchecked(raw);
98        let mut budget = context.budget();
99        source_info.validate_with_budget(contract, &mut budget)?;
100        Ok(source_info)
101    }
102
103    pub fn source_info_version(&self) -> u32 {
104        self.source_info_version
105    }
106
107    pub fn contract_id(&self) -> &str {
108        &self.contract_id
109    }
110
111    pub fn source_bundle_id(&self) -> &str {
112        &self.source_bundle_id
113    }
114
115    pub fn sources(&self) -> &[SourceFileInfo] {
116        &self.sources
117    }
118
119    pub fn imports(&self) -> &[SourceImportInfo] {
120        &self.imports
121    }
122
123    pub fn declarations(&self) -> &[SourceDeclaration] {
124        &self.declarations
125    }
126
127    pub fn field_labels(&self) -> &[FieldLabelProvenance] {
128        &self.field_labels
129    }
130
131    pub fn methods(&self) -> &[SourceMethodInfo] {
132        &self.methods
133    }
134
135    pub fn function_arguments(&self) -> &[SourceFunctionArgumentInfo] {
136        &self.function_arguments
137    }
138
139    pub fn actors(&self) -> &[SourceActorInfo] {
140        &self.actors
141    }
142
143    pub fn validate(
144        &self,
145        contract: &Contract,
146        limits: &Limits,
147    ) -> Result<(), ContractValidationError> {
148        crate::source::validate_source_info(self, contract, limits)
149    }
150
151    pub(crate) fn validate_with_budget(
152        &self,
153        contract: &Contract,
154        budget: &mut crate::budget::Budget<'_>,
155    ) -> Result<(), ContractValidationError> {
156        crate::source::validate_source_info_with_budget(self, contract, budget)
157    }
158}
159
160#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
161#[serde(deny_unknown_fields)]
162pub struct SourceFileInfo {
163    pub name: String,
164    pub source: String,
165}
166
167#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
168#[serde(rename_all = "snake_case")]
169pub enum SourceImportKind {
170    Type,
171    Service,
172}
173
174#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
175#[serde(deny_unknown_fields)]
176pub struct SourceImportInfo {
177    pub from: String,
178    pub import: String,
179    pub to: String,
180    pub kind: SourceImportKind,
181}
182
183#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
184#[serde(deny_unknown_fields)]
185pub struct SourceDeclaration {
186    pub source: String,
187    pub name: String,
188    #[serde(rename = "type")]
189    pub ty: TypeRef,
190    #[serde(default, skip_serializing_if = "Vec::is_empty")]
191    pub docs: Vec<String>,
192}
193
194#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
195#[serde(deny_unknown_fields)]
196pub struct SourceActorInfo {
197    pub source: String,
198    #[serde(default, skip_serializing_if = "Vec::is_empty")]
199    pub docs: Vec<String>,
200}
201
202#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
203#[serde(deny_unknown_fields)]
204pub struct FieldLabelProvenance {
205    /// A source occurrence is retained even when multiple spellings lower to
206    /// the same semantic container node.
207    pub origin: SourceOrigin,
208    /// A stable AST-shaped occurrence path within `origin`, not a byte span.
209    pub path: String,
210    pub container: TypeRef,
211    pub id: u32,
212    pub label: SourceLabel,
213    #[serde(default, skip_serializing_if = "Vec::is_empty")]
214    pub docs: Vec<String>,
215}
216
217#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
218#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
219pub enum SourceOrigin {
220    Declaration { source: String, name: String },
221    Actor { source: String },
222}
223
224#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
225#[serde(deny_unknown_fields)]
226pub struct SourceMethodInfo {
227    pub origin: SourceOrigin,
228    pub path: String,
229    pub service: TypeRef,
230    pub name: String,
231    #[serde(default, skip_serializing_if = "Vec::is_empty")]
232    pub docs: Vec<String>,
233}
234
235#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
236#[serde(deny_unknown_fields)]
237pub struct SourceFunctionArgumentInfo {
238    pub origin: SourceOrigin,
239    pub path: String,
240    pub function: TypeRef,
241    pub direction: SourceFunctionArgumentDirection,
242    pub position: u32,
243    pub name: String,
244}
245
246#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
247#[serde(rename_all = "snake_case")]
248pub enum SourceFunctionArgumentDirection {
249    Argument,
250    Result,
251}
252
253/// Source spelling is intentionally separate from the semantic field ID.
254/// `positional` differentiates tuple syntax from an explicitly numeric label.
255#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
256#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
257pub enum SourceLabel {
258    Named { name: String },
259    Numeric,
260    Positional,
261}