Skip to content

Commit fbec960

Browse files
committed
refactor: Allow overlapping routes
We now allow overlapping routes based on specificity ** This is similar to https://go.dev/blog/routing-enhancements)[Go's new routing enhancements]. (And it intuitively makes sense). ** https://github.com/oxidecomputer/dropshot/issues/199[Oxide discussion on route overlapping here]. ** The motivation is because I usually keep things in a single binary and it just looks better for my frontend to be accessed at `https://myapp.com/` and my api to be accessed at `https://myapp.com/api`. Due to the overlapping restriction the frontend previously had to be at something like `https://myapp.com/-/`. Some say this is bad URL design but the tribe has spoken. ** There are two drawbacks to this approach: *** If you make overlapping routes mistakenly, you might be routed to a handler that you did not expect. The only way you find this out is through thorough testing. *** Currently because of how specificity matching works we don't do backmatching. That means that if we have two routes registered: `/foo/bar` and `/{slug}/bar/lol`, A request intended for path `/foo/bar/lol` will fail with a 404.
1 parent f58ce52 commit fbec960

20 files changed

+352
-285
lines changed

README.adoc

+22-11
Original file line numberDiff line numberDiff line change
@@ -4,38 +4,46 @@
44

55
= Dropshot
66

7+
== A fork in the road
8+
79
This is a fork of https://github.com/oxidecomputer/dropshot[Oxide Computer's Dropshot crate].
810

911
Oxide's crate is awesome and probably the best rust crate for managing a REST API with automatic OpenAPI generation to date.
1012

1113
This fork is because I wanted slightly different design decisions than the crew at Oxide went with. The main differences
1214
I added are:
1315

14-
* Allowed routes to overlap, with the more specific route winning over less specific routes(those with path vars). (And
15-
when both routes are specific then we simply choose the one that was registered first.)
16-
** Oxide discussion here: https://github.com/oxidecomputer/dropshot.
17-
** My motivation is because I love keeping everything in a single binary and it just looks better for my frontend
16+
* Allowed routes to overlap, with the more specific route winning over less specific route(those with path vars).
17+
** This is similar to https://go.dev/blog/routing-enhancements)[Go's new routing enhancements]. (And it
18+
intuitively makes sense).
19+
** https://github.com/oxidecomputer/dropshot/issues/199[Oxide discussion on route overlapping here].
20+
** The motivation is because I usually keep things in a single binary and it just looks better for my frontend
1821
to be accessed at `https://myapp.com/` and my api to be accessed at `https://myapp.com/api`. Due to the overlapping
19-
restriction the frontend previously had to be at something like `https://myapp.com/-/`.
20-
** The drawback is without proper testing you can have overlapping routes that don't follow the pattern of access
21-
you thought they would. My solution to this is just good tests.
22-
** I also lowered some of the logging levels. Typically I run my dev terminal at `DEBUG` log level and like it so
23-
that there is more information than I would run in production but not a deluge of it. The logging levels previously
24-
were all mostly at the debug level making actual dev signal hard to get.
25-
22+
restriction the frontend previously had to be at something like `https://myapp.com/-/`. Some say this is bad URL design
23+
but the tribe has spoken.
24+
** There are two drawbacks to this approach:
25+
*** If you make overlapping routes mistakenly, you might be routed to a handler that you did not expect. The only
26+
way you find this out is through thorough testing.
27+
*** Currently because of how specificity matching works we don't do backmatching. That means that if we have two routes
28+
registered: `/foo/bar` and `/{slug}/bar/lol`, A request intended for path `/foo/bar/lol` will fail with a 404.
2629
* Moved the slog logger to use tracing instead.
2730
** All my crates use tracing and I've been having a really good time with the tracing ethos and implementation.
2831
** It's also used by a lot of core rust crates, seems to be pretty well supported, and has good async support.
2932
** First I attempted to create a https://docs.rs/tracing-slog/latest/tracing_slog/[slog drain that converted between the two]
3033
but the code felt like a hack and didn't map key/value pairs very well.
3134
** Software philosophically I don't think a lib focused on REST API ergonomics should be handling log output or
3235
configuration, so I removed some of those pieces.
36+
** I also lowered some of the logging levels. Typically I run my dev terminal at `DEBUG` log level and like it so
37+
that there is more information than I would run in production but not a deluge of it. The logging levels previously
38+
were all mostly at the debug level making actual dev signal hard to get.
3339

