Chyby GraphQL API: errors a errorDetails

Výsledek mutace nebo dotazu, který má pole errors, nabízí vedle něj volitelné pole errorDetails se strojově čitelným popisem týchž chyb: stabilní kód, kategorie, vstupní pole a parametry. Seznam kódů, jejich kategorie a pravidla stability jsou v katalogu kódů chyb.

Ukázky JSON níže jsou v dekódovaném tvaru. Server odpovídá odsazeným JSON a uvozovky a znaky mimo ASCII uvnitř řetězců posílá jako \uXXXX — na tom se nic nemění.

Pole errors se nemění

Tvar errorDetails

errorDetails { code category message text field params { name value } }

Položka i vysvětluje errors[i]: seznamy mají vždy stejný počet položek ve stejném pořadí a message je přesně řetězec errors[i]. errorDetails je [] právě tehdy, když errors je [].

Pole Význam
code Stabilní kód chyby, např. NOT_FOUND. UNCLASSIFIED = chyba zatím bez kódu, zpracujte ji podle textu jako dosud.
category Kategorie odvozená z kódu (validation, auth, retry, outcome_unknown, …) — obecné rozhodnutí, co s chybou dělat.
message Přesně text errors[i].
text Text chyby bez dosavadních ozdob (předpona pole: , příčina nebo cesta v závorce); u některých kódů srozumitelnější věta, např. u NOT_DELETABLE „Záznam nelze smazat, protože se stále používá.“.
field Cesta ke vstupní hodnotě tak, jak ji píše text chyby (ean, items[0].product); null u chyby, která se netýká jednoho pole.
params Podrobnosti jako dvojice name/value; hodnoty jsou vždy řetězce. [], když žádné nejsou.

Všechna pole jsou v GraphQL schématu nullable. Kterýkoli parametr může u konkrétní chyby chybět.

Příklad — úprava neexistující objednávky:

{
  "data": {
    "updateOrder": {
      "data": null,
      "errors": ["Error 404: Not found."],
      "errorDetails": [
        {
          "code": "NOT_FOUND",
          "category": "not_found",
          "message": "Error 404: Not found.",
          "text": "Error 404: Not found.",
          "field": null,
          "params": []
        }
      ]
    }
  }
}

Chyba, kterou server zatím neklasifikuje, přijde s kódem UNCLASSIFIED a kategorií unknown; message i text jsou dosavadní text:

"errors": ["Text chyby."],
"errorDetails": [
  {
    "code": "UNCLASSIFIED",
    "category": "unknown",
    "message": "Text chyby.",
    "text": "Text chyby.",
    "field": null,
    "params": []
  }
]

Až chyba dostane kód, změní se jen code, category, případně text, field a params — např. úprava zrušené objednávky:

"errors": ["Objednávka je zrušená, nelze ji upravovat."],
"errorDetails": [
  {
    "code": "ORDER_CANCELLED",
    "category": "business",
    "message": "Objednávka je zrušená, nelze ji upravovat.",
    "text": "Objednávka je zrušená, nelze ji upravovat.",
    "field": null,
    "params": [{ "name": "order", "value": "4711" }]
  }
]

Příklad — založení produktu s EAN, který už má jiný produkt, a s neexistujícím výrobcem. Chyby kontroly hodnot mají v field vstupní pole, v text hlášku bez předpony pole: a v params hodnotu, kterou text cituje:

"errors": [
  "ean: Produkt se stejným eanem: \"8590000000001\" už existuje.",
  "producer: Vybraná možnost není platná."
],
"errorDetails": [
  {
    "code": "VALUE_NOT_UNIQUE",
    "category": "validation",
    "message": "ean: Produkt se stejným eanem: \"8590000000001\" už existuje.",
    "text": "Produkt se stejným eanem: \"8590000000001\" už existuje.",
    "field": "ean",
    "params": [{ "name": "value", "value": "8590000000001" }]
  },
  {
    "code": "VALUE_INVALID",
    "category": "validation",
    "message": "producer: Vybraná možnost není platná.",
    "text": "Vybraná možnost není platná.",
    "field": "producer",
    "params": []
  }
]

Stejný text může mít různé kódy: Error 403: Forbidden. u mazání znamená FORBIDDEN (entita patří jiné firmě), nebo NOT_DELETABLE (záznam nelze smazat — na entitu odkazují jiné záznamy, nebo by smazání skladového pohybu odebralo víc kusů, než je na skladě; důvod uvádí text). Rozhodujte podle kódu.

Kódy chyb jednotlivých oblastí uvádí i jejich dokumentace, např. sady a volba konkrétních productDepots u položky objednávky.

Objednávky (createOrder, updateOrder, cancelReservation, deleteReservation) mají kódy u všech svých chyb, např. ORDER_CANCELLED, ORDER_BILLED_ITEM_NOT_REMOVABLE, ORDER_PROCESS_STATUS_CHANGE_FORBIDDEN, při vyskladnění STOCK_INSUFFICIENT (s product, productDepot, requested, available) a ORDER_DISPATCH_QUANTITY_MISMATCH. Vyskladnění, které narazí na rezervaci změněnou souběžným požadavkem, má OPERATION_IN_PROGRESS: nic se nezapsalo a požadavek lze poslat znovu. Výpis kontroly položky nebo formuláře v jedné chybě má kód podle chyby, je-li jediná (field např. items[0].type), jinak VALIDATION_FAILED bez field.

