from __future__ import annotations import argparse import json import sys from pathlib import Path from app.mailer.campaign.loader import CampaignLoadError, CampaignSchemaError, load_campaign_config from app.mailer.campaign.validation import Severity, validate_campaign_config def _default_campaign_path() -> Path: return Path(__file__).resolve().parents[1] / "examples" / "campaign.json" def _print_text_report(report) -> None: print(f"Campaign: {report.campaign_name} ({report.campaign_id})") print(f"Entries: {report.entries_mode}" + (f", {report.entries_count} item(s)" if report.entries_count is not None else "")) print(f"Attachments base path: {report.attachments_base_path}") print(f"Rate limit: {report.rate_limit}") print(f"IMAP append: {'enabled' if report.imap_append_enabled else 'disabled'}") print(f"Issues: {report.error_count} error(s), {report.warning_count} warning(s)") if report.issues: print() for issue in report.issues: location = f" [{issue.path}]" if issue.path else "" print(f"- {issue.severity.upper()} {issue.code}{location}: {issue.message}") def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser(description="Validate a MultiMailer campaign JSON file.") parser.add_argument("--campaign", default=str(_default_campaign_path()), help="Path to campaign JSON file") parser.add_argument("--schema", default=None, help="Optional path to campaign.schema.json") parser.add_argument("--no-schema", action="store_true", help="Skip JSON Schema validation") parser.add_argument("--check-files", action="store_true", help="Check referenced local files and CSV headers") parser.add_argument("--json", action="store_true", help="Print machine-readable validation report") args = parser.parse_args(argv) campaign_path = Path(args.campaign).resolve() try: config = load_campaign_config( campaign_path, validate_schema=not args.no_schema, schema_path=args.schema, ) report = validate_campaign_config(config, campaign_file=campaign_path, check_files=args.check_files) except CampaignSchemaError as exc: if args.json: print(json.dumps({"ok": False, "schema_errors": [error.__dict__ for error in exc.errors]}, indent=2), file=sys.stdout) else: print(str(exc), file=sys.stderr) for error in exc.errors: print(f"- {error.path}: {error.message}", file=sys.stderr) return 2 except CampaignLoadError as exc: print(str(exc), file=sys.stderr) return 2 except Exception as exc: print(f"campaign validation failed: {exc}", file=sys.stderr) return 2 if args.json: print(report.model_dump_json(indent=2)) else: _print_text_report(report) return 0 if report.ok else 1 if __name__ == "__main__": raise SystemExit(main())