Skip to content

Commit

Permalink
Merge pull request #124 from sebringrose/static-dir
Browse files Browse the repository at this point in the history
Static dir
  • Loading branch information
sejori authored Feb 6, 2023
2 parents 1053074 + 5c69c29 commit bc1e031
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 24 deletions.
9 changes: 5 additions & 4 deletions mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
import Server from "./server.ts"
export default Server
export * from "./server.ts"
// alias for traditional Router setup
export const Router = Server

// Router alias for traditional Router setup
export { Server as Router }

// Handlers
export * from "./handlers/static.ts"
Expand All @@ -22,5 +23,5 @@ export * from "./middleware/logger.ts"
// Utils
export * from "./utils/Crypto.ts"
export * from "./utils/ResponseCache.ts"
// export * from "./utils/Cascade.ts"
// ^ not needed by users & clutters docs
export * from "./utils/Cascade.ts"
export * from "./utils/helpers.ts"
7 changes: 3 additions & 4 deletions server.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Server as stdServer } from "https://deno.land/std@0.174.0/http/server.ts"
import { Cascade } from "./utils/Cascade.ts"
import { promisify } from "./utils/helpers.ts"

export class RequestContext {
server: Server
Expand Down Expand Up @@ -56,7 +55,7 @@ export class Server {
middleware.forEach(mware => this.use(mware))
return middleware.length
}
return this.middleware.push(promisify(middleware))
return this.middleware.push(Cascade.promisify(middleware))
}

/**
Expand All @@ -83,14 +82,14 @@ export class Server {
if (!routeObj.path) throw new Error("Missing route path: `/${route}`")
if (!routeObj.handler) throw new Error("Missing route handler")
routeObj.method = routeObj.method || "GET"
routeObj.handler = promisify(routeObj.handler!) as Handler
routeObj.handler = Cascade.promisify(routeObj.handler!) as Handler

return this.routes.push({
...routeObj as Route,
middleware: [routeObj.middleware]
.flat()
.filter(Boolean)
.map((mware) => promisify(mware!)),
.map((mware) => Cascade.promisify(mware!)),
})
}

Expand Down
8 changes: 8 additions & 0 deletions tests/utils/Cascade_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ Deno.test("UTIL: Cascade", async (t) => {
const cascade = new Cascade(testContext, toCall)
const result = await cascade.start()

await t.step("promisify works", () => {
const testServer = new Server()
const testContext = new RequestContext(testServer, new Request("http://localhost"))
const testMW = () => new Response("hello")
const testMWProm = Cascade.promisify(testMW)
assert(testMWProm(testContext, () => {}) instanceof Promise)
})

await t.step("cascade run as expected", async () => {
// check async code before await next() call is properly awaited
assert(
Expand Down
32 changes: 24 additions & 8 deletions tests/utils/helpers_test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { assert } from "https://deno.land/std@0.174.0/testing/asserts.ts"
import { Server, RequestContext } from "../../server.ts"
import {
mergeHeaders,
promisify,
staticDir,
} from "../../utils/helpers.ts"
import {
Server
} from "../../server.ts"

Deno.test("UTIL: helpers", async (t) => {
await t.step("mergeHeaders", () => {
Expand All @@ -16,12 +18,26 @@ Deno.test("UTIL: helpers", async (t) => {
mergeHeaders(base, source)
assert(base.has("Content-Type") && base.has("Authorization"))
})
})

await t.step("promisify", () => {
const testServer = new Server()
const testContext = new RequestContext(testServer, new Request("http://localhost"))
const testMW = () => new Response("hello")
const testMWProm = promisify(testMW)
assert(testMWProm(testContext, () => {}) instanceof Promise)
Deno.test("UTIL: staticDir", async (t) => {
await t.step("returns all file routes with supplied middleware and static handler", async () => {
const server = new Server()
const request = new Request('https://localhost:7777/utils/helpers_test.ts')

let text = ''
const routes = await staticDir(new URL("../", import.meta.url), () => { text = "I was set" })

assert(routes.find(route => route.path.includes("handlers")))
assert(routes.find(route => route.path.includes("middleware")))
assert(routes.find(route => route.path.includes("mocks")))
assert(routes.find(route => route.path.includes("utils")))

server.addRoutes(routes)
const response = await server.requestHandler(request)
const fileText = await response.text()

assert(fileText == await Deno.readTextFile(new URL("./helpers_test.ts", import.meta.url)))
assert(text === "I was set")
})
})
10 changes: 9 additions & 1 deletion utils/Cascade.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { RequestContext, PromiseMiddleware } from "../server.ts"
import { RequestContext, PromiseMiddleware, Middleware, NextMiddleware } from "../server.ts"

type Result = undefined | void | Response

Expand All @@ -11,6 +11,14 @@ export class Cascade {

constructor(public ctx: RequestContext, private toCall: Array<PromiseMiddleware>) {}

static promisify = (fcn: Middleware): PromiseMiddleware => {
return fcn.constructor.name === "AsyncFunction"
? fcn as PromiseMiddleware
: (ctx: RequestContext, next: NextMiddleware) => new Promise((res, rej) => {
try { res(fcn(ctx, next)) } catch(e) { rej(e) }
})
}

async run(fcn: PromiseMiddleware): Promise<Result> {
if (!fcn) return

Expand Down
37 changes: 30 additions & 7 deletions utils/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { Middleware, NextMiddleware, PromiseMiddleware, RequestContext } from "../server.ts"
import {
Middleware,
Route
} from "../server.ts"

import { staticHandler } from "../handlers/static.ts"

export const mergeHeaders = (base: Headers, source: Headers) => {
for (const pair of source) {
Expand All @@ -8,10 +13,28 @@ export const mergeHeaders = (base: Headers, source: Headers) => {
return base
}

export const promisify = (fcn: Middleware): PromiseMiddleware => {
return fcn.constructor.name === "AsyncFunction"
? fcn as PromiseMiddleware
: (ctx: RequestContext, next: NextMiddleware) => new Promise((res, rej) => {
try { res(fcn(ctx, next)) } catch(e) { rej(e) }
})
export const staticDir = async (dirUrl: URL, middleware?: Middleware | Middleware[], routes?: Route[], depth = 1): Promise<Route[]> => {
if (!(await Deno.stat(dirUrl)).isDirectory) throw new Error("URL does not point to directory.")
if (!routes) routes = []

for await (const file of Deno.readDir(dirUrl)) {
const fileUrl = new URL(`file://${dirUrl.pathname}/${file.name}`)
if (file.isDirectory) {
await staticDir(fileUrl, middleware, routes, depth+1)
} else {
const pieces = dirUrl.pathname.split("/")

let dirPath = ''
for (let i=1; i<depth; i++) dirPath = `${pieces[pieces.length-i]}/${dirPath}`

const filePath = `${dirPath}${file.name}`
routes.push({
path: `/${filePath}`,
middleware,
handler: staticHandler(fileUrl)
})
}
}

return routes
}

0 comments on commit bc1e031

Please sign in to comment.