{"record":{"id":"7888d916f69b4e55","repo":"fullstackhero/dotnet-starter-kit","slug":"unknown-visibility-value-cmd-visibility","errorCode":null,"errorMessage":"Unknown visibility value '{cmd.Visibility}'.","messagePattern":"Unknown visibility value '(.+?)'\\.","errorType":"exception","errorClass":"CustomException","httpStatus":400,"severity":"warning","filePath":"src/Modules/Files/Modules.Files/Features/v1/ChangeVisibility/ChangeFileVisibilityCommandHandler.cs","lineNumber":29,"sourceCode":"using Mediator;\nusing Microsoft.EntityFrameworkCore;\n\nnamespace FSH.Modules.Files.Features.v1.ChangeVisibility;\n\npublic sealed class ChangeFileVisibilityCommandHandler(\n    FilesDbContext db,\n    FileAccessPolicyRegistry policies,\n    ICurrentUser currentUser,\n    IStorageService storage)\n    : ICommandHandler<ChangeFileVisibilityCommand, FileAssetDto>\n{\n    public async ValueTask<FileAssetDto> Handle(ChangeFileVisibilityCommand cmd, CancellationToken cancellationToken)\n    {\n        ArgumentNullException.ThrowIfNull(cmd);\n\n        if (cmd.Visibility is not (Visibility.Public or Visibility.Private))\n        {\n            throw new CustomException(\n                $\"Unknown visibility value '{cmd.Visibility}'.\",\n                errors: null,\n                System.Net.HttpStatusCode.BadRequest);\n        }\n\n        var f = await db.FileAssets\n            .FirstOrDefaultAsync(x => x.Id == cmd.FileAssetId, cancellationToken)\n            .ConfigureAwait(false)\n            ?? throw new NotFoundException(\"file not found\");\n\n        var userId = currentUser.GetUserId().ToString();\n        var policy = policies.Resolve(f.OwnerType)\n            ?? throw new ForbiddenException(\"no policy\");\n        var ctx = new FileAccessContext(f.Id, f.OwnerType, f.OwnerId, f.CreatedByUserId, (int)f.Visibility);\n        if (!await policy.CanChangeVisibilityAsync(ctx, userId, cancellationToken).ConfigureAwait(false))\n        {\n            throw new ForbiddenException(\"not allowed to change this file's visibility\");\n        }","sourceCodeStart":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Files/Modules.Files/Features/v1/ChangeVisibility/ChangeFileVisibilityCommandHandler.cs#L11-L47","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Send exactly \"Public\" or \"Private\" (match the enum's serialized casing).","Constrain the UI to a dropdown/typed union of allowed values instead of free text.","Check the API version — a value valid in an older version may have been removed.","Add client-side zod validation restricting visibility to the allowed literals before sending."],"exampleFix":"// before\nawait apiFetch(`/files/${id}/visibility`, { method: 'PATCH', body: JSON.stringify({ visibility: 'public' }) }); // 400\n// after\nconst allowed = ['Public', 'Private'] as const;\nif (!allowed.includes(next as any)) throw new Error('invalid visibility');\nawait apiFetch(`/files/${id}/visibility`, { method: 'PATCH', body: JSON.stringify({ visibility: next }) });","handlingStrategy":"validation","validationCode":"const allowed = ['Public', 'Private'] as const;\ntype Visibility = (typeof allowed)[number];\nconst isVisibility = (v: string): v is Visibility => (allowed as readonly string[]).includes(v);","typeGuard":"const isVisibility = (v: unknown): v is 'Public' | 'Private' => v === 'Public' || v === 'Private';","tryCatchPattern":null,"preventionTips":["Use a typed union/dropdown instead of free-text input for visibility","Add zod schema with enum validation before sending","Keep client enum literals in sync with the backend contract"],"tags":["files","enum","bad-request","validation"],"backgroundTag":"invalid-enum-value","analyzedSha":"3f2959e683e9f83f13e55e1678c9119f63c7e8e5","analyzedAt":"2026-09-15T22:20:53.684Z","contentChangedAt":"2026-09-15T22:20:53.684Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}