Skip to content

feat: add middleware for Accept: text/markdown content negotiation#37

Merged
jamestelfer merged 3 commits into
mainfrom
feat/markdown-content-negotiation
Mar 28, 2026
Merged

feat: add middleware for Accept: text/markdown content negotiation#37
jamestelfer merged 3 commits into
mainfrom
feat/markdown-content-negotiation

Conversation

@jamestelfer
Copy link
Copy Markdown
Contributor

@jamestelfer jamestelfer commented Mar 28, 2026

Purpose

Completes HTTP content negotiation support for markdown content, building on the flattened markdown output from #36. Clients can now request documentation as raw markdown using the standard Accept: text/markdown header rather than needing to know the site's .md URL convention. This supports LLM tooling and programmatic consumers that prefer markdown over HTML.

Context

Summary by CodeRabbit

  • New Features

    • Requests with an Accept header including text/markdown (GET/HEAD) are redirected (302) to the corresponding .md URL; root (/) maps to /index.md and query strings are preserved.
    • Requests are passed through unchanged when method isn't GET/HEAD, when the path already references a file, or when markdown isn't requested.
  • Tests

    • Added tests validating markdown content-negotiation, redirect targets, query preservation, and pass-through cases.

302-redirects GET/HEAD requests with Accept: text/markdown to {path}.md.
Passes through for other methods, paths with file extensions, or missing
text/markdown Accept header. Preserves query strings. Root path redirects
to /index.md.
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 28, 2026

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 28, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d88be81c-c5e3-4ca1-9d6e-e8f2a95403ae

📥 Commits

Reviewing files that changed from the base of the PR and between 603393b and 9f596ec.

📒 Files selected for processing (2)
  • functions/_middleware.test.ts
  • functions/_middleware.ts
✅ Files skipped from review due to trivial changes (1)
  • functions/_middleware.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • functions/_middleware.ts

Walkthrough

Adds a request middleware and tests: for GET/HEAD requests whose Accept header includes text/markdown, the middleware redirects to a corresponding .md path (stripping trailing slashes and mapping empty path to /index) and preserves the original query string; other requests are passed through.

Changes

Cohort / File(s) Summary
Middleware Implementation
functions/_middleware.ts
New onRequest(context) and Context interface. Handles GET/HEAD markdown content negotiation: checks Accept for text/markdown, strips trailing slash, treats empty pathname as /index, skips paths with file extensions, and returns 302 to ${path}.md including original query string. Non-matching requests call context.next().
Middleware Tests
functions/_middleware.test.ts
New Vitest suite covering: redirects for GET/HEAD with Accept: text/markdown (including when mixed with other media types), mapping //index.md, stripping trailing slash before computing target, preserving query strings, and pass-through behavior for non-GET/HEAD, absent Accept, Accept not including text/markdown, or URLs already containing an extension (including with trailing slash).

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Middleware as onRequest (Middleware)
    participant Handler as context.next (Handler)

    Client->>Middleware: HTTP Request
    activate Middleware

    alt Method not GET/HEAD OR Accept missing/doesn't include text/markdown OR path has extension
        Middleware->>Handler: context.next()
        activate Handler
        Handler-->>Middleware: Response
        deactivate Handler
        Middleware-->>Client: Response (passthrough)
    else GET/HEAD and Accept includes text/markdown and path has no extension
        Middleware-->>Client: 302 Redirect to <rgba(0,128,0,0.5)>path.md</rgba> (preserve query)
    end

    deactivate Middleware
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly Related PRs

Poem

I’m a rabbit who hops through slashes and q’s,
I scent Accept headers and follow the clues,
From / to /index.md I gladly steer,
Preserving queries — a breadcrumb cheer! 🐇✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely describes the main change: adding middleware for content negotiation based on the Accept header for Markdown.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 feat/markdown-content-negotiation

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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
functions/_middleware.test.ts (1)

16-116: Good test coverage of the core requirements.

The test suite thoroughly covers:

  • GET/HEAD redirect behavior
  • Root path normalization to /index.md
  • Query string preservation
  • Pass-through for non-matching methods, Accept headers, and file extensions

Consider adding tests for edge cases:

  • Trailing slash paths (e.g., /about/) — relates to the malformed redirect issue
  • Paths with dots in directory segments (e.g., /v1.0/docs) to confirm only the final segment is checked
📝 Suggested additional test cases
it("handles trailing slash paths", async () => {
  const context = createContext("https://example.com/about/", {
    headers: { Accept: "text/markdown" },
  })

  const response = await onRequest(context)

  expect(response.status).toBe(302)
  expect(response.headers.get("Location")).toBe("/about.md")
})

it("redirects paths with dots in directory names", async () => {
  const context = createContext("https://example.com/v1.0/docs", {
    headers: { Accept: "text/markdown" },
  })

  const response = await onRequest(context)

  expect(response.status).toBe(302)
  expect(response.headers.get("Location")).toBe("/v1.0/docs.md")
})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@functions/_middleware.test.ts` around lines 16 - 116, Add tests for
trailing-slash and dotted-directory edge cases to ensure onRequest redirects
produce well-formed .md locations: add a test invoking createContext with
"https://example.com/about/" and Accept: "text/markdown" and assert onRequest
returns 302 with Location "/about.md" (not "/about/.md"), and another test with
"https://example.com/v1.0/docs" and Accept: "text/markdown" asserting 302 and
Location "/v1.0/docs.md"; place these alongside the existing tests so they
exercise the same onRequest middleware and confirm query-preservation and
behavior for final path segment only.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@functions/_middleware.ts`:
- Around line 13-17: Normalize the request pathname by stripping any trailing
slashes before inspecting or using it: compute a normalizedPath (derived from
new URL(request.url).pathname with trailing slashes removed, defaulting to "/"
if empty), then derive lastSegment from normalizedPath.split("/").pop() and use
normalizedPath for any redirect construction instead of the raw url.pathname;
update the logic around the lastSegment variable and any redirect code in
functions/_middleware.ts so trailing-slash paths like "/about/" produce
"/about.md" rather than "/about/.md".

---

Nitpick comments:
In `@functions/_middleware.test.ts`:
- Around line 16-116: Add tests for trailing-slash and dotted-directory edge
cases to ensure onRequest redirects produce well-formed .md locations: add a
test invoking createContext with "https://example.com/about/" and Accept:
"text/markdown" and assert onRequest returns 302 with Location "/about.md" (not
"/about/.md"), and another test with "https://example.com/v1.0/docs" and Accept:
"text/markdown" asserting 302 and Location "/v1.0/docs.md"; place these
alongside the existing tests so they exercise the same onRequest middleware and
confirm query-preservation and behavior for final path segment only.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ba862ed0-8069-4f62-8767-2d98ea6f74ce

📥 Commits

Reviewing files that changed from the base of the PR and between df1e3f4 and 603393b.

📒 Files selected for processing (2)
  • functions/_middleware.test.ts
  • functions/_middleware.ts

Comment thread functions/_middleware.ts Outdated
@jamestelfer jamestelfer changed the title Add middleware for Accept: text/markdown content negotiation feat: add middleware for Accept: text/markdown content negotiation Mar 28, 2026
Paths like /about/ produced /about/.md instead of /about.md. Strip
trailing slashes before inspecting or constructing the redirect URL.
Also reorders checks to parse the URL only after confirming the Accept
header matches.
@jamestelfer jamestelfer merged commit 61bfae8 into main Mar 28, 2026
3 checks passed
@jamestelfer jamestelfer deleted the feat/markdown-content-negotiation branch March 28, 2026 10:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant