route_tree 1.0.0-nullsafety route_tree: ^1.0.0-nullsafety copied to clipboard
A generic library for parsing URI paths.
RouteTree #
A generic library for parsing URI paths.
RouteTree is a generic routing library that makes it easy to implement routing logic based on the path segments of a URI. This is done by nesting [Segment] nodes to build a tree not unlike the [Widget] trees you see in Flutter apps.
Quick example #
final router = Segment.root<String>(
create: (context) => "home",
createError: (context) => "Error!",
children: [
Segment.path(
name: "first_path",
create: (context) => "routed to first_path",
),
Segment.path(
name: "second_path",
create: (context) => "routed to second_path",
children: [
Segment.param(
parser: const UintParser("id"),
create: (context) => "routed to /second_path/${context["id"]}",
),
],
),
],
);
assert(router.route(Uri.parse("/second_path/12")), "routed to second_path/12");
Note the template parameter that denotes what data type [Segment.route] will return. This makes route_tree so flexible.
How it works #
The basic building blocks of this library are Segments, SegmentParsers and the ParseContext.
Segments #
Segments are to route_tree
what Widgets are to Flutter. Each [Segment] represents a possible path segment that can be part of a route. Since it's templated it can be used in several ways, like routing in a Flutter app or choosing the correct function to handle incoming HTTP requests in a backend. The abstract class [Segment] contains several factory constructors to make finding the right one easier.
[RootSegment] or [Segment.root]
The root node of any route_tree.
Defines the "/"-route, the root error-handler as well as a list of [SegmentVerifier]s that can be used to assert certain properties of the route_tree.
By default RootSegments contain [findConflictingParamKeys], which returns an error if your route_tree contains duplicate parameter keys like /users/{id}/edit/{id}
.
/// Matches "/"
final router = Segment.root<String>(
create: (context) => "home",
createError: (context) => "root error",
children: [
...
],
);
[PathSegment] or [Segment.path]
The Segment you're probably gonna use most often. It defines a simple literal path segment. Internally children of a [Segment] with a [LiteralParser] (which by default only applies to [PathSegment]s) are contained in a Map<String, PathSegment> for constant time lookup of corresponding path segments.
Segment.root<String>(
...
children: [
/// Matches "/about"
Segment.path(
name: "about",
create: (context) => "about",
),
/// Matches "/settings/*", but not "/settings" itself, since [create] was
/// left null
Segment.path(
name: "settings",
children: [
/// Matches "/settings/privacy"
Segment.path(
name: "privacy",
create: (context) => "privacy",
),
],
),
],
);
[RegExpPathSegment] or [Segment.regExpPath]
Used to define one or (usually) more paths that should be matched by one [Segment] using a regular expression.
Segment.root<String>(
...
children: [
/// Matches any path segment consisting of letters
Segment.regExpPath(
regExp: RegExp(r"[a-zA-Z]+"),
create: (context) => "letter path",
),
],
);
[ParamSegment] or [Segment.param]
Used to define a path parameter. If the segment of a URI is matched, the matched value is added to the [ParseContext], from which it can be queried using [ParseContext.operator[]]
Segment.root<String>(
create: (context) => "home",
createError: (context) => "error!",
children: [
/// Matches "/users/*", since [create] was left null
Segment.path(
name: "users",
children: [
/// Matches "/users/{id}", where {id} is any non-negative integer as defined
/// by [UintParser].
Segment.param(
parser: const UintParser("id"),
create: (context) => context["id"] as String,
),
],
),
],
);
[RegExpParamSegment] or [Segment.regExpParam]
Consider URLs used by Twitter where a URL like https://twitter.com/{name}
, where name
can be any word and a hypothetical Twitter client obviously couldn't hardcode all possible names. This is where this [Segment] comes into play. It can be used to define a path parameter that matches the provided regular expression and injects it into the [ParseContext], from which it can be queried using [ParseContext.operator[]].
final router = Segment.root<String>(
create: (context) => "home",
createError: (context) => "error!",
children: [
/// Matches any path segment consisting of letters
Segment.regExpParam(
parser: RegExpParamParser.forward(
key: "name",
regExp: RegExp(r"\w+"),
),
create: (context) => context["name"] as String,
),
],
);
assert(router.route(Uri.parse("/flutterdev")) == "flutterdev");
SegmentParsers #
The actual parsing logic for looking up correct segments is delegated to instances of [SegmentParser]. The predefined parsers are:
- [SegmentParser.withFunction], which takes a [Parser] function and can be used to quickly define custom SegmentParses
- [LiteralParser], which is primarily used by [PathSegment] (& [Segment.path]) and matches a path segment literally
- [RegExpParser], which matches a path segment against a regular expression
- [ParamParser], subclass of [SegmentParser] and base class of parsers that inject matches into the [ParseContext]
- [ParamParser.withFunction], which can be used to quickly define custom ParamParsers
- [RegExpPararamParser], similar to [RegExpParser], but also injects a successful match into the [ParseContext]
- [IntParser] & [UintParser], which match integers and non-negative integers respectively. The latter is useful for for
id
path parameters.
ParseContext #
The [ParseContext] holds the initial URI as well as any path parameters in the current route. It is passed to the create
-function when they're called after a path has been matched.