fullstackhero/dotnet-starter-kit · error · CustomException
ex.Message
Error message
ex.Message
What it means
After loading the product, AdjustProductStock calls product.AdjustStock(delta); the domain invariant (e.g. stock cannot go below zero) raises InvalidOperationException, which the handler converts to CustomException with 409 Conflict carrying the domain message. The request was valid but the current stock state forbids the adjustment.
Solutions
- Fetch current stock and reduce the delta so the result stays within the allowed range.
- Re-read the product before retrying — stock may have changed concurrently.
- Correct the manual quantity entry (e.g. ship fewer units than available).
- Split the adjustment into valid increments if partial fulfilment is possible.
Example fix
// before await api.adjustStock(productId, -1000); // conflict: not enough stock // after const p = await api.getProduct(productId); await api.adjustStock(productId, Math.max(-p.stock, -1000));
Defensive patterns
Strategy: validation
Validate before calling
const p = await api.getProduct(productId);
if (delta < 0 && p.stock + delta < 0) {
return alert(`Cannot remove ${-delta}; only ${p.stock} in stock.`);
} Type guard
const isNonNegative = (n) => typeof n === 'number' && Number.isFinite(n) && n >= 0;
Try / catch
try { await api.adjustStock(productId, delta); }
catch (e) { if (e.status === 409) { await refreshStock(productId); alert(e.message); } else throw e; } Prevention
- Refetch current stock before each adjustment
- Clamp negative deltas to available stock client-side
- Expect 409 under concurrency and retry with fresh data
- Validate quantity inputs against a sane range
When it happens
Trigger: Sending a negative delta larger than current stock (would underflow below the domain minimum), or any adjustment the Product entity's stock rules reject.
Common situations: Warehouse app submitting returns/shipments computed from a stale stock figure; concurrent adjustments where another operation already consumed the stock; entering a wrong (oversized) quantity manually.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Product not found.
- Top-up request cannot be rejected because it is (only…
- Cannot credit a top-up to a wallet.
- A brand with name ' ' already exists.
- Another brand with name
AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15).
Data as JSON: /api/errors/2672f5ccbbdbf021.
Report an issue: GitHub.
Appendix: source
Thrown at src/Modules/Catalog/Modules.Catalog/Features/v1/Products/AdjustProductStock/AdjustProductStockCommandHandler.cs:28
public sealed class AdjustProductStockCommandHandler(CatalogDbContext dbContext)
: ICommandHandler<AdjustProductStockCommand, int>
{
public async ValueTask<int> Handle(AdjustProductStockCommand command, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(command);
var product = await dbContext.Products
.FirstOrDefaultAsync(p => p.Id == command.ProductId, cancellationToken)
.ConfigureAwait(false)
?? throw new NotFoundException($"Product {command.ProductId} not found.");
try
{
product.AdjustStock(command.Delta);
}
catch (InvalidOperationException ex)
{
throw new CustomException(ex.Message, (IEnumerable<string>?)null, HttpStatusCode.Conflict);
}
await dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
return product.Stock;
}
}
View on GitHub (pinned to 3f2959e683)