Skip to content
DocumentationBuild a provider adapter
On this page

Page resources

Open Markdownllms.txtView source

Last updated

This recipe connects an external carrier API to a One-owned shipment operation without letting the carrier own the application contract.

The .one and Rust APIs below are illustrative end-product syntax.

1. Keep the domain operation independent

one 1

semantic parcelhub.shipping
    domain one.contracts@1
    operation CreateShipment@1
        input ShipmentRequest
        output Shipment
        faults [AddressRejected, ServiceUnavailable]
        effect external_create(shipment)
        idempotency caller_key(input.request_id)
        outcome query ShipmentStatus.GetByRequest
        authority shipment:create

Carrier product names, URLs, headers, credentials, and response codes do not enter this contract.

2. Publish a bounded realization claim

semantic acme_carrier.provider
    domain one.providers@1
    realization AcmeShipmentCreate@1
        provides parcelhub.shipping#CreateShipment@1
        target process_remote
        protocol https_json

        supports idempotency caller_key
        supports outcome_lookup by_request_id
        supports cancellation before_dispatch

        requires secret acme_carrier_api_key
        requires outbound_origin https("api.carrier.example")
        requires region [eu]

Publication makes the claim eligible for governed catalog admission. It does not select the provider, create a binding, read a credential, or authorize an external effect.

3. Keep the adapter thin

pub async fn create_shipment(
    ctx: CarrierContext,
    request: ShipmentRequest,
) -> Result<Shipment, CarrierBoundaryFault> {
    let wire = AcmeCreateRequest::project(&request)?;
    let response = ctx
        .http()
        .post(ctx.endpoint().shipments())
        .idempotency_key(request.request_id)
        .json(wire)
        .send()
        .await?;

    AcmeCreateResponse::lower(response)
}

Typed projection owns the external wire format and reports loss. The adapter maps protocol behavior and evidence; it does not copy generic HTTP lifecycle, secret delivery, retry, timeout, or observation machinery.

4. Reconcile unknown outcomes

pub async fn observe(
    ctx: CarrierContext,
    request_id: ShipmentRequestId,
) -> Result<ProviderObservation<Shipment>, CarrierBoundaryFault> {
    let response = ctx.http().get(ctx.endpoint().by_request(request_id)).send().await?;
    AcmeShipmentObservation::lower(response)
}

An observation is linked to the original external effect. It does not rewrite the attempt or become a One fact merely because the carrier returned success.

5. Prove the exact claim

one test --root AcmeShipmentProvider --scenario conformance
one plan --root FulfillmentWorker
one inspect plan:sha256:… --subject provider

Conformance covers schema mapping, authentication, deadlines, rate limits, idempotency, response loss, outcome lookup, malformed replies, disclosure, and removal. Planning records why this exact realization satisfies the root and why other candidates do not.

See providers and adapters and communication.