Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -79,17 +79,28 @@ def _load_messages(path: Path) -> list[dict]:
raise InputError(f"file not found: {path}")
ext = path.suffix.lower()
if ext == ".json":
with path.open("r", encoding="utf-8") as fh:
data = json.load(fh)
try:
with path.open("r", encoding="utf-8") as fh:
data = json.load(fh)
except json.JSONDecodeError as exc:
raise InputError(f"invalid JSON in {path}: {exc}") from exc
if isinstance(data, dict) and "messages" in data:
data = data["messages"]
if not isinstance(data, list):
raise InputError("JSON must be a list of message records or {\"messages\": [...]}")
return data
messages = data
if ext == ".csv":
with path.open("r", encoding="utf-8", newline="") as fh:
return list(csv.DictReader(fh))
raise InputError(f"unsupported file extension '{ext}'; expected .json or .csv")
try:
with path.open("r", encoding="utf-8", newline="") as fh:
messages = list(csv.DictReader(fh))
except (csv.Error, UnicodeError) as exc:
raise InputError(f"invalid CSV in {path}: {exc}") from exc
elif ext != ".json":
raise InputError(f"unsupported file extension '{ext}'; expected .json or .csv")
for index, record in enumerate(messages, 1):
if not isinstance(record, dict):
raise InputError(f"record {index} must be an object")
return messages


def _latest_stage(record: dict) -> str | None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
This packet is readiness evidence, not the Sent campaign API request. Its
snake_case fields are namespaced by an explicit schema version so they cannot
be mistaken for Sent's camelCase contract.

Exit codes:
0 - valid evidence packet
1 - invalid packet or unreadable/malformed input
"""

from __future__ import annotations
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
#!/usr/bin/env python3
"""Validate the exact Sent campaign request used by profile campaign endpoints."""
"""Validate the exact Sent campaign request used by profile campaign endpoints.

Exit codes:
0 - valid campaign payload
1 - invalid payload or unreadable/malformed input
"""

from __future__ import annotations

import argparse
import json
import re
import sys
from pathlib import Path
from typing import Any

Expand Down Expand Up @@ -115,18 +121,21 @@ def issue(field: str, reason: str) -> None:
return issues


def main() -> int:
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("payload", type=Path)
args = parser.parse_args()
args = parser.parse_args(argv)
try:
payload = json.loads(args.payload.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as exc:
print(f"{args.payload}: {exc}")
return 2
except OSError as exc:
print(f"{args.payload}: <file>: {exc}", file=sys.stderr)
return 1
except json.JSONDecodeError as exc:
print(f"{args.payload}: <file>: invalid JSON ({exc})", file=sys.stderr)
return 1
issues = validate(payload, str(args.payload))
if issues:
print("\n".join(issues))
print("\n".join(issues), file=sys.stderr)
return 1
print("OK")
return 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
This validator intentionally accepts the Sent v3 request contract, not Meta's
Cloud API ``components[]`` format. Meta payloads are useful reference material,
but must be labelled and converted before they are sent to Sent.

Exit codes:
0 - valid template payload (warnings may be printed)
1 - invalid payload or unreadable/malformed input
"""

from __future__ import annotations
Expand Down Expand Up @@ -329,15 +333,15 @@ def main(argv: list[str] | None = None) -> int:
payload = json.loads(args.path.read_text(encoding="utf-8"))
except OSError as exc:
print(f"could not read {args.path}: {exc}", file=sys.stderr)
return 2
return 1
except json.JSONDecodeError as exc:
print(f"invalid JSON in {args.path}: {exc}", file=sys.stderr)
return 2
return 1
result = lint_template(payload)
if result.warnings:
print(_format("WARN", result.warnings))
if result.errors:
print(_format("FAIL", result.errors))
print(_format("FAIL", result.errors), file=sys.stderr)
return 1
print("OK")
return 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,17 +79,28 @@ def _load_messages(path: Path) -> list[dict]:
raise InputError(f"file not found: {path}")
ext = path.suffix.lower()
if ext == ".json":
with path.open("r", encoding="utf-8") as fh:
data = json.load(fh)
try:
with path.open("r", encoding="utf-8") as fh:
data = json.load(fh)
except json.JSONDecodeError as exc:
raise InputError(f"invalid JSON in {path}: {exc}") from exc
if isinstance(data, dict) and "messages" in data:
data = data["messages"]
if not isinstance(data, list):
raise InputError("JSON must be a list of message records or {\"messages\": [...]}")
return data
messages = data
if ext == ".csv":
with path.open("r", encoding="utf-8", newline="") as fh:
return list(csv.DictReader(fh))
raise InputError(f"unsupported file extension '{ext}'; expected .json or .csv")
try:
with path.open("r", encoding="utf-8", newline="") as fh:
messages = list(csv.DictReader(fh))
except (csv.Error, UnicodeError) as exc:
raise InputError(f"invalid CSV in {path}: {exc}") from exc
elif ext != ".json":
raise InputError(f"unsupported file extension '{ext}'; expected .json or .csv")
for index, record in enumerate(messages, 1):
if not isinstance(record, dict):
raise InputError(f"record {index} must be an object")
return messages


def _latest_stage(record: dict) -> str | None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
This packet is readiness evidence, not the Sent campaign API request. Its
snake_case fields are namespaced by an explicit schema version so they cannot
be mistaken for Sent's camelCase contract.

Exit codes:
0 - valid evidence packet
1 - invalid packet or unreadable/malformed input
"""

from __future__ import annotations
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
#!/usr/bin/env python3
"""Validate the exact Sent campaign request used by profile campaign endpoints."""
"""Validate the exact Sent campaign request used by profile campaign endpoints.

