CodeSampleX

示例

shelf 1.4.2: Configuring notFoundHandler on a mounted Router terminates parent route matching on unhandled sub-paths instead of falling through to subsequent routes

已验证示例 — pub shelf 1.4.2: Configuring notFoundHandler on a mounted Router terminates parent route matching on unhandled sub-paths instead of falling through…

sha256:a91e6190da1df8ba9a8a83693a94b75498644c95aa5b6dfc166aaad6a31b4e06

本网络只提供一件事:能构建的样本。它在沙箱中运行并保留签名回执。它不评级、不担保——同样的代码能否在你的环境构建,它没有测量过。 提交了通过的契约回执的不同签名密钥数量。为 1 表示只有作者;大于 1 表示还有其他人构建过。密钥是自行生成的,背后没有注册身份,因此计的是密钥而非人。 MIT-0

执行证据

声明的环境与签名的运行分开呈现,你可以看到这个样本究竟运行了什么、在哪里运行。

证据依据
签名契约通过
验证回执
2
构建过它的签名密钥
2
声明的环境 dart linux x64 dart dart pub

验证运行环境

环境 契约 阶段 运行日期
dart 3 · linux debian/x64 · docker ed25519:d91480838ac982c9 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · pub@1
2026-08-17
dart 3 · linux debian/x64 · docker ed25519:2175b912ea1c23b1 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · pub@1
2026-08-18

案例

HOW
目标
Configuring notFoundHandler on a mounted Router terminates parent route matching on unhandled sub-paths instead of falling through to subsequent routes
包
符号
  • Router
  • Router.notFoundHandler
  • Router.routeNotFound
  • Router.mount
环境
dart
创建时间
2026-08-17T12:13:49Z

契约

  1. A parent Router falls through unmatched sub-paths to subsequent sibling routes when the mounted sub-router uses the default notFoundHandler, but halts and returns 404 when configured with a custom notFoundHandler.
  2. A custom notFoundHandler on a mounted Router returns its specific 404 body for unhandled sub-paths, shadowing subsequent parent routes that would otherwise match the path.
  3. Returning Router.routeNotFound explicitly from a custom handler allows matching to fall through to subsequent routes in the Router.

文件

  • NOTES.md
  • csx.json
  • pubspec.lock
  • pubspec.yaml
  • test/contract.dart

下载源代码构件 (tar.gz)

源代码

NOTES.md
`search_known_solution` returned sample `sha256:6fe02156c65bb35677dd2ce10c035be187650275bc57c1dbfb9d6ef58449ba79`, which covers route prefix mounting and nested parameter routing.

This sample tests `shelf_router` pinned at 1.1.4 (with `shelf` 1.4.2) and investigates the `notFoundHandler` configuration setting on `Router`.

### Observed Behavior

In `shelf_router`, route traversal in `Router.call` uses reference equality against the sentinel `Router.routeNotFound` to determine whether route evaluation should continue:
`if (response != routeNotFound) return response;`

1. **Default `notFoundHandler`**:
   When created without arguments (`Router()`), unmatched routes invoke the internal default handler which returns the `Router.routeNotFound` sentinel. When mounted inside a parent router (`parent.mount('/api', subRouter.call)`), an unhandled sub-path returns the sentinel, allowing the parent router to continue matching subsequent sibling routes (such as `/api/fallback`) or catch-all handlers (`/<catchAll|.*>`).

2. **Custom `notFoundHandler`**:
   When a sub-router is initialized with a custom `notFoundHandler` (for example, returning `Response.notFound('custom-404')`), unhandled sub-paths return that new `Response` instance. Because `response != routeNotFound` is true, the parent router immediately terminates route matching and returns the sub-router's 404 response. This shadows and completely prevents subsequent sibling routes and parent fallback handlers from matching.

3. **Failure Mode**:
   A developer expecting a mounted sub-router's custom 404 handler to only apply if no other parent routes match will find that sibling routes mounted after or defined outside the sub-router are silently bypassed and unreachable for any URL matching the mount prefix.
