Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
jamieecarrasquillo authored and JonathanRosado committed Jan 3, 2022
1 parent 0e23c9e commit 7f9a5e9
Show file tree
Hide file tree
Showing 17 changed files with 996 additions and 0 deletions.
Empty file added .gitignore
Empty file.
5 changes: 5 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions .idea/codeStyles/codeStyleConfig.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions .idea/deno-experiments.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/deno.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

119 changes: 119 additions & 0 deletions application.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { serve, ServeInit } from "https://deno.land/[email protected]/http/server.ts";
import { Handler } from "./handler.ts";
import {CatchFunc, HandleFunc, Matcher} from "./types.ts";
import { Request as RoarterRequest } from "./request.ts";

export class Application {
handlers: Handler[] = [];
catchFunc: CatchFunc = () => {
throw new Error("Not Implemented");
};

match(custom: Matcher): Handler {
const handler = new Handler().match(custom);
this.handlers.push(handler);
return handler;
}

get get(): Handler {
const handler = new Handler().get;
this.handlers.push(handler);
return handler;
}

get post(): Handler {
const handler = new Handler().post;
this.handlers.push(handler);
return handler;
}

get put(): Handler {
const handler = new Handler().put;
this.handlers.push(handler);
return handler;
}

get delete(): Handler {
const handler = new Handler().delete;
this.handlers.push(handler);
return handler;
}

get patch(): Handler {
const handler = new Handler().patch;
this.handlers.push(handler);
return handler;
}

path(path: string): Handler {
const handler = new Handler().path(path);
this.handlers.push(handler);
return handler;
}

queries(queries: string[]): Handler {
const handler = new Handler().queries(queries);
this.handlers.push(handler);
return handler;
}

handle(handler: HandleFunc | Application): Handler {
const h = new Handler().handle(handler);
this.handlers.push(h);
return h;
}

catch(fn: CatchFunc) {
this.catchFunc = fn;
}

// Handler runs all the registered handlers and returns the first
// response it finds. After returning the first response, it runs
// the remaining handlers. The purpose of this is to support middleware
// after a response has been sent.
async runHandlers(req: RoarterRequest): Promise<Response> {
let runRemaining = true
let i = 0;
try {
try {
for (i; i < this.handlers.length; i++) {
const handler = this.handlers[i];
const response = await handler.run(req);
if (response) {
i++;
return response;
}
}
} catch (e) {
// If an error occurs, we wan't to skip all handlers and run the catchFunc
runRemaining = false
return await this.catchFunc(req, e);
}
// If after running all the handlers there is no response, throw the error
throw new Error(
`No Response was sent for ${req.method} ${req.url}`,
);
} finally {
if (runRemaining) {
// Since at this point we have already returned a response,
// we run the remaining handlers ignoring their response and errors
for (i; i < this.handlers.length; i++) {
const handler = this.handlers[i];
handler.run(req).catch(e => {
this.catchFunc(req, e)
})
}
}
}
}

async handler(domReq: Request): Promise<Response> {
const req = new RoarterRequest(domReq);
return this.runHandlers(req)
}

async serve(options?: ServeInit) {
this.handler = this.handler.bind(this);
await serve(this.handler, options);
}
}
83 changes: 83 additions & 0 deletions handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { HandleFunc, Matcher } from "./types.ts";
import {
buildPathMatcher,
buildQueryMatcher,
deleteMatcher,
getMatcher,
patchMatcher,
postMatcher,
putMatcher,
} from "./matchers.ts";
import { Response } from "./response.ts";
import { Request } from "./request.ts";
import {Application} from "./application.ts";

export class Handler {
matchers: Matcher[] = [];
handler: (HandleFunc | Application) | null = null;

private isHandlerASubRouter(): boolean {
return this.handler instanceof Application
}

runMatchers(req: Request): boolean {
for (let i = 0; i < this.matchers.length; i++) {
const matcher: Matcher = this.matchers[i];
if (!matcher(req, this.isHandlerASubRouter())) return false;
}
return true;
}

async runHandler(req: Request) {
if (!this.handler) return;
if (this.isHandlerASubRouter()) {
return (this.handler as Application).runHandlers(req)
} else {
return (this.handler as HandleFunc)(req);
}
}

match(custom: Matcher): Handler {
this.matchers.push(custom);
return this;
}

get get(): Handler {
return this.match(getMatcher);
}

get post(): Handler {
return this.match(postMatcher);
}

get put(): Handler {
return this.match(putMatcher);
}

get delete(): Handler {
return this.match(deleteMatcher);
}

get patch(): Handler {
return this.match(patchMatcher);
}

path(urlPath: string): Handler {
return this.match(buildPathMatcher(urlPath));
}

queries(queries: string[]): Handler {
return this.match(buildQueryMatcher(queries));
}

handle(handler: HandleFunc | Application) {
this.handler = handler;
return this;
}

async run(req: Request) {
if (this.runMatchers(req)) {
return this.runHandler(req);
}
}
}
65 changes: 65 additions & 0 deletions matchers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { Matcher, ROMap } from "./types.ts";
import { extract, match } from "./utils/path.ts";

export const getMatcher: Matcher = (req) => {
return req.method === "GET";
};

export const postMatcher: Matcher = (req) => {
return req.method === "POST";
};

export const putMatcher: Matcher = (req) => {
return req.method === "PUT";
};

export const deleteMatcher: Matcher = (req) => {
return req.method === "DELETE";
};

export const patchMatcher: Matcher = (req) => {
return req.method === "PATCH";
};

export const buildPathMatcher: (path: string) => Matcher = (path) => {
const prefixKey = "PathMatcher#PathPrefix"
return (req, matcherIsForASubRouter) => {
const url = new URL(req.url);

// If the matcher is for a subrouter (as opposed to a handlerFunc), then we want to match the prefix
// of the path name and store it for later use
if (matcherIsForASubRouter) {
let pathCopy = path
if (req.vars.has(prefixKey)) {
pathCopy = req.vars.get(prefixKey) + pathCopy
}
if (!match(pathCopy, url.pathname, true)) return false;
req.vars.set(prefixKey, pathCopy)
const m = extract(pathCopy, url.pathname, true);
for (const [key, val] of m) {
req.params.set(key, val)
}
return true
} else {
let pathCopy = path
if (req.vars.has(prefixKey)) {
pathCopy = req.vars.get(prefixKey) + pathCopy
}
if (!match(pathCopy, url.pathname)) return false;
const m = extract(pathCopy, url.pathname);
for (const [key, val] of m) {
req.params.set(key, val)
}
return true;
}
};
};

export const buildQueryMatcher: (queries: string[]) => Matcher = (queries) => {
return (req) => {
for (const query of queries) {
if (!req.queries.has(query)) return false;
}
return true;
};
};
17 changes: 17 additions & 0 deletions request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { ROMap } from "./types.ts";

class RoarterRequest extends Request {
public params: Map<string, string> = new Map<string, string>();
public vars: Map<string, string> = new Map<string, string>();
public queries: URLSearchParams;
public pathname: string;

constructor(input: RequestInfo, init?: RequestInit) {
super(input, init);
const url = new URL(this.url);
this.pathname = url.pathname;
this.queries = url.searchParams;
}
}

export { RoarterRequest as Request };
9 changes: 9 additions & 0 deletions response.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class RoarterResponse extends Response {
// @ts-ignore
constructor(body?: BodyInit | null, init?: ResponseInit) {
// @ts-ignore
super(body, init);
}
}

export { RoarterResponse as Response };
Loading

0 comments on commit 7f9a5e9

Please sign in to comment.