Skip to content

feat: add core types, error definitions, and configuration#11

Merged
mynameistito merged 6 commits into
mainfrom
init/02-core-types-clean
Mar 26, 2026
Merged

feat: add core types, error definitions, and configuration#11
mynameistito merged 6 commits into
mainfrom
init/02-core-types-clean

Conversation

@mynameistito
Copy link
Copy Markdown
Owner

Summary

  • Cherry-picked all PR feat: PR-2 add core types, error definitions, and configuration #2 commits onto a clean branch based on main
  • Adds AppState type for application state management
  • Adds tagged error classes using better-result for railway-oriented error handling
  • Adds app directory paths (~/.discord-search)
  • Adds settings file I/O with Zod validation

Context

PR #2 (init/02-core-types) was accidentally merged with PR #1 commits mixed in, then reverted. This PR brings over only the PR #2-specific commits cleanly onto main.

Test plan

  • bun run typecheck passes
  • bun run check passes linting

Add foundation layer: AppState type, tagged error classes using
better-result, app directory paths, and settings file I/O with Zod
validation.
…andling

- Add SettingsSchema with Zod for runtime validation of settings JSON
- Add chmod(0o600) to secure settings file after writes
- Create OUTPUT_DIR in ensureAppDir() with secure permissions
- Add owner-only (0o700) permissions to APP_DIR and OUTPUT_DIR
- Improve error handling: distinguish ENOENT from real failures
- Delete duplicate AppState type (unused, kept Config as source of truth)
- Handle null/undefined JSON payloads gracefully
- Validate settings input before merge/save operations
- Add proper error cause chaining for debugging

Resolves 8 security and reliability issues from code review.
- Only ignore known unsupported cases (ENOTSUP, EINVAL) for chmod
- Return ExportError when settings file permissions cannot be secured
- Propagate real permission errors in ensureAppDir() instead of swallowing
- Ensures security issues are surfaced instead of being hidden
- Fix inverted error handling logic that swallowed non-standard errors
- Align with loadConfig pattern: only swallow ENOENT, throw all other errors
- Prevent silent data loss when errors lack code property
- Ensure permission/disk errors are properly propagated
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 26, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 90efc2a9-e1f9-4b7e-81a5-e410bdff1b9c

📥 Commits

Reviewing files that changed from the base of the PR and between b34b1e1 and 7223587.

📒 Files selected for processing (6)
  • .gitignore
  • biome.jsonc
  • src/config.ts
  • src/errors.ts
  • src/paths.ts
  • src/types.ts

📝 Walkthrough

Summary by CodeRabbit

Chores

  • Updated .gitignore to exclude additional report file patterns and added .traycer/ directory
  • Disabled bitwise operator linting rule in Biome configuration

New Features

  • Added configuration management system to load and save bot settings with validation
  • Added structured error handling framework with type-safe error classes for different failure scenarios
  • Added application directory initialization and filesystem path constants

Walkthrough

This PR introduces foundational infrastructure modules: src/config.ts implements configuration validation and loading using Zod; src/errors.ts defines tagged error classes for consistent error handling; src/paths.ts manages application filesystem paths and initialization; src/types.ts defines the AppState type. Project configuration files are also updated.

Changes

Cohort / File(s) Summary
Core Infrastructure
src/config.ts, src/errors.ts, src/paths.ts, src/types.ts
New modules establishing configuration validation and loading, error type definitions, filesystem path constants, directory initialization, and application state type. Implements Zod-based settings validation, OAuth2 invite link generation, and graceful error handling with explicit error payloads.
Project Configuration
.gitignore, biome.jsonc
Updated .gitignore to ignore report.*.json pattern and .traycer/ directory. Disabled suspicious.noBitwiseOperators linting rule in Biome configuration.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • discord-search#2: Directly related—modifies the same new modules (src/config.ts, src/errors.ts, src/paths.ts, src/types.ts) with identical exports and functionality.

Suggested labels

source, configuration

Poem

🐰 A rabbit hops through code so clean,
Config loaded, paths have been seen,
Errors tagged with purpose bright,
Types defined, all feeling right!
The foundation's set, let's build with care!

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch init/02-core-types-clean

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@mynameistito mynameistito merged commit c28c60f into main Mar 26, 2026
11 of 12 checks passed
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 6 files

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Mar 26, 2026

Greptile Summary

This PR cherry-picks the PR #2-specific commits cleanly, introducing the foundational infrastructure for discord-search: tagged error classes (src/errors.ts), app directory constants and setup (src/paths.ts), settings file I/O with Zod validation and environment-variable fallbacks (src/config.ts), and the top-level AppState type (src/types.ts).

The implementation is well-structured — railway-oriented error handling via better-result, secure file permissions (0o700 for directories, 0o600 for the settings file), and graceful recovery from corrupt/missing settings. The main observations are:

  • Duplicated chmod error handlingpaths.ts has a private chmodSafe helper that config.ts re-implements inline; exporting it from paths.ts would keep the two in sync.
  • Misleading error label on read failure inside saveSettings — an I/O error from readSettingsFile() will surface as "Failed to save settings" because it is caught by the outer try/catch.
  • Spread may silently delete keys{ ...existing, ...validationResult.data } will overwrite stored keys with undefined if the caller passes fields explicitly as undefined; filtering out undefined before merging would make the behaviour explicit.

