#include "router.h" #include #include #include #define INITIAL_CHILDREN 4 #define INITIAL_PARAMS 8 // node_new creates a new router node. static node_t* node_new(void) { node_t *n = calloc(1, sizeof(node_t)); if (!n) return NULL; n->children = malloc(INITIAL_CHILDREN * sizeof(node_t*)); n->child_cap = INITIAL_CHILDREN; return n; } // node_free recursively frees a node and its children. static void node_free(node_t *n) { if (!n) return; free(n->segment); for (int i = 0; i < n->child_count; i++) { node_free(n->children[i]); } free(n->children); for (int i = 0; i < n->param_count; i++) { free(n->param_names[i]); } free(n->param_names); free(n); } // node_add_child adds a child node. static void node_add_child(node_t *parent, node_t *child) { if (parent->child_count >= parent->child_cap) { parent->child_cap *= 2; parent->children = realloc(parent->children, parent->child_cap * sizeof(node_t*)); } parent->children[parent->child_count++] = child; } // read_segment extracts the next path segment. static int read_segment(const char *path, int start, char **segment, int *end) { if (start >= strlen(path)) { *segment = NULL; *end = start; return 0; } // Skip leading slash if (path[start] == '/') start++; if (start >= strlen(path)) { *segment = NULL; *end = start; return 0; } // Find end of segment int pos = start; while (path[pos] && path[pos] != '/') pos++; // Extract segment int len = pos - start; *segment = malloc(len + 1); memcpy(*segment, path + start, len); (*segment)[len] = '\0'; *end = pos; return path[pos] != '\0'; // has more segments? } // is_dynamic checks if segment is a dynamic parameter. static int is_dynamic(const char *seg) { return seg && seg[0] == ':'; } // is_wildcard checks if segment is a wildcard. static int is_wildcard(const char *seg) { return seg && seg[0] == '*'; } // extract_param_name gets the parameter name from :name or *name. static char* extract_param_name(const char *seg) { if (!seg || strlen(seg) < 2) return NULL; if (seg[0] == ':' || seg[0] == '*') { return strdup(seg + 1); } return NULL; } // router_new creates a new router. router_t* router_new(void) { router_t *r = calloc(1, sizeof(router_t)); if (!r) return NULL; r->get = node_new(); r->post = node_new(); r->put = node_new(); r->patch = node_new(); r->delete = node_new(); r->param_cap = INITIAL_PARAMS; r->param_values = malloc(r->param_cap * sizeof(char*)); r->param_names = malloc(r->param_cap * sizeof(char*)); return r; } // router_free destroys the router. void router_free(router_t *r) { if (!r) return; node_free(r->get); node_free(r->post); node_free(r->put); node_free(r->patch); node_free(r->delete); free(r->param_values); free(r->param_names); free(r); } // get_method_node returns the root node for a method. static node_t* get_method_node(router_t *r, const char *method) { if (strcmp(method, "GET") == 0) return r->get; if (strcmp(method, "POST") == 0) return r->post; if (strcmp(method, "PUT") == 0) return r->put; if (strcmp(method, "PATCH") == 0) return r->patch; if (strcmp(method, "DELETE") == 0) return r->delete; return NULL; } // router_add registers a route. int router_add(router_t *r, const char *method, const char *path, handler_t handler) { node_t *root = get_method_node(r, method); if (!root) return -1; // Handle root path if (strcmp(path, "/") == 0) { root->handler = handler; return 0; } node_t *current = root; int pos = 0; char **params = NULL; int param_count = 0; while (1) { char *seg; int end; int has_more = read_segment(path, pos, &seg, &end); if (!seg) break; // Check for dynamic/wildcard int is_dyn = is_dynamic(seg); int is_wc = is_wildcard(seg); if (is_wc && has_more) { free(seg); free(params); return -1; // wildcard must be last } // Track parameter names if (is_dyn || is_wc) { params = realloc(params, (param_count + 1) * sizeof(char*)); params[param_count++] = extract_param_name(seg); } // Find or create child node node_t *child = NULL; for (int i = 0; i < current->child_count; i++) { if (strcmp(current->children[i]->segment, seg) == 0) { child = current->children[i]; break; } } if (!child) { child = node_new(); child->segment = seg; child->is_dynamic = is_dyn; child->is_wildcard = is_wc; node_add_child(current, child); } else { free(seg); } current = child; pos = end; } // Set handler and params current->handler = handler; current->param_names = params; current->param_count = param_count; return 0; } // Convenience methods int router_get(router_t *r, const char *path, handler_t handler) { return router_add(r, "GET", path, handler); } int router_post(router_t *r, const char *path, handler_t handler) { return router_add(r, "POST", path, handler); } int router_put(router_t *r, const char *path, handler_t handler) { return router_add(r, "PUT", path, handler); } int router_delete(router_t *r, const char *path, handler_t handler) { return router_add(r, "DELETE", path, handler); } // match_node recursively matches a path against the tree. static handler_t match_node(node_t *n, const char *path, int start, char **param_values, char **param_names, int *param_idx, int max_params) { // Check wildcards first for (int i = 0; i < n->child_count; i++) { if (n->children[i]->is_wildcard) { // Capture rest of path const char *rest = path + start; if (*rest == '/') rest++; if (*param_idx < max_params) { param_values[*param_idx] = (char*)rest; param_names[*param_idx] = n->children[i]->param_names[0]; (*param_idx)++; } return n->children[i]->handler; } } // Extract current segment char *seg; int end; int has_more = read_segment(path, start, &seg, &end); if (!seg) { // End of path return n->handler; } // Try exact match first for (int i = 0; i < n->child_count; i++) { node_t *child = n->children[i]; if (!child->is_dynamic && !child->is_wildcard && strcmp(child->segment, seg) == 0) { handler_t h = match_node(child, path, end, param_values, param_names, param_idx, max_params); free(seg); return h; } } // Try dynamic segments for (int i = 0; i < n->child_count; i++) { node_t *child = n->children[i]; if (child->is_dynamic) { // Capture parameter value if (*param_idx < max_params) { param_values[*param_idx] = seg; // Don't free seg here param_names[*param_idx] = child->param_names[0]; (*param_idx)++; } handler_t h = match_node(child, path, end, param_values, param_names, param_idx, max_params); if (h) return h; // Backtrack if (*param_idx > 0) (*param_idx)--; } } free(seg); return NULL; } // router_lookup finds a matching route. handler_t router_lookup(router_t *r, const char *method, const char *path, char ***params, char ***names, int *count) { node_t *root = get_method_node(r, method); if (!root) return NULL; // Handle root path if (strcmp(path, "/") == 0) { *count = 0; return root->handler; } // Reset parameter buffers memset(r->param_values, 0, r->param_cap * sizeof(char*)); memset(r->param_names, 0, r->param_cap * sizeof(char*)); int param_idx = 0; handler_t h = match_node(root, path, 0, r->param_values, r->param_names, ¶m_idx, r->param_cap); if (h) { *params = r->param_values; *names = r->param_names; *count = param_idx; } return h; } // router_param gets a parameter by name. const char* router_param(conn_t *c, const char *name) { // This would need to be implemented with connection context // For now, return NULL return NULL; } // router_param_idx gets a parameter by index. const char* router_param_idx(conn_t *c, int index) { // This would need to be implemented with connection context // For now, return NULL return NULL; }