Exit codes:
0 - valid campaign payload
1 - invalid payload or unreadable/malformed input
"""

from __future__ import annotations

import argparse
import json
import re
import sys
from pathlib import Path
from typing import Any

Expand Down Expand Up @@ -115,18 +121,21 @@ def issue(field: str, reason: str) -> None:
return issues


def main() -> int:
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("payload", type=Path)
args = parser.parse_args()
args = parser.parse_args(argv)
try:
payload = json.loads(args.payload.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as exc:
print(f"{args.payload}: {exc}")
return 2
except OSError as exc:
print(f"{args.payload}: <file>: {exc}", file=sys.stderr)
return 1
except json.JSONDecodeError as exc:
print(f"{args.payload}: <file>: invalid JSON ({exc})", file=sys.stderr)
return 1
issues = validate(payload, str(args.payload))
if issues:
print("\n".join(issues))
print("\n".join(issues), file=sys.stderr)
return 1
print("OK")
return 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
This validator intentionally accepts the Sent v3 request contract, not Meta's
Cloud API ``components[]`` format. Meta payloads are useful reference material,
but must be labelled and converted before they are sent to Sent.

Exit codes:
0 - valid template payload (warnings may be printed)
1 - invalid payload or unreadable/malformed input
"""

from __future__ import annotations
Expand Down Expand Up @@ -329,15 +333,15 @@ def main(argv: list[str] | None = None) -> int:
payload = json.loads(args.path.read_text(encoding="utf-8"))
except OSError as exc:
print(f"could not read {args.path}: {exc}", file=sys.stderr)
return 2
return 1
except json.JSONDecodeError as exc:
print(f"invalid JSON in {args.path}: {exc}", file=sys.stderr)
return 2
return 1
result = lint_template(payload)
if result.warnings:
print(_format("WARN", result.warnings))
if result.errors:
print(_format("FAIL", result.errors))
print(_format("FAIL", result.errors), file=sys.stderr)
return 1
print("OK")
return 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,17 +79,28 @@ def _load_messages(path: Path) -> list[dict]:
raise InputError(f"file not found: {path}")
ext = path.suffix.lower()
if ext == ".json":
with path.open("r", encoding="utf-8") as fh:
data = json.load(fh)
try:
with path.open("r", encoding="utf-8") as fh:
data = json.load(fh)
except json.JSONDecodeError as exc:
raise InputError(f"invalid JSON in {path}: {exc}") from exc
if isinstance(data, dict) and "messages" in data:
data = data["messages"]
if not isinstance(data, list):
raise InputError("JSON must be a list of message records or {\"messages\": [...]}")
return data
messages = data
if ext == ".csv":
with path.open("r", encoding="utf-8", newline="") as fh:
return list(csv.DictReader(fh))
raise InputError(f"unsupported file extension '{ext}'; expected .json or .csv")
try:
with path.open("r", encoding="utf-8", newline="") as fh:
messages = list(csv.DictReader(fh))
except (csv.Error, UnicodeError) as exc:
raise InputError(f"invalid CSV in {path}: {exc}") from exc
elif ext != ".json":
raise InputError(f"unsupported file extension '{ext}'; expected .json or .csv")
for index, record in enumerate(messages, 1):
if not isinstance(record, dict):
raise InputError(f"record {index} must be an object")
return messages


def _latest_stage(record: dict) -> str | None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
This packet is readiness evidence, not the Sent campaign API request. Its
snake_case fields are namespaced by an explicit schema version so they cannot
be mistaken for Sent's camelCase contract.

Exit codes:
0 - valid evidence packet
1 - invalid packet or unreadable/malformed input
"""

from __future__ import annotations
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
#!/usr/bin/env python3
"""Validate the exact Sent campaign request used by profile campaign endpoints."""
"""Validate the exact Sent campaign request used by profile campaign endpoints.

Exit codes:
0 - valid campaign payload
1 - invalid payload or unreadable/malformed input
"""

from __future__ import annotations

import argparse
import json
import re
import sys
from pathlib import Path
from typing import Any

Expand Down Expand Up @@ -115,18 +121,21 @@ def issue(field: str, reason: str) -> None:
return issues


def main() -> int:
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("payload", type=Path)
args = parser.parse_args()
args = parser.parse_args(argv)
try:
payload = json.loads(args.payload.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as exc:
print(f"{args.payload}: {exc}")
return 2
except OSError as exc:
print(f"{args.payload}: <file>: {exc}", file=sys.stderr)
return 1
except json.JSONDecodeError as exc:
print(f"{args.payload}: <file>: invalid JSON ({exc})", file=sys.stderr)
return 1
issues = validate(payload, str(args.payload))
if issues:
print("\n".join(issues))
print("\n".join(issues), file=sys.stderr)
return 1
print("OK")
return 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
This validator intentionally accepts the Sent v3 request contract, not Meta's
Cloud API ``components[]`` format. Meta payloads are useful reference material,
but must be labelled and converted before they are sent to Sent.

Exit codes:
0 - valid template payload (warnings may be printed)
1 - invalid payload or unreadable/malformed input
"""

from __future__ import annotations
Expand Down Expand Up @@ -329,15 +333,15 @@ def main(argv: list[str] | None = None) -> int:
payload = json.loads(args.path.read_text(encoding="utf-8"))
except OSError as exc:
print(f"could not read {args.path}: {exc}", file=sys.stderr)
return 2
return 1
except json.JSONDecodeError as exc:
print(f"invalid JSON in {args.path}: {exc}", file=sys.stderr)
return 2
return 1
result = lint_template(payload)
if result.warnings:
print(_format("WARN", result.warnings))
if result.errors:
print(_format("FAIL", result.errors))
print(_format("FAIL", result.errors), file=sys.stderr)
return 1
print("OK")
return 0
Expand Down
Loading