1use std::borrow::Cow;
18use std::collections::BTreeSet;
19use std::time::Duration;
20use serde::{Deserialize, Serialize};
22use crate::PREFIX;
24use crate::catalog::Node;
25use crate::config::SchemaSettings;
26use crate::operations::{
27 Backoff, Contract, Entry, Jitter, OperationPolicy, ReplaySafety, RestartPolicy, RetryPolicy, Visibility, WireDeclaration, lifecycle,
28};
29use crate::plugin::{Grant, Requirement};
30use crate::schema::declaration::Named;
31use crate::schema::graph::Schema;
32
33const ENTRY_LIMIT: usize = 4096;
36const GRANT_LIMIT: usize = 256;
37
38#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
45#[serde(try_from = "Decoded")]
46pub struct Manifest {
47 pub facility: String,
48 pub owner: String,
49 pub provides: Vec<Contract>,
50 pub requires: Vec<Required>,
51 pub lifecycle: [lifecycle::Descriptor; 2],
52}
53
54#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
55#[serde(deny_unknown_fields)]
56pub struct Required {
57 pub contract: Contract,
58
59 #[serde(default)]
60 pub grants: Vec<Grant>,
61}
62
63#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
64#[serde(deny_unknown_fields)]
65pub struct Identity {
66 pub facility: String,
67 pub id: String,
68 pub version: String,
69 pub description: String,
70 pub config: String,
71}
72
73#[derive(Deserialize)]
74#[serde(deny_unknown_fields)]
75struct Decoded {
76 #[serde(default)]
77 kind: Option<String>,
78
79 facility: String,
80 owner: String,
81 provides: Vec<Contract>,
82 requires: Vec<Required>,
83 lifecycle: [lifecycle::Descriptor; 2],
84}
85
86#[derive(Deserialize)]
88#[serde(deny_unknown_fields)]
89pub struct NamedContract {
90 pub operation: crate::operations::Definition,
91 pub version: u32,
92 pub codec: crate::operations::Codec,
93 pub request: String,
94 pub response: String,
95
96 #[serde(default)]
97 pub input: Option<String>,
98
99 #[serde(default)]
100 pub output: Option<String>,
101
102 #[serde(default)]
103 pub grpc: Option<crate::operations::Grpc>,
104}
105
106#[derive(Deserialize)]
107#[serde(deny_unknown_fields)]
108pub struct NamedRequired {
109 pub contract: NamedContract,
110
111 #[serde(default)]
112 pub grants: Vec<Grant>,
113}
114
115#[derive(Deserialize)]
116#[serde(deny_unknown_fields)]
117pub struct NamedManifest {
118 pub facility: String,
119 pub owner: String,
120 pub provides: Vec<NamedContract>,
121 pub requires: Vec<NamedRequired>,
122 pub lifecycle: [lifecycle::Descriptor; 2],
123}
124
125pub(crate) struct Encoder<const N: usize> {
126 pub(crate) bytes: [u8; N],
127 pub(crate) cursor: usize,
128}
129
130#[derive(Debug, thiserror::Error)]
133pub enum Error {
134 #[error("missing complete {facility} contract inventory for `{owner}`")]
135 Missing { facility: String, owner: String },
136
137 #[error("multiple {facility} contract inventories for `{owner}`")]
138 Duplicate { facility: String, owner: String },
139
140 #[error("contract inventory exceeds its entry or fragment budget")]
141 Budget,
142
143 #[error("contract inventory requires a guest or sidecar facility and a valid artifact owner")]
144 Identity,
145
146 #[error("contract inventory has an invalid fragment kind")]
147 Kind,
148
149 #[error("requirement `{id}` carries a duplicate or invalid grant")]
150 Grant { id: String },
151
152 #[error("duplicate ordinary operation `{id}` in {direction}")]
153 OperationDuplicate { direction: &'static str, id: String },
154
155 #[error("invalid ordinary operation metadata: {0}")]
156 Operation(#[from] crate::Error),
157
158 #[error("SDK lifecycle inventory must contain initialization then shutdown")]
159 LifecycleOrder,
160
161 #[error("invalid SDK lifecycle inventory: {0}")]
162 Lifecycle(#[from] lifecycle::Error),
163
164 #[error("invalid shared contract schema: {0}")]
165 Schema(#[from] crate::schema::graph::Error),
166
167 #[error("invalid resolved contract: {0}")]
168 Contract(#[from] crate::operations::ContractError),
169
170 #[error("invalid contract inventory JSON: {0}")]
171 Decode(#[from] serde_json::Error),
172}
173
174impl NamedContract {
179 pub fn resolve(self, declarations: &[Named], settings: &mut SchemaSettings) -> Result<Contract, Error> {
180 let mut roots = vec![("request", self.request.as_str()), ("response", self.response.as_str())];
181 if let Some(input) = &self.input {
182 roots.push(("input", input.as_str()));
183 }
184 if let Some(output) = &self.output {
185 roots.push(("output", output.as_str()));
186 }
187 let schema = Schema::from_named_roots(&roots, declarations, settings)?;
188 let schema = super::retain_schema(schema, settings)?;
189 Ok(Contract::new(self.operation, self.version, self.codec, schema)?.with_grpc(self.grpc)?)
190 }
191}
192
193impl NamedManifest {
194 pub fn resolve(self, declarations: &[Named], settings: &mut SchemaSettings) -> Result<Manifest, Error> {
195 if self.provides.len() > ENTRY_LIMIT || self.requires.len() > ENTRY_LIMIT {
196 return Err(Error::Budget);
197 }
198 let provides = self.provides.into_iter().map(|contract| contract.resolve(declarations, settings)).collect::<Result<_, _>>()?;
199 let requires = self
200 .requires
201 .into_iter()
202 .map(|entry| Ok(Required { contract: entry.contract.resolve(declarations, settings)?, grants: entry.grants }))
203 .collect::<Result<_, Error>>()?;
204 let manifest = Manifest { facility: self.facility, owner: self.owner, provides, requires, lifecycle: self.lifecycle };
205 validate(&manifest)?;
206 Ok(manifest)
207 }
208}
209
210impl TryFrom<Decoded> for Manifest {
211 type Error = Error;
212
213 fn try_from(value: Decoded) -> Result<Self, Error> {
214 if value.kind.as_deref().is_some_and(|kind| kind != "contracts") {
215 return Err(Error::Kind);
216 }
217 let manifest = Self {
218 facility: value.facility,
219 owner: value.owner,
220 provides: value.provides,
221 requires: value.requires,
222 lifecycle: value.lifecycle,
223 };
224 validate(&manifest)?;
225 Ok(manifest)
226 }
227}
228
229impl Required {
230 pub fn from_tree(entries: &[Node<Requirement>]) -> Result<Vec<Self>, crate::operations::ContractError> {
231 let mut required = Vec::with_capacity(crate::catalog::count(entries));
232 crate::catalog::try_for_each(entries, |entry| {
233 required.push(Self { contract: entry.declaration.contract()?, grants: entry.grants.to_vec() });
234 Ok::<(), crate::operations::ContractError>(())
235 })?;
236 Ok(required)
237 }
238
239 pub fn grants(&self, grant: &Grant) -> bool {
240 self.grants.contains(grant)
241 }
242}
243
244impl<const N: usize> Encoder<N> {
245 pub(crate) const fn raw(&mut self, bytes: &[u8]) {
246 if N == 0 {
247 self.cursor += bytes.len();
248 } else {
249 super::write(&mut self.bytes, &mut self.cursor, bytes);
250 }
251 }
252
253 pub(crate) const fn quoted(&mut self, value: &str) {
254 if N == 0 {
255 self.cursor += super::quoted_len(value);
256 } else {
257 super::quoted(&mut self.bytes, &mut self.cursor, value);
258 }
259 }
260
261 pub(crate) const fn number(&mut self, value: u64) {
262 if N == 0 {
263 self.cursor += super::digits(value);
264 } else {
265 super::number(&mut self.bytes, &mut self.cursor, value);
266 }
267 }
268}
269
270pub fn decode(fragment: &[u8]) -> Result<Manifest, Error> {
273 if fragment.len() > crate::FRAGMENT_LIMIT {
274 return Err(Error::Budget);
275 }
276 Ok(serde_json::from_slice(fragment)?)
277}
278
279pub fn select(manifests: Vec<Manifest>, facility: &str, owner: &str) -> Result<Manifest, Error> {
280 let mut matching = manifests.into_iter().filter(|value| value.facility == facility && value.owner == owner);
281 let manifest = matching.next().ok_or_else(|| Error::Missing { facility: facility.to_owned(), owner: owner.to_owned() })?;
282 if matching.next().is_some() {
283 return Err(Error::Duplicate { facility: facility.to_owned(), owner: owner.to_owned() });
284 }
285 validate(&manifest)?;
286 Ok(manifest)
287}
288
289pub fn validate(manifest: &Manifest) -> Result<(), Error> {
290 if !valid_identity(&manifest.facility, &manifest.owner) {
291 return Err(Error::Identity);
292 }
293 let required = manifest.requires.iter().map(|required| &required.contract);
294 for (direction, contracts) in [("provides", manifest.provides.iter().collect::<Vec<_>>()), ("requires", required.collect())] {
295 if contracts.len() > ENTRY_LIMIT {
296 return Err(Error::Budget);
297 }
298 let mut ids = BTreeSet::new();
299 for contract in contracts {
300 contract.operation().validate()?;
301 if !ids.insert(&contract.operation().id) {
302 return Err(Error::OperationDuplicate { direction, id: contract.operation().id.to_string() });
303 }
304 }
305 }
306 let grants = manifest.requires.iter().map(|required| required.grants.len()).sum::<usize>();
307 if grants > ENTRY_LIMIT {
308 return Err(Error::Budget);
309 }
310 for required in &manifest.requires {
311 let mut seen = BTreeSet::new();
312 let operation = &required.contract.operation().id;
313 for grant in &required.grants {
314 if grant.operation() != operation.as_str()
315 || grant.scope().is_none_or(|scope| scope.is_empty() || scope.len() > GRANT_LIMIT)
316 || !seen.insert(grant)
317 {
318 return Err(Error::Grant { id: required.contract.operation().id.to_string() });
319 }
320 }
321 }
322 if manifest.lifecycle[0].action() != lifecycle::Action::Initialize || manifest.lifecycle[1].action() != lifecycle::Action::Shutdown {
323 return Err(Error::LifecycleOrder);
324 }
325 for descriptor in &manifest.lifecycle {
326 lifecycle::Descriptor::new(descriptor.action(), descriptor.contract().operation().policy)?;
327 }
328 Ok(())
329}
330
331pub const fn encoded_tree_len(
332 facility: &str, owner: &str, provides: &[Node<WireDeclaration>], requires: &[Node<Requirement>], lifecycle: &[OperationPolicy; 2],
333) -> usize {
334 let size = encoded::<0>(facility, owner, provides, requires, lifecycle).cursor;
335 assert!(size - PREFIX.len() - 1 <= crate::FRAGMENT_LIMIT);
336 size
337}
338
339pub const fn encode_tree<const N: usize>(
340 facility: &str, owner: &str, provides: &[Node<WireDeclaration>], requires: &[Node<Requirement>], lifecycle: &[OperationPolicy; 2],
341) -> [u8; N] {
342 assert!(N == encoded_tree_len(facility, owner, provides, requires, lifecycle));
343 encoded::<N>(facility, owner, provides, requires, lifecycle).bytes
344}
345
346pub const fn identity_len(facility: &str, id: &str, version: &str, description: &str, config: &str) -> usize {
347 let size = encoded_identity::<0>(facility, id, version, description, config).cursor;
348 assert!(size - PREFIX.len() - 1 <= crate::FRAGMENT_LIMIT);
349 size
350}
351
352pub const fn encode_identity<const N: usize>(facility: &str, id: &str, version: &str, description: &str, config: &str) -> [u8; N] {
353 assert!(N == identity_len(facility, id, version, description, config));
354 encoded_identity::<N>(facility, id, version, description, config).bytes
355}
356
357pub(crate) const fn valid_identity(facility: &str, owner: &str) -> bool {
358 matches!(facility.as_bytes(), b"guest" | b"sidecar") && crate::artifact_subset(owner)
359}
360
361pub(crate) const fn wire<const N: usize>(out: &mut Encoder<N>, entry: &WireDeclaration) {
362 out.raw(b"{\"operation\":");
363 declaration(out, &entry.operation);
364 out.raw(b",\"version\":");
365 out.number(entry.version as u64);
366 out.raw(b",\"codec\":\"postcard\",\"request\":");
367 out.quoted(entry.request.name);
368 out.raw(b",\"response\":");
369 out.quoted(entry.response.name);
370 if let Some(input) = entry.input {
371 out.raw(b",\"input\":");
372 out.quoted(input.name);
373 }
374 if let Some(output) = entry.output {
375 out.raw(b",\"output\":");
376 out.quoted(output.name);
377 }
378 if let Some(binding) = &entry.grpc {
379 out.raw(b",\"grpc\":{\"package\":");
380 out.quoted(match &binding.package {
381 Cow::Borrowed(value) => value,
382 Cow::Owned(_) => panic!("const gRPC package must be borrowed"),
383 });
384 out.raw(b",\"service\":");
385 out.quoted(match &binding.service {
386 Cow::Borrowed(value) => value,
387 Cow::Owned(_) => panic!("const gRPC service must be borrowed"),
388 });
389 out.raw(b",\"method\":");
390 out.quoted(match &binding.method {
391 Cow::Borrowed(value) => value,
392 Cow::Owned(_) => panic!("const gRPC method must be borrowed"),
393 });
394 out.raw(b"}");
395 }
396 out.raw(b"}");
397}
398
399pub(crate) const fn policy<const N: usize>(out: &mut Encoder<N>, value: OperationPolicy) {
400 out.raw(b"{\"timeout\":");
401 duration(out, value.timeout);
402 out.raw(b",\"attempt_timeout\":");
403 duration(out, value.attempt_timeout);
404 out.raw(b",\"retries\":");
405 match value.retries {
406 RetryPolicy::Never => out.quoted("never"),
407 RetryPolicy::OnTransient { max_retries, backoff: value } => {
408 out.raw(b"{\"on_transient\":{\"max_retries\":");
409 out.number(max_retries as u64);
410 out.raw(b",\"backoff\":");
411 backoff(out, value);
412 out.raw(b"}}");
413 }
414 }
415 out.raw(b",\"restart\":");
416 match value.restart {
417 RestartPolicy::Never => out.quoted("never"),
418 RestartPolicy::OnTransient { credit, sustained_health, backoff: value } => {
419 out.raw(b"{\"on_transient\":{\"credit\":");
420 out.number(credit as u64);
421 out.raw(b",\"sustained_health\":");
422 duration(out, sustained_health);
423 out.raw(b",\"backoff\":");
424 backoff(out, value);
425 out.raw(b"}}");
426 }
427 }
428 out.raw(b"}");
429}
430
431const fn encoded_identity<const N: usize>(facility: &str, id: &str, version: &str, description: &str, config: &str) -> Encoder<N> {
434 assert!(valid_identity(facility, id));
435 let mut out = Encoder { bytes: [0; N], cursor: 0 };
436 out.raw(PREFIX);
437 out.raw(b"{\"kind\":\"plugin\",\"facility\":");
438 out.quoted(facility);
439 out.raw(b",\"id\":");
440 out.quoted(id);
441 out.raw(b",\"version\":");
442 out.quoted(version);
443 out.raw(b",\"description\":");
444 out.quoted(description);
445 out.raw(b",\"config\":");
446 out.quoted(config);
447 out.raw(b"}\0");
448 out
449}
450
451const fn encoded<const N: usize>(
452 facility: &str, owner: &str, provides: &[Node<WireDeclaration>], requires: &[Node<Requirement>], lifecycle: &[OperationPolicy; 2],
453) -> Encoder<N> {
454 assert!(valid_identity(facility, owner));
455 assert!(crate::catalog::count(provides) <= ENTRY_LIMIT);
456 assert!(crate::catalog::count(requires) <= ENTRY_LIMIT);
457 let mut out = Encoder { bytes: [0; N], cursor: 0 };
458 out.raw(PREFIX);
459 out.raw(b"{\"kind\":\"contracts\",\"facility\":");
460 out.quoted(facility);
461 out.raw(b",\"owner\":");
462 out.quoted(owner);
463 out.raw(b",\"provides\":[");
464 tree(&mut out, provides, &mut true);
465 out.raw(b"],\"requires\":[");
466 required(&mut out, requires, &mut true);
467 out.raw(b"],\"lifecycle\":[{\"action\":\"initialize\",\"version\":1,\"policy\":");
468 policy(&mut out, lifecycle[0]);
469 out.raw(b"},{\"action\":\"shutdown\",\"version\":1,\"policy\":");
470 policy(&mut out, lifecycle[1]);
471 out.raw(b"}]}\0");
472 out
473}
474
475const fn required<const N: usize>(out: &mut Encoder<N>, entries: &[Node<Requirement>], first: &mut bool) {
476 let mut index = 0;
477 while index < entries.len() {
478 match &entries[index] {
479 Node::Leaf(entry) => {
480 if !*first {
481 out.raw(b",");
482 }
483 *first = false;
484 out.raw(b"{\"contract\":");
485 wire(out, &entry.declaration);
486 out.raw(b",\"grants\":[");
487 let mut grant = 0;
488 while grant < entry.grants.len() {
489 if grant > 0 {
490 out.raw(b",");
491 }
492 let (operation, scope) = entry.grants[grant].borrowed();
493 assert!(
494 super::publications::same_bytes(operation, entry.declaration.operation.id),
495 "a requirement grants only its own operation"
496 );
497 let Some(scope) = scope else {
498 panic!("a requirement grant names its scope");
499 };
500 assert!(!scope.is_empty() && scope.len() <= GRANT_LIMIT, "grant scopes are nonempty and bounded");
501 out.raw(b"{\"operation\":");
502 out.quoted(operation);
503 out.raw(b",\"scope\":");
504 out.quoted(scope);
505 out.raw(b"}");
506 grant += 1;
507 }
508 out.raw(b"]}");
509 }
510 Node::Nested(nested) => required(out, nested, first),
511 }
512 index += 1;
513 }
514}
515
516const fn tree<const N: usize>(out: &mut Encoder<N>, entries: &[Node<WireDeclaration>], first: &mut bool) {
517 let mut index = 0;
518 while index < entries.len() {
519 match &entries[index] {
520 Node::Leaf(entry) => {
521 if !*first {
522 out.raw(b",");
523 }
524 *first = false;
525 wire(out, entry);
526 }
527 Node::Nested(nested) => tree(out, nested, first),
528 }
529 index += 1;
530 }
531}
532
533const fn declaration<const N: usize>(out: &mut Encoder<N>, entry: &Entry) {
534 out.raw(b"{\"id\":");
535 out.quoted(entry.id);
536 out.raw(b",\"description\":");
537 out.quoted(entry.description);
538 out.raw(b",\"policy\":");
539 policy(out, entry.policy);
540 out.raw(b",\"visibility\":");
541 out.quoted(match entry.visibility {
542 Visibility::Private => "private",
543 Visibility::Shared => "shared",
544 Visibility::Host => "host",
545 });
546 out.raw(b",\"replay\":");
547 out.quoted(match entry.replay {
548 ReplaySafety::Unknown => "unknown",
549 ReplaySafety::Idempotent => "idempotent",
550 ReplaySafety::Deduplicated => "deduplicated",
551 });
552 out.raw(b"}");
553}
554
555const fn duration<const N: usize>(out: &mut Encoder<N>, value: Duration) {
556 out.raw(b"{\"secs\":");
557 out.number(value.as_secs());
558 out.raw(b",\"nanos\":");
559 out.number(value.subsec_nanos() as u64);
560 out.raw(b"}");
561}
562
563const fn backoff<const N: usize>(out: &mut Encoder<N>, value: Backoff) {
564 let jitter = match value {
565 Backoff::Fixed { delay, jitter } => {
566 out.raw(b"{\"fixed\":{\"delay\":");
567 duration(out, delay);
568 jitter
569 }
570 Backoff::Exponential { initial, maximum, jitter } => {
571 out.raw(b"{\"exponential\":{\"initial\":");
572 duration(out, initial);
573 out.raw(b",\"maximum\":");
574 duration(out, maximum);
575 jitter
576 }
577 };
578 out.raw(b",\"jitter\":");
579 out.quoted(match jitter {
580 Jitter::None => "none",
581 Jitter::Deterministic => "deterministic",
582 });
583 out.raw(b"}}");
584}
585
586