Bug: backend sends null for fields the contract types as optional (absent) #36
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
The backend sends
nullfor empty fields that@recipe/contracttypes as optional (field?: T), i.e. as absent, nevernull. Clients that trust the contract's types crash or misbehave.Measured on the dev backend across 50 recipes (
GET /recipe/:id, 375 ingredients):nullRecipeIngredientDto.subtext(subtext?: string)RecipeIngredientDto.unit(unit?: string)RecipeIngredientDto.amount(amount?: number)RecipeIngredientGroupDto.title(title?: string)createdByon recipes and ingredient groups (AbstractDto.createdBy?: string)None of these fields was ever omitted; the backend always sends the key, with
nullwhen empty. The cause is in the backend's DTO mappers, which copy the nullable database columns as they are (e.g.dto.amount = entity.amountinRecipeIngredientDtoEntityMapper.toDto).Impact
recipe-app maps the DTOs to models typed
number | undefined/string | undefined, so thenullleaks into the view layer. It went unnoticed while the display code happened to tolerate it ({ing.amount ?? ""}); a shared quantity formatter that checkedamount !== undefinedcrashed the recipe detail page for most recipes (found while verifying recipe-app#47). recipe-app#47 now normalisesnull→undefinedinIngredientMapperforamountandunit, but that is a client-side workaround for a contract mismatch, and the other fields are untouched.Decision needed
The contract has no rule on
nullvs absent, and it mixes both: some fields are explicitlyT | null(avatarUrl,defaultListId,HealthResponse.commit), the recipe fields are?: T.Options:
field?: T | null(orfield: T | nullwhere the backend always sends them), and add a convention todocs/api-conventions.md. The user prefers this: explicitnullover absent fields.null.Either way,
docs/api-conventions.mdshould state the rule, and both siblings adapt: the backend mappers or the contract types, and the app mappers (recipe-appIngredientMapperwould drop or keep its normalisation accordingly).🤖 Generated with Claude Code