Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion docs/walkthrough.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,9 @@ and a switch over them says what is wrong:

<!-- sample: samples/Bookings/BookingApi.cs -->
```csharp
static string? CheckStay(DateRange stay) => stay switch
static string? CheckStay(DateRange? stay) => stay switch
{
null => "Give the stay.",
DateRange.Finite { Length: > 30 } => "A stay is at most 30 nights.",
DateRange.Finite => null,
DateRange.EmptyRange => "The stay has no nights.",
Expand All @@ -211,6 +212,9 @@ JSON body, the converter throws. Either way the response is a 400 with an empty

Good to know:

- **A JSON `null` reaches the handler.** System.Text.Json doesn't check nullable annotations unless
you turn on `RespectNullableAnnotations`. So `"stay":null`, or a body with no `stay`, binds as
`null` although `Stay` isn't nullable, and the `null` arm turns it into a 400.
- **The discard arm is there for the compiler.** The five shapes are the only ones that can exist,
but C# can't prove it. [Pattern matching](ranges.md#pattern-matching) explains why the discard
should throw.
Expand Down
3 changes: 2 additions & 1 deletion samples/Bookings/BookingApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,9 @@ static async Task<IResult> GetAvailability(int id, DateRange window, BookingCont
return TypedResults.Ok(new Availability(id, window, booked, free));
}

static string? CheckStay(DateRange stay) => stay switch
static string? CheckStay(DateRange? stay) => stay switch
{
null => "Give the stay.",
DateRange.Finite { Length: > 30 } => "A stay is at most 30 nights.",
DateRange.Finite => null,
DateRange.EmptyRange => "The stay has no nights.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ await sample.Expect(HttpStatusCode.Created,
}

[TestMethod]
public async Task Validation_RejectsAnOpenStay_AStayOutOfSeason_AndALiteralThatDoesNotParse()
public async Task Validation_RejectsANullStay_AnOpenStay_AStayOutOfSeason_AndALiteralThatDoesNotParse()
{
await using var sample = await SampleHost.StartAsync();

Expand All @@ -91,6 +91,11 @@ await sample.Expect(HttpStatusCode.BadRequest, Problem + """{"stay":["A stay is
sample.Post("""{"roomId":1,"guest":"Linus","stay":"[2026-08-01,2026-09-01)"}"""));
await sample.Expect(HttpStatusCode.Created, null,
sample.Post("""{"roomId":1,"guest":"Linus","stay":"[2026-08-01,2026-08-31)"}"""));
// System.Text.Json binds a JSON null, or no stay at all, as null whatever the annotation (#465).
await sample.Expect(HttpStatusCode.BadRequest, Problem + """{"stay":["Give the stay."]}}""",
sample.Post("""{"roomId":1,"guest":"Linus","stay":null}"""));
await sample.Expect(HttpStatusCode.BadRequest, Problem + """{"stay":["Give the stay."]}}""",
sample.Post("""{"roomId":1,"guest":"Linus"}"""));
await sample.Expect(HttpStatusCode.BadRequest, Problem + """{"guest":["Name the guest."]}}""",
sample.Post("""{"roomId":1,"stay":"[2026-09-01,2026-09-03)"}"""));
await sample.Expect(HttpStatusCode.BadRequest, Problem + """{"stay":["Garden takes bookings for [2026-05-01,2026-09-30] only."]}}""",
Expand Down
Loading