3440
* Various other small quality of life changes.
3541

3642
Since the two changes above are largely non-backward compatible changes, I created
3743
this fork instead of trying to get these merged upstream.
3844

45+
== Regularly scheduled programming
46+
3947
Dropshot is a general-purpose crate for exposing REST APIs from a Rust program.
4048
For more, see the https://docs.rs/dropshot/[online Dropshot documentation].
4149
You can build the documentation yourself with:
@@ -69,3 +77,6 @@ It's early, and we may find we need richer facilities in the framework. But we'
6977
== Examples
7078

7179
To run the examples in dropshot/examples, clone the repository and run `cargo run --example [example_name]`, e.g. `cargo run --example basic`. (Do not include the file extension.)
80+
81+
Since we've moved to tracing, you can turn on logging for each test by uncommenting the tracing_subscriber implementation
82+
inside each example.

dropshot/examples/basic.rs

+6-5
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,12 @@ async fn main() -> Result<(), String> {
2424
// port.
2525
let config_dropshot: ConfigDropshot = Default::default();
2626

27-
tracing_subscriber::fmt()
28-
.with_max_level(tracing::Level::TRACE)
29-
.with_target(false)
30-
.compact()
31-
.init();
27+
// Uncomment for logs
28+
// tracing_subscriber::fmt()
29+
// .with_max_level(tracing::Level::INFO)
30+
// .with_target(false)
31+
// .compact()
32+
// .init();
3233

3334
// Build a description of the API.
3435
let mut api = ApiDescription::new();

dropshot/examples/file_server.rs

+6-5
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,12 @@ async fn main() -> Result<(), String> {
2626
// port.
2727
let config_dropshot = Default::default();
2828

29-
tracing_subscriber::fmt()
30-
.with_max_level(tracing::Level::TRACE)
31-
.with_target(false)
32-
.compact()
33-
.init();
29+
// Uncomment for logs
30+
// tracing_subscriber::fmt()
31+
// .with_max_level(tracing::Level::INFO)
32+
// .with_target(false)
33+
// .compact()
34+
// .init();
3435

3536
// Build a description of the API -- in this case it's not much of an API!.
3637
let mut api = ApiDescription::new();

dropshot/examples/https.rs

+6-5
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,12 @@ async fn main() -> Result<(), String> {
6666
key_file: key_file.path().to_path_buf(),
6767
});
6868

69-
tracing_subscriber::fmt()
70-
.with_max_level(tracing::Level::TRACE)
71-
.with_target(false)
72-
.compact()
73-
.init();
69+
// Uncomment for logs
70+
// tracing_subscriber::fmt()
71+
// .with_max_level(tracing::Level::INFO)
72+
// .with_target(false)
73+
// .compact()
74+
// .init();
7475

7576
// Build a description of the API.
7677
let mut api = ApiDescription::new();

dropshot/examples/index.rs

+6-5
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@ async fn main() -> Result<(), String> {
2020
// port.
2121
let config_dropshot: ConfigDropshot = Default::default();
2222

23-
tracing_subscriber::fmt()
24-
.with_max_level(tracing::Level::TRACE)
25-
.with_target(false)
26-
.compact()
27-
.init();
23+
// Uncomment for logs
24+
// tracing_subscriber::fmt()
25+
// .with_max_level(tracing::Level::INFO)
26+
// .with_target(false)
27+
// .compact()
28+
// .init();
2829

2930
// Build a description of the API.
3031
let mut api = ApiDescription::new();

dropshot/examples/module-basic.rs

+6-5
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@ async fn main() -> Result<(), String> {
1616
// port.
1717
let config_dropshot = Default::default();
1818

19-
tracing_subscriber::fmt()
20-
.with_max_level(tracing::Level::TRACE)
21-
.with_target(false)
22-
.compact()
23-
.init();
19+
// Uncomment for logs
20+
// tracing_subscriber::fmt()
21+
// .with_max_level(tracing::Level::INFO)
22+
// .with_target(false)
23+
// .compact()
24+
// .init();
2425

2526
// Build a description of the API.
2627
let mut api = ApiDescription::new();

dropshot/examples/module-shared-context.rs

+6-5
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,12 @@ async fn main() -> Result<(), String> {
2424
// port.
2525
let config_dropshot = Default::default();
2626

27-
tracing_subscriber::fmt()
28-
.with_max_level(tracing::Level::TRACE)
29-
.with_target(false)
30-
.compact()
31-
.init();
27+
// Uncomment for logs
28+
// tracing_subscriber::fmt()
29+
// .with_max_level(tracing::Level::INFO)
30+
// .with_target(false)
31+
// .compact()
32+
// .init();
3233

3334
// Build a description of the API.
3435
let mut api = ApiDescription::new();

dropshot/examples/multipart.rs

+6-5
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,12 @@ async fn main() -> Result<(), String> {
1919
// port.
2020
let config_dropshot: ConfigDropshot = Default::default();
2121

22-
tracing_subscriber::fmt()
23-
.with_max_level(tracing::Level::TRACE)
24-
.with_target(false)
25-
.compact()
26-
.init();
22+
// Uncomment for logs
23+
// tracing_subscriber::fmt()
24+
// .with_max_level(tracing::Level::INFO)
25+
// .with_target(false)
26+
// .compact()
27+
// .init();
2728

2829
// Build a description of the API.
2930
let mut api = ApiDescription::new();

dropshot/examples/multiple-servers.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ impl SharedMultiServerContext {
188188
};
189189

190190
tracing_subscriber::fmt()
191-
.with_max_level(tracing::Level::TRACE)
191+
.with_max_level(tracing::Level::INFO)
192192
.with_target(false)
193193
.compact()
194194
.init();

dropshot/examples/pagination-basic.rs

+6-5
Original file line numberDiff line numberDiff line change
@@ -98,11 +98,12 @@ async fn example_list_projects(
9898

9999
#[tokio::main]
100100
async fn main() -> Result<(), String> {
101-
tracing_subscriber::fmt()
102-
.with_max_level(tracing::Level::TRACE)
103-
.with_target(false)
104-
.compact()
105-
.init();
101+
// Uncomment for logs
102+
// tracing_subscriber::fmt()
103+
// .with_max_level(tracing::Level::INFO)
104+
// .with_target(false)
105+
// .compact()
106+
// .init();
106107

107108
let port = std::env::args()
108109
.nth(1)

dropshot/examples/pagination-multiple-resources.rs

+6-5
Original file line numberDiff line numberDiff line change
@@ -267,11 +267,12 @@ where
267267

268268
#[tokio::main]
269269
async fn main() -> Result<(), String> {
270-
tracing_subscriber::fmt()
271-
.with_max_level(tracing::Level::TRACE)
272-
.with_target(false)
273-
.compact()
274-
.init();
270+
// Uncomment for logs
271+
// tracing_subscriber::fmt()
272+
// .with_max_level(tracing::Level::INFO)
273+
// .with_target(false)
274+
// .compact()
275+
// .init();
275276

276277
let port = std::env::args()
277278
.nth(1)

dropshot/examples/pagination-multiple-sorts.rs

+6-5
Original file line numberDiff line numberDiff line change
@@ -281,11 +281,12 @@ async fn example_list_projects(
281281

282282
#[tokio::main]
283283
async fn main() -> Result<(), String> {
284-
tracing_subscriber::fmt()
285-
.with_max_level(tracing::Level::TRACE)
286-
.with_target(false)
287-
.compact()
288-
.init();
284+
// Uncomment for logs
285+
// tracing_subscriber::fmt()
286+
// .with_max_level(tracing::Level::INFO)
287+
// .with_target(false)
288+
// .compact()
289+
// .init();
289290

290291
let port = std::env::args()
291292
.nth(1)

dropshot/examples/petstore.rs

+6-5
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ use schemars::JsonSchema;
66
use serde::{Deserialize, Serialize};
77

88
fn main() -> Result<(), String> {
9-
tracing_subscriber::fmt()
10-
.with_max_level(tracing::Level::TRACE)
11-
.with_target(false)
12-
.compact()
13-
.init();
9+
// Uncomment for logs
10+
// tracing_subscriber::fmt()
11+
// .with_max_level(tracing::Level::INFO)
12+
// .with_target(false)
13+
// .compact()
14+
// .init();
1415

1516
// Build a description of the API.
1617
let mut api = ApiDescription::new();

dropshot/examples/request-headers.rs

+6-5
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@ use dropshot::RequestContext;
2020

2121
#[tokio::main]
2222
async fn main() -> Result<(), String> {
23-
tracing_subscriber::fmt()
24-
.with_max_level(tracing::Level::TRACE)
25-
.with_target(false)
26-
.compact()
27-
.init();
23+
// Uncomment for logs
24+
// tracing_subscriber::fmt()
25+
// .with_max_level(tracing::Level::INFO)
26+
// .with_target(false)
27+
// .compact()
28+
// .init();
2829

2930
let config_dropshot: ConfigDropshot = Default::default();
3031
let mut api = ApiDescription::new();

dropshot/examples/schema-with-example.rs

+6-5
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,12 @@ fn bar_example() -> Bar {
4848
}
4949

5050
fn main() -> Result<(), String> {
51-
tracing_subscriber::fmt()
52-
.with_max_level(tracing::Level::TRACE)
53-
.with_target(false)
54-
.compact()
55-
.init();
51+
// Uncomment for logs
52+
// tracing_subscriber::fmt()
53+
// .with_max_level(tracing::Level::INFO)
54+
// .with_target(false)
55+
// .compact()
56+
// .init();
5657

5758
let mut api = ApiDescription::new();
5859
api.register(get_foo).unwrap();

dropshot/examples/self-referential.rs

+6-5
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,12 @@ use std::sync::Arc;
1818

1919
#[tokio::main]
2020
async fn main() -> Result<(), String> {
21-
tracing_subscriber::fmt()
22-
.with_max_level(tracing::Level::TRACE)
23-
.with_target(false)
24-
.compact()
25-
.init();
21+
// Uncomment for logs
22+
// tracing_subscriber::fmt()
23+
// .with_max_level(tracing::Level::INFO)
24+
// .with_target(false)
25+
// .compact()
26+
// .init();
2627

2728
let config_dropshot: ConfigDropshot = Default::default();
2829

dropshot/examples/websocket.rs

+6-5
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@ use tokio_tungstenite::tungstenite::Message;
1616

1717
#[tokio::main]
1818
async fn main() -> Result<(), String> {
19-
tracing_subscriber::fmt()
20-
.with_max_level(tracing::Level::TRACE)
21-
.with_target(false)
22-
.compact()
23-
.init();
19+
// Uncomment for logs
20+
// tracing_subscriber::fmt()
21+
// .with_max_level(tracing::Level::INFO)
22+
// .with_target(false)
23+
// .compact()
24+
// .init();
2425

2526
// We must specify a configuration with a bind address. We'll use 127.0.0.1
2627
// since it's available and won't expose this server outside the host. We

dropshot/examples/well-tagged.rs

+6-5
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,12 @@ async fn get_fryism(
4545

4646
#[tokio::main]
4747
async fn main() -> Result<(), String> {
48-
tracing_subscriber::fmt()
49-
.with_max_level(tracing::Level::TRACE)
50-
.with_target(false)
51-
.compact()
52-
.init();
48+
// Uncomment for logs
49+
// tracing_subscriber::fmt()
50+
// .with_max_level(tracing::Level::INFO)
51+
// .with_target(false)
52+
// .compact()
53+
// .init();
5354

5455
// We must specify a configuration with a bind address. We'll use 127.0.0.1
5556
// since it's available and won't expose this server outside the host. We

0 commit comments

Comments
 (0)