The shared optimistic-concurrency parser (ConcurrencyHeaders.ParseIfMatch) classified any non-canonical/weak/list If-Match value as Malformed → 400. Per RFC 7232 §3.1 a syntactically -valid entity-tag that simply doesn't strong-match must be 412; 400 is only for a genuine grammar violation. - Rewrite ParseIfMatch as a real RFC 7232 entity-tag/list parser: walks the comma-separated 1#entity-tag list, validates each [W/]DQUOTE *etagc DQUOTE member, and collects the strong members whose opaque text is our canonical decimal. Weak / empty / non-canonical / out-of-range tags are valid but contribute no version (→ empty set → 412); genuine grammar violations (unquoted, SP-in-tag, unterminated, garbage) → 400. - Reshape IfMatchCondition.ExpectedVersion : Option<int> → ExpectedVersions : Option<Seq<int>> and VersionedAggregateExtensions.CheckVersion → set membership (any strong match proceeds; empty set always 412). Threads through 10 replace/update commands + handlers + request mappers + 9 controllers. - No wire-contract change (400 + 412 already declared on every PUT; the field is header-derived and internal — no DTO/route/response-type/OpenAPI change). - Tests: ConcurrencyHeadersTests rewritten for the new classification (lists, weak, empty, non-canonical → Version/empty-set; grammar violations → Malformed) + new VersionedAggregateExtensionsTests for CheckVersion membership/empty-set/force-write. - Docs: api-conventions.md §7a rewritten; decisions.md entry appended. Refs #253 #197 fixes #265 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
35 lines
1.1 KiB
C#
35 lines
1.1 KiB
C#
using ErsatzTV.Application.MediaCollections;
|
|
using ErsatzTV.Application.MediaItems;
|
|
using ErsatzTV.Core.Domain;
|
|
|
|
namespace ErsatzTV.Controllers.Api.Requests;
|
|
|
|
public record UpdateRerunCollectionRequest(
|
|
string Name,
|
|
CollectionType CollectionType,
|
|
int SelectedId,
|
|
PlaybackOrder FirstRunPlaybackOrder,
|
|
PlaybackOrder RerunPlaybackOrder)
|
|
{
|
|
public UpdateRerunCollection ToCommand(int id, Option<Seq<int>> expectedVersions = default)
|
|
{
|
|
(MediaCollectionViewModel collection,
|
|
MultiCollectionViewModel multiCollection,
|
|
SmartCollectionViewModel smartCollection,
|
|
NamedMediaItemViewModel mediaItem) =
|
|
RerunCollectionRequestMapping.ResolveSelection(CollectionType, SelectedId);
|
|
|
|
return new UpdateRerunCollection(
|
|
id,
|
|
Name,
|
|
CollectionType,
|
|
collection,
|
|
multiCollection,
|
|
smartCollection,
|
|
mediaItem,
|
|
FirstRunPlaybackOrder,
|
|
RerunPlaybackOrder,
|
|
expectedVersions);
|
|
}
|
|
}
|