several updates to functionality
This commit is contained in:
212
validation.py
Normal file
212
validation.py
Normal file
@@ -0,0 +1,212 @@
|
||||
from datetime import datetime, time
|
||||
from typing import Any, Optional
|
||||
|
||||
DATE_FORMAT = "%Y-%m-%d"
|
||||
TIME_FORMAT = "%H:%M"
|
||||
|
||||
|
||||
def validate_config(config: dict[str, Any]) -> dict[str, Any]:
|
||||
errors: list[str] = []
|
||||
|
||||
if not isinstance(config, dict):
|
||||
raise ValueError("config skal være et JSON-objekt")
|
||||
|
||||
start_date_value = config.get("start_date")
|
||||
start_time_value = config.get("start_time")
|
||||
|
||||
if not isinstance(start_date_value, str):
|
||||
errors.append("start_date skal være en streng i format YYYY-MM-DD")
|
||||
start_date_obj = None
|
||||
else:
|
||||
try:
|
||||
start_date_obj = datetime.strptime(start_date_value, DATE_FORMAT).date()
|
||||
except ValueError:
|
||||
errors.append("start_date skal være i format YYYY-MM-DD")
|
||||
start_date_obj = None
|
||||
|
||||
if not isinstance(start_time_value, str):
|
||||
errors.append("start_time skal være en streng i format HH:MM")
|
||||
start_time_obj = None
|
||||
else:
|
||||
try:
|
||||
t_hour, t_min = map(int, start_time_value.split(":"))
|
||||
start_time_obj = time(t_hour, t_min)
|
||||
except (ValueError, TypeError):
|
||||
errors.append("start_time skal være i format HH:MM")
|
||||
start_time_obj = None
|
||||
|
||||
blocked_weeks = config.get("blocked_weeks", [])
|
||||
if blocked_weeks is None:
|
||||
blocked_weeks = []
|
||||
if not isinstance(blocked_weeks, list):
|
||||
errors.append("blocked_weeks skal være en liste af heltal")
|
||||
else:
|
||||
invalid_weeks = [
|
||||
week for week in blocked_weeks
|
||||
if not isinstance(week, int) or week < 1 or week > 53
|
||||
]
|
||||
if invalid_weeks:
|
||||
errors.append(
|
||||
"blocked_weeks skal indeholde heltal mellem 1 og 53: "
|
||||
f"{invalid_weeks}"
|
||||
)
|
||||
|
||||
blocked_dates = config.get("blocked_dates", [])
|
||||
if blocked_dates is None:
|
||||
blocked_dates = []
|
||||
if not isinstance(blocked_dates, list):
|
||||
errors.append("blocked_dates skal være en liste af dato-strenge")
|
||||
else:
|
||||
invalid_dates = []
|
||||
blocked_full_dates: list[str] = []
|
||||
blocked_month_days: list[str] = []
|
||||
for date_value in blocked_dates:
|
||||
if not isinstance(date_value, str):
|
||||
invalid_dates.append(date_value)
|
||||
continue
|
||||
try:
|
||||
date_obj = datetime.strptime(date_value, DATE_FORMAT).date()
|
||||
except ValueError:
|
||||
month_day = _parse_month_day(date_value)
|
||||
if month_day:
|
||||
blocked_month_days.append(month_day)
|
||||
else:
|
||||
invalid_dates.append(date_value)
|
||||
else:
|
||||
blocked_full_dates.append(date_obj.strftime(DATE_FORMAT))
|
||||
if invalid_dates:
|
||||
errors.append(
|
||||
"blocked_dates skal være i format YYYY-MM-DD eller MM-DD: "
|
||||
f"{invalid_dates}"
|
||||
)
|
||||
|
||||
if "skip_day_after_ascension" in config and not isinstance(
|
||||
config["skip_day_after_ascension"], bool
|
||||
):
|
||||
errors.append("skip_day_after_ascension skal være true/false")
|
||||
|
||||
for key in ("repo_url", "organizer_email", "uid_namespace"):
|
||||
if key in config and config[key] is not None and not isinstance(config[key], str):
|
||||
errors.append(f"{key} skal være en streng")
|
||||
|
||||
if errors:
|
||||
raise ValueError("Konfigurationsfejl:\n- " + "\n- ".join(errors))
|
||||
|
||||
config["start_date_obj"] = start_date_obj
|
||||
config["start_time_obj"] = start_time_obj
|
||||
config["blocked_weeks"] = blocked_weeks
|
||||
config["blocked_dates"] = blocked_full_dates
|
||||
config["blocked_month_days"] = blocked_month_days
|
||||
return config
|
||||
|
||||
|
||||
def validate_story_data(story_data: Any) -> dict[str, Any]:
|
||||
errors: list[str] = []
|
||||
|
||||
if not isinstance(story_data, dict):
|
||||
raise ValueError("Story JSON skal være et objekt med meta og events")
|
||||
|
||||
meta = story_data.get("meta")
|
||||
if not isinstance(meta, dict):
|
||||
errors.append("meta skal være et objekt")
|
||||
else:
|
||||
name = meta.get("name")
|
||||
if not isinstance(name, str) or not name.strip():
|
||||
errors.append("meta.name skal være en ikke-tom streng")
|
||||
for key in ("id", "theme_color", "text_color", "bg_color", "font", "log_prefix"):
|
||||
value = meta.get(key)
|
||||
if value is not None and not isinstance(value, str):
|
||||
errors.append(f"meta.{key} skal være en streng hvis den er sat")
|
||||
|
||||
events = story_data.get("events")
|
||||
if not isinstance(events, list):
|
||||
errors.append("events skal være en liste")
|
||||
else:
|
||||
for idx, event_entry in enumerate(events, start=1):
|
||||
if not isinstance(event_entry, dict):
|
||||
errors.append(f"events[{idx}] skal være et objekt")
|
||||
continue
|
||||
title = event_entry.get("title")
|
||||
if not isinstance(title, str) or not title.strip():
|
||||
errors.append(f"events[{idx}].title skal være en ikke-tom streng")
|
||||
story = event_entry.get("story")
|
||||
if not isinstance(story, str) or not story.strip():
|
||||
errors.append(f"events[{idx}].story skal være en ikke-tom streng")
|
||||
hint = event_entry.get("hint")
|
||||
if hint is not None and not isinstance(hint, str):
|
||||
errors.append(f"events[{idx}].hint skal være en streng hvis den er sat")
|
||||
event_id = event_entry.get("id")
|
||||
if event_id is not None and not isinstance(event_id, str):
|
||||
errors.append(f"events[{idx}].id skal være en streng hvis den er sat")
|
||||
uid = event_entry.get("uid")
|
||||
if uid is not None and not isinstance(uid, str):
|
||||
errors.append(f"events[{idx}].uid skal være en streng hvis den er sat")
|
||||
|
||||
if errors:
|
||||
raise ValueError("Historie-fejl:\n- " + "\n- ".join(errors))
|
||||
|
||||
return story_data
|
||||
|
||||
|
||||
def _parse_month_day(value: str) -> Optional[str]:
|
||||
parts = value.split("-")
|
||||
if len(parts) != 2:
|
||||
return None
|
||||
if not all(part.isdigit() for part in parts):
|
||||
return None
|
||||
month = int(parts[0])
|
||||
day = int(parts[1])
|
||||
try:
|
||||
datetime(2000, month, day)
|
||||
except ValueError:
|
||||
return None
|
||||
return f"{month:02d}-{day:02d}"
|
||||
|
||||
|
||||
def _events_missing_ids(events_data: list[dict[str, Any]]) -> list[int]:
|
||||
missing: list[int] = []
|
||||
for idx, event_entry in enumerate(events_data, start=1):
|
||||
uid_value = event_entry.get("uid")
|
||||
if isinstance(uid_value, str):
|
||||
uid_value = uid_value.strip() or None
|
||||
if not event_entry.get("id") and not uid_value:
|
||||
missing.append(idx)
|
||||
return missing
|
||||
|
||||
|
||||
def validate_uid_mapping(
|
||||
events_data: list[dict[str, Any]],
|
||||
duplicate_titles: set[str],
|
||||
log_totals: set[int],
|
||||
) -> bool:
|
||||
ambiguous_reasons: list[str] = []
|
||||
|
||||
if duplicate_titles:
|
||||
titles_list = ", ".join(sorted(duplicate_titles))
|
||||
ambiguous_reasons.append(f"duplikerede titler i eksisterende .ics ({titles_list})")
|
||||
|
||||
if len(log_totals) > 1:
|
||||
totals_list = ", ".join(str(total) for total in sorted(log_totals))
|
||||
ambiguous_reasons.append(f"flere log-totaler i eksisterende .ics ({totals_list})")
|
||||
elif len(log_totals) == 1:
|
||||
total = next(iter(log_totals))
|
||||
if total != len(events_data):
|
||||
ambiguous_reasons.append(
|
||||
"log-total matcher ikke antal events i story "
|
||||
f"({total} != {len(events_data)})"
|
||||
)
|
||||
|
||||
if not ambiguous_reasons:
|
||||
return True
|
||||
|
||||
missing = _events_missing_ids(events_data)
|
||||
if missing:
|
||||
missing_list = ", ".join(str(index) for index in missing)
|
||||
raise ValueError(
|
||||
"Ambivalent UID-mapping: "
|
||||
+ "; ".join(ambiguous_reasons)
|
||||
+ ". Tilfoej events[].id eller events[].uid for events: "
|
||||
+ missing_list
|
||||
)
|
||||
|
||||
return False
|
||||
Reference in New Issue
Block a user