srctree

Gregory Mullen parent 152361cc 7aa168b5
make endpoints somewhat recursive

examples/endpoint.zig added: 118, removed: 35, total 83
@@ -1,10 +1,11 @@
//! Quick start example using Verse Endpoints.
const Endpoints = verse.Endpoints(.{
@import("endpoint/example.zig"),
@import("endpoint/index.zig"),
});
 
pub fn main() !void {
var endpoints = Endpoints.init();
endpoints.serve(std.heap.page_allocator, .{
var endpoints = Endpoints.init(std.heap.page_allocator);
endpoints.serve(.{
.mode = .{ .http = .{ .port = 8084 } },
}) catch |err| {
std.log.err("Unable to serve endpoints! err: [{}]", .{err});
@@ -12,10 +13,5 @@ pub fn main() !void {
};
}
 
fn index(frame: *verse.Frame) verse.Router.Error!void {
try frame.quickStart();
try frame.sendRawSlice("hello world");
}
 
const std = @import("std");
const verse = @import("verse");
 
ev/null added: 118, removed: 35, total 83
@@ -1,9 +0,0 @@
/// .root is a special cased name to resolve at "/"
pub const verse_name = .root;
 
pub fn index(frame: *verse.Frame) !void {
try frame.quickStart();
try frame.sendRawSlice("hello world");
}
 
const verse = @import("verse");
 
filename was Deleted added: 118, removed: 35, total 83
@@ -0,0 +1,27 @@
//! `verse_name = .root` is a special case name to enable resolve at "/", and only
//! valid as a top level endpoint definition. Otherwise for any endpoint
//! hierarchy depth > 0 `verse_name = .root` will resolve to "/root".
 
pub const verse_name = .root;
 
pub const verse_routes = [_]Router.Match{
Router.ANY("hi", hi),
};
 
pub const verse_endpoints = verse.Endpoints(.{
@import("random.zig"),
});
 
pub fn index(frame: *Frame) !void {
try frame.quickStart();
try frame.sendRawSlice("hello world");
}
 
fn hi(frame: *Frame) !void {
try frame.quickStart();
try frame.sendRawSlice("hi, mom!");
}
 
const verse = @import("verse");
const Frame = verse.Frame;
const Router = verse.Router;
 
filename was Deleted added: 118, removed: 35, total 83
@@ -0,0 +1,39 @@
pub const verse_name = .random;
 
var Random = std.Random.DefaultPrng.init(31337);
var random = Random.random();
 
pub const verse_routes = [_]Router.Match{
Router.GET("number", number),
Router.GET("quote", quote),
};
 
fn number(frame: *Frame) !void {
try frame.quickStart();
var buffer: [0xff]u8 = undefined;
try frame.sendRawSlice(try std.fmt.bufPrint(&buffer, "{}", .{random.int(usize)}));
}
 
const quotes = enum {
@"You can't take the sky from me",
@"Curse your sudden but inevitable betrayal!",
@"I swear by my pretty floral bonnet, I will end you!",
@"I’m thinking you weren’t burdened with an overabundance of schooling",
@"Well, look at this! Appears we got here just in the nick of time. What does that make us? -- Big damn heroes, sir.",
@"It is, however, somewhat fuzzier on the subject of kneecaps",
@"When you can't run anymore, you crawl... and when you can't do that -- You find someone to carry you.",
@"My food is problematic!",
};
 
fn quote(frame: *Frame) !void {
try frame.quickStart();
const rand_quote = @tagName(random.enumValue(quotes));
try frame.sendRawSlice("<p>");
try frame.sendRawSlice(rand_quote);
try frame.sendRawSlice("</p>");
}
 
const std = @import("std");
const verse = @import("verse");
const Frame = verse.Frame;
const Router = verse.Router;
 
src/endpoint.zig added: 118, removed: 35, total 83
@@ -1,7 +1,5 @@
const Endpoint = @This();
 
targets: []Target,
 
pub const Target = struct {
name: []const u8,
};
@@ -11,30 +9,39 @@ pub const Options = struct {
auth: Auth.Provider = Auth.InvalidAuth.provider(),
};
 
/// `endpoints` can be a tuple of any number of supported containers. The only
/// supported container is a struct that includes the minimum set of definitions
/// for verse to construct a valid server route.
/// TODO enumerate minimal example
pub fn Endpoints(endpoints: anytype) type {
if (@typeInfo(@TypeOf(endpoints)).Struct.is_tuple == false) return error.InvalidEndpointTypes;
inline for (endpoints) |ep| {
validateEndpoint(ep);
}
 
return struct {
alloc: Allocator,
 
pub const Self = @This();
pub const Endpoints = endpoints;
 
pub const routes = buildRoutes(endpoints[0]);
 
pub fn init() Self {
return .{};
pub fn init(a: Allocator) Self {
return .{
.alloc = a,
};
}
 
pub fn serve(_: *Self, a: Allocator, options: Options) !void {
var server = try Server.init(a, .{
pub fn serve(self: *Self, options: Options) !void {
var server = try Server.init(self.alloc, .{
.mode = options.mode,
.router = .{ .routefn = route },
});
try server.serve();
}
 
pub fn route(frame: *Frame) !Router.BuildFn {
pub fn route(frame: *Frame) Router.RoutingError!Router.BuildFn {
return Router.router(frame, &routes);
}
};
@@ -48,8 +55,20 @@ fn validateEndpoint(EP: anytype) void {
fn routeCount(EP: type) usize {
var count: usize = 0;
for (@typeInfo(EP).Struct.decls) |decl| {
if (std.mem.eql(u8, "index", decl.name)) count += 1;
if (eql(u8, "index", decl.name)) count += 1;
}
 
if (@hasDecl(EP, "verse_routes")) for (EP.verse_routes) |route| {
if (route.name.len == 0) {
@compileError("route name omitted for: " ++ @typeName(EP) ++ ". To support a directory URI, define an index() instead");
}
count += 1;
};
 
if (@hasDecl(EP, "verse_endpoints")) {
count += EP.verse_endpoints.Endpoints.len;
}
 
return count;
}
 
@@ -57,17 +76,28 @@ fn buildRoutes(EP: anytype) [routeCount(EP)]Router.Match {
var match: [routeCount(EP)]Router.Match = undefined;
var idx: usize = 0;
for (@typeInfo(EP).Struct.decls) |decl| {
if (std.mem.eql(u8, "index", decl.name)) {
if (eql(u8, "index", decl.name)) {
match[idx] = Router.ANY("", EP.index);
idx += 1;
}
}
 
if (@hasDecl(EP, "verse_routes")) for (EP.verse_routes) |route| {
match[idx] = route;
idx += 1;
};
 
if (@hasDecl(EP, "verse_endpoints")) for (EP.verse_endpoints.Endpoints) |endpoint| {
match[idx] = Router.ROUTE(@tagName(endpoint.verse_name), &EP.verse_endpoints.routes);
idx += 1;
};
 
return match;
}
 
const std = @import("std");
const Allocator = std.mem.Allocator;
const eql = std.mem.eql;
const Frame = @import("frame.zig");
const Auth = @import("auth.zig");
const Server = @import("server.zig");
 
src/router.zig added: 118, removed: 35, total 83
@@ -2,6 +2,9 @@ routefn: RouteFn,
builderfn: BuilderFn = defaultBuilder,
routerfn: RouterFn = defaultRouter,
 
/// TODO document
const Router = @This();
 
/// The default page generator, this is the function that will be called, and
/// expected to write the page data back to the client.
pub const BuildFn = *const fn (*Frame) Error!void;
@@ -22,9 +25,6 @@ pub const RouteFn = *const fn (*Frame) RoutingError!BuildFn;
/// return a default BuildFn.
pub const RouterFn = *const fn (*Frame, RouteFn) BuildFn;
 
/// TODO document
const Router = @This();
 
/// The Verse router will scan through an array of Match structs looking for a
/// given name. Verse doesn't assert that the given name will match a director
/// or endpoint/page specifically. e.g. `/uri/page` and `/uri/page/` will both