Confidence Score: 4/5

Safe to merge — solid infrastructure foundation with only minor style and clarity concerns, no correctness or security issues.

All three new source files are well-structured, errors are properly handled with railway-oriented results, and file permissions are secured. The three flagged items (chmod duplication, misleading error label, undefined-spread behaviour) are all non-blocking P2 style concerns that don't affect correctness in the normal path. Score of 4 rather than 5 because the undefined-spread issue could silently delete user settings under a specific (unlikely but realistic) caller pattern.

src/config.ts — three minor style concerns worth addressing before the next layer of features builds on top of this API.

Important Files Changed

Filename Overview
src/config.ts New file — implements settings file I/O (loadConfig, saveSettings) with Zod validation, merge-on-save semantics, and a Discord invite link generator. A few minor style concerns: readSettingsFile errors are misleadingly labelled, chmod handling is duplicated from paths.ts, and spread with undefined values can silently delete saved keys.
src/errors.ts New file — defines six tagged error classes via better-result. Clean, consistent, no issues found.
src/paths.ts New file — exposes app directory constants and ensureAppDir with secure 0o700 permissions, auto-generated .gitignore, and a private chmodSafe helper. Would benefit from exporting chmodSafe so config.ts can reuse it.
src/types.ts New file — minimal AppState type wrapping Config. No issues.
biome.jsonc Disables noBitwiseOperators lint rule to allow the `
.gitignore Narrows report glob from report*.json to report.*.json and adds .traycer/. The narrower glob is intentional but excludes files like reportXYZ.json — verify this matches the actual report output format.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A([Caller]) -->|loadConfig| B[readSettingsFile]
    B --> C{File exists?}
    C -->|No| D[Return empty SettingsJson]
    C -->|Yes| E[Parse JSON]
    E --> F{Valid JSON?}
    F -->|SyntaxError| D
    F -->|Yes| G[Zod safeParse]
    G -->|Invalid| D
    G -->|Valid| H[Return SettingsJson]
    D --> I[Resolve env vars DISCORD_BOT_TOKEN etc.]
    H --> I
    I --> J{token present?}
    J -->|No| K[Err: ConfigError]
    J -->|Yes| L[Ok: Config]

    A2([Caller]) -->|saveSettings| M[Validate input Zod safeParse]
    M -->|Invalid| N[Err: ExportError]
    M -->|Valid| O[ensureAppDir]
    O --> P[readSettingsFile existing]
    P --> Q[Merge: existing + new values]
    Q --> R[Bun.write SETTINGS_FILE]
    R --> S[chmod 0o600]
    S --> T{chmod error?}
    T -->|ENOTSUP / EINVAL| U[Ok: void - ignore]
    T -->|Other error| V[Err: ExportError]
    T -->|Success| U
Loading

Reviews (1): Last reviewed commit: "fix: .gitignore patterns, type inference..." | Re-trigger Greptile

Comment thread src/config.ts

await ensureAppDir();

const existing = await readSettingsFile();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Read error surfaced as save error

readSettingsFile() is called inside the outer try/catch, so any I/O error it throws (e.g. permission denied) will be caught at line 123 and wrapped as "Failed to save settings: ...". This is technically handled, but the error message will mislead callers — the failure was a read, not a write. Consider explicitly catching and re-wrapping it with a more accurate message:

const existing = await readSettingsFile().catch((cause) => {
  throw new ExportError({
    message: `Failed to read existing settings before saving: ${cause instanceof Error ? cause.message : String(cause)}`,
    cause,
  });
});

Comment thread src/config.ts
Comment on lines +101 to +120
// Secure file permissions (owner-only)
try {
await chmod(SETTINGS_FILE, 0o600);
} catch (err) {
if (
err &&
typeof err === "object" &&
"code" in err &&
(err.code === "ENOTSUP" || err.code === "EINVAL")
) {
// Ignore known unsupported cases (e.g., Windows)
} else {
return new Err(
new ExportError({
message: `Failed to secure settings file permissions: ${err instanceof Error ? err.message : String(err)}`,
cause: err,
})
);
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Duplicated chmod error handling

paths.ts already contains a private chmodSafe helper with the exact same error-suppression logic for ENOTSUP/EINVAL. Since this block duplicates it verbatim, consider exporting chmodSafe from paths.ts and reusing it here:

// in paths.ts — export it
export const chmodSafe = async (path: string, mode: number): Promise<void> => { ... };

Then in config.ts:

import { ensureAppDir, SETTINGS_FILE, chmodSafe } from "@/paths.ts";
// ...
await chmodSafe(SETTINGS_FILE, 0o600);

This keeps the two implementations in sync and simplifies saveSettings.

Comment thread src/config.ts

const existing = await readSettingsFile();

const merged = { ...existing, ...validationResult.data };
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Spread with explicit undefined silently deletes keys

{ ...existing, ...validationResult.data } will overwrite any key in existing with undefined if the caller passes that field explicitly as undefined (e.g. saveSettings({ token: "abc", guildId: undefined })). JSON.stringify will then omit that key, effectively deleting guildId from the saved file — which is likely unintended when the intent was only to update the token.

Consider filtering out undefined values before merging:

const patch = Object.fromEntries(
  Object.entries(validationResult.data).filter(([, v]) => v !== undefined)
);
const merged = { ...existing, ...patch };

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant