- Published on
Keep provider status strings out of UI components
- Authors

- Name
- Mehdi Akiki
Article · Derived state
One component checks whether an account is active. Another accepts active or ready. A third treats every value except disabled as usable.
All three read the same provider field. They have quietly developed different meanings for it.
When the provider adds a new state, the interface becomes inconsistent. The fix belongs at the translation boundary, before those components receive the data.
Make one product decision
Decide what the product actually needs to know. It may need to display an account as usable, blocked, or waiting for review. Those are product states, even if the first provider happens to use similar words.
A small Rust mapping can keep that decision in one place:
#[derive(Debug, PartialEq, Eq)]
enum Availability {
Usable,
Blocked,
Unknown,
}
fn map_status(provider_status: &str) -> Availability {
match provider_status {
"active" => Availability::Usable,
"suspended" | "closed" => Availability::Blocked,
_ => Availability::Unknown,
}
}
These provider strings and meanings are illustrative. Confirm the real provider's contract rather than copying this table. A label that sounds positive may still describe an account that cannot perform the operation your product needs.
Unknown is a deliberate result. It gives the product a chance to explain that the state is unsupported and to stop an action that requires known eligibility. Silently mapping every new value to usable is a risky default.
The stable internal model article explains the wider boundary. Here the narrow change is to stop every interface component from becoming its own provider adapter.
Preserve detail where it is useful
Do not erase the provider's original status if support or a provider-specific feature needs it. Keep it as bounded diagnostic or source detail with an explicit purpose. The normal product decision should still use the translated state.
Some products expose provider workflows directly. In that case, a single generic enum may lose important meaning. Model the provider-specific feature explicitly instead of pretending every provider has the same lifecycle.
The mapping should also distinguish a missing field from an unrecognized value when that difference matters. Defaulting a missing status to active turns a decoding convenience into a business rule.
Test the decisions the interface makes
Use fixtures for every supported state, one unknown state, and a missing or invalid field at the decoder boundary. Assert the resulting product action as well as the enum value.
For example, an unknown state should not enable a destructive action simply because the UI renderer knows how to display its text. Authorization and provider capability checks still belong on the server.
OpenAPI describes schema enums, but a schema cannot prove that a provider still attaches the same business meaning to a value. Keep behavioural contract tests and observations around the adapter.
When a new state arrives, update one mapping and its fixtures. The rest of the interface can continue using the product's contract. That is a much smaller change than searching every component for another slightly different interpretation of active.
A new state reaches the presentation decision
The Rust fixture parses a response whose status is new_provider_state. The adapter does not know that value. It returns Unknown, and the presentation function disables the action with the label “Provider state needs review.” A missing status follows the same path. Neither case becomes usable through an empty string or a default branch.
The control responses matter too. Active allows the action. Closed blocks it. This keeps a conservative unknown case from accidentally making every account unusable. The test checks both the product label and the boolean that controls the action.
Then the fixture makes a deliberate compatibility change: the new provider state maps to Blocked. Active remains Usable. This is the point where provider documentation and the product's meaning need review. A new string arriving in JSON should not silently make that decision for us.
The complete adapter test keeps the raw status at the boundary. The product view receives a stable availability decision. If the UI needs a specific explanation, add a product reason code with reviewed wording rather than passing every provider message straight through.
This is a JSON presentation fixture, not a browser accessibility test or a live provider compatibility test. It proves the selected mapping and fallback behaviour for known inputs. It cannot tell us what a provider will introduce next. Keep an alert or safe diagnostic count for unknown values so the fallback does not become an invisible permanent state.
A small product boundary makes that maintenance easier. A change to a provider enum can be reviewed in one adapter, with controls for the states users already rely on. It need not become a series of scattered conditional changes across buttons, background jobs, and exports.