Produkty, sady, firmy a platby mají kódy u všech svých chyb: dotazy productPurchasePrice, productSellPrice a productSellItems hlásí neexistující odkaz jako REFERENCE_NOT_FOUND (field je argument, např. depots[0], id jeho hodnota) a odkaz mimo vaši firmu jako FORBIDDEN s týmž field; změnu isBundle, která by zanechala zboží nebo rezervace, odmítá BUNDLE_SWITCH_FORBIDDEN a sadu, která by obsahovala sama sebe, BUNDLE_CYCLE. Smazání firmy, které selže, má COMPANY_DELETE_FAILED s kódem příčiny v cause.

Kódy mají i chyby ostatních částí API: chybějící nebo cizí záznam (NOT_FOUND, FORBIDDEN — kategorie, štítky, kurzy měn, inventury, souhlasy zákazníků, profil uživatele; u createCustomerConsent uvádí field, který z odkazů consent/customer se nenašel), odeslání EET (EET_NOT_SUPPORTED), historie změn loggableHistory (MISSING_ARGUMENT / INVALID_ARGUMENT s názvem argumentu ve field; historie entity jiné firmy FORBIDDEN, chyba si přitom ponechává message „Access Denied.“ a číselný code 403), soubor inventury, který není platný Excel XLSX (VALUE_INVALID_FORMAT), příliš velký export (EXPORT_TOO_LARGE s count a limit) a typ exportu, který server neumí vytvořit (INTERNAL_ERROR).

Server doplňuje konkrétní kódy postupně; přechod chyby z UNCLASSIFIED na konkrétní kód ohlásí v novinkách.

Vybírejte vždy všechna podpole

Používejte v klientu jednu konstantu s celým výběrem a vkládejte ji všude, kde vybíráte errors:

errorDetails { code category message text field params { name value } }

Výběr errorDetails bez podpolí ({ errors errorDetails }) server neodmítne, ale vrátí za každou chybu prázdný seznam ("errorDetails": [[], []]), takže kódy nedostanete. Pozor na generátory dotazů, které pole vykreslují jako holý název (errors: null → errors): stejný vzor u errorDetails vede právě k tomuto výsledku. Server takový výběr zaznamená jako varování, odpověď nemění.

code je řetězec, ne výčet

code je ve schématu String. Negenerujte z kódů GraphQL enum ani uzavřený výčet, který by na neznámou hodnotu selhal: kódy průběžně přibývají. Kód, který klient nezná, zpracujte podle category.

encodeOutput

Pro OAuth klienta s nastaveným encodeOutput je errorDetails zakódované stejně jako errors téže odpovědi: message, text, field a hodnoty params jsou urlencoded právě tehdy, když je urlencoded errors[i]; code, category a názvy parametrů se kódováním nemění. Například "errors": ["Error+403%3A+Forbidden.+%28items%5B0%5D.vat%29"] má položku s "text": "Error+403%3A+Forbidden." a "field": "items%5B0%5D.vat". Dekódujte obě pole stejně.

Kdy smí klient errorDetails vybírat

Server, který errorDetails nezná (starší verze, jiný server, návrat k předchozí verzi), dokument s tímto polem nejdřív provede a teprve potom odmítne výběr: mutace proběhne a uloží se, klient ale dostane null a chybu (viz níže). Podporu proto zjišťujte dřív, než errorDetails pošlete — nikdy zkusmo.

Chyba Field "errorDetails" not found

Když přesto přijde chyba na nejvyšší úrovni s textem začínajícím Field "errorDetails" not found in type, mluvíte se serverem bez podpory a výsledek operace je neznámý:

mutation { createOrder(…) { data { id } errors errorDetails { code } } }
{
  "data": { "createOrder": null },
  "errors": [
    {
      "message": "Field \"errorDetails\" not found in type \"createOrder\". Available fields are: \"data\", \"errors\"",
      "locations": [{ "line": 1, "column": … }]
    }
  ]
}

…a objednávka byla vytvořena.

Kategorie outcome_unknown

Chyba s kategorií outcome_unknown (např. zablokování databáze nebo vypršení spojení s dopravcem) znamená, že zápis mohl proběhnout. Před opakováním načtěte stav (objednávku, zásilku, pohyb), jinak hrozí zdvojený doklad. Naproti tomu retry znamená, že se nic nezapsalo a požadavek lze poslat znovu beze změny.

Dávkové požadavky

Tělo požadavku s polem JSON (dávka několika dokumentů) dostane pole odpovědí ve stejném pořadí. Každá odpověď obsahuje v errors na nejvyšší úrovni jen chyby svého dokumentu; úspěšný dokument klíč errors nemá. Dříve odpověď na pozdější dokument opakovala i chyby dokumentů před ním. errors a errorDetails uvnitř výsledků se to netýká, ty byly vždy jen svého výsledku.

Chyby na nejvyšší úrovni (extensions)

Chyby v poli errors na nejvyšší úrovni odpovědi (např. Access Denied. nebo neplatný argument) mohou nést stejný kód jako klíč extensions (code, category a podle potřeby field, params, text) za dosavadními klíči message, code a locations, které se nemění. Server ho posílá jen na požádání: u požadavků s hlavičkou X-Depoto-Error-Codes: 1, nebo trvale pro integraci, které ho na žádost zapne podpora Depoto. Schopnost error-details se ke klíči extensions nevztahuje.