fullstackhero/dotnet-starter-kit · warning · CustomException

Unknown visibility value

Error message

Unknown visibility value '{cmd.Visibility}'.

What it means

ChangeFileVisibilityCommandHandler validates that cmd.Visibility is one of the known enum values (Public or Private) and throws CustomException with 400 BadRequest otherwise. Because the value is likely bound from a string in the request body, an unrecognized string arrives as an out-of-range/undefined enum value rather than failing model binding.

Solutions

  1. Send exactly "Public" or "Private" (match the enum's serialized casing).
  2. Constrain the UI to a dropdown/typed union of allowed values instead of free text.
  3. Check the API version — a value valid in an older version may have been removed.
  4. Add client-side zod validation restricting visibility to the allowed literals before sending.

Example fix

// before
await apiFetch(`/files/${id}/visibility`, { method: 'PATCH', body: JSON.stringify({ visibility: 'public' }) }); // 400
// after
const allowed = ['Public', 'Private'] as const;
if (!allowed.includes(next as any)) throw new Error('invalid visibility');
await apiFetch(`/files/${id}/visibility`, { method: 'PATCH', body: JSON.stringify({ visibility: next }) });
Defensive patterns

Strategy: validation

Validate before calling

const allowed = ['Public', 'Private'] as const;
type Visibility = (typeof allowed)[number];
const isVisibility = (v: string): v is Visibility => (allowed as readonly string[]).includes(v);

Type guard

const isVisibility = (v: unknown): v is 'Public' | 'Private' => v === 'Public' || v === 'Private';

Prevention

When it happens

Trigger: PATCHing visibility with a typo'd or unsupported string (e.g. "public" wrong case if JSON deserialization is case-sensitive, "internal", "shared", "") that does not map to Public or Private.

Common situations: Older client versions sending removed enum values after an API change; hand-written API clients guessing values; tests sending raw strings not in the enum; numeric enum values outside the defined range.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15). Data as JSON: /api/errors/7888d916f69b4e55. Report an issue: GitHub.

Appendix: source

Thrown at src/Modules/Files/Modules.Files/Features/v1/ChangeVisibility/ChangeFileVisibilityCommandHandler.cs:29

using Mediator;
using Microsoft.EntityFrameworkCore;

namespace FSH.Modules.Files.Features.v1.ChangeVisibility;

public sealed class ChangeFileVisibilityCommandHandler(
    FilesDbContext db,
    FileAccessPolicyRegistry policies,
    ICurrentUser currentUser,
    IStorageService storage)
    : ICommandHandler<ChangeFileVisibilityCommand, FileAssetDto>
{
    public async ValueTask<FileAssetDto> Handle(ChangeFileVisibilityCommand cmd, CancellationToken cancellationToken)
    {
        ArgumentNullException.ThrowIfNull(cmd);

        if (cmd.Visibility is not (Visibility.Public or Visibility.Private))
        {
            throw new CustomException(
                $"Unknown visibility value '{cmd.Visibility}'.",
                errors: null,
                System.Net.HttpStatusCode.BadRequest);
        }

        var f = await db.FileAssets
            .FirstOrDefaultAsync(x => x.Id == cmd.FileAssetId, cancellationToken)
            .ConfigureAwait(false)
            ?? throw new NotFoundException("file not found");

        var userId = currentUser.GetUserId().ToString();
        var policy = policies.Resolve(f.OwnerType)
            ?? throw new ForbiddenException("no policy");
        var ctx = new FileAccessContext(f.Id, f.OwnerType, f.OwnerId, f.CreatedByUserId, (int)f.Visibility);
        if (!await policy.CanChangeVisibilityAsync(ctx, userId, cancellationToken).ConfigureAwait(false))
        {
            throw new ForbiddenException("not allowed to change this file's visibility");
        }

View on GitHub (pinned to 3f2959e683)