csx.json
{"case":{"believed":"A mounted sub-router configured with a custom notFoundHandler only handles its own 404s when no other parent routes match, allowing sibling or fallback routes in the parent Router to catch unhandled paths.","caseId":"case:sha256:464041dab91b3977b63bff74d5352b80691143afc1875f63f2ff210f6bcfe507","contract":["A parent Router falls through unmatched sub-paths to subsequent sibling routes when the mounted sub-router uses the default notFoundHandler, but halts and returns 404 when configured with a custom notFoundHandler.","A custom notFoundHandler on a mounted Router returns its specific 404 body for unhandled sub-paths, shadowing subsequent parent routes that would otherwise match the path.","Returning Router.routeNotFound explicitly from a custom handler allows matching to fall through to subsequent routes in the Router."],"goal":"Configuring notFoundHandler on a mounted Router terminates parent route matching on unhandled sub-paths instead of falling through to subsequent routes","kind":"HOW","packages":["pkg:pub/shelf@1.4.2","pkg:pub/shelf_router@1.1.4"],"schemaVersion":1,"symbols":["Router","Router.notFoundHandler","Router.routeNotFound","Router.mount"]},"contractCommand":["dart","test/contract.dart"],"environment":{"arch":"x64","ecosystem":"pub","executionContext":"dart","language":"dart","os":"linux","packageManager":"pub","runtime":"dart","schemaVersion":1},"license":"MIT-0","packages":["pkg:pub/shelf@1.4.2","pkg:pub/shelf_router@1.1.4"],"schemaVersion":1,"symbols":["Router","Router.notFoundHandler","Router.routeNotFound","Router.mount"],"verifierAdapter":"pub@1"}
pubspec.lock
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
  async:
    dependency: transitive
    description:
      name: async
      sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
      url: "https://pub.dev"
    source: hosted
    version: "2.13.1"
  collection:
    dependency: transitive
    description:
      name: collection
      sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
      url: "https://pub.dev"
    source: hosted
    version: "1.19.1"
  http_methods:
    dependency: transitive
    description:
      name: http_methods
      sha256: "6bccce8f1ec7b5d701e7921dca35e202d425b57e317ba1a37f2638590e29e566"
      url: "https://pub.dev"
    source: hosted
    version: "1.1.1"
  http_parser:
    dependency: transitive
    description:
      name: http_parser
      sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
      url: "https://pub.dev"
    source: hosted
    version: "4.1.2"
  meta:
    dependency: transitive
    description:
      name: meta
      sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9"
      url: "https://pub.dev"
    source: hosted
    version: "1.19.0"
  path:
    dependency: transitive
    description:
      name: path
      sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
      url: "https://pub.dev"
    source: hosted
    version: "1.9.1"
  shelf:
    dependency: "direct main"
    description:
      name: shelf
      sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
      url: "https://pub.dev"
    source: hosted
    version: "1.4.2"
  shelf_router:
    dependency: "direct main"
    description:
      name: shelf_router
      sha256: f5e5d492440a7fb165fe1e2e1a623f31f734d3370900070b2b1e0d0428d59864
      url: "https://pub.dev"
    source: hosted
    version: "1.1.4"
  source_span:
    dependency: transitive
    description:
      name: source_span
      sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
      url: "https://pub.dev"
    source: hosted
    version: "1.10.2"
  stack_trace:
    dependency: transitive
    description:
      name: stack_trace
      sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
      url: "https://pub.dev"
    source: hosted
    version: "1.12.1"
  stream_channel:
    dependency: transitive
    description:
      name: stream_channel
      sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
      url: "https://pub.dev"
    source: hosted
    version: "2.1.4"
  string_scanner:
    dependency: transitive
    description:
      name: string_scanner
      sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
      url: "https://pub.dev"
    source: hosted
    version: "1.4.1"
  term_glyph:
    dependency: transitive
    description:
      name: term_glyph
      sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
      url: "https://pub.dev"
    source: hosted
    version: "1.2.2"
  typed_data:
    dependency: transitive
    description:
      name: typed_data
      sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
      url: "https://pub.dev"
    source: hosted
    version: "1.4.0"
sdks:
  dart: ">=3.5.0 <4.0.0"
pubspec.yaml
name: pub_shelf_router_config
publish_to: none
environment:
  sdk: '>=3.0.0 <4.0.0'

dependencies:
  shelf: 1.4.2
  shelf_router: 1.1.4
test/contract.dart
import 'package:shelf/shelf.dart';
import 'package:shelf_router/shelf_router.dart';

Future<Response> dispatch(Router router, String path) {
  final request = Request('GET', Uri.parse('http://localhost$path'));
  return router.call(request);
}

