1use std::sync::Arc;
2
3use serde::{Deserialize, Serialize};
4use std::fmt;
5
6#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct InvalidIdentity {
13 pub input: String,
14 pub reason: String,
15}
16
17impl fmt::Display for InvalidIdentity {
18 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
19 write!(f, "invalid identity {:?}: {}", self.input, self.reason)
20 }
21}
22
23impl std::error::Error for InvalidIdentity {}
24
25fn validate_identity_string(s: &str) -> Result<(), InvalidIdentity> {
26 if s.is_empty() {
27 return Err(InvalidIdentity {
28 input: s.to_string(),
29 reason: "must not be empty".to_string(),
30 });
31 }
32 if s.contains(char::is_whitespace) {
33 return Err(InvalidIdentity {
34 input: s.to_string(),
35 reason: "must not contain whitespace".to_string(),
36 });
37 }
38 if s.contains('/') {
39 return Err(InvalidIdentity {
40 input: s.to_string(),
41 reason: "must not contain slashes".to_string(),
42 });
43 }
44 Ok(())
45}
46
47macro_rules! validated_string_newtype {
52 ($(#[$meta:meta])* $name:ident) => {
53 $(#[$meta])*
54 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
55 pub struct $name(String);
56
57 impl $name {
58 pub fn parse(s: &str) -> Result<Self, InvalidIdentity> {
65 validate_identity_string(s)?;
66 Ok(Self(s.to_string()))
67 }
68
69 #[must_use]
70 pub fn as_str(&self) -> &str {
71 &self.0
72 }
73 }
74
75 impl fmt::Display for $name {
76 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77 f.write_str(&self.0)
78 }
79 }
80
81 impl Serialize for $name {
82 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
83 where
84 S: serde::Serializer,
85 {
86 serializer.serialize_str(&self.0)
87 }
88 }
89
90 impl<'de> Deserialize<'de> for $name {
91 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
92 where
93 D: serde::Deserializer<'de>,
94 {
95 let s = String::deserialize(deserializer)?;
96 Self::parse(&s).map_err(serde::de::Error::custom)
97 }
98 }
99 };
100}
101
102validated_string_newtype!(
103 AgentIdentity
105);
106
107validated_string_newtype!(
108 AgentRuntimeId
110);
111
112#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
118#[serde(rename_all = "snake_case")]
119pub enum AgentAddressability {
120 #[default]
121 Addressable,
122 InternalOnly,
123}
124
125#[derive(Debug, Clone, PartialEq, Eq, Hash)]
131pub struct DisplayName(String);
132
133impl DisplayName {
134 pub fn parse(s: &str) -> Result<Self, InvalidIdentity> {
138 if s.is_empty() {
139 return Err(InvalidIdentity {
140 input: s.to_string(),
141 reason: "display name must not be empty".to_string(),
142 });
143 }
144 Ok(Self(s.to_string()))
145 }
146
147 #[must_use]
148 pub fn as_str(&self) -> &str {
149 &self.0
150 }
151}
152
153impl fmt::Display for DisplayName {
154 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155 f.write_str(&self.0)
156 }
157}
158
159impl Serialize for DisplayName {
160 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
161 where
162 S: serde::Serializer,
163 {
164 serializer.serialize_str(&self.0)
165 }
166}
167
168impl<'de> Deserialize<'de> for DisplayName {
169 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
170 where
171 D: serde::Deserializer<'de>,
172 {
173 let s = String::deserialize(deserializer)?;
174 Self::parse(&s).map_err(serde::de::Error::custom)
175 }
176}
177
178macro_rules! monotonic_u64_newtype {
183 ($(#[$meta:meta])* $name:ident) => {
184 $(#[$meta])*
185 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
186 #[serde(transparent)]
187 pub struct $name(u64);
188
189 impl $name {
190 #[must_use]
191 pub const fn new(value: u64) -> Self {
192 Self(value)
193 }
194
195 #[must_use]
196 pub const fn get(self) -> u64 {
197 self.0
198 }
199 }
200
201 impl fmt::Display for $name {
202 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203 write!(f, "{}", self.0)
204 }
205 }
206 };
207}
208
209monotonic_u64_newtype!(
210 ContinuityGeneration
212);
213
214monotonic_u64_newtype!(
215 CheckpointVersion
217);
218
219monotonic_u64_newtype!(
220 FencingToken
222);
223
224macro_rules! string_newtype {
229 ($(#[$meta:meta])* $name:ident) => {
230 $(#[$meta])*
231 #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
232 #[serde(transparent)]
233 pub struct $name(String);
234
235 impl $name {
236 #[must_use]
237 pub fn new(s: impl Into<String>) -> Self {
238 Self(s.into())
239 }
240
241 #[must_use]
242 pub fn as_str(&self) -> &str {
243 &self.0
244 }
245 }
246
247 impl fmt::Display for $name {
248 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
249 f.write_str(&self.0)
250 }
251 }
252 };
253}
254
255string_newtype!(
256 CorrelationId
258);
259
260string_newtype!(
261 DispatchIdempotencyKey
263);
264
265#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
271pub struct ContinuityRecord {
272 pub identity: AgentIdentity,
273 pub agent_runtime_id: AgentRuntimeId,
274 pub session_id: meerkat_core::types::SessionId,
275 pub generation: ContinuityGeneration,
276 pub checkpoint_version: CheckpointVersion,
277}
278
279#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
285#[serde(rename_all = "snake_case")]
286pub enum ContinuityFailureKind {
287 SnapshotMissing,
288 SnapshotCorrupted,
289 GenerationMismatch,
290 StoreUnavailable,
291}
292
293#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
295pub struct ContinuityFailure {
296 pub identity: AgentIdentity,
297 pub kind: ContinuityFailureKind,
298 pub record: Option<ContinuityRecord>,
299 pub detail: String,
300}
301
302#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
308#[serde(rename_all = "snake_case", tag = "state")]
309pub enum ContinuityResolveState {
310 Uninitialized,
311 Ready { record: ContinuityRecord },
312 Broken { failure: ContinuityFailure },
313}
314
315#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
321pub struct LeaseGrant {
322 pub identity: AgentIdentity,
323 pub fencing_token: FencingToken,
324 #[serde(
325 serialize_with = "serde_duration_ms::serialize",
326 deserialize_with = "serde_duration_ms::deserialize"
327 )]
328 pub ttl: std::time::Duration,
329}
330
331mod serde_duration_ms {
333 use serde::{Deserialize, Deserializer, Serializer};
334 use std::time::Duration;
335
336 pub fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
337 where
338 S: Serializer,
339 {
340 let ms = duration.as_millis();
341 serializer.serialize_u64(ms as u64)
343 }
344
345 pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
346 where
347 D: Deserializer<'de>,
348 {
349 let ms = u64::deserialize(deserializer)?;
350 Ok(Duration::from_millis(ms))
351 }
352}
353
354#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
360#[serde(rename_all = "snake_case", tag = "result")]
361pub enum LeaseAcquireResult {
362 Acquired(LeaseGrant),
363 AlreadyHeld {
364 identity: AgentIdentity,
365 holder: String,
366 },
367}
368
369#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
371#[serde(rename_all = "snake_case", tag = "result")]
372pub enum LeaseRenewResult {
373 Renewed(LeaseGrant),
374 Lost { identity: AgentIdentity },
375}
376
377#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
383#[serde(rename_all = "snake_case")]
384pub enum DispatchOrigin {
385 Connector,
386 Scheduler,
387 Policy,
388 Flow,
389 System,
390}
391
392#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
394pub struct DispatchInput {
395 pub content: meerkat_core::ContentInput,
396 pub origin: DispatchOrigin,
397 pub correlation_id: Option<CorrelationId>,
398 pub idempotency_key: Option<DispatchIdempotencyKey>,
399}
400
401impl DispatchInput {
402 pub fn system(text: impl Into<String>) -> Self {
404 Self {
405 content: meerkat_core::ContentInput::Text(text.into()),
406 origin: DispatchOrigin::System,
407 correlation_id: None,
408 idempotency_key: None,
409 }
410 }
411
412 pub fn with_origin(text: impl Into<String>, origin: DispatchOrigin) -> Self {
414 Self {
415 content: meerkat_core::ContentInput::Text(text.into()),
416 origin,
417 correlation_id: None,
418 idempotency_key: None,
419 }
420 }
421
422 pub fn with_correlation(mut self, id: impl Into<String>) -> Self {
424 self.correlation_id = Some(CorrelationId::new(id));
425 self
426 }
427
428 pub fn with_idempotency(mut self, key: impl Into<String>) -> Self {
430 self.idempotency_key = Some(DispatchIdempotencyKey::new(key));
431 self
432 }
433}
434
435#[derive(Debug, Clone, PartialEq, Eq)]
441pub enum ManagedPeerEdgeError {
442 SelfEdge,
443}
444
445impl fmt::Display for ManagedPeerEdgeError {
446 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
447 match self {
448 Self::SelfEdge => write!(f, "self-edges are not allowed"),
449 }
450 }
451}
452
453impl std::error::Error for ManagedPeerEdgeError {}
454
455#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
460#[serde(into = "ManagedPeerEdgeRaw")]
461pub struct ManagedPeerEdge {
462 a: AgentIdentity,
463 b: AgentIdentity,
464}
465
466#[derive(Serialize, Deserialize)]
468struct ManagedPeerEdgeRaw {
469 a: AgentIdentity,
470 b: AgentIdentity,
471}
472
473impl From<ManagedPeerEdge> for ManagedPeerEdgeRaw {
474 fn from(edge: ManagedPeerEdge) -> Self {
475 Self {
476 a: edge.a,
477 b: edge.b,
478 }
479 }
480}
481
482impl TryFrom<ManagedPeerEdgeRaw> for ManagedPeerEdge {
483 type Error = ManagedPeerEdgeError;
484
485 fn try_from(raw: ManagedPeerEdgeRaw) -> Result<Self, Self::Error> {
486 Self::new(raw.a, raw.b)
487 }
488}
489
490impl<'de> Deserialize<'de> for ManagedPeerEdge {
491 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
492 where
493 D: serde::Deserializer<'de>,
494 {
495 let raw = ManagedPeerEdgeRaw::deserialize(deserializer)?;
496 Self::try_from(raw).map_err(serde::de::Error::custom)
497 }
498}
499
500impl ManagedPeerEdge {
501 pub fn new(a: AgentIdentity, b: AgentIdentity) -> Result<Self, ManagedPeerEdgeError> {
507 if a == b {
508 return Err(ManagedPeerEdgeError::SelfEdge);
509 }
510 if a < b {
511 Ok(Self { a, b })
512 } else {
513 Ok(Self { a: b, b: a })
514 }
515 }
516
517 #[must_use]
518 pub fn a(&self) -> &AgentIdentity {
519 &self.a
520 }
521
522 #[must_use]
523 pub fn b(&self) -> &AgentIdentity {
524 &self.b
525 }
526}
527
528#[derive(Debug, Clone, PartialEq, Eq)]
534pub struct NotAddressable {
535 pub identity: AgentIdentity,
536 pub addressability: AgentAddressability,
537}
538
539impl fmt::Display for NotAddressable {
540 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
541 write!(
542 f,
543 "agent {:?} is not addressable (current: {:?})",
544 self.identity, self.addressability
545 )
546 }
547}
548
549impl std::error::Error for NotAddressable {}
550
551#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
557pub struct DurableAgentSpec {
558 pub identity: AgentIdentity,
559 pub profile: meerkat_mob::ProfileName,
560 #[serde(default)]
561 pub addressability: AgentAddressability,
562 pub display_name: Option<DisplayName>,
563 #[serde(default)]
564 pub labels: std::collections::BTreeMap<String, String>,
565 pub context: Option<serde_json::Value>,
566 #[serde(default)]
567 pub additional_instructions: Vec<String>,
568 #[serde(default)]
569 pub initial_message: Option<meerkat_core::ContentInput>,
570 #[serde(default)]
571 pub runtime_mode_override: Option<meerkat_mob::MobRuntimeMode>,
572}
573
574#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
580#[serde(rename_all = "snake_case")]
581pub enum IdentityLifecycleState {
582 Active,
583 Retiring,
584 Suspended,
585 Uninitialized,
586}
587
588#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
590pub struct LeaseInfo {
591 pub fencing_token: FencingToken,
592 #[serde(
593 serialize_with = "serde_duration_ms::serialize",
594 deserialize_with = "serde_duration_ms::deserialize"
595 )]
596 pub ttl_remaining: std::time::Duration,
597 pub healthy: bool,
598}
599
600#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
602#[serde(rename_all = "snake_case", tag = "kind")]
603pub enum DurabilityPolicy {
604 SyncWriteThrough,
605 AsyncReplicated,
606 BufferedExport { max_loss_window_ms: u64 },
607}
608
609#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
611pub struct ContinuityHealth {
612 pub store_reachable: bool,
613 pub durability_policy: DurabilityPolicy,
614 pub last_checkpoint_version: Option<CheckpointVersion>,
615}
616
617#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
619pub struct IdentityStatus {
620 pub identity: AgentIdentity,
621 pub state: IdentityLifecycleState,
622 pub agent_runtime_id: Option<AgentRuntimeId>,
623 pub session_id: Option<meerkat_core::types::SessionId>,
624 pub profile: Option<meerkat_mob::ProfileName>,
625 pub runtime_mode: Option<meerkat_mob::MobRuntimeMode>,
626 pub addressability: AgentAddressability,
627 pub display_name: Option<DisplayName>,
628 #[serde(default)]
629 pub labels: std::collections::BTreeMap<String, String>,
630 pub generation: Option<ContinuityGeneration>,
631 pub checkpoint_version: Option<CheckpointVersion>,
632 pub lease: Option<LeaseInfo>,
633 pub continuity_health: Option<ContinuityHealth>,
634}
635
636#[derive(Clone, Default)]
641pub struct AgentRuntimeServices {
642 mob_handle: Option<meerkat_mob::MobHandle>,
643}
644
645impl AgentRuntimeServices {
646 pub fn new(mob_handle: meerkat_mob::MobHandle) -> Self {
647 Self {
648 mob_handle: Some(mob_handle),
649 }
650 }
651
652 pub fn empty() -> Self {
653 Self { mob_handle: None }
654 }
655
656 pub fn mob_handle(&self) -> Option<meerkat_mob::MobHandle> {
657 self.mob_handle.clone()
658 }
659
660 pub fn has_mob_handle(&self) -> bool {
661 self.mob_handle.is_some()
662 }
663}
664
665impl std::fmt::Debug for AgentRuntimeServices {
666 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
667 f.debug_struct("AgentRuntimeServices")
668 .field("mob_handle", &self.mob_handle.is_some())
669 .finish()
670 }
671}
672
673impl PartialEq for AgentRuntimeServices {
674 fn eq(&self, other: &Self) -> bool {
675 self.mob_handle.is_some() == other.mob_handle.is_some()
676 }
677}
678
679impl Eq for AgentRuntimeServices {}
680
681#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
683pub struct AgentBuildContext {
684 pub identity: AgentIdentity,
685 pub active_peers: Vec<AgentIdentity>,
686 pub managed_edges: Vec<ManagedPeerEdge>,
687 #[serde(default, skip)]
688 pub runtime_services: AgentRuntimeServices,
689}
690
691#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
693pub struct ExternalToolDef {
694 pub name: String,
695 pub description: String,
696 pub input_schema: serde_json::Value,
697}
698
699#[derive(Clone, Default)]
701pub struct LocalExternalToolOverlay {
702 dispatcher: Option<Arc<dyn meerkat_core::agent::AgentToolDispatcher>>,
703}
704
705impl LocalExternalToolOverlay {
706 pub fn new(dispatcher: Arc<dyn meerkat_core::agent::AgentToolDispatcher>) -> Self {
707 Self {
708 dispatcher: Some(dispatcher),
709 }
710 }
711
712 pub fn empty() -> Self {
713 Self { dispatcher: None }
714 }
715
716 pub fn dispatcher(&self) -> Option<Arc<dyn meerkat_core::agent::AgentToolDispatcher>> {
717 self.dispatcher.clone()
718 }
719
720 pub fn is_some(&self) -> bool {
721 self.dispatcher.is_some()
722 }
723}
724
725impl std::fmt::Debug for LocalExternalToolOverlay {
726 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
727 f.debug_struct("LocalExternalToolOverlay")
728 .field("dispatcher", &self.dispatcher.is_some())
729 .finish()
730 }
731}
732
733impl PartialEq for LocalExternalToolOverlay {
734 fn eq(&self, other: &Self) -> bool {
735 self.dispatcher.is_some() == other.dispatcher.is_some()
736 }
737}
738
739impl Eq for LocalExternalToolOverlay {}
740
741#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
747pub struct AgentBuildDraft {
748 pub model: Option<String>,
749 pub system_prompt: Option<String>,
750 #[serde(default)]
751 pub additional_instructions: Vec<String>,
752 #[serde(default)]
753 pub labels: std::collections::BTreeMap<String, String>,
754 pub app_context: Option<serde_json::Value>,
755 #[serde(default)]
756 pub external_tools: Vec<ExternalToolDef>,
757 #[serde(default, skip)]
758 pub local_external_tools: LocalExternalToolOverlay,
759}
760
761#[derive(Debug, Clone, PartialEq, Eq)]
770pub struct SessionSnapshot {
771 pub data: Vec<u8>,
772}
773
774impl Serialize for SessionSnapshot {
775 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
776 where
777 S: serde::Serializer,
778 {
779 use base64::Engine;
780 use serde::ser::SerializeStruct;
781 let encoded = base64::engine::general_purpose::STANDARD.encode(&self.data);
782 let mut s = serializer.serialize_struct("SessionSnapshot", 1)?;
783 s.serialize_field("data", &encoded)?;
784 s.end()
785 }
786}
787
788impl<'de> Deserialize<'de> for SessionSnapshot {
789 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
790 where
791 D: serde::Deserializer<'de>,
792 {
793 use base64::Engine;
794
795 #[derive(Deserialize)]
796 struct Wrapper {
797 data: String,
798 }
799
800 let wrapper = Wrapper::deserialize(deserializer)?;
801 let data = base64::engine::general_purpose::STANDARD
802 .decode(&wrapper.data)
803 .map_err(serde::de::Error::custom)?;
804 Ok(Self { data })
805 }
806}
807
808#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
814pub struct RosterContext {
815 pub mob_definition: Option<meerkat_mob::MobDefinition>,
816 pub previous_identities: Vec<AgentIdentity>,
817}
818
819#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
821pub struct TopologyContext {
822 pub roster: Vec<DurableAgentSpec>,
823}
824
825#[derive(Debug)]
831pub enum ContinuityStoreError {
832 StaleFencingToken {
833 identity: AgentIdentity,
834 presented: FencingToken,
835 current: FencingToken,
836 },
837 StaleCheckpointVersion {
838 identity: AgentIdentity,
839 presented: CheckpointVersion,
840 current: CheckpointVersion,
841 },
842 NotFound {
843 identity: AgentIdentity,
844 },
845 Io(String),
846 Corruption(String),
847}
848
849impl fmt::Display for ContinuityStoreError {
850 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
851 match self {
852 Self::StaleFencingToken {
853 identity,
854 presented,
855 current,
856 } => write!(
857 f,
858 "stale fencing token for {identity}: presented {presented}, current {current}"
859 ),
860 Self::StaleCheckpointVersion {
861 identity,
862 presented,
863 current,
864 } => write!(
865 f,
866 "stale checkpoint version for {identity}: presented {presented}, current {current}"
867 ),
868 Self::NotFound { identity } => {
869 write!(f, "continuity record not found for {identity}")
870 }
871 Self::Io(msg) => write!(f, "continuity store I/O error: {msg}"),
872 Self::Corruption(msg) => write!(f, "continuity store corruption: {msg}"),
873 }
874 }
875}
876
877impl std::error::Error for ContinuityStoreError {}
878
879#[derive(Debug)]
881pub enum LeaseError {
882 ProviderUnavailable(String),
883 Io(String),
884}
885
886impl fmt::Display for LeaseError {
887 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
888 match self {
889 Self::ProviderUnavailable(msg) => {
890 write!(f, "lease provider unavailable: {msg}")
891 }
892 Self::Io(msg) => write!(f, "lease I/O error: {msg}"),
893 }
894 }
895}
896
897impl std::error::Error for LeaseError {}
898
899#[derive(Debug)]
901pub enum RosterError {
902 ProviderUnavailable(String),
903 Io(String),
904}
905
906impl fmt::Display for RosterError {
907 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
908 match self {
909 Self::ProviderUnavailable(msg) => {
910 write!(f, "roster provider unavailable: {msg}")
911 }
912 Self::Io(msg) => write!(f, "roster I/O error: {msg}"),
913 }
914 }
915}
916
917impl std::error::Error for RosterError {}
918
919#[derive(Debug)]
921pub enum TopologyError {
922 InvalidEdge(String),
923 ProviderUnavailable(String),
924}
925
926impl fmt::Display for TopologyError {
927 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
928 match self {
929 Self::InvalidEdge(msg) => write!(f, "invalid topology edge: {msg}"),
930 Self::ProviderUnavailable(msg) => {
931 write!(f, "topology provider unavailable: {msg}")
932 }
933 }
934 }
935}
936
937impl std::error::Error for TopologyError {}
938
939#[derive(Debug)]
941pub enum CustomizerError {
942 BuildFailed(String),
943 Io(String),
944}
945
946impl fmt::Display for CustomizerError {
947 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
948 match self {
949 Self::BuildFailed(msg) => write!(f, "customizer build failed: {msg}"),
950 Self::Io(msg) => write!(f, "customizer I/O error: {msg}"),
951 }
952 }
953}
954
955impl std::error::Error for CustomizerError {}