#include "ScheduleValidator.h" namespace timbre_core { namespace { ValidationResult failure(ValidationCode code, std::size_t index = 0) noexcept { return ValidationResult(code, index); } } // namespace const char* ValidationResult::message() const noexcept { switch (code) { case ValidationCode::Ok: return "ok"; case ValidationCode::TooManyProfiles: return "too many profiles"; case ValidationCode::TooManyPatterns: return "too many patterns"; case ValidationCode::TooManySchedules: return "too many schedules"; case ValidationCode::TooManyHolidays: return "too many holidays"; case ValidationCode::InvalidProfileId: return "invalid profile id"; case ValidationCode::DuplicateProfileId: return "duplicate profile id"; case ValidationCode::InvalidPatternId: return "invalid pattern id"; case ValidationCode::DuplicatePatternId: return "duplicate pattern id"; case ValidationCode::InvalidPhaseCount: return "invalid phase count"; case ValidationCode::InvalidPhaseDuration: return "invalid phase duration"; case ValidationCode::PatternTotalTooLarge: return "pattern total is too large"; case ValidationCode::InvalidHolidayId: return "invalid holiday id"; case ValidationCode::DuplicateHolidayId: return "duplicate holiday id"; case ValidationCode::InvalidHolidayDate: return "invalid holiday date"; case ValidationCode::InvalidScheduleId: return "invalid schedule id"; case ValidationCode::DuplicateScheduleId: return "duplicate schedule id"; case ValidationCode::InvalidMinute: return "invalid minute"; case ValidationCode::InvalidWeekdayMask: return "invalid weekday mask"; case ValidationCode::UnknownProfile: return "schedule references an unknown profile"; case ValidationCode::UnknownPattern: return "schedule references an unknown pattern"; case ValidationCode::InvalidActiveProfile: return "active profile does not exist"; } return "validation error"; } ValidationResult ScheduleValidator::validateProfile(const Profile& profile, std::size_t index) noexcept { if (profile.id == kInvalidId) { return failure(ValidationCode::InvalidProfileId, index); } return ValidationResult{}; } ValidationResult ScheduleValidator::validatePattern(const Pattern& pattern, std::size_t index) noexcept { if (!isPatternId(pattern.id)) { return failure(ValidationCode::InvalidPatternId, index); } if (pattern.phaseCount > kMaxPatternPhases) { return failure(ValidationCode::InvalidPhaseCount, index); } std::uint16_t total = 0; for (std::size_t i = 0; i < pattern.phaseCount; ++i) { if (!validPhaseDuration(pattern.phases[i])) { return failure(ValidationCode::InvalidPhaseDuration, index); } total = static_cast(total + pattern.phases[i]); if (!validPatternTotal(total)) { return failure(ValidationCode::PatternTotalTooLarge, index); } } return ValidationResult{}; } ValidationResult ScheduleValidator::validateHoliday(const Holiday& holiday, std::size_t index) noexcept { if (holiday.id == kInvalidId) { return failure(ValidationCode::InvalidHolidayId, index); } if (!validHolidayDate(holiday.month, holiday.day)) { return failure(ValidationCode::InvalidHolidayDate, index); } return ValidationResult{}; } bool ScheduleValidator::validHolidayDate(std::uint8_t month, std::uint8_t day) noexcept { // Holidays are recurring month/day values. February 29 is valid and is // naturally inert in non-leap years. if (month < 1 || month > 12 || day < 1) { return false; } static constexpr std::uint8_t maximumDays[] = { 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; return day <= maximumDays[month]; } ValidationResult ScheduleValidator::validateSchedule(const Schedule& schedule, const AppState& state, std::size_t index) noexcept { if (schedule.id == kInvalidId) { return failure(ValidationCode::InvalidScheduleId, index); } if (!validMinute(schedule.minuteOfDay)) { return failure(ValidationCode::InvalidMinute, index); } if (!validWeekdayMask(schedule.weekdayMask)) { return failure(ValidationCode::InvalidWeekdayMask, index); } if (state.profile(schedule.profileId) == nullptr) { return failure(ValidationCode::UnknownProfile, index); } if (state.pattern(schedule.patternId) == nullptr || !isPatternId(schedule.patternId)) { return failure(ValidationCode::UnknownPattern, index); } const Pattern* pattern = state.pattern(schedule.patternId); if (pattern == nullptr || !validatePattern(*pattern).valid) { return failure(ValidationCode::UnknownPattern, index); } return ValidationResult{}; } ValidationResult ScheduleValidator::validate(const AppState& state) noexcept { if (state.profiles.size() > kMaxProfiles) { return failure(ValidationCode::TooManyProfiles, state.profiles.size()); } if (state.patterns.size() != kMaxPatterns) { return failure(ValidationCode::TooManyPatterns, state.patterns.size()); } if (state.schedules.size() > kMaxSchedules) { return failure(ValidationCode::TooManySchedules, state.schedules.size()); } if (state.holidays.size() > kMaxHolidays) { return failure(ValidationCode::TooManyHolidays, state.holidays.size()); } for (std::size_t i = 0; i < state.profiles.size(); ++i) { const ValidationResult result = validateProfile(state.profiles[i], i); if (!result.valid) { return result; } for (std::size_t j = 0; j < i; ++j) { if (state.profiles[j].id == state.profiles[i].id) { return failure(ValidationCode::DuplicateProfileId, i); } } } for (std::size_t i = 0; i < state.patterns.size(); ++i) { const ValidationResult result = validatePattern(state.patterns[i], i); if (!result.valid) { return result; } for (std::size_t j = 0; j < i; ++j) { if (state.patterns[j].id == state.patterns[i].id) { return failure(ValidationCode::DuplicatePatternId, i); } } } for (std::size_t i = 0; i < state.holidays.size(); ++i) { const ValidationResult result = validateHoliday(state.holidays[i], i); if (!result.valid) { return result; } for (std::size_t j = 0; j < i; ++j) { if (state.holidays[j].id == state.holidays[i].id) { return failure(ValidationCode::DuplicateHolidayId, i); } if (state.holidays[j].enabled && state.holidays[i].enabled && state.holidays[j].month == state.holidays[i].month && state.holidays[j].day == state.holidays[i].day) { return failure(ValidationCode::DuplicateHolidayId, i); } } } for (std::size_t i = 0; i < state.schedules.size(); ++i) { const ValidationResult result = validateSchedule(state.schedules[i], state, i); if (!result.valid) { return result; } for (std::size_t j = 0; j < i; ++j) { if (state.schedules[j].id == state.schedules[i].id) { return failure(ValidationCode::DuplicateScheduleId, i); } } } if (state.profiles.size() != 0 && state.profile(state.activeProfileId) == nullptr) { return failure(ValidationCode::InvalidActiveProfile, 0); } return ValidationResult{}; } } // namespace timbre_core