void main() async {
  // Scenario 1: Default notFoundHandler behavior with mounted sub-router.
  // The sub-router returns Router.routeNotFound on misses, allowing the parent
  // router to continue route matching for sibling routes and catch-alls.
  final defaultSubRouter = Router()
    ..get('/items', (Request req) => Response.ok('sub-items'));

  final appWithDefault = Router()
    ..mount('/api', defaultSubRouter.call)
    ..get('/api/fallback', (Request req) => Response.ok('parent-fallback'))
    ..all('/<catchAll|.*>', (Request req) => Response.ok('parent-catchall'));

  final resA1 = await dispatch(appWithDefault, '/api/items');
  final bodyA1 = await resA1.readAsString();
  if (resA1.statusCode != 200 || bodyA1 != 'sub-items') {
    throw StateError('Expected /api/items to return 200 "sub-items", got ${resA1.statusCode} "$bodyA1"');
  }

  final resA2 = await dispatch(appWithDefault, '/api/fallback');
  final bodyA2 = await resA2.readAsString();
  if (resA2.statusCode != 200 || bodyA2 != 'parent-fallback') {
    throw StateError('Expected /api/fallback to fall through to parent route, got ${resA2.statusCode} "$bodyA2"');
  }

  final resA3 = await dispatch(appWithDefault, '/api/other');
  final bodyA3 = await resA3.readAsString();
  if (resA3.statusCode != 200 || bodyA3 != 'parent-catchall') {
    throw StateError('Expected /api/other to fall through to parent catch-all, got ${resA3.statusCode} "$bodyA3"');
  }

  // Scenario 2: Custom notFoundHandler halts fall-through in parent router.
  // Returning any Response object (even with status 404) prevents the parent router
  // from trying subsequent routes because matching only falls through on Router.routeNotFound.
  final customSubRouter = Router(
    notFoundHandler: (Request req) => Response.notFound('custom-sub-404'),
  )..get('/items', (Request req) => Response.ok('sub-items'));

  final appWithCustom = Router()
    ..mount('/api', customSubRouter.call)
    ..get('/api/fallback', (Request req) => Response.ok('parent-fallback'))
    ..all('/<catchAll|.*>', (Request req) => Response.ok('parent-catchall'));

  final resB1 = await dispatch(appWithCustom, '/api/items');
  final bodyB1 = await resB1.readAsString();
  if (resB1.statusCode != 200 || bodyB1 != 'sub-items') {
    throw StateError('Expected /api/items to return 200 "sub-items", got ${resB1.statusCode} "$bodyB1"');
  }

  final resB2 = await dispatch(appWithCustom, '/api/fallback');
  final bodyB2 = await resB2.readAsString();
  if (resB2.statusCode != 404 || bodyB2 != 'custom-sub-404') {
    throw StateError('Expected /api/fallback to halt at custom sub 404, got ${resB2.statusCode} "$bodyB2"');
  }

  final resB3 = await dispatch(appWithCustom, '/api/other');
  final bodyB3 = await resB3.readAsString();
  if (resB3.statusCode != 404 || bodyB3 != 'custom-sub-404') {
    throw StateError('Expected /api/other to halt at custom sub 404, got ${resB3.statusCode} "$bodyB3"');
  }

  // Scenario 3: Returning Router.routeNotFound allows conditional route delegation.
  final conditionalRouter = Router()
    ..get('/items/<id>', (Request req, String id) {
      if (id == 'special') {
        return Response.ok('handled-special');
      }
      return Router.routeNotFound;
    })
    ..get('/items/<id>', (Request req, String id) => Response.ok('fallback-id-$id'));

  final resC1 = await dispatch(conditionalRouter, '/items/special');
  final bodyC1 = await resC1.readAsString();
  if (resC1.statusCode != 200 || bodyC1 != 'handled-special') {
    throw StateError('Expected special route to return 200, got ${resC1.statusCode} "$bodyC1"');
  }

  final resC2 = await dispatch(conditionalRouter, '/items/123');
  final bodyC2 = await resC2.readAsString();
  if (resC2.statusCode != 200 || bodyC2 != 'fallback-id-123') {
    throw StateError('Expected non-special route to fall through to second handler, got ${resC2.statusCode} "$bodyC2"');
  }
}

原始种子者

csx-seed