mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-12-19 19:06:16 +01:00
Preserve line numbers in config validation (#15584)
* use ruamel to parse and preserve line numbers for config validation * maintain exception for non validation errors * fix types * include input in log messages
This commit is contained in:
parent
c4af9205c2
commit
b4d0add62b
@ -3,12 +3,15 @@ import faulthandler
|
|||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
import ruamel.yaml
|
||||||
from pydantic import ValidationError
|
from pydantic import ValidationError
|
||||||
|
|
||||||
from frigate.app import FrigateApp
|
from frigate.app import FrigateApp
|
||||||
from frigate.config import FrigateConfig
|
from frigate.config import FrigateConfig
|
||||||
from frigate.log import setup_logging
|
from frigate.log import setup_logging
|
||||||
|
from frigate.util.config import find_config_file
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
@ -42,10 +45,50 @@ def main() -> None:
|
|||||||
print("*************************************************************")
|
print("*************************************************************")
|
||||||
print("*************************************************************")
|
print("*************************************************************")
|
||||||
print("*** Config Validation Errors ***")
|
print("*** Config Validation Errors ***")
|
||||||
print("*************************************************************")
|
print("*************************************************************\n")
|
||||||
|
# Attempt to get the original config file for line number tracking
|
||||||
|
config_path = find_config_file()
|
||||||
|
with open(config_path, "r") as f:
|
||||||
|
yaml_config = ruamel.yaml.YAML()
|
||||||
|
yaml_config.preserve_quotes = True
|
||||||
|
full_config = yaml_config.load(f)
|
||||||
|
|
||||||
for error in e.errors():
|
for error in e.errors():
|
||||||
location = ".".join(str(item) for item in error["loc"])
|
error_path = error["loc"]
|
||||||
print(f"{location}: {error['msg']}")
|
|
||||||
|
current = full_config
|
||||||
|
line_number = "Unknown"
|
||||||
|
last_line_number = "Unknown"
|
||||||
|
|
||||||
|
try:
|
||||||
|
for i, part in enumerate(error_path):
|
||||||
|
key: Union[int, str] = (
|
||||||
|
int(part) if isinstance(part, str) and part.isdigit() else part
|
||||||
|
)
|
||||||
|
|
||||||
|
if isinstance(current, ruamel.yaml.comments.CommentedMap):
|
||||||
|
current = current[key]
|
||||||
|
elif isinstance(current, list):
|
||||||
|
if isinstance(key, int):
|
||||||
|
current = current[key]
|
||||||
|
|
||||||
|
if hasattr(current, "lc"):
|
||||||
|
last_line_number = current.lc.line
|
||||||
|
|
||||||
|
if i == len(error_path) - 1:
|
||||||
|
if hasattr(current, "lc"):
|
||||||
|
line_number = current.lc.line
|
||||||
|
else:
|
||||||
|
line_number = last_line_number
|
||||||
|
|
||||||
|
except Exception as traverse_error:
|
||||||
|
print(f"Could not determine exact line number: {traverse_error}")
|
||||||
|
|
||||||
|
print(f"Line # : {line_number}")
|
||||||
|
print(f"Key : {' -> '.join(map(str, error_path))}")
|
||||||
|
print(f"Value : {error.get('input','-')}")
|
||||||
|
print(f"Message : {error.get('msg', error.get('type', 'Unknown'))}\n")
|
||||||
|
|
||||||
print("*************************************************************")
|
print("*************************************************************")
|
||||||
print("*** End Config Validation Errors ***")
|
print("*** End Config Validation Errors ***")
|
||||||
print("*************************************************************")
|
print("*************************************************************")
|
||||||
|
@ -7,15 +7,18 @@ import os
|
|||||||
import traceback
|
import traceback
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
|
from io import StringIO
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
import ruamel.yaml
|
||||||
from fastapi import APIRouter, Body, Path, Request, Response
|
from fastapi import APIRouter, Body, Path, Request, Response
|
||||||
from fastapi.encoders import jsonable_encoder
|
from fastapi.encoders import jsonable_encoder
|
||||||
from fastapi.params import Depends
|
from fastapi.params import Depends
|
||||||
from fastapi.responses import JSONResponse, PlainTextResponse
|
from fastapi.responses import JSONResponse, PlainTextResponse
|
||||||
from markupsafe import escape
|
from markupsafe import escape
|
||||||
from peewee import operator
|
from peewee import operator
|
||||||
|
from pydantic import ValidationError
|
||||||
|
|
||||||
from frigate.api.defs.query.app_query_parameters import AppTimelineHourlyQueryParameters
|
from frigate.api.defs.query.app_query_parameters import AppTimelineHourlyQueryParameters
|
||||||
from frigate.api.defs.request.app_body import AppConfigSetBody
|
from frigate.api.defs.request.app_body import AppConfigSetBody
|
||||||
@ -183,7 +186,6 @@ def config_raw():
|
|||||||
@router.post("/config/save")
|
@router.post("/config/save")
|
||||||
def config_save(save_option: str, body: Any = Body(media_type="text/plain")):
|
def config_save(save_option: str, body: Any = Body(media_type="text/plain")):
|
||||||
new_config = body.decode()
|
new_config = body.decode()
|
||||||
|
|
||||||
if not new_config:
|
if not new_config:
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
content=(
|
content=(
|
||||||
@ -194,13 +196,64 @@ def config_save(save_option: str, body: Any = Body(media_type="text/plain")):
|
|||||||
|
|
||||||
# Validate the config schema
|
# Validate the config schema
|
||||||
try:
|
try:
|
||||||
|
# Use ruamel to parse and preserve line numbers
|
||||||
|
yaml_config = ruamel.yaml.YAML()
|
||||||
|
yaml_config.preserve_quotes = True
|
||||||
|
full_config = yaml_config.load(StringIO(new_config))
|
||||||
|
|
||||||
FrigateConfig.parse_yaml(new_config)
|
FrigateConfig.parse_yaml(new_config)
|
||||||
|
|
||||||
|
except ValidationError as e:
|
||||||
|
error_message = []
|
||||||
|
|
||||||
|
for error in e.errors():
|
||||||
|
error_path = error["loc"]
|
||||||
|
current = full_config
|
||||||
|
line_number = "Unknown"
|
||||||
|
last_line_number = "Unknown"
|
||||||
|
|
||||||
|
try:
|
||||||
|
for i, part in enumerate(error_path):
|
||||||
|
key = int(part) if part.isdigit() else part
|
||||||
|
|
||||||
|
if isinstance(current, ruamel.yaml.comments.CommentedMap):
|
||||||
|
current = current[key]
|
||||||
|
elif isinstance(current, list):
|
||||||
|
current = current[key]
|
||||||
|
|
||||||
|
if hasattr(current, "lc"):
|
||||||
|
last_line_number = current.lc.line
|
||||||
|
|
||||||
|
if i == len(error_path) - 1:
|
||||||
|
if hasattr(current, "lc"):
|
||||||
|
line_number = current.lc.line
|
||||||
|
else:
|
||||||
|
line_number = last_line_number
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
line_number = "Unable to determine"
|
||||||
|
|
||||||
|
error_message.append(
|
||||||
|
f"Line {line_number}: {' -> '.join(map(str, error_path))} - {error.get('msg', error.get('type', 'Unknown'))}"
|
||||||
|
)
|
||||||
|
|
||||||
|
return JSONResponse(
|
||||||
|
content=(
|
||||||
|
{
|
||||||
|
"success": False,
|
||||||
|
"message": "Your configuration is invalid.\nSee the official documentation at docs.frigate.video.\n\n"
|
||||||
|
+ "\n".join(error_message),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
status_code=400,
|
||||||
|
)
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
content=(
|
content=(
|
||||||
{
|
{
|
||||||
"success": False,
|
"success": False,
|
||||||
"message": f"\nConfig Error:\n\n{escape(str(traceback.format_exc()))}",
|
"message": f"\nYour configuration is invalid.\nSee the official documentation at docs.frigate.video.\n\n{escape(str(traceback.format_exc()))}",
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
status_code=400,
|
status_code=400,
|
||||||
|
Loading…
Reference in New Issue
Block a user