Skip to content

Commit 9cc4e38

Browse files
committed
refactor(errors): replace generic Error with Deno.errors types
- Change BasicAuth.create() to throw Deno.errors.InvalidData for empty users - Change Context.ensureBodyNotConsumed() to throw Deno.errors.BadResource - Change Context.render() and streamRender() to throw Deno.errors.NotSupported - Change Engine.resolveTemplate() to throw Deno.errors.NotFound for missing templates - Change Scanner.validateModule() to throw Deno.errors.InvalidData and TypeError - Change Session.create() to throw Deno.errors.InvalidData for empty cookieSecret - Remove trailing periods from single-line property JSDoc comments - Update @throws tags to match actual error classes
1 parent 0258689 commit 9cc4e38

5 files changed

Lines changed: 50 additions & 47 deletions

File tree

src/core/Context.ts

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,27 @@ import * as Core from '@core/index.ts'
66
* @description Parses body once; exposes headers, cookies, state.
77
*/
88
export class Context {
9-
/** Parsed body; undefined until parsed. */
9+
/** Parsed body; undefined until parsed */
1010
private bodyData: unknown = undefined
11-
/** Format body was parsed as. */
11+
/** Format body was parsed as */
1212
private bodyParsedAs: 'arraybuffer' | 'blob' | 'form' | 'json' | 'text' | null = null
13-
/** Parsed cookie name-to-value map; lazy. */
13+
/** Parsed cookie name-to-value map; lazy */
1414
private cookieMap: Record<string, string> | undefined = undefined
15-
/** Custom error handler when set. */
15+
/** Custom error handler when set */
1616
private errorHandler: Types.ErrorHandler | undefined = undefined
17-
/** Lowercased request header map; lazy. */
17+
/** Lowercased request header map; lazy */
1818
private headerMap: Record<string, string> | undefined = undefined
19-
/** Parsed query string params; lazy. */
19+
/** Parsed query string params; lazy */
2020
private queryParams: Record<string, string> | undefined = undefined
21-
/** Incoming fetch Request. */
21+
/** Incoming fetch Request */
2222
private req: Request
23-
/** Arbitrary state for middleware/handlers. */
23+
/** Arbitrary state for middleware/handlers */
2424
private requestState: Record<string, unknown> = {}
25-
/** Response headers to send. */
25+
/** Response headers to send */
2626
private responseHeaders: Record<string, string> = {}
27-
/** Matched route path params. */
27+
/** Matched route path params */
2828
private routeParams: Record<string, string>
29-
/** Parsed request URL. */
29+
/** Parsed request URL */
3030
private urlObj: URL
3131

3232
/**
@@ -285,7 +285,9 @@ export class Context {
285285
async render(templatePath: string, data: Types.TemplateData = {}): Promise<Response> {
286286
const view = this.state['view'] as Types.ViewEngine | undefined
287287
if (view === undefined) {
288-
throw new Error('View engine not configured, set viewsDir in RouterOptions')
288+
throw new Deno.errors.NotSupported(
289+
'View engine not configured, set viewsDir in RouterOptions'
290+
)
289291
}
290292
const html = await view.render(templatePath, data)
291293
return this.send.html(html)
@@ -344,7 +346,9 @@ export class Context {
344346
streamRender(templatePath: string, data: Types.TemplateData = {}): Response {
345347
const view = this.state['view'] as Types.ViewEngine
346348
if (view === undefined) {
347-
throw new Error('View engine not configured, set viewsDir in RouterOptions')
349+
throw new Deno.errors.NotSupported(
350+
'View engine not configured, set viewsDir in RouterOptions'
351+
)
348352
}
349353
const stream = view.streamRender(templatePath, data)
350354
return this.send.stream(stream, undefined, 'text/html; charset=utf-8')
@@ -365,14 +369,14 @@ export class Context {
365369
return this.bodyData as string
366370
}
367371

368-
/** Throws if body was already consumed. */
372+
/** Throws if body was already consumed */
369373
private ensureBodyNotConsumed(): void {
370374
if (this.bodyParsedAs !== null) {
371-
throw new Error('Request body already consumed')
375+
throw new Deno.errors.BadResource('Request body already consumed')
372376
}
373377
}
374378

375-
/** Parse Cookie header into key-value map. */
379+
/** Parse Cookie header into key-value map */
376380
private parseCookies(): void {
377381
const result: Record<string, string> = {}
378382
const cookieHeader = this.req.headers.get('cookie')
@@ -387,14 +391,14 @@ export class Context {
387391
this.cookieMap = result
388392
}
389393

390-
/** Parse request headers into lowercased map. */
394+
/** Parse request headers into lowercased map */
391395
private parseHeaders(): void {
392396
this.headerMap = Object.fromEntries(
393397
Array.from(this.req.headers.entries(), ([key, value]) => [key.toLowerCase(), value])
394398
)
395399
}
396400

397-
/** Parse URL search params into map. */
401+
/** Parse URL search params into map */
398402
private parseQuery(): void {
399403
this.queryParams = Object.fromEntries(this.urlObj.searchParams.entries())
400404
}

src/middleware/BasicAuth.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ export class BasicAuth {
1111
* @description Validates Authorization header against user list.
1212
* @param options - List of username/password pairs
1313
* @returns Middleware that returns 401 when invalid
14-
* @throws {Error} When users array is empty
14+
* @throws {Deno.errors.InvalidData} When users array is empty
1515
*/
1616
static create(options: Types.BasicAuthOptions): Types.Middleware {
1717
const { users } = options
1818
if (!users || users.length === 0) {
19-
throw new Error('BasicAuth requires at least one user in the users array')
19+
throw new Deno.errors.InvalidData('BasicAuth requires at least one user in the users array')
2020
}
2121
return async (
2222
ctx: Core.Context,

src/middleware/Session.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@ export class Session {
2020
* @description Populates ctx.state with session and helpers; requires cookieSecret.
2121
* @param options - Session options with required cookieSecret
2222
* @returns Middleware that populates ctx.state.session
23-
* @throws {Error} When cookieSecret is missing or empty
23+
* @throws {Deno.errors.InvalidData} When cookieSecret is missing or empty
2424
*/
2525
static create(options: Types.SessionOptions): Types.Middleware {
2626
const { cookieSecret, ...rest } = options
2727
if (!cookieSecret || cookieSecret.length === 0) {
28-
throw new Error('Session cookieSecret must be a non-empty string')
28+
throw new Deno.errors.InvalidData('Session cookieSecret must be a non-empty string')
2929
}
3030
const sessionOptions = { ...Session.defaultOptions, ...rest } as Types.SessionCookieOpts
3131
return async (

src/rendering/Engine.ts

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import * as Rendering from '@rendering/index.ts'
33
import * as EngineParts from '@rendering/engine/index.ts'
44

55
/**
6-
* Template rendering engine
7-
* @description Compiles and renders DVE templates with cache
6+
* Template rendering engine.
7+
* @description Compiles and renders DVE templates with cache.
88
*/
99
export class Engine implements Types.ViewEngine {
1010
/** Default views directory */
@@ -17,8 +17,8 @@ export class Engine implements Types.ViewEngine {
1717
private discoveredPaths: Set<string> | null = null
1818

1919
/**
20-
* Create new engine instance
21-
* @description Stores default viewsDir from options
20+
* Create new engine instance.
21+
* @description Stores default viewsDir from options.
2222
* @param options - Engine configuration options
2323
*/
2424
constructor(options: Types.EngineOptions) {
@@ -40,18 +40,17 @@ export class Engine implements Types.ViewEngine {
4040
this.compileCache.delete(absPath)
4141
}
4242

43-
/** Reset discovered template paths */
44-
refreshPaths(): void {
43+
/** Reset discovered template paths */ refreshPaths(): void {
4544
this.discoveredPaths = null
4645
}
4746

4847
/**
49-
* Render template with data
50-
* @description Loads template and produces final HTML
48+
* Render template with data.
49+
* @description Loads template and produces final HTML.
5150
* @param templatePath - Relative template path
5251
* @param data - Template scope data
5352
* @returns Rendered HTML string
54-
* @throws {Error} When template path not discovered
53+
* @throws {Deno.errors.NotFound} When template path not discovered
5554
*/
5655
async render(templatePath: string, data: Types.TemplateData = {}): Promise<string> {
5756
const compiled = await this.resolveTemplate(templatePath)
@@ -64,7 +63,7 @@ export class Engine implements Types.ViewEngine {
6463
* @param templatePath - Relative template path
6564
* @param data - Template scope data
6665
* @returns ReadableStream with HTML content
67-
* @throws {Error} When template not found
66+
* @throws {Deno.errors.NotFound} When template not found
6867
*/
6968
streamRender(templatePath: string, data: Types.TemplateData = {}): ReadableStream {
7069
const { readable, writable } = new TransformStream()
@@ -75,8 +74,8 @@ export class Engine implements Types.ViewEngine {
7574
}
7675

7776
/**
78-
* Compile template and cache
79-
* @description Parses template text into AST nodes
77+
* Compile template and cache.
78+
* @description Parses template text into AST nodes.
8079
* @param absTemplatePath - Absolute path to template
8180
* @returns Compile result with AST
8281
*/
@@ -93,8 +92,8 @@ export class Engine implements Types.ViewEngine {
9392
}
9493

9594
/**
96-
* Load template text with cache
97-
* @description Loads file contents from disk once
95+
* Load template text with cache.
96+
* @description Loads file contents from disk once.
9897
* @param absPath - Absolute template file path
9998
* @returns Template file contents
10099
*/
@@ -109,8 +108,8 @@ export class Engine implements Types.ViewEngine {
109108
}
110109

111110
/**
112-
* Render node to chunk
113-
* @description Renders individual node to HTML chunk
111+
* Render node to chunk.
112+
* @description Renders individual node to HTML chunk.
114113
* @param node - AST node to render
115114
* @param data - Template scope data
116115
* @param viewsDir - Root directory for includes
@@ -164,8 +163,8 @@ export class Engine implements Types.ViewEngine {
164163
}
165164

166165
/**
167-
* Render AST nodes to HTML
168-
* @description Evaluates variables, includes, and blocks
166+
* Render AST nodes to HTML.
167+
* @description Evaluates variables, includes, and blocks.
169168
* @param ast - Parsed template AST nodes
170169
* @param data - Current scope data
171170
* @param viewsDir - Root directory for includes
@@ -187,8 +186,8 @@ export class Engine implements Types.ViewEngine {
187186
}
188187

189188
/**
190-
* Render template nodes to stream
191-
* @description Streams HTML output progressively
189+
* Render template nodes to stream.
190+
* @description Streams HTML output progressively.
192191
* @param templatePath - Relative template path
193192
* @param data - Template scope data
194193
* @param writable - Writable stream for output
@@ -218,7 +217,7 @@ export class Engine implements Types.ViewEngine {
218217
* @description Discovers paths, normalizes, validates, and compiles.
219218
* @param templatePath - Relative template path
220219
* @returns Compiled template with AST
221-
* @throws {Error} When template not found
220+
* @throws {Deno.errors.NotFound} When template not found
222221
*/
223222
private async resolveTemplate(templatePath: string): Promise<Types.CompileResult> {
224223
if (this.discoveredPaths === null) {
@@ -229,7 +228,7 @@ export class Engine implements Types.ViewEngine {
229228
? normalizedPath
230229
: `${normalizedPath}.dve`
231230
if (!this.discoveredPaths.has(pathWithExt)) {
232-
throw new Error(`Template "${templatePath}" not found in views directory`)
231+
throw new Deno.errors.NotFound(`Template "${templatePath}" not found in views directory`)
233232
}
234233
const absPath = EngineParts.Utils.join(this.defaultViewsDir, pathWithExt)
235234
return await this.compileTemplate(absPath)

src/routing/Scanner.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ export class Scanner {
117117
* @param module - Loaded route module object
118118
* @param routePath - Path for error messages
119119
* @param methods - Valid HTTP method names
120-
* @throws {Error} When no method or non-function export
120+
* @throws {Deno.errors.InvalidData} When no method or non-function export
121121
*/
122122
static validateModule(
123123
module: Record<string, unknown>,
@@ -126,14 +126,14 @@ export class Scanner {
126126
): void {
127127
const exportedMethods = Object.keys(module).filter((key) => methods.includes(key))
128128
if (exportedMethods.length === 0) {
129-
throw new Error(
129+
throw new Deno.errors.InvalidData(
130130
`Route "${routePath}" must export at least one HTTP method (${methods.join(', ')})`
131131
)
132132
}
133133
for (const [key, value] of Object.entries(module)) {
134134
if (methods.includes(key)) {
135135
if (typeof value !== 'function') {
136-
throw new Error(
136+
throw new TypeError(
137137
`Route "${routePath}" export "${key}" must be a function, got ${typeof value}`
138138
)
139139
}

0 commit comments

Comments
 (0)