Skip to content

Commit d8953ec

Browse files
authored
Merge pull request #2 from openSVM/openSVM_zup_issue_1_44a73718
improve code quality (Run ID: openSVM_zup_issue_1_44a73718)
2 parents 5e4b210 + 04da0d3 commit d8953ec

10 files changed

Lines changed: 899 additions & 190 deletions

File tree

src/framework/core.zig

Lines changed: 122 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,132 @@
11
const std = @import("std");
22

3+
pub const Method = enum {
4+
GET,
5+
POST,
6+
PUT,
7+
DELETE,
8+
OPTIONS,
9+
HEAD,
10+
PATCH,
11+
12+
pub fn fromString(method_str: []const u8) ?Method {
13+
if (std.mem.eql(u8, method_str, "GET")) return .GET;
14+
if (std.mem.eql(u8, method_str, "POST")) return .POST;
15+
if (std.mem.eql(u8, method_str, "PUT")) return .PUT;
16+
if (std.mem.eql(u8, method_str, "DELETE")) return .DELETE;
17+
if (std.mem.eql(u8, method_str, "OPTIONS")) return .OPTIONS;
18+
if (std.mem.eql(u8, method_str, "HEAD")) return .HEAD;
19+
if (std.mem.eql(u8, method_str, "PATCH")) return .PATCH;
20+
return null;
21+
}
22+
23+
pub fn toString(self: Method) []const u8 {
24+
return switch (self) {
25+
.GET => "GET",
26+
.POST => "POST",
27+
.PUT => "PUT",
28+
.DELETE => "DELETE",
29+
.OPTIONS => "OPTIONS",
30+
.HEAD => "HEAD",
31+
.PATCH => "PATCH",
32+
};
33+
}
34+
};
35+
36+
pub const Request = struct {
37+
method: Method,
38+
path: []const u8,
39+
headers: std.StringHashMap([]const u8),
40+
body: ?[]const u8,
41+
42+
pub fn init(allocator: std.mem.Allocator) Request {
43+
return .{
44+
.method = .GET,
45+
.path = "",
46+
.headers = std.StringHashMap([]const u8).init(allocator),
47+
.body = null,
48+
};
49+
}
50+
51+
pub fn deinit(self: *Request) void {
52+
var allocator = self.headers.allocator;
53+
self.headers.deinit();
54+
if (self.body) |body| {
55+
allocator.free(body);
56+
}
57+
}
58+
};
59+
60+
pub const Response = struct {
61+
status: u16,
62+
headers: std.StringHashMap([]const u8),
63+
body: ?[]const u8,
64+
65+
pub fn init(allocator: std.mem.Allocator) Response {
66+
return .{
67+
.status = 200,
68+
.headers = std.StringHashMap([]const u8).init(allocator),
69+
.body = null,
70+
};
71+
}
72+
73+
pub fn deinit(self: *Response) void {
74+
var allocator = self.headers.allocator;
75+
self.headers.deinit();
76+
if (self.body) |body| {
77+
allocator.free(body);
78+
}
79+
}
80+
};
81+
382
pub const Context = struct {
483
allocator: std.mem.Allocator,
5-
84+
request: Request,
85+
response: Response,
86+
params: std.StringHashMap([]const u8),
87+
688
pub fn init(allocator: std.mem.Allocator) Context {
789
return .{
890
.allocator = allocator,
91+
.request = Request.init(allocator),
92+
.response = Response.init(allocator),
93+
.params = std.StringHashMap([]const u8).init(allocator),
994
};
1095
}
96+
97+
pub fn deinit(self: *Context) void {
98+
self.request.deinit();
99+
self.response.deinit();
100+
self.params.deinit();
101+
}
11102
};
103+
104+
pub const Handler = *const fn (*Context) anyerror!void;
105+
106+
pub const Middleware = struct {
107+
data: ?*anyopaque,
108+
handle_fn: *const fn (*Context, Handler) anyerror!void,
109+
deinit_fn: ?*const fn (*Middleware) void,
110+
111+
pub fn init(
112+
data: ?*anyopaque,
113+
handle_fn: *const fn (*Context, Handler) anyerror!void,
114+
deinit_fn: ?*const fn (*Middleware) void,
115+
) Middleware {
116+
return .{
117+
.data = data,
118+
.handle_fn = handle_fn,
119+
.deinit_fn = deinit_fn,
120+
};
121+
}
122+
123+
pub fn handle(self: *const Middleware, ctx: *Context, next: Handler) !void {
124+
return self.handle_fn(ctx, next);
125+
}
126+
127+
pub fn deinit(self: *Middleware) void {
128+
if (self.deinit_fn) |deinit_fn| {
129+
deinit_fn(self);
130+
}
131+
}
132+
};

src/framework/example.zig

