Web/router.c
2025-09-15 12:57:54 -05:00

337 lines
7.9 KiB
C

#include "router.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#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, &param_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;
}