blob: 6a885d63b0c973c661eda53cc03324543efea9cd [file] [log] [blame]
# Copyright (c) 2025 Project CHIP Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
import logging
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Dict, List, Optional
@dataclass
class RouteResponse:
status: int
headers: Dict[str, str]
body: Dict[str, Any]
@dataclass
class QueryConfig:
params: Dict[str, Any]
@dataclass
class Route:
method: str
path: str
response: RouteResponse
body: Optional[Any] = None
query: Optional[QueryConfig] = None
@dataclass
class Configuration:
routing: List[Route] = field(default_factory=list)
def load_configurations(config_path: Path, routing_config_dir: Path) -> Configuration:
"""
Load and combine configuration files from specified paths.
Args:
config_path (Path): Path to the main configuration file.
routing_config_dir (Path): Directory containing routing configuration files.
Returns:
Configuration: A Configuration object containing all merged routing configurations.
Raises:
ValueError: If config_path is not a file or routing_config_dir is not a directory.
"""
if not config_path.is_file():
raise ValueError(f"'{config_path}' is not a file")
if not routing_config_dir.is_dir():
raise ValueError(f"'{routing_config_dir}' is not a directory")
# Load routing configuration
routing_config: List[Route] = load_routing_configuration_dir(routing_config_dir)
# Create and return the Configuration object
return Configuration(routing=routing_config)
def load_routing_configuration_dir(directory: Path) -> List[Route]:
"""
Load and merge all routing configuration files from a specified directory.
Args:
directory (Path): Directory path containing JSON configuration files.
Returns:
List[Route]: A list of Route objects containing all merged routing configurations.
Raises:
ValueError: If the specified directory path is not a directory.
Note:
- Processes all .json files in the specified directory
- Continues processing even if one file fails to load, logging the error
"""
if not directory.is_dir():
raise ValueError(f"'{directory}' is not a directory")
all_routes: List[Route] = []
# Process all JSON files in the directory
for file_path in directory.glob("*.json"):
try:
routes = load_routing_configuration_file(file_path)
all_routes.extend(routes)
except Exception as e:
print(f"Error loading configuration from {file_path}: {str(e)}")
continue
return all_routes
def load_routing_configuration_file(file_path: Path) -> List[Route]:
"""
Load and parse a single routing configuration JSON file.
Args:
file_path (Path): Path to the JSON configuration file.
Returns:
List[Route]: A list of Route objects parsed from the configuration file.
Raises:
FileNotFoundError: If the configuration file doesn't exist.
json.JSONDecodeError: If the file contains invalid JSON.
KeyError: If the required 'routes' key is missing in the configuration.
Format:
The JSON file should contain:
{
"routes": [
{
"method": str,
"path": str,
"response": {
"status": int,
"headers": dict[str, str],
"body": dict[str, Any]
},
"body": Any, # Optional
"query": dict[str, Any] # Optional
}
]
}
"""
try:
with open(file_path, "r") as file:
config = json.load(file)
if "routes" not in config:
logging.error("Missing required 'routes' key in configuration file: %s", file_path)
raise KeyError("Configuration file must contain 'routes' key")
logging.debug("Routes configuration loaded successfully from %s", file_path)
routes = []
for route_config in config["routes"]:
# Create RouteResponse object
response = RouteResponse(
status=route_config["response"]["status"],
headers=route_config["response"].get("headers", {}),
body=route_config["response"].get("body", {}),
)
# Create QueryConfig if query params exist
query = None
if "query" in route_config:
query = QueryConfig(params=route_config["query"])
# Create Route object
route = Route(
method=route_config["method"],
path=route_config["path"],
response=response,
body=route_config.get("body"),
query=query,
)
routes.append(route)
return routes
except FileNotFoundError:
logging.error("Configuration file not found: %s", file_path)
raise
except json.JSONDecodeError as e:
logging.error("Invalid JSON in configuration file: %s", file_path)
raise e