Problem Details lezen zonder een foutmelding als beslisregel te gebruiken
De kern
Gebruik de ontvangen HTTP-status voor de foutklasse; bewaar een afwijkende status in het JSON-object als contractafwijking. Kies geen actie op basis van een titel.
Begin bij de beslissing van de lezer
Een alleen-lezenclient moet een mislukt verzoek begrijpelijk tonen. Dat vraagt niet meteen om een retry-engine. De eerste beslissing is smaller: ligt de foutklasse bij het verzoek of bij de dienst, is er een herkenbaar probleemtype, en spreken de antwoordlagen elkaar tegen? Bewaar die uitkomsten afzonderlijk. Een mooie titel mag een fout niet ongemerkt tot succes promoveren.
RFC 9457 maakt de lichaamsstatus adviserend en schrijft voor dat een bekend lid met een verkeerd waardetype wordt genegeerd alsof het ontbreekt.[1] Onze lokale presentatieregel gebruikt de ontvangen HTTP-status voor de grove foutklasse. Een geldig getypeerde maar afwijkende lichaamsstatus geeft een contractmelding; de code bepaalt niet wie de afwijking veroorzaakte.
Een kaart met vier onafhankelijke velden
- Ontvangen HTTP-status: de transportwaarneming, niet een herberekende waarde.
- Representatie: alleen application/problem+json gaat naar deze probleemtak.
- Probleemtype: een machine-identiteit; een ontbrekend type wordt hier about:blank.
- Afwijking: leg een verschil met de adviserend meegestuurde status vast zonder de volledige fouttekst te loggen.
Een titel kan vertaald of redactioneel gewijzigd worden. Bouw dus geen dispatch op de tekst Temporary. Houd een generieke foutweergave beschikbaar voor een andere representatie. Leesbare uitleg kan nuttig zijn voor de gebruiker, maar de onderstaande oefening slaat haar bewust niet op en verzint geen provider-specifieke herstelactie.
Voer de tegenstrijdige fixture uit
Het volledige Python-voorbeeld gebruikt reeds gedecodeerde, verzonnen JSON-objecten en verstuurt niets. De invoer combineert HTTP 400 met lichaamsstatus 503. De fixtures negeren null, "400" en True als lichaamsstatus, samen met type:true; ignored_members bewaart alleen de lidnamen als diagnose. Een ontbrekend of verkeerd getypeerd type valt terug op about:blank. RFC 9457 noemt status een JSON-getal: 400.0 en 503.0 worden daarom numeriek vergeleken, niet als verkeerd type geweigerd.[1]
Gebruik Kopieer code om alle code exact te kopiëren. Handmatig selecteren kan de laatste regelovergang weglaten.
import json
def decide(http_status, media_type, body):
if type(http_status) is not int or http_status not in range(400, 600):
raise ValueError("expected an HTTP error status")
if media_type.split(";", 1)[0].strip().lower() != "application/problem+json":
return {"state": "generic_error", "http_status": http_status}
if type(body) is not dict:
raise ValueError("problem must be an object")
types = {"type": (str,), "status": (int, float), "title": (str,),
"detail": (str,), "instance": (str,)}
ignored = sorted(k for k, allowed in types.items()
if k in body and type(body[k]) not in allowed)
members = {k: v for k, v in body.items() if k not in ignored}
declared = members.get("status")
return {"state": "request_issue" if http_status in range(400, 500) else "service_issue",
"http_status": http_status, "type": members.get("type", "about:blank"),
"status_mismatch": declared is not None and declared != http_status,
"ignored_members": ignored}
# Invented already-decoded JSON objects; no HTTP requests.
example = decide(400, "application/problem+json", {"status": 503, "title": "Temporary"})
assert example["state"] == "request_issue" and example["status_mismatch"] is True
assert decide(503, "text/html", None)["state"] == "generic_error"
assert decide(503, "application/problem+json", {})["type"] == "about:blank"
for bad in [None, "400", True]:
result = decide(400, "application/problem+json", {"status": bad, "type": True})
assert result["ignored_members"] == ["status", "type"]
assert result["type"] == "about:blank" and result["status_mismatch"] is False
assert decide(400, "application/problem+json", {"status": 400.0})["status_mismatch"] is False
assert decide(400, "application/problem+json", {"status": 503.0})["status_mismatch"] is True
try:
decide(True, "application/problem+json", {})
except ValueError:
pass
else:
raise AssertionError("boolean transport status accepted")
print(json.dumps({"example": example, "ignored_member_cases": 3,
"numeric_status_cases": 2, "transport_rejections": 1}, sort_keys=True))
{"example": {"http_status": 400, "ignored_members": [], "state": "request_issue", "status_mismatch": true, "type": "about:blank"}, "ignored_member_cases": 3, "numeric_status_cases": 2, "transport_rejections": 1}
Dit is de letterlijk lokaal geproduceerde uitvoer. De toestand request_issue blijft bij HTTP 400; status_mismatch blijft zichtbaar. De HTML-tak leverde in de uitgevoerde assert een generic_error op. Geen van die uitkomsten verklaart waarom een echte dienst een fout gaf. Een statusklasse is geen diagnose en ook geen toestemming om opnieuw te proberen.
Wat nog buiten deze kaart valt
De code valideert geen volledige Problem Details-specificatie, URI-syntaxis, extensievelden of netwerkrespons. Alleen reeds gedecodeerde JSON met eindige int/float-getallen valt binnen dit Python-profiel; geldige numerieke HTTP-codes in het lichaam worden niet apart gevalideerd. De transportstatus moet lokaal een integer in 400–599 zijn: True wordt geweigerd. Dat is een foutweergaveprofiel, geen RFC-bereikbeperking. Een andere representatie geeft generic_error; een niet-object in de probleemtak geeft een invoerfout, geen geteste transportfallback. Titel, detail en instance met verkeerd type worden eveneens genegeerd; hun inhoud wordt niet getoond. De typewaarde wordt niet opgelost of opgehaald. Autorisatie, rate limiting, retry en exacte importreconciliatie blijven afzonderlijke workflows. Brononderzoek en fixtures zijn uitgevoerd; brongebruik en onafhankelijke feiten- en vertaalreview blijven open.
Bronnen & onderbouwing
- Oorspronkelijke brontitel: RFC 9457 — Problem Details for HTTP APIs
Geraadpleegd: 2026-10-18T22:58:29.733925+00:00