1use super::type_graph::{Actor, Declaration, TypeNode, TypeRef};
2use super::validation_error::{ContractJsonError, ContractValidationError};
3use crate::limits::Limits;
4use serde::{Deserialize, Serialize, Serializer};
5
6pub const CONTRACT_FORMAT: &str = "candid-core";
7pub const FORMAT_VERSION: u32 = 1;
8pub const SEMANTICS_PROFILE: &str = "candid-1";
9pub const CANONICALIZATION_PROFILE: &str = "candid-core-canon-1";
10const PACKAGE_MANIFEST: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/Cargo.toml"));
11
12#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
13#[serde(deny_unknown_fields)]
14pub struct ContractIdentities {
15 pub contract: String,
16 #[serde(default, skip_serializing_if = "Option::is_none")]
17 pub interface: Option<String>,
18}
19
20#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
50#[serde(deny_unknown_fields)]
51pub struct ProducerInfo {
52 pub name: String,
53 pub version: String,
54 pub candid_version: String,
55 pub candid_parser_version: String,
56}
57
58impl ProducerInfo {
59 pub fn current() -> Self {
74 Self {
75 name: env!("CARGO_PKG_NAME").to_string(),
76 version: env!("CARGO_PKG_VERSION").to_string(),
77 candid_version: exact_dependency_version(PACKAGE_MANIFEST, "candid"),
78 candid_parser_version: exact_dependency_version(PACKAGE_MANIFEST, "candid_parser"),
79 }
80 }
81}
82
83fn exact_dependency_version(manifest: &str, dependency: &str) -> String {
84 manifest_dependency_version(manifest, dependency).unwrap_or_else(|| {
85 panic!("{dependency} must be declared as an exact dependency in Cargo.toml")
86 })
87}
88
89fn manifest_dependency_version(manifest: &str, dependency: &str) -> Option<String> {
109 let inline_prefix = format!("{dependency} = ");
110 let section_header = format!("[dependencies.{dependency}]");
111 let mut in_dependencies = false;
112 let mut in_dependency_section = false;
113
114 for line in manifest.lines().map(str::trim) {
115 if line.starts_with('[') {
116 in_dependencies = line == "[dependencies]";
117 in_dependency_section = line == section_header;
118 } else if in_dependencies {
119 if let Some(version) = line
120 .strip_prefix(&inline_prefix)
121 .and_then(exact_version_literal)
122 {
123 return Some(version.to_string());
124 }
125 } else if in_dependency_section {
126 if let Some(value) = line.strip_prefix("version = ") {
127 return exact_version_literal(value).map(str::to_string);
128 }
129 }
130 }
131 None
132}
133
134fn exact_version_literal(declaration: &str) -> Option<&str> {
137 let literal = match declaration.strip_prefix('{') {
138 Some(table) => table.split_once("version = ")?.1,
139 None => declaration,
140 };
141 literal
142 .strip_prefix('"')
143 .and_then(|value| value.strip_prefix('='))
144 .and_then(|value| value.split_once('"').map(|(version, _)| version))
145}
146
147#[cfg(test)]
148mod manifest_tests {
149 use super::*;
150
151 #[test]
155 fn the_packages_own_manifest_reports_both_engine_versions() {
156 assert!(!exact_dependency_version(PACKAGE_MANIFEST, "candid").is_empty());
157 assert!(!exact_dependency_version(PACKAGE_MANIFEST, "candid_parser").is_empty());
158 }
159
160 #[test]
161 fn every_spelling_cargo_can_produce_is_read_identically() {
162 let plain = "[dependencies]\ncandid = \"=0.10.30\"\n";
163 let inline_table = "[dependencies]\ncandid = { version = \"=0.10.30\", optional = true }\n";
164 let normalized = "[dependencies.candid]\nversion = \"=0.10.30\"\noptional = true\n";
166 for manifest in [plain, inline_table, normalized] {
167 assert_eq!(exact_dependency_version(manifest, "candid"), "0.10.30");
168 }
169 }
170
171 #[test]
172 fn a_dev_dependency_is_never_mistaken_for_the_real_one() {
173 let manifest = "[dependencies.candid_parser]\nversion = \"=0.4.0\"\noptional = true\n\
176 \n[dev-dependencies.candid_parser]\nversion = \"=0.9.9\"\n";
177 assert_eq!(exact_dependency_version(manifest, "candid_parser"), "0.4.0");
178 assert_eq!(
180 manifest_dependency_version(
181 "[dev-dependencies.candid_parser]\nversion = \"=0.4.0\"\n",
182 "candid_parser"
183 ),
184 None
185 );
186 }
187
188 #[test]
189 fn a_floating_requirement_is_not_an_exact_pin() {
190 for manifest in [
191 "[dependencies]\ncandid = \"0.10.30\"\n",
192 "[dependencies]\ncandid = { version = \"^0.10\" }\n",
193 "[dependencies.candid]\nversion = \"0.10.30\"\n",
194 ] {
195 assert_eq!(manifest_dependency_version(manifest, "candid"), None);
196 }
197 }
198}
199
200#[derive(Debug, Clone, PartialEq, Eq)]
206pub struct Contract {
207 pub(crate) format: String,
208 pub(crate) format_version: u32,
209 pub(crate) semantics_profile: String,
210 pub(crate) canonicalization_profile: String,
211 pub(crate) identities: ContractIdentities,
212 pub(crate) producer: ProducerInfo,
213 pub(crate) types: Vec<TypeNode>,
214 pub(crate) declarations: Vec<Declaration>,
215 pub(crate) actor: Option<Actor>,
216}
217
218impl Contract {
219 pub fn format(&self) -> &str {
220 &self.format
221 }
222
223 pub fn format_version(&self) -> u32 {
224 self.format_version
225 }
226
227 pub fn semantics_profile(&self) -> &str {
228 &self.semantics_profile
229 }
230
231 pub fn canonicalization_profile(&self) -> &str {
232 &self.canonicalization_profile
233 }
234
235 pub fn identities(&self) -> &ContractIdentities {
236 &self.identities
237 }
238
239 pub fn contract_id(&self) -> &str {
240 &self.identities.contract
241 }
242
243 pub fn interface_id(&self) -> Option<&str> {
244 self.identities.interface.as_deref()
245 }
246
247 pub fn producer(&self) -> &ProducerInfo {
248 &self.producer
249 }
250
251 pub fn types(&self) -> &[TypeNode] {
252 &self.types
253 }
254
255 pub fn declarations(&self) -> &[Declaration] {
256 &self.declarations
257 }
258
259 pub fn actor(&self) -> Option<&Actor> {
260 self.actor.as_ref()
261 }
262
263 pub fn validate(&self) -> Result<(), ContractValidationError> {
265 self.validate_with_limits(&Limits::default())
266 }
267
268 pub fn validate_with_limits(&self, limits: &Limits) -> Result<(), ContractValidationError> {
269 self.validate_with_context(&crate::RuntimeContext::new(limits.clone()))
270 }
271
272 pub fn validate_with_context(
273 &self,
274 context: &crate::RuntimeContext,
275 ) -> Result<(), ContractValidationError> {
276 let mut budget = context.budget();
277 crate::validate::validate_contract_with_budget(self, &mut budget)
278 }
279
280 pub fn canonicalize(&self) -> Result<Self, ContractValidationError> {
283 self.canonicalize_with_limits(&Limits::default())
284 }
285
286 pub fn canonicalize_with_limits(
287 &self,
288 limits: &Limits,
289 ) -> Result<Self, ContractValidationError> {
290 self.canonicalize_with_context(&crate::RuntimeContext::new(limits.clone()))
291 }
292
293 pub fn canonicalize_with_context(
294 &self,
295 context: &crate::RuntimeContext,
296 ) -> Result<Self, ContractValidationError> {
297 let mut budget = context.budget();
298 crate::canonical::canonicalize_contract_with_budget(self, &mut budget)
299 }
300
301 pub fn to_json_pretty(&self) -> Result<String, ContractValidationError> {
308 self.to_json_pretty_with_context(&crate::RuntimeContext::default())
309 }
310
311 pub fn to_json_pretty_with_limits(
319 &self,
320 limits: &Limits,
321 ) -> Result<String, ContractValidationError> {
322 self.to_json_pretty_with_context(&crate::RuntimeContext::new(limits.clone()))
323 }
324
325 pub fn to_json_pretty_with_context(
337 &self,
338 context: &crate::RuntimeContext,
339 ) -> Result<String, ContractValidationError> {
340 let mut budget = context.budget();
341 let canonical =
342 crate::validate::validate_and_canonicalize_with_budget(self, &mut budget)?.contract;
343 budget
344 .checkpoint()
345 .map_err(crate::budget::BudgetError::into_contract_error)?;
346 let json = serde_json::to_string_pretty(&canonical).map_err(|error| {
347 ContractValidationError::single(
348 "contract_json_serialization_failed",
349 "$",
350 error.to_string(),
351 )
352 })?;
353 let max_work = budget.limits().max_canonicalization_work;
354 budget
355 .charge("canonicalization_work", max_work, json.len())
356 .map_err(crate::budget::BudgetError::into_contract_error)?;
357 budget
358 .checkpoint()
359 .map_err(crate::budget::BudgetError::into_contract_error)?;
360 Ok(json)
361 }
362
363 pub fn from_json(input: &str) -> Result<Self, ContractJsonError> {
366 Self::from_json_with_limits(input, &Limits::default())
367 }
368
369 pub fn from_json_with_limits(input: &str, limits: &Limits) -> Result<Self, ContractJsonError> {
370 Self::from_json_with_context(input, &crate::RuntimeContext::new(limits.clone()))
371 }
372
373 pub fn from_json_with_context(
376 input: &str,
377 context: &crate::RuntimeContext,
378 ) -> Result<Self, ContractJsonError> {
379 let mut budget = context.budget();
380 let raw: RawContract = crate::budget::decode_bounded(&mut budget, input.len(), || {
381 serde_json::from_str(input)
382 })?;
383 Self::from_raw_with_mapping_and_budget(raw, &mut budget)
384 .map(|(contract, _)| contract)
385 .map_err(ContractJsonError::InvalidContract)
386 }
387
388 pub fn from_slice_with_limits(
391 input: &[u8],
392 limits: &Limits,
393 ) -> Result<Self, ContractJsonError> {
394 Self::from_slice_with_context(input, &crate::RuntimeContext::new(limits.clone()))
395 }
396
397 pub fn from_slice_with_context(
400 input: &[u8],
401 context: &crate::RuntimeContext,
402 ) -> Result<Self, ContractJsonError> {
403 let mut budget = context.budget();
404 let raw: RawContract = crate::budget::decode_bounded(&mut budget, input.len(), || {
405 serde_json::from_slice(input)
406 })?;
407 Self::from_raw_with_mapping_and_budget(raw, &mut budget)
408 .map(|(contract, _)| contract)
409 .map_err(ContractJsonError::InvalidContract)
410 }
411
412 pub fn try_from_raw(raw: RawContract) -> Result<Self, ContractValidationError> {
413 Self::from_raw_with_limits(raw, &Limits::default())
414 }
415
416 pub fn try_from_raw_with_limits(
417 raw: RawContract,
418 limits: &Limits,
419 ) -> Result<Self, ContractValidationError> {
420 Self::from_raw_with_limits(raw, limits)
421 }
422
423 pub fn try_from_raw_with_context(
424 raw: RawContract,
425 context: &crate::RuntimeContext,
426 ) -> Result<Self, ContractValidationError> {
427 let mut budget = context.budget();
428 Ok(Self::from_raw_with_mapping_and_budget(raw, &mut budget)?.0)
429 }
430
431 fn from_raw_with_limits(
432 raw: RawContract,
433 limits: &Limits,
434 ) -> Result<Self, ContractValidationError> {
435 Ok(Self::from_raw_with_mapping(raw, limits)?.0)
436 }
437
438 pub(crate) fn from_raw_with_mapping(
439 raw: RawContract,
440 limits: &Limits,
441 ) -> Result<(Self, Vec<TypeRef>), ContractValidationError> {
442 let mut budget = crate::budget::Budget::from_limits(limits);
443 Self::from_raw_with_mapping_and_budget(raw, &mut budget)
444 }
445
446 pub(crate) fn from_raw_with_mapping_and_budget(
447 raw: RawContract,
448 budget: &mut crate::budget::Budget<'_>,
449 ) -> Result<(Self, Vec<TypeRef>), ContractValidationError> {
450 let contract = Self {
451 format: raw.format,
452 format_version: raw.format_version,
453 semantics_profile: raw.semantics_profile,
454 canonicalization_profile: raw.canonicalization_profile,
455 identities: raw.identities,
456 producer: raw.producer,
457 types: raw.types,
458 declarations: raw.declarations,
459 actor: raw.actor,
460 };
461 let canonicalized =
462 crate::validate::validate_and_canonicalize_with_budget(&contract, budget)?;
463 Ok((canonicalized.contract, canonicalized.old_to_new))
464 }
465
466 pub(crate) fn new_unchecked(
467 types: Vec<TypeNode>,
468 declarations: Vec<Declaration>,
469 actor: Option<Actor>,
470 ) -> Self {
471 Self {
472 format: CONTRACT_FORMAT.to_string(),
473 format_version: FORMAT_VERSION,
474 semantics_profile: SEMANTICS_PROFILE.to_string(),
475 canonicalization_profile: CANONICALIZATION_PROFILE.to_string(),
476 identities: ContractIdentities {
477 contract: format!("candid-core:contract:v1:sha256:{}", "0".repeat(64)),
478 interface: actor
479 .as_ref()
480 .map(|_| format!("candid-core:interface:v1:sha256:{}", "0".repeat(64))),
481 },
482 producer: ProducerInfo::current(),
483 types,
484 declarations,
485 actor,
486 }
487 }
488}
489
490#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
527#[serde(deny_unknown_fields)]
528pub struct ContractDraft {
529 pub types: Vec<TypeNode>,
530 #[serde(default)]
531 pub declarations: Vec<Declaration>,
532 #[serde(
535 default,
536 deserialize_with = "deserialize_actor_forbidding_null",
537 skip_serializing_if = "Option::is_none"
538 )]
539 pub actor: Option<Actor>,
540 #[serde(
546 default,
547 deserialize_with = "deserialize_producer_forbidding_null",
548 skip_serializing_if = "Option::is_none"
549 )]
550 pub producer: Option<ProducerInfo>,
551}
552
553fn deserialize_producer_forbidding_null<'de, D>(
558 deserializer: D,
559) -> Result<Option<ProducerInfo>, D::Error>
560where
561 D: serde::Deserializer<'de>,
562{
563 ProducerInfo::deserialize(deserializer).map(Some)
564}
565
566impl ContractDraft {
567 pub fn new(types: Vec<TypeNode>, declarations: Vec<Declaration>, actor: Option<Actor>) -> Self {
568 Self {
569 types,
570 declarations,
571 actor,
572 producer: None,
573 }
574 }
575
576 #[must_use]
579 pub fn with_producer(mut self, producer: ProducerInfo) -> Self {
580 self.producer = Some(producer);
581 self
582 }
583
584 pub fn build(self) -> Result<Contract, ContractValidationError> {
587 self.build_with_limits(&Limits::default())
588 }
589
590 pub fn build_with_limits(self, limits: &Limits) -> Result<Contract, ContractValidationError> {
592 let mut budget = crate::budget::Budget::from_limits(limits);
593 self.build_with_budget(&mut budget)
594 }
595
596 pub fn build_with_context(
599 self,
600 context: &crate::RuntimeContext,
601 ) -> Result<Contract, ContractValidationError> {
602 let mut budget = context.budget();
603 self.build_with_budget(&mut budget)
604 }
605
606 fn build_with_budget(
607 self,
608 budget: &mut crate::budget::Budget<'_>,
609 ) -> Result<Contract, ContractValidationError> {
610 let mut contract = Contract::new_unchecked(self.types, self.declarations, self.actor);
611 if let Some(producer) = self.producer {
612 contract.producer = producer;
613 }
614 crate::validate::validate_structure_with_budget(&contract, budget)?;
615 Ok(
616 crate::canonical::canonicalize_with_mapping_unchecked_with_budget(&contract, budget)?
617 .contract,
618 )
619 }
620}
621
622#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
648#[serde(deny_unknown_fields)]
649pub struct RawContract {
650 pub format: String,
651 pub format_version: u32,
652 pub semantics_profile: String,
653 pub canonicalization_profile: String,
654 pub identities: ContractIdentities,
655 pub producer: ProducerInfo,
656 pub types: Vec<TypeNode>,
657 #[serde(default)]
658 pub declarations: Vec<Declaration>,
659 #[serde(
663 default,
664 deserialize_with = "deserialize_actor_forbidding_null",
665 skip_serializing_if = "Option::is_none"
666 )]
667 pub actor: Option<Actor>,
668}
669
670fn deserialize_actor_forbidding_null<'de, D>(deserializer: D) -> Result<Option<Actor>, D::Error>
674where
675 D: serde::Deserializer<'de>,
676{
677 Actor::deserialize(deserializer).map(Some)
678}
679
680impl From<&Contract> for RawContract {
681 fn from(contract: &Contract) -> Self {
682 Self {
683 format: contract.format.clone(),
684 format_version: contract.format_version,
685 semantics_profile: contract.semantics_profile.clone(),
686 canonicalization_profile: contract.canonicalization_profile.clone(),
687 identities: contract.identities.clone(),
688 producer: contract.producer.clone(),
689 types: contract.types.clone(),
690 declarations: contract.declarations.clone(),
691 actor: contract.actor.clone(),
692 }
693 }
694}
695
696impl Serialize for Contract {
697 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
698 where
699 S: Serializer,
700 {
701 RawContract::from(self).serialize(serializer)
702 }
703}