Lines changed: 75 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
const std = @import("std");
22
const Server = @import("server.zig").Server;
3-
const Config = @import("server.zig").Config;
3+
const ServerConfig = @import("server.zig").ServerConfig;
44
const core = @import("core.zig");
5+
const Router = @import("router.zig").Router;
56

67
// Example middleware that logs requests
78
const LoggerMiddleware = struct {
@@ -27,17 +28,33 @@ const LoggerMiddleware = struct {
2728
}
2829
};
2930

31+
// Helper function to set text response
32+
fn setText(ctx: *core.Context, text: []const u8) !void {
33+
ctx.response.body = try ctx.allocator.dupe(u8, text);
34+
try ctx.response.headers.put("Content-Type", "text/plain");
35+
}
36+
37+
// Helper function to set JSON response
38+
fn setJson(ctx: *core.Context, data: anytype) !void {
39+
var json_string = std.ArrayList(u8).init(ctx.allocator);
40+
defer json_string.deinit();
41+
42+
try std.json.stringify(data, .{}, json_string.writer());
43+
ctx.response.body = try ctx.allocator.dupe(u8, json_string.items);
44+
try ctx.response.headers.put("Content-Type", "application/json");
45+
}
46+
3047
// Example handlers
3148
fn homeHandlerImpl(ctx: *core.Context) !void {
32-
try ctx.text("Welcome to Zup!");
49+
try setText(ctx, "Welcome to Zup!");
3350
}
3451

3552
fn jsonHandlerImpl(ctx: *core.Context) !void {
3653
const data = .{
3754
.message = "Hello, JSON!",
3855
.timestamp = std.time.timestamp(),
3956
};
40-
try ctx.json(data);
57+
try setJson(ctx, data);
4158
}
4259

4360
fn userHandlerImpl(ctx: *core.Context) !void {
@@ -47,11 +64,15 @@ fn userHandlerImpl(ctx: *core.Context) !void {
4764
.name = "Example User",
4865
.email = "user@example.com",
4966
};
50-
try ctx.json(response);
67+
try setJson(ctx, response);
5168
}
5269

5370
fn echoHandlerImpl(ctx: *core.Context) !void {
54-
try ctx.text(ctx.request.body);
71+
if (ctx.request.body) |body| {
72+
try setText(ctx, body);
73+
} else {
74+
try setText(ctx, "No body provided");
75+
}
5576
}
5677

