From 33aafaf34fcd61a55745f92a8d4d564be3d570f8 Mon Sep 17 00:00:00 2001 From: Mason Daugherty Date: Mon, 13 Oct 2025 15:24:50 -0400 Subject: [PATCH] fix ref: make path case insensitive --- reference/middleware.test.ts | 94 ++++++++++++++++++++++++++++++++++++ reference/middleware.ts | 33 +++++++++++++ reference/package.json | 9 +++- reference/tsconfig.json | 18 +++++++ 4 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 reference/middleware.test.ts create mode 100644 reference/middleware.ts create mode 100644 reference/tsconfig.json diff --git a/reference/middleware.test.ts b/reference/middleware.test.ts new file mode 100644 index 000000000..ff09d0720 --- /dev/null +++ b/reference/middleware.test.ts @@ -0,0 +1,94 @@ +/** + * Test file for middleware redirect logic. + * This can be run locally to verify the case-insensitive redirect behavior. + */ + +/** + * Simple test cases to verify the middleware logic. + */ +const testCases = [ + // Test uppercase in path + { + input: '/python/LangChain/index.html', + expected: '/python/langchain/index.html', + shouldRedirect: true, + }, + // Test mixed case + { + input: '/python/LangGraph/Graphs', + expected: '/python/langgraph/graphs', + shouldRedirect: true, + }, + // Test all lowercase (no redirect needed) + { + input: '/python/langchain/index.html', + expected: '/python/langchain/index.html', + shouldRedirect: false, + }, + // Test JavaScript paths + { + input: '/javascript/LangChain/Agents', + expected: '/javascript/langchain/agents', + shouldRedirect: true, + }, + // Test complex path with query params + { + input: '/python/Integrations/LangChain_OpenAI', + expected: '/python/integrations/langchain_openai', + shouldRedirect: true, + }, +]; + +/** + * Simulate the middleware logic for testing. + */ +function simulateMiddleware(pathname: string): { shouldRedirect: boolean; newPath: string } { + const shouldRedirect = pathname !== pathname.toLowerCase(); + return { + shouldRedirect, + newPath: pathname.toLowerCase(), + }; +} + +/** + * Run tests and log results. + */ +function runTests(): void { + console.log('Running middleware tests...\n'); + + let passed = 0; + let failed = 0; + + for (const testCase of testCases) { + const result = simulateMiddleware(testCase.input); + const testPassed = + result.shouldRedirect === testCase.shouldRedirect && + result.newPath === testCase.expected; + + if (testPassed) { + passed++; + console.log(`✓ PASS: ${testCase.input}`); + } else { + failed++; + console.log(`✗ FAIL: ${testCase.input}`); + console.log(` Expected redirect: ${testCase.shouldRedirect}, got: ${result.shouldRedirect}`); + console.log(` Expected path: ${testCase.expected}, got: ${result.newPath}`); + } + } + + console.log(`\nResults: ${passed} passed, ${failed} failed`); + + if (failed === 0) { + console.log('All tests passed! ✓'); + } else { + console.error('Some tests failed! ✗'); + process.exit(1); + } +} + +// Run tests if this file is executed directly +if (require.main === module) { + runTests(); +} + +export { testCases, simulateMiddleware, runTests }; diff --git a/reference/middleware.ts b/reference/middleware.ts new file mode 100644 index 000000000..04665062c --- /dev/null +++ b/reference/middleware.ts @@ -0,0 +1,33 @@ +import { NextRequest, NextResponse } from 'next/server'; + +/** + * Middleware to handle case-insensitive redirects for API reference paths. + * Redirects any path containing uppercase letters to its lowercase equivalent. + */ +export function middleware(request: NextRequest): NextResponse { + const { pathname, search } = request.nextUrl; + + if (pathname !== pathname.toLowerCase()) { + const url = request.nextUrl.clone(); + url.pathname = pathname.toLowerCase(); + + // Preserve query parameters + url.search = search; + + // 301 permanent redirect to the lowercase version + return NextResponse.redirect(url, { status: 301 }); + } + + return NextResponse.next(); +} + +/** + * Configure which paths the middleware should run on. + * This applies to all Python API reference paths. + */ +export const config = { + matcher: [ + '/python/:path*', + '/javascript/:path*', + ], +}; \ No newline at end of file diff --git a/reference/package.json b/reference/package.json index 35414a107..6069e9344 100644 --- a/reference/package.json +++ b/reference/package.json @@ -6,12 +6,19 @@ "build": "concurrently \"pnpm build:js\" \"pnpm build:py\"", "build:js": "pnpm -C ./javascript build", "build:py": "make -C ./python build", - "preview": "vercel dev" + "preview": "vercel dev", + "test:middleware": "tsx middleware.test.ts" }, "packageManager": "pnpm@10.14.0", "dependencies": { "concurrently": "^9.2.1", "serve": "^14.2.5", "vercel": "^48.1.4" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "next": "^15.1.0", + "tsx": "^4.19.0", + "typescript": "^5.7.0" } } diff --git a/reference/tsconfig.json b/reference/tsconfig.json new file mode 100644 index 000000000..033afeddb --- /dev/null +++ b/reference/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022"], + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "types": ["node"] + }, + "include": ["middleware.ts", "middleware.test.ts"], + "exclude": ["node_modules", "dist", "python", "javascript"] +}