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}