Skip to main content

cookie_agent_protocol/
provider.rs

1use std::{borrow::Cow, collections::BTreeMap, fmt};
2
3use jiff::Timestamp;
4use schemars::{JsonSchema, Schema, SchemaGenerator, json_schema};
5use serde::{Deserialize, Serialize};
6use zeroize::Zeroize;
7
8use crate::*;
9
10const MAX_SAFE_INTEGER: u64 = 9_007_199_254_740_991;
11
12macro_rules! positive_counter {
13    ($name:ident, $description:literal) => {
14        #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
15        #[serde(transparent)]
16        pub struct $name(u64);
17
18        impl $name {
19            pub fn new(value: u64) -> Result<Self, ProviderSchemaError> {
20                if (1..=MAX_SAFE_INTEGER).contains(&value) {
21                    Ok(Self(value))
22                } else {
23                    Err(ProviderSchemaError::InvalidCounter)
24                }
25            }
26
27            #[must_use]
28            pub const fn get(self) -> u64 { self.0 }
29        }
30
31        impl<'de> Deserialize<'de> for $name {
32            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
33            where D: serde::Deserializer<'de> {
34                Self::new(u64::deserialize(deserializer)?).map_err(serde::de::Error::custom)
35            }
36        }
37
38        impl JsonSchema for $name {
39            fn inline_schema() -> bool { true }
40            fn schema_name() -> Cow<'static, str> { Cow::Borrowed(stringify!($name)) }
41            fn json_schema(_: &mut SchemaGenerator) -> Schema {
42                json_schema!({"type":"integer","minimum":1,"maximum":MAX_SAFE_INTEGER,"description":$description})
43            }
44        }
45    };
46}
47
48positive_counter!(
49    ProviderStoreGeneration,
50    "Monotonic provider-store generation."
51);
52positive_counter!(
53    ProviderConnectionGeneration,
54    "Monotonic provider connection generation."
55);
56
57#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
58pub struct BoundedSetupString(String);
59
60impl BoundedSetupString {
61    pub const MAX_BYTES: usize = 2048;
62    pub fn new(value: impl Into<String>) -> Result<Self, ProviderSchemaError> {
63        let value = value.into();
64        if value.is_empty() || value.len() > Self::MAX_BYTES || value.chars().any(char::is_control)
65        {
66            Err(ProviderSchemaError::InvalidSetupString)
67        } else {
68            Ok(Self(value))
69        }
70    }
71    #[must_use]
72    pub fn as_str(&self) -> &str {
73        &self.0
74    }
75}
76impl Serialize for BoundedSetupString {
77    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
78    where
79        S: serde::Serializer,
80    {
81        serializer.serialize_str(&self.0)
82    }
83}
84impl<'de> Deserialize<'de> for BoundedSetupString {
85    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
86    where
87        D: serde::Deserializer<'de>,
88    {
89        Self::new(String::deserialize(deserializer)?).map_err(serde::de::Error::custom)
90    }
91}
92impl JsonSchema for BoundedSetupString {
93    fn inline_schema() -> bool {
94        true
95    }
96    fn schema_name() -> Cow<'static, str> {
97        Cow::Borrowed("BoundedSetupString")
98    }
99    fn json_schema(_: &mut SchemaGenerator) -> Schema {
100        json_schema!({"type":"string","minLength":1,"maxLength":2048,"pattern":"^[^\\p{Cc}\\p{Cf}]+$"})
101    }
102}
103
104#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
105#[serde(untagged)]
106pub enum SafeSetupValue {
107    Bool(bool),
108    Integer(i64),
109    Code(SafeCode),
110    String(BoundedSetupString),
111}
112
113#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
114#[serde(rename_all = "snake_case")]
115pub enum SetupFieldType {
116    String,
117    Code,
118    Integer,
119    Bool,
120}
121
122#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
123#[serde(rename_all = "snake_case")]
124pub enum CredentialFieldType {
125    ApiKey,
126    AccessToken,
127    AccessKeyId,
128    SecretAccessKey,
129    SessionToken,
130}
131
132#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
133#[serde(deny_unknown_fields)]
134pub struct SetupFieldValidation {
135    pub value_type: SetupFieldType,
136    #[serde(deserialize_with = "crate::deserialize_required_option")]
137    #[schemars(with = "crate::NullableSchema<u32>", required)]
138    pub min_length: Option<u32>,
139    #[serde(deserialize_with = "crate::deserialize_required_option")]
140    #[schemars(with = "crate::NullableSchema<u32>", required)]
141    pub max_length: Option<u32>,
142    #[serde(deserialize_with = "crate::deserialize_required_option")]
143    #[schemars(with = "crate::NullableSchema<i64>", required)]
144    pub minimum: Option<i64>,
145    #[serde(deserialize_with = "crate::deserialize_required_option")]
146    #[schemars(with = "crate::NullableSchema<i64>", required)]
147    pub maximum: Option<i64>,
148}
149
150#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
151#[serde(deny_unknown_fields)]
152pub struct SetupFieldDescriptor {
153    pub id: SetupFieldId,
154    pub display_name: SafeDisplayText,
155    pub help: SafeDisplayText,
156    pub required: bool,
157    #[serde(deserialize_with = "crate::deserialize_required_option")]
158    #[schemars(with = "crate::NullableSchema<SafeSetupValue>", required)]
159    pub default: Option<SafeSetupValue>,
160    pub validation: SetupFieldValidation,
161    pub safe_to_project: bool,
162}
163
164#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
165#[serde(deny_unknown_fields)]
166pub struct AuthCredentialDescriptor {
167    pub id: AuthFieldName,
168    pub display_name: SafeDisplayText,
169    pub help: SafeDisplayText,
170    pub required: bool,
171    pub credential_type: CredentialFieldType,
172}
173
174#[derive(Clone, Debug, Eq, JsonSchema, PartialEq, Serialize)]
175#[serde(deny_unknown_fields)]
176pub struct AuthMethodDescriptor {
177    pub id: AuthMethodId,
178    pub display_name: SafeDisplayText,
179    #[schemars(length(min = 1, max = 32))]
180    pub credentials: Vec<AuthCredentialDescriptor>,
181}
182impl<'de> Deserialize<'de> for AuthMethodDescriptor {
183    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
184    where
185        D: serde::Deserializer<'de>,
186    {
187        #[derive(Deserialize)]
188        #[serde(deny_unknown_fields)]
189        struct Wire {
190            id: AuthMethodId,
191            display_name: SafeDisplayText,
192            credentials: Vec<AuthCredentialDescriptor>,
193        }
194        let wire = Wire::deserialize(deserializer)?;
195        if wire.credentials.is_empty()
196            || wire.credentials.len() > 32
197            || wire
198                .credentials
199                .windows(2)
200                .any(|pair| pair[0].id >= pair[1].id)
201        {
202            return Err(serde::de::Error::custom(
203                "auth credentials must be a sorted unique list of 1..=32 fields",
204            ));
205        }
206        Ok(Self {
207            id: wire.id,
208            display_name: wire.display_name,
209            credentials: wire.credentials,
210        })
211    }
212}
213
214#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
215#[serde(rename_all = "snake_case")]
216pub enum ProviderPresence {
217    Current,
218    Removed,
219}
220
221#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
222#[serde(rename_all = "snake_case")]
223pub enum ProviderSupportState {
224    Supported,
225    Unsupported,
226    Quarantined,
227}
228
229#[derive(Clone, Debug, Eq, JsonSchema, PartialEq, Serialize)]
230#[serde(deny_unknown_fields)]
231pub struct ProviderSupport {
232    pub state: ProviderSupportState,
233    #[serde(deserialize_with = "crate::deserialize_required_option")]
234    #[schemars(with = "crate::NullableSchema<SafeCode>", required)]
235    pub reason: Option<SafeCode>,
236}
237impl<'de> Deserialize<'de> for ProviderSupport {
238    fn deserialize<D>(d: D) -> Result<Self, D::Error>
239    where
240        D: serde::Deserializer<'de>,
241    {
242        #[derive(Deserialize)]
243        #[serde(deny_unknown_fields)]
244        struct Wire {
245            state: ProviderSupportState,
246            #[serde(deserialize_with = "crate::deserialize_required_option")]
247            reason: Option<SafeCode>,
248        }
249        let wire = Wire::deserialize(d)?;
250        if (wire.state == ProviderSupportState::Supported) != wire.reason.is_none() {
251            return Err(serde::de::Error::custom(
252                "supported providers have no reason; unsupported and quarantined providers require one",
253            ));
254        }
255        Ok(Self {
256            state: wire.state,
257            reason: wire.reason,
258        })
259    }
260}
261
262#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
263#[serde(deny_unknown_fields)]
264pub struct QuarantineDiagnostic {
265    pub code: SafeCode,
266    pub message: SafeErrorMessage,
267}
268
269#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
270#[serde(rename_all = "snake_case")]
271pub enum ProviderConfigurationState {
272    Unconfigured,
273    Authored,
274    Stored,
275    AuthoredAndStored,
276}
277
278#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
279#[serde(rename_all = "snake_case")]
280pub enum EffectiveAuthSource {
281    AuthoredApiKey,
282    AuthoredOverride,
283    ProviderStore,
284    NoAuth,
285}
286
287#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
288#[serde(rename_all = "snake_case")]
289pub enum EffectiveAuthState {
290    AuthoredApiKey,
291    AuthoredOverride,
292    ProviderStore,
293    NoAuth,
294    Unavailable,
295}
296
297#[derive(Clone, Debug, Eq, JsonSchema, PartialEq, Serialize)]
298#[serde(deny_unknown_fields)]
299pub struct DurableConnectionDescriptor {
300    pub provider_id: ProviderId,
301    pub setup_values: BTreeMap<SetupFieldId, SafeSetupValue>,
302    pub setup_fingerprint: Sha256Digest,
303    pub recipe_fingerprint: Sha256Digest,
304    pub auth_method: AuthMethodId,
305    #[schemars(length(max = 32))]
306    pub credential_fields: Vec<AuthFieldName>,
307    pub connection_generation: ProviderConnectionGeneration,
308    pub connected_at: Timestamp,
309}
310impl<'de> Deserialize<'de> for DurableConnectionDescriptor {
311    fn deserialize<D>(d: D) -> Result<Self, D::Error>
312    where
313        D: serde::Deserializer<'de>,
314    {
315        #[derive(Deserialize)]
316        #[serde(deny_unknown_fields)]
317        struct Wire {
318            provider_id: ProviderId,
319            setup_values: BTreeMap<SetupFieldId, SafeSetupValue>,
320            setup_fingerprint: Sha256Digest,
321            recipe_fingerprint: Sha256Digest,
322            auth_method: AuthMethodId,
323            credential_fields: Vec<AuthFieldName>,
324            connection_generation: ProviderConnectionGeneration,
325            connected_at: Timestamp,
326        }
327        let wire = Wire::deserialize(d)?;
328        if wire.setup_values.len() > 32
329            || wire.credential_fields.len() > 32
330            || wire
331                .credential_fields
332                .windows(2)
333                .any(|pair| pair[0] >= pair[1])
334        {
335            return Err(serde::de::Error::custom(
336                "durable connection setup and credential metadata are not bounded and sorted",
337            ));
338        }
339        Ok(Self {
340            provider_id: wire.provider_id,
341            setup_values: wire.setup_values,
342            setup_fingerprint: wire.setup_fingerprint,
343            recipe_fingerprint: wire.recipe_fingerprint,
344            auth_method: wire.auth_method,
345            credential_fields: wire.credential_fields,
346            connection_generation: wire.connection_generation,
347            connected_at: wire.connected_at,
348        })
349    }
350}
351
352#[derive(Clone, Debug, Eq, JsonSchema, PartialEq, Serialize)]
353#[serde(deny_unknown_fields)]
354pub struct ProviderDescriptor {
355    pub id: ProviderId,
356    pub display_name: SafeDisplayText,
357    pub presence: ProviderPresence,
358    pub support: ProviderSupport,
359    pub setup_fields: Vec<SetupFieldDescriptor>,
360    pub auth_methods: Vec<AuthMethodDescriptor>,
361    pub configuration: ProviderConfigurationState,
362    pub effective_auth_state: EffectiveAuthState,
363    #[serde(deserialize_with = "crate::deserialize_required_option")]
364    #[schemars(with = "crate::NullableSchema<DurableConnectionDescriptor>", required)]
365    pub durable_connection: Option<DurableConnectionDescriptor>,
366    #[serde(deserialize_with = "crate::deserialize_required_option")]
367    #[schemars(with = "crate::NullableSchema<QuarantineDiagnostic>", required)]
368    pub quarantine: Option<QuarantineDiagnostic>,
369}
370impl<'de> Deserialize<'de> for ProviderDescriptor {
371    fn deserialize<D>(d: D) -> Result<Self, D::Error>
372    where
373        D: serde::Deserializer<'de>,
374    {
375        #[derive(Deserialize)]
376        #[serde(deny_unknown_fields)]
377        struct Wire {
378            id: ProviderId,
379            display_name: SafeDisplayText,
380            presence: ProviderPresence,
381            support: ProviderSupport,
382            setup_fields: Vec<SetupFieldDescriptor>,
383            auth_methods: Vec<AuthMethodDescriptor>,
384            configuration: ProviderConfigurationState,
385            effective_auth_state: EffectiveAuthState,
386            #[serde(deserialize_with = "crate::deserialize_required_option")]
387            durable_connection: Option<DurableConnectionDescriptor>,
388            #[serde(deserialize_with = "crate::deserialize_required_option")]
389            quarantine: Option<QuarantineDiagnostic>,
390        }
391        let wire = Wire::deserialize(d)?;
392        if wire.setup_fields.len() > 32
393            || wire
394                .setup_fields
395                .windows(2)
396                .any(|pair| pair[0].id >= pair[1].id)
397            || wire.auth_methods.len() > 16
398            || wire
399                .auth_methods
400                .windows(2)
401                .any(|pair| pair[0].id >= pair[1].id)
402            || (wire.support.state == ProviderSupportState::Quarantined)
403                != wire.quarantine.is_some()
404            || wire
405                .durable_connection
406                .as_ref()
407                .is_some_and(|connection| connection.provider_id != wire.id)
408        {
409            return Err(serde::de::Error::custom(
410                "provider descriptor metadata is inconsistent or not strictly sorted",
411            ));
412        }
413        Ok(Self {
414            id: wire.id,
415            display_name: wire.display_name,
416            presence: wire.presence,
417            support: wire.support,
418            setup_fields: wire.setup_fields,
419            auth_methods: wire.auth_methods,
420            configuration: wire.configuration,
421            effective_auth_state: wire.effective_auth_state,
422            durable_connection: wire.durable_connection,
423            quarantine: wire.quarantine,
424        })
425    }
426}
427
428#[derive(Clone, Eq, PartialEq)]
429pub struct ProviderCredentialValues(BTreeMap<String, String>);
430
431impl ProviderCredentialValues {
432    #[must_use]
433    pub fn get(&self, field: &AuthFieldName) -> Option<&str> {
434        self.0.get(field.as_str()).map(String::as_str)
435    }
436    pub fn field_names(&self) -> impl Iterator<Item = &str> {
437        self.0.keys().map(String::as_str)
438    }
439}
440impl fmt::Debug for ProviderCredentialValues {
441    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
442        formatter.write_str("ProviderCredentialValues(<redacted>)")
443    }
444}
445impl Drop for ProviderCredentialValues {
446    fn drop(&mut self) {
447        for value in self.0.values_mut() {
448            value.zeroize();
449        }
450    }
451}
452impl<'de> Deserialize<'de> for ProviderCredentialValues {
453    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
454    where
455        D: serde::Deserializer<'de>,
456    {
457        struct Visitor;
458        impl<'de> serde::de::Visitor<'de> for Visitor {
459            type Value = BTreeMap<String, String>;
460            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
461                formatter.write_str("a unique credential field map")
462            }
463            fn visit_map<A>(self, mut access: A) -> Result<Self::Value, A::Error>
464            where
465                A: serde::de::MapAccess<'de>,
466            {
467                let mut raw = BTreeMap::new();
468                while let Some((key, value)) = access.next_entry::<String, String>()? {
469                    if raw.len() >= 32 {
470                        return Err(serde::de::Error::custom("auth values exceed 32 fields"));
471                    }
472                    if raw.insert(key, value).is_some() {
473                        return Err(serde::de::Error::custom("duplicate auth credential field"));
474                    }
475                }
476                Ok(raw)
477            }
478        }
479        let raw = deserializer.deserialize_map(Visitor)?;
480        if raw.len() > 32
481            || raw.iter().any(|(key, value)| {
482                AuthFieldName::new(key.clone()).is_err()
483                    || value.is_empty()
484                    || value.len() > 16 * 1024
485            })
486        {
487            return Err(serde::de::Error::custom(
488                "auth values must contain at most 32 strict bounded nonempty credential fields",
489            ));
490        }
491        Ok(Self(raw))
492    }
493}
494
495#[derive(Clone, Debug, Eq, PartialEq)]
496pub struct ProviderConnectParams {
497    pub provider_id: ProviderId,
498    pub expected_catalog_revision: CatalogRevision,
499    pub setup_values: BTreeMap<SetupFieldId, SafeSetupValue>,
500    pub auth_method: AuthMethodId,
501    pub auth_values: ProviderCredentialValues,
502    pub client_connect_id: ClientConnectId,
503}
504
505impl ProviderConnectParams {
506    pub fn validate(&self) -> Result<(), ProviderSchemaError> {
507        if self.setup_values.len() > 32 {
508            Err(ProviderSchemaError::TooManySetupFields)
509        } else {
510            Ok(())
511        }
512    }
513}
514impl<'de> Deserialize<'de> for ProviderConnectParams {
515    fn deserialize<D>(d: D) -> Result<Self, D::Error>
516    where
517        D: serde::Deserializer<'de>,
518    {
519        #[derive(Deserialize)]
520        #[serde(deny_unknown_fields)]
521        struct Wire {
522            provider_id: ProviderId,
523            expected_catalog_revision: CatalogRevision,
524            #[serde(deserialize_with = "deserialize_setup_values")]
525            setup_values: BTreeMap<SetupFieldId, SafeSetupValue>,
526            auth_method: AuthMethodId,
527            auth_values: ProviderCredentialValues,
528            client_connect_id: ClientConnectId,
529        }
530        let w = Wire::deserialize(d)?;
531        let value = Self {
532            provider_id: w.provider_id,
533            expected_catalog_revision: w.expected_catalog_revision,
534            setup_values: w.setup_values,
535            auth_method: w.auth_method,
536            auth_values: w.auth_values,
537            client_connect_id: w.client_connect_id,
538        };
539        value.validate().map_err(serde::de::Error::custom)?;
540        Ok(value)
541    }
542}
543
544fn deserialize_setup_values<'de, D>(
545    d: D,
546) -> Result<BTreeMap<SetupFieldId, SafeSetupValue>, D::Error>
547where
548    D: serde::Deserializer<'de>,
549{
550    struct Visitor;
551    impl<'de> serde::de::Visitor<'de> for Visitor {
552        type Value = BTreeMap<SetupFieldId, SafeSetupValue>;
553        fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
554            f.write_str("a unique bounded setup field map")
555        }
556        fn visit_map<A>(self, mut access: A) -> Result<Self::Value, A::Error>
557        where
558            A: serde::de::MapAccess<'de>,
559        {
560            let mut values = BTreeMap::new();
561            while let Some((key, value)) = access.next_entry::<SetupFieldId, SafeSetupValue>()? {
562                if values.len() >= 32 {
563                    return Err(serde::de::Error::custom("setup values exceed 32 fields"));
564                }
565                if values.insert(key, value).is_some() {
566                    return Err(serde::de::Error::custom("duplicate setup field"));
567                }
568            }
569            Ok(values)
570        }
571    }
572    d.deserialize_map(Visitor)
573}
574
575#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
576#[serde(deny_unknown_fields)]
577pub struct DurableProviderReceipt {
578    pub receipt_id: DurableProviderReceiptId,
579    pub store_revision: ProviderStoreRevision,
580    pub provider_state_revision: ProviderStateRevision,
581    pub committed_at: Timestamp,
582}
583
584#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
585#[serde(deny_unknown_fields)]
586pub struct ProviderConnectResult {
587    pub durable_connection: DurableConnectionDescriptor,
588    pub effective_auth_source: EffectiveAuthSource,
589    pub runtime: RuntimeSnapshotV1,
590    pub replayed: bool,
591}
592
593#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
594#[serde(deny_unknown_fields)]
595pub struct ProviderDisconnectParams {
596    pub provider_id: ProviderId,
597    pub expected_runtime_revision: RuntimeRevision,
598    pub expected_provider_state_revision: ProviderStateRevision,
599    #[serde(deserialize_with = "crate::deserialize_required_option")]
600    #[schemars(with = "crate::NullableSchema<ProviderConnectionGeneration>", required)]
601    pub expected_connection_generation: Option<ProviderConnectionGeneration>,
602    pub client_request_id: ClientRequestId,
603}
604
605#[derive(Clone, Debug, Eq, JsonSchema, PartialEq, Serialize)]
606#[serde(deny_unknown_fields)]
607pub struct ProviderDisconnectResult {
608    pub durable_receipt: DurableProviderReceipt,
609    pub provider_id: ProviderId,
610    pub disconnected: bool,
611    pub effective_auth_state: EffectiveAuthState,
612    pub runtime: RuntimeSnapshotResult,
613    pub replayed: bool,
614}
615impl<'de> Deserialize<'de> for ProviderDisconnectResult {
616    fn deserialize<D>(d: D) -> Result<Self, D::Error>
617    where
618        D: serde::Deserializer<'de>,
619    {
620        #[derive(Deserialize)]
621        #[serde(deny_unknown_fields)]
622        struct Wire {
623            durable_receipt: DurableProviderReceipt,
624            provider_id: ProviderId,
625            disconnected: bool,
626            effective_auth_state: EffectiveAuthState,
627            runtime: RuntimeSnapshotResult,
628            replayed: bool,
629        }
630        let w = Wire::deserialize(d)?;
631        if !w.disconnected {
632            return Err(serde::de::Error::custom(
633                "disconnected must be true on success",
634            ));
635        }
636        Ok(Self {
637            durable_receipt: w.durable_receipt,
638            provider_id: w.provider_id,
639            disconnected: w.disconnected,
640            effective_auth_state: w.effective_auth_state,
641            runtime: w.runtime,
642            replayed: w.replayed,
643        })
644    }
645}
646
647#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
648#[serde(rename_all = "snake_case")]
649pub enum ProviderConnectErrorCode {
650    UnknownProvider,
651    UnsupportedProvider,
652    QuarantinedProvider,
653    RemovedWithoutRetainedRecipeMatch,
654    CatalogRevisionConflict,
655    MissingSetupField,
656    InvalidSetupField,
657    MissingCredential,
658    InvalidCredential,
659    UnsupportedAuthMethod,
660    ProviderStoreWriteFailed,
661    LockContention,
662    RuntimeCompileFailed,
663    IdempotencyConflict,
664}
665
666#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
667#[serde(deny_unknown_fields)]
668pub struct ProviderConnectError {
669    pub code: ProviderConnectErrorCode,
670    pub provider_id: ProviderId,
671    pub client_connect_id: ClientConnectId,
672}
673
674#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
675#[serde(rename_all = "snake_case")]
676pub enum ProviderDisconnectErrorCode {
677    InvalidProvider,
678    CustomProviderNotStoreBacked,
679    RuntimeRevisionConflict,
680    ProviderStateRevisionConflict,
681    StaleProviderConnectionGeneration,
682    ProviderStoreWriteFailed,
683    LockContention,
684    RuntimeCompileFailed,
685    IdempotencyConflict,
686}
687
688#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
689#[serde(deny_unknown_fields)]
690pub struct ProviderDisconnectError {
691    pub code: ProviderDisconnectErrorCode,
692    pub provider_id: ProviderId,
693    pub client_request_id: ClientRequestId,
694}
695
696#[derive(Clone, Copy, Debug, Eq, PartialEq)]
697pub enum ProviderSchemaError {
698    InvalidCounter,
699    InvalidSetupString,
700    TooManySetupFields,
701}
702impl fmt::Display for ProviderSchemaError {
703    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
704        formatter.write_str(match self {
705            Self::InvalidCounter => "counter must be positive and I-JSON safe",
706            Self::InvalidSetupString => "setup string must be control-free and 1..=2048 bytes",
707            Self::TooManySetupFields => "setup values exceed 32 fields",
708        })
709    }
710}
711impl std::error::Error for ProviderSchemaError {}