Skip to main content

gsm_core/
provider_ops.rs

1//! Provider-core messaging operation contracts (schemas + serde types).
2//!
3//! These types intentionally mirror the JSON Schemas under `schemas/messaging/ops`.
4
5use greentic_types::{ChannelMessageEnvelope, TenantCtx};
6#[cfg(feature = "schemars")]
7use schemars::JsonSchema;
8use serde::{Deserialize, Serialize};
9use serde_json::Value;
10use std::collections::BTreeMap;
11
12/// Canonical message envelope used by ingest/output operations.
13pub type MessageEnvelope = ChannelMessageEnvelope;
14
15/// Output shape for ingest; aliasing the canonical envelope keeps schema parity.
16pub type IngestOutput = ChannelMessageEnvelope;
17
18/// Attachment payload embedded in send/reply requests.
19#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
20#[cfg_attr(feature = "schemars", derive(JsonSchema))]
21pub struct AttachmentInput {
22    pub name: String,
23    pub content_type: String,
24    pub data_base64: String,
25}
26
27/// Optional routing hints for sends.
28#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
29#[cfg_attr(feature = "schemars", derive(JsonSchema))]
30pub struct SendMetadata {
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub thread_id: Option<String>,
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub reply_to: Option<String>,
35    #[serde(default, skip_serializing_if = "Vec::is_empty")]
36    pub tags: Vec<String>,
37}
38
39/// Input contract for the send operation.
40#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
41#[cfg_attr(feature = "schemars", derive(JsonSchema))]
42pub struct SendInput {
43    pub to: String,
44    #[serde(default, skip_serializing_if = "Option::is_none")]
45    pub text: Option<String>,
46    #[serde(default, skip_serializing_if = "Vec::is_empty")]
47    pub attachments: Vec<AttachmentInput>,
48    #[serde(default, skip_serializing_if = "Option::is_none")]
49    pub metadata: Option<SendMetadata>,
50}
51
52/// Input contract for a reply operation.
53#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
54#[cfg_attr(feature = "schemars", derive(JsonSchema))]
55pub struct ReplyInput {
56    pub to: String,
57    pub reply_to: String,
58    #[serde(default, skip_serializing_if = "Option::is_none")]
59    pub text: Option<String>,
60    #[serde(default, skip_serializing_if = "Vec::is_empty")]
61    pub attachments: Vec<AttachmentInput>,
62    #[serde(default, skip_serializing_if = "Option::is_none")]
63    pub metadata: Option<ReplyMetadata>,
64}
65
66/// Metadata attached to replies.
67#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
68#[cfg_attr(feature = "schemars", derive(JsonSchema))]
69pub struct ReplyMetadata {
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub thread_id: Option<String>,
72    #[serde(default, skip_serializing_if = "Vec::is_empty")]
73    pub tags: Vec<String>,
74}
75
76/// Delivery status reported by providers.
77#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
78#[serde(rename_all = "lowercase")]
79#[cfg_attr(feature = "schemars", derive(JsonSchema))]
80pub enum SendStatus {
81    Sent,
82    Queued,
83}
84
85/// Output contract shared by send/reply.
86#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
87#[cfg_attr(feature = "schemars", derive(JsonSchema))]
88pub struct SendOutput {
89    pub message_id: String,
90    #[serde(default, skip_serializing_if = "Option::is_none")]
91    pub provider_message_id: Option<String>,
92    #[serde(default, skip_serializing_if = "Option::is_none")]
93    pub thread_id: Option<String>,
94    pub status: SendStatus,
95    #[serde(default, skip_serializing_if = "Option::is_none")]
96    pub ts: Option<String>,
97}
98
99/// Output contract for replies; shares the same fields as send.
100pub type ReplyOutput = SendOutput;
101
102/// Input contract for ingest (webhook normalization).
103#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
104#[cfg_attr(feature = "schemars", derive(JsonSchema))]
105pub struct IngestInput {
106    pub provider_type: String,
107    pub payload: Value,
108    #[serde(default, skip_serializing_if = "Option::is_none")]
109    pub tenant: Option<TenantCtx>,
110    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
111    pub headers: BTreeMap<String, String>,
112    #[serde(default, skip_serializing_if = "Option::is_none")]
113    pub received_at: Option<String>,
114}