5778
pub fn main() !void {
@@ -60,24 +81,28 @@ pub fn main() !void {
6081
defer _ = gpa.deinit();
6182
const allocator = gpa.allocator();
6283

63-
// Create server with custom config
64-
var server = try Server.init(allocator, .{
65-
.address = "127.0.0.1",
66-
.port = 8080,
67-
.thread_count = 4,
68-
});
69-
defer server.deinit();
84+
// Create router
85+
var router = Router.init(allocator);
86+
defer router.deinit();
7087

7188
// Add global middleware
7289
var logger = LoggerMiddleware.init();
7390
defer logger.deinit();
74-
try server.use(core.Middleware.init(logger, LoggerMiddleware.handle));
91+
try router.use(core.Middleware.init(logger, LoggerMiddleware.handle));
7592

7693
// Define routes
77-
try server.get("/", homeHandlerImpl);
78-
try server.get("/json", jsonHandlerImpl);
79-
try server.get("/users/:id", userHandlerImpl);
80-
try server.post("/echo", echoHandlerImpl);
94+
try router.get("/", homeHandlerImpl);
95+
try router.get("/json", jsonHandlerImpl);
96+
try router.get("/users/:id", userHandlerImpl);
97+
try router.post("/echo", echoHandlerImpl);
98+
99+
// Create server with custom config
100+
var server = try Server.init(allocator, .{
101+
.host = "127.0.0.1",
102+
.port = 8080,
103+
.thread_count = 4,
104+
}, &router);
105+
defer server.deinit();
81106

82107
// Start server
83108
std.log.info("Server running at http://127.0.0.1:8080", .{});
@@ -88,33 +113,46 @@ test "basic routes" {
88113
const testing = std.testing;
89114
const allocator = testing.allocator;
90115

91-
var server = try Server.init(allocator, .{
92-
.port = 0, // Random port for testing
93-
});
94-
defer server.deinit();
116+
// Create router
117+
var router = Router.init(allocator);
118+
defer router.deinit();
95119

96120
// Add test routes
97-
try server.get("/test", &struct {
121+
try router.get("/test", &struct {
98122
fn handler(ctx: *core.Context) !void {
99-
try ctx.text("test ok");
123+
try setText(ctx, "test ok");
100124
}
101125
}.handler);
102126

103-
try server.post("/echo", echoHandlerImpl);
127+
try router.post("/echo", echoHandlerImpl);
128+
129+
// Create server
130+
var server = try Server.init(allocator, .{
131+
.port = 0, // Random port for testing
132+
.thread_count = 1,
133+
}, &router);
134+
defer server.deinit();
104135

105136
// Start server in background
106-
const thread = try std.Thread.spawn(.{}, Server.start, .{&server});
107-
defer {
108-
server.running.store(false, .release);
109-
thread.join();
110-
}
137+
var running = true;
138+
const thread = try std.Thread.spawn(.{}, struct {
139+
fn run(srv: *Server, is_running: *bool) void {
140+
srv.start() catch |err| {
141+
std.debug.print("Server error: {}\n", .{err});
142+
};
143+
is_running.* = false;
144+
}
145+
}.run, .{&server, &running});
111146

112147
// Wait a bit for server to start
113-
std.time.sleep(10 * std.time.ns_per_ms);
148+
std.time.sleep(100 * std.time.ns_per_ms);
149+
150+
// Get server address
151+
const server_address = server.address;
114152

115153
// Test GET request
116154
{
117-
const client = try std.net.tcpConnectToAddress(server.listener.listen_address);
155+
const client = try std.net.tcpConnectToAddress(server_address);
118156
defer client.close();
119157

120158
try client.writer().writeAll(
@@ -134,7 +172,7 @@ test "basic routes" {
134172

135173
// Test POST request
136174
{
137-
const client = try std.net.tcpConnectToAddress(server.listener.listen_address);
175+
const client = try std.net.tcpConnectToAddress(server_address);
138176
defer client.close();
139177

140178
const body = "Hello, Echo!";
@@ -156,4 +194,8 @@ test "basic routes" {
156194

157195
try testing.expect(std.mem.indexOf(u8, response, body) != null);
158196
}
159-
}
197+
198+
// Stop server
199+
server.stop();
200+
thread.join();
201+
}

src/framework/router.zig

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ pub const Router = struct {
7878
// Find matching route
7979
const route = self.findRoute(ctx.request.method, ctx.request.path) orelse return error.RouteNotFound;
8080

81+
// Extract path parameters
82+
try extractParams(ctx, route.pattern, ctx.request.path);
83+
8184
// If no middleware, just call the handler
8285
if (self.global_middleware.items.len == 0) {
8386
return route.handler(ctx);
@@ -103,22 +106,62 @@ pub const Router = struct {
103106

104107
fn matchPattern(self: *Router, pattern: []const u8, path: []const u8) bool {
105108
_ = self;
109+
110+
// Handle root path
111+
if (std.mem.eql(u8, pattern, "/") and std.mem.eql(u8, path, "/")) {
112+
return true;
113+
}
114+
106115
var pattern_parts = std.mem.split(u8, pattern, "/");
107116
var path_parts = std.mem.split(u8, path, "/");
108-
117+
118+
// Skip empty first part if path starts with "/"
119+
if (pattern.len > 0 and pattern[0] == '/') _ = pattern_parts.next();
120+
if (path.len > 0 and path[0] == '/') _ = path_parts.next();
121+
109122
while (true) {
110123
const pattern_part = pattern_parts.next() orelse {
124+
// If we've reached the end of the pattern, the match is successful
125+
// only if we've also reached the end of the path
111126
return path_parts.next() == null;
112127
};
113-
const path_part = path_parts.next() orelse return false;
114-
128+
129+
const path_part = path_parts.next() orelse {
130+
// If we've reached the end of the path but not the pattern,
131+
// the match fails
132+
return false;
133+
};
134+
135+
// Handle path parameters (starting with ":")
115136
if (std.mem.startsWith(u8, pattern_part, ":")) {
137+
// This is a path parameter, it matches any path part
116138
continue;
117139
}
118-
140+
141+
// For regular path parts, they must match exactly
119142
if (!std.mem.eql(u8, pattern_part, path_part)) {
120143
return false;
121144
}
122145
}
123146
}
124-
};
147+
148+
fn extractParams(ctx: *core.Context, pattern: []const u8, path: []const u8) !void {
149+
var pattern_parts = std.mem.split(u8, pattern, "/");
150+
var path_parts = std.mem.split(u8, path, "/");
151+
152+
// Skip empty first part if path starts with "/"
153+
if (pattern.len > 0 and pattern[0] == '/') _ = pattern_parts.next();
154+
if (path.len > 0 and path[0] == '/') _ = path_parts.next();
155+
156+
while (true) {
157+
const pattern_part = pattern_parts.next() orelse break;
158+
const path_part = path_parts.next() orelse break;
159+
160+
// Extract parameter if pattern part starts with ":"
161+
if (std.mem.startsWith(u8, pattern_part, ":")) {
162+
const param_name = pattern_part[1..]; // Skip the ":" prefix
163+
try ctx.params.put(param_name, path_part);
164+
}
165+
}
166+
}
167+
};

0 commit comments

Comments
 (0)