337 lines
7.9 KiB
C
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, ¶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;
|
|
}
|