Skip to main content

backbone_integrations/application/service/
integrations_ports.rs

1//! Inbound target port (hand-authored, user-owned) — the ACL to the internal module a connector feeds.
2//!
3//! An inbound provider event maps to an INTERNAL action through the target module's PUBLIC contract — a
4//! settled payment notification → `backbone-payment` `create_payment`; a marketplace order →
5//! `backbone-selling`; a bank-feed line → `backbone-banking`. Integrations never imports those modules — a
6//! composing service wires the real target behind this port; the seam test drives the REAL module. Zero
7//! normal Cargo edge. Not every event needs an action: a "pending" notification is intentionally IGNORED.
8
9use serde::{Deserialize, Serialize};
10use uuid::Uuid;
11
12/// A request to map a parsed inbound event into an internal action.
13#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
14pub struct MapRequest {
15    /// Legacy tenancy twin (ADR-0029): the module itself is tenant-agnostic — its
16    /// tables carry no company column and the composing service's tenancy
17    /// decorator owns org scoping. The field stays because the TARGET modules on
18    /// the far side of this port (payment, selling, banking) may still be
19    /// company-fenced; the caller names the owning company and an unknown or
20    /// stale value fails closed at the target, never here.
21    pub company_id: Uuid,
22    pub connector_kind: String, // payment_gateway | marketplace | bank_feed | courier
23    pub event_type: String,
24    pub external_id: String,
25    /// Stable per-event key (the integration event id) — the target forwards it so a re-map of a stranded
26    /// event can't create a duplicate internal record.
27    pub idempotency_key: String,
28    pub payload: serde_json::Value,
29}
30
31/// The internal record an event mapped to.
32#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
33pub struct MappedRef {
34    pub internal_ref_type: String, // payment | sales_order | bank_transaction
35    pub internal_ref_id: Uuid,
36}
37
38/// The outcome of mapping — either an internal record was created/changed, or the event was intentionally
39/// ignored (no internal action required for this provider event, e.g. a "pending" notification).
40#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
41pub enum MapOutcome {
42    Mapped(MappedRef),
43    Ignored(String), // reason
44}
45
46/// The target rejected the mapping (unmappable payload / business-rule failure). `code` is stable.
47#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
48pub struct MapRejected {
49    pub code: String,
50    pub message: String,
51}
52
53/// The inbound mapping seam. A composing service implements it over the target module.
54#[async_trait::async_trait]
55pub trait TargetPort: Send + Sync {
56    async fn map(&self, req: &MapRequest) -> Result<MapOutcome, MapRejected>;
57}