215 lines
8.1 KiB
C++
Executable File
215 lines
8.1 KiB
C++
Executable File
#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<std::uint16_t>(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
|