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

  1. Fetch current stock and reduce the delta so the result stays within the allowed range.
  2. Re-read the product before retrying — stock may have changed concurrently.
  3. Correct the manual quantity entry (e.g. ship fewer units than available).
  4. 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

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


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)