diff --git a/FirstClassErrors.GenDoc.UnitTests/FirstClassErrors.GenDoc.UnitTests.csproj b/FirstClassErrors.GenDoc.UnitTests/FirstClassErrors.GenDoc.UnitTests.csproj index 1cba3088..96d82e43 100644 --- a/FirstClassErrors.GenDoc.UnitTests/FirstClassErrors.GenDoc.UnitTests.csproj +++ b/FirstClassErrors.GenDoc.UnitTests/FirstClassErrors.GenDoc.UnitTests.csproj @@ -27,6 +27,7 @@ + diff --git a/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheExtractedCatalog.verified.txt b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheExtractedCatalog.verified.txt new file mode 100644 index 00000000..91f8a578 --- /dev/null +++ b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheExtractedCatalog.verified.txt @@ -0,0 +1,225 @@ +{ + Documentation: [ + { + Code: BOOKING_COMMAND_INVALID, + Title: Booking request invalid, + Explanation: The endpoint could not bind the incoming request into a booking command: one or more arguments were missing or invalid. Every failure is collected under this envelope, each with its full argument path., + BusinessRule: Every required argument must be present, and every argument must convert into its value object., + Diagnostics: [ + { + PossibleCause: The client sent a request that violates the endpoint's contract (missing or malformed arguments)., + Origin: External, + AnalysisHint: Read the inner errors: each names the failing argument and the rule it violated. + } + ], + Examples: [ + { + ShortMessage: We could not accept your booking request., + DiagnosticMessage: The booking command is invalid: one or more request arguments failed to bind., + DetailedMessage: One or more details of the booking request are missing or invalid. + } + ], + Source: BookingEndpoint, + SourceDescription: Primary-port errors raised by the booking endpoint when it binds an incoming request into a command. + }, + { + Code: BOOKING_DATE_MALFORMED, + Title: Malformed booking date, + Explanation: An incoming request carries a value that is not an ISO yyyy-MM-dd date, so it cannot be parsed into a BookingDate value object., + BusinessRule: A booking date must be an ISO calendar date in the yyyy-MM-dd format., + Diagnostics: [ + { + PossibleCause: The client sent a date in a locale-specific or malformed format, or an impossible calendar date., + Origin: External, + AnalysisHint: Send dates in ISO 8601 yyyy-MM-dd form, for example 2026-08-10. + } + ], + Examples: [ + { + ShortMessage: The date is invalid., + DiagnosticMessage: '2026-13-40' is not a valid ISO (yyyy-MM-dd) date., + DetailedMessage: A booking date is not a valid ISO (yyyy-MM-dd) date. + } + ], + Source: BookingDate, + SourceDescription: Errors raised when parsing a booking date (check-in / check-out) from a request string. + }, + { + Code: BOOKING_GUEST_INVALID, + Title: Booking guest invalid, + Explanation: A guest in the request's guests list could not be bound: its first name was missing or its e-mail was malformed. Its failures are grouped under this per-element envelope, with indexed paths such as Guests[1]., + BusinessRule: Each guest must have a first name, and any e-mail present must be valid., + Diagnostics: [ + { + PossibleCause: The client sent a guest with a missing first name or a malformed e-mail address., + Origin: External, + AnalysisHint: Read the inner errors under the indexed Guests[i] path for the failing field. + } + ], + Examples: [ + { + ShortMessage: A guest's information is invalid., + DiagnosticMessage: The guest is invalid: one or more of its fields failed to bind., + DetailedMessage: One of the guests is missing a first name or has an invalid e-mail address. + } + ], + Source: BookingEndpoint, + SourceDescription: Primary-port errors raised by the booking endpoint when it binds an incoming request into a command. + }, + { + Code: BOOKING_STAY_INVALID, + Title: Booking stay invalid, + Explanation: The stay sub-object of the request could not be bound: one or both of its dates were missing or malformed. Its failures are grouped under this nested envelope, with paths prefixed by Stay., + BusinessRule: Both stay dates must be present and valid ISO dates., + Diagnostics: [ + { + PossibleCause: The client sent a stay with a missing or malformed check-in or check-out date., + Origin: External, + AnalysisHint: Read the inner errors under the Stay path for the failing date. + } + ], + Examples: [ + { + ShortMessage: We could not read the stay dates., + DiagnosticMessage: The stay is invalid: one or more of its dates failed to bind., + DetailedMessage: The check-in or check-out date of the stay is missing or invalid. + } + ], + Source: BookingEndpoint, + SourceDescription: Primary-port errors raised by the booking endpoint when it binds an incoming request into a command. + }, + { + Code: BOOKING_TAG_MALFORMED, + Title: Malformed booking tag, + Explanation: An element of the request's tag list is empty, too long, or contains whitespace, so it cannot be parsed into a Tag value object., + BusinessRule: A tag must be a single non-empty token of at most 32 characters, without whitespace., + Diagnostics: [ + { + PossibleCause: The client sent a blank tag, a phrase containing spaces, or an over-long value., + Origin: External, + AnalysisHint: Send each tag as a single whitespace-free token; join multi-word tags with a hyphen. + } + ], + Examples: [ + { + ShortMessage: A tag is invalid., + DiagnosticMessage: 'late checkout' is not a valid tag., + DetailedMessage: One of the booking tags is not a valid single token. + } + ], + Source: Tag, + SourceDescription: Errors raised when parsing a booking tag from an element of a request list. + }, + { + Code: CURRENCY_CODE_MALFORMED, + Title: Malformed currency code, + Explanation: An incoming request carries a value that is not a well-formed three-letter currency code, so it cannot be parsed into a Currency value object., + BusinessRule: A currency code must be exactly three upper-case ASCII letters (for example EUR)., + Diagnostics: [ + { + PossibleCause: The client sent a currency in the wrong shape (lower-case, a symbol, a name, or the wrong length)., + Origin: External, + AnalysisHint: Send the ISO-4217 alphabetic code in upper case, for example USD or EUR. + } + ], + Examples: [ + { + ShortMessage: The currency is invalid., + DiagnosticMessage: 'EURO' is not a valid three-letter currency code., + DetailedMessage: The billing currency code is not a valid three-letter code. + } + ], + Source: Currency, + SourceDescription: Errors raised when parsing a billing currency code from a request string. + }, + { + Code: EMAIL_ADDRESS_MALFORMED, + Title: Malformed e-mail address, + Explanation: An incoming request carries a value that is not a well-formed e-mail address, so it cannot be parsed into an EmailAddress value object., + BusinessRule: A guest e-mail address must contain a single '@' with a non-empty local part and domain., + Diagnostics: [ + { + PossibleCause: The client sent a misspelled or truncated address (missing '@', empty local part or domain)., + Origin: External, + AnalysisHint: Validate the address on the client, and compare the sent value against the expected format. + } + ], + Examples: [ + { + ShortMessage: The e-mail address is invalid., + DiagnosticMessage: 'not-an-email' is not a valid e-mail address., + DetailedMessage: The value provided is not a valid e-mail address. + } + ], + Source: EmailAddress, + SourceDescription: Errors raised when parsing a guest e-mail address from a request string. + }, + { + Code: NIGHT_COUNT_NOT_POSITIVE, + Title: Non-positive number of nights, + Explanation: An incoming request asks for zero or a negative number of nights, which is not a valid stay length., + BusinessRule: A booking must be for at least one night., + Diagnostics: [ + { + PossibleCause: The client sent a number of nights of zero or below., + Origin: External, + AnalysisHint: Send a number of nights of one or more. + } + ], + Examples: [ + { + ShortMessage: The number of nights is invalid., + DiagnosticMessage: A booking must be for at least one night, but 0 was requested., + DetailedMessage: The requested number of nights must be one or more. + } + ], + Source: NightCount, + SourceDescription: Errors raised when building the number of nights from a request value. + }, + { + Code: ROOM_NUMBER_OUT_OF_RANGE, + Title: Room number out of range, + Explanation: An element of the request's room-number list is outside the supported 1-999 range., + BusinessRule: A room number must be between 1 and 999 inclusive., + Diagnostics: [ + { + PossibleCause: The client sent a room number of zero, a negative value, or a value above 999., + Origin: External, + AnalysisHint: Send room numbers within the supported 1-999 range. + } + ], + Examples: [ + { + ShortMessage: A room number is invalid., + DiagnosticMessage: Room number 1000 is outside the supported range 1-999., + DetailedMessage: One of the requested room numbers is outside the supported range. + } + ], + Source: RoomNumber, + SourceDescription: Errors raised when building a room number from an element of a request list. + }, + { + Code: STAY_CHECKOUT_NOT_AFTER_CHECKIN, + Title: Check-out not after check-in, + Explanation: Both stay dates parse, but the check-out date is on or before the check-in date, so the stay has no positive length. This cross-field rule is enforced by the Stay.Create factory., + BusinessRule: Check-out must be strictly after check-in., + Diagnostics: [ + { + PossibleCause: The client sent a check-out date equal to or earlier than the check-in date., + Origin: External, + AnalysisHint: Send a check-out date at least one day after the check-in date. + } + ], + Examples: [ + { + ShortMessage: The stay dates are invalid., + DiagnosticMessage: Check-out 2026-08-10 must be strictly after check-in 2026-08-14., + DetailedMessage: The check-out date must be after the check-in date. + } + ], + Source: Stay, + SourceDescription: Errors raised when validating a stay's check-in and check-out dates together. + } + ], + HasFailures: false +} \ No newline at end of file diff --git a/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheJsonRendering.verified.json b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheJsonRendering.verified.json new file mode 100644 index 00000000..848c5f02 --- /dev/null +++ b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheJsonRendering.verified.json @@ -0,0 +1,234 @@ +{ + "errors": [ + { + "code": "BOOKING_COMMAND_INVALID", + "title": "Booking request invalid", + "explanation": "The endpoint could not bind the incoming request into a booking command: one or more arguments were missing or invalid. Every failure is collected under this envelope, each with its full argument path.", + "businessRule": "Every required argument must be present, and every argument must convert into its value object.", + "source": "BookingEndpoint", + "sourceDescription": "Primary-port errors raised by the booking endpoint when it binds an incoming request into a command.", + "diagnostics": [ + { + "possibleCause": "The client sent a request that violates the endpoint\u0027s contract (missing or malformed arguments).", + "origin": "External", + "analysisHint": "Read the inner errors: each names the failing argument and the rule it violated." + } + ], + "examples": [ + { + "shortMessage": "We could not accept your booking request.", + "detailedMessage": "One or more details of the booking request are missing or invalid.", + "diagnosticMessage": "The booking command is invalid: one or more request arguments failed to bind." + } + ], + "context": [] + }, + { + "code": "BOOKING_DATE_MALFORMED", + "title": "Malformed booking date", + "explanation": "An incoming request carries a value that is not an ISO yyyy-MM-dd date, so it cannot be parsed into a BookingDate value object.", + "businessRule": "A booking date must be an ISO calendar date in the yyyy-MM-dd format.", + "source": "BookingDate", + "sourceDescription": "Errors raised when parsing a booking date (check-in / check-out) from a request string.", + "diagnostics": [ + { + "possibleCause": "The client sent a date in a locale-specific or malformed format, or an impossible calendar date.", + "origin": "External", + "analysisHint": "Send dates in ISO 8601 yyyy-MM-dd form, for example 2026-08-10." + } + ], + "examples": [ + { + "shortMessage": "The date is invalid.", + "detailedMessage": "A booking date is not a valid ISO (yyyy-MM-dd) date.", + "diagnosticMessage": "\u00272026-13-40\u0027 is not a valid ISO (yyyy-MM-dd) date." + } + ], + "context": [] + }, + { + "code": "BOOKING_GUEST_INVALID", + "title": "Booking guest invalid", + "explanation": "A guest in the request\u0027s guests list could not be bound: its first name was missing or its e-mail was malformed. Its failures are grouped under this per-element envelope, with indexed paths such as Guests[1].", + "businessRule": "Each guest must have a first name, and any e-mail present must be valid.", + "source": "BookingEndpoint", + "sourceDescription": "Primary-port errors raised by the booking endpoint when it binds an incoming request into a command.", + "diagnostics": [ + { + "possibleCause": "The client sent a guest with a missing first name or a malformed e-mail address.", + "origin": "External", + "analysisHint": "Read the inner errors under the indexed Guests[i] path for the failing field." + } + ], + "examples": [ + { + "shortMessage": "A guest\u0027s information is invalid.", + "detailedMessage": "One of the guests is missing a first name or has an invalid e-mail address.", + "diagnosticMessage": "The guest is invalid: one or more of its fields failed to bind." + } + ], + "context": [] + }, + { + "code": "BOOKING_STAY_INVALID", + "title": "Booking stay invalid", + "explanation": "The stay sub-object of the request could not be bound: one or both of its dates were missing or malformed. Its failures are grouped under this nested envelope, with paths prefixed by Stay.", + "businessRule": "Both stay dates must be present and valid ISO dates.", + "source": "BookingEndpoint", + "sourceDescription": "Primary-port errors raised by the booking endpoint when it binds an incoming request into a command.", + "diagnostics": [ + { + "possibleCause": "The client sent a stay with a missing or malformed check-in or check-out date.", + "origin": "External", + "analysisHint": "Read the inner errors under the Stay path for the failing date." + } + ], + "examples": [ + { + "shortMessage": "We could not read the stay dates.", + "detailedMessage": "The check-in or check-out date of the stay is missing or invalid.", + "diagnosticMessage": "The stay is invalid: one or more of its dates failed to bind." + } + ], + "context": [] + }, + { + "code": "BOOKING_TAG_MALFORMED", + "title": "Malformed booking tag", + "explanation": "An element of the request\u0027s tag list is empty, too long, or contains whitespace, so it cannot be parsed into a Tag value object.", + "businessRule": "A tag must be a single non-empty token of at most 32 characters, without whitespace.", + "source": "Tag", + "sourceDescription": "Errors raised when parsing a booking tag from an element of a request list.", + "diagnostics": [ + { + "possibleCause": "The client sent a blank tag, a phrase containing spaces, or an over-long value.", + "origin": "External", + "analysisHint": "Send each tag as a single whitespace-free token; join multi-word tags with a hyphen." + } + ], + "examples": [ + { + "shortMessage": "A tag is invalid.", + "detailedMessage": "One of the booking tags is not a valid single token.", + "diagnosticMessage": "\u0027late checkout\u0027 is not a valid tag." + } + ], + "context": [] + }, + { + "code": "CURRENCY_CODE_MALFORMED", + "title": "Malformed currency code", + "explanation": "An incoming request carries a value that is not a well-formed three-letter currency code, so it cannot be parsed into a Currency value object.", + "businessRule": "A currency code must be exactly three upper-case ASCII letters (for example EUR).", + "source": "Currency", + "sourceDescription": "Errors raised when parsing a billing currency code from a request string.", + "diagnostics": [ + { + "possibleCause": "The client sent a currency in the wrong shape (lower-case, a symbol, a name, or the wrong length).", + "origin": "External", + "analysisHint": "Send the ISO-4217 alphabetic code in upper case, for example USD or EUR." + } + ], + "examples": [ + { + "shortMessage": "The currency is invalid.", + "detailedMessage": "The billing currency code is not a valid three-letter code.", + "diagnosticMessage": "\u0027EURO\u0027 is not a valid three-letter currency code." + } + ], + "context": [] + }, + { + "code": "EMAIL_ADDRESS_MALFORMED", + "title": "Malformed e-mail address", + "explanation": "An incoming request carries a value that is not a well-formed e-mail address, so it cannot be parsed into an EmailAddress value object.", + "businessRule": "A guest e-mail address must contain a single \u0027@\u0027 with a non-empty local part and domain.", + "source": "EmailAddress", + "sourceDescription": "Errors raised when parsing a guest e-mail address from a request string.", + "diagnostics": [ + { + "possibleCause": "The client sent a misspelled or truncated address (missing \u0027@\u0027, empty local part or domain).", + "origin": "External", + "analysisHint": "Validate the address on the client, and compare the sent value against the expected format." + } + ], + "examples": [ + { + "shortMessage": "The e-mail address is invalid.", + "detailedMessage": "The value provided is not a valid e-mail address.", + "diagnosticMessage": "\u0027not-an-email\u0027 is not a valid e-mail address." + } + ], + "context": [] + }, + { + "code": "NIGHT_COUNT_NOT_POSITIVE", + "title": "Non-positive number of nights", + "explanation": "An incoming request asks for zero or a negative number of nights, which is not a valid stay length.", + "businessRule": "A booking must be for at least one night.", + "source": "NightCount", + "sourceDescription": "Errors raised when building the number of nights from a request value.", + "diagnostics": [ + { + "possibleCause": "The client sent a number of nights of zero or below.", + "origin": "External", + "analysisHint": "Send a number of nights of one or more." + } + ], + "examples": [ + { + "shortMessage": "The number of nights is invalid.", + "detailedMessage": "The requested number of nights must be one or more.", + "diagnosticMessage": "A booking must be for at least one night, but 0 was requested." + } + ], + "context": [] + }, + { + "code": "ROOM_NUMBER_OUT_OF_RANGE", + "title": "Room number out of range", + "explanation": "An element of the request\u0027s room-number list is outside the supported 1-999 range.", + "businessRule": "A room number must be between 1 and 999 inclusive.", + "source": "RoomNumber", + "sourceDescription": "Errors raised when building a room number from an element of a request list.", + "diagnostics": [ + { + "possibleCause": "The client sent a room number of zero, a negative value, or a value above 999.", + "origin": "External", + "analysisHint": "Send room numbers within the supported 1-999 range." + } + ], + "examples": [ + { + "shortMessage": "A room number is invalid.", + "detailedMessage": "One of the requested room numbers is outside the supported range.", + "diagnosticMessage": "Room number 1000 is outside the supported range 1-999." + } + ], + "context": [] + }, + { + "code": "STAY_CHECKOUT_NOT_AFTER_CHECKIN", + "title": "Check-out not after check-in", + "explanation": "Both stay dates parse, but the check-out date is on or before the check-in date, so the stay has no positive length. This cross-field rule is enforced by the Stay.Create factory.", + "businessRule": "Check-out must be strictly after check-in.", + "source": "Stay", + "sourceDescription": "Errors raised when validating a stay\u0027s check-in and check-out dates together.", + "diagnostics": [ + { + "possibleCause": "The client sent a check-out date equal to or earlier than the check-in date.", + "origin": "External", + "analysisHint": "Send a check-out date at least one day after the check-in date." + } + ], + "examples": [ + { + "shortMessage": "The stay dates are invalid.", + "detailedMessage": "The check-out date must be after the check-in date.", + "diagnosticMessage": "Check-out 2026-08-10 must be strictly after check-in 2026-08-14." + } + ], + "context": [] + } + ] +} \ No newline at end of file diff --git a/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheLocalizedHtmlRendering_culture=de.verified.html b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheLocalizedHtmlRendering_culture=de.verified.html new file mode 100644 index 00000000..b30c3441 --- /dev/null +++ b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheLocalizedHtmlRendering_culture=de.verified.html @@ -0,0 +1,525 @@ + + + + + +Fehlerkatalog + + + + + + +
+

10 dokumentierte Fehler

+
+ +
+ + +
+

BookingEndpoint

+

Primär-Port-Fehler, die vom Buchungsendpunkt ausgelöst werden, wenn er eine eingehende Anforderung an einen Befehl bindet.

+
+

BOOKING_COMMAND_INVALID #

+

Buchungsanforderung ungültig

+

Dokumentation

Der Endpunkt konnte die eingehende Anforderung nicht an einen Buchungsbefehl binden: Ein oder mehrere Argumente fehlten oder waren ungültig. Jeder Fehler wird unter diesem Umschlag gesammelt, jeweils mit seinem vollständigen Argumentpfad.

+
Geschäftsregel: Jedes erforderliche Argument muss vorhanden sein, und jedes Argument muss in sein Value-Object konvertiert werden.
+

Diagnosen

+
+ + + + + + +
Mögliche UrsacheUrsprungAnalysehinweis
Der Client hat eine Anforderung gesendet, die den Vertrag des Endpunkts verletzt (fehlende oder fehlerhafte Argumente).ExternalLesen Sie die inneren Fehler: Jeder benennt das fehlerhafte Argument und die verletzte Regel.
+
+

Beispiele

+
+

Öffentliche Antwort (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:booking-command-invalid",
+  "title": "Wir konnten Ihre Buchungsanforderung nicht annehmen.",
+  "detail": "Eine oder mehrere Angaben der Buchungsanforderung fehlen oder sind ungültig.",
+  "code": "BOOKING_COMMAND_INVALID"
+}
+

Diagnose (intern — nicht für externe Weitergabe)

+
2026-07-04T13:42:18.734Z ERROR [BookingEndpoint] The booking command is invalid: one or more request arguments failed to bind. error.code=BOOKING_COMMAND_INVALID
+
+
+
+
+

BOOKING_GUEST_INVALID #

+

Gast der Buchung ungültig

+

Dokumentation

Ein Gast der Gästeliste der Anforderung konnte nicht gebunden werden: Sein Vorname fehlte oder seine E-Mail war fehlerhaft. Seine Fehler werden unter diesem Umschlag pro Element gruppiert, mit indizierten Pfaden wie Guests[1].

+
Geschäftsregel: Jeder Gast muss einen Vornamen haben, und jede vorhandene E-Mail muss gültig sein.
+

Diagnosen

+
+ + + + + + +
Mögliche UrsacheUrsprungAnalysehinweis
Der Client hat einen Gast mit einem fehlenden Vornamen oder einer fehlerhaften E-Mail-Adresse gesendet.ExternalLesen Sie die inneren Fehler unter dem indizierten Pfad Guests[i] für das fehlerhafte Feld.
+
+

Beispiele

+
+

Öffentliche Antwort (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:booking-guest-invalid",
+  "title": "Die Angaben eines Gastes sind ungültig.",
+  "detail": "Einer der Gäste hat keinen Vornamen oder eine ungültige E-Mail-Adresse.",
+  "code": "BOOKING_GUEST_INVALID"
+}
+

Diagnose (intern — nicht für externe Weitergabe)

+
2026-07-04T13:42:18.734Z ERROR [BookingEndpoint] The guest is invalid: one or more of its fields failed to bind. error.code=BOOKING_GUEST_INVALID
+
+
+
+
+

BOOKING_STAY_INVALID #

+

Aufenthalt der Buchung ungültig

+

Dokumentation

Das Aufenthalts-Unterobjekt der Anforderung konnte nicht gebunden werden: Eines oder beide seiner Daten fehlten oder waren fehlerhaft. Seine Fehler werden unter diesem verschachtelten Umschlag gruppiert, mit durch Stay präfixierten Pfaden.

+
Geschäftsregel: Beide Aufenthaltsdaten müssen vorhanden und gültige ISO-Daten sein.
+

Diagnosen

+
+ + + + + + +
Mögliche UrsacheUrsprungAnalysehinweis
Der Client hat einen Aufenthalt mit einem fehlenden oder fehlerhaften An- oder Abreisedatum gesendet.ExternalLesen Sie die inneren Fehler unter dem Stay-Pfad für das fehlerhafte Datum.
+
+

Beispiele

+
+

Öffentliche Antwort (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:booking-stay-invalid",
+  "title": "Wir konnten die Aufenthaltsdaten nicht lesen.",
+  "detail": "Das An- oder Abreisedatum des Aufenthalts fehlt oder ist ungültig.",
+  "code": "BOOKING_STAY_INVALID"
+}
+

Diagnose (intern — nicht für externe Weitergabe)

+
2026-07-04T13:42:18.734Z ERROR [BookingEndpoint] The stay is invalid: one or more of its dates failed to bind. error.code=BOOKING_STAY_INVALID
+
+
+
+
+
+

BookingDate

+

Fehler, die beim Parsen eines Buchungsdatums (Anreise / Abreise) aus einer Anforderungszeichenfolge ausgelöst werden.

+
+

BOOKING_DATE_MALFORMED #

+

Fehlerhaftes Buchungsdatum

+

Dokumentation

Eine eingehende Anforderung enthält einen Wert, der kein ISO-Datum (yyyy-MM-dd) ist und daher nicht in ein BookingDate-Value-Object geparst werden kann.

+
Geschäftsregel: Ein Buchungsdatum muss ein ISO-Kalenderdatum im Format yyyy-MM-dd sein.
+

Diagnosen

+
+ + + + + + +
Mögliche UrsacheUrsprungAnalysehinweis
Der Client hat ein Datum in einem länderspezifischen oder fehlerhaften Format oder ein unmögliches Kalenderdatum gesendet.ExternalSenden Sie Datumsangaben im Format ISO 8601 yyyy-MM-dd, zum Beispiel 2026-08-10.
+
+

Beispiele

+
+

Öffentliche Antwort (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:booking-date-malformed",
+  "title": "Das Datum ist ungültig.",
+  "detail": "Ein Buchungsdatum ist kein gültiges ISO-Datum (yyyy-MM-dd).",
+  "code": "BOOKING_DATE_MALFORMED"
+}
+

Diagnose (intern — nicht für externe Weitergabe)

+
2026-07-04T13:42:18.734Z ERROR [BookingDate] '2026-13-40' is not a valid ISO (yyyy-MM-dd) date. error.code=BOOKING_DATE_MALFORMED
+
+
+
+
+
+

Tag

+

Fehler, die beim Parsen eines Buchungs-Tags aus einem Element einer Anforderungsliste ausgelöst werden.

+
+

BOOKING_TAG_MALFORMED #

+

Fehlerhaftes Buchungs-Tag

+

Dokumentation

Ein Element der Tag-Liste der Anforderung ist leer, zu lang oder enthält Leerzeichen und kann daher nicht in ein Tag-Value-Object geparst werden.

+
Geschäftsregel: Ein Tag muss ein einzelnes, nicht leeres Token von höchstens 32 Zeichen ohne Leerzeichen sein.
+

Diagnosen

+
+ + + + + + +
Mögliche UrsacheUrsprungAnalysehinweis
Der Client hat ein leeres Tag, einen Ausdruck mit Leerzeichen oder einen zu langen Wert gesendet.ExternalSenden Sie jedes Tag als einzelnes Token ohne Leerzeichen; verbinden Sie mehrwortige Tags mit einem Bindestrich.
+
+

Beispiele

+
+

Öffentliche Antwort (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:booking-tag-malformed",
+  "title": "Ein Tag ist ungültig.",
+  "detail": "Eines der Buchungs-Tags ist kein gültiges einzelnes Token.",
+  "code": "BOOKING_TAG_MALFORMED"
+}
+

Diagnose (intern — nicht für externe Weitergabe)

+
2026-07-04T13:42:18.734Z ERROR [Tag] 'late checkout' is not a valid tag. error.code=BOOKING_TAG_MALFORMED
+
+
+
+
+
+

Currency

+

Fehler, die beim Parsen eines Abrechnungswährungscodes aus einer Anforderungszeichenfolge ausgelöst werden.

+
+

CURRENCY_CODE_MALFORMED #

+

Fehlerhafter Währungscode

+

Dokumentation

Eine eingehende Anforderung enthält einen Wert, der kein wohlgeformter dreistelliger Währungscode ist und daher nicht in ein Currency-Value-Object geparst werden kann.

+
Geschäftsregel: Ein Währungscode muss aus genau drei ASCII-Großbuchstaben bestehen (zum Beispiel EUR).
+

Diagnosen

+
+ + + + + + +
Mögliche UrsacheUrsprungAnalysehinweis
Der Client hat eine Währung in der falschen Form gesendet (Kleinbuchstaben, ein Symbol, ein Name oder die falsche Länge).ExternalSenden Sie den alphabetischen ISO-4217-Code in Großbuchstaben, zum Beispiel USD oder EUR.
+
+

Beispiele

+
+

Öffentliche Antwort (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:currency-code-malformed",
+  "title": "Die Währung ist ungültig.",
+  "detail": "Der Abrechnungswährungscode ist kein gültiger dreistelliger Code.",
+  "code": "CURRENCY_CODE_MALFORMED"
+}
+

Diagnose (intern — nicht für externe Weitergabe)

+
2026-07-04T13:42:18.734Z ERROR [Currency] 'EURO' is not a valid three-letter currency code. error.code=CURRENCY_CODE_MALFORMED
+
+
+
+
+
+

EmailAddress

+

Fehler, die beim Parsen der E-Mail-Adresse eines Gastes aus einer Anforderungszeichenfolge ausgelöst werden.

+
+

EMAIL_ADDRESS_MALFORMED #

+

Fehlerhafte E-Mail-Adresse

+

Dokumentation

Eine eingehende Anforderung enthält einen Wert, der keine wohlgeformte E-Mail-Adresse ist und daher nicht in ein EmailAddress-Value-Object geparst werden kann.

+
Geschäftsregel: Eine E-Mail-Adresse eines Gastes muss genau ein „@“ mit einem nicht leeren lokalen Teil und einer nicht leeren Domäne enthalten.
+

Diagnosen

+
+ + + + + + +
Mögliche UrsacheUrsprungAnalysehinweis
Der Client hat eine falsch geschriebene oder abgeschnittene Adresse gesendet (fehlendes „@“, leerer lokaler Teil oder leere Domäne).ExternalValidieren Sie die Adresse auf dem Client und vergleichen Sie den gesendeten Wert mit dem erwarteten Format.
+
+

Beispiele

+
+

Öffentliche Antwort (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:email-address-malformed",
+  "title": "Die E-Mail-Adresse ist ungültig.",
+  "detail": "Der angegebene Wert ist keine gültige E-Mail-Adresse.",
+  "code": "EMAIL_ADDRESS_MALFORMED"
+}
+

Diagnose (intern — nicht für externe Weitergabe)

+
2026-07-04T13:42:18.734Z ERROR [EmailAddress] 'not-an-email' is not a valid e-mail address. error.code=EMAIL_ADDRESS_MALFORMED
+
+
+
+
+
+

NightCount

+

Fehler, die beim Erstellen der Anzahl der Nächte aus einem Anforderungswert ausgelöst werden.

+
+

NIGHT_COUNT_NOT_POSITIVE #

+

Nicht positive Anzahl von Nächten

+

Dokumentation

Eine eingehende Anforderung verlangt null oder eine negative Anzahl von Nächten, was keine gültige Aufenthaltsdauer ist.

+
Geschäftsregel: Eine Buchung muss mindestens eine Nacht umfassen.
+

Diagnosen

+
+ + + + + + +
Mögliche UrsacheUrsprungAnalysehinweis
Der Client hat eine Anzahl von Nächten von null oder weniger gesendet.ExternalSenden Sie eine Anzahl von Nächten von eins oder mehr.
+
+

Beispiele

+
+

Öffentliche Antwort (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:night-count-not-positive",
+  "title": "Die Anzahl der Nächte ist ungültig.",
+  "detail": "Die angeforderte Anzahl von Nächten muss eins oder mehr betragen.",
+  "code": "NIGHT_COUNT_NOT_POSITIVE"
+}
+

Diagnose (intern — nicht für externe Weitergabe)

+
2026-07-04T13:42:18.734Z ERROR [NightCount] A booking must be for at least one night, but 0 was requested. error.code=NIGHT_COUNT_NOT_POSITIVE
+
+
+
+
+
+

RoomNumber

+

Fehler, die beim Erstellen einer Zimmernummer aus einem Element einer Anforderungsliste ausgelöst werden.

+
+

ROOM_NUMBER_OUT_OF_RANGE #

+

Zimmernummer außerhalb des Bereichs

+

Dokumentation

Ein Element der Zimmernummernliste der Anforderung liegt außerhalb des unterstützten Bereichs (1-999).

+
Geschäftsregel: Eine Zimmernummer muss zwischen 1 und 999 (einschließlich) liegen.
+

Diagnosen

+
+ + + + + + +
Mögliche UrsacheUrsprungAnalysehinweis
Der Client hat eine Zimmernummer von null, einen negativen Wert oder einen Wert über 999 gesendet.ExternalSenden Sie Zimmernummern innerhalb des unterstützten Bereichs (1-999).
+
+

Beispiele

+
+

Öffentliche Antwort (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:room-number-out-of-range",
+  "title": "Eine Zimmernummer ist ungültig.",
+  "detail": "Eine der angeforderten Zimmernummern liegt außerhalb des unterstützten Bereichs.",
+  "code": "ROOM_NUMBER_OUT_OF_RANGE"
+}
+

Diagnose (intern — nicht für externe Weitergabe)

+
2026-07-04T13:42:18.734Z ERROR [RoomNumber] Room number 1000 is outside the supported range 1-999. error.code=ROOM_NUMBER_OUT_OF_RANGE
+
+
+
+
+
+

Stay

+

Fehler, die bei der gemeinsamen Validierung von An- und Abreisedatum eines Aufenthalts ausgelöst werden.

+
+

STAY_CHECKOUT_NOT_AFTER_CHECKIN #

+

Abreise nicht nach Anreise

+

Dokumentation

Beide Aufenthaltsdaten werden geparst, aber das Abreisedatum liegt vor oder auf dem Anreisedatum, sodass der Aufenthalt keine positive Dauer hat. Diese feldübergreifende Regel wird von der Stay.Create-Factory durchgesetzt.

+
Geschäftsregel: Die Abreise muss streng nach der Anreise liegen.
+

Diagnosen

+
+ + + + + + +
Mögliche UrsacheUrsprungAnalysehinweis
Der Client hat ein Abreisedatum gesendet, das gleich oder früher als das Anreisedatum ist.ExternalSenden Sie ein Abreisedatum, das mindestens einen Tag nach dem Anreisedatum liegt.
+
+

Beispiele

+
+

Öffentliche Antwort (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:stay-checkout-not-after-checkin",
+  "title": "Die Aufenthaltsdaten sind ungültig.",
+  "detail": "Das Abreisedatum muss nach dem Anreisedatum liegen.",
+  "code": "STAY_CHECKOUT_NOT_AFTER_CHECKIN"
+}
+

Diagnose (intern — nicht für externe Weitergabe)

+
2026-07-04T13:42:18.734Z ERROR [Stay] Check-out 2026-08-10 must be strictly after check-in 2026-08-14. error.code=STAY_CHECKOUT_NOT_AFTER_CHECKIN
+
+
+
+
+
+ + + diff --git a/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheLocalizedHtmlRendering_culture=es.verified.html b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheLocalizedHtmlRendering_culture=es.verified.html new file mode 100644 index 00000000..e0772855 --- /dev/null +++ b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheLocalizedHtmlRendering_culture=es.verified.html @@ -0,0 +1,525 @@ + + + + + +Catálogo de errores + + + + + + +
+

10 errores documentados

+
+ +
+ + +
+

BookingEndpoint

+

Errores de puerto primario generados por el endpoint de reserva al enlazar una solicitud entrante en un comando.

+
+

BOOKING_COMMAND_INVALID #

+

Solicitud de reserva no válida

+

Documentación

El endpoint no pudo enlazar la solicitud entrante en un comando de reserva: uno o varios argumentos faltaban o no eran válidos. Cada fallo se recopila bajo este sobre, con su ruta de argumento completa.

+
Regla de negocio: Todos los argumentos requeridos deben estar presentes, y cada argumento debe convertirse en su value object.
+

Diagnósticos

+
+ + + + + + +
Causa posibleOrigenPista de análisis
El cliente envió una solicitud que incumple el contrato del endpoint (argumentos faltantes o mal formados).ExternalLea los errores internos: cada uno nombra el argumento fallido y la regla que incumple.
+
+

Ejemplos

+
+

Respuesta pública (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:booking-command-invalid",
+  "title": "No pudimos aceptar su solicitud de reserva.",
+  "detail": "Uno o varios datos de la solicitud de reserva faltan o no son válidos.",
+  "code": "BOOKING_COMMAND_INVALID"
+}
+

Diagnóstico (interno — no destinado a exposición externa)

+
2026-07-04T13:42:18.734Z ERROR [BookingEndpoint] The booking command is invalid: one or more request arguments failed to bind. error.code=BOOKING_COMMAND_INVALID
+
+
+
+
+

BOOKING_GUEST_INVALID #

+

Huésped de la reserva no válido

+

Documentación

Un huésped de la lista de huéspedes de la solicitud no pudo enlazarse: su nombre faltaba o su correo electrónico estaba mal formado. Sus fallos se agrupan bajo este sobre por elemento, con rutas indexadas como Guests[1].

+
Regla de negocio: Cada huésped debe tener un nombre, y todo correo electrónico presente debe ser válido.
+

Diagnósticos

+
+ + + + + + +
Causa posibleOrigenPista de análisis
El cliente envió un huésped con un nombre faltante o una dirección de correo electrónico mal formada.ExternalLea los errores internos bajo la ruta indexada Guests[i] para el campo fallido.
+
+

Ejemplos

+
+

Respuesta pública (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:booking-guest-invalid",
+  "title": "La información de un huésped no es válida.",
+  "detail": "Uno de los huéspedes no tiene nombre o tiene una dirección de correo electrónico no válida.",
+  "code": "BOOKING_GUEST_INVALID"
+}
+

Diagnóstico (interno — no destinado a exposición externa)

+
2026-07-04T13:42:18.734Z ERROR [BookingEndpoint] The guest is invalid: one or more of its fields failed to bind. error.code=BOOKING_GUEST_INVALID
+
+
+
+
+

BOOKING_STAY_INVALID #

+

Estancia de la reserva no válida

+

Documentación

El subobjeto de estancia de la solicitud no pudo enlazarse: una o ambas de sus fechas faltaban o estaban mal formadas. Sus fallos se agrupan bajo este sobre anidado, con rutas prefijadas por Stay.

+
Regla de negocio: Ambas fechas de la estancia deben estar presentes y ser fechas ISO válidas.
+

Diagnósticos

+
+ + + + + + +
Causa posibleOrigenPista de análisis
El cliente envió una estancia con una fecha de entrada o de salida faltante o mal formada.ExternalLea los errores internos bajo la ruta Stay para la fecha fallida.
+
+

Ejemplos

+
+

Respuesta pública (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:booking-stay-invalid",
+  "title": "No pudimos leer las fechas de la estancia.",
+  "detail": "La fecha de entrada o de salida de la estancia falta o no es válida.",
+  "code": "BOOKING_STAY_INVALID"
+}
+

Diagnóstico (interno — no destinado a exposición externa)

+
2026-07-04T13:42:18.734Z ERROR [BookingEndpoint] The stay is invalid: one or more of its dates failed to bind. error.code=BOOKING_STAY_INVALID
+
+
+
+
+
+

BookingDate

+

Errores generados al analizar una fecha de reserva (entrada / salida) a partir de una cadena de la solicitud.

+
+

BOOKING_DATE_MALFORMED #

+

Fecha de reserva mal formada

+

Documentación

Una solicitud entrante contiene un valor que no es una fecha ISO yyyy-MM-dd, por lo que no puede analizarse en un value object BookingDate.

+
Regla de negocio: Una fecha de reserva debe ser una fecha de calendario ISO en formato yyyy-MM-dd.
+

Diagnósticos

+
+ + + + + + +
Causa posibleOrigenPista de análisis
El cliente envió una fecha en un formato local o mal formado, o una fecha de calendario imposible.ExternalEnvíe las fechas en formato ISO 8601 yyyy-MM-dd, por ejemplo 2026-08-10.
+
+

Ejemplos

+
+

Respuesta pública (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:booking-date-malformed",
+  "title": "La fecha no es válida.",
+  "detail": "Una fecha de reserva no es una fecha ISO (yyyy-MM-dd) válida.",
+  "code": "BOOKING_DATE_MALFORMED"
+}
+

Diagnóstico (interno — no destinado a exposición externa)

+
2026-07-04T13:42:18.734Z ERROR [BookingDate] '2026-13-40' is not a valid ISO (yyyy-MM-dd) date. error.code=BOOKING_DATE_MALFORMED
+
+
+
+
+
+

Tag

+

Errores generados al analizar una etiqueta de reserva a partir de un elemento de una lista de la solicitud.

+
+

BOOKING_TAG_MALFORMED #

+

Etiqueta de reserva mal formada

+

Documentación

Un elemento de la lista de etiquetas de la solicitud está vacío, es demasiado largo o contiene espacios, por lo que no puede analizarse en un value object Tag.

+
Regla de negocio: Una etiqueta debe ser un único token no vacío de un máximo de 32 caracteres, sin espacios.
+

Diagnósticos

+
+ + + + + + +
Causa posibleOrigenPista de análisis
El cliente envió una etiqueta vacía, una expresión con espacios o un valor demasiado largo.ExternalEnvíe cada etiqueta como un único token sin espacios; una las etiquetas de varias palabras con un guion.
+
+

Ejemplos

+
+

Respuesta pública (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:booking-tag-malformed",
+  "title": "Una etiqueta no es válida.",
+  "detail": "Una de las etiquetas de reserva no es un token único válido.",
+  "code": "BOOKING_TAG_MALFORMED"
+}
+

Diagnóstico (interno — no destinado a exposición externa)

+
2026-07-04T13:42:18.734Z ERROR [Tag] 'late checkout' is not a valid tag. error.code=BOOKING_TAG_MALFORMED
+
+
+
+
+
+

Currency

+

Errores generados al analizar un código de moneda de facturación a partir de una cadena de la solicitud.

+
+

CURRENCY_CODE_MALFORMED #

+

Código de moneda mal formado

+

Documentación

Una solicitud entrante contiene un valor que no es un código de moneda de tres letras bien formado, por lo que no puede analizarse en un value object Currency.

+
Regla de negocio: Un código de moneda debe constar exactamente de tres letras ASCII mayúsculas (por ejemplo, EUR).
+

Diagnósticos

+
+ + + + + + +
Causa posibleOrigenPista de análisis
El cliente envió una moneda con la forma incorrecta (minúsculas, un símbolo, un nombre o una longitud incorrecta).ExternalEnvíe el código alfabético ISO-4217 en mayúsculas, por ejemplo USD o EUR.
+
+

Ejemplos

+
+

Respuesta pública (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:currency-code-malformed",
+  "title": "La moneda no es válida.",
+  "detail": "El código de moneda de facturación no es un código de tres letras válido.",
+  "code": "CURRENCY_CODE_MALFORMED"
+}
+

Diagnóstico (interno — no destinado a exposición externa)

+
2026-07-04T13:42:18.734Z ERROR [Currency] 'EURO' is not a valid three-letter currency code. error.code=CURRENCY_CODE_MALFORMED
+
+
+
+
+
+

EmailAddress

+

Errores generados al analizar la dirección de correo electrónico de un huésped a partir de una cadena de la solicitud.

+
+

EMAIL_ADDRESS_MALFORMED #

+

Dirección de correo electrónico mal formada

+

Documentación

Una solicitud entrante contiene un valor que no es una dirección de correo electrónico bien formada, por lo que no puede analizarse en un value object EmailAddress.

+
Regla de negocio: Una dirección de correo electrónico de huésped debe contener una sola « @ » con una parte local y un dominio no vacíos.
+

Diagnósticos

+
+ + + + + + +
Causa posibleOrigenPista de análisis
El cliente envió una dirección mal escrita o truncada (falta « @ », parte local o dominio vacíos).ExternalValide la dirección en el cliente y compare el valor enviado con el formato esperado.
+
+

Ejemplos

+
+

Respuesta pública (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:email-address-malformed",
+  "title": "La dirección de correo electrónico no es válida.",
+  "detail": "El valor proporcionado no es una dirección de correo electrónico válida.",
+  "code": "EMAIL_ADDRESS_MALFORMED"
+}
+

Diagnóstico (interno — no destinado a exposición externa)

+
2026-07-04T13:42:18.734Z ERROR [EmailAddress] 'not-an-email' is not a valid e-mail address. error.code=EMAIL_ADDRESS_MALFORMED
+
+
+
+
+
+

NightCount

+

Errores generados al construir el número de noches a partir de un valor de la solicitud.

+
+

NIGHT_COUNT_NOT_POSITIVE #

+

Número de noches no positivo

+

Documentación

Una solicitud entrante pide cero o un número negativo de noches, lo que no es una duración de estancia válida.

+
Regla de negocio: Una reserva debe ser de al menos una noche.
+

Diagnósticos

+
+ + + + + + +
Causa posibleOrigenPista de análisis
El cliente envió un número de noches de cero o inferior.ExternalEnvíe un número de noches de una o más.
+
+

Ejemplos

+
+

Respuesta pública (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:night-count-not-positive",
+  "title": "El número de noches no es válido.",
+  "detail": "El número de noches solicitado debe ser de una o más.",
+  "code": "NIGHT_COUNT_NOT_POSITIVE"
+}
+

Diagnóstico (interno — no destinado a exposición externa)

+
2026-07-04T13:42:18.734Z ERROR [NightCount] A booking must be for at least one night, but 0 was requested. error.code=NIGHT_COUNT_NOT_POSITIVE
+
+
+
+
+
+

RoomNumber

+

Errores generados al construir un número de habitación a partir de un elemento de una lista de la solicitud.

+
+

ROOM_NUMBER_OUT_OF_RANGE #

+

Número de habitación fuera de rango

+

Documentación

Un elemento de la lista de números de habitación de la solicitud está fuera del rango admitido (1-999).

+
Regla de negocio: Un número de habitación debe estar entre 1 y 999 inclusive.
+

Diagnósticos

+
+ + + + + + +
Causa posibleOrigenPista de análisis
El cliente envió un número de habitación de cero, negativo o superior a 999.ExternalEnvíe números de habitación dentro del rango admitido (1-999).
+
+

Ejemplos

+
+

Respuesta pública (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:room-number-out-of-range",
+  "title": "Un número de habitación no es válido.",
+  "detail": "Uno de los números de habitación solicitados está fuera del rango admitido.",
+  "code": "ROOM_NUMBER_OUT_OF_RANGE"
+}
+

Diagnóstico (interno — no destinado a exposición externa)

+
2026-07-04T13:42:18.734Z ERROR [RoomNumber] Room number 1000 is outside the supported range 1-999. error.code=ROOM_NUMBER_OUT_OF_RANGE
+
+
+
+
+
+

Stay

+

Errores generados al validar conjuntamente las fechas de entrada y salida de una estancia.

+
+

STAY_CHECKOUT_NOT_AFTER_CHECKIN #

+

Salida no posterior a la entrada

+

Documentación

Ambas fechas de la estancia se analizan, pero la fecha de salida es anterior o igual a la fecha de entrada, por lo que la estancia no tiene una duración positiva. Esta regla entre campos la aplica la factory Stay.Create.

+
Regla de negocio: La salida debe ser estrictamente posterior a la entrada.
+

Diagnósticos

+
+ + + + + + +
Causa posibleOrigenPista de análisis
El cliente envió una fecha de salida igual o anterior a la fecha de entrada.ExternalEnvíe una fecha de salida al menos un día después de la fecha de entrada.
+
+

Ejemplos

+
+

Respuesta pública (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:stay-checkout-not-after-checkin",
+  "title": "Las fechas de la estancia no son válidas.",
+  "detail": "La fecha de salida debe ser posterior a la fecha de entrada.",
+  "code": "STAY_CHECKOUT_NOT_AFTER_CHECKIN"
+}
+

Diagnóstico (interno — no destinado a exposición externa)

+
2026-07-04T13:42:18.734Z ERROR [Stay] Check-out 2026-08-10 must be strictly after check-in 2026-08-14. error.code=STAY_CHECKOUT_NOT_AFTER_CHECKIN
+
+
+
+
+
+ + + diff --git a/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheLocalizedHtmlRendering_culture=fr.verified.html b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheLocalizedHtmlRendering_culture=fr.verified.html new file mode 100644 index 00000000..91d358f6 --- /dev/null +++ b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheLocalizedHtmlRendering_culture=fr.verified.html @@ -0,0 +1,525 @@ + + + + + +Catalogue des erreurs + + + + + + +
+

10 erreurs documentées

+
+ +
+ + +
+

BookingEndpoint

+

Erreurs de port primaire levées par le point d'entrée de réservation lorsqu'il lie une requête entrante en une commande.

+
+

BOOKING_COMMAND_INVALID #

+

Requête de réservation invalide

+

Documentation

Le point d'entrée n'a pas pu lier la requête entrante en une commande de réservation : un ou plusieurs arguments étaient manquants ou invalides. Chaque échec est collecté sous cette enveloppe, avec son chemin d'argument complet.

+
Règle métier : Chaque argument requis doit être présent, et chaque argument doit se convertir en son value object.
+

Diagnostics

+
+ + + + + + +
Cause possibleOriginePiste d'analyse
Le client a envoyé une requête qui viole le contrat du point d'entrée (arguments manquants ou mal formés).ExternalLisez les erreurs internes : chacune nomme l'argument défaillant et la règle qu'il viole.
+
+

Exemples

+
+

Réponse publique (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:booking-command-invalid",
+  "title": "Nous n'avons pas pu accepter votre demande de réservation.",
+  "detail": "Un ou plusieurs détails de la demande de réservation sont manquants ou invalides.",
+  "code": "BOOKING_COMMAND_INVALID"
+}
+

Diagnostic (interne — non destiné à l'exposition externe)

+
2026-07-04T13:42:18.734Z ERROR [BookingEndpoint] The booking command is invalid: one or more request arguments failed to bind. error.code=BOOKING_COMMAND_INVALID
+
+
+
+
+

BOOKING_GUEST_INVALID #

+

Voyageur de la réservation invalide

+

Documentation

Un voyageur de la liste de voyageurs de la requête n'a pas pu être lié : son prénom était manquant ou son e-mail mal formé. Ses échecs sont regroupés sous cette enveloppe par élément, avec des chemins indexés tels que Guests[1].

+
Règle métier : Chaque voyageur doit avoir un prénom, et tout e-mail présent doit être valide.
+

Diagnostics

+
+ + + + + + +
Cause possibleOriginePiste d'analyse
Le client a envoyé un voyageur avec un prénom manquant ou une adresse e-mail mal formée.ExternalLisez les erreurs internes sous le chemin indexé Guests[i] pour le champ défaillant.
+
+

Exemples

+
+

Réponse publique (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:booking-guest-invalid",
+  "title": "Les informations d'un voyageur sont invalides.",
+  "detail": "L'un des voyageurs n'a pas de prénom ou a une adresse e-mail invalide.",
+  "code": "BOOKING_GUEST_INVALID"
+}
+

Diagnostic (interne — non destiné à l'exposition externe)

+
2026-07-04T13:42:18.734Z ERROR [BookingEndpoint] The guest is invalid: one or more of its fields failed to bind. error.code=BOOKING_GUEST_INVALID
+
+
+
+
+

BOOKING_STAY_INVALID #

+

Séjour de la réservation invalide

+

Documentation

Le sous-objet séjour de la requête n'a pas pu être lié : une ou deux de ses dates étaient manquantes ou mal formées. Ses échecs sont regroupés sous cette enveloppe imbriquée, avec des chemins préfixés par Stay.

+
Règle métier : Les deux dates du séjour doivent être présentes et être des dates ISO valides.
+

Diagnostics

+
+ + + + + + +
Cause possibleOriginePiste d'analyse
Le client a envoyé un séjour avec une date d'arrivée ou de départ manquante ou mal formée.ExternalLisez les erreurs internes sous le chemin Stay pour la date défaillante.
+
+

Exemples

+
+

Réponse publique (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:booking-stay-invalid",
+  "title": "Nous n'avons pas pu lire les dates du séjour.",
+  "detail": "La date d'arrivée ou de départ du séjour est manquante ou invalide.",
+  "code": "BOOKING_STAY_INVALID"
+}
+

Diagnostic (interne — non destiné à l'exposition externe)

+
2026-07-04T13:42:18.734Z ERROR [BookingEndpoint] The stay is invalid: one or more of its dates failed to bind. error.code=BOOKING_STAY_INVALID
+
+
+
+
+
+

BookingDate

+

Erreurs levées lors de l'analyse d'une date de réservation (arrivée / départ) à partir d'une chaîne de requête.

+
+

BOOKING_DATE_MALFORMED #

+

Date de réservation mal formée

+

Documentation

Une requête entrante porte une valeur qui n'est pas une date ISO yyyy-MM-dd : elle ne peut donc pas être analysée en un value object BookingDate.

+
Règle métier : Une date de réservation doit être une date calendaire ISO au format yyyy-MM-dd.
+

Diagnostics

+
+ + + + + + +
Cause possibleOriginePiste d'analyse
Le client a envoyé une date dans un format localisé ou mal formé, ou une date calendaire impossible.ExternalEnvoyez les dates au format ISO 8601 yyyy-MM-dd, par exemple 2026-08-10.
+
+

Exemples

+
+

Réponse publique (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:booking-date-malformed",
+  "title": "La date est invalide.",
+  "detail": "Une date de réservation n'est pas une date ISO (yyyy-MM-dd) valide.",
+  "code": "BOOKING_DATE_MALFORMED"
+}
+

Diagnostic (interne — non destiné à l'exposition externe)

+
2026-07-04T13:42:18.734Z ERROR [BookingDate] '2026-13-40' is not a valid ISO (yyyy-MM-dd) date. error.code=BOOKING_DATE_MALFORMED
+
+
+
+
+
+

Tag

+

Erreurs levées lors de l'analyse d'une étiquette de réservation à partir d'un élément d'une liste de requête.

+
+

BOOKING_TAG_MALFORMED #

+

Étiquette de réservation mal formée

+

Documentation

Un élément de la liste d'étiquettes de la requête est vide, trop long ou contient des espaces : il ne peut donc pas être analysé en un value object Tag.

+
Règle métier : Une étiquette doit être un unique jeton non vide d'au plus 32 caractères, sans espace.
+

Diagnostics

+
+ + + + + + +
Cause possibleOriginePiste d'analyse
Le client a envoyé une étiquette vide, une expression contenant des espaces, ou une valeur trop longue.ExternalEnvoyez chaque étiquette comme un unique jeton sans espace ; reliez les étiquettes en plusieurs mots par un trait d'union.
+
+

Exemples

+
+

Réponse publique (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:booking-tag-malformed",
+  "title": "Une étiquette est invalide.",
+  "detail": "L'une des étiquettes de réservation n'est pas un jeton unique valide.",
+  "code": "BOOKING_TAG_MALFORMED"
+}
+

Diagnostic (interne — non destiné à l'exposition externe)

+
2026-07-04T13:42:18.734Z ERROR [Tag] 'late checkout' is not a valid tag. error.code=BOOKING_TAG_MALFORMED
+
+
+
+
+
+

Currency

+

Erreurs levées lors de l'analyse d'un code de devise de facturation à partir d'une chaîne de requête.

+
+

CURRENCY_CODE_MALFORMED #

+

Code de devise mal formé

+

Documentation

Une requête entrante porte une valeur qui n'est pas un code de devise à trois lettres bien formé : elle ne peut donc pas être analysée en un value object Currency.

+
Règle métier : Un code de devise doit être composé d'exactement trois lettres ASCII majuscules (par exemple EUR).
+

Diagnostics

+
+ + + + + + +
Cause possibleOriginePiste d'analyse
Le client a envoyé une devise dans la mauvaise forme (minuscules, symbole, nom, ou mauvaise longueur).ExternalEnvoyez le code alphabétique ISO-4217 en majuscules, par exemple USD ou EUR.
+
+

Exemples

+
+

Réponse publique (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:currency-code-malformed",
+  "title": "La devise est invalide.",
+  "detail": "Le code de devise de facturation n'est pas un code à trois lettres valide.",
+  "code": "CURRENCY_CODE_MALFORMED"
+}
+

Diagnostic (interne — non destiné à l'exposition externe)

+
2026-07-04T13:42:18.734Z ERROR [Currency] 'EURO' is not a valid three-letter currency code. error.code=CURRENCY_CODE_MALFORMED
+
+
+
+
+
+

EmailAddress

+

Erreurs levées lors de l'analyse d'une adresse e-mail de voyageur à partir d'une chaîne de requête.

+
+

EMAIL_ADDRESS_MALFORMED #

+

Adresse e-mail mal formée

+

Documentation

Une requête entrante porte une valeur qui n'est pas une adresse e-mail bien formée : elle ne peut donc pas être analysée en un value object EmailAddress.

+
Règle métier : Une adresse e-mail de voyageur doit contenir un seul « @ » avec une partie locale et un domaine non vides.
+

Diagnostics

+
+ + + + + + +
Cause possibleOriginePiste d'analyse
Le client a envoyé une adresse mal orthographiée ou tronquée (« @ » manquant, partie locale ou domaine vide).ExternalValidez l'adresse côté client et comparez la valeur envoyée au format attendu.
+
+

Exemples

+
+

Réponse publique (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:email-address-malformed",
+  "title": "L'adresse e-mail est invalide.",
+  "detail": "La valeur fournie n'est pas une adresse e-mail valide.",
+  "code": "EMAIL_ADDRESS_MALFORMED"
+}
+

Diagnostic (interne — non destiné à l'exposition externe)

+
2026-07-04T13:42:18.734Z ERROR [EmailAddress] 'not-an-email' is not a valid e-mail address. error.code=EMAIL_ADDRESS_MALFORMED
+
+
+
+
+
+

NightCount

+

Erreurs levées lors de la construction du nombre de nuits à partir d'une valeur de requête.

+
+

NIGHT_COUNT_NOT_POSITIVE #

+

Nombre de nuits non positif

+

Documentation

Une requête entrante demande zéro ou un nombre négatif de nuits, ce qui n'est pas une durée de séjour valide.

+
Règle métier : Une réservation doit porter sur au moins une nuit.
+

Diagnostics

+
+ + + + + + +
Cause possibleOriginePiste d'analyse
Le client a envoyé un nombre de nuits nul ou négatif.ExternalEnvoyez un nombre de nuits d'une ou plus.
+
+

Exemples

+
+

Réponse publique (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:night-count-not-positive",
+  "title": "Le nombre de nuits est invalide.",
+  "detail": "Le nombre de nuits demandé doit être d'au moins une.",
+  "code": "NIGHT_COUNT_NOT_POSITIVE"
+}
+

Diagnostic (interne — non destiné à l'exposition externe)

+
2026-07-04T13:42:18.734Z ERROR [NightCount] A booking must be for at least one night, but 0 was requested. error.code=NIGHT_COUNT_NOT_POSITIVE
+
+
+
+
+
+

RoomNumber

+

Erreurs levées lors de la construction d'un numéro de chambre à partir d'un élément d'une liste de requête.

+
+

ROOM_NUMBER_OUT_OF_RANGE #

+

Numéro de chambre hors plage

+

Documentation

Un élément de la liste de numéros de chambre de la requête est en dehors de la plage prise en charge (1-999).

+
Règle métier : Un numéro de chambre doit être compris entre 1 et 999 inclus.
+

Diagnostics

+
+ + + + + + +
Cause possibleOriginePiste d'analyse
Le client a envoyé un numéro de chambre nul, négatif, ou supérieur à 999.ExternalEnvoyez des numéros de chambre dans la plage prise en charge (1-999).
+
+

Exemples

+
+

Réponse publique (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:room-number-out-of-range",
+  "title": "Un numéro de chambre est invalide.",
+  "detail": "L'un des numéros de chambre demandés est en dehors de la plage prise en charge.",
+  "code": "ROOM_NUMBER_OUT_OF_RANGE"
+}
+

Diagnostic (interne — non destiné à l'exposition externe)

+
2026-07-04T13:42:18.734Z ERROR [RoomNumber] Room number 1000 is outside the supported range 1-999. error.code=ROOM_NUMBER_OUT_OF_RANGE
+
+
+
+
+
+

Stay

+

Erreurs levées lors de la validation conjointe des dates d'arrivée et de départ d'un séjour.

+
+

STAY_CHECKOUT_NOT_AFTER_CHECKIN #

+

Départ non postérieur à l'arrivée

+

Documentation

Les deux dates du séjour sont analysées, mais la date de départ est antérieure ou égale à la date d'arrivée : le séjour n'a donc pas de durée positive. Cette règle inter-champs est appliquée par la fabrique Stay.Create.

+
Règle métier : Le départ doit être strictement postérieur à l'arrivée.
+

Diagnostics

+
+ + + + + + +
Cause possibleOriginePiste d'analyse
Le client a envoyé une date de départ égale ou antérieure à la date d'arrivée.ExternalEnvoyez une date de départ au moins un jour après la date d'arrivée.
+
+

Exemples

+
+

Réponse publique (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:stay-checkout-not-after-checkin",
+  "title": "Les dates du séjour sont invalides.",
+  "detail": "La date de départ doit être postérieure à la date d'arrivée.",
+  "code": "STAY_CHECKOUT_NOT_AFTER_CHECKIN"
+}
+

Diagnostic (interne — non destiné à l'exposition externe)

+
2026-07-04T13:42:18.734Z ERROR [Stay] Check-out 2026-08-10 must be strictly after check-in 2026-08-14. error.code=STAY_CHECKOUT_NOT_AFTER_CHECKIN
+
+
+
+
+
+ + + diff --git a/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheLocalizedHtmlRendering_culture=sv.verified.html b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheLocalizedHtmlRendering_culture=sv.verified.html new file mode 100644 index 00000000..467cabcb --- /dev/null +++ b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheLocalizedHtmlRendering_culture=sv.verified.html @@ -0,0 +1,525 @@ + + + + + +Felkatalog + + + + + + +
+

10 dokumenterade fel

+
+ +
+ + +
+

BookingEndpoint

+

Primärportsfel som utlöses av bokningsslutpunkten när den binder en inkommande begäran till ett kommando.

+
+

BOOKING_COMMAND_INVALID #

+

Bokningsbegäran ogiltig

+

Dokumentation

Slutpunkten kunde inte binda den inkommande begäran till ett bokningskommando: ett eller flera argument saknades eller var ogiltiga. Varje fel samlas under detta hölje, vart och ett med sin fullständiga argumentsökväg.

+
Affärsregel: Varje obligatoriskt argument måste finnas, och varje argument måste konverteras till sitt värdeobjekt.
+

Diagnostik

+
+ + + + + + +
Möjlig orsakUrsprungAnalysledtråd
Klienten skickade en begäran som bryter mot slutpunktens kontrakt (saknade eller felaktiga argument).ExternalLäs de inre felen: vart och ett namnger det felande argumentet och regeln som bryts.
+
+

Exempel

+
+

Publikt svar (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:booking-command-invalid",
+  "title": "Vi kunde inte acceptera din bokningsbegäran.",
+  "detail": "En eller flera uppgifter i bokningsbegäran saknas eller är ogiltiga.",
+  "code": "BOOKING_COMMAND_INVALID"
+}
+

Diagnostik (intern — inte avsedd för extern exponering)

+
2026-07-04T13:42:18.734Z ERROR [BookingEndpoint] The booking command is invalid: one or more request arguments failed to bind. error.code=BOOKING_COMMAND_INVALID
+
+
+
+
+

BOOKING_GUEST_INVALID #

+

Bokningens gäst ogiltig

+

Dokumentation

En gäst i begärans gästlista kunde inte bindas: dess förnamn saknades eller dess e-post var felaktig. Dess fel grupperas under detta hölje per element, med indexerade sökvägar som Guests[1].

+
Affärsregel: Varje gäst måste ha ett förnamn, och varje e-post som finns måste vara giltig.
+

Diagnostik

+
+ + + + + + +
Möjlig orsakUrsprungAnalysledtråd
Klienten skickade en gäst med ett saknat förnamn eller en felaktig e-postadress.ExternalLäs de inre felen under den indexerade sökvägen Guests[i] för det felande fältet.
+
+

Exempel

+
+

Publikt svar (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:booking-guest-invalid",
+  "title": "En gästs uppgifter är ogiltiga.",
+  "detail": "En av gästerna saknar förnamn eller har en ogiltig e-postadress.",
+  "code": "BOOKING_GUEST_INVALID"
+}
+

Diagnostik (intern — inte avsedd för extern exponering)

+
2026-07-04T13:42:18.734Z ERROR [BookingEndpoint] The guest is invalid: one or more of its fields failed to bind. error.code=BOOKING_GUEST_INVALID
+
+
+
+
+

BOOKING_STAY_INVALID #

+

Bokningens vistelse ogiltig

+

Dokumentation

Begärans vistelsedelobjekt kunde inte bindas: ett eller båda av dess datum saknades eller var felaktiga. Dess fel grupperas under detta nästlade hölje, med sökvägar prefixerade med Stay.

+
Affärsregel: Båda vistelsedatumen måste finnas och vara giltiga ISO-datum.
+

Diagnostik

+
+ + + + + + +
Möjlig orsakUrsprungAnalysledtråd
Klienten skickade en vistelse med ett saknat eller felaktigt in- eller utcheckningsdatum.ExternalLäs de inre felen under Stay-sökvägen för det felande datumet.
+
+

Exempel

+
+

Publikt svar (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:booking-stay-invalid",
+  "title": "Vi kunde inte läsa vistelsedatumen.",
+  "detail": "Vistelsens in- eller utcheckningsdatum saknas eller är ogiltigt.",
+  "code": "BOOKING_STAY_INVALID"
+}
+

Diagnostik (intern — inte avsedd för extern exponering)

+
2026-07-04T13:42:18.734Z ERROR [BookingEndpoint] The stay is invalid: one or more of its dates failed to bind. error.code=BOOKING_STAY_INVALID
+
+
+
+
+
+

BookingDate

+

Fel som utlöses när ett bokningsdatum (incheckning / utcheckning) tolkas från en begäranssträng.

+
+

BOOKING_DATE_MALFORMED #

+

Felaktigt bokningsdatum

+

Dokumentation

En inkommande begäran innehåller ett värde som inte är ett ISO-datum (yyyy-MM-dd) och därför inte kan tolkas till ett BookingDate-värdeobjekt.

+
Affärsregel: Ett bokningsdatum måste vara ett ISO-kalenderdatum i formatet yyyy-MM-dd.
+

Diagnostik

+
+ + + + + + +
Möjlig orsakUrsprungAnalysledtråd
Klienten skickade ett datum i ett språkspecifikt eller felaktigt format, eller ett omöjligt kalenderdatum.ExternalSkicka datum i formatet ISO 8601 yyyy-MM-dd, till exempel 2026-08-10.
+
+

Exempel

+
+

Publikt svar (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:booking-date-malformed",
+  "title": "Datumet är ogiltigt.",
+  "detail": "Ett bokningsdatum är inte ett giltigt ISO-datum (yyyy-MM-dd).",
+  "code": "BOOKING_DATE_MALFORMED"
+}
+

Diagnostik (intern — inte avsedd för extern exponering)

+
2026-07-04T13:42:18.734Z ERROR [BookingDate] '2026-13-40' is not a valid ISO (yyyy-MM-dd) date. error.code=BOOKING_DATE_MALFORMED
+
+
+
+
+
+

Tag

+

Fel som utlöses när en bokningstagg tolkas från ett element i en begäranslista.

+
+

BOOKING_TAG_MALFORMED #

+

Felaktig bokningstagg

+

Dokumentation

Ett element i begärans tagglista är tomt, för långt eller innehåller blanksteg och kan därför inte tolkas till ett Tag-värdeobjekt.

+
Affärsregel: En tagg måste vara en enda icke-tom token på högst 32 tecken, utan blanksteg.
+

Diagnostik

+
+ + + + + + +
Möjlig orsakUrsprungAnalysledtråd
Klienten skickade en tom tagg, ett uttryck med blanksteg eller ett för långt värde.ExternalSkicka varje tagg som en enda token utan blanksteg; koppla ihop flerordstaggar med bindestreck.
+
+

Exempel

+
+

Publikt svar (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:booking-tag-malformed",
+  "title": "En tagg är ogiltig.",
+  "detail": "En av bokningstaggarna är inte en giltig enskild token.",
+  "code": "BOOKING_TAG_MALFORMED"
+}
+

Diagnostik (intern — inte avsedd för extern exponering)

+
2026-07-04T13:42:18.734Z ERROR [Tag] 'late checkout' is not a valid tag. error.code=BOOKING_TAG_MALFORMED
+
+
+
+
+
+

Currency

+

Fel som utlöses när en faktureringsvalutakod tolkas från en begäranssträng.

+
+

CURRENCY_CODE_MALFORMED #

+

Felaktig valutakod

+

Dokumentation

En inkommande begäran innehåller ett värde som inte är en välformad valutakod med tre bokstäver och därför inte kan tolkas till ett Currency-värdeobjekt.

+
Affärsregel: En valutakod måste bestå av exakt tre versala ASCII-bokstäver (till exempel EUR).
+

Diagnostik

+
+ + + + + + +
Möjlig orsakUrsprungAnalysledtråd
Klienten skickade en valuta i fel form (gemener, en symbol, ett namn eller fel längd).ExternalSkicka den alfabetiska ISO-4217-koden i versaler, till exempel USD eller EUR.
+
+

Exempel

+
+

Publikt svar (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:currency-code-malformed",
+  "title": "Valutan är ogiltig.",
+  "detail": "Faktureringsvalutakoden är inte en giltig kod med tre bokstäver.",
+  "code": "CURRENCY_CODE_MALFORMED"
+}
+

Diagnostik (intern — inte avsedd för extern exponering)

+
2026-07-04T13:42:18.734Z ERROR [Currency] 'EURO' is not a valid three-letter currency code. error.code=CURRENCY_CODE_MALFORMED
+
+
+
+
+
+

EmailAddress

+

Fel som utlöses när en gästs e-postadress tolkas från en begäranssträng.

+
+

EMAIL_ADDRESS_MALFORMED #

+

Felaktig e-postadress

+

Dokumentation

En inkommande begäran innehåller ett värde som inte är en välformad e-postadress och därför inte kan tolkas till ett EmailAddress-värdeobjekt.

+
Affärsregel: En gästs e-postadress måste innehålla exakt ett ”@” med en icke-tom lokal del och en icke-tom domän.
+

Diagnostik

+
+ + + + + + +
Möjlig orsakUrsprungAnalysledtråd
Klienten skickade en felstavad eller avkortad adress (saknat ”@”, tom lokal del eller domän).ExternalValidera adressen på klienten och jämför det skickade värdet med det förväntade formatet.
+
+

Exempel

+
+

Publikt svar (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:email-address-malformed",
+  "title": "E-postadressen är ogiltig.",
+  "detail": "Det angivna värdet är inte en giltig e-postadress.",
+  "code": "EMAIL_ADDRESS_MALFORMED"
+}
+

Diagnostik (intern — inte avsedd för extern exponering)

+
2026-07-04T13:42:18.734Z ERROR [EmailAddress] 'not-an-email' is not a valid e-mail address. error.code=EMAIL_ADDRESS_MALFORMED
+
+
+
+
+
+

NightCount

+

Fel som utlöses när antalet nätter byggs från ett begäransvärde.

+
+

NIGHT_COUNT_NOT_POSITIVE #

+

Icke-positivt antal nätter

+

Dokumentation

En inkommande begäran begär noll eller ett negativt antal nätter, vilket inte är en giltig vistelselängd.

+
Affärsregel: En bokning måste omfatta minst en natt.
+

Diagnostik

+
+ + + + + + +
Möjlig orsakUrsprungAnalysledtråd
Klienten skickade ett antal nätter på noll eller lägre.ExternalSkicka ett antal nätter på en eller fler.
+
+

Exempel

+
+

Publikt svar (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:night-count-not-positive",
+  "title": "Antalet nätter är ogiltigt.",
+  "detail": "Det begärda antalet nätter måste vara en eller fler.",
+  "code": "NIGHT_COUNT_NOT_POSITIVE"
+}
+

Diagnostik (intern — inte avsedd för extern exponering)

+
2026-07-04T13:42:18.734Z ERROR [NightCount] A booking must be for at least one night, but 0 was requested. error.code=NIGHT_COUNT_NOT_POSITIVE
+
+
+
+
+
+

RoomNumber

+

Fel som utlöses när ett rumsnummer byggs från ett element i en begäranslista.

+
+

ROOM_NUMBER_OUT_OF_RANGE #

+

Rumsnummer utanför intervallet

+

Dokumentation

Ett element i begärans lista över rumsnummer ligger utanför det intervall som stöds (1-999).

+
Affärsregel: Ett rumsnummer måste vara mellan 1 och 999 inklusive.
+

Diagnostik

+
+ + + + + + +
Möjlig orsakUrsprungAnalysledtråd
Klienten skickade ett rumsnummer på noll, ett negativt värde eller ett värde över 999.ExternalSkicka rumsnummer inom det intervall som stöds (1-999).
+
+

Exempel

+
+

Publikt svar (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:room-number-out-of-range",
+  "title": "Ett rumsnummer är ogiltigt.",
+  "detail": "Ett av de begärda rumsnumren ligger utanför det intervall som stöds.",
+  "code": "ROOM_NUMBER_OUT_OF_RANGE"
+}
+

Diagnostik (intern — inte avsedd för extern exponering)

+
2026-07-04T13:42:18.734Z ERROR [RoomNumber] Room number 1000 is outside the supported range 1-999. error.code=ROOM_NUMBER_OUT_OF_RANGE
+
+
+
+
+
+

Stay

+

Fel som utlöses vid gemensam validering av en vistelses in- och utcheckningsdatum.

+
+

STAY_CHECKOUT_NOT_AFTER_CHECKIN #

+

Utcheckning inte efter incheckning

+

Dokumentation

Båda vistelsedatumen tolkas, men utcheckningsdatumet är före eller lika med incheckningsdatumet, så vistelsen har ingen positiv längd. Denna regel över flera fält tillämpas av fabriken Stay.Create.

+
Affärsregel: Utcheckningen måste vara strikt efter incheckningen.
+

Diagnostik

+
+ + + + + + +
Möjlig orsakUrsprungAnalysledtråd
Klienten skickade ett utcheckningsdatum som är lika med eller tidigare än incheckningsdatumet.ExternalSkicka ett utcheckningsdatum minst en dag efter incheckningsdatumet.
+
+

Exempel

+
+

Publikt svar (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:stay-checkout-not-after-checkin",
+  "title": "Vistelsedatumen är ogiltiga.",
+  "detail": "Utcheckningsdatumet måste vara efter incheckningsdatumet.",
+  "code": "STAY_CHECKOUT_NOT_AFTER_CHECKIN"
+}
+

Diagnostik (intern — inte avsedd för extern exponering)

+
2026-07-04T13:42:18.734Z ERROR [Stay] Check-out 2026-08-10 must be strictly after check-in 2026-08-14. error.code=STAY_CHECKOUT_NOT_AFTER_CHECKIN
+
+
+
+
+
+ + + diff --git a/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheLocalizedMarkdownRendering_culture=de.verified.md b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheLocalizedMarkdownRendering_culture=de.verified.md new file mode 100644 index 00000000..71cd6be7 --- /dev/null +++ b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheLocalizedMarkdownRendering_culture=de.verified.md @@ -0,0 +1,411 @@ +# Fehlerkatalog + +## Inhaltsverzeichnis + +- [BookingEndpoint-Fehler](#src-booking-endpoint) + - [Buchungsanforderung ungültig](#err-booking-command-invalid) + - [Gast der Buchung ungültig](#err-booking-guest-invalid) + - [Aufenthalt der Buchung ungültig](#err-booking-stay-invalid) +- [BookingDate-Fehler](#src-booking-date) + - [Fehlerhaftes Buchungsdatum](#err-booking-date-malformed) +- [Tag-Fehler](#src-tag) + - [Fehlerhaftes Buchungs-Tag](#err-booking-tag-malformed) +- [Currency-Fehler](#src-currency) + - [Fehlerhafter Währungscode](#err-currency-code-malformed) +- [EmailAddress-Fehler](#src-email-address) + - [Fehlerhafte E-Mail-Adresse](#err-email-address-malformed) +- [NightCount-Fehler](#src-night-count) + - [Nicht positive Anzahl von Nächten](#err-night-count-not-positive) +- [RoomNumber-Fehler](#src-room-number) + - [Zimmernummer außerhalb des Bereichs](#err-room-number-out-of-range) +- [Stay-Fehler](#src-stay) + - [Abreise nicht nach Anreise](#err-stay-checkout-not-after-checkin) + + + +## BookingEndpoint-Fehler + +Primär-Port-Fehler, die vom Buchungsendpunkt ausgelöst werden, wenn er eine eingehende Anforderung an einen Befehl bindet. + + + +### Buchungsanforderung ungültig + +- **Code:** `BOOKING_COMMAND_INVALID` +- **Quelle:** `BookingEndpoint` + +Der Endpunkt konnte die eingehende Anforderung nicht an einen Buchungsbefehl binden: Ein oder mehrere Argumente fehlten oder waren ungültig. Jeder Fehler wird unter diesem Umschlag gesammelt, jeweils mit seinem vollständigen Argumentpfad. + +> **Geschäftsregel:** Jedes erforderliche Argument muss vorhanden sein, und jedes Argument muss in sein Value-Object konvertiert werden. + +#### Diagnosen + +- **Der Client hat eine Anforderung gesendet, die den Vertrag des Endpunkts verletzt (fehlende oder fehlerhafte Argumente).** — _Ursprung:_ External — Lesen Sie die inneren Fehler: Jeder benennt das fehlerhafte Argument und die verletzte Regel. + +#### Beispiele + +**Öffentliche Antwort (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:booking-command-invalid", + "title": "Wir konnten Ihre Buchungsanforderung nicht annehmen.", + "detail": "Eine oder mehrere Angaben der Buchungsanforderung fehlen oder sind ungültig.", + "code": "BOOKING_COMMAND_INVALID" +} +``` + +**Diagnose (intern — nicht für externe Weitergabe)** + +```text +2026-07-04T13:42:18.734Z ERROR [BookingEndpoint] The booking command is invalid: one or more request arguments failed to bind. error.code=BOOKING_COMMAND_INVALID +``` + + + +### Gast der Buchung ungültig + +- **Code:** `BOOKING_GUEST_INVALID` +- **Quelle:** `BookingEndpoint` + +Ein Gast der Gästeliste der Anforderung konnte nicht gebunden werden: Sein Vorname fehlte oder seine E-Mail war fehlerhaft. Seine Fehler werden unter diesem Umschlag pro Element gruppiert, mit indizierten Pfaden wie Guests[1]. + +> **Geschäftsregel:** Jeder Gast muss einen Vornamen haben, und jede vorhandene E-Mail muss gültig sein. + +#### Diagnosen + +- **Der Client hat einen Gast mit einem fehlenden Vornamen oder einer fehlerhaften E-Mail-Adresse gesendet.** — _Ursprung:_ External — Lesen Sie die inneren Fehler unter dem indizierten Pfad Guests[i] für das fehlerhafte Feld. + +#### Beispiele + +**Öffentliche Antwort (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:booking-guest-invalid", + "title": "Die Angaben eines Gastes sind ungültig.", + "detail": "Einer der Gäste hat keinen Vornamen oder eine ungültige E-Mail-Adresse.", + "code": "BOOKING_GUEST_INVALID" +} +``` + +**Diagnose (intern — nicht für externe Weitergabe)** + +```text +2026-07-04T13:42:18.734Z ERROR [BookingEndpoint] The guest is invalid: one or more of its fields failed to bind. error.code=BOOKING_GUEST_INVALID +``` + + + +### Aufenthalt der Buchung ungültig + +- **Code:** `BOOKING_STAY_INVALID` +- **Quelle:** `BookingEndpoint` + +Das Aufenthalts-Unterobjekt der Anforderung konnte nicht gebunden werden: Eines oder beide seiner Daten fehlten oder waren fehlerhaft. Seine Fehler werden unter diesem verschachtelten Umschlag gruppiert, mit durch Stay präfixierten Pfaden. + +> **Geschäftsregel:** Beide Aufenthaltsdaten müssen vorhanden und gültige ISO-Daten sein. + +#### Diagnosen + +- **Der Client hat einen Aufenthalt mit einem fehlenden oder fehlerhaften An- oder Abreisedatum gesendet.** — _Ursprung:_ External — Lesen Sie die inneren Fehler unter dem Stay-Pfad für das fehlerhafte Datum. + +#### Beispiele + +**Öffentliche Antwort (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:booking-stay-invalid", + "title": "Wir konnten die Aufenthaltsdaten nicht lesen.", + "detail": "Das An- oder Abreisedatum des Aufenthalts fehlt oder ist ungültig.", + "code": "BOOKING_STAY_INVALID" +} +``` + +**Diagnose (intern — nicht für externe Weitergabe)** + +```text +2026-07-04T13:42:18.734Z ERROR [BookingEndpoint] The stay is invalid: one or more of its dates failed to bind. error.code=BOOKING_STAY_INVALID +``` + + + +## BookingDate-Fehler + +Fehler, die beim Parsen eines Buchungsdatums (Anreise / Abreise) aus einer Anforderungszeichenfolge ausgelöst werden. + + + +### Fehlerhaftes Buchungsdatum + +- **Code:** `BOOKING_DATE_MALFORMED` +- **Quelle:** `BookingDate` + +Eine eingehende Anforderung enthält einen Wert, der kein ISO-Datum (yyyy-MM-dd) ist und daher nicht in ein BookingDate-Value-Object geparst werden kann. + +> **Geschäftsregel:** Ein Buchungsdatum muss ein ISO-Kalenderdatum im Format yyyy-MM-dd sein. + +#### Diagnosen + +- **Der Client hat ein Datum in einem länderspezifischen oder fehlerhaften Format oder ein unmögliches Kalenderdatum gesendet.** — _Ursprung:_ External — Senden Sie Datumsangaben im Format ISO 8601 yyyy-MM-dd, zum Beispiel 2026-08-10. + +#### Beispiele + +**Öffentliche Antwort (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:booking-date-malformed", + "title": "Das Datum ist ungültig.", + "detail": "Ein Buchungsdatum ist kein gültiges ISO-Datum (yyyy-MM-dd).", + "code": "BOOKING_DATE_MALFORMED" +} +``` + +**Diagnose (intern — nicht für externe Weitergabe)** + +```text +2026-07-04T13:42:18.734Z ERROR [BookingDate] '2026-13-40' is not a valid ISO (yyyy-MM-dd) date. error.code=BOOKING_DATE_MALFORMED +``` + + + +## Tag-Fehler + +Fehler, die beim Parsen eines Buchungs-Tags aus einem Element einer Anforderungsliste ausgelöst werden. + + + +### Fehlerhaftes Buchungs-Tag + +- **Code:** `BOOKING_TAG_MALFORMED` +- **Quelle:** `Tag` + +Ein Element der Tag-Liste der Anforderung ist leer, zu lang oder enthält Leerzeichen und kann daher nicht in ein Tag-Value-Object geparst werden. + +> **Geschäftsregel:** Ein Tag muss ein einzelnes, nicht leeres Token von höchstens 32 Zeichen ohne Leerzeichen sein. + +#### Diagnosen + +- **Der Client hat ein leeres Tag, einen Ausdruck mit Leerzeichen oder einen zu langen Wert gesendet.** — _Ursprung:_ External — Senden Sie jedes Tag als einzelnes Token ohne Leerzeichen; verbinden Sie mehrwortige Tags mit einem Bindestrich. + +#### Beispiele + +**Öffentliche Antwort (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:booking-tag-malformed", + "title": "Ein Tag ist ungültig.", + "detail": "Eines der Buchungs-Tags ist kein gültiges einzelnes Token.", + "code": "BOOKING_TAG_MALFORMED" +} +``` + +**Diagnose (intern — nicht für externe Weitergabe)** + +```text +2026-07-04T13:42:18.734Z ERROR [Tag] 'late checkout' is not a valid tag. error.code=BOOKING_TAG_MALFORMED +``` + + + +## Currency-Fehler + +Fehler, die beim Parsen eines Abrechnungswährungscodes aus einer Anforderungszeichenfolge ausgelöst werden. + + + +### Fehlerhafter Währungscode + +- **Code:** `CURRENCY_CODE_MALFORMED` +- **Quelle:** `Currency` + +Eine eingehende Anforderung enthält einen Wert, der kein wohlgeformter dreistelliger Währungscode ist und daher nicht in ein Currency-Value-Object geparst werden kann. + +> **Geschäftsregel:** Ein Währungscode muss aus genau drei ASCII-Großbuchstaben bestehen (zum Beispiel EUR). + +#### Diagnosen + +- **Der Client hat eine Währung in der falschen Form gesendet (Kleinbuchstaben, ein Symbol, ein Name oder die falsche Länge).** — _Ursprung:_ External — Senden Sie den alphabetischen ISO-4217-Code in Großbuchstaben, zum Beispiel USD oder EUR. + +#### Beispiele + +**Öffentliche Antwort (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:currency-code-malformed", + "title": "Die Währung ist ungültig.", + "detail": "Der Abrechnungswährungscode ist kein gültiger dreistelliger Code.", + "code": "CURRENCY_CODE_MALFORMED" +} +``` + +**Diagnose (intern — nicht für externe Weitergabe)** + +```text +2026-07-04T13:42:18.734Z ERROR [Currency] 'EURO' is not a valid three-letter currency code. error.code=CURRENCY_CODE_MALFORMED +``` + + + +## EmailAddress-Fehler + +Fehler, die beim Parsen der E-Mail-Adresse eines Gastes aus einer Anforderungszeichenfolge ausgelöst werden. + + + +### Fehlerhafte E-Mail-Adresse + +- **Code:** `EMAIL_ADDRESS_MALFORMED` +- **Quelle:** `EmailAddress` + +Eine eingehende Anforderung enthält einen Wert, der keine wohlgeformte E-Mail-Adresse ist und daher nicht in ein EmailAddress-Value-Object geparst werden kann. + +> **Geschäftsregel:** Eine E-Mail-Adresse eines Gastes muss genau ein „@“ mit einem nicht leeren lokalen Teil und einer nicht leeren Domäne enthalten. + +#### Diagnosen + +- **Der Client hat eine falsch geschriebene oder abgeschnittene Adresse gesendet (fehlendes „@“, leerer lokaler Teil oder leere Domäne).** — _Ursprung:_ External — Validieren Sie die Adresse auf dem Client und vergleichen Sie den gesendeten Wert mit dem erwarteten Format. + +#### Beispiele + +**Öffentliche Antwort (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:email-address-malformed", + "title": "Die E-Mail-Adresse ist ungültig.", + "detail": "Der angegebene Wert ist keine gültige E-Mail-Adresse.", + "code": "EMAIL_ADDRESS_MALFORMED" +} +``` + +**Diagnose (intern — nicht für externe Weitergabe)** + +```text +2026-07-04T13:42:18.734Z ERROR [EmailAddress] 'not-an-email' is not a valid e-mail address. error.code=EMAIL_ADDRESS_MALFORMED +``` + + + +## NightCount-Fehler + +Fehler, die beim Erstellen der Anzahl der Nächte aus einem Anforderungswert ausgelöst werden. + + + +### Nicht positive Anzahl von Nächten + +- **Code:** `NIGHT_COUNT_NOT_POSITIVE` +- **Quelle:** `NightCount` + +Eine eingehende Anforderung verlangt null oder eine negative Anzahl von Nächten, was keine gültige Aufenthaltsdauer ist. + +> **Geschäftsregel:** Eine Buchung muss mindestens eine Nacht umfassen. + +#### Diagnosen + +- **Der Client hat eine Anzahl von Nächten von null oder weniger gesendet.** — _Ursprung:_ External — Senden Sie eine Anzahl von Nächten von eins oder mehr. + +#### Beispiele + +**Öffentliche Antwort (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:night-count-not-positive", + "title": "Die Anzahl der Nächte ist ungültig.", + "detail": "Die angeforderte Anzahl von Nächten muss eins oder mehr betragen.", + "code": "NIGHT_COUNT_NOT_POSITIVE" +} +``` + +**Diagnose (intern — nicht für externe Weitergabe)** + +```text +2026-07-04T13:42:18.734Z ERROR [NightCount] A booking must be for at least one night, but 0 was requested. error.code=NIGHT_COUNT_NOT_POSITIVE +``` + + + +## RoomNumber-Fehler + +Fehler, die beim Erstellen einer Zimmernummer aus einem Element einer Anforderungsliste ausgelöst werden. + + + +### Zimmernummer außerhalb des Bereichs + +- **Code:** `ROOM_NUMBER_OUT_OF_RANGE` +- **Quelle:** `RoomNumber` + +Ein Element der Zimmernummernliste der Anforderung liegt außerhalb des unterstützten Bereichs (1-999). + +> **Geschäftsregel:** Eine Zimmernummer muss zwischen 1 und 999 (einschließlich) liegen. + +#### Diagnosen + +- **Der Client hat eine Zimmernummer von null, einen negativen Wert oder einen Wert über 999 gesendet.** — _Ursprung:_ External — Senden Sie Zimmernummern innerhalb des unterstützten Bereichs (1-999). + +#### Beispiele + +**Öffentliche Antwort (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:room-number-out-of-range", + "title": "Eine Zimmernummer ist ungültig.", + "detail": "Eine der angeforderten Zimmernummern liegt außerhalb des unterstützten Bereichs.", + "code": "ROOM_NUMBER_OUT_OF_RANGE" +} +``` + +**Diagnose (intern — nicht für externe Weitergabe)** + +```text +2026-07-04T13:42:18.734Z ERROR [RoomNumber] Room number 1000 is outside the supported range 1-999. error.code=ROOM_NUMBER_OUT_OF_RANGE +``` + + + +## Stay-Fehler + +Fehler, die bei der gemeinsamen Validierung von An- und Abreisedatum eines Aufenthalts ausgelöst werden. + + + +### Abreise nicht nach Anreise + +- **Code:** `STAY_CHECKOUT_NOT_AFTER_CHECKIN` +- **Quelle:** `Stay` + +Beide Aufenthaltsdaten werden geparst, aber das Abreisedatum liegt vor oder auf dem Anreisedatum, sodass der Aufenthalt keine positive Dauer hat. Diese feldübergreifende Regel wird von der Stay.Create-Factory durchgesetzt. + +> **Geschäftsregel:** Die Abreise muss streng nach der Anreise liegen. + +#### Diagnosen + +- **Der Client hat ein Abreisedatum gesendet, das gleich oder früher als das Anreisedatum ist.** — _Ursprung:_ External — Senden Sie ein Abreisedatum, das mindestens einen Tag nach dem Anreisedatum liegt. + +#### Beispiele + +**Öffentliche Antwort (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:stay-checkout-not-after-checkin", + "title": "Die Aufenthaltsdaten sind ungültig.", + "detail": "Das Abreisedatum muss nach dem Anreisedatum liegen.", + "code": "STAY_CHECKOUT_NOT_AFTER_CHECKIN" +} +``` + +**Diagnose (intern — nicht für externe Weitergabe)** + +```text +2026-07-04T13:42:18.734Z ERROR [Stay] Check-out 2026-08-10 must be strictly after check-in 2026-08-14. error.code=STAY_CHECKOUT_NOT_AFTER_CHECKIN +``` + diff --git a/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheLocalizedMarkdownRendering_culture=es.verified.md b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheLocalizedMarkdownRendering_culture=es.verified.md new file mode 100644 index 00000000..52b61fc9 --- /dev/null +++ b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheLocalizedMarkdownRendering_culture=es.verified.md @@ -0,0 +1,411 @@ +# Catálogo de errores + +## Índice + +- [Errores de BookingEndpoint](#src-booking-endpoint) + - [Solicitud de reserva no válida](#err-booking-command-invalid) + - [Huésped de la reserva no válido](#err-booking-guest-invalid) + - [Estancia de la reserva no válida](#err-booking-stay-invalid) +- [Errores de BookingDate](#src-booking-date) + - [Fecha de reserva mal formada](#err-booking-date-malformed) +- [Errores de Tag](#src-tag) + - [Etiqueta de reserva mal formada](#err-booking-tag-malformed) +- [Errores de Currency](#src-currency) + - [Código de moneda mal formado](#err-currency-code-malformed) +- [Errores de EmailAddress](#src-email-address) + - [Dirección de correo electrónico mal formada](#err-email-address-malformed) +- [Errores de NightCount](#src-night-count) + - [Número de noches no positivo](#err-night-count-not-positive) +- [Errores de RoomNumber](#src-room-number) + - [Número de habitación fuera de rango](#err-room-number-out-of-range) +- [Errores de Stay](#src-stay) + - [Salida no posterior a la entrada](#err-stay-checkout-not-after-checkin) + + + +## Errores de BookingEndpoint + +Errores de puerto primario generados por el endpoint de reserva al enlazar una solicitud entrante en un comando. + + + +### Solicitud de reserva no válida + +- **Código:** `BOOKING_COMMAND_INVALID` +- **Fuente:** `BookingEndpoint` + +El endpoint no pudo enlazar la solicitud entrante en un comando de reserva: uno o varios argumentos faltaban o no eran válidos. Cada fallo se recopila bajo este sobre, con su ruta de argumento completa. + +> **Regla de negocio:** Todos los argumentos requeridos deben estar presentes, y cada argumento debe convertirse en su value object. + +#### Diagnósticos + +- **El cliente envió una solicitud que incumple el contrato del endpoint (argumentos faltantes o mal formados).** — _origen:_ External — Lea los errores internos: cada uno nombra el argumento fallido y la regla que incumple. + +#### Ejemplos + +**Respuesta pública (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:booking-command-invalid", + "title": "No pudimos aceptar su solicitud de reserva.", + "detail": "Uno o varios datos de la solicitud de reserva faltan o no son válidos.", + "code": "BOOKING_COMMAND_INVALID" +} +``` + +**Diagnóstico (interno — no destinado a exposición externa)** + +```text +2026-07-04T13:42:18.734Z ERROR [BookingEndpoint] The booking command is invalid: one or more request arguments failed to bind. error.code=BOOKING_COMMAND_INVALID +``` + + + +### Huésped de la reserva no válido + +- **Código:** `BOOKING_GUEST_INVALID` +- **Fuente:** `BookingEndpoint` + +Un huésped de la lista de huéspedes de la solicitud no pudo enlazarse: su nombre faltaba o su correo electrónico estaba mal formado. Sus fallos se agrupan bajo este sobre por elemento, con rutas indexadas como Guests[1]. + +> **Regla de negocio:** Cada huésped debe tener un nombre, y todo correo electrónico presente debe ser válido. + +#### Diagnósticos + +- **El cliente envió un huésped con un nombre faltante o una dirección de correo electrónico mal formada.** — _origen:_ External — Lea los errores internos bajo la ruta indexada Guests[i] para el campo fallido. + +#### Ejemplos + +**Respuesta pública (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:booking-guest-invalid", + "title": "La información de un huésped no es válida.", + "detail": "Uno de los huéspedes no tiene nombre o tiene una dirección de correo electrónico no válida.", + "code": "BOOKING_GUEST_INVALID" +} +``` + +**Diagnóstico (interno — no destinado a exposición externa)** + +```text +2026-07-04T13:42:18.734Z ERROR [BookingEndpoint] The guest is invalid: one or more of its fields failed to bind. error.code=BOOKING_GUEST_INVALID +``` + + + +### Estancia de la reserva no válida + +- **Código:** `BOOKING_STAY_INVALID` +- **Fuente:** `BookingEndpoint` + +El subobjeto de estancia de la solicitud no pudo enlazarse: una o ambas de sus fechas faltaban o estaban mal formadas. Sus fallos se agrupan bajo este sobre anidado, con rutas prefijadas por Stay. + +> **Regla de negocio:** Ambas fechas de la estancia deben estar presentes y ser fechas ISO válidas. + +#### Diagnósticos + +- **El cliente envió una estancia con una fecha de entrada o de salida faltante o mal formada.** — _origen:_ External — Lea los errores internos bajo la ruta Stay para la fecha fallida. + +#### Ejemplos + +**Respuesta pública (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:booking-stay-invalid", + "title": "No pudimos leer las fechas de la estancia.", + "detail": "La fecha de entrada o de salida de la estancia falta o no es válida.", + "code": "BOOKING_STAY_INVALID" +} +``` + +**Diagnóstico (interno — no destinado a exposición externa)** + +```text +2026-07-04T13:42:18.734Z ERROR [BookingEndpoint] The stay is invalid: one or more of its dates failed to bind. error.code=BOOKING_STAY_INVALID +``` + + + +## Errores de BookingDate + +Errores generados al analizar una fecha de reserva (entrada / salida) a partir de una cadena de la solicitud. + + + +### Fecha de reserva mal formada + +- **Código:** `BOOKING_DATE_MALFORMED` +- **Fuente:** `BookingDate` + +Una solicitud entrante contiene un valor que no es una fecha ISO yyyy-MM-dd, por lo que no puede analizarse en un value object BookingDate. + +> **Regla de negocio:** Una fecha de reserva debe ser una fecha de calendario ISO en formato yyyy-MM-dd. + +#### Diagnósticos + +- **El cliente envió una fecha en un formato local o mal formado, o una fecha de calendario imposible.** — _origen:_ External — Envíe las fechas en formato ISO 8601 yyyy-MM-dd, por ejemplo 2026-08-10. + +#### Ejemplos + +**Respuesta pública (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:booking-date-malformed", + "title": "La fecha no es válida.", + "detail": "Una fecha de reserva no es una fecha ISO (yyyy-MM-dd) válida.", + "code": "BOOKING_DATE_MALFORMED" +} +``` + +**Diagnóstico (interno — no destinado a exposición externa)** + +```text +2026-07-04T13:42:18.734Z ERROR [BookingDate] '2026-13-40' is not a valid ISO (yyyy-MM-dd) date. error.code=BOOKING_DATE_MALFORMED +``` + + + +## Errores de Tag + +Errores generados al analizar una etiqueta de reserva a partir de un elemento de una lista de la solicitud. + + + +### Etiqueta de reserva mal formada + +- **Código:** `BOOKING_TAG_MALFORMED` +- **Fuente:** `Tag` + +Un elemento de la lista de etiquetas de la solicitud está vacío, es demasiado largo o contiene espacios, por lo que no puede analizarse en un value object Tag. + +> **Regla de negocio:** Una etiqueta debe ser un único token no vacío de un máximo de 32 caracteres, sin espacios. + +#### Diagnósticos + +- **El cliente envió una etiqueta vacía, una expresión con espacios o un valor demasiado largo.** — _origen:_ External — Envíe cada etiqueta como un único token sin espacios; una las etiquetas de varias palabras con un guion. + +#### Ejemplos + +**Respuesta pública (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:booking-tag-malformed", + "title": "Una etiqueta no es válida.", + "detail": "Una de las etiquetas de reserva no es un token único válido.", + "code": "BOOKING_TAG_MALFORMED" +} +``` + +**Diagnóstico (interno — no destinado a exposición externa)** + +```text +2026-07-04T13:42:18.734Z ERROR [Tag] 'late checkout' is not a valid tag. error.code=BOOKING_TAG_MALFORMED +``` + + + +## Errores de Currency + +Errores generados al analizar un código de moneda de facturación a partir de una cadena de la solicitud. + + + +### Código de moneda mal formado + +- **Código:** `CURRENCY_CODE_MALFORMED` +- **Fuente:** `Currency` + +Una solicitud entrante contiene un valor que no es un código de moneda de tres letras bien formado, por lo que no puede analizarse en un value object Currency. + +> **Regla de negocio:** Un código de moneda debe constar exactamente de tres letras ASCII mayúsculas (por ejemplo, EUR). + +#### Diagnósticos + +- **El cliente envió una moneda con la forma incorrecta (minúsculas, un símbolo, un nombre o una longitud incorrecta).** — _origen:_ External — Envíe el código alfabético ISO-4217 en mayúsculas, por ejemplo USD o EUR. + +#### Ejemplos + +**Respuesta pública (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:currency-code-malformed", + "title": "La moneda no es válida.", + "detail": "El código de moneda de facturación no es un código de tres letras válido.", + "code": "CURRENCY_CODE_MALFORMED" +} +``` + +**Diagnóstico (interno — no destinado a exposición externa)** + +```text +2026-07-04T13:42:18.734Z ERROR [Currency] 'EURO' is not a valid three-letter currency code. error.code=CURRENCY_CODE_MALFORMED +``` + + + +## Errores de EmailAddress + +Errores generados al analizar la dirección de correo electrónico de un huésped a partir de una cadena de la solicitud. + + + +### Dirección de correo electrónico mal formada + +- **Código:** `EMAIL_ADDRESS_MALFORMED` +- **Fuente:** `EmailAddress` + +Una solicitud entrante contiene un valor que no es una dirección de correo electrónico bien formada, por lo que no puede analizarse en un value object EmailAddress. + +> **Regla de negocio:** Una dirección de correo electrónico de huésped debe contener una sola « @ » con una parte local y un dominio no vacíos. + +#### Diagnósticos + +- **El cliente envió una dirección mal escrita o truncada (falta « @ », parte local o dominio vacíos).** — _origen:_ External — Valide la dirección en el cliente y compare el valor enviado con el formato esperado. + +#### Ejemplos + +**Respuesta pública (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:email-address-malformed", + "title": "La dirección de correo electrónico no es válida.", + "detail": "El valor proporcionado no es una dirección de correo electrónico válida.", + "code": "EMAIL_ADDRESS_MALFORMED" +} +``` + +**Diagnóstico (interno — no destinado a exposición externa)** + +```text +2026-07-04T13:42:18.734Z ERROR [EmailAddress] 'not-an-email' is not a valid e-mail address. error.code=EMAIL_ADDRESS_MALFORMED +``` + + + +## Errores de NightCount + +Errores generados al construir el número de noches a partir de un valor de la solicitud. + + + +### Número de noches no positivo + +- **Código:** `NIGHT_COUNT_NOT_POSITIVE` +- **Fuente:** `NightCount` + +Una solicitud entrante pide cero o un número negativo de noches, lo que no es una duración de estancia válida. + +> **Regla de negocio:** Una reserva debe ser de al menos una noche. + +#### Diagnósticos + +- **El cliente envió un número de noches de cero o inferior.** — _origen:_ External — Envíe un número de noches de una o más. + +#### Ejemplos + +**Respuesta pública (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:night-count-not-positive", + "title": "El número de noches no es válido.", + "detail": "El número de noches solicitado debe ser de una o más.", + "code": "NIGHT_COUNT_NOT_POSITIVE" +} +``` + +**Diagnóstico (interno — no destinado a exposición externa)** + +```text +2026-07-04T13:42:18.734Z ERROR [NightCount] A booking must be for at least one night, but 0 was requested. error.code=NIGHT_COUNT_NOT_POSITIVE +``` + + + +## Errores de RoomNumber + +Errores generados al construir un número de habitación a partir de un elemento de una lista de la solicitud. + + + +### Número de habitación fuera de rango + +- **Código:** `ROOM_NUMBER_OUT_OF_RANGE` +- **Fuente:** `RoomNumber` + +Un elemento de la lista de números de habitación de la solicitud está fuera del rango admitido (1-999). + +> **Regla de negocio:** Un número de habitación debe estar entre 1 y 999 inclusive. + +#### Diagnósticos + +- **El cliente envió un número de habitación de cero, negativo o superior a 999.** — _origen:_ External — Envíe números de habitación dentro del rango admitido (1-999). + +#### Ejemplos + +**Respuesta pública (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:room-number-out-of-range", + "title": "Un número de habitación no es válido.", + "detail": "Uno de los números de habitación solicitados está fuera del rango admitido.", + "code": "ROOM_NUMBER_OUT_OF_RANGE" +} +``` + +**Diagnóstico (interno — no destinado a exposición externa)** + +```text +2026-07-04T13:42:18.734Z ERROR [RoomNumber] Room number 1000 is outside the supported range 1-999. error.code=ROOM_NUMBER_OUT_OF_RANGE +``` + + + +## Errores de Stay + +Errores generados al validar conjuntamente las fechas de entrada y salida de una estancia. + + + +### Salida no posterior a la entrada + +- **Código:** `STAY_CHECKOUT_NOT_AFTER_CHECKIN` +- **Fuente:** `Stay` + +Ambas fechas de la estancia se analizan, pero la fecha de salida es anterior o igual a la fecha de entrada, por lo que la estancia no tiene una duración positiva. Esta regla entre campos la aplica la factory Stay.Create. + +> **Regla de negocio:** La salida debe ser estrictamente posterior a la entrada. + +#### Diagnósticos + +- **El cliente envió una fecha de salida igual o anterior a la fecha de entrada.** — _origen:_ External — Envíe una fecha de salida al menos un día después de la fecha de entrada. + +#### Ejemplos + +**Respuesta pública (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:stay-checkout-not-after-checkin", + "title": "Las fechas de la estancia no son válidas.", + "detail": "La fecha de salida debe ser posterior a la fecha de entrada.", + "code": "STAY_CHECKOUT_NOT_AFTER_CHECKIN" +} +``` + +**Diagnóstico (interno — no destinado a exposición externa)** + +```text +2026-07-04T13:42:18.734Z ERROR [Stay] Check-out 2026-08-10 must be strictly after check-in 2026-08-14. error.code=STAY_CHECKOUT_NOT_AFTER_CHECKIN +``` + diff --git a/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheLocalizedMarkdownRendering_culture=fr.verified.md b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheLocalizedMarkdownRendering_culture=fr.verified.md new file mode 100644 index 00000000..7918a061 --- /dev/null +++ b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheLocalizedMarkdownRendering_culture=fr.verified.md @@ -0,0 +1,411 @@ +# Catalogue des erreurs + +## Table des matières + +- [Erreurs BookingEndpoint](#src-booking-endpoint) + - [Requête de réservation invalide](#err-booking-command-invalid) + - [Voyageur de la réservation invalide](#err-booking-guest-invalid) + - [Séjour de la réservation invalide](#err-booking-stay-invalid) +- [Erreurs BookingDate](#src-booking-date) + - [Date de réservation mal formée](#err-booking-date-malformed) +- [Erreurs Tag](#src-tag) + - [Étiquette de réservation mal formée](#err-booking-tag-malformed) +- [Erreurs Currency](#src-currency) + - [Code de devise mal formé](#err-currency-code-malformed) +- [Erreurs EmailAddress](#src-email-address) + - [Adresse e-mail mal formée](#err-email-address-malformed) +- [Erreurs NightCount](#src-night-count) + - [Nombre de nuits non positif](#err-night-count-not-positive) +- [Erreurs RoomNumber](#src-room-number) + - [Numéro de chambre hors plage](#err-room-number-out-of-range) +- [Erreurs Stay](#src-stay) + - [Départ non postérieur à l'arrivée](#err-stay-checkout-not-after-checkin) + + + +## Erreurs BookingEndpoint + +Erreurs de port primaire levées par le point d'entrée de réservation lorsqu'il lie une requête entrante en une commande. + + + +### Requête de réservation invalide + +- **Code :** `BOOKING_COMMAND_INVALID` +- **Source :** `BookingEndpoint` + +Le point d'entrée n'a pas pu lier la requête entrante en une commande de réservation : un ou plusieurs arguments étaient manquants ou invalides. Chaque échec est collecté sous cette enveloppe, avec son chemin d'argument complet. + +> **Règle métier :** Chaque argument requis doit être présent, et chaque argument doit se convertir en son value object. + +#### Diagnostics + +- **Le client a envoyé une requête qui viole le contrat du point d'entrée (arguments manquants ou mal formés).** — _origine :_ External — Lisez les erreurs internes : chacune nomme l'argument défaillant et la règle qu'il viole. + +#### Exemples + +**Réponse publique (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:booking-command-invalid", + "title": "Nous n'avons pas pu accepter votre demande de réservation.", + "detail": "Un ou plusieurs détails de la demande de réservation sont manquants ou invalides.", + "code": "BOOKING_COMMAND_INVALID" +} +``` + +**Diagnostic (interne — non destiné à l'exposition externe)** + +```text +2026-07-04T13:42:18.734Z ERROR [BookingEndpoint] The booking command is invalid: one or more request arguments failed to bind. error.code=BOOKING_COMMAND_INVALID +``` + + + +### Voyageur de la réservation invalide + +- **Code :** `BOOKING_GUEST_INVALID` +- **Source :** `BookingEndpoint` + +Un voyageur de la liste de voyageurs de la requête n'a pas pu être lié : son prénom était manquant ou son e-mail mal formé. Ses échecs sont regroupés sous cette enveloppe par élément, avec des chemins indexés tels que Guests[1]. + +> **Règle métier :** Chaque voyageur doit avoir un prénom, et tout e-mail présent doit être valide. + +#### Diagnostics + +- **Le client a envoyé un voyageur avec un prénom manquant ou une adresse e-mail mal formée.** — _origine :_ External — Lisez les erreurs internes sous le chemin indexé Guests[i] pour le champ défaillant. + +#### Exemples + +**Réponse publique (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:booking-guest-invalid", + "title": "Les informations d'un voyageur sont invalides.", + "detail": "L'un des voyageurs n'a pas de prénom ou a une adresse e-mail invalide.", + "code": "BOOKING_GUEST_INVALID" +} +``` + +**Diagnostic (interne — non destiné à l'exposition externe)** + +```text +2026-07-04T13:42:18.734Z ERROR [BookingEndpoint] The guest is invalid: one or more of its fields failed to bind. error.code=BOOKING_GUEST_INVALID +``` + + + +### Séjour de la réservation invalide + +- **Code :** `BOOKING_STAY_INVALID` +- **Source :** `BookingEndpoint` + +Le sous-objet séjour de la requête n'a pas pu être lié : une ou deux de ses dates étaient manquantes ou mal formées. Ses échecs sont regroupés sous cette enveloppe imbriquée, avec des chemins préfixés par Stay. + +> **Règle métier :** Les deux dates du séjour doivent être présentes et être des dates ISO valides. + +#### Diagnostics + +- **Le client a envoyé un séjour avec une date d'arrivée ou de départ manquante ou mal formée.** — _origine :_ External — Lisez les erreurs internes sous le chemin Stay pour la date défaillante. + +#### Exemples + +**Réponse publique (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:booking-stay-invalid", + "title": "Nous n'avons pas pu lire les dates du séjour.", + "detail": "La date d'arrivée ou de départ du séjour est manquante ou invalide.", + "code": "BOOKING_STAY_INVALID" +} +``` + +**Diagnostic (interne — non destiné à l'exposition externe)** + +```text +2026-07-04T13:42:18.734Z ERROR [BookingEndpoint] The stay is invalid: one or more of its dates failed to bind. error.code=BOOKING_STAY_INVALID +``` + + + +## Erreurs BookingDate + +Erreurs levées lors de l'analyse d'une date de réservation (arrivée / départ) à partir d'une chaîne de requête. + + + +### Date de réservation mal formée + +- **Code :** `BOOKING_DATE_MALFORMED` +- **Source :** `BookingDate` + +Une requête entrante porte une valeur qui n'est pas une date ISO yyyy-MM-dd : elle ne peut donc pas être analysée en un value object BookingDate. + +> **Règle métier :** Une date de réservation doit être une date calendaire ISO au format yyyy-MM-dd. + +#### Diagnostics + +- **Le client a envoyé une date dans un format localisé ou mal formé, ou une date calendaire impossible.** — _origine :_ External — Envoyez les dates au format ISO 8601 yyyy-MM-dd, par exemple 2026-08-10. + +#### Exemples + +**Réponse publique (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:booking-date-malformed", + "title": "La date est invalide.", + "detail": "Une date de réservation n'est pas une date ISO (yyyy-MM-dd) valide.", + "code": "BOOKING_DATE_MALFORMED" +} +``` + +**Diagnostic (interne — non destiné à l'exposition externe)** + +```text +2026-07-04T13:42:18.734Z ERROR [BookingDate] '2026-13-40' is not a valid ISO (yyyy-MM-dd) date. error.code=BOOKING_DATE_MALFORMED +``` + + + +## Erreurs Tag + +Erreurs levées lors de l'analyse d'une étiquette de réservation à partir d'un élément d'une liste de requête. + + + +### Étiquette de réservation mal formée + +- **Code :** `BOOKING_TAG_MALFORMED` +- **Source :** `Tag` + +Un élément de la liste d'étiquettes de la requête est vide, trop long ou contient des espaces : il ne peut donc pas être analysé en un value object Tag. + +> **Règle métier :** Une étiquette doit être un unique jeton non vide d'au plus 32 caractères, sans espace. + +#### Diagnostics + +- **Le client a envoyé une étiquette vide, une expression contenant des espaces, ou une valeur trop longue.** — _origine :_ External — Envoyez chaque étiquette comme un unique jeton sans espace ; reliez les étiquettes en plusieurs mots par un trait d'union. + +#### Exemples + +**Réponse publique (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:booking-tag-malformed", + "title": "Une étiquette est invalide.", + "detail": "L'une des étiquettes de réservation n'est pas un jeton unique valide.", + "code": "BOOKING_TAG_MALFORMED" +} +``` + +**Diagnostic (interne — non destiné à l'exposition externe)** + +```text +2026-07-04T13:42:18.734Z ERROR [Tag] 'late checkout' is not a valid tag. error.code=BOOKING_TAG_MALFORMED +``` + + + +## Erreurs Currency + +Erreurs levées lors de l'analyse d'un code de devise de facturation à partir d'une chaîne de requête. + + + +### Code de devise mal formé + +- **Code :** `CURRENCY_CODE_MALFORMED` +- **Source :** `Currency` + +Une requête entrante porte une valeur qui n'est pas un code de devise à trois lettres bien formé : elle ne peut donc pas être analysée en un value object Currency. + +> **Règle métier :** Un code de devise doit être composé d'exactement trois lettres ASCII majuscules (par exemple EUR). + +#### Diagnostics + +- **Le client a envoyé une devise dans la mauvaise forme (minuscules, symbole, nom, ou mauvaise longueur).** — _origine :_ External — Envoyez le code alphabétique ISO-4217 en majuscules, par exemple USD ou EUR. + +#### Exemples + +**Réponse publique (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:currency-code-malformed", + "title": "La devise est invalide.", + "detail": "Le code de devise de facturation n'est pas un code à trois lettres valide.", + "code": "CURRENCY_CODE_MALFORMED" +} +``` + +**Diagnostic (interne — non destiné à l'exposition externe)** + +```text +2026-07-04T13:42:18.734Z ERROR [Currency] 'EURO' is not a valid three-letter currency code. error.code=CURRENCY_CODE_MALFORMED +``` + + + +## Erreurs EmailAddress + +Erreurs levées lors de l'analyse d'une adresse e-mail de voyageur à partir d'une chaîne de requête. + + + +### Adresse e-mail mal formée + +- **Code :** `EMAIL_ADDRESS_MALFORMED` +- **Source :** `EmailAddress` + +Une requête entrante porte une valeur qui n'est pas une adresse e-mail bien formée : elle ne peut donc pas être analysée en un value object EmailAddress. + +> **Règle métier :** Une adresse e-mail de voyageur doit contenir un seul « @ » avec une partie locale et un domaine non vides. + +#### Diagnostics + +- **Le client a envoyé une adresse mal orthographiée ou tronquée (« @ » manquant, partie locale ou domaine vide).** — _origine :_ External — Validez l'adresse côté client et comparez la valeur envoyée au format attendu. + +#### Exemples + +**Réponse publique (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:email-address-malformed", + "title": "L'adresse e-mail est invalide.", + "detail": "La valeur fournie n'est pas une adresse e-mail valide.", + "code": "EMAIL_ADDRESS_MALFORMED" +} +``` + +**Diagnostic (interne — non destiné à l'exposition externe)** + +```text +2026-07-04T13:42:18.734Z ERROR [EmailAddress] 'not-an-email' is not a valid e-mail address. error.code=EMAIL_ADDRESS_MALFORMED +``` + + + +## Erreurs NightCount + +Erreurs levées lors de la construction du nombre de nuits à partir d'une valeur de requête. + + + +### Nombre de nuits non positif + +- **Code :** `NIGHT_COUNT_NOT_POSITIVE` +- **Source :** `NightCount` + +Une requête entrante demande zéro ou un nombre négatif de nuits, ce qui n'est pas une durée de séjour valide. + +> **Règle métier :** Une réservation doit porter sur au moins une nuit. + +#### Diagnostics + +- **Le client a envoyé un nombre de nuits nul ou négatif.** — _origine :_ External — Envoyez un nombre de nuits d'une ou plus. + +#### Exemples + +**Réponse publique (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:night-count-not-positive", + "title": "Le nombre de nuits est invalide.", + "detail": "Le nombre de nuits demandé doit être d'au moins une.", + "code": "NIGHT_COUNT_NOT_POSITIVE" +} +``` + +**Diagnostic (interne — non destiné à l'exposition externe)** + +```text +2026-07-04T13:42:18.734Z ERROR [NightCount] A booking must be for at least one night, but 0 was requested. error.code=NIGHT_COUNT_NOT_POSITIVE +``` + + + +## Erreurs RoomNumber + +Erreurs levées lors de la construction d'un numéro de chambre à partir d'un élément d'une liste de requête. + + + +### Numéro de chambre hors plage + +- **Code :** `ROOM_NUMBER_OUT_OF_RANGE` +- **Source :** `RoomNumber` + +Un élément de la liste de numéros de chambre de la requête est en dehors de la plage prise en charge (1-999). + +> **Règle métier :** Un numéro de chambre doit être compris entre 1 et 999 inclus. + +#### Diagnostics + +- **Le client a envoyé un numéro de chambre nul, négatif, ou supérieur à 999.** — _origine :_ External — Envoyez des numéros de chambre dans la plage prise en charge (1-999). + +#### Exemples + +**Réponse publique (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:room-number-out-of-range", + "title": "Un numéro de chambre est invalide.", + "detail": "L'un des numéros de chambre demandés est en dehors de la plage prise en charge.", + "code": "ROOM_NUMBER_OUT_OF_RANGE" +} +``` + +**Diagnostic (interne — non destiné à l'exposition externe)** + +```text +2026-07-04T13:42:18.734Z ERROR [RoomNumber] Room number 1000 is outside the supported range 1-999. error.code=ROOM_NUMBER_OUT_OF_RANGE +``` + + + +## Erreurs Stay + +Erreurs levées lors de la validation conjointe des dates d'arrivée et de départ d'un séjour. + + + +### Départ non postérieur à l'arrivée + +- **Code :** `STAY_CHECKOUT_NOT_AFTER_CHECKIN` +- **Source :** `Stay` + +Les deux dates du séjour sont analysées, mais la date de départ est antérieure ou égale à la date d'arrivée : le séjour n'a donc pas de durée positive. Cette règle inter-champs est appliquée par la fabrique Stay.Create. + +> **Règle métier :** Le départ doit être strictement postérieur à l'arrivée. + +#### Diagnostics + +- **Le client a envoyé une date de départ égale ou antérieure à la date d'arrivée.** — _origine :_ External — Envoyez une date de départ au moins un jour après la date d'arrivée. + +#### Exemples + +**Réponse publique (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:stay-checkout-not-after-checkin", + "title": "Les dates du séjour sont invalides.", + "detail": "La date de départ doit être postérieure à la date d'arrivée.", + "code": "STAY_CHECKOUT_NOT_AFTER_CHECKIN" +} +``` + +**Diagnostic (interne — non destiné à l'exposition externe)** + +```text +2026-07-04T13:42:18.734Z ERROR [Stay] Check-out 2026-08-10 must be strictly after check-in 2026-08-14. error.code=STAY_CHECKOUT_NOT_AFTER_CHECKIN +``` + diff --git a/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheLocalizedMarkdownRendering_culture=sv.verified.md b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheLocalizedMarkdownRendering_culture=sv.verified.md new file mode 100644 index 00000000..a070e430 --- /dev/null +++ b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheLocalizedMarkdownRendering_culture=sv.verified.md @@ -0,0 +1,411 @@ +# Felkatalog + +## Innehållsförteckning + +- [BookingEndpoint-fel](#src-booking-endpoint) + - [Bokningsbegäran ogiltig](#err-booking-command-invalid) + - [Bokningens gäst ogiltig](#err-booking-guest-invalid) + - [Bokningens vistelse ogiltig](#err-booking-stay-invalid) +- [BookingDate-fel](#src-booking-date) + - [Felaktigt bokningsdatum](#err-booking-date-malformed) +- [Tag-fel](#src-tag) + - [Felaktig bokningstagg](#err-booking-tag-malformed) +- [Currency-fel](#src-currency) + - [Felaktig valutakod](#err-currency-code-malformed) +- [EmailAddress-fel](#src-email-address) + - [Felaktig e-postadress](#err-email-address-malformed) +- [NightCount-fel](#src-night-count) + - [Icke-positivt antal nätter](#err-night-count-not-positive) +- [RoomNumber-fel](#src-room-number) + - [Rumsnummer utanför intervallet](#err-room-number-out-of-range) +- [Stay-fel](#src-stay) + - [Utcheckning inte efter incheckning](#err-stay-checkout-not-after-checkin) + + + +## BookingEndpoint-fel + +Primärportsfel som utlöses av bokningsslutpunkten när den binder en inkommande begäran till ett kommando. + + + +### Bokningsbegäran ogiltig + +- **Kod:** `BOOKING_COMMAND_INVALID` +- **Källa:** `BookingEndpoint` + +Slutpunkten kunde inte binda den inkommande begäran till ett bokningskommando: ett eller flera argument saknades eller var ogiltiga. Varje fel samlas under detta hölje, vart och ett med sin fullständiga argumentsökväg. + +> **Affärsregel:** Varje obligatoriskt argument måste finnas, och varje argument måste konverteras till sitt värdeobjekt. + +#### Diagnostik + +- **Klienten skickade en begäran som bryter mot slutpunktens kontrakt (saknade eller felaktiga argument).** — _ursprung:_ External — Läs de inre felen: vart och ett namnger det felande argumentet och regeln som bryts. + +#### Exempel + +**Publikt svar (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:booking-command-invalid", + "title": "Vi kunde inte acceptera din bokningsbegäran.", + "detail": "En eller flera uppgifter i bokningsbegäran saknas eller är ogiltiga.", + "code": "BOOKING_COMMAND_INVALID" +} +``` + +**Diagnostik (intern — inte avsedd för extern exponering)** + +```text +2026-07-04T13:42:18.734Z ERROR [BookingEndpoint] The booking command is invalid: one or more request arguments failed to bind. error.code=BOOKING_COMMAND_INVALID +``` + + + +### Bokningens gäst ogiltig + +- **Kod:** `BOOKING_GUEST_INVALID` +- **Källa:** `BookingEndpoint` + +En gäst i begärans gästlista kunde inte bindas: dess förnamn saknades eller dess e-post var felaktig. Dess fel grupperas under detta hölje per element, med indexerade sökvägar som Guests[1]. + +> **Affärsregel:** Varje gäst måste ha ett förnamn, och varje e-post som finns måste vara giltig. + +#### Diagnostik + +- **Klienten skickade en gäst med ett saknat förnamn eller en felaktig e-postadress.** — _ursprung:_ External — Läs de inre felen under den indexerade sökvägen Guests[i] för det felande fältet. + +#### Exempel + +**Publikt svar (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:booking-guest-invalid", + "title": "En gästs uppgifter är ogiltiga.", + "detail": "En av gästerna saknar förnamn eller har en ogiltig e-postadress.", + "code": "BOOKING_GUEST_INVALID" +} +``` + +**Diagnostik (intern — inte avsedd för extern exponering)** + +```text +2026-07-04T13:42:18.734Z ERROR [BookingEndpoint] The guest is invalid: one or more of its fields failed to bind. error.code=BOOKING_GUEST_INVALID +``` + + + +### Bokningens vistelse ogiltig + +- **Kod:** `BOOKING_STAY_INVALID` +- **Källa:** `BookingEndpoint` + +Begärans vistelsedelobjekt kunde inte bindas: ett eller båda av dess datum saknades eller var felaktiga. Dess fel grupperas under detta nästlade hölje, med sökvägar prefixerade med Stay. + +> **Affärsregel:** Båda vistelsedatumen måste finnas och vara giltiga ISO-datum. + +#### Diagnostik + +- **Klienten skickade en vistelse med ett saknat eller felaktigt in- eller utcheckningsdatum.** — _ursprung:_ External — Läs de inre felen under Stay-sökvägen för det felande datumet. + +#### Exempel + +**Publikt svar (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:booking-stay-invalid", + "title": "Vi kunde inte läsa vistelsedatumen.", + "detail": "Vistelsens in- eller utcheckningsdatum saknas eller är ogiltigt.", + "code": "BOOKING_STAY_INVALID" +} +``` + +**Diagnostik (intern — inte avsedd för extern exponering)** + +```text +2026-07-04T13:42:18.734Z ERROR [BookingEndpoint] The stay is invalid: one or more of its dates failed to bind. error.code=BOOKING_STAY_INVALID +``` + + + +## BookingDate-fel + +Fel som utlöses när ett bokningsdatum (incheckning / utcheckning) tolkas från en begäranssträng. + + + +### Felaktigt bokningsdatum + +- **Kod:** `BOOKING_DATE_MALFORMED` +- **Källa:** `BookingDate` + +En inkommande begäran innehåller ett värde som inte är ett ISO-datum (yyyy-MM-dd) och därför inte kan tolkas till ett BookingDate-värdeobjekt. + +> **Affärsregel:** Ett bokningsdatum måste vara ett ISO-kalenderdatum i formatet yyyy-MM-dd. + +#### Diagnostik + +- **Klienten skickade ett datum i ett språkspecifikt eller felaktigt format, eller ett omöjligt kalenderdatum.** — _ursprung:_ External — Skicka datum i formatet ISO 8601 yyyy-MM-dd, till exempel 2026-08-10. + +#### Exempel + +**Publikt svar (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:booking-date-malformed", + "title": "Datumet är ogiltigt.", + "detail": "Ett bokningsdatum är inte ett giltigt ISO-datum (yyyy-MM-dd).", + "code": "BOOKING_DATE_MALFORMED" +} +``` + +**Diagnostik (intern — inte avsedd för extern exponering)** + +```text +2026-07-04T13:42:18.734Z ERROR [BookingDate] '2026-13-40' is not a valid ISO (yyyy-MM-dd) date. error.code=BOOKING_DATE_MALFORMED +``` + + + +## Tag-fel + +Fel som utlöses när en bokningstagg tolkas från ett element i en begäranslista. + + + +### Felaktig bokningstagg + +- **Kod:** `BOOKING_TAG_MALFORMED` +- **Källa:** `Tag` + +Ett element i begärans tagglista är tomt, för långt eller innehåller blanksteg och kan därför inte tolkas till ett Tag-värdeobjekt. + +> **Affärsregel:** En tagg måste vara en enda icke-tom token på högst 32 tecken, utan blanksteg. + +#### Diagnostik + +- **Klienten skickade en tom tagg, ett uttryck med blanksteg eller ett för långt värde.** — _ursprung:_ External — Skicka varje tagg som en enda token utan blanksteg; koppla ihop flerordstaggar med bindestreck. + +#### Exempel + +**Publikt svar (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:booking-tag-malformed", + "title": "En tagg är ogiltig.", + "detail": "En av bokningstaggarna är inte en giltig enskild token.", + "code": "BOOKING_TAG_MALFORMED" +} +``` + +**Diagnostik (intern — inte avsedd för extern exponering)** + +```text +2026-07-04T13:42:18.734Z ERROR [Tag] 'late checkout' is not a valid tag. error.code=BOOKING_TAG_MALFORMED +``` + + + +## Currency-fel + +Fel som utlöses när en faktureringsvalutakod tolkas från en begäranssträng. + + + +### Felaktig valutakod + +- **Kod:** `CURRENCY_CODE_MALFORMED` +- **Källa:** `Currency` + +En inkommande begäran innehåller ett värde som inte är en välformad valutakod med tre bokstäver och därför inte kan tolkas till ett Currency-värdeobjekt. + +> **Affärsregel:** En valutakod måste bestå av exakt tre versala ASCII-bokstäver (till exempel EUR). + +#### Diagnostik + +- **Klienten skickade en valuta i fel form (gemener, en symbol, ett namn eller fel längd).** — _ursprung:_ External — Skicka den alfabetiska ISO-4217-koden i versaler, till exempel USD eller EUR. + +#### Exempel + +**Publikt svar (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:currency-code-malformed", + "title": "Valutan är ogiltig.", + "detail": "Faktureringsvalutakoden är inte en giltig kod med tre bokstäver.", + "code": "CURRENCY_CODE_MALFORMED" +} +``` + +**Diagnostik (intern — inte avsedd för extern exponering)** + +```text +2026-07-04T13:42:18.734Z ERROR [Currency] 'EURO' is not a valid three-letter currency code. error.code=CURRENCY_CODE_MALFORMED +``` + + + +## EmailAddress-fel + +Fel som utlöses när en gästs e-postadress tolkas från en begäranssträng. + + + +### Felaktig e-postadress + +- **Kod:** `EMAIL_ADDRESS_MALFORMED` +- **Källa:** `EmailAddress` + +En inkommande begäran innehåller ett värde som inte är en välformad e-postadress och därför inte kan tolkas till ett EmailAddress-värdeobjekt. + +> **Affärsregel:** En gästs e-postadress måste innehålla exakt ett ”@” med en icke-tom lokal del och en icke-tom domän. + +#### Diagnostik + +- **Klienten skickade en felstavad eller avkortad adress (saknat ”@”, tom lokal del eller domän).** — _ursprung:_ External — Validera adressen på klienten och jämför det skickade värdet med det förväntade formatet. + +#### Exempel + +**Publikt svar (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:email-address-malformed", + "title": "E-postadressen är ogiltig.", + "detail": "Det angivna värdet är inte en giltig e-postadress.", + "code": "EMAIL_ADDRESS_MALFORMED" +} +``` + +**Diagnostik (intern — inte avsedd för extern exponering)** + +```text +2026-07-04T13:42:18.734Z ERROR [EmailAddress] 'not-an-email' is not a valid e-mail address. error.code=EMAIL_ADDRESS_MALFORMED +``` + + + +## NightCount-fel + +Fel som utlöses när antalet nätter byggs från ett begäransvärde. + + + +### Icke-positivt antal nätter + +- **Kod:** `NIGHT_COUNT_NOT_POSITIVE` +- **Källa:** `NightCount` + +En inkommande begäran begär noll eller ett negativt antal nätter, vilket inte är en giltig vistelselängd. + +> **Affärsregel:** En bokning måste omfatta minst en natt. + +#### Diagnostik + +- **Klienten skickade ett antal nätter på noll eller lägre.** — _ursprung:_ External — Skicka ett antal nätter på en eller fler. + +#### Exempel + +**Publikt svar (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:night-count-not-positive", + "title": "Antalet nätter är ogiltigt.", + "detail": "Det begärda antalet nätter måste vara en eller fler.", + "code": "NIGHT_COUNT_NOT_POSITIVE" +} +``` + +**Diagnostik (intern — inte avsedd för extern exponering)** + +```text +2026-07-04T13:42:18.734Z ERROR [NightCount] A booking must be for at least one night, but 0 was requested. error.code=NIGHT_COUNT_NOT_POSITIVE +``` + + + +## RoomNumber-fel + +Fel som utlöses när ett rumsnummer byggs från ett element i en begäranslista. + + + +### Rumsnummer utanför intervallet + +- **Kod:** `ROOM_NUMBER_OUT_OF_RANGE` +- **Källa:** `RoomNumber` + +Ett element i begärans lista över rumsnummer ligger utanför det intervall som stöds (1-999). + +> **Affärsregel:** Ett rumsnummer måste vara mellan 1 och 999 inklusive. + +#### Diagnostik + +- **Klienten skickade ett rumsnummer på noll, ett negativt värde eller ett värde över 999.** — _ursprung:_ External — Skicka rumsnummer inom det intervall som stöds (1-999). + +#### Exempel + +**Publikt svar (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:room-number-out-of-range", + "title": "Ett rumsnummer är ogiltigt.", + "detail": "Ett av de begärda rumsnumren ligger utanför det intervall som stöds.", + "code": "ROOM_NUMBER_OUT_OF_RANGE" +} +``` + +**Diagnostik (intern — inte avsedd för extern exponering)** + +```text +2026-07-04T13:42:18.734Z ERROR [RoomNumber] Room number 1000 is outside the supported range 1-999. error.code=ROOM_NUMBER_OUT_OF_RANGE +``` + + + +## Stay-fel + +Fel som utlöses vid gemensam validering av en vistelses in- och utcheckningsdatum. + + + +### Utcheckning inte efter incheckning + +- **Kod:** `STAY_CHECKOUT_NOT_AFTER_CHECKIN` +- **Källa:** `Stay` + +Båda vistelsedatumen tolkas, men utcheckningsdatumet är före eller lika med incheckningsdatumet, så vistelsen har ingen positiv längd. Denna regel över flera fält tillämpas av fabriken Stay.Create. + +> **Affärsregel:** Utcheckningen måste vara strikt efter incheckningen. + +#### Diagnostik + +- **Klienten skickade ett utcheckningsdatum som är lika med eller tidigare än incheckningsdatumet.** — _ursprung:_ External — Skicka ett utcheckningsdatum minst en dag efter incheckningsdatumet. + +#### Exempel + +**Publikt svar (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:stay-checkout-not-after-checkin", + "title": "Vistelsedatumen är ogiltiga.", + "detail": "Utcheckningsdatumet måste vara efter incheckningsdatumet.", + "code": "STAY_CHECKOUT_NOT_AFTER_CHECKIN" +} +``` + +**Diagnostik (intern — inte avsedd för extern exponering)** + +```text +2026-07-04T13:42:18.734Z ERROR [Stay] Check-out 2026-08-10 must be strictly after check-in 2026-08-14. error.code=STAY_CHECKOUT_NOT_AFTER_CHECKIN +``` + diff --git a/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSingleHtmlRendering.verified.html b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSingleHtmlRendering.verified.html new file mode 100644 index 00000000..b05a8eed --- /dev/null +++ b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSingleHtmlRendering.verified.html @@ -0,0 +1,525 @@ + + + + + +Error Catalog + + + + + + +
+

10 documented errors

+
+ +
+ + +
+

BookingEndpoint

+

Primary-port errors raised by the booking endpoint when it binds an incoming request into a command.

+
+

BOOKING_COMMAND_INVALID #

+

Booking request invalid

+

Documentation

The endpoint could not bind the incoming request into a booking command: one or more arguments were missing or invalid. Every failure is collected under this envelope, each with its full argument path.

+
Business rule: Every required argument must be present, and every argument must convert into its value object.
+

Diagnostics

+
+ + + + + + +
Possible causeOriginAnalysis hint
The client sent a request that violates the endpoint's contract (missing or malformed arguments).ExternalRead the inner errors: each names the failing argument and the rule it violated.
+
+

Examples

+
+

Public response (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:booking-command-invalid",
+  "title": "We could not accept your booking request.",
+  "detail": "One or more details of the booking request are missing or invalid.",
+  "code": "BOOKING_COMMAND_INVALID"
+}
+

Diagnostic (internal — not for external exposure)

+
2026-07-04T13:42:18.734Z ERROR [BookingEndpoint] The booking command is invalid: one or more request arguments failed to bind. error.code=BOOKING_COMMAND_INVALID
+
+
+
+
+

BOOKING_GUEST_INVALID #

+

Booking guest invalid

+

Documentation

A guest in the request's guests list could not be bound: its first name was missing or its e-mail was malformed. Its failures are grouped under this per-element envelope, with indexed paths such as Guests[1].

+
Business rule: Each guest must have a first name, and any e-mail present must be valid.
+

Diagnostics

+
+ + + + + + +
Possible causeOriginAnalysis hint
The client sent a guest with a missing first name or a malformed e-mail address.ExternalRead the inner errors under the indexed Guests[i] path for the failing field.
+
+

Examples

+
+

Public response (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:booking-guest-invalid",
+  "title": "A guest's information is invalid.",
+  "detail": "One of the guests is missing a first name or has an invalid e-mail address.",
+  "code": "BOOKING_GUEST_INVALID"
+}
+

Diagnostic (internal — not for external exposure)

+
2026-07-04T13:42:18.734Z ERROR [BookingEndpoint] The guest is invalid: one or more of its fields failed to bind. error.code=BOOKING_GUEST_INVALID
+
+
+
+
+

BOOKING_STAY_INVALID #

+

Booking stay invalid

+

Documentation

The stay sub-object of the request could not be bound: one or both of its dates were missing or malformed. Its failures are grouped under this nested envelope, with paths prefixed by Stay.

+
Business rule: Both stay dates must be present and valid ISO dates.
+

Diagnostics

+
+ + + + + + +
Possible causeOriginAnalysis hint
The client sent a stay with a missing or malformed check-in or check-out date.ExternalRead the inner errors under the Stay path for the failing date.
+
+

Examples

+
+

Public response (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:booking-stay-invalid",
+  "title": "We could not read the stay dates.",
+  "detail": "The check-in or check-out date of the stay is missing or invalid.",
+  "code": "BOOKING_STAY_INVALID"
+}
+

Diagnostic (internal — not for external exposure)

+
2026-07-04T13:42:18.734Z ERROR [BookingEndpoint] The stay is invalid: one or more of its dates failed to bind. error.code=BOOKING_STAY_INVALID
+
+
+
+
+
+

BookingDate

+

Errors raised when parsing a booking date (check-in / check-out) from a request string.

+
+

BOOKING_DATE_MALFORMED #

+

Malformed booking date

+

Documentation

An incoming request carries a value that is not an ISO yyyy-MM-dd date, so it cannot be parsed into a BookingDate value object.

+
Business rule: A booking date must be an ISO calendar date in the yyyy-MM-dd format.
+

Diagnostics

+
+ + + + + + +
Possible causeOriginAnalysis hint
The client sent a date in a locale-specific or malformed format, or an impossible calendar date.ExternalSend dates in ISO 8601 yyyy-MM-dd form, for example 2026-08-10.
+
+

Examples

+
+

Public response (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:booking-date-malformed",
+  "title": "The date is invalid.",
+  "detail": "A booking date is not a valid ISO (yyyy-MM-dd) date.",
+  "code": "BOOKING_DATE_MALFORMED"
+}
+

Diagnostic (internal — not for external exposure)

+
2026-07-04T13:42:18.734Z ERROR [BookingDate] '2026-13-40' is not a valid ISO (yyyy-MM-dd) date. error.code=BOOKING_DATE_MALFORMED
+
+
+
+
+
+

Tag

+

Errors raised when parsing a booking tag from an element of a request list.

+
+

BOOKING_TAG_MALFORMED #

+

Malformed booking tag

+

Documentation

An element of the request's tag list is empty, too long, or contains whitespace, so it cannot be parsed into a Tag value object.

+
Business rule: A tag must be a single non-empty token of at most 32 characters, without whitespace.
+

Diagnostics

+
+ + + + + + +
Possible causeOriginAnalysis hint
The client sent a blank tag, a phrase containing spaces, or an over-long value.ExternalSend each tag as a single whitespace-free token; join multi-word tags with a hyphen.
+
+

Examples

+
+

Public response (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:booking-tag-malformed",
+  "title": "A tag is invalid.",
+  "detail": "One of the booking tags is not a valid single token.",
+  "code": "BOOKING_TAG_MALFORMED"
+}
+

Diagnostic (internal — not for external exposure)

+
2026-07-04T13:42:18.734Z ERROR [Tag] 'late checkout' is not a valid tag. error.code=BOOKING_TAG_MALFORMED
+
+
+
+
+
+

Currency

+

Errors raised when parsing a billing currency code from a request string.

+
+

CURRENCY_CODE_MALFORMED #

+

Malformed currency code

+

Documentation

An incoming request carries a value that is not a well-formed three-letter currency code, so it cannot be parsed into a Currency value object.

+
Business rule: A currency code must be exactly three upper-case ASCII letters (for example EUR).
+

Diagnostics

+
+ + + + + + +
Possible causeOriginAnalysis hint
The client sent a currency in the wrong shape (lower-case, a symbol, a name, or the wrong length).ExternalSend the ISO-4217 alphabetic code in upper case, for example USD or EUR.
+
+

Examples

+
+

Public response (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:currency-code-malformed",
+  "title": "The currency is invalid.",
+  "detail": "The billing currency code is not a valid three-letter code.",
+  "code": "CURRENCY_CODE_MALFORMED"
+}
+

Diagnostic (internal — not for external exposure)

+
2026-07-04T13:42:18.734Z ERROR [Currency] 'EURO' is not a valid three-letter currency code. error.code=CURRENCY_CODE_MALFORMED
+
+
+
+
+
+

EmailAddress

+

Errors raised when parsing a guest e-mail address from a request string.

+
+

EMAIL_ADDRESS_MALFORMED #

+

Malformed e-mail address

+

Documentation

An incoming request carries a value that is not a well-formed e-mail address, so it cannot be parsed into an EmailAddress value object.

+
Business rule: A guest e-mail address must contain a single '@' with a non-empty local part and domain.
+

Diagnostics

+
+ + + + + + +
Possible causeOriginAnalysis hint
The client sent a misspelled or truncated address (missing '@', empty local part or domain).ExternalValidate the address on the client, and compare the sent value against the expected format.
+
+

Examples

+
+

Public response (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:email-address-malformed",
+  "title": "The e-mail address is invalid.",
+  "detail": "The value provided is not a valid e-mail address.",
+  "code": "EMAIL_ADDRESS_MALFORMED"
+}
+

Diagnostic (internal — not for external exposure)

+
2026-07-04T13:42:18.734Z ERROR [EmailAddress] 'not-an-email' is not a valid e-mail address. error.code=EMAIL_ADDRESS_MALFORMED
+
+
+
+
+
+

NightCount

+

Errors raised when building the number of nights from a request value.

+
+

NIGHT_COUNT_NOT_POSITIVE #

+

Non-positive number of nights

+

Documentation

An incoming request asks for zero or a negative number of nights, which is not a valid stay length.

+
Business rule: A booking must be for at least one night.
+

Diagnostics

+
+ + + + + + +
Possible causeOriginAnalysis hint
The client sent a number of nights of zero or below.ExternalSend a number of nights of one or more.
+
+

Examples

+
+

Public response (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:night-count-not-positive",
+  "title": "The number of nights is invalid.",
+  "detail": "The requested number of nights must be one or more.",
+  "code": "NIGHT_COUNT_NOT_POSITIVE"
+}
+

Diagnostic (internal — not for external exposure)

+
2026-07-04T13:42:18.734Z ERROR [NightCount] A booking must be for at least one night, but 0 was requested. error.code=NIGHT_COUNT_NOT_POSITIVE
+
+
+
+
+
+

RoomNumber

+

Errors raised when building a room number from an element of a request list.

+
+

ROOM_NUMBER_OUT_OF_RANGE #

+

Room number out of range

+

Documentation

An element of the request's room-number list is outside the supported 1-999 range.

+
Business rule: A room number must be between 1 and 999 inclusive.
+

Diagnostics

+
+ + + + + + +
Possible causeOriginAnalysis hint
The client sent a room number of zero, a negative value, or a value above 999.ExternalSend room numbers within the supported 1-999 range.
+
+

Examples

+
+

Public response (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:room-number-out-of-range",
+  "title": "A room number is invalid.",
+  "detail": "One of the requested room numbers is outside the supported range.",
+  "code": "ROOM_NUMBER_OUT_OF_RANGE"
+}
+

Diagnostic (internal — not for external exposure)

+
2026-07-04T13:42:18.734Z ERROR [RoomNumber] Room number 1000 is outside the supported range 1-999. error.code=ROOM_NUMBER_OUT_OF_RANGE
+
+
+
+
+
+

Stay

+

Errors raised when validating a stay's check-in and check-out dates together.

+
+

STAY_CHECKOUT_NOT_AFTER_CHECKIN #

+

Check-out not after check-in

+

Documentation

Both stay dates parse, but the check-out date is on or before the check-in date, so the stay has no positive length. This cross-field rule is enforced by the Stay.Create factory.

+
Business rule: Check-out must be strictly after check-in.
+

Diagnostics

+
+ + + + + + +
Possible causeOriginAnalysis hint
The client sent a check-out date equal to or earlier than the check-in date.ExternalSend a check-out date at least one day after the check-in date.
+
+

Examples

+
+

Public response (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:stay-checkout-not-after-checkin",
+  "title": "The stay dates are invalid.",
+  "detail": "The check-out date must be after the check-in date.",
+  "code": "STAY_CHECKOUT_NOT_AFTER_CHECKIN"
+}
+

Diagnostic (internal — not for external exposure)

+
2026-07-04T13:42:18.734Z ERROR [Stay] Check-out 2026-08-10 must be strictly after check-in 2026-08-14. error.code=STAY_CHECKOUT_NOT_AFTER_CHECKIN
+
+
+
+
+
+ + + diff --git a/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSingleMarkdownRendering.verified.md b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSingleMarkdownRendering.verified.md new file mode 100644 index 00000000..1d2b0bbb --- /dev/null +++ b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSingleMarkdownRendering.verified.md @@ -0,0 +1,411 @@ +# Error Catalog + +## Table of contents + +- [BookingEndpoint errors](#src-booking-endpoint) + - [Booking request invalid](#err-booking-command-invalid) + - [Booking guest invalid](#err-booking-guest-invalid) + - [Booking stay invalid](#err-booking-stay-invalid) +- [BookingDate errors](#src-booking-date) + - [Malformed booking date](#err-booking-date-malformed) +- [Tag errors](#src-tag) + - [Malformed booking tag](#err-booking-tag-malformed) +- [Currency errors](#src-currency) + - [Malformed currency code](#err-currency-code-malformed) +- [EmailAddress errors](#src-email-address) + - [Malformed e-mail address](#err-email-address-malformed) +- [NightCount errors](#src-night-count) + - [Non-positive number of nights](#err-night-count-not-positive) +- [RoomNumber errors](#src-room-number) + - [Room number out of range](#err-room-number-out-of-range) +- [Stay errors](#src-stay) + - [Check-out not after check-in](#err-stay-checkout-not-after-checkin) + + + +## BookingEndpoint errors + +Primary-port errors raised by the booking endpoint when it binds an incoming request into a command. + + + +### Booking request invalid + +- **Code:** `BOOKING_COMMAND_INVALID` +- **Source:** `BookingEndpoint` + +The endpoint could not bind the incoming request into a booking command: one or more arguments were missing or invalid. Every failure is collected under this envelope, each with its full argument path. + +> **Business rule:** Every required argument must be present, and every argument must convert into its value object. + +#### Diagnostics + +- **The client sent a request that violates the endpoint's contract (missing or malformed arguments).** — _origin:_ External — Read the inner errors: each names the failing argument and the rule it violated. + +#### Examples + +**Public response (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:booking-command-invalid", + "title": "We could not accept your booking request.", + "detail": "One or more details of the booking request are missing or invalid.", + "code": "BOOKING_COMMAND_INVALID" +} +``` + +**Diagnostic (internal — not for external exposure)** + +```text +2026-07-04T13:42:18.734Z ERROR [BookingEndpoint] The booking command is invalid: one or more request arguments failed to bind. error.code=BOOKING_COMMAND_INVALID +``` + + + +### Booking guest invalid + +- **Code:** `BOOKING_GUEST_INVALID` +- **Source:** `BookingEndpoint` + +A guest in the request's guests list could not be bound: its first name was missing or its e-mail was malformed. Its failures are grouped under this per-element envelope, with indexed paths such as Guests[1]. + +> **Business rule:** Each guest must have a first name, and any e-mail present must be valid. + +#### Diagnostics + +- **The client sent a guest with a missing first name or a malformed e-mail address.** — _origin:_ External — Read the inner errors under the indexed Guests[i] path for the failing field. + +#### Examples + +**Public response (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:booking-guest-invalid", + "title": "A guest's information is invalid.", + "detail": "One of the guests is missing a first name or has an invalid e-mail address.", + "code": "BOOKING_GUEST_INVALID" +} +``` + +**Diagnostic (internal — not for external exposure)** + +```text +2026-07-04T13:42:18.734Z ERROR [BookingEndpoint] The guest is invalid: one or more of its fields failed to bind. error.code=BOOKING_GUEST_INVALID +``` + + + +### Booking stay invalid + +- **Code:** `BOOKING_STAY_INVALID` +- **Source:** `BookingEndpoint` + +The stay sub-object of the request could not be bound: one or both of its dates were missing or malformed. Its failures are grouped under this nested envelope, with paths prefixed by Stay. + +> **Business rule:** Both stay dates must be present and valid ISO dates. + +#### Diagnostics + +- **The client sent a stay with a missing or malformed check-in or check-out date.** — _origin:_ External — Read the inner errors under the Stay path for the failing date. + +#### Examples + +**Public response (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:booking-stay-invalid", + "title": "We could not read the stay dates.", + "detail": "The check-in or check-out date of the stay is missing or invalid.", + "code": "BOOKING_STAY_INVALID" +} +``` + +**Diagnostic (internal — not for external exposure)** + +```text +2026-07-04T13:42:18.734Z ERROR [BookingEndpoint] The stay is invalid: one or more of its dates failed to bind. error.code=BOOKING_STAY_INVALID +``` + + + +## BookingDate errors + +Errors raised when parsing a booking date (check-in / check-out) from a request string. + + + +### Malformed booking date + +- **Code:** `BOOKING_DATE_MALFORMED` +- **Source:** `BookingDate` + +An incoming request carries a value that is not an ISO yyyy-MM-dd date, so it cannot be parsed into a BookingDate value object. + +> **Business rule:** A booking date must be an ISO calendar date in the yyyy-MM-dd format. + +#### Diagnostics + +- **The client sent a date in a locale-specific or malformed format, or an impossible calendar date.** — _origin:_ External — Send dates in ISO 8601 yyyy-MM-dd form, for example 2026-08-10. + +#### Examples + +**Public response (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:booking-date-malformed", + "title": "The date is invalid.", + "detail": "A booking date is not a valid ISO (yyyy-MM-dd) date.", + "code": "BOOKING_DATE_MALFORMED" +} +``` + +**Diagnostic (internal — not for external exposure)** + +```text +2026-07-04T13:42:18.734Z ERROR [BookingDate] '2026-13-40' is not a valid ISO (yyyy-MM-dd) date. error.code=BOOKING_DATE_MALFORMED +``` + + + +## Tag errors + +Errors raised when parsing a booking tag from an element of a request list. + + + +### Malformed booking tag + +- **Code:** `BOOKING_TAG_MALFORMED` +- **Source:** `Tag` + +An element of the request's tag list is empty, too long, or contains whitespace, so it cannot be parsed into a Tag value object. + +> **Business rule:** A tag must be a single non-empty token of at most 32 characters, without whitespace. + +#### Diagnostics + +- **The client sent a blank tag, a phrase containing spaces, or an over-long value.** — _origin:_ External — Send each tag as a single whitespace-free token; join multi-word tags with a hyphen. + +#### Examples + +**Public response (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:booking-tag-malformed", + "title": "A tag is invalid.", + "detail": "One of the booking tags is not a valid single token.", + "code": "BOOKING_TAG_MALFORMED" +} +``` + +**Diagnostic (internal — not for external exposure)** + +```text +2026-07-04T13:42:18.734Z ERROR [Tag] 'late checkout' is not a valid tag. error.code=BOOKING_TAG_MALFORMED +``` + + + +## Currency errors + +Errors raised when parsing a billing currency code from a request string. + + + +### Malformed currency code + +- **Code:** `CURRENCY_CODE_MALFORMED` +- **Source:** `Currency` + +An incoming request carries a value that is not a well-formed three-letter currency code, so it cannot be parsed into a Currency value object. + +> **Business rule:** A currency code must be exactly three upper-case ASCII letters (for example EUR). + +#### Diagnostics + +- **The client sent a currency in the wrong shape (lower-case, a symbol, a name, or the wrong length).** — _origin:_ External — Send the ISO-4217 alphabetic code in upper case, for example USD or EUR. + +#### Examples + +**Public response (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:currency-code-malformed", + "title": "The currency is invalid.", + "detail": "The billing currency code is not a valid three-letter code.", + "code": "CURRENCY_CODE_MALFORMED" +} +``` + +**Diagnostic (internal — not for external exposure)** + +```text +2026-07-04T13:42:18.734Z ERROR [Currency] 'EURO' is not a valid three-letter currency code. error.code=CURRENCY_CODE_MALFORMED +``` + + + +## EmailAddress errors + +Errors raised when parsing a guest e-mail address from a request string. + + + +### Malformed e-mail address + +- **Code:** `EMAIL_ADDRESS_MALFORMED` +- **Source:** `EmailAddress` + +An incoming request carries a value that is not a well-formed e-mail address, so it cannot be parsed into an EmailAddress value object. + +> **Business rule:** A guest e-mail address must contain a single '@' with a non-empty local part and domain. + +#### Diagnostics + +- **The client sent a misspelled or truncated address (missing '@', empty local part or domain).** — _origin:_ External — Validate the address on the client, and compare the sent value against the expected format. + +#### Examples + +**Public response (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:email-address-malformed", + "title": "The e-mail address is invalid.", + "detail": "The value provided is not a valid e-mail address.", + "code": "EMAIL_ADDRESS_MALFORMED" +} +``` + +**Diagnostic (internal — not for external exposure)** + +```text +2026-07-04T13:42:18.734Z ERROR [EmailAddress] 'not-an-email' is not a valid e-mail address. error.code=EMAIL_ADDRESS_MALFORMED +``` + + + +## NightCount errors + +Errors raised when building the number of nights from a request value. + + + +### Non-positive number of nights + +- **Code:** `NIGHT_COUNT_NOT_POSITIVE` +- **Source:** `NightCount` + +An incoming request asks for zero or a negative number of nights, which is not a valid stay length. + +> **Business rule:** A booking must be for at least one night. + +#### Diagnostics + +- **The client sent a number of nights of zero or below.** — _origin:_ External — Send a number of nights of one or more. + +#### Examples + +**Public response (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:night-count-not-positive", + "title": "The number of nights is invalid.", + "detail": "The requested number of nights must be one or more.", + "code": "NIGHT_COUNT_NOT_POSITIVE" +} +``` + +**Diagnostic (internal — not for external exposure)** + +```text +2026-07-04T13:42:18.734Z ERROR [NightCount] A booking must be for at least one night, but 0 was requested. error.code=NIGHT_COUNT_NOT_POSITIVE +``` + + + +## RoomNumber errors + +Errors raised when building a room number from an element of a request list. + + + +### Room number out of range + +- **Code:** `ROOM_NUMBER_OUT_OF_RANGE` +- **Source:** `RoomNumber` + +An element of the request's room-number list is outside the supported 1-999 range. + +> **Business rule:** A room number must be between 1 and 999 inclusive. + +#### Diagnostics + +- **The client sent a room number of zero, a negative value, or a value above 999.** — _origin:_ External — Send room numbers within the supported 1-999 range. + +#### Examples + +**Public response (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:room-number-out-of-range", + "title": "A room number is invalid.", + "detail": "One of the requested room numbers is outside the supported range.", + "code": "ROOM_NUMBER_OUT_OF_RANGE" +} +``` + +**Diagnostic (internal — not for external exposure)** + +```text +2026-07-04T13:42:18.734Z ERROR [RoomNumber] Room number 1000 is outside the supported range 1-999. error.code=ROOM_NUMBER_OUT_OF_RANGE +``` + + + +## Stay errors + +Errors raised when validating a stay's check-in and check-out dates together. + + + +### Check-out not after check-in + +- **Code:** `STAY_CHECKOUT_NOT_AFTER_CHECKIN` +- **Source:** `Stay` + +Both stay dates parse, but the check-out date is on or before the check-in date, so the stay has no positive length. This cross-field rule is enforced by the Stay.Create factory. + +> **Business rule:** Check-out must be strictly after check-in. + +#### Diagnostics + +- **The client sent a check-out date equal to or earlier than the check-in date.** — _origin:_ External — Send a check-out date at least one day after the check-in date. + +#### Examples + +**Public response (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:stay-checkout-not-after-checkin", + "title": "The stay dates are invalid.", + "detail": "The check-out date must be after the check-in date.", + "code": "STAY_CHECKOUT_NOT_AFTER_CHECKIN" +} +``` + +**Diagnostic (internal — not for external exposure)** + +```text +2026-07-04T13:42:18.734Z ERROR [Stay] Check-out 2026-08-10 must be strictly after check-in 2026-08-14. error.code=STAY_CHECKOUT_NOT_AFTER_CHECKIN +``` + diff --git a/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitHtmlRendering#assets-search-index.verified.json b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitHtmlRendering#assets-search-index.verified.json new file mode 100644 index 00000000..0063a512 --- /dev/null +++ b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitHtmlRendering#assets-search-index.verified.json @@ -0,0 +1,82 @@ +[ + { + "code": "BOOKING_COMMAND_INVALID", + "title": "Booking request invalid", + "summary": "We could not accept your booking request.", + "source": "BookingEndpoint", + "href": "errors/BOOKING_COMMAND_INVALID.html", + "text": "booking_command_invalid booking request invalid the endpoint could not bind the incoming request into a booking command: one or more arguments were missing or invalid. every failure is collected under this envelope, each with its full argument path. every required argument must be present, and every argument must convert into its value object. bookingendpoint we could not accept your booking request. one or more details of the booking request are missing or invalid. the booking command is invalid: one or more request arguments failed to bind. the client sent a request that violates the endpoint's contract (missing or malformed arguments). read the inner errors: each names the failing argument and the rule it violated." + }, + { + "code": "BOOKING_DATE_MALFORMED", + "title": "Malformed booking date", + "summary": "The date is invalid.", + "source": "BookingDate", + "href": "errors/BOOKING_DATE_MALFORMED.html", + "text": "booking_date_malformed malformed booking date an incoming request carries a value that is not an iso yyyy-mm-dd date, so it cannot be parsed into a bookingdate value object. a booking date must be an iso calendar date in the yyyy-mm-dd format. bookingdate the date is invalid. a booking date is not a valid iso (yyyy-mm-dd) date. '2026-13-40' is not a valid iso (yyyy-mm-dd) date. the client sent a date in a locale-specific or malformed format, or an impossible calendar date. send dates in iso 8601 yyyy-mm-dd form, for example 2026-08-10." + }, + { + "code": "BOOKING_GUEST_INVALID", + "title": "Booking guest invalid", + "summary": "A guest's information is invalid.", + "source": "BookingEndpoint", + "href": "errors/BOOKING_GUEST_INVALID.html", + "text": "booking_guest_invalid booking guest invalid a guest in the request's guests list could not be bound: its first name was missing or its e-mail was malformed. its failures are grouped under this per-element envelope, with indexed paths such as guests[1]. each guest must have a first name, and any e-mail present must be valid. bookingendpoint a guest's information is invalid. one of the guests is missing a first name or has an invalid e-mail address. the guest is invalid: one or more of its fields failed to bind. the client sent a guest with a missing first name or a malformed e-mail address. read the inner errors under the indexed guests[i] path for the failing field." + }, + { + "code": "BOOKING_STAY_INVALID", + "title": "Booking stay invalid", + "summary": "We could not read the stay dates.", + "source": "BookingEndpoint", + "href": "errors/BOOKING_STAY_INVALID.html", + "text": "booking_stay_invalid booking stay invalid the stay sub-object of the request could not be bound: one or both of its dates were missing or malformed. its failures are grouped under this nested envelope, with paths prefixed by stay. both stay dates must be present and valid iso dates. bookingendpoint we could not read the stay dates. the check-in or check-out date of the stay is missing or invalid. the stay is invalid: one or more of its dates failed to bind. the client sent a stay with a missing or malformed check-in or check-out date. read the inner errors under the stay path for the failing date." + }, + { + "code": "BOOKING_TAG_MALFORMED", + "title": "Malformed booking tag", + "summary": "A tag is invalid.", + "source": "Tag", + "href": "errors/BOOKING_TAG_MALFORMED.html", + "text": "booking_tag_malformed malformed booking tag an element of the request's tag list is empty, too long, or contains whitespace, so it cannot be parsed into a tag value object. a tag must be a single non-empty token of at most 32 characters, without whitespace. tag a tag is invalid. one of the booking tags is not a valid single token. 'late checkout' is not a valid tag. the client sent a blank tag, a phrase containing spaces, or an over-long value. send each tag as a single whitespace-free token; join multi-word tags with a hyphen." + }, + { + "code": "CURRENCY_CODE_MALFORMED", + "title": "Malformed currency code", + "summary": "The currency is invalid.", + "source": "Currency", + "href": "errors/CURRENCY_CODE_MALFORMED.html", + "text": "currency_code_malformed malformed currency code an incoming request carries a value that is not a well-formed three-letter currency code, so it cannot be parsed into a currency value object. a currency code must be exactly three upper-case ascii letters (for example eur). currency the currency is invalid. the billing currency code is not a valid three-letter code. 'euro' is not a valid three-letter currency code. the client sent a currency in the wrong shape (lower-case, a symbol, a name, or the wrong length). send the iso-4217 alphabetic code in upper case, for example usd or eur." + }, + { + "code": "EMAIL_ADDRESS_MALFORMED", + "title": "Malformed e-mail address", + "summary": "The e-mail address is invalid.", + "source": "EmailAddress", + "href": "errors/EMAIL_ADDRESS_MALFORMED.html", + "text": "email_address_malformed malformed e-mail address an incoming request carries a value that is not a well-formed e-mail address, so it cannot be parsed into an emailaddress value object. a guest e-mail address must contain a single '@' with a non-empty local part and domain. emailaddress the e-mail address is invalid. the value provided is not a valid e-mail address. 'not-an-email' is not a valid e-mail address. the client sent a misspelled or truncated address (missing '@', empty local part or domain). validate the address on the client, and compare the sent value against the expected format." + }, + { + "code": "NIGHT_COUNT_NOT_POSITIVE", + "title": "Non-positive number of nights", + "summary": "The number of nights is invalid.", + "source": "NightCount", + "href": "errors/NIGHT_COUNT_NOT_POSITIVE.html", + "text": "night_count_not_positive non-positive number of nights an incoming request asks for zero or a negative number of nights, which is not a valid stay length. a booking must be for at least one night. nightcount the number of nights is invalid. the requested number of nights must be one or more. a booking must be for at least one night, but 0 was requested. the client sent a number of nights of zero or below. send a number of nights of one or more." + }, + { + "code": "ROOM_NUMBER_OUT_OF_RANGE", + "title": "Room number out of range", + "summary": "A room number is invalid.", + "source": "RoomNumber", + "href": "errors/ROOM_NUMBER_OUT_OF_RANGE.html", + "text": "room_number_out_of_range room number out of range an element of the request's room-number list is outside the supported 1-999 range. a room number must be between 1 and 999 inclusive. roomnumber a room number is invalid. one of the requested room numbers is outside the supported range. room number 1000 is outside the supported range 1-999. the client sent a room number of zero, a negative value, or a value above 999. send room numbers within the supported 1-999 range." + }, + { + "code": "STAY_CHECKOUT_NOT_AFTER_CHECKIN", + "title": "Check-out not after check-in", + "summary": "The stay dates are invalid.", + "source": "Stay", + "href": "errors/STAY_CHECKOUT_NOT_AFTER_CHECKIN.html", + "text": "stay_checkout_not_after_checkin check-out not after check-in both stay dates parse, but the check-out date is on or before the check-in date, so the stay has no positive length. this cross-field rule is enforced by the stay.create factory. check-out must be strictly after check-in. stay the stay dates are invalid. the check-out date must be after the check-in date. check-out 2026-08-10 must be strictly after check-in 2026-08-14. the client sent a check-out date equal to or earlier than the check-in date. send a check-out date at least one day after the check-in date." + } +] diff --git a/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitHtmlRendering#errors-BOOKING_COMMAND_INVALID.verified.html b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitHtmlRendering#errors-BOOKING_COMMAND_INVALID.verified.html new file mode 100644 index 00000000..90126e5e --- /dev/null +++ b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitHtmlRendering#errors-BOOKING_COMMAND_INVALID.verified.html @@ -0,0 +1,182 @@ + + + + + +BOOKING_COMMAND_INVALID — Error Catalog + + + + + + +
+

← Back to the catalog

+
+

BOOKING_COMMAND_INVALID #

+

Booking request invalid

+

Documentation

The endpoint could not bind the incoming request into a booking command: one or more arguments were missing or invalid. Every failure is collected under this envelope, each with its full argument path.

+
Business rule: Every required argument must be present, and every argument must convert into its value object.
+

Diagnostics

+
+ + + + + + +
Possible causeOriginAnalysis hint
The client sent a request that violates the endpoint's contract (missing or malformed arguments).ExternalRead the inner errors: each names the failing argument and the rule it violated.
+
+

Examples

+
+

Public response (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:booking-command-invalid",
+  "title": "We could not accept your booking request.",
+  "detail": "One or more details of the booking request are missing or invalid.",
+  "code": "BOOKING_COMMAND_INVALID"
+}
+

Diagnostic (internal — not for external exposure)

+
2026-07-04T13:42:18.734Z ERROR [BookingEndpoint] The booking command is invalid: one or more request arguments failed to bind. error.code=BOOKING_COMMAND_INVALID
+
+
+
+
+ + + diff --git a/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitHtmlRendering#errors-BOOKING_DATE_MALFORMED.verified.html b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitHtmlRendering#errors-BOOKING_DATE_MALFORMED.verified.html new file mode 100644 index 00000000..f330ac87 --- /dev/null +++ b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitHtmlRendering#errors-BOOKING_DATE_MALFORMED.verified.html @@ -0,0 +1,182 @@ + + + + + +BOOKING_DATE_MALFORMED — Error Catalog + + + + + + +
+

← Back to the catalog

+
+

BOOKING_DATE_MALFORMED #

+

Malformed booking date

+

Documentation

An incoming request carries a value that is not an ISO yyyy-MM-dd date, so it cannot be parsed into a BookingDate value object.

+
Business rule: A booking date must be an ISO calendar date in the yyyy-MM-dd format.
+

Diagnostics

+
+ + + + + + +
Possible causeOriginAnalysis hint
The client sent a date in a locale-specific or malformed format, or an impossible calendar date.ExternalSend dates in ISO 8601 yyyy-MM-dd form, for example 2026-08-10.
+
+

Examples

+
+

Public response (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:booking-date-malformed",
+  "title": "The date is invalid.",
+  "detail": "A booking date is not a valid ISO (yyyy-MM-dd) date.",
+  "code": "BOOKING_DATE_MALFORMED"
+}
+

Diagnostic (internal — not for external exposure)

+
2026-07-04T13:42:18.734Z ERROR [BookingDate] '2026-13-40' is not a valid ISO (yyyy-MM-dd) date. error.code=BOOKING_DATE_MALFORMED
+
+
+
+
+ + + diff --git a/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitHtmlRendering#errors-BOOKING_GUEST_INVALID.verified.html b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitHtmlRendering#errors-BOOKING_GUEST_INVALID.verified.html new file mode 100644 index 00000000..fae71458 --- /dev/null +++ b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitHtmlRendering#errors-BOOKING_GUEST_INVALID.verified.html @@ -0,0 +1,182 @@ + + + + + +BOOKING_GUEST_INVALID — Error Catalog + + + + + + +
+

← Back to the catalog

+
+

BOOKING_GUEST_INVALID #

+

Booking guest invalid

+

Documentation

A guest in the request's guests list could not be bound: its first name was missing or its e-mail was malformed. Its failures are grouped under this per-element envelope, with indexed paths such as Guests[1].

+
Business rule: Each guest must have a first name, and any e-mail present must be valid.
+

Diagnostics

+
+ + + + + + +
Possible causeOriginAnalysis hint
The client sent a guest with a missing first name or a malformed e-mail address.ExternalRead the inner errors under the indexed Guests[i] path for the failing field.
+
+

Examples

+
+

Public response (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:booking-guest-invalid",
+  "title": "A guest's information is invalid.",
+  "detail": "One of the guests is missing a first name or has an invalid e-mail address.",
+  "code": "BOOKING_GUEST_INVALID"
+}
+

Diagnostic (internal — not for external exposure)

+
2026-07-04T13:42:18.734Z ERROR [BookingEndpoint] The guest is invalid: one or more of its fields failed to bind. error.code=BOOKING_GUEST_INVALID
+
+
+
+
+ + + diff --git a/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitHtmlRendering#errors-BOOKING_STAY_INVALID.verified.html b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitHtmlRendering#errors-BOOKING_STAY_INVALID.verified.html new file mode 100644 index 00000000..8d361acb --- /dev/null +++ b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitHtmlRendering#errors-BOOKING_STAY_INVALID.verified.html @@ -0,0 +1,182 @@ + + + + + +BOOKING_STAY_INVALID — Error Catalog + + + + + + +
+

← Back to the catalog

+
+

BOOKING_STAY_INVALID #

+

Booking stay invalid

+

Documentation

The stay sub-object of the request could not be bound: one or both of its dates were missing or malformed. Its failures are grouped under this nested envelope, with paths prefixed by Stay.

+
Business rule: Both stay dates must be present and valid ISO dates.
+

Diagnostics

+
+ + + + + + +
Possible causeOriginAnalysis hint
The client sent a stay with a missing or malformed check-in or check-out date.ExternalRead the inner errors under the Stay path for the failing date.
+
+

Examples

+
+

Public response (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:booking-stay-invalid",
+  "title": "We could not read the stay dates.",
+  "detail": "The check-in or check-out date of the stay is missing or invalid.",
+  "code": "BOOKING_STAY_INVALID"
+}
+

Diagnostic (internal — not for external exposure)

+
2026-07-04T13:42:18.734Z ERROR [BookingEndpoint] The stay is invalid: one or more of its dates failed to bind. error.code=BOOKING_STAY_INVALID
+
+
+
+
+ + + diff --git a/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitHtmlRendering#errors-BOOKING_TAG_MALFORMED.verified.html b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitHtmlRendering#errors-BOOKING_TAG_MALFORMED.verified.html new file mode 100644 index 00000000..5139c33a --- /dev/null +++ b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitHtmlRendering#errors-BOOKING_TAG_MALFORMED.verified.html @@ -0,0 +1,182 @@ + + + + + +BOOKING_TAG_MALFORMED — Error Catalog + + + + + + +
+

← Back to the catalog

+
+

BOOKING_TAG_MALFORMED #

+

Malformed booking tag

+

Documentation

An element of the request's tag list is empty, too long, or contains whitespace, so it cannot be parsed into a Tag value object.

+
Business rule: A tag must be a single non-empty token of at most 32 characters, without whitespace.
+

Diagnostics

+
+ + + + + + +
Possible causeOriginAnalysis hint
The client sent a blank tag, a phrase containing spaces, or an over-long value.ExternalSend each tag as a single whitespace-free token; join multi-word tags with a hyphen.
+
+

Examples

+
+

Public response (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:booking-tag-malformed",
+  "title": "A tag is invalid.",
+  "detail": "One of the booking tags is not a valid single token.",
+  "code": "BOOKING_TAG_MALFORMED"
+}
+

Diagnostic (internal — not for external exposure)

+
2026-07-04T13:42:18.734Z ERROR [Tag] 'late checkout' is not a valid tag. error.code=BOOKING_TAG_MALFORMED
+
+
+
+
+ + + diff --git a/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitHtmlRendering#errors-CURRENCY_CODE_MALFORMED.verified.html b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitHtmlRendering#errors-CURRENCY_CODE_MALFORMED.verified.html new file mode 100644 index 00000000..7cbb44d6 --- /dev/null +++ b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitHtmlRendering#errors-CURRENCY_CODE_MALFORMED.verified.html @@ -0,0 +1,182 @@ + + + + + +CURRENCY_CODE_MALFORMED — Error Catalog + + + + + + +
+

← Back to the catalog

+
+

CURRENCY_CODE_MALFORMED #

+

Malformed currency code

+

Documentation

An incoming request carries a value that is not a well-formed three-letter currency code, so it cannot be parsed into a Currency value object.

+
Business rule: A currency code must be exactly three upper-case ASCII letters (for example EUR).
+

Diagnostics

+
+ + + + + + +
Possible causeOriginAnalysis hint
The client sent a currency in the wrong shape (lower-case, a symbol, a name, or the wrong length).ExternalSend the ISO-4217 alphabetic code in upper case, for example USD or EUR.
+
+

Examples

+
+

Public response (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:currency-code-malformed",
+  "title": "The currency is invalid.",
+  "detail": "The billing currency code is not a valid three-letter code.",
+  "code": "CURRENCY_CODE_MALFORMED"
+}
+

Diagnostic (internal — not for external exposure)

+
2026-07-04T13:42:18.734Z ERROR [Currency] 'EURO' is not a valid three-letter currency code. error.code=CURRENCY_CODE_MALFORMED
+
+
+
+
+ + + diff --git a/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitHtmlRendering#errors-EMAIL_ADDRESS_MALFORMED.verified.html b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitHtmlRendering#errors-EMAIL_ADDRESS_MALFORMED.verified.html new file mode 100644 index 00000000..0a85ce89 --- /dev/null +++ b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitHtmlRendering#errors-EMAIL_ADDRESS_MALFORMED.verified.html @@ -0,0 +1,182 @@ + + + + + +EMAIL_ADDRESS_MALFORMED — Error Catalog + + + + + + +
+

← Back to the catalog

+
+

EMAIL_ADDRESS_MALFORMED #

+

Malformed e-mail address

+

Documentation

An incoming request carries a value that is not a well-formed e-mail address, so it cannot be parsed into an EmailAddress value object.

+
Business rule: A guest e-mail address must contain a single '@' with a non-empty local part and domain.
+

Diagnostics

+
+ + + + + + +
Possible causeOriginAnalysis hint
The client sent a misspelled or truncated address (missing '@', empty local part or domain).ExternalValidate the address on the client, and compare the sent value against the expected format.
+
+

Examples

+
+

Public response (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:email-address-malformed",
+  "title": "The e-mail address is invalid.",
+  "detail": "The value provided is not a valid e-mail address.",
+  "code": "EMAIL_ADDRESS_MALFORMED"
+}
+

Diagnostic (internal — not for external exposure)

+
2026-07-04T13:42:18.734Z ERROR [EmailAddress] 'not-an-email' is not a valid e-mail address. error.code=EMAIL_ADDRESS_MALFORMED
+
+
+
+
+ + + diff --git a/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitHtmlRendering#errors-NIGHT_COUNT_NOT_POSITIVE.verified.html b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitHtmlRendering#errors-NIGHT_COUNT_NOT_POSITIVE.verified.html new file mode 100644 index 00000000..773cb118 --- /dev/null +++ b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitHtmlRendering#errors-NIGHT_COUNT_NOT_POSITIVE.verified.html @@ -0,0 +1,182 @@ + + + + + +NIGHT_COUNT_NOT_POSITIVE — Error Catalog + + + + + + +
+

← Back to the catalog

+
+

NIGHT_COUNT_NOT_POSITIVE #

+

Non-positive number of nights

+

Documentation

An incoming request asks for zero or a negative number of nights, which is not a valid stay length.

+
Business rule: A booking must be for at least one night.
+

Diagnostics

+
+ + + + + + +
Possible causeOriginAnalysis hint
The client sent a number of nights of zero or below.ExternalSend a number of nights of one or more.
+
+

Examples

+
+

Public response (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:night-count-not-positive",
+  "title": "The number of nights is invalid.",
+  "detail": "The requested number of nights must be one or more.",
+  "code": "NIGHT_COUNT_NOT_POSITIVE"
+}
+

Diagnostic (internal — not for external exposure)

+
2026-07-04T13:42:18.734Z ERROR [NightCount] A booking must be for at least one night, but 0 was requested. error.code=NIGHT_COUNT_NOT_POSITIVE
+
+
+
+
+ + + diff --git a/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitHtmlRendering#errors-ROOM_NUMBER_OUT_OF_RANGE.verified.html b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitHtmlRendering#errors-ROOM_NUMBER_OUT_OF_RANGE.verified.html new file mode 100644 index 00000000..104282ba --- /dev/null +++ b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitHtmlRendering#errors-ROOM_NUMBER_OUT_OF_RANGE.verified.html @@ -0,0 +1,182 @@ + + + + + +ROOM_NUMBER_OUT_OF_RANGE — Error Catalog + + + + + + +
+

← Back to the catalog

+
+

ROOM_NUMBER_OUT_OF_RANGE #

+

Room number out of range

+

Documentation

An element of the request's room-number list is outside the supported 1-999 range.

+
Business rule: A room number must be between 1 and 999 inclusive.
+

Diagnostics

+
+ + + + + + +
Possible causeOriginAnalysis hint
The client sent a room number of zero, a negative value, or a value above 999.ExternalSend room numbers within the supported 1-999 range.
+
+

Examples

+
+

Public response (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:room-number-out-of-range",
+  "title": "A room number is invalid.",
+  "detail": "One of the requested room numbers is outside the supported range.",
+  "code": "ROOM_NUMBER_OUT_OF_RANGE"
+}
+

Diagnostic (internal — not for external exposure)

+
2026-07-04T13:42:18.734Z ERROR [RoomNumber] Room number 1000 is outside the supported range 1-999. error.code=ROOM_NUMBER_OUT_OF_RANGE
+
+
+
+
+ + + diff --git a/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitHtmlRendering#errors-STAY_CHECKOUT_NOT_AFTER_CHECKIN.verified.html b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitHtmlRendering#errors-STAY_CHECKOUT_NOT_AFTER_CHECKIN.verified.html new file mode 100644 index 00000000..9e6a2366 --- /dev/null +++ b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitHtmlRendering#errors-STAY_CHECKOUT_NOT_AFTER_CHECKIN.verified.html @@ -0,0 +1,182 @@ + + + + + +STAY_CHECKOUT_NOT_AFTER_CHECKIN — Error Catalog + + + + + + +
+

← Back to the catalog

+
+

STAY_CHECKOUT_NOT_AFTER_CHECKIN #

+

Check-out not after check-in

+

Documentation

Both stay dates parse, but the check-out date is on or before the check-in date, so the stay has no positive length. This cross-field rule is enforced by the Stay.Create factory.

+
Business rule: Check-out must be strictly after check-in.
+

Diagnostics

+
+ + + + + + +
Possible causeOriginAnalysis hint
The client sent a check-out date equal to or earlier than the check-in date.ExternalSend a check-out date at least one day after the check-in date.
+
+

Examples

+
+

Public response (RFC 9457)

+
{
+  "type": "urn:problem:booking-service:stay-checkout-not-after-checkin",
+  "title": "The stay dates are invalid.",
+  "detail": "The check-out date must be after the check-in date.",
+  "code": "STAY_CHECKOUT_NOT_AFTER_CHECKIN"
+}
+

Diagnostic (internal — not for external exposure)

+
2026-07-04T13:42:18.734Z ERROR [Stay] Check-out 2026-08-10 must be strictly after check-in 2026-08-14. error.code=STAY_CHECKOUT_NOT_AFTER_CHECKIN
+
+
+
+
+ + + diff --git a/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitHtmlRendering#index.verified.html b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitHtmlRendering#index.verified.html new file mode 100644 index 00000000..02e843ef --- /dev/null +++ b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitHtmlRendering#index.verified.html @@ -0,0 +1,211 @@ + + + + + +Error Catalog + + + + + + +
+

10 documented errors

+
+ +
+ + +
+ + + diff --git a/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#README.verified.md b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#README.verified.md new file mode 100644 index 00000000..5f464923 --- /dev/null +++ b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#README.verified.md @@ -0,0 +1,20 @@ +# Error Catalog + +- [BookingEndpoint errors](./booking-endpoint-errors.md) + - [Booking request invalid](./booking-command-invalid.md) + - [Booking guest invalid](./booking-guest-invalid.md) + - [Booking stay invalid](./booking-stay-invalid.md) +- [BookingDate errors](./booking-date-errors.md) + - [Malformed booking date](./booking-date-malformed.md) +- [Tag errors](./tag-errors.md) + - [Malformed booking tag](./booking-tag-malformed.md) +- [Currency errors](./currency-errors.md) + - [Malformed currency code](./currency-code-malformed.md) +- [EmailAddress errors](./email-address-errors.md) + - [Malformed e-mail address](./email-address-malformed.md) +- [NightCount errors](./night-count-errors.md) + - [Non-positive number of nights](./night-count-not-positive.md) +- [RoomNumber errors](./room-number-errors.md) + - [Room number out of range](./room-number-out-of-range.md) +- [Stay errors](./stay-errors.md) + - [Check-out not after check-in](./stay-checkout-not-after-checkin.md) diff --git a/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#booking-command-invalid.verified.md b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#booking-command-invalid.verified.md new file mode 100644 index 00000000..57b0da2b --- /dev/null +++ b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#booking-command-invalid.verified.md @@ -0,0 +1,32 @@ +# Booking request invalid + +- **Code:** `BOOKING_COMMAND_INVALID` +- **Source:** `BookingEndpoint` + +The endpoint could not bind the incoming request into a booking command: one or more arguments were missing or invalid. Every failure is collected under this envelope, each with its full argument path. + +> **Business rule:** Every required argument must be present, and every argument must convert into its value object. + +## Diagnostics + +- **The client sent a request that violates the endpoint's contract (missing or malformed arguments).** — _origin:_ External — Read the inner errors: each names the failing argument and the rule it violated. + +## Examples + +**Public response (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:booking-command-invalid", + "title": "We could not accept your booking request.", + "detail": "One or more details of the booking request are missing or invalid.", + "code": "BOOKING_COMMAND_INVALID" +} +``` + +**Diagnostic (internal — not for external exposure)** + +```text +2026-07-04T13:42:18.734Z ERROR [BookingEndpoint] The booking command is invalid: one or more request arguments failed to bind. error.code=BOOKING_COMMAND_INVALID +``` + diff --git a/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#booking-date-errors.verified.md b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#booking-date-errors.verified.md new file mode 100644 index 00000000..3c65e6b9 --- /dev/null +++ b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#booking-date-errors.verified.md @@ -0,0 +1,5 @@ +# BookingDate errors + +Errors raised when parsing a booking date (check-in / check-out) from a request string. + +- [Malformed booking date](./booking-date-malformed.md) diff --git a/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#booking-date-malformed.verified.md b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#booking-date-malformed.verified.md new file mode 100644 index 00000000..7ee6c184 --- /dev/null +++ b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#booking-date-malformed.verified.md @@ -0,0 +1,32 @@ +# Malformed booking date + +- **Code:** `BOOKING_DATE_MALFORMED` +- **Source:** `BookingDate` + +An incoming request carries a value that is not an ISO yyyy-MM-dd date, so it cannot be parsed into a BookingDate value object. + +> **Business rule:** A booking date must be an ISO calendar date in the yyyy-MM-dd format. + +## Diagnostics + +- **The client sent a date in a locale-specific or malformed format, or an impossible calendar date.** — _origin:_ External — Send dates in ISO 8601 yyyy-MM-dd form, for example 2026-08-10. + +## Examples + +**Public response (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:booking-date-malformed", + "title": "The date is invalid.", + "detail": "A booking date is not a valid ISO (yyyy-MM-dd) date.", + "code": "BOOKING_DATE_MALFORMED" +} +``` + +**Diagnostic (internal — not for external exposure)** + +```text +2026-07-04T13:42:18.734Z ERROR [BookingDate] '2026-13-40' is not a valid ISO (yyyy-MM-dd) date. error.code=BOOKING_DATE_MALFORMED +``` + diff --git a/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#booking-endpoint-errors.verified.md b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#booking-endpoint-errors.verified.md new file mode 100644 index 00000000..6ad340dc --- /dev/null +++ b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#booking-endpoint-errors.verified.md @@ -0,0 +1,7 @@ +# BookingEndpoint errors + +Primary-port errors raised by the booking endpoint when it binds an incoming request into a command. + +- [Booking request invalid](./booking-command-invalid.md) +- [Booking guest invalid](./booking-guest-invalid.md) +- [Booking stay invalid](./booking-stay-invalid.md) diff --git a/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#booking-guest-invalid.verified.md b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#booking-guest-invalid.verified.md new file mode 100644 index 00000000..99615703 --- /dev/null +++ b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#booking-guest-invalid.verified.md @@ -0,0 +1,32 @@ +# Booking guest invalid + +- **Code:** `BOOKING_GUEST_INVALID` +- **Source:** `BookingEndpoint` + +A guest in the request's guests list could not be bound: its first name was missing or its e-mail was malformed. Its failures are grouped under this per-element envelope, with indexed paths such as Guests[1]. + +> **Business rule:** Each guest must have a first name, and any e-mail present must be valid. + +## Diagnostics + +- **The client sent a guest with a missing first name or a malformed e-mail address.** — _origin:_ External — Read the inner errors under the indexed Guests[i] path for the failing field. + +## Examples + +**Public response (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:booking-guest-invalid", + "title": "A guest's information is invalid.", + "detail": "One of the guests is missing a first name or has an invalid e-mail address.", + "code": "BOOKING_GUEST_INVALID" +} +``` + +**Diagnostic (internal — not for external exposure)** + +```text +2026-07-04T13:42:18.734Z ERROR [BookingEndpoint] The guest is invalid: one or more of its fields failed to bind. error.code=BOOKING_GUEST_INVALID +``` + diff --git a/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#booking-stay-invalid.verified.md b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#booking-stay-invalid.verified.md new file mode 100644 index 00000000..84b2546e --- /dev/null +++ b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#booking-stay-invalid.verified.md @@ -0,0 +1,32 @@ +# Booking stay invalid + +- **Code:** `BOOKING_STAY_INVALID` +- **Source:** `BookingEndpoint` + +The stay sub-object of the request could not be bound: one or both of its dates were missing or malformed. Its failures are grouped under this nested envelope, with paths prefixed by Stay. + +> **Business rule:** Both stay dates must be present and valid ISO dates. + +## Diagnostics + +- **The client sent a stay with a missing or malformed check-in or check-out date.** — _origin:_ External — Read the inner errors under the Stay path for the failing date. + +## Examples + +**Public response (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:booking-stay-invalid", + "title": "We could not read the stay dates.", + "detail": "The check-in or check-out date of the stay is missing or invalid.", + "code": "BOOKING_STAY_INVALID" +} +``` + +**Diagnostic (internal — not for external exposure)** + +```text +2026-07-04T13:42:18.734Z ERROR [BookingEndpoint] The stay is invalid: one or more of its dates failed to bind. error.code=BOOKING_STAY_INVALID +``` + diff --git a/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#booking-tag-malformed.verified.md b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#booking-tag-malformed.verified.md new file mode 100644 index 00000000..16c05037 --- /dev/null +++ b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#booking-tag-malformed.verified.md @@ -0,0 +1,32 @@ +# Malformed booking tag + +- **Code:** `BOOKING_TAG_MALFORMED` +- **Source:** `Tag` + +An element of the request's tag list is empty, too long, or contains whitespace, so it cannot be parsed into a Tag value object. + +> **Business rule:** A tag must be a single non-empty token of at most 32 characters, without whitespace. + +## Diagnostics + +- **The client sent a blank tag, a phrase containing spaces, or an over-long value.** — _origin:_ External — Send each tag as a single whitespace-free token; join multi-word tags with a hyphen. + +## Examples + +**Public response (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:booking-tag-malformed", + "title": "A tag is invalid.", + "detail": "One of the booking tags is not a valid single token.", + "code": "BOOKING_TAG_MALFORMED" +} +``` + +**Diagnostic (internal — not for external exposure)** + +```text +2026-07-04T13:42:18.734Z ERROR [Tag] 'late checkout' is not a valid tag. error.code=BOOKING_TAG_MALFORMED +``` + diff --git a/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#currency-code-malformed.verified.md b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#currency-code-malformed.verified.md new file mode 100644 index 00000000..303740d5 --- /dev/null +++ b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#currency-code-malformed.verified.md @@ -0,0 +1,32 @@ +# Malformed currency code + +- **Code:** `CURRENCY_CODE_MALFORMED` +- **Source:** `Currency` + +An incoming request carries a value that is not a well-formed three-letter currency code, so it cannot be parsed into a Currency value object. + +> **Business rule:** A currency code must be exactly three upper-case ASCII letters (for example EUR). + +## Diagnostics + +- **The client sent a currency in the wrong shape (lower-case, a symbol, a name, or the wrong length).** — _origin:_ External — Send the ISO-4217 alphabetic code in upper case, for example USD or EUR. + +## Examples + +**Public response (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:currency-code-malformed", + "title": "The currency is invalid.", + "detail": "The billing currency code is not a valid three-letter code.", + "code": "CURRENCY_CODE_MALFORMED" +} +``` + +**Diagnostic (internal — not for external exposure)** + +```text +2026-07-04T13:42:18.734Z ERROR [Currency] 'EURO' is not a valid three-letter currency code. error.code=CURRENCY_CODE_MALFORMED +``` + diff --git a/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#currency-errors.verified.md b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#currency-errors.verified.md new file mode 100644 index 00000000..dafdee49 --- /dev/null +++ b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#currency-errors.verified.md @@ -0,0 +1,5 @@ +# Currency errors + +Errors raised when parsing a billing currency code from a request string. + +- [Malformed currency code](./currency-code-malformed.md) diff --git a/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#email-address-errors.verified.md b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#email-address-errors.verified.md new file mode 100644 index 00000000..5ec6ee53 --- /dev/null +++ b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#email-address-errors.verified.md @@ -0,0 +1,5 @@ +# EmailAddress errors + +Errors raised when parsing a guest e-mail address from a request string. + +- [Malformed e-mail address](./email-address-malformed.md) diff --git a/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#email-address-malformed.verified.md b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#email-address-malformed.verified.md new file mode 100644 index 00000000..ac04ea53 --- /dev/null +++ b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#email-address-malformed.verified.md @@ -0,0 +1,32 @@ +# Malformed e-mail address + +- **Code:** `EMAIL_ADDRESS_MALFORMED` +- **Source:** `EmailAddress` + +An incoming request carries a value that is not a well-formed e-mail address, so it cannot be parsed into an EmailAddress value object. + +> **Business rule:** A guest e-mail address must contain a single '@' with a non-empty local part and domain. + +## Diagnostics + +- **The client sent a misspelled or truncated address (missing '@', empty local part or domain).** — _origin:_ External — Validate the address on the client, and compare the sent value against the expected format. + +## Examples + +**Public response (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:email-address-malformed", + "title": "The e-mail address is invalid.", + "detail": "The value provided is not a valid e-mail address.", + "code": "EMAIL_ADDRESS_MALFORMED" +} +``` + +**Diagnostic (internal — not for external exposure)** + +```text +2026-07-04T13:42:18.734Z ERROR [EmailAddress] 'not-an-email' is not a valid e-mail address. error.code=EMAIL_ADDRESS_MALFORMED +``` + diff --git a/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#night-count-errors.verified.md b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#night-count-errors.verified.md new file mode 100644 index 00000000..288439cd --- /dev/null +++ b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#night-count-errors.verified.md @@ -0,0 +1,5 @@ +# NightCount errors + +Errors raised when building the number of nights from a request value. + +- [Non-positive number of nights](./night-count-not-positive.md) diff --git a/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#night-count-not-positive.verified.md b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#night-count-not-positive.verified.md new file mode 100644 index 00000000..bef74b53 --- /dev/null +++ b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#night-count-not-positive.verified.md @@ -0,0 +1,32 @@ +# Non-positive number of nights + +- **Code:** `NIGHT_COUNT_NOT_POSITIVE` +- **Source:** `NightCount` + +An incoming request asks for zero or a negative number of nights, which is not a valid stay length. + +> **Business rule:** A booking must be for at least one night. + +## Diagnostics + +- **The client sent a number of nights of zero or below.** — _origin:_ External — Send a number of nights of one or more. + +## Examples + +**Public response (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:night-count-not-positive", + "title": "The number of nights is invalid.", + "detail": "The requested number of nights must be one or more.", + "code": "NIGHT_COUNT_NOT_POSITIVE" +} +``` + +**Diagnostic (internal — not for external exposure)** + +```text +2026-07-04T13:42:18.734Z ERROR [NightCount] A booking must be for at least one night, but 0 was requested. error.code=NIGHT_COUNT_NOT_POSITIVE +``` + diff --git a/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#room-number-errors.verified.md b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#room-number-errors.verified.md new file mode 100644 index 00000000..01a49643 --- /dev/null +++ b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#room-number-errors.verified.md @@ -0,0 +1,5 @@ +# RoomNumber errors + +Errors raised when building a room number from an element of a request list. + +- [Room number out of range](./room-number-out-of-range.md) diff --git a/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#room-number-out-of-range.verified.md b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#room-number-out-of-range.verified.md new file mode 100644 index 00000000..6a4b71bd --- /dev/null +++ b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#room-number-out-of-range.verified.md @@ -0,0 +1,32 @@ +# Room number out of range + +- **Code:** `ROOM_NUMBER_OUT_OF_RANGE` +- **Source:** `RoomNumber` + +An element of the request's room-number list is outside the supported 1-999 range. + +> **Business rule:** A room number must be between 1 and 999 inclusive. + +## Diagnostics + +- **The client sent a room number of zero, a negative value, or a value above 999.** — _origin:_ External — Send room numbers within the supported 1-999 range. + +## Examples + +**Public response (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:room-number-out-of-range", + "title": "A room number is invalid.", + "detail": "One of the requested room numbers is outside the supported range.", + "code": "ROOM_NUMBER_OUT_OF_RANGE" +} +``` + +**Diagnostic (internal — not for external exposure)** + +```text +2026-07-04T13:42:18.734Z ERROR [RoomNumber] Room number 1000 is outside the supported range 1-999. error.code=ROOM_NUMBER_OUT_OF_RANGE +``` + diff --git a/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#stay-checkout-not-after-checkin.verified.md b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#stay-checkout-not-after-checkin.verified.md new file mode 100644 index 00000000..1a5b0b67 --- /dev/null +++ b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#stay-checkout-not-after-checkin.verified.md @@ -0,0 +1,32 @@ +# Check-out not after check-in + +- **Code:** `STAY_CHECKOUT_NOT_AFTER_CHECKIN` +- **Source:** `Stay` + +Both stay dates parse, but the check-out date is on or before the check-in date, so the stay has no positive length. This cross-field rule is enforced by the Stay.Create factory. + +> **Business rule:** Check-out must be strictly after check-in. + +## Diagnostics + +- **The client sent a check-out date equal to or earlier than the check-in date.** — _origin:_ External — Send a check-out date at least one day after the check-in date. + +## Examples + +**Public response (RFC 9457)** + +```json +{ + "type": "urn:problem:booking-service:stay-checkout-not-after-checkin", + "title": "The stay dates are invalid.", + "detail": "The check-out date must be after the check-in date.", + "code": "STAY_CHECKOUT_NOT_AFTER_CHECKIN" +} +``` + +**Diagnostic (internal — not for external exposure)** + +```text +2026-07-04T13:42:18.734Z ERROR [Stay] Check-out 2026-08-10 must be strictly after check-in 2026-08-14. error.code=STAY_CHECKOUT_NOT_AFTER_CHECKIN +``` + diff --git a/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#stay-errors.verified.md b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#stay-errors.verified.md new file mode 100644 index 00000000..139d6e98 --- /dev/null +++ b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#stay-errors.verified.md @@ -0,0 +1,5 @@ +# Stay errors + +Errors raised when validating a stay's check-in and check-out dates together. + +- [Check-out not after check-in](./stay-checkout-not-after-checkin.md) diff --git a/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#tag-errors.verified.md b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#tag-errors.verified.md new file mode 100644 index 00000000..735776b2 --- /dev/null +++ b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.TheSplitMarkdownRendering#tag-errors.verified.md @@ -0,0 +1,5 @@ +# Tag errors + +Errors raised when parsing a booking tag from an element of a request list. + +- [Malformed booking tag](./booking-tag-malformed.md) diff --git a/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.cs b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.cs new file mode 100644 index 00000000..3b639ffe --- /dev/null +++ b/FirstClassErrors.GenDoc.UnitTests/RequestBinderUsageCatalogSnapshotTests.cs @@ -0,0 +1,133 @@ +#region Usings declarations + +using System.Globalization; + +using FirstClassErrors.GenDoc.Rendering; +using FirstClassErrors.RequestBinder.Usage.Model; + +using VerifyTests; +using VerifyXunit; + +#endregion + +namespace FirstClassErrors.GenDoc.UnitTests; + +/// +/// End-to-end snapshots of the real FirstClassErrors.RequestBinder.Usage catalog — the sibling of +/// for the request-binder sample. The catalog is extracted in-process +/// with the same reader the worker runs, so this exercises the real reflection and the living examples (the binder +/// sample's documented errors) without spawning a worker or needing the SDK. This is what makes issue #154's goal +/// real for the binder: its errors appear in a generated, snapshot-tested catalog. +/// +/// +/// On the first run Verify writes *.received.* files and fails; review and approve them (they become +/// *.verified.*) to lock the golden output. +/// +public sealed class RequestBinderUsageCatalogSnapshotTests { + + #region Statics members declarations + + private const string SampleService = "booking-service"; + + // The sample catalog is localized, so the snapshots pin the invariant (English) culture to stay deterministic + // regardless of the machine's culture. + private static ErrorDocumentationExtractionResult Extract() { + return ExtractFor(CultureInfo.InvariantCulture); + } + + private static ErrorDocumentationExtractionResult ExtractFor(CultureInfo culture) { + CultureInfo previous = CultureInfo.CurrentUICulture; + CultureInfo.CurrentUICulture = culture; + try { + return AssemblyErrorDocumentationReader.GetErrorDocumentationFrom(typeof(PlaceBookingCommand).Assembly); + } finally { + CultureInfo.CurrentUICulture = previous; + } + } + + #endregion + + [Fact(DisplayName = "The extraction of the request-binder Usage catalog matches its snapshot.")] + public async Task TheExtractedCatalog() { + await Verifier.Verify(Extract()); + } + + [Fact(DisplayName = "The JSON rendering of the request-binder Usage catalog matches its snapshot.")] + public async Task TheJsonRendering() { + string json = new JsonErrorDocumentationRenderer().Render(Extract().Documentation, new RenderRequest(RenderLayouts.Single))[0].Content; + + await Verifier.Verify(json, extension: "json"); + } + + [Fact(DisplayName = "The single-file Markdown rendering of the request-binder Usage catalog matches its snapshot.")] + public async Task TheSingleMarkdownRendering() { + string markdown = new MarkdownErrorDocumentationRenderer().Render(Extract().Documentation, new RenderRequest(RenderLayouts.Single, CultureInfo.InvariantCulture, SampleService))[0].Content; + + await Verifier.Verify(markdown, extension: "md"); + } + + [Fact(DisplayName = "Each file of the split Markdown rendering of the request-binder Usage catalog matches its snapshot.")] + public async Task TheSplitMarkdownRendering() { + IReadOnlyList documents = + new MarkdownErrorDocumentationRenderer().Render(Extract().Documentation, new RenderRequest(RenderLayouts.Split, CultureInfo.InvariantCulture, SampleService)); + + List files = documents + .Select(document => new Target("md", document.Content, Path.GetFileNameWithoutExtension(document.RelativePath))) + .ToList(); + + await Verifier.Verify(files); + } + + [Theory(DisplayName = "The Markdown rendering of the request-binder Usage catalog is localized per language.")] + [InlineData("fr")] + [InlineData("es")] + [InlineData("de")] + [InlineData("sv")] + public async Task TheLocalizedMarkdownRendering(string culture) { + CultureInfo cultureInfo = CultureInfo.GetCultureInfo(culture); + + string markdown = new MarkdownErrorDocumentationRenderer() + .Render(ExtractFor(cultureInfo).Documentation, new RenderRequest(RenderLayouts.Single, cultureInfo, SampleService))[0] + .Content; + + await Verifier.Verify(markdown, extension: "md").UseParameters(culture); + } + + [Fact(DisplayName = "The single-page HTML rendering of the request-binder Usage catalog matches its snapshot.")] + public async Task TheSingleHtmlRendering() { + string html = new HtmlErrorDocumentationRenderer().Render(Extract().Documentation, new RenderRequest(RenderLayouts.Single, CultureInfo.InvariantCulture, SampleService))[0].Content; + + await Verifier.Verify(html, extension: "html"); + } + + [Fact(DisplayName = "Each file of the split HTML rendering of the request-binder Usage catalog matches its snapshot.")] + public async Task TheSplitHtmlRendering() { + IReadOnlyList documents = + new HtmlErrorDocumentationRenderer().Render(Extract().Documentation, new RenderRequest(RenderLayouts.Split, CultureInfo.InvariantCulture, SampleService)); + + List files = documents + .Select(document => new Target( + Path.GetExtension(document.RelativePath).TrimStart('.'), + document.Content, + Path.ChangeExtension(document.RelativePath, null).Replace('/', '-').Replace('\\', '-'))) + .ToList(); + + await Verifier.Verify(files); + } + + [Theory(DisplayName = "The HTML rendering of the request-binder Usage catalog is localized per language.")] + [InlineData("fr")] + [InlineData("es")] + [InlineData("de")] + [InlineData("sv")] + public async Task TheLocalizedHtmlRendering(string culture) { + CultureInfo cultureInfo = CultureInfo.GetCultureInfo(culture); + + string html = new HtmlErrorDocumentationRenderer() + .Render(ExtractFor(cultureInfo).Documentation, new RenderRequest(RenderLayouts.Single, cultureInfo, SampleService))[0] + .Content; + + await Verifier.Verify(html, extension: "html").UseParameters(culture); + } + +} diff --git a/FirstClassErrors.RequestBinder.Usage.UnitTests/BinderShowcaseTests.cs b/FirstClassErrors.RequestBinder.Usage.UnitTests/BinderShowcaseTests.cs new file mode 100644 index 00000000..e54746db --- /dev/null +++ b/FirstClassErrors.RequestBinder.Usage.UnitTests/BinderShowcaseTests.cs @@ -0,0 +1,78 @@ +#region Usings declarations + +using FirstClassErrors.RequestBinder.Usage.Binding; +using FirstClassErrors.RequestBinder.Usage.Boundary; +using FirstClassErrors.RequestBinder.Usage.Model; + +using NFluent; + +#endregion + +namespace FirstClassErrors.RequestBinder.Usage.UnitTests; + +/// Behavioural tests for the variations (the overloads and options the canonical binder does not use). +public sealed class BinderShowcaseTests { + + #region Statics members declarations + + private static BookingRequest With(IReadOnlyList? tags = null, + StayDto? stay = null, + IReadOnlyList? guests = null, + string? guestEmail = null) { + return new BookingRequest(guestEmail, Reference: null, Currency: null, Nights: null, MaxNights: null, + stay, tags, RoomNumbers: null, guests); + } + + #endregion + + [Fact(DisplayName = "A required simple list records REQUEST_ARGUMENT_REQUIRED when the list is absent.")] + public void RequiredListAbsentRecords() { + Outcome> outcome = BinderShowcase.BindTagsAsRequired(With(tags: null)); + + Check.That(outcome.IsFailure).IsTrue(); + Error inner = outcome.Error!.InnerErrors.Single(); + Check.That(inner.Code.ToString()).IsEqualTo("REQUEST_ARGUMENT_REQUIRED"); + Check.That(BookingRequests.ArgumentPathOf(inner)).IsEqualTo("Tags"); + } + + [Fact(DisplayName = "A required simple list that is present but empty binds an empty list.")] + public void RequiredListPresentButEmptyBindsEmpty() { + Outcome> outcome = BinderShowcase.BindTagsAsRequired(With(tags: [])); + + Check.That(outcome.IsSuccess).IsTrue(); + Check.That(outcome.GetResultOrThrow()).IsEmpty(); + } + + [Fact(DisplayName = "Optional complex and optional complex-list properties yield null / empty when absent, recording nothing.")] + public void OptionalSectionsAbsentBindToNullAndEmpty() { + Outcome outcome = BinderShowcase.BindOptionalSections(With(stay: null, guests: null)); + + Check.That(outcome.IsSuccess).IsTrue(); + BinderShowcase.OptionalSections sections = outcome.GetResultOrThrow(); + Check.That(sections.Stay).IsNull(); + Check.That(sections.Guests).IsEmpty(); + } + + [Fact(DisplayName = "A custom IArgumentNameProvider reports the failing argument under its snake_case path.")] + public void SnakeCaseNamesReportSnakeCasePath() { + Outcome outcome = BinderShowcase.BindGuestEmailWithSnakeCaseNames(With(guestEmail: null)); + + Error inner = outcome.Error!.InnerErrors.Single(); + Check.That(BookingRequests.ArgumentPathOf(inner)).IsEqualTo("guest_email"); + } + + [Fact(DisplayName = "Custom structural codes replace the defaults, and consumers branch on them symbolically.")] + public void CustomStructuralCodesAreRaisedAndMatchedSymbolically() { + Outcome outcome = BinderShowcase.BindGuestEmailWithCustomStructuralCodes(With(guestEmail: null)); + + Error inner = outcome.Error!.InnerErrors.Single(); + Check.That(inner.Code.ToString()).IsEqualTo("HOTELAPI_ARGUMENT_REQUIRED"); + Check.That(BinderShowcase.IsMissingArgument(inner, BinderShowcase.HotelApiArgumentRequired.Code)).IsTrue(); + } + + [Fact(DisplayName = "Selecting a non-nullable value-type property trips the binder's programming-error guard.")] + public void NonNullableValueTypeGuardTrips() { + Check.That(BinderShowcase.NonNullableValueTypeGuardTrips()).IsTrue(); + } + +} diff --git a/FirstClassErrors.RequestBinder.Usage.UnitTests/BookingBinderTests.cs b/FirstClassErrors.RequestBinder.Usage.UnitTests/BookingBinderTests.cs new file mode 100644 index 00000000..627e494b --- /dev/null +++ b/FirstClassErrors.RequestBinder.Usage.UnitTests/BookingBinderTests.cs @@ -0,0 +1,113 @@ +#region Usings declarations + +using FirstClassErrors.RequestBinder.Usage.Binding; +using FirstClassErrors.RequestBinder.Usage.Boundary; +using FirstClassErrors.RequestBinder.Usage.Model; + +using NFluent; + +#endregion + +namespace FirstClassErrors.RequestBinder.Usage.UnitTests; + +/// +/// Behavioural tests for the canonical : they prove the examples are honest — a valid +/// request binds into the complete command, and an invalid one produces the full, ordered, coded error tree with no +/// exception. +/// +public sealed class BookingBinderTests { + + [Fact(DisplayName = "A fully valid request binds into the complete command.")] + public void ValidRequestBinds() { + Outcome outcome = BookingBinder.BindBooking(BookingRequests.Valid()); + + Check.That(outcome.IsSuccess).IsTrue(); + PlaceBookingCommand command = outcome.GetResultOrThrow(); + + Check.That(command.GuestEmail.Value).IsEqualTo("alice@example.org"); + Check.That(command.Reference).IsEqualTo("REF-42"); + Check.That(command.Currency.Code).IsEqualTo("EUR"); // fell back + Check.That(command.Nights.Value).IsEqualTo(3); + Check.That(command.MaxNights.HasValue).IsFalse(); // absent -> real null, not 0 + Check.That(command.Stay.CheckIn.Value).IsEqualTo(new DateOnly(2026, 8, 10)); + Check.That(command.Stay.CheckOut.Value).IsEqualTo(new DateOnly(2026, 8, 14)); + Check.That(command.Tags.Select(t => t.Value)).ContainsExactly("vip"); + Check.That(command.RoomNumbers.Select(r => r.Value)).ContainsExactly(101, 102); + Check.That(command.Guests.Select(g => g.FirstName)).ContainsExactly("Alice", "Bob"); + Check.That(command.Guests[1].Email).IsNull(); // optional e-mail absent + } + + [Fact(DisplayName = "A request failing at every level yields one envelope with the full ordered error tree, and no exception.")] + public void InvalidRequestYieldsTheOrderedTree() { + Outcome outcome = BookingBinder.BindBooking(BookingRequests.InvalidEverywhere()); + + Check.That(outcome.IsFailure).IsTrue(); + Error envelope = outcome.Error!; + Check.That(envelope.Code.ToString()).IsEqualTo("BOOKING_COMMAND_INVALID"); + + // Children, in binding (declaration) order. + Check.That(envelope.InnerErrors).HasSize(9); + Check.That(envelope.InnerErrors.Select(e => e.Code.ToString())).ContainsExactly( + "REQUEST_ARGUMENT_INVALID", // GuestEmail + "REQUEST_ARGUMENT_REQUIRED", // Reference + "REQUEST_ARGUMENT_INVALID", // Currency + "REQUEST_ARGUMENT_REQUIRED", // Nights + "REQUEST_ARGUMENT_INVALID", // MaxNights + "BOOKING_STAY_INVALID", // Stay (nested envelope) + "REQUEST_ARGUMENT_INVALID", // Tags[1] + "REQUEST_ARGUMENT_INVALID", // RoomNumbers[1] + "BOOKING_GUEST_INVALID"); // Guests[1] (nested envelope) + } + + [Fact(DisplayName = "Every failing scalar carries its full argument path in context.")] + public void FailingScalarsCarryTheirPaths() { + Error envelope = BookingBinder.BindBooking(BookingRequests.InvalidEverywhere()).Error!; + + Check.That(BookingRequests.ArgumentPathOf(envelope.InnerErrors[0])).IsEqualTo("GuestEmail"); + Check.That(BookingRequests.ArgumentPathOf(envelope.InnerErrors[1])).IsEqualTo("Reference"); + Check.That(BookingRequests.ArgumentPathOf(envelope.InnerErrors[2])).IsEqualTo("Currency"); + Check.That(BookingRequests.ArgumentPathOf(envelope.InnerErrors[3])).IsEqualTo("Nights"); + Check.That(BookingRequests.ArgumentPathOf(envelope.InnerErrors[4])).IsEqualTo("MaxNights"); + Check.That(BookingRequests.ArgumentPathOf(envelope.InnerErrors[6])).IsEqualTo("Tags[1]"); + Check.That(BookingRequests.ArgumentPathOf(envelope.InnerErrors[7])).IsEqualTo("RoomNumbers[1]"); + } + + [Fact(DisplayName = "A nested stay envelope carries the prefixed path of its failing date.")] + public void NestedStayEnvelopeCarriesPrefixedPaths() { + Error envelope = BookingBinder.BindBooking(BookingRequests.InvalidEverywhere()).Error!; + Error stayInvalid = envelope.InnerErrors[5]; + + Check.That(stayInvalid.Code.ToString()).IsEqualTo("BOOKING_STAY_INVALID"); + Check.That(stayInvalid.InnerErrors.Select(BookingRequests.ArgumentPathOf)).ContainsExactly("Stay.CheckIn"); + } + + [Fact(DisplayName = "A per-element guest envelope carries the indexed paths of its failing fields.")] + public void GuestEnvelopeCarriesIndexedPaths() { + Error envelope = BookingBinder.BindBooking(BookingRequests.InvalidEverywhere()).Error!; + Error guestInvalid = envelope.InnerErrors[8]; + + Check.That(guestInvalid.Code.ToString()).IsEqualTo("BOOKING_GUEST_INVALID"); + Check.That(guestInvalid.InnerErrors.Select(BookingRequests.ArgumentPathOf)) + .ContainsExactly("Guests[1].FirstName", "Guests[1].Email"); + } + + [Fact(DisplayName = "The cross-field Create rule surfaces when both dates parse but check-out is not after check-in.")] + public void CrossFieldRuleSurfaces() { + BookingRequest request = BookingRequests.Valid() with { + Stay = new StayDto("2026-08-14", "2026-08-10") // check-out before check-in + }; + + Error envelope = BookingBinder.BindBooking(request).Error!; + + // The Stay slot fails, and the leaf is the factory's own cross-field domain error (surfaced as-is). + Error stayFailure = envelope.InnerErrors.Single(); + Check.That(FindCode(stayFailure, "STAY_CHECKOUT_NOT_AFTER_CHECKIN")).IsTrue(); + } + + private static bool FindCode(Error error, string code) { + if (error.Code.ToString() == code) { return true; } + + return error.InnerErrors.Any(inner => FindCode(inner, code)); + } + +} diff --git a/FirstClassErrors.RequestBinder.Usage.UnitTests/BookingEndpointTests.cs b/FirstClassErrors.RequestBinder.Usage.UnitTests/BookingEndpointTests.cs new file mode 100644 index 00000000..1ac39176 --- /dev/null +++ b/FirstClassErrors.RequestBinder.Usage.UnitTests/BookingEndpointTests.cs @@ -0,0 +1,29 @@ +#region Usings declarations + +using FirstClassErrors.RequestBinder.Usage.Binding; + +using NFluent; + +#endregion + +namespace FirstClassErrors.RequestBinder.Usage.UnitTests; + +/// Tests for the framework-agnostic : the Outcome pipeline it rejoins on success and failure. +public sealed class BookingEndpointTests { + + [Fact(DisplayName = "A valid request is handled into a success outcome and confirmed by the pipeline.")] + public void ValidRequestIsConfirmed() { + BookingEndpoint endpoint = new(); + + Check.That(endpoint.Handle(BookingRequests.Valid()).IsSuccess).IsTrue(); + Check.That(endpoint.Place(BookingRequests.Valid())).IsEqualTo("confirmed:REF-42"); + } + + [Fact(DisplayName = "An invalid request is rejected by the pipeline with the envelope code, without throwing.")] + public void InvalidRequestIsRejected() { + BookingEndpoint endpoint = new(); + + Check.That(endpoint.Place(BookingRequests.InvalidEverywhere())).IsEqualTo("rejected:BOOKING_COMMAND_INVALID"); + } + +} diff --git a/FirstClassErrors.RequestBinder.Usage.UnitTests/BookingRequests.cs b/FirstClassErrors.RequestBinder.Usage.UnitTests/BookingRequests.cs new file mode 100644 index 00000000..e07929de --- /dev/null +++ b/FirstClassErrors.RequestBinder.Usage.UnitTests/BookingRequests.cs @@ -0,0 +1,54 @@ +#region Usings declarations + +using FirstClassErrors.RequestBinder.Usage.Boundary; + +#endregion + +namespace FirstClassErrors.RequestBinder.Usage.UnitTests; + +/// Canonical valid / invalid request fixtures and a shared argument-path reader, used across the tests. +internal static class BookingRequests { + + #region Statics members declarations + + /// A request whose every field is valid: it binds into a complete command. + internal static BookingRequest Valid() { + return new BookingRequest( + GuestEmail: "alice@example.org", + Reference: "REF-42", + Currency: null, // omitted: falls back to EUR + Nights: 3, + MaxNights: null, // omitted: a real null, not 0 + Stay: new StayDto("2026-08-10", "2026-08-14"), + Tags: ["vip"], + RoomNumbers: [101, 102], + Guests: [new GuestDto("Alice", "alice@example.org"), new GuestDto("Bob", null)]); + } + + /// + /// A request that fails at every level, so the binder's collect-all behaviour and the full ordered error tree + /// can be asserted at once. + /// + internal static BookingRequest InvalidEverywhere() { + return new BookingRequest( + GuestEmail: "not-an-email", // invalid + Reference: null, // missing + Currency: "EURO", // invalid (4 letters) + Nights: null, // missing (value-type required) + MaxNights: -1, // invalid (present, not positive) + Stay: new StayDto("not-a-date", "2026-08-14"), // CheckIn invalid -> nested stay envelope + Tags: ["ok", "bad tag"], // Tags[1] invalid (whitespace) + RoomNumbers: [101, 1000], // RoomNumbers[1] out of range + Guests: [new GuestDto("Alice", "alice@example.org"), new GuestDto(null, "bad-email")]); // Guests[1] envelope + } + + /// The full argument path recorded in a binding error's context (the "RequestArgument" entry). + internal static string? ArgumentPathOf(Error error) { + error.Context.ToNameDictionary().TryGetValue("RequestArgument", out object? path); + + return path as string; + } + + #endregion + +} diff --git a/FirstClassErrors.RequestBinder.Usage.UnitTests/FirstClassErrors.RequestBinder.Usage.UnitTests.csproj b/FirstClassErrors.RequestBinder.Usage.UnitTests/FirstClassErrors.RequestBinder.Usage.UnitTests.csproj new file mode 100644 index 00000000..c7daa200 --- /dev/null +++ b/FirstClassErrors.RequestBinder.Usage.UnitTests/FirstClassErrors.RequestBinder.Usage.UnitTests.csproj @@ -0,0 +1,35 @@ + + + + net10.0 + enable + enable + false + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + diff --git a/FirstClassErrors.RequestBinder.Usage/Binding/BinderShowcase.cs b/FirstClassErrors.RequestBinder.Usage/Binding/BinderShowcase.cs new file mode 100644 index 00000000..4e5c193b --- /dev/null +++ b/FirstClassErrors.RequestBinder.Usage/Binding/BinderShowcase.cs @@ -0,0 +1,143 @@ +#region Usings declarations + +using FirstClassErrors.RequestBinder.Usage.Boundary; +using FirstClassErrors.RequestBinder.Usage.Errors; +using FirstClassErrors.RequestBinder.Usage.Model; +using FirstClassErrors.RequestBinder.Usage.Options; + +#endregion + +namespace FirstClassErrors.RequestBinder.Usage.Binding; + +/// +/// Focused variations that round out the picture gives: the overloads and options the +/// canonical one-pass binder does not use. Each reuses the same DTO and envelope, so +/// the difference is only in the one binder feature it demonstrates. +/// +public static class BinderShowcase { + + #region Statics members declarations + + /// + /// The structural-error definition (code + public messages, kept together) this sample raises for a + /// missing argument when it overrides the binder defaults. Derived from the built-in default with + /// WithCode, so it keeps the default messages and only swaps the code; WithMessage would override + /// the messages too. + /// + public static readonly BinderErrorDefinition HotelApiArgumentRequired = + RequestBindingError.DefaultArgumentRequired.WithCode(ErrorCode.Create("HOTELAPI_ARGUMENT_REQUIRED")); + + /// + /// The structural-error definition this sample raises for a present-but-invalid argument when it overrides the + /// binder defaults. + /// + public static readonly BinderErrorDefinition HotelApiArgumentInvalid = + RequestBindingError.DefaultArgumentInvalid.WithCode(ErrorCode.Create("HOTELAPI_ARGUMENT_INVALID")); + + /// + /// A required list of simple properties (contrast , which binds the tags as + /// optional): an absent list records REQUEST_ARGUMENT_REQUIRED, while a present-but-empty list is valid + /// and binds an empty list — a required list constrains presence, not element count. + /// + public static Outcome> BindTagsAsRequired(BookingRequest request) { + RequestBinder binder = Bind.PropertiesOf(request).FailWith(PlaceBookingError.CommandInvalid); + + RequiredField> tags = binder.ListOfSimpleProperties(r => r.Tags).AsRequired(Tag.Parse); + + return binder.New(s => s.Get(tags)); + } + + /// + /// An optional complex property and an optional list of complex properties: an absent stay yields + /// null, an absent guests list yields an empty list — both record nothing. Present-but-invalid content + /// still records under its (prefixed / indexed) path. + /// + public static Outcome BindOptionalSections(BookingRequest request) { + RequestBinder binder = Bind.PropertiesOf(request).FailWith(PlaceBookingError.CommandInvalid); + + OptionalReferenceField stay = binder.ComplexProperty(r => r.Stay).FailWith(PlaceBookingError.StayInvalid).AsOptionalReference(BookingBinder.BindStay); + RequiredField> guests = binder.ListOfComplexProperties(r => r.Guests).FailWith(PlaceBookingError.GuestInvalid).AsOptional(BookingBinder.BindGuest); + + return binder.New(s => new OptionalSections(s.Get(stay), s.Get(guests))); + } + + /// + /// Binds the guest e-mail with a custom () + /// fixed once through Bind.WithOptions, so a failing GuestEmail is reported under the path + /// guest_email — the key a snake_case client actually sent — rather than the C# property name. + /// + public static Outcome BindGuestEmailWithSnakeCaseNames(BookingRequest request) { + RequestBinder binder = + Bind.WithOptions(new RequestBinderOptions(new SnakeCaseArgumentNames())) + .PropertiesOf(request) + .FailWith(PlaceBookingError.CommandInvalid); + + RequiredField email = binder.SimpleProperty(r => r.GuestEmail).AsRequired(EmailAddress.Parse); + + return binder.New(s => s.Get(email)); + } + + /// + /// Binds the guest e-mail with custom structural codes: the binder raises + /// HOTELAPI_ARGUMENT_REQUIRED / HOTELAPI_ARGUMENT_INVALID instead of the defaults, so its + /// structural failures line up with the rest of the application's catalog. Consumers still branch + /// symbolically (see ), never on a message string. + /// + public static Outcome BindGuestEmailWithCustomStructuralCodes(BookingRequest request) { + RequestBinderOptions options = new( + new SnakeCaseArgumentNames(), + HotelApiArgumentRequired, + HotelApiArgumentInvalid); + + RequestBinder binder = + Bind.WithOptions(options) + .PropertiesOf(request) + .FailWith(PlaceBookingError.CommandInvalid); + + RequiredField email = binder.SimpleProperty(r => r.GuestEmail).AsRequired(EmailAddress.Parse); + + return binder.New(s => s.Get(email)); + } + + /// + /// The symbolic way to branch on a binder's structural failures: compare the error's + /// against the well-known code rather than parsing its message. Pass the default + /// (), or your own configured code. + /// + public static bool IsMissingArgument(Error error, ErrorCode argumentRequiredCode) { + return error.Code == argumentRequiredCode; + } + + /// + /// The binder's programming-error guard, shown in isolation: selecting a non-nullable value-type property + /// (an int, not an int?) throws at SimpleProperty(...), + /// because an absent value would be indistinguishable from its default. Declaring the DTO property nullable is + /// the fix. Returns true to confirm the guard tripped. + /// + public static bool NonNullableValueTypeGuardTrips() { + RequestBinder binder = Bind.PropertiesOf(new NonNullableProbe(0)).FailWith(PlaceBookingError.CommandInvalid); + + try { + binder.SimpleProperty(p => p.Nights); + + return false; + } catch (ArgumentException) { + return true; + } + } + + #endregion + + #region Nested types declarations + + /// The pair produced by : an optional stay and the guests list. + /// The bound stay, or null when the request omitted it. + /// The bound guests (empty when the request omitted the list). + public sealed record OptionalSections(Stay? Stay, IReadOnlyList Guests); + + /// A DTO with a non-nullable value-type property, used only by . + private sealed record NonNullableProbe(int Nights); + + #endregion + +} diff --git a/FirstClassErrors.RequestBinder.Usage/Binding/BookingBinder.cs b/FirstClassErrors.RequestBinder.Usage/Binding/BookingBinder.cs new file mode 100644 index 00000000..bb42b9cc --- /dev/null +++ b/FirstClassErrors.RequestBinder.Usage/Binding/BookingBinder.cs @@ -0,0 +1,85 @@ +#region Usings declarations + +using FirstClassErrors.RequestBinder.Usage.Boundary; +using FirstClassErrors.RequestBinder.Usage.Errors; +using FirstClassErrors.RequestBinder.Usage.Model; + +#endregion + +namespace FirstClassErrors.RequestBinder.Usage.Binding; + +/// +/// The canonical request-binder example: turns a DTO into a +/// of value objects, collecting every failure into one coded envelope +/// instead of stopping at the first. One method, one pass, exercising every converter shape. +/// +public static class BookingBinder { + + #region Statics members declarations + + /// + /// Binds a full booking request. On success, a complete command of value objects; on failure, the + /// envelope grouping every field failure, in declaration order, + /// with each argument's full (indexed, prefixed) path. Raises no exception on the invalid-input path. + /// + public static Outcome BindBooking(BookingRequest request) { + RequestBinder binder = Bind.PropertiesOf(request).FailWith(PlaceBookingError.CommandInvalid); + + // Scalars: required + converter, required + raw (presence only), optional + fallback. + RequiredField email = binder.SimpleProperty(r => r.GuestEmail).AsRequired(EmailAddress.Parse); + RequiredField reference = binder.SimpleProperty(r => r.Reference).AsRequired(); + RequiredField currency = binder.SimpleProperty(r => r.Currency).AsOptional(Currency.Parse, "EUR"); + + // Value-type scalars: a nullable value-type property bound over its underlying int; required, and optional + // (a real Nullable when absent — never default(NightCount)). + RequiredField nights = binder.SimpleProperty(r => r.Nights).AsRequired(NightCount.From); + OptionalValueField maxNights = binder.SimpleProperty(r => r.MaxNights).AsOptionalValue(NightCount.From); + + // Complex (nested) property: a required sub-object bound by a nested binder under its own envelope. + RequiredField stay = binder.ComplexProperty(r => r.Stay).FailWith(PlaceBookingError.StayInvalid).AsRequired(BindStay); + + // Lists: of simple reference elements (optional), of value-type elements (required), of complex elements (required). + RequiredField> tags = binder.ListOfSimpleProperties(r => r.Tags).AsOptional(Tag.Parse); + RequiredField> rooms = binder.ListOfSimpleProperties(r => r.RoomNumbers).AsRequired(RoomNumber.From); + RequiredField> guests = binder.ListOfComplexProperties(r => r.Guests).FailWith(PlaceBookingError.GuestInvalid).AsRequired(BindGuest); + + // Total assembler: runs once, only when no failure was recorded. + return binder.New(s => new PlaceBookingCommand( + s.Get(email), + s.Get(reference), + s.Get(currency), + s.Get(nights), + s.Get(maxNights), + s.Get(stay), + s.Get(tags), + s.Get(rooms), + s.Get(guests))); + } + + /// + /// Binds a nested stay, then assembles it through the validating Create terminal so the + /// cross-field rule (check-out strictly after check-in) — which no single field can check — is enforced and + /// flattened. Each date is reported under a Stay.-prefixed path. Shared with . + /// + internal static Outcome BindStay(RequestBinder stay) { + RequiredField checkIn = stay.SimpleProperty(s => s.CheckIn).AsRequired(BookingDate.Parse); + RequiredField checkOut = stay.SimpleProperty(s => s.CheckOut).AsRequired(BookingDate.Parse); + + return stay.Create(s => Stay.Create(s.Get(checkIn), s.Get(checkOut))); + } + + /// + /// Binds a single guest: a required raw first name and an optional e-mail address, assembled by the total + /// New terminal. Each field is reported under an indexed path such as Guests[1].FirstName. Shared + /// with . + /// + internal static Outcome BindGuest(RequestBinder guest) { + RequiredField firstName = guest.SimpleProperty(g => g.FirstName).AsRequired(); + OptionalReferenceField email = guest.SimpleProperty(g => g.Email).AsOptionalReference(EmailAddress.Parse); + + return guest.New(s => new Guest(s.Get(firstName), s.Get(email))); + } + + #endregion + +} diff --git a/FirstClassErrors.RequestBinder.Usage/Binding/BookingEndpoint.cs b/FirstClassErrors.RequestBinder.Usage/Binding/BookingEndpoint.cs new file mode 100644 index 00000000..83eab4e5 --- /dev/null +++ b/FirstClassErrors.RequestBinder.Usage/Binding/BookingEndpoint.cs @@ -0,0 +1,39 @@ +#region Usings declarations + +using FirstClassErrors.RequestBinder.Usage.Boundary; +using FirstClassErrors.RequestBinder.Usage.Model; + +#endregion + +namespace FirstClassErrors.RequestBinder.Usage.Binding; + +/// +/// A framework-agnostic primary adapter (incoming port): it depends on nothing from ASP.NET, gRPC or any transport +/// — it takes a plain DTO and returns an . The same code +/// serves an HTTP controller, a message consumer, a CLI or a gRPC handler; each transport only has to deserialize +/// into the DTO and map the returned onto its own response. This type is also the +/// documentation anchor for the endpoint's envelope errors (see ). +/// +public sealed class BookingEndpoint { + + /// + /// Binds the request into a command. The returned rejoins the caller's own pipeline — + /// see for a transport-shaped example. + /// + public Outcome Handle(BookingRequest request) { + return BookingBinder.BindBooking(request); + } + + /// + /// Binds the request and rejoins the pipeline with Then / Finally to + /// produce a single response value — a confirmation string on success, a rejection string carrying the envelope + /// code on failure. Mirrors what a transport handler would do to shape its response, without any framework + /// dependency. + /// + public string Place(BookingRequest request) { + return Handle(request) + .Then(command => Outcome.Success($"confirmed:{command.Reference}")) + .Finally(confirmation => confirmation, error => $"rejected:{error.Code}"); + } + +} diff --git a/FirstClassErrors.RequestBinder.Usage/Boundary/BookingRequest.cs b/FirstClassErrors.RequestBinder.Usage/Boundary/BookingRequest.cs new file mode 100644 index 00000000..7baed13a --- /dev/null +++ b/FirstClassErrors.RequestBinder.Usage/Boundary/BookingRequest.cs @@ -0,0 +1,27 @@ +namespace FirstClassErrors.RequestBinder.Usage.Boundary; + +/// +/// The incoming request DTO — the "wire" shape a primary adapter (HTTP controller, message consumer, CLI) hands the +/// binder. Every property is nullable: absence is data the binder must detect, so a missing value arrives as +/// null rather than a silent default. Nothing here is validated; the binder turns it into a +/// of value objects. +/// +/// The guest e-mail address (required once bound). +/// The booking reference (required, bound raw). +/// The billing currency code (optional, falls back to a default). +/// The number of nights — a nullable value type, bound through the value-type overload. +/// An optional cap on the number of nights — bound through AsOptionalValue. +/// The nested stay object (required complex property). +/// The booking tags (a list of simple string properties). +/// The requested room numbers (a list of nullable value-type properties). +/// The guests (a list of nested complex properties). +public sealed record BookingRequest( + string? GuestEmail, + string? Reference, + string? Currency, + int? Nights, + int? MaxNights, + StayDto? Stay, + IReadOnlyList? Tags, + IReadOnlyList? RoomNumbers, + IReadOnlyList? Guests); diff --git a/FirstClassErrors.RequestBinder.Usage/Boundary/GuestDto.cs b/FirstClassErrors.RequestBinder.Usage/Boundary/GuestDto.cs new file mode 100644 index 00000000..123b98f9 --- /dev/null +++ b/FirstClassErrors.RequestBinder.Usage/Boundary/GuestDto.cs @@ -0,0 +1,10 @@ +namespace FirstClassErrors.RequestBinder.Usage.Boundary; + +/// +/// The wire shape of one guest: a nullable first name and a nullable e-mail. Bound by a per-element nested binder +/// into a , with each field reported under an indexed path such as +/// Guests[1].FirstName. +/// +/// The guest's first name (required once bound). +/// The guest's e-mail address (optional). +public sealed record GuestDto(string? FirstName, string? Email); diff --git a/FirstClassErrors.RequestBinder.Usage/Boundary/StayDto.cs b/FirstClassErrors.RequestBinder.Usage/Boundary/StayDto.cs new file mode 100644 index 00000000..0f63d563 --- /dev/null +++ b/FirstClassErrors.RequestBinder.Usage/Boundary/StayDto.cs @@ -0,0 +1,9 @@ +namespace FirstClassErrors.RequestBinder.Usage.Boundary; + +/// +/// The wire shape of a nested stay: two nullable date strings. Bound by a nested binder into a +/// , with each field reported under a path prefixed by Stay.. +/// +/// The check-in date as an ISO string (required once bound). +/// The check-out date as an ISO string (required once bound). +public sealed record StayDto(string? CheckIn, string? CheckOut); diff --git a/FirstClassErrors.RequestBinder.Usage/Errors/PlaceBookingError.cs b/FirstClassErrors.RequestBinder.Usage/Errors/PlaceBookingError.cs new file mode 100644 index 00000000..e0a774cc --- /dev/null +++ b/FirstClassErrors.RequestBinder.Usage/Errors/PlaceBookingError.cs @@ -0,0 +1,115 @@ +#region Usings declarations + +using FirstClassErrors.RequestBinder.Usage.Binding; +using FirstClassErrors.RequestBinder.Usage.Model; +using FirstClassErrors.RequestBinder.Usage.Resources; + +#endregion + +namespace FirstClassErrors.RequestBinder.Usage.Errors; + +/// +/// The primary-port (incoming) envelope errors of the booking endpoint. Each factory is the FailWith(...) +/// envelope a binder groups its collected field failures into — the command-level envelope for the whole request, +/// and one per nested scope (the stay, each guest). They are the coded, documented roots of a binding-failure tree. +/// +[ProvidesErrorsFor(nameof(BookingEndpoint), + Description = "BookingEndpoint_Source", + DescriptionResourceType = typeof(RequestBinderUsageMessages))] +public static class PlaceBookingError { + + #region Statics members declarations + + /// The top-level envelope grouping every field failure of a booking request. + [DocumentedBy(nameof(CommandInvalidDocumentation))] + internal static PrimaryPortError CommandInvalid(PrimaryPortInnerErrors violations) { + return PrimaryPortError.Create( + Code.CommandInvalid, + "The booking command is invalid: one or more request arguments failed to bind.", + violations) + .WithPublicMessage( + RequestBinderUsageMessages.Get("PlaceBooking_CommandInvalid_ShortMessage"), + RequestBinderUsageMessages.Get("PlaceBooking_CommandInvalid_DetailedMessage")); + } + + /// The nested envelope grouping the failures of the stay sub-object. + [DocumentedBy(nameof(StayInvalidDocumentation))] + internal static PrimaryPortError StayInvalid(PrimaryPortInnerErrors violations) { + return PrimaryPortError.Create( + Code.StayInvalid, + "The stay is invalid: one or more of its dates failed to bind.", + violations) + .WithPublicMessage( + RequestBinderUsageMessages.Get("PlaceBooking_StayInvalid_ShortMessage"), + RequestBinderUsageMessages.Get("PlaceBooking_StayInvalid_DetailedMessage")); + } + + /// The per-element envelope grouping the failures of a single guest of the guests list. + [DocumentedBy(nameof(GuestInvalidDocumentation))] + internal static PrimaryPortError GuestInvalid(PrimaryPortInnerErrors violations) { + return PrimaryPortError.Create( + Code.GuestInvalid, + "The guest is invalid: one or more of its fields failed to bind.", + violations) + .WithPublicMessage( + RequestBinderUsageMessages.Get("PlaceBooking_GuestInvalid_ShortMessage"), + RequestBinderUsageMessages.Get("PlaceBooking_GuestInvalid_DetailedMessage")); + } + + private static ErrorDocumentation CommandInvalidDocumentation() { + return DescribeError.WithTitle(RequestBinderUsageMessages.Get("PlaceBooking_CommandInvalid_Title")) + .WithDescription(RequestBinderUsageMessages.Get("PlaceBooking_CommandInvalid_Description")) + .WithRule(RequestBinderUsageMessages.Get("PlaceBooking_CommandInvalid_Rule")) + .WithDiagnostic(RequestBinderUsageMessages.Get("PlaceBooking_CommandInvalid_Cause1"), + ErrorOrigin.External, + RequestBinderUsageMessages.Get("PlaceBooking_CommandInvalid_Hint1")) + .WithExamples(() => CommandInvalid(Violation(InvalidEmailAddressError.Malformed("not-an-email")))); + } + + private static ErrorDocumentation StayInvalidDocumentation() { + return DescribeError.WithTitle(RequestBinderUsageMessages.Get("PlaceBooking_StayInvalid_Title")) + .WithDescription(RequestBinderUsageMessages.Get("PlaceBooking_StayInvalid_Description")) + .WithRule(RequestBinderUsageMessages.Get("PlaceBooking_StayInvalid_Rule")) + .WithDiagnostic(RequestBinderUsageMessages.Get("PlaceBooking_StayInvalid_Cause1"), + ErrorOrigin.External, + RequestBinderUsageMessages.Get("PlaceBooking_StayInvalid_Hint1")) + .WithExamples(() => StayInvalid(Violation(InvalidBookingDateError.Malformed("2026-13-40")))); + } + + private static ErrorDocumentation GuestInvalidDocumentation() { + return DescribeError.WithTitle(RequestBinderUsageMessages.Get("PlaceBooking_GuestInvalid_Title")) + .WithDescription(RequestBinderUsageMessages.Get("PlaceBooking_GuestInvalid_Description")) + .WithRule(RequestBinderUsageMessages.Get("PlaceBooking_GuestInvalid_Rule")) + .WithDiagnostic(RequestBinderUsageMessages.Get("PlaceBooking_GuestInvalid_Cause1"), + ErrorOrigin.External, + RequestBinderUsageMessages.Get("PlaceBooking_GuestInvalid_Hint1")) + .WithExamples(() => GuestInvalid(Violation(InvalidEmailAddressError.Malformed("still-not-an-email")))); + } + + /// Wraps a single representative leaf failure into an inner-errors collection for the documentation examples. + private static PrimaryPortInnerErrors Violation(DomainError leaf) { + PrimaryPortInnerErrors violations = new(); + violations.Add(leaf); + + return violations; + } + + #endregion + + #region Nested types declarations + + private static class Code { + + #region Statics members declarations + + public static readonly ErrorCode CommandInvalid = ErrorCode.Create("BOOKING_COMMAND_INVALID"); + public static readonly ErrorCode StayInvalid = ErrorCode.Create("BOOKING_STAY_INVALID"); + public static readonly ErrorCode GuestInvalid = ErrorCode.Create("BOOKING_GUEST_INVALID"); + + #endregion + + } + + #endregion + +} diff --git a/FirstClassErrors.RequestBinder.Usage/FirstClassErrors.RequestBinder.Usage.csproj b/FirstClassErrors.RequestBinder.Usage/FirstClassErrors.RequestBinder.Usage.csproj new file mode 100644 index 00000000..edd1e9a5 --- /dev/null +++ b/FirstClassErrors.RequestBinder.Usage/FirstClassErrors.RequestBinder.Usage.csproj @@ -0,0 +1,30 @@ + + + + + net8.0;net10.0 + enable + enable + + + + + true + + + + + + + + + + diff --git a/FirstClassErrors.RequestBinder.Usage/Model/BookingDate.cs b/FirstClassErrors.RequestBinder.Usage/Model/BookingDate.cs new file mode 100644 index 00000000..3bc586d4 --- /dev/null +++ b/FirstClassErrors.RequestBinder.Usage/Model/BookingDate.cs @@ -0,0 +1,66 @@ +namespace FirstClassErrors.RequestBinder.Usage.Model; + +/// +/// A calendar date on a booking (check-in / check-out) — a reference-type value object parsed from an ISO +/// yyyy-MM-dd request string. Bound inside the nested Stay binder. +/// +public sealed class BookingDate : IEquatable, IComparable { + + #region Constructors declarations + + private BookingDate(DateOnly value) { + Value = value; + } + + #endregion + + /// The validated calendar date. + public DateOnly Value { get; } + + #region Statics members declarations + + /// + /// Parses (an ISO yyyy-MM-dd date) into a , or fails + /// with a documented . + /// + public static Outcome Parse(string raw) { + if (!DateOnly.TryParseExact(raw, "yyyy-MM-dd", out DateOnly parsed)) { + return Outcome.Failure(InvalidBookingDateError.Malformed(raw)); + } + + return Outcome.Success(new BookingDate(parsed)); + } + + #endregion + + /// + public int CompareTo(BookingDate? other) { + if (other is null) { return 1; } + + return Value.CompareTo(other.Value); + } + + /// + public bool Equals(BookingDate? other) { + if (other is null) { return false; } + if (ReferenceEquals(this, other)) { return true; } + + return Value.Equals(other.Value); + } + + /// + public override bool Equals(object? obj) { + return ReferenceEquals(this, obj) || (obj is BookingDate other && Equals(other)); + } + + /// + public override int GetHashCode() { + return Value.GetHashCode(); + } + + /// + public override string ToString() { + return Value.ToString("yyyy-MM-dd", System.Globalization.CultureInfo.InvariantCulture); + } + +} diff --git a/FirstClassErrors.RequestBinder.Usage/Model/Currency.cs b/FirstClassErrors.RequestBinder.Usage/Model/Currency.cs new file mode 100644 index 00000000..3d4fadbc --- /dev/null +++ b/FirstClassErrors.RequestBinder.Usage/Model/Currency.cs @@ -0,0 +1,68 @@ +namespace FirstClassErrors.RequestBinder.Usage.Model; + +/// +/// An ISO-4217-style currency code — a reference-type value object parsed from a request string. Used to show an +/// optional-with-fallback binding: absent, it falls back to a configured default rather than failing. +/// +public sealed class Currency : IEquatable { + + #region Constructors declarations + + private Currency(string code) { + Code = code; + } + + #endregion + + /// The three-letter, upper-case currency code (for example EUR). + public string Code { get; } + + #region Statics members declarations + + /// + /// Parses into a , or fails with a documented + /// . Kept deliberately shallow (three upper-case letters): a sample checks + /// the shape, not the full ISO register. + /// + public static Outcome Parse(string raw) { + if (raw is not { Length: 3 } || !IsThreeUpperCaseLetters(raw)) { + return Outcome.Failure(InvalidCurrencyError.Malformed(raw)); + } + + return Outcome.Success(new Currency(raw)); + } + + private static bool IsThreeUpperCaseLetters(string raw) { + foreach (char c in raw) { + if (c is < 'A' or > 'Z') { return false; } + } + + return true; + } + + #endregion + + /// + public bool Equals(Currency? other) { + if (other is null) { return false; } + if (ReferenceEquals(this, other)) { return true; } + + return string.Equals(Code, other.Code, StringComparison.Ordinal); + } + + /// + public override bool Equals(object? obj) { + return ReferenceEquals(this, obj) || (obj is Currency other && Equals(other)); + } + + /// + public override int GetHashCode() { + return StringComparer.Ordinal.GetHashCode(Code); + } + + /// + public override string ToString() { + return Code; + } + +} diff --git a/FirstClassErrors.RequestBinder.Usage/Model/EmailAddress.cs b/FirstClassErrors.RequestBinder.Usage/Model/EmailAddress.cs new file mode 100644 index 00000000..2631ad50 --- /dev/null +++ b/FirstClassErrors.RequestBinder.Usage/Model/EmailAddress.cs @@ -0,0 +1,64 @@ +namespace FirstClassErrors.RequestBinder.Usage.Model; + +/// +/// A guest e-mail address — a reference-type value object parsed from an untrusted request string. +/// +/// +/// Its factory returns an and is the method group a binder passes to +/// AsRequired / AsOptionalReference. Like every value object in FirstClassErrors, it is a +/// sealed class with a private constructor, so its validating factory is the single entry point. +/// +public sealed class EmailAddress : IEquatable { + + #region Constructors declarations + + private EmailAddress(string value) { + Value = value; + } + + #endregion + + /// The validated e-mail address. + public string Value { get; } + + #region Statics members declarations + + /// + /// Parses into an , or fails with a documented + /// . Intentionally minimal (a single @): a sample validates just + /// enough to show the failure path, not to be a production e-mail parser. + /// + public static Outcome Parse(string raw) { + if (string.IsNullOrWhiteSpace(raw) || raw.IndexOf('@') <= 0 || raw.IndexOf('@') != raw.LastIndexOf('@') || raw.EndsWith("@", StringComparison.Ordinal)) { + return Outcome.Failure(InvalidEmailAddressError.Malformed(raw)); + } + + return Outcome.Success(new EmailAddress(raw)); + } + + #endregion + + /// + public bool Equals(EmailAddress? other) { + if (other is null) { return false; } + if (ReferenceEquals(this, other)) { return true; } + + return string.Equals(Value, other.Value, StringComparison.Ordinal); + } + + /// + public override bool Equals(object? obj) { + return ReferenceEquals(this, obj) || (obj is EmailAddress other && Equals(other)); + } + + /// + public override int GetHashCode() { + return StringComparer.Ordinal.GetHashCode(Value); + } + + /// + public override string ToString() { + return Value; + } + +} diff --git a/FirstClassErrors.RequestBinder.Usage/Model/Guest.cs b/FirstClassErrors.RequestBinder.Usage/Model/Guest.cs new file mode 100644 index 00000000..d9862ad0 --- /dev/null +++ b/FirstClassErrors.RequestBinder.Usage/Model/Guest.cs @@ -0,0 +1,9 @@ +namespace FirstClassErrors.RequestBinder.Usage.Model; + +/// +/// A guest on the booking: a required first name and an optional e-mail address. Built by a total constructor +/// (the binder's New terminal) inside the nested per-element binder of the guests list. +/// +/// The guest's first name (bound raw, presence-only). +/// The guest's e-mail address, or null when the request omitted it. +public sealed record Guest(string FirstName, EmailAddress? Email); diff --git a/FirstClassErrors.RequestBinder.Usage/Model/InvalidBookingDateError.cs b/FirstClassErrors.RequestBinder.Usage/Model/InvalidBookingDateError.cs new file mode 100644 index 00000000..89c7d75c --- /dev/null +++ b/FirstClassErrors.RequestBinder.Usage/Model/InvalidBookingDateError.cs @@ -0,0 +1,58 @@ +#region Usings declarations + +using FirstClassErrors.RequestBinder.Usage.Resources; +using FirstClassErrors.RequestBinder.Usage.Utils; + +#endregion + +namespace FirstClassErrors.RequestBinder.Usage.Model; + +/// +/// The domain error a fails with when a request carries a value that is not an ISO +/// yyyy-MM-dd date. +/// +[ProvidesErrorsFor(nameof(BookingDate), + Description = "BookingDate_Source", + DescriptionResourceType = typeof(RequestBinderUsageMessages))] +public static class InvalidBookingDateError { + + #region Statics members declarations + + /// The value carried by the request is not a well-formed ISO date. + [DocumentedBy(nameof(MalformedDocumentation))] + internal static DomainError Malformed(string rawValue) { + return DomainError.Create( + Code.Malformed, + DocumentationFormatter.Format("'{0}' is not a valid ISO (yyyy-MM-dd) date.", rawValue)) + .WithPublicMessage( + RequestBinderUsageMessages.Get("BookingDate_Malformed_ShortMessage"), + RequestBinderUsageMessages.Get("BookingDate_Malformed_DetailedMessage")); + } + + private static ErrorDocumentation MalformedDocumentation() { + return DescribeError.WithTitle(RequestBinderUsageMessages.Get("BookingDate_Malformed_Title")) + .WithDescription(RequestBinderUsageMessages.Get("BookingDate_Malformed_Description")) + .WithRule(RequestBinderUsageMessages.Get("BookingDate_Malformed_Rule")) + .WithDiagnostic(RequestBinderUsageMessages.Get("BookingDate_Malformed_Cause1"), + ErrorOrigin.External, + RequestBinderUsageMessages.Get("BookingDate_Malformed_Hint1")) + .WithExamples(() => Malformed("2026-13-40")); + } + + #endregion + + #region Nested types declarations + + private static class Code { + + #region Statics members declarations + + public static readonly ErrorCode Malformed = ErrorCode.Create("BOOKING_DATE_MALFORMED"); + + #endregion + + } + + #endregion + +} diff --git a/FirstClassErrors.RequestBinder.Usage/Model/InvalidCurrencyError.cs b/FirstClassErrors.RequestBinder.Usage/Model/InvalidCurrencyError.cs new file mode 100644 index 00000000..5c2317d3 --- /dev/null +++ b/FirstClassErrors.RequestBinder.Usage/Model/InvalidCurrencyError.cs @@ -0,0 +1,58 @@ +#region Usings declarations + +using FirstClassErrors.RequestBinder.Usage.Resources; +using FirstClassErrors.RequestBinder.Usage.Utils; + +#endregion + +namespace FirstClassErrors.RequestBinder.Usage.Model; + +/// +/// The domain error a fails with when a request carries a code that is not a well-formed +/// three-letter currency code. +/// +[ProvidesErrorsFor(nameof(Currency), + Description = "Currency_Source", + DescriptionResourceType = typeof(RequestBinderUsageMessages))] +public static class InvalidCurrencyError { + + #region Statics members declarations + + /// The value carried by the request is not a well-formed currency code. + [DocumentedBy(nameof(MalformedDocumentation))] + internal static DomainError Malformed(string rawValue) { + return DomainError.Create( + Code.Malformed, + DocumentationFormatter.Format("'{0}' is not a valid three-letter currency code.", rawValue)) + .WithPublicMessage( + RequestBinderUsageMessages.Get("Currency_Malformed_ShortMessage"), + RequestBinderUsageMessages.Get("Currency_Malformed_DetailedMessage")); + } + + private static ErrorDocumentation MalformedDocumentation() { + return DescribeError.WithTitle(RequestBinderUsageMessages.Get("Currency_Malformed_Title")) + .WithDescription(RequestBinderUsageMessages.Get("Currency_Malformed_Description")) + .WithRule(RequestBinderUsageMessages.Get("Currency_Malformed_Rule")) + .WithDiagnostic(RequestBinderUsageMessages.Get("Currency_Malformed_Cause1"), + ErrorOrigin.External, + RequestBinderUsageMessages.Get("Currency_Malformed_Hint1")) + .WithExamples(() => Malformed("EURO")); + } + + #endregion + + #region Nested types declarations + + private static class Code { + + #region Statics members declarations + + public static readonly ErrorCode Malformed = ErrorCode.Create("CURRENCY_CODE_MALFORMED"); + + #endregion + + } + + #endregion + +} diff --git a/FirstClassErrors.RequestBinder.Usage/Model/InvalidEmailAddressError.cs b/FirstClassErrors.RequestBinder.Usage/Model/InvalidEmailAddressError.cs new file mode 100644 index 00000000..dd7f09b3 --- /dev/null +++ b/FirstClassErrors.RequestBinder.Usage/Model/InvalidEmailAddressError.cs @@ -0,0 +1,58 @@ +#region Usings declarations + +using FirstClassErrors.RequestBinder.Usage.Resources; +using FirstClassErrors.RequestBinder.Usage.Utils; + +#endregion + +namespace FirstClassErrors.RequestBinder.Usage.Model; + +/// +/// The domain error an fails with when a request carries a malformed address. It is the +/// leaf error a binder wraps in REQUEST_ARGUMENT_INVALID when the conversion of a bound property fails. +/// +[ProvidesErrorsFor(nameof(EmailAddress), + Description = "EmailAddress_Source", + DescriptionResourceType = typeof(RequestBinderUsageMessages))] +public static class InvalidEmailAddressError { + + #region Statics members declarations + + /// The value carried by the request is not a well-formed e-mail address. + [DocumentedBy(nameof(MalformedDocumentation))] + internal static DomainError Malformed(string rawValue) { + return DomainError.Create( + Code.Malformed, + DocumentationFormatter.Format("'{0}' is not a valid e-mail address.", rawValue)) + .WithPublicMessage( + RequestBinderUsageMessages.Get("EmailAddress_Malformed_ShortMessage"), + RequestBinderUsageMessages.Get("EmailAddress_Malformed_DetailedMessage")); + } + + private static ErrorDocumentation MalformedDocumentation() { + return DescribeError.WithTitle(RequestBinderUsageMessages.Get("EmailAddress_Malformed_Title")) + .WithDescription(RequestBinderUsageMessages.Get("EmailAddress_Malformed_Description")) + .WithRule(RequestBinderUsageMessages.Get("EmailAddress_Malformed_Rule")) + .WithDiagnostic(RequestBinderUsageMessages.Get("EmailAddress_Malformed_Cause1"), + ErrorOrigin.External, + RequestBinderUsageMessages.Get("EmailAddress_Malformed_Hint1")) + .WithExamples(() => Malformed("not-an-email")); + } + + #endregion + + #region Nested types declarations + + private static class Code { + + #region Statics members declarations + + public static readonly ErrorCode Malformed = ErrorCode.Create("EMAIL_ADDRESS_MALFORMED"); + + #endregion + + } + + #endregion + +} diff --git a/FirstClassErrors.RequestBinder.Usage/Model/InvalidNightCountError.cs b/FirstClassErrors.RequestBinder.Usage/Model/InvalidNightCountError.cs new file mode 100644 index 00000000..2a8bd607 --- /dev/null +++ b/FirstClassErrors.RequestBinder.Usage/Model/InvalidNightCountError.cs @@ -0,0 +1,58 @@ +#region Usings declarations + +using FirstClassErrors.RequestBinder.Usage.Resources; +using FirstClassErrors.RequestBinder.Usage.Utils; + +#endregion + +namespace FirstClassErrors.RequestBinder.Usage.Model; + +/// +/// The domain error a fails with when a request carries a number of nights that is not +/// strictly positive. +/// +[ProvidesErrorsFor(nameof(NightCount), + Description = "NightCount_Source", + DescriptionResourceType = typeof(RequestBinderUsageMessages))] +public static class InvalidNightCountError { + + #region Statics members declarations + + /// The number of nights carried by the request is zero or negative. + [DocumentedBy(nameof(NotStrictlyPositiveDocumentation))] + internal static DomainError NotStrictlyPositive(int nights) { + return DomainError.Create( + Code.NotStrictlyPositive, + DocumentationFormatter.Format("A booking must be for at least one night, but {0} was requested.", nights)) + .WithPublicMessage( + RequestBinderUsageMessages.Get("NightCount_NotPositive_ShortMessage"), + RequestBinderUsageMessages.Get("NightCount_NotPositive_DetailedMessage")); + } + + private static ErrorDocumentation NotStrictlyPositiveDocumentation() { + return DescribeError.WithTitle(RequestBinderUsageMessages.Get("NightCount_NotPositive_Title")) + .WithDescription(RequestBinderUsageMessages.Get("NightCount_NotPositive_Description")) + .WithRule(RequestBinderUsageMessages.Get("NightCount_NotPositive_Rule")) + .WithDiagnostic(RequestBinderUsageMessages.Get("NightCount_NotPositive_Cause1"), + ErrorOrigin.External, + RequestBinderUsageMessages.Get("NightCount_NotPositive_Hint1")) + .WithExamples(() => NotStrictlyPositive(0)); + } + + #endregion + + #region Nested types declarations + + private static class Code { + + #region Statics members declarations + + public static readonly ErrorCode NotStrictlyPositive = ErrorCode.Create("NIGHT_COUNT_NOT_POSITIVE"); + + #endregion + + } + + #endregion + +} diff --git a/FirstClassErrors.RequestBinder.Usage/Model/InvalidRoomNumberError.cs b/FirstClassErrors.RequestBinder.Usage/Model/InvalidRoomNumberError.cs new file mode 100644 index 00000000..4c0efaba --- /dev/null +++ b/FirstClassErrors.RequestBinder.Usage/Model/InvalidRoomNumberError.cs @@ -0,0 +1,58 @@ +#region Usings declarations + +using FirstClassErrors.RequestBinder.Usage.Resources; +using FirstClassErrors.RequestBinder.Usage.Utils; + +#endregion + +namespace FirstClassErrors.RequestBinder.Usage.Model; + +/// +/// The domain error a fails with when a request list carries a room number outside the +/// supported 1–999 range. +/// +[ProvidesErrorsFor(nameof(RoomNumber), + Description = "RoomNumber_Source", + DescriptionResourceType = typeof(RequestBinderUsageMessages))] +public static class InvalidRoomNumberError { + + #region Statics members declarations + + /// An element of the request's room-number list is outside the supported range. + [DocumentedBy(nameof(OutOfRangeDocumentation))] + internal static DomainError OutOfRange(int number) { + return DomainError.Create( + Code.OutOfRange, + DocumentationFormatter.Format("Room number {0} is outside the supported range 1-999.", number)) + .WithPublicMessage( + RequestBinderUsageMessages.Get("RoomNumber_OutOfRange_ShortMessage"), + RequestBinderUsageMessages.Get("RoomNumber_OutOfRange_DetailedMessage")); + } + + private static ErrorDocumentation OutOfRangeDocumentation() { + return DescribeError.WithTitle(RequestBinderUsageMessages.Get("RoomNumber_OutOfRange_Title")) + .WithDescription(RequestBinderUsageMessages.Get("RoomNumber_OutOfRange_Description")) + .WithRule(RequestBinderUsageMessages.Get("RoomNumber_OutOfRange_Rule")) + .WithDiagnostic(RequestBinderUsageMessages.Get("RoomNumber_OutOfRange_Cause1"), + ErrorOrigin.External, + RequestBinderUsageMessages.Get("RoomNumber_OutOfRange_Hint1")) + .WithExamples(() => OutOfRange(1000)); + } + + #endregion + + #region Nested types declarations + + private static class Code { + + #region Statics members declarations + + public static readonly ErrorCode OutOfRange = ErrorCode.Create("ROOM_NUMBER_OUT_OF_RANGE"); + + #endregion + + } + + #endregion + +} diff --git a/FirstClassErrors.RequestBinder.Usage/Model/InvalidStayError.cs b/FirstClassErrors.RequestBinder.Usage/Model/InvalidStayError.cs new file mode 100644 index 00000000..cd0cfded --- /dev/null +++ b/FirstClassErrors.RequestBinder.Usage/Model/InvalidStayError.cs @@ -0,0 +1,64 @@ +#region Usings declarations + +using FirstClassErrors.RequestBinder.Usage.Resources; +using FirstClassErrors.RequestBinder.Usage.Utils; + +#endregion + +namespace FirstClassErrors.RequestBinder.Usage.Model; + +/// +/// The domain error a fails with when its dates individually parse but violate the cross-field +/// rule (check-out not strictly after check-in). Returned by , it surfaces from the +/// binder's Create terminal as-is — the factory owns the rule, so it is not grouped under the field +/// envelope. +/// +[ProvidesErrorsFor(nameof(Stay), + Description = "Stay_Source", + DescriptionResourceType = typeof(RequestBinderUsageMessages))] +public static class InvalidStayError { + + #region Statics members declarations + + /// The requested check-out date is on or before the check-in date. + [DocumentedBy(nameof(CheckOutNotAfterCheckInDocumentation))] + internal static DomainError CheckOutNotAfterCheckIn(BookingDate checkIn, BookingDate checkOut) { + return DomainError.Create( + Code.CheckOutNotAfterCheckIn, + DocumentationFormatter.Format("Check-out {0} must be strictly after check-in {1}.", checkOut.Value, checkIn.Value)) + .WithPublicMessage( + RequestBinderUsageMessages.Get("Stay_CheckOutNotAfterCheckIn_ShortMessage"), + RequestBinderUsageMessages.Get("Stay_CheckOutNotAfterCheckIn_DetailedMessage")); + } + + private static ErrorDocumentation CheckOutNotAfterCheckInDocumentation() { + return DescribeError.WithTitle(RequestBinderUsageMessages.Get("Stay_CheckOutNotAfterCheckIn_Title")) + .WithDescription(RequestBinderUsageMessages.Get("Stay_CheckOutNotAfterCheckIn_Description")) + .WithRule(RequestBinderUsageMessages.Get("Stay_CheckOutNotAfterCheckIn_Rule")) + .WithDiagnostic(RequestBinderUsageMessages.Get("Stay_CheckOutNotAfterCheckIn_Cause1"), + ErrorOrigin.External, + RequestBinderUsageMessages.Get("Stay_CheckOutNotAfterCheckIn_Hint1")) + .WithExamples(() => CheckOutNotAfterCheckIn(SampleDate("2026-08-14"), SampleDate("2026-08-10"))); + } + + private static BookingDate SampleDate(string iso) { + return BookingDate.Parse(iso).GetResultOrThrow(); + } + + #endregion + + #region Nested types declarations + + private static class Code { + + #region Statics members declarations + + public static readonly ErrorCode CheckOutNotAfterCheckIn = ErrorCode.Create("STAY_CHECKOUT_NOT_AFTER_CHECKIN"); + + #endregion + + } + + #endregion + +} diff --git a/FirstClassErrors.RequestBinder.Usage/Model/InvalidTagError.cs b/FirstClassErrors.RequestBinder.Usage/Model/InvalidTagError.cs new file mode 100644 index 00000000..8e4a1bf1 --- /dev/null +++ b/FirstClassErrors.RequestBinder.Usage/Model/InvalidTagError.cs @@ -0,0 +1,58 @@ +#region Usings declarations + +using FirstClassErrors.RequestBinder.Usage.Resources; +using FirstClassErrors.RequestBinder.Usage.Utils; + +#endregion + +namespace FirstClassErrors.RequestBinder.Usage.Model; + +/// +/// The domain error a fails with when a request list carries a malformed tag (empty, too long, +/// or containing whitespace). +/// +[ProvidesErrorsFor(nameof(Tag), + Description = "Tag_Source", + DescriptionResourceType = typeof(RequestBinderUsageMessages))] +public static class InvalidTagError { + + #region Statics members declarations + + /// An element of the request's tag list is not a well-formed tag. + [DocumentedBy(nameof(MalformedDocumentation))] + internal static DomainError Malformed(string rawValue) { + return DomainError.Create( + Code.Malformed, + DocumentationFormatter.Format("'{0}' is not a valid tag.", rawValue)) + .WithPublicMessage( + RequestBinderUsageMessages.Get("Tag_Malformed_ShortMessage"), + RequestBinderUsageMessages.Get("Tag_Malformed_DetailedMessage")); + } + + private static ErrorDocumentation MalformedDocumentation() { + return DescribeError.WithTitle(RequestBinderUsageMessages.Get("Tag_Malformed_Title")) + .WithDescription(RequestBinderUsageMessages.Get("Tag_Malformed_Description")) + .WithRule(RequestBinderUsageMessages.Get("Tag_Malformed_Rule")) + .WithDiagnostic(RequestBinderUsageMessages.Get("Tag_Malformed_Cause1"), + ErrorOrigin.External, + RequestBinderUsageMessages.Get("Tag_Malformed_Hint1")) + .WithExamples(() => Malformed("late checkout")); + } + + #endregion + + #region Nested types declarations + + private static class Code { + + #region Statics members declarations + + public static readonly ErrorCode Malformed = ErrorCode.Create("BOOKING_TAG_MALFORMED"); + + #endregion + + } + + #endregion + +} diff --git a/FirstClassErrors.RequestBinder.Usage/Model/NightCount.cs b/FirstClassErrors.RequestBinder.Usage/Model/NightCount.cs new file mode 100644 index 00000000..a28821d7 --- /dev/null +++ b/FirstClassErrors.RequestBinder.Usage/Model/NightCount.cs @@ -0,0 +1,61 @@ +namespace FirstClassErrors.RequestBinder.Usage.Model; + +/// +/// A strictly-positive number of nights — a value-type value object over . It exists to +/// show the binder's value-type overloads: a nullable value-type request property (int?) bound through a +/// converter over the underlying (), and the AsOptionalValue path that +/// yields a real when the property is absent. +/// +/// +/// A consumer is free to model a value object as a readonly struct, and the binder supports it first-class. +/// The trade-off — the reason FirstClassErrors' own value objects stay classes — is that a struct always +/// exposes default(NightCount) (here a Value of 0) that bypasses ; a +/// consumer choosing a struct accepts that, and simply never relies on a default-constructed instance being valid. +/// +public readonly struct NightCount : IEquatable { + + #region Properties declarations + + /// The validated number of nights (always ≥ 1 when obtained through ). + public int Value { get; private init; } + + #endregion + + #region Statics members declarations + + /// + /// Creates a from , or fails with a documented + /// when it is not strictly positive. This is the + /// Func<int, Outcome<NightCount>> a binder passes to AsRequired / AsOptionalValue. + /// + public static Outcome From(int nights) { + if (nights < 1) { + return Outcome.Failure(InvalidNightCountError.NotStrictlyPositive(nights)); + } + + return Outcome.Success(new NightCount { Value = nights }); + } + + #endregion + + /// + public bool Equals(NightCount other) { + return Value == other.Value; + } + + /// + public override bool Equals(object? obj) { + return obj is NightCount other && Equals(other); + } + + /// + public override int GetHashCode() { + return Value; + } + + /// + public override string ToString() { + return Value.ToString(System.Globalization.CultureInfo.InvariantCulture); + } + +} diff --git a/FirstClassErrors.RequestBinder.Usage/Model/PlaceBookingCommand.cs b/FirstClassErrors.RequestBinder.Usage/Model/PlaceBookingCommand.cs new file mode 100644 index 00000000..7319583d --- /dev/null +++ b/FirstClassErrors.RequestBinder.Usage/Model/PlaceBookingCommand.cs @@ -0,0 +1,26 @@ +namespace FirstClassErrors.RequestBinder.Usage.Model; + +/// +/// The typed command a valid booking request binds into: a record of value objects, ready for the application core. +/// Every field is already validated by construction — the boundary is the only place untrusted input is turned into +/// these types. +/// +/// The booking guest's e-mail address (required). +/// The client-supplied booking reference, bound raw (required, presence-only). +/// The billing currency; defaults when the request omits it. +/// The number of nights (required value-type binding). +/// An optional cap on the number of nights — null when the request omitted it. +/// The validated stay (check-in / check-out), built through a cross-field factory. +/// The booking tags (an empty list when the request omitted them). +/// The requested room numbers (a list of value-type elements). +/// The guests on the booking (a list of nested objects). +public sealed record PlaceBookingCommand( + EmailAddress GuestEmail, + string Reference, + Currency Currency, + NightCount Nights, + NightCount? MaxNights, + Stay Stay, + IReadOnlyList Tags, + IReadOnlyList RoomNumbers, + IReadOnlyList Guests); diff --git a/FirstClassErrors.RequestBinder.Usage/Model/RoomNumber.cs b/FirstClassErrors.RequestBinder.Usage/Model/RoomNumber.cs new file mode 100644 index 00000000..8be1fc02 --- /dev/null +++ b/FirstClassErrors.RequestBinder.Usage/Model/RoomNumber.cs @@ -0,0 +1,65 @@ +namespace FirstClassErrors.RequestBinder.Usage.Model; + +/// +/// A hotel room number in the range 1–999 — a second value-type value object over . It +/// exists to show binding a list of value-type properties (IReadOnlyList<int?>): each element is +/// converted through over the underlying , and a null element is +/// reported under its indexed path. +/// +/// +/// See for the note on why a consumer may model a value object as a struct while +/// the library keeps its own as classes. +/// +public readonly struct RoomNumber : IEquatable { + + #region Constants + + private const int Lowest = 1; + private const int Highest = 999; + + #endregion + + #region Properties declarations + + /// The validated room number (always within 1–999 when obtained through ). + public int Value { get; private init; } + + #endregion + + #region Statics members declarations + + /// + /// Creates a from , or fails with a documented + /// when it is outside the 1–999 range. + /// + public static Outcome From(int number) { + if (number is < Lowest or > Highest) { + return Outcome.Failure(InvalidRoomNumberError.OutOfRange(number)); + } + + return Outcome.Success(new RoomNumber { Value = number }); + } + + #endregion + + /// + public bool Equals(RoomNumber other) { + return Value == other.Value; + } + + /// + public override bool Equals(object? obj) { + return obj is RoomNumber other && Equals(other); + } + + /// + public override int GetHashCode() { + return Value; + } + + /// + public override string ToString() { + return Value.ToString(System.Globalization.CultureInfo.InvariantCulture); + } + +} diff --git a/FirstClassErrors.RequestBinder.Usage/Model/Stay.cs b/FirstClassErrors.RequestBinder.Usage/Model/Stay.cs new file mode 100644 index 00000000..e5fbc713 --- /dev/null +++ b/FirstClassErrors.RequestBinder.Usage/Model/Stay.cs @@ -0,0 +1,43 @@ +namespace FirstClassErrors.RequestBinder.Usage.Model; + +/// +/// A validated stay: a check-in and a check-out date, where check-out is strictly after check-in. It is built by a +/// validating factory () that enforces the cross-field rule no single field could check +/// on its own — the case the binder's Create terminal exists for. +/// +public sealed record Stay { + + #region Constructors declarations + + private Stay(BookingDate checkIn, BookingDate checkOut) { + CheckIn = checkIn; + CheckOut = checkOut; + } + + #endregion + + /// The (inclusive) check-in date. + public BookingDate CheckIn { get; } + + /// The (exclusive) check-out date, strictly after . + public BookingDate CheckOut { get; } + + #region Statics members declarations + + /// + /// Builds a from two already-parsed dates, or fails with a documented + /// when check-out is not strictly after check-in. This is the + /// Command.Create(...) the binder's Create terminal flattens, so the cross-field failure surfaces + /// directly instead of nesting a second . + /// + public static Outcome Create(BookingDate checkIn, BookingDate checkOut) { + if (checkOut.Value <= checkIn.Value) { + return Outcome.Failure(InvalidStayError.CheckOutNotAfterCheckIn(checkIn, checkOut)); + } + + return Outcome.Success(new Stay(checkIn, checkOut)); + } + + #endregion + +} diff --git a/FirstClassErrors.RequestBinder.Usage/Model/Tag.cs b/FirstClassErrors.RequestBinder.Usage/Model/Tag.cs new file mode 100644 index 00000000..555ea2f3 --- /dev/null +++ b/FirstClassErrors.RequestBinder.Usage/Model/Tag.cs @@ -0,0 +1,68 @@ +namespace FirstClassErrors.RequestBinder.Usage.Model; + +/// +/// A free-form booking tag (for example vip or late-checkout) — a reference-type value object parsed +/// from each element of a request list. Used to show binding a list of simple properties, where every +/// failing element is reported under its own indexed path. +/// +public sealed class Tag : IEquatable { + + #region Constructors declarations + + private Tag(string value) { + Value = value; + } + + #endregion + + /// The validated tag. + public string Value { get; } + + #region Statics members declarations + + /// + /// Parses into a , or fails with a documented + /// . A tag is a single non-empty token without whitespace. + /// + public static Outcome Parse(string raw) { + if (string.IsNullOrEmpty(raw) || raw.Length > 32 || ContainsWhitespace(raw)) { + return Outcome.Failure(InvalidTagError.Malformed(raw)); + } + + return Outcome.Success(new Tag(raw)); + } + + private static bool ContainsWhitespace(string raw) { + foreach (char c in raw) { + if (char.IsWhiteSpace(c)) { return true; } + } + + return false; + } + + #endregion + + /// + public bool Equals(Tag? other) { + if (other is null) { return false; } + if (ReferenceEquals(this, other)) { return true; } + + return string.Equals(Value, other.Value, StringComparison.Ordinal); + } + + /// + public override bool Equals(object? obj) { + return ReferenceEquals(this, obj) || (obj is Tag other && Equals(other)); + } + + /// + public override int GetHashCode() { + return StringComparer.Ordinal.GetHashCode(Value); + } + + /// + public override string ToString() { + return Value; + } + +} diff --git a/FirstClassErrors.RequestBinder.Usage/Options/SnakeCaseArgumentNames.cs b/FirstClassErrors.RequestBinder.Usage/Options/SnakeCaseArgumentNames.cs new file mode 100644 index 00000000..12725726 --- /dev/null +++ b/FirstClassErrors.RequestBinder.Usage/Options/SnakeCaseArgumentNames.cs @@ -0,0 +1,42 @@ +#region Usings declarations + +using System.Reflection; +using System.Text; + +#endregion + +namespace FirstClassErrors.RequestBinder.Usage.Options; + +/// +/// A sample that reports argument paths in snake_case (for example +/// guest_email, stay.check_in) instead of the C# property names. Plugged in through +/// Bind.WithOptions(new RequestBinderOptions(new SnakeCaseArgumentNames())), it makes the paths reported in +/// binding errors match the keys a snake_case JSON client actually sent. A real provider would usually read the +/// serializer's naming policy or a [JsonPropertyName] attribute; this one derives the name mechanically to +/// stay dependency-free. +/// +public sealed class SnakeCaseArgumentNames : IArgumentNameProvider { + + /// + public string GetArgumentNameFrom(PropertyInfo property) { + ArgumentNullException.ThrowIfNull(property); + + return ToSnakeCase(property.Name); + } + + private static string ToSnakeCase(string name) { + StringBuilder builder = new(name.Length + 8); + for (int i = 0; i < name.Length; i++) { + char c = name[i]; + if (char.IsUpper(c)) { + if (i > 0) { builder.Append('_'); } + builder.Append(char.ToLowerInvariant(c)); + } else { + builder.Append(c); + } + } + + return builder.ToString(); + } + +} diff --git a/FirstClassErrors.RequestBinder.Usage/Resources/RequestBinderUsageMessages.cs b/FirstClassErrors.RequestBinder.Usage/Resources/RequestBinderUsageMessages.cs new file mode 100644 index 00000000..6756a5cb --- /dev/null +++ b/FirstClassErrors.RequestBinder.Usage/Resources/RequestBinderUsageMessages.cs @@ -0,0 +1,32 @@ +#region Usings declarations + +using System.Resources; + +#endregion + +namespace FirstClassErrors.RequestBinder.Usage.Resources; + +/// +/// The localized text of the sample's errors (titles, explanations, rules, diagnostics and public messages). +/// Values are read from the RequestBinderUsageMessages resources for the current UI culture — which the +/// documentation worker sets per run — falling back to the neutral (English) resources, and finally to the key +/// itself. Mirrors UsageErrorMessages in FirstClassErrors.Usage. +/// +/// +/// This type doubles as the DescriptionResourceType passed to [ProvidesErrorsFor]: the extractor +/// resolves a source group's Description key against these same resources. +/// +internal static class RequestBinderUsageMessages { + + #region Statics members declarations + + private static readonly ResourceManager Resources = new(typeof(RequestBinderUsageMessages)); + + /// Returns the localized string for in the current UI culture. + public static string Get(string key) { + return Resources.GetString(key) ?? key; + } + + #endregion + +} diff --git a/FirstClassErrors.RequestBinder.Usage/Resources/RequestBinderUsageMessages.de.resx b/FirstClassErrors.RequestBinder.Usage/Resources/RequestBinderUsageMessages.de.resx new file mode 100644 index 00000000..bb1f9e7a --- /dev/null +++ b/FirstClassErrors.RequestBinder.Usage/Resources/RequestBinderUsageMessages.de.resx @@ -0,0 +1,304 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + Fehler, die beim Parsen der E-Mail-Adresse eines Gastes aus einer Anforderungszeichenfolge ausgelöst werden. + + + Fehler, die beim Parsen eines Abrechnungswährungscodes aus einer Anforderungszeichenfolge ausgelöst werden. + + + Fehler, die beim Parsen eines Buchungs-Tags aus einem Element einer Anforderungsliste ausgelöst werden. + + + Fehler, die beim Parsen eines Buchungsdatums (Anreise / Abreise) aus einer Anforderungszeichenfolge ausgelöst werden. + + + Fehler, die beim Erstellen der Anzahl der Nächte aus einem Anforderungswert ausgelöst werden. + + + Fehler, die beim Erstellen einer Zimmernummer aus einem Element einer Anforderungsliste ausgelöst werden. + + + Fehler, die bei der gemeinsamen Validierung von An- und Abreisedatum eines Aufenthalts ausgelöst werden. + + + Primär-Port-Fehler, die vom Buchungsendpunkt ausgelöst werden, wenn er eine eingehende Anforderung an einen Befehl bindet. + + + + Fehlerhafte E-Mail-Adresse + + + Eine eingehende Anforderung enthält einen Wert, der keine wohlgeformte E-Mail-Adresse ist und daher nicht in ein EmailAddress-Value-Object geparst werden kann. + + + Eine E-Mail-Adresse eines Gastes muss genau ein „@“ mit einem nicht leeren lokalen Teil und einer nicht leeren Domäne enthalten. + + + Der Client hat eine falsch geschriebene oder abgeschnittene Adresse gesendet (fehlendes „@“, leerer lokaler Teil oder leere Domäne). + + + Validieren Sie die Adresse auf dem Client und vergleichen Sie den gesendeten Wert mit dem erwarteten Format. + + + Die E-Mail-Adresse ist ungültig. + + + Der angegebene Wert ist keine gültige E-Mail-Adresse. + + + + Fehlerhafter Währungscode + + + Eine eingehende Anforderung enthält einen Wert, der kein wohlgeformter dreistelliger Währungscode ist und daher nicht in ein Currency-Value-Object geparst werden kann. + + + Ein Währungscode muss aus genau drei ASCII-Großbuchstaben bestehen (zum Beispiel EUR). + + + Der Client hat eine Währung in der falschen Form gesendet (Kleinbuchstaben, ein Symbol, ein Name oder die falsche Länge). + + + Senden Sie den alphabetischen ISO-4217-Code in Großbuchstaben, zum Beispiel USD oder EUR. + + + Die Währung ist ungültig. + + + Der Abrechnungswährungscode ist kein gültiger dreistelliger Code. + + + + Fehlerhaftes Buchungs-Tag + + + Ein Element der Tag-Liste der Anforderung ist leer, zu lang oder enthält Leerzeichen und kann daher nicht in ein Tag-Value-Object geparst werden. + + + Ein Tag muss ein einzelnes, nicht leeres Token von höchstens 32 Zeichen ohne Leerzeichen sein. + + + Der Client hat ein leeres Tag, einen Ausdruck mit Leerzeichen oder einen zu langen Wert gesendet. + + + Senden Sie jedes Tag als einzelnes Token ohne Leerzeichen; verbinden Sie mehrwortige Tags mit einem Bindestrich. + + + Ein Tag ist ungültig. + + + Eines der Buchungs-Tags ist kein gültiges einzelnes Token. + + + + Fehlerhaftes Buchungsdatum + + + Eine eingehende Anforderung enthält einen Wert, der kein ISO-Datum (yyyy-MM-dd) ist und daher nicht in ein BookingDate-Value-Object geparst werden kann. + + + Ein Buchungsdatum muss ein ISO-Kalenderdatum im Format yyyy-MM-dd sein. + + + Der Client hat ein Datum in einem länderspezifischen oder fehlerhaften Format oder ein unmögliches Kalenderdatum gesendet. + + + Senden Sie Datumsangaben im Format ISO 8601 yyyy-MM-dd, zum Beispiel 2026-08-10. + + + Das Datum ist ungültig. + + + Ein Buchungsdatum ist kein gültiges ISO-Datum (yyyy-MM-dd). + + + + Nicht positive Anzahl von Nächten + + + Eine eingehende Anforderung verlangt null oder eine negative Anzahl von Nächten, was keine gültige Aufenthaltsdauer ist. + + + Eine Buchung muss mindestens eine Nacht umfassen. + + + Der Client hat eine Anzahl von Nächten von null oder weniger gesendet. + + + Senden Sie eine Anzahl von Nächten von eins oder mehr. + + + Die Anzahl der Nächte ist ungültig. + + + Die angeforderte Anzahl von Nächten muss eins oder mehr betragen. + + + + Zimmernummer außerhalb des Bereichs + + + Ein Element der Zimmernummernliste der Anforderung liegt außerhalb des unterstützten Bereichs (1-999). + + + Eine Zimmernummer muss zwischen 1 und 999 (einschließlich) liegen. + + + Der Client hat eine Zimmernummer von null, einen negativen Wert oder einen Wert über 999 gesendet. + + + Senden Sie Zimmernummern innerhalb des unterstützten Bereichs (1-999). + + + Eine Zimmernummer ist ungültig. + + + Eine der angeforderten Zimmernummern liegt außerhalb des unterstützten Bereichs. + + + + Abreise nicht nach Anreise + + + Beide Aufenthaltsdaten werden geparst, aber das Abreisedatum liegt vor oder auf dem Anreisedatum, sodass der Aufenthalt keine positive Dauer hat. Diese feldübergreifende Regel wird von der Stay.Create-Factory durchgesetzt. + + + Die Abreise muss streng nach der Anreise liegen. + + + Der Client hat ein Abreisedatum gesendet, das gleich oder früher als das Anreisedatum ist. + + + Senden Sie ein Abreisedatum, das mindestens einen Tag nach dem Anreisedatum liegt. + + + Die Aufenthaltsdaten sind ungültig. + + + Das Abreisedatum muss nach dem Anreisedatum liegen. + + + + Buchungsanforderung ungültig + + + Der Endpunkt konnte die eingehende Anforderung nicht an einen Buchungsbefehl binden: Ein oder mehrere Argumente fehlten oder waren ungültig. Jeder Fehler wird unter diesem Umschlag gesammelt, jeweils mit seinem vollständigen Argumentpfad. + + + Jedes erforderliche Argument muss vorhanden sein, und jedes Argument muss in sein Value-Object konvertiert werden. + + + Der Client hat eine Anforderung gesendet, die den Vertrag des Endpunkts verletzt (fehlende oder fehlerhafte Argumente). + + + Lesen Sie die inneren Fehler: Jeder benennt das fehlerhafte Argument und die verletzte Regel. + + + Wir konnten Ihre Buchungsanforderung nicht annehmen. + + + Eine oder mehrere Angaben der Buchungsanforderung fehlen oder sind ungültig. + + + Aufenthalt der Buchung ungültig + + + Das Aufenthalts-Unterobjekt der Anforderung konnte nicht gebunden werden: Eines oder beide seiner Daten fehlten oder waren fehlerhaft. Seine Fehler werden unter diesem verschachtelten Umschlag gruppiert, mit durch Stay präfixierten Pfaden. + + + Beide Aufenthaltsdaten müssen vorhanden und gültige ISO-Daten sein. + + + Der Client hat einen Aufenthalt mit einem fehlenden oder fehlerhaften An- oder Abreisedatum gesendet. + + + Lesen Sie die inneren Fehler unter dem Stay-Pfad für das fehlerhafte Datum. + + + Wir konnten die Aufenthaltsdaten nicht lesen. + + + Das An- oder Abreisedatum des Aufenthalts fehlt oder ist ungültig. + + + Gast der Buchung ungültig + + + Ein Gast der Gästeliste der Anforderung konnte nicht gebunden werden: Sein Vorname fehlte oder seine E-Mail war fehlerhaft. Seine Fehler werden unter diesem Umschlag pro Element gruppiert, mit indizierten Pfaden wie Guests[1]. + + + Jeder Gast muss einen Vornamen haben, und jede vorhandene E-Mail muss gültig sein. + + + Der Client hat einen Gast mit einem fehlenden Vornamen oder einer fehlerhaften E-Mail-Adresse gesendet. + + + Lesen Sie die inneren Fehler unter dem indizierten Pfad Guests[i] für das fehlerhafte Feld. + + + Die Angaben eines Gastes sind ungültig. + + + Einer der Gäste hat keinen Vornamen oder eine ungültige E-Mail-Adresse. + + diff --git a/FirstClassErrors.RequestBinder.Usage/Resources/RequestBinderUsageMessages.es.resx b/FirstClassErrors.RequestBinder.Usage/Resources/RequestBinderUsageMessages.es.resx new file mode 100644 index 00000000..89a6bd0d --- /dev/null +++ b/FirstClassErrors.RequestBinder.Usage/Resources/RequestBinderUsageMessages.es.resx @@ -0,0 +1,304 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + Errores generados al analizar la dirección de correo electrónico de un huésped a partir de una cadena de la solicitud. + + + Errores generados al analizar un código de moneda de facturación a partir de una cadena de la solicitud. + + + Errores generados al analizar una etiqueta de reserva a partir de un elemento de una lista de la solicitud. + + + Errores generados al analizar una fecha de reserva (entrada / salida) a partir de una cadena de la solicitud. + + + Errores generados al construir el número de noches a partir de un valor de la solicitud. + + + Errores generados al construir un número de habitación a partir de un elemento de una lista de la solicitud. + + + Errores generados al validar conjuntamente las fechas de entrada y salida de una estancia. + + + Errores de puerto primario generados por el endpoint de reserva al enlazar una solicitud entrante en un comando. + + + + Dirección de correo electrónico mal formada + + + Una solicitud entrante contiene un valor que no es una dirección de correo electrónico bien formada, por lo que no puede analizarse en un value object EmailAddress. + + + Una dirección de correo electrónico de huésped debe contener una sola « @ » con una parte local y un dominio no vacíos. + + + El cliente envió una dirección mal escrita o truncada (falta « @ », parte local o dominio vacíos). + + + Valide la dirección en el cliente y compare el valor enviado con el formato esperado. + + + La dirección de correo electrónico no es válida. + + + El valor proporcionado no es una dirección de correo electrónico válida. + + + + Código de moneda mal formado + + + Una solicitud entrante contiene un valor que no es un código de moneda de tres letras bien formado, por lo que no puede analizarse en un value object Currency. + + + Un código de moneda debe constar exactamente de tres letras ASCII mayúsculas (por ejemplo, EUR). + + + El cliente envió una moneda con la forma incorrecta (minúsculas, un símbolo, un nombre o una longitud incorrecta). + + + Envíe el código alfabético ISO-4217 en mayúsculas, por ejemplo USD o EUR. + + + La moneda no es válida. + + + El código de moneda de facturación no es un código de tres letras válido. + + + + Etiqueta de reserva mal formada + + + Un elemento de la lista de etiquetas de la solicitud está vacío, es demasiado largo o contiene espacios, por lo que no puede analizarse en un value object Tag. + + + Una etiqueta debe ser un único token no vacío de un máximo de 32 caracteres, sin espacios. + + + El cliente envió una etiqueta vacía, una expresión con espacios o un valor demasiado largo. + + + Envíe cada etiqueta como un único token sin espacios; una las etiquetas de varias palabras con un guion. + + + Una etiqueta no es válida. + + + Una de las etiquetas de reserva no es un token único válido. + + + + Fecha de reserva mal formada + + + Una solicitud entrante contiene un valor que no es una fecha ISO yyyy-MM-dd, por lo que no puede analizarse en un value object BookingDate. + + + Una fecha de reserva debe ser una fecha de calendario ISO en formato yyyy-MM-dd. + + + El cliente envió una fecha en un formato local o mal formado, o una fecha de calendario imposible. + + + Envíe las fechas en formato ISO 8601 yyyy-MM-dd, por ejemplo 2026-08-10. + + + La fecha no es válida. + + + Una fecha de reserva no es una fecha ISO (yyyy-MM-dd) válida. + + + + Número de noches no positivo + + + Una solicitud entrante pide cero o un número negativo de noches, lo que no es una duración de estancia válida. + + + Una reserva debe ser de al menos una noche. + + + El cliente envió un número de noches de cero o inferior. + + + Envíe un número de noches de una o más. + + + El número de noches no es válido. + + + El número de noches solicitado debe ser de una o más. + + + + Número de habitación fuera de rango + + + Un elemento de la lista de números de habitación de la solicitud está fuera del rango admitido (1-999). + + + Un número de habitación debe estar entre 1 y 999 inclusive. + + + El cliente envió un número de habitación de cero, negativo o superior a 999. + + + Envíe números de habitación dentro del rango admitido (1-999). + + + Un número de habitación no es válido. + + + Uno de los números de habitación solicitados está fuera del rango admitido. + + + + Salida no posterior a la entrada + + + Ambas fechas de la estancia se analizan, pero la fecha de salida es anterior o igual a la fecha de entrada, por lo que la estancia no tiene una duración positiva. Esta regla entre campos la aplica la factory Stay.Create. + + + La salida debe ser estrictamente posterior a la entrada. + + + El cliente envió una fecha de salida igual o anterior a la fecha de entrada. + + + Envíe una fecha de salida al menos un día después de la fecha de entrada. + + + Las fechas de la estancia no son válidas. + + + La fecha de salida debe ser posterior a la fecha de entrada. + + + + Solicitud de reserva no válida + + + El endpoint no pudo enlazar la solicitud entrante en un comando de reserva: uno o varios argumentos faltaban o no eran válidos. Cada fallo se recopila bajo este sobre, con su ruta de argumento completa. + + + Todos los argumentos requeridos deben estar presentes, y cada argumento debe convertirse en su value object. + + + El cliente envió una solicitud que incumple el contrato del endpoint (argumentos faltantes o mal formados). + + + Lea los errores internos: cada uno nombra el argumento fallido y la regla que incumple. + + + No pudimos aceptar su solicitud de reserva. + + + Uno o varios datos de la solicitud de reserva faltan o no son válidos. + + + Estancia de la reserva no válida + + + El subobjeto de estancia de la solicitud no pudo enlazarse: una o ambas de sus fechas faltaban o estaban mal formadas. Sus fallos se agrupan bajo este sobre anidado, con rutas prefijadas por Stay. + + + Ambas fechas de la estancia deben estar presentes y ser fechas ISO válidas. + + + El cliente envió una estancia con una fecha de entrada o de salida faltante o mal formada. + + + Lea los errores internos bajo la ruta Stay para la fecha fallida. + + + No pudimos leer las fechas de la estancia. + + + La fecha de entrada o de salida de la estancia falta o no es válida. + + + Huésped de la reserva no válido + + + Un huésped de la lista de huéspedes de la solicitud no pudo enlazarse: su nombre faltaba o su correo electrónico estaba mal formado. Sus fallos se agrupan bajo este sobre por elemento, con rutas indexadas como Guests[1]. + + + Cada huésped debe tener un nombre, y todo correo electrónico presente debe ser válido. + + + El cliente envió un huésped con un nombre faltante o una dirección de correo electrónico mal formada. + + + Lea los errores internos bajo la ruta indexada Guests[i] para el campo fallido. + + + La información de un huésped no es válida. + + + Uno de los huéspedes no tiene nombre o tiene una dirección de correo electrónico no válida. + + diff --git a/FirstClassErrors.RequestBinder.Usage/Resources/RequestBinderUsageMessages.fr.resx b/FirstClassErrors.RequestBinder.Usage/Resources/RequestBinderUsageMessages.fr.resx new file mode 100644 index 00000000..c00318d3 --- /dev/null +++ b/FirstClassErrors.RequestBinder.Usage/Resources/RequestBinderUsageMessages.fr.resx @@ -0,0 +1,304 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + Erreurs levées lors de l'analyse d'une adresse e-mail de voyageur à partir d'une chaîne de requête. + + + Erreurs levées lors de l'analyse d'un code de devise de facturation à partir d'une chaîne de requête. + + + Erreurs levées lors de l'analyse d'une étiquette de réservation à partir d'un élément d'une liste de requête. + + + Erreurs levées lors de l'analyse d'une date de réservation (arrivée / départ) à partir d'une chaîne de requête. + + + Erreurs levées lors de la construction du nombre de nuits à partir d'une valeur de requête. + + + Erreurs levées lors de la construction d'un numéro de chambre à partir d'un élément d'une liste de requête. + + + Erreurs levées lors de la validation conjointe des dates d'arrivée et de départ d'un séjour. + + + Erreurs de port primaire levées par le point d'entrée de réservation lorsqu'il lie une requête entrante en une commande. + + + + Adresse e-mail mal formée + + + Une requête entrante porte une valeur qui n'est pas une adresse e-mail bien formée : elle ne peut donc pas être analysée en un value object EmailAddress. + + + Une adresse e-mail de voyageur doit contenir un seul « @ » avec une partie locale et un domaine non vides. + + + Le client a envoyé une adresse mal orthographiée ou tronquée (« @ » manquant, partie locale ou domaine vide). + + + Validez l'adresse côté client et comparez la valeur envoyée au format attendu. + + + L'adresse e-mail est invalide. + + + La valeur fournie n'est pas une adresse e-mail valide. + + + + Code de devise mal formé + + + Une requête entrante porte une valeur qui n'est pas un code de devise à trois lettres bien formé : elle ne peut donc pas être analysée en un value object Currency. + + + Un code de devise doit être composé d'exactement trois lettres ASCII majuscules (par exemple EUR). + + + Le client a envoyé une devise dans la mauvaise forme (minuscules, symbole, nom, ou mauvaise longueur). + + + Envoyez le code alphabétique ISO-4217 en majuscules, par exemple USD ou EUR. + + + La devise est invalide. + + + Le code de devise de facturation n'est pas un code à trois lettres valide. + + + + Étiquette de réservation mal formée + + + Un élément de la liste d'étiquettes de la requête est vide, trop long ou contient des espaces : il ne peut donc pas être analysé en un value object Tag. + + + Une étiquette doit être un unique jeton non vide d'au plus 32 caractères, sans espace. + + + Le client a envoyé une étiquette vide, une expression contenant des espaces, ou une valeur trop longue. + + + Envoyez chaque étiquette comme un unique jeton sans espace ; reliez les étiquettes en plusieurs mots par un trait d'union. + + + Une étiquette est invalide. + + + L'une des étiquettes de réservation n'est pas un jeton unique valide. + + + + Date de réservation mal formée + + + Une requête entrante porte une valeur qui n'est pas une date ISO yyyy-MM-dd : elle ne peut donc pas être analysée en un value object BookingDate. + + + Une date de réservation doit être une date calendaire ISO au format yyyy-MM-dd. + + + Le client a envoyé une date dans un format localisé ou mal formé, ou une date calendaire impossible. + + + Envoyez les dates au format ISO 8601 yyyy-MM-dd, par exemple 2026-08-10. + + + La date est invalide. + + + Une date de réservation n'est pas une date ISO (yyyy-MM-dd) valide. + + + + Nombre de nuits non positif + + + Une requête entrante demande zéro ou un nombre négatif de nuits, ce qui n'est pas une durée de séjour valide. + + + Une réservation doit porter sur au moins une nuit. + + + Le client a envoyé un nombre de nuits nul ou négatif. + + + Envoyez un nombre de nuits d'une ou plus. + + + Le nombre de nuits est invalide. + + + Le nombre de nuits demandé doit être d'au moins une. + + + + Numéro de chambre hors plage + + + Un élément de la liste de numéros de chambre de la requête est en dehors de la plage prise en charge (1-999). + + + Un numéro de chambre doit être compris entre 1 et 999 inclus. + + + Le client a envoyé un numéro de chambre nul, négatif, ou supérieur à 999. + + + Envoyez des numéros de chambre dans la plage prise en charge (1-999). + + + Un numéro de chambre est invalide. + + + L'un des numéros de chambre demandés est en dehors de la plage prise en charge. + + + + Départ non postérieur à l'arrivée + + + Les deux dates du séjour sont analysées, mais la date de départ est antérieure ou égale à la date d'arrivée : le séjour n'a donc pas de durée positive. Cette règle inter-champs est appliquée par la fabrique Stay.Create. + + + Le départ doit être strictement postérieur à l'arrivée. + + + Le client a envoyé une date de départ égale ou antérieure à la date d'arrivée. + + + Envoyez une date de départ au moins un jour après la date d'arrivée. + + + Les dates du séjour sont invalides. + + + La date de départ doit être postérieure à la date d'arrivée. + + + + Requête de réservation invalide + + + Le point d'entrée n'a pas pu lier la requête entrante en une commande de réservation : un ou plusieurs arguments étaient manquants ou invalides. Chaque échec est collecté sous cette enveloppe, avec son chemin d'argument complet. + + + Chaque argument requis doit être présent, et chaque argument doit se convertir en son value object. + + + Le client a envoyé une requête qui viole le contrat du point d'entrée (arguments manquants ou mal formés). + + + Lisez les erreurs internes : chacune nomme l'argument défaillant et la règle qu'il viole. + + + Nous n'avons pas pu accepter votre demande de réservation. + + + Un ou plusieurs détails de la demande de réservation sont manquants ou invalides. + + + Séjour de la réservation invalide + + + Le sous-objet séjour de la requête n'a pas pu être lié : une ou deux de ses dates étaient manquantes ou mal formées. Ses échecs sont regroupés sous cette enveloppe imbriquée, avec des chemins préfixés par Stay. + + + Les deux dates du séjour doivent être présentes et être des dates ISO valides. + + + Le client a envoyé un séjour avec une date d'arrivée ou de départ manquante ou mal formée. + + + Lisez les erreurs internes sous le chemin Stay pour la date défaillante. + + + Nous n'avons pas pu lire les dates du séjour. + + + La date d'arrivée ou de départ du séjour est manquante ou invalide. + + + Voyageur de la réservation invalide + + + Un voyageur de la liste de voyageurs de la requête n'a pas pu être lié : son prénom était manquant ou son e-mail mal formé. Ses échecs sont regroupés sous cette enveloppe par élément, avec des chemins indexés tels que Guests[1]. + + + Chaque voyageur doit avoir un prénom, et tout e-mail présent doit être valide. + + + Le client a envoyé un voyageur avec un prénom manquant ou une adresse e-mail mal formée. + + + Lisez les erreurs internes sous le chemin indexé Guests[i] pour le champ défaillant. + + + Les informations d'un voyageur sont invalides. + + + L'un des voyageurs n'a pas de prénom ou a une adresse e-mail invalide. + + diff --git a/FirstClassErrors.RequestBinder.Usage/Resources/RequestBinderUsageMessages.resx b/FirstClassErrors.RequestBinder.Usage/Resources/RequestBinderUsageMessages.resx new file mode 100644 index 00000000..5e584c62 --- /dev/null +++ b/FirstClassErrors.RequestBinder.Usage/Resources/RequestBinderUsageMessages.resx @@ -0,0 +1,304 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + Errors raised when parsing a guest e-mail address from a request string. + + + Errors raised when parsing a billing currency code from a request string. + + + Errors raised when parsing a booking tag from an element of a request list. + + + Errors raised when parsing a booking date (check-in / check-out) from a request string. + + + Errors raised when building the number of nights from a request value. + + + Errors raised when building a room number from an element of a request list. + + + Errors raised when validating a stay's check-in and check-out dates together. + + + Primary-port errors raised by the booking endpoint when it binds an incoming request into a command. + + + + Malformed e-mail address + + + An incoming request carries a value that is not a well-formed e-mail address, so it cannot be parsed into an EmailAddress value object. + + + A guest e-mail address must contain a single '@' with a non-empty local part and domain. + + + The client sent a misspelled or truncated address (missing '@', empty local part or domain). + + + Validate the address on the client, and compare the sent value against the expected format. + + + The e-mail address is invalid. + + + The value provided is not a valid e-mail address. + + + + Malformed currency code + + + An incoming request carries a value that is not a well-formed three-letter currency code, so it cannot be parsed into a Currency value object. + + + A currency code must be exactly three upper-case ASCII letters (for example EUR). + + + The client sent a currency in the wrong shape (lower-case, a symbol, a name, or the wrong length). + + + Send the ISO-4217 alphabetic code in upper case, for example USD or EUR. + + + The currency is invalid. + + + The billing currency code is not a valid three-letter code. + + + + Malformed booking tag + + + An element of the request's tag list is empty, too long, or contains whitespace, so it cannot be parsed into a Tag value object. + + + A tag must be a single non-empty token of at most 32 characters, without whitespace. + + + The client sent a blank tag, a phrase containing spaces, or an over-long value. + + + Send each tag as a single whitespace-free token; join multi-word tags with a hyphen. + + + A tag is invalid. + + + One of the booking tags is not a valid single token. + + + + Malformed booking date + + + An incoming request carries a value that is not an ISO yyyy-MM-dd date, so it cannot be parsed into a BookingDate value object. + + + A booking date must be an ISO calendar date in the yyyy-MM-dd format. + + + The client sent a date in a locale-specific or malformed format, or an impossible calendar date. + + + Send dates in ISO 8601 yyyy-MM-dd form, for example 2026-08-10. + + + The date is invalid. + + + A booking date is not a valid ISO (yyyy-MM-dd) date. + + + + Non-positive number of nights + + + An incoming request asks for zero or a negative number of nights, which is not a valid stay length. + + + A booking must be for at least one night. + + + The client sent a number of nights of zero or below. + + + Send a number of nights of one or more. + + + The number of nights is invalid. + + + The requested number of nights must be one or more. + + + + Room number out of range + + + An element of the request's room-number list is outside the supported 1-999 range. + + + A room number must be between 1 and 999 inclusive. + + + The client sent a room number of zero, a negative value, or a value above 999. + + + Send room numbers within the supported 1-999 range. + + + A room number is invalid. + + + One of the requested room numbers is outside the supported range. + + + + Check-out not after check-in + + + Both stay dates parse, but the check-out date is on or before the check-in date, so the stay has no positive length. This cross-field rule is enforced by the Stay.Create factory. + + + Check-out must be strictly after check-in. + + + The client sent a check-out date equal to or earlier than the check-in date. + + + Send a check-out date at least one day after the check-in date. + + + The stay dates are invalid. + + + The check-out date must be after the check-in date. + + + + Booking request invalid + + + The endpoint could not bind the incoming request into a booking command: one or more arguments were missing or invalid. Every failure is collected under this envelope, each with its full argument path. + + + Every required argument must be present, and every argument must convert into its value object. + + + The client sent a request that violates the endpoint's contract (missing or malformed arguments). + + + Read the inner errors: each names the failing argument and the rule it violated. + + + We could not accept your booking request. + + + One or more details of the booking request are missing or invalid. + + + Booking stay invalid + + + The stay sub-object of the request could not be bound: one or both of its dates were missing or malformed. Its failures are grouped under this nested envelope, with paths prefixed by Stay. + + + Both stay dates must be present and valid ISO dates. + + + The client sent a stay with a missing or malformed check-in or check-out date. + + + Read the inner errors under the Stay path for the failing date. + + + We could not read the stay dates. + + + The check-in or check-out date of the stay is missing or invalid. + + + Booking guest invalid + + + A guest in the request's guests list could not be bound: its first name was missing or its e-mail was malformed. Its failures are grouped under this per-element envelope, with indexed paths such as Guests[1]. + + + Each guest must have a first name, and any e-mail present must be valid. + + + The client sent a guest with a missing first name or a malformed e-mail address. + + + Read the inner errors under the indexed Guests[i] path for the failing field. + + + A guest's information is invalid. + + + One of the guests is missing a first name or has an invalid e-mail address. + + diff --git a/FirstClassErrors.RequestBinder.Usage/Resources/RequestBinderUsageMessages.sv.resx b/FirstClassErrors.RequestBinder.Usage/Resources/RequestBinderUsageMessages.sv.resx new file mode 100644 index 00000000..6aa34900 --- /dev/null +++ b/FirstClassErrors.RequestBinder.Usage/Resources/RequestBinderUsageMessages.sv.resx @@ -0,0 +1,304 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + Fel som utlöses när en gästs e-postadress tolkas från en begäranssträng. + + + Fel som utlöses när en faktureringsvalutakod tolkas från en begäranssträng. + + + Fel som utlöses när en bokningstagg tolkas från ett element i en begäranslista. + + + Fel som utlöses när ett bokningsdatum (incheckning / utcheckning) tolkas från en begäranssträng. + + + Fel som utlöses när antalet nätter byggs från ett begäransvärde. + + + Fel som utlöses när ett rumsnummer byggs från ett element i en begäranslista. + + + Fel som utlöses vid gemensam validering av en vistelses in- och utcheckningsdatum. + + + Primärportsfel som utlöses av bokningsslutpunkten när den binder en inkommande begäran till ett kommando. + + + + Felaktig e-postadress + + + En inkommande begäran innehåller ett värde som inte är en välformad e-postadress och därför inte kan tolkas till ett EmailAddress-värdeobjekt. + + + En gästs e-postadress måste innehålla exakt ett ”@” med en icke-tom lokal del och en icke-tom domän. + + + Klienten skickade en felstavad eller avkortad adress (saknat ”@”, tom lokal del eller domän). + + + Validera adressen på klienten och jämför det skickade värdet med det förväntade formatet. + + + E-postadressen är ogiltig. + + + Det angivna värdet är inte en giltig e-postadress. + + + + Felaktig valutakod + + + En inkommande begäran innehåller ett värde som inte är en välformad valutakod med tre bokstäver och därför inte kan tolkas till ett Currency-värdeobjekt. + + + En valutakod måste bestå av exakt tre versala ASCII-bokstäver (till exempel EUR). + + + Klienten skickade en valuta i fel form (gemener, en symbol, ett namn eller fel längd). + + + Skicka den alfabetiska ISO-4217-koden i versaler, till exempel USD eller EUR. + + + Valutan är ogiltig. + + + Faktureringsvalutakoden är inte en giltig kod med tre bokstäver. + + + + Felaktig bokningstagg + + + Ett element i begärans tagglista är tomt, för långt eller innehåller blanksteg och kan därför inte tolkas till ett Tag-värdeobjekt. + + + En tagg måste vara en enda icke-tom token på högst 32 tecken, utan blanksteg. + + + Klienten skickade en tom tagg, ett uttryck med blanksteg eller ett för långt värde. + + + Skicka varje tagg som en enda token utan blanksteg; koppla ihop flerordstaggar med bindestreck. + + + En tagg är ogiltig. + + + En av bokningstaggarna är inte en giltig enskild token. + + + + Felaktigt bokningsdatum + + + En inkommande begäran innehåller ett värde som inte är ett ISO-datum (yyyy-MM-dd) och därför inte kan tolkas till ett BookingDate-värdeobjekt. + + + Ett bokningsdatum måste vara ett ISO-kalenderdatum i formatet yyyy-MM-dd. + + + Klienten skickade ett datum i ett språkspecifikt eller felaktigt format, eller ett omöjligt kalenderdatum. + + + Skicka datum i formatet ISO 8601 yyyy-MM-dd, till exempel 2026-08-10. + + + Datumet är ogiltigt. + + + Ett bokningsdatum är inte ett giltigt ISO-datum (yyyy-MM-dd). + + + + Icke-positivt antal nätter + + + En inkommande begäran begär noll eller ett negativt antal nätter, vilket inte är en giltig vistelselängd. + + + En bokning måste omfatta minst en natt. + + + Klienten skickade ett antal nätter på noll eller lägre. + + + Skicka ett antal nätter på en eller fler. + + + Antalet nätter är ogiltigt. + + + Det begärda antalet nätter måste vara en eller fler. + + + + Rumsnummer utanför intervallet + + + Ett element i begärans lista över rumsnummer ligger utanför det intervall som stöds (1-999). + + + Ett rumsnummer måste vara mellan 1 och 999 inklusive. + + + Klienten skickade ett rumsnummer på noll, ett negativt värde eller ett värde över 999. + + + Skicka rumsnummer inom det intervall som stöds (1-999). + + + Ett rumsnummer är ogiltigt. + + + Ett av de begärda rumsnumren ligger utanför det intervall som stöds. + + + + Utcheckning inte efter incheckning + + + Båda vistelsedatumen tolkas, men utcheckningsdatumet är före eller lika med incheckningsdatumet, så vistelsen har ingen positiv längd. Denna regel över flera fält tillämpas av fabriken Stay.Create. + + + Utcheckningen måste vara strikt efter incheckningen. + + + Klienten skickade ett utcheckningsdatum som är lika med eller tidigare än incheckningsdatumet. + + + Skicka ett utcheckningsdatum minst en dag efter incheckningsdatumet. + + + Vistelsedatumen är ogiltiga. + + + Utcheckningsdatumet måste vara efter incheckningsdatumet. + + + + Bokningsbegäran ogiltig + + + Slutpunkten kunde inte binda den inkommande begäran till ett bokningskommando: ett eller flera argument saknades eller var ogiltiga. Varje fel samlas under detta hölje, vart och ett med sin fullständiga argumentsökväg. + + + Varje obligatoriskt argument måste finnas, och varje argument måste konverteras till sitt värdeobjekt. + + + Klienten skickade en begäran som bryter mot slutpunktens kontrakt (saknade eller felaktiga argument). + + + Läs de inre felen: vart och ett namnger det felande argumentet och regeln som bryts. + + + Vi kunde inte acceptera din bokningsbegäran. + + + En eller flera uppgifter i bokningsbegäran saknas eller är ogiltiga. + + + Bokningens vistelse ogiltig + + + Begärans vistelsedelobjekt kunde inte bindas: ett eller båda av dess datum saknades eller var felaktiga. Dess fel grupperas under detta nästlade hölje, med sökvägar prefixerade med Stay. + + + Båda vistelsedatumen måste finnas och vara giltiga ISO-datum. + + + Klienten skickade en vistelse med ett saknat eller felaktigt in- eller utcheckningsdatum. + + + Läs de inre felen under Stay-sökvägen för det felande datumet. + + + Vi kunde inte läsa vistelsedatumen. + + + Vistelsens in- eller utcheckningsdatum saknas eller är ogiltigt. + + + Bokningens gäst ogiltig + + + En gäst i begärans gästlista kunde inte bindas: dess förnamn saknades eller dess e-post var felaktig. Dess fel grupperas under detta hölje per element, med indexerade sökvägar som Guests[1]. + + + Varje gäst måste ha ett förnamn, och varje e-post som finns måste vara giltig. + + + Klienten skickade en gäst med ett saknat förnamn eller en felaktig e-postadress. + + + Läs de inre felen under den indexerade sökvägen Guests[i] för det felande fältet. + + + En gästs uppgifter är ogiltiga. + + + En av gästerna saknar förnamn eller har en ogiltig e-postadress. + + diff --git a/FirstClassErrors.RequestBinder.Usage/Utils/DocumentationFormatter.cs b/FirstClassErrors.RequestBinder.Usage/Utils/DocumentationFormatter.cs new file mode 100644 index 00000000..ed3b6c85 --- /dev/null +++ b/FirstClassErrors.RequestBinder.Usage/Utils/DocumentationFormatter.cs @@ -0,0 +1,100 @@ +#region Usings declarations + +using System.Globalization; + +#endregion + +namespace FirstClassErrors.RequestBinder.Usage.Utils; + +/// +/// Formats the values interpolated into the sample's diagnostic (author-language) error messages so they +/// render culture-invariantly — an integer, a date or a decimal reads identically on every machine, and the +/// generated documentation stays reproducible. Mirrors the same helper in FirstClassErrors.Usage. +/// +internal static class DocumentationFormatter { + + #region Static members + + private static readonly CultureInfo DocumentationCulture = CultureInfo.InvariantCulture; + + public static string Format(string format, params object[]? arguments) { + ArgumentNullException.ThrowIfNull(format); + + if (arguments is null || arguments.Length == 0) { return format; } + + string[] formattedArguments = arguments + .Select(Format) + .ToArray(); + + return string.Format(CultureInfo.InvariantCulture, format, formattedArguments); + } + + private static string Format(object? value) { + if (value is null) { return DocumentationValue.Null; } + + return FormatDynamic((dynamic)value); + } + + private static string FormatDynamic(string value) { + return value; + } + + private static string FormatDynamic(bool value) { + return value ? DocumentationValue.True : DocumentationValue.False; + } + + private static string FormatDynamic(int value) { + return value.ToString(DocumentationCulture); + } + + private static string FormatDynamic(long value) { + return value.ToString(DocumentationCulture); + } + + private static string FormatDynamic(decimal value) { + return value.ToString(DocumentationCulture); + } + + private static string FormatDynamic(DateOnly value) { + return value.ToString(DocumentationFormat.DateOnly, DocumentationCulture); + } + + private static string FormatDynamic(DateTime value) { + return value.ToUniversalTime().ToString(DocumentationFormat.DateTime, DocumentationCulture); + } + + private static string FormatDynamic(Guid value) { + return value.ToString(DocumentationFormat.Guid); + } + + private static string FormatDynamic(Enum value) { + return value.ToString(); + } + + private static string FormatDynamic(object value) { + return value.ToString() ?? value.GetType().Name; + } + + #endregion + + #region Nested types + + private static class DocumentationFormat { + + public const string Guid = "D"; + public const string DateTime = "yyyy-MM-ddTHH:mm:ssZ"; + public const string DateOnly = "yyyy-MM-dd"; + + } + + private static class DocumentationValue { + + public const string Null = "null"; + public const string True = "true"; + public const string False = "false"; + + } + + #endregion + +} diff --git a/FirstClassErrors.sln b/FirstClassErrors.sln index affa9e4a..1204a08d 100644 --- a/FirstClassErrors.sln +++ b/FirstClassErrors.sln @@ -51,6 +51,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dummies.UnitTests", "Dummie EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FirstClassErrors.RequestBinder.PropertyTests", "FirstClassErrors.RequestBinder.PropertyTests\FirstClassErrors.RequestBinder.PropertyTests.csproj", "{1FC2C501-8842-4ABE-845E-000ED6575DD6}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FirstClassErrors.RequestBinder.Usage", "FirstClassErrors.RequestBinder.Usage\FirstClassErrors.RequestBinder.Usage.csproj", "{D12EE45B-78F8-4CD4-8E67-E4A9DDA01AE6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FirstClassErrors.RequestBinder.Usage.UnitTests", "FirstClassErrors.RequestBinder.Usage.UnitTests\FirstClassErrors.RequestBinder.Usage.UnitTests.csproj", "{04707ACA-FB2E-43BF-A12C-28E01BF5F60D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -61,6 +65,30 @@ Global Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D12EE45B-78F8-4CD4-8E67-E4A9DDA01AE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D12EE45B-78F8-4CD4-8E67-E4A9DDA01AE6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D12EE45B-78F8-4CD4-8E67-E4A9DDA01AE6}.Debug|x64.ActiveCfg = Debug|Any CPU + {D12EE45B-78F8-4CD4-8E67-E4A9DDA01AE6}.Debug|x64.Build.0 = Debug|Any CPU + {D12EE45B-78F8-4CD4-8E67-E4A9DDA01AE6}.Debug|x86.ActiveCfg = Debug|Any CPU + {D12EE45B-78F8-4CD4-8E67-E4A9DDA01AE6}.Debug|x86.Build.0 = Debug|Any CPU + {D12EE45B-78F8-4CD4-8E67-E4A9DDA01AE6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D12EE45B-78F8-4CD4-8E67-E4A9DDA01AE6}.Release|Any CPU.Build.0 = Release|Any CPU + {D12EE45B-78F8-4CD4-8E67-E4A9DDA01AE6}.Release|x64.ActiveCfg = Release|Any CPU + {D12EE45B-78F8-4CD4-8E67-E4A9DDA01AE6}.Release|x64.Build.0 = Release|Any CPU + {D12EE45B-78F8-4CD4-8E67-E4A9DDA01AE6}.Release|x86.ActiveCfg = Release|Any CPU + {D12EE45B-78F8-4CD4-8E67-E4A9DDA01AE6}.Release|x86.Build.0 = Release|Any CPU + {04707ACA-FB2E-43BF-A12C-28E01BF5F60D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {04707ACA-FB2E-43BF-A12C-28E01BF5F60D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {04707ACA-FB2E-43BF-A12C-28E01BF5F60D}.Debug|x64.ActiveCfg = Debug|Any CPU + {04707ACA-FB2E-43BF-A12C-28E01BF5F60D}.Debug|x64.Build.0 = Debug|Any CPU + {04707ACA-FB2E-43BF-A12C-28E01BF5F60D}.Debug|x86.ActiveCfg = Debug|Any CPU + {04707ACA-FB2E-43BF-A12C-28E01BF5F60D}.Debug|x86.Build.0 = Debug|Any CPU + {04707ACA-FB2E-43BF-A12C-28E01BF5F60D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {04707ACA-FB2E-43BF-A12C-28E01BF5F60D}.Release|Any CPU.Build.0 = Release|Any CPU + {04707ACA-FB2E-43BF-A12C-28E01BF5F60D}.Release|x64.ActiveCfg = Release|Any CPU + {04707ACA-FB2E-43BF-A12C-28E01BF5F60D}.Release|x64.Build.0 = Release|Any CPU + {04707ACA-FB2E-43BF-A12C-28E01BF5F60D}.Release|x86.ActiveCfg = Release|Any CPU + {04707ACA-FB2E-43BF-A12C-28E01BF5F60D}.Release|x86.Build.0 = Release|Any CPU {8DA5E330-53BC-4EA3-A9B8-916DABA3878C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8DA5E330-53BC-4EA3-A9B8-916DABA3878C}.Debug|Any CPU.Build.0 = Debug|Any CPU {8DA5E330-53BC-4EA3-A9B8-916DABA3878C}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -296,6 +324,8 @@ Global {7991C766-2720-43E5-A9CB-2F3D6C2025FC} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {75EA57DD-0128-4EB9-ADBE-567BFF602A93} = {B7C3D08D-EFC5-4F5D-8DE4-5B7938354DBB} {1FC2C501-8842-4ABE-845E-000ED6575DD6} = {B7C3D08D-EFC5-4F5D-8DE4-5B7938354DBB} + {D12EE45B-78F8-4CD4-8E67-E4A9DDA01AE6} = {B7C3D08D-EFC5-4F5D-8DE4-5B7938354DBB} + {04707ACA-FB2E-43BF-A12C-28E01BF5F60D} = {B7C3D08D-EFC5-4F5D-8DE4-5B7938354DBB} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4988972E-3E0D-4F48-8656-0E67ECE994BF} diff --git a/doc/handwritten/for-users/RequestBinder.en.md b/doc/handwritten/for-users/RequestBinder.en.md index 0cc47d70..ed2124c5 100644 --- a/doc/handwritten/for-users/RequestBinder.en.md +++ b/doc/handwritten/for-users/RequestBinder.en.md @@ -554,6 +554,19 @@ Check.That(outcome.Error!.InnerErrors.Select(e => e.Code.ToString())) Assert the *whole* set of collected failures, in order — that is what proves the collect-all behavior, not just that binding failed. +## Runnable examples + +The patterns in this guide are realized as compiled, tested, and +snapshot-documented code in the +[`FirstClassErrors.RequestBinder.Usage`](../../../FirstClassErrors.RequestBinder.Usage) +sample: a full hotel-booking boundary bound end-to-end (`BookingBinder`), a +showcase of every remaining overload and option — required/optional lists, custom +argument names, custom structural codes (`BinderShowcase`) — a framework-agnostic +call site (`BookingEndpoint`), and companion unit tests that assert the +collect-all order, codes, and argument paths. Its documented errors appear in +their own generated, snapshot-tested catalog, so the binder path is exercised in +living documentation. + ## 📌 Review checklist Before approving a binding, verify that: diff --git a/doc/handwritten/for-users/RequestBinder.fr.md b/doc/handwritten/for-users/RequestBinder.fr.md index dca74862..c95fd819 100644 --- a/doc/handwritten/for-users/RequestBinder.fr.md +++ b/doc/handwritten/for-users/RequestBinder.fr.md @@ -576,6 +576,20 @@ Assertez l’ensemble *complet* des échecs collectés, dans l’ordre — c’e prouve le comportement de collecte exhaustive, pas seulement que la liaison a échoué. +## Exemples exécutables + +Les patterns de ce guide sont réalisés en code compilé, testé et documenté par +snapshot dans l’échantillon +[`FirstClassErrors.RequestBinder.Usage`](../../../FirstClassErrors.RequestBinder.Usage) : +une frontière de réservation d’hôtel liée de bout en bout (`BookingBinder`), une +vitrine de toutes les surcharges et options restantes — listes requises/optionnelles, +noms d’arguments personnalisés, codes structurels personnalisés (`BinderShowcase`) — +un site d’appel indépendant de tout framework (`BookingEndpoint`), et des tests +unitaires compagnons qui assertent l’ordre du collect-all, les codes et les chemins +d’argument. Ses erreurs documentées apparaissent dans leur propre catalogue généré +et testé par snapshot, de sorte que le chemin du binder est exercé dans de la +documentation vivante. + ## 📌 Checklist de revue Avant d’approuver une liaison, vérifiez que :