"""The route decorator and associated variables and functions.""" from __future__ import annotations import re from reflex import constants def verify_route_validity(route: str) -> None: """Verify if the route is valid, and throw an error if not. Args: route: The route that need to be checked Raises: ValueError: If the route is invalid. """ pattern = catchall_in_route(route) if pattern and not route.endswith(pattern): raise ValueError(f"Catch-all must be the last part of the URL: {route}") if route == "api" or route.startswith("api/"): raise ValueError( f"Cannot have a route prefixed with 'api/': {route} (conflicts with NextJS)" ) def get_route_args(route: str) -> dict[str, str]: """Get the dynamic arguments for the given route. Args: route: The route to get the arguments for. Returns: The route arguments. """ args = {} def add_route_arg(match: re.Match[str], type_: str): """Add arg from regex search result. Args: match: Result of a regex search type_: The assigned type for this arg Raises: ValueError: If the route is invalid. """ arg_name = match.groups()[0] if arg_name in args: raise ValueError( f"Arg name [{arg_name}] is used more than once in this URL" ) args[arg_name] = type_ # Regex to check for route args. check = constants.RouteRegex.ARG check_strict_catchall = constants.RouteRegex.STRICT_CATCHALL check_opt_catchall = constants.RouteRegex.OPT_CATCHALL # Iterate over the route parts and check for route args. for part in route.split("/"): match_opt = check_opt_catchall.match(part) if match_opt: add_route_arg(match_opt, constants.RouteArgType.LIST) break match_strict = check_strict_catchall.match(part) if match_strict: add_route_arg(match_strict, constants.RouteArgType.LIST) break match = check.match(part) if match: # Add the route arg to the list. add_route_arg(match, constants.RouteArgType.SINGLE) return args def catchall_in_route(route: str) -> str: """Extract the catchall part from a route. Args: route: the route from which to extract Returns: str: the catchall part of the URI """ match_ = constants.RouteRegex.CATCHALL.search(route) return match_.group() if match_ else "" def catchall_prefix(route: str) -> str: """Extract the prefix part from a route that contains a catchall. Args: route: the route from which to extract Returns: str: the prefix part of the URI """ pattern = catchall_in_route(route) return route.replace(pattern, "") if pattern else "" def replace_brackets_with_keywords(input_string: str) -> str: """Replace brackets and everything inside it in a string with a keyword. Args: input_string: String to replace. Returns: new string containing keywords. """ # /posts -> /post # /posts/[slug] -> /posts/__SINGLE_SEGMENT__ # /posts/[slug]/comments -> /posts/__SINGLE_SEGMENT__/comments # /posts/[[slug]] -> /posts/__DOUBLE_SEGMENT__ # / posts/[[...slug2]]-> /posts/__DOUBLE_CATCHALL_SEGMENT__ # /posts/[...slug3]-> /posts/__SINGLE_CATCHALL_SEGMENT__ # Replace [[...]] with __DOUBLE_CATCHALL_SEGMENT__ output_string = re.sub( r"\[\[\.\.\..+?\]\]", constants.RouteRegex.DOUBLE_CATCHALL_SEGMENT, input_string, ) # Replace [...] with __SINGLE_CATCHALL_SEGMENT__ output_string = re.sub( r"\[\.\.\..+?\]", constants.RouteRegex.SINGLE_CATCHALL_SEGMENT, output_string, ) # Replace [[]] with __DOUBLE_SEGMENT__ output_string = re.sub( r"\[\[.+?\]\]", constants.RouteRegex.DOUBLE_SEGMENT, output_string ) # Replace [] with __SINGLE_SEGMENT__ output_string = re.sub( r"\[.+?\]", constants.RouteRegex.SINGLE_SEGMENT, output_string ) return output_string