Use this file to discover all available pages before exploring further.
ClinicFlow uses a layered exception strategy: the Domain layer defines a rich hierarchy of typed exceptions that carry standardised string error codes, the Application layer adds a ValidationException for FluentValidation pipeline failures, and the presentation layer (once implemented) is responsible for mapping these to HTTP status codes and structured error responses.
All error codes are compile-time constants defined in DomainErrors — a static class in ClinicFlow.Domain.Common. The nested classes are: General, Validation, Appointment, AppointmentType, Schedule, Patient, Doctor, MedicalSpecialty, ClinicalFormTemplate, User, Penalty, and MedicalEncounter. Error strings are never written as magic strings inside business logic; always reference the constant directly (e.g., DomainErrors.Appointment.CannotCancel).
All domain exceptions ultimately inherit from DomainException, which is the single base class:
// ClinicFlow.Domain.Exceptions.Basepublic abstract class DomainException(string errorCode) : Exception(errorCode) { }
The Message property of every DomainException is the errorCode constant string — not a human-readable sentence. This keeps the domain layer free from presentation concerns; the host layer translates codes to user-facing messages.
DomainValidationException — invalid input or object state
Thrown when a value object or entity receives a value that violates a validation rule (wrong format, out of range, etc.).
namespace ClinicFlow.Domain.Exceptions.Base;public class DomainValidationException(string errorCode) : DomainException(errorCode) { }
Typical triggers: invalid email format, negative duration, phone number not matching E.164, blood type not in allowed set.
BusinessRuleValidationException — business rule violated
Thrown when an operation is structurally valid but violates a business invariant (e.g., trying to activate an already-active specialty, or adding a specialty to a global appointment type).
namespace ClinicFlow.Domain.Exceptions.Base;public class BusinessRuleValidationException(string errorCode) : DomainException(errorCode) { }
Typical triggers: duplicate name creation, status transition that is not allowed by the domain state machine, constraint violations on aggregate relationships.
EntityNotFoundException — entity not found
Thrown by application-layer handlers when a repository returns null for a required entity lookup.
namespace ClinicFlow.Domain.Exceptions.Base;public class EntityNotFoundException(string errorCode, string entityName, object id) : DomainException(errorCode){ public string EntityName { get; } = entityName; public object Id { get; } = id;}
EntityName and Id provide structured context that a presentation layer can include in a 404 response body.
// Example: carries the current status so the caller can reason about itpublic class AppointmentCancellationNotAllowedException( string errorCode, AppointmentStatus currentStatus) : DomainException(errorCode){ public AppointmentStatus CurrentStatus { get; } = currentStatus;}
The Application layer adds one additional exception type that is not a domain exception — it comes from the MediatR validation pipeline:
using FluentValidation.Results;namespace ClinicFlow.Application.Exceptions;public class ValidationException : Exception{ public const string DefaultErrorMessage = "One or more validation failures have occurred."; /// <summary> /// Gets the collection of validation failures grouped by property name. /// </summary> public IDictionary<string, string[]> Errors { get; } public ValidationException() : base(DefaultErrorMessage) { Errors = new Dictionary<string, string[]>(); } public ValidationException(IEnumerable<ValidationFailure> failures) : this() { Errors = failures .GroupBy(e => e.PropertyName, e => e.ErrorMessage) .ToDictionary(g => g.Key, g => g.ToArray()); }}
ValidationException is thrown by the ValidationBehavior<TRequest, TResponse> MediatR pipeline behaviour when any registered AbstractValidator<T> returns failures. Errors is a dictionary keyed by property name — suitable for a structured 400 response with per-field error details.
DomainErrors is a nested static class in ClinicFlow.Domain.Common that organises all error code strings by domain concept. The presentation layer maps these constants to HTTP status codes and localised messages.
General & Validation
Appointment
AppointmentType
Patient & User
Doctor & Specialty
Schedule, Penalty & Medical
public static class General{ public const string RequiredFieldNull = "REQUIRED_FIELD_NULL"; public const string NotFound = "ENTITY_NOT_FOUND";}public static class Validation{ public const string ValueRequired = "VALUE_REQUIRED"; public const string InvalidValue = "INVALID_VALUE"; public const string ValueTooShort = "VALUE_TOO_SHORT"; public const string ValueCannotBeNegative = "VALUE_CANNOT_BE_NEGATIVE"; public const string ValueMustBePositive = "VALUE_MUST_BE_POSITIVE"; public const string ValueCannotBeInFuture = "VALUE_CANNOT_BE_IN_FUTURE"; public const string ValueMustBeInFuture = "VALUE_MUST_BE_IN_FUTURE"; public const string InvalidEmailFormat = "INVALID_EMAIL_FORMAT"; public const string InvalidPhoneFormat = "INVALID_PHONE_FORMAT"; public const string InvalidBloodType = "INVALID_BLOOD_TYPE"; public const string InvalidFormat = "INVALID_FORMAT"; public const string StartTimeMustBeBeforeEndTime = "START_TIME_MUST_BE_BEFORE_END_TIME"; public const string EndTimeMustBeAfterStartTime = "END_TIME_MUST_BE_AFTER_START_TIME"; public const string InvalidDateRange = "INVALID_DATE_RANGE"; public const string InvalidEnumValue = "INVALID_ENUM_VALUE"; public const string ValueTooLong = "VALUE_TOO_LONG"; public const string ValueExceedsMaximum = "VALUE_EXCEEDS_MAXIMUM"; public const string DuplicateValues = "DUPLICATE_VALUES";}
public static class Appointment{ public const string CannotCancel = "CANCELLATION_NOT_ALLOWED"; public const string UnauthorizedCancellation = "CANCELLATION_UNAUTHORIZED"; public const string MissingCancellationReason = "MISSING_CANCELLATION_REASON"; public const string CannotReschedule = "RESCHEDULING_NOT_ALLOWED"; public const string CannotMarkNoShow = "NO_SHOW_NOT_ALLOWED"; public const string UnauthorizedNoShow = "NO_SHOW_UNAUTHORIZED"; public const string Conflict = "APPOINTMENT_CONFLICT"; public const string DataMismatch = "APPOINTMENT_DATA_MISMATCH"; public const string UnauthorizedScheduling = "SCHEDULING_UNAUTHORIZED"; public const string CannotCheckIn = "CHECK_IN_NOT_ALLOWED"; public const string CannotStart = "START_NOT_ALLOWED"; public const string CannotComplete = "COMPLETE_NOT_ALLOWED"; public const string UnauthorizedDoctor = "UNAUTHORIZED_DOCTOR"; public const string UnauthorizedSpecialty = "UNAUTHORIZED_SPECIALTY"; public const string PhoneNotVerified = "PHONE_NOT_VERIFIED"; public const string CannotReassign = "REASSIGNMENT_NOT_ALLOWED"; public const string CannotUpdateNotes = "UPDATE_NOTES_NOT_ALLOWED";}
public static class AppointmentType{ public const string NameAlreadyExists = "APPOINTMENT_TYPE_NAME_ALREADY_EXISTS"; public const string AlreadyActive = "APPOINTMENT_TYPE_ALREADY_ACTIVE"; public const string AlreadyInactive = "APPOINTMENT_TYPE_ALREADY_INACTIVE"; public const string TemplateAlreadyRequired = "TEMPLATE_ALREADY_REQUIRED"; public const string SpecialtyAlreadyAllowed = "SPECIALTY_ALREADY_ALLOWED"; public const string CannotAddSpecialtyToGlobalType = "CANNOT_ADD_SPECIALTY_TO_GLOBAL_TYPE"; public const string CannotRemoveSpecialtyFromGlobalType = "CANNOT_REMOVE_SPECIALTY_FROM_GLOBAL_TYPE"; public const string AlreadyRestricted = "ALREADY_RESTRICTED"; public const string RequiresAtLeastOneSpecialty = "REQUIRES_AT_LEAST_ONE_SPECIALTY"; public const string AlreadyUnrestricted = "ALREADY_UNRESTRICTED"; public const string SpecialtyNotFound = "SPECIALTY_NOT_FOUND"; public const string TemplateNotFound = "TEMPLATE_NOT_FOUND"; public const string InvalidAgeRange = "INVALID_AGE_RANGE"; public const string MinimumAgeNotMet = "MINIMUM_AGE_NOT_MET"; public const string MaximumAgeExceeded = "MAXIMUM_AGE_EXCEEDED"; public const string LegalGuardianRequired = "LEGAL_GUARDIAN_REQUIRED";}
public static class Patient{ public const string Blocked = "PATIENT_BLOCKED"; public const string CannotBeSelf = "INVALID_FAMILY_RELATIONSHIP"; public const string ProfileIncomplete = "PATIENT_PROFILE_INCOMPLETE"; public const string CannotRemovePrimaryUser = "CANNOT_REMOVE_PRIMARY_USER"; public const string UnauthorizedRemoval = "UNAUTHORIZED_REMOVAL"; public const string OnlyPrimaryUserCanCloseAccount = "ONLY_PRIMARY_USER_CAN_CLOSE_ACCOUNT"; public const string CannotCloseAccountWithPendingAppointments = "CANNOT_CLOSE_ACCOUNT_WITH_PENDING_APPOINTMENTS"; public const string ActiveProfileAlreadyExists = "PATIENT_ACTIVE_PROFILE_ALREADY_EXISTS"; public const string UserIdMismatch = "PATIENT_USER_ID_MISMATCH"; public const string UnauthorizedAccess = "PATIENT_UNAUTHORIZED_ACCESS";}public static class User{ public const string PhoneAlreadyVerified = "PHONE_ALREADY_VERIFIED"; public const string InvalidVerificationCode = "INVALID_VERIFICATION_CODE"; public const string AccountInactive = "ACCOUNT_INACTIVE"; public const string AccountLockedOut = "ACCOUNT_LOCKED_OUT"; public const string AlreadyActive = "USER_ALREADY_ACTIVE"; public const string AlreadyInactive = "USER_ALREADY_INACTIVE"; public const string InvalidCredentials = "INVALID_CREDENTIALS"; public const string EmailAlreadyExists = "EMAIL_ALREADY_EXISTS";}
public static class Doctor{ public const string InactiveProfileExists = "INACTIVE_DOCTOR_PROFILE_EXISTS"; public const string AlreadyActive = "DOCTOR_ALREADY_ACTIVE"; public const string AlreadySuspended = "DOCTOR_ALREADY_SUSPENDED";}public static class MedicalSpecialty{ public const string NameAlreadyExists = "SPECIALTY_NAME_ALREADY_EXISTS"; public const string AlreadyActive = "SPECIALTY_ALREADY_ACTIVE"; public const string AlreadyInactive = "SPECIALTY_ALREADY_INACTIVE"; public const string HasActiveDoctors = "SPECIALTY_HAS_ACTIVE_DOCTORS"; public const string InvalidCancellationLimit = "INVALID_CANCELLATION_LIMIT"; public const string InvalidEncounterDuration = "INVALID_ENCOUNTER_DURATION";}public static class ClinicalFormTemplate{ public const string CodeAlreadyExists = "TEMPLATE_CODE_ALREADY_EXISTS"; public const string NameAlreadyExists = "TEMPLATE_NAME_ALREADY_EXISTS"; public const string AlreadyActive = "TEMPLATE_ALREADY_ACTIVE"; public const string AlreadyInactive = "TEMPLATE_ALREADY_INACTIVE";}
public static class Schedule{ public const string InvalidDayOfWeek = "INVALID_DAY_OF_WEEK"; public const string InvalidTimeRange = "INVALID_TIME_RANGE"; public const string DoctorNotAvailable = "DOCTOR_NOT_AVAILABLE"; public const string ScheduleAlreadyExists = "SCHEDULE_ALREADY_EXISTS"; public const string AlreadyInactive = "SCHEDULE_ALREADY_INACTIVE";}public static class Penalty{ public const string AlreadyRemoved = "PENALTY_ALREADY_REMOVED";}public static class MedicalEncounter{ public const string DoctorMismatch = "DOCTOR_MISMATCH"; public const string AppointmentMismatch = "APPOINTMENT_MISMATCH"; public const string CodeMismatch = "TEMPLATE_CODE_MISMATCH"; public const string MissingPayload = "MISSING_PAYLOAD"; public const string ValidationFailed = "VALIDATION_FAILED"; public const string MissingRequiredTemplate = "MISSING_REQUIRED_TEMPLATE"; public const string DetailAlreadyExists = "CLINICAL_DETAIL_ALREADY_EXISTS"; public const string AppointmentNotInProgress = "APPOINTMENT_NOT_IN_PROGRESS";}