Skip to content

Commit

Permalink
Restructure configuration (#199)
Browse files Browse the repository at this point in the history
This PR restructures configuration in both the RealWorld example and the
starter template.
The goal is to separate the application config (which can and should be
injected) from the server config (which is only needed at startup). We
also provide an example of how to add additional app configuration.

Other misc changes:

- I removed the `Test` application profile, since it's not really a fit.
The tests change the configuration anyway and they can start from the
development one.
- I've added the telemetry setup to the tests in the starter project.
  • Loading branch information
LukeMathWalker committed Feb 23, 2024
1 parent 54f861e commit a30962d
Show file tree
Hide file tree
Showing 40 changed files with 1,205 additions and 889 deletions.
1 change: 1 addition & 0 deletions doc_examples/quickstart/02-register_new_route.snap
Expand Up @@ -4,6 +4,7 @@ pub fn blueprint() -> Blueprint {
let mut bp = Blueprint::new();
ApiKit::new().register(&mut bp);
telemetry::register(&mut bp);
ApplicationConfig::register(&mut bp);
bp.route(GET, "/api/ping", f!(crate::routes::status::ping));
bp.route(
Expand Down
10 changes: 5 additions & 5 deletions doc_examples/quickstart/02.patch
@@ -1,10 +1,10 @@
diff --git a/demo/src/blueprint.rs b/demo/src/blueprint.rs
index 4a196d8..0a6857d 100644
index f2b3732..a282fa0 100644
--- a/demo/src/blueprint.rs
+++ b/demo/src/blueprint.rs
@@ -11,6 +11,11 @@ pub fn blueprint() -> Blueprint {
add_telemetry_middleware(&mut bp);
@@ -12,5 +12,10 @@ pub fn blueprint() -> Blueprint {
ApplicationConfig::register(&mut bp);

bp.route(GET, "/api/ping", f!(crate::routes::status::ping));
+ bp.route(
+ GET,
Expand All @@ -13,7 +13,7 @@ index 4a196d8..0a6857d 100644
+ );
bp
}

diff --git a/demo/src/routes/greet.rs b/demo/src/routes/greet.rs
new file mode 100644
index 0000000..38ec1e3
Expand Down
1 change: 1 addition & 0 deletions doc_examples/quickstart/04-register_common_invocation.snap
Expand Up @@ -4,5 +4,6 @@ pub fn blueprint() -> Blueprint {
let mut bp = Blueprint::new();
ApiKit::new().register(&mut bp);
// [...]
bp
}
```
9 changes: 5 additions & 4 deletions doc_examples/quickstart/05-bis.patch
@@ -1,9 +1,10 @@
diff --git a/demo/src/blueprint.rs b/demo/src/blueprint.rs
index a282fa0..2067e4a 100644
--- a/demo/src/blueprint.rs
+++ b/demo/src/blueprint.rs
@@ -11,11 +11,7 @@ pub fn blueprint() -> Blueprint {
add_telemetry_middleware(&mut bp);
@@ -12,10 +12,6 @@ pub fn blueprint() -> Blueprint {
ApplicationConfig::register(&mut bp);

bp.route(GET, "/api/ping", f!(crate::routes::status::ping));
- bp.route(
- GET,
Expand All @@ -13,7 +14,7 @@ diff --git a/demo/src/blueprint.rs b/demo/src/blueprint.rs
+ bp.route(GET, "/api/greet/:name", f!(crate::routes::greet::greet));
bp
}

diff --git a/demo/src/routes/greet.rs b/demo/src/routes/greet.rs
--- a/demo/src/routes/greet.rs
+++ b/demo/src/routes/greet.rs
Expand Down
8 changes: 4 additions & 4 deletions doc_examples/quickstart/05-error.snap
Expand Up @@ -3,12 +3,12 @@
│ instance of `demo::user_agent::UserAgent` as input, but I can't find a constructor for that
│ type.
│
[31m│[0m ╭─[[36;1;4mdemo/src/blueprint.rs[0m:13:1]
[31m│[0m [2m13[0mbp.route(GET, "/api/ping", f!(crate::routes::status::ping));
[31m│[0m [2m14[0mbp.route(GET, "/api/greet/:name", f!(crate::routes::greet::greet));
[31m│[0m ╭─[[36;1;4mdemo/src/blueprint.rs[0m:14:1]
[31m│[0m [2m14[0mbp.route(GET, "/api/ping", f!(crate::routes::status::ping));
[31m│[0m [2m15[0mbp.route(GET, "/api/greet/:name", f!(crate::routes::greet::greet));
│ ·  ───────────────┬───────────────
│ · The request handler was registered here ──╯
[31m│[0m [2m15[0mbp
[31m│[0m [2m16[0mbp
│ ╰────
│ ╭─[demo/src/routes/greet.rs:10:1]
│ 10
Expand Down
6 changes: 3 additions & 3 deletions doc_examples/quickstart/06.patch
Expand Up @@ -2,14 +2,14 @@ diff --git a/demo/src/blueprint.rs b/demo/src/blueprint.rs
--- a/demo/src/blueprint.rs
+++ b/demo/src/blueprint.rs
@@ -1,4 +1,5 @@
use crate::telemetry;
use crate::{configuration::ApplicationConfig, telemetry};
+use pavex::blueprint::constructor::Lifecycle;
use pavex::blueprint::{router::GET, Blueprint};
use pavex::f;
use pavex::kit::ApiKit;
@@ -10,6 +11,11 @@ pub fn blueprint() -> Blueprint {
ApiKit::new().register(&mut bp);
@@ -11,6 +12,11 @@ pub fn blueprint() -> Blueprint {
telemetry::register(&mut bp);
ApplicationConfig::register(&mut bp);

+ bp.constructor(
+ f!(crate::user_agent::UserAgent::extract),
Expand Down
8 changes: 4 additions & 4 deletions doc_examples/quickstart/07-error.snap
Expand Up @@ -3,12 +3,12 @@
│ handler for it. If I don't have an error handler, I don't know what to do with the error when
│ the constructor fails!
│
[31m│[0m ╭─[[36;1;4mdemo/src/blueprint.rs[0m:14:1]
[31m│[0m [2m14[0mbp.constructor(
[31m│[0m [2m15[0mf!(crate::user_agent::UserAgent::extract),
[31m│[0m ╭─[[36;1;4mdemo/src/blueprint.rs[0m:15:1]
[31m│[0m [2m15[0mbp.constructor(
[31m│[0m [2m16[0mf!(crate::user_agent::UserAgent::extract),
│ ·  ────────────────────┬────────────────────
│ · ╰── The fallible constructor was registered here
[31m│[0m [2m16[0mLifecycle::RequestScoped,
[31m│[0m [2m17[0mLifecycle::RequestScoped,
│ ╰────
│  help: Add an error handler via `.error_handler`

Expand Down
1 change: 1 addition & 0 deletions doc_examples/quickstart/demo-blueprint_definition.snap
Expand Up @@ -4,6 +4,7 @@ pub fn blueprint() -> Blueprint {
let mut bp = Blueprint::new();
ApiKit::new().register(&mut bp);
telemetry::register(&mut bp);
ApplicationConfig::register(&mut bp);
bp.route(GET, "/api/ping", f!(crate::routes::status::ping));
bp
Expand Down
12 changes: 7 additions & 5 deletions doc_examples/quickstart/demo-bp_server_binary.snap
@@ -1,11 +1,12 @@
```rust title="demo_server/src/bin/api.rs"
use anyhow::Context;
use demo_server::{
configuration::load_configuration,
configuration::Config,
telemetry::{get_subscriber, init_telemetry},
};
use demo_server_sdk::{build_application_state, run};
use pavex::server::Server;
use pavex_tracing::fields::{error_details, error_message, ERROR_DETAILS, ERROR_MESSAGE};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
Expand All @@ -16,9 +17,10 @@ async fn main() -> anyhow::Result<()> {
// in order to have a single choke point where we make sure to log fatal errors
// that will cause the application to exit.
if let Err(e) = _main().await {
tracing::error!(
error.msg = %e,
error.error_chain = ?e,
tracing::event!(
tracing::Level::ERROR,
{ ERROR_MESSAGE } = error_message(&e),
{ ERROR_DETAILS } = error_details(&e),
"The application is exiting due to an error"
)
}
Expand All @@ -30,7 +32,7 @@ async fn _main() -> anyhow::Result<()> {
// Load environment variables from a .env file, if it exists.
let _ = dotenvy::dotenv();
let config = load_configuration(None)?;
let config = Config::load(None)?;
let application_state = build_application_state().await;
let tcp_listener = config
Expand Down
1 change: 1 addition & 0 deletions doc_examples/quickstart/demo-route_registration.snap
Expand Up @@ -4,6 +4,7 @@ pub fn blueprint() -> Blueprint {
let mut bp = Blueprint::new();
ApiKit::new().register(&mut bp);
telemetry::register(&mut bp);
ApplicationConfig::register(&mut bp);
bp.route(GET, "/api/ping", f!(crate::routes::status::ping));
bp
Expand Down
4 changes: 2 additions & 2 deletions doc_examples/quickstart/tutorial.yml
Expand Up @@ -96,7 +96,7 @@ steps:
hl_lines: [ 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 ]
- name: "register"
source_path: "demo/src/blueprint.rs"
ranges: [ "8..9", "13..17", "21..22" ]
ranges: [ "8..9", "14..18", "22..23" ]
hl_lines: [ 10, 11, 12, 13 ]
commands:
- command: "cargo px c"
Expand All @@ -117,7 +117,7 @@ steps:
ranges: [ "21..25" ]
- name: "register"
source_path: "demo/src/blueprint.rs"
ranges: [ "8..9", "13..18", "22..23" ]
ranges: [ "8..9", "14..19", "23..24" ]
hl_lines: [ 8 ]
commands:
- command: "cargo px c"
Expand Down
10 changes: 7 additions & 3 deletions docs/guide/project_structure/index.md
Expand Up @@ -37,16 +37,17 @@ Using the `demo` project as an example, the relationship between the project cra
graph
d[demo] -->|contains| bp[Blueprint];
bp -->|is used to generate| dss[demo_server_sdk];
dss -->|is invoked by| ds[demo_server];
dss -->|is invoked by| dst[API tests in demo_server];
dss -->|is used by| ds[demo_server];
dss -->|is used by| dst[API tests in demo_server];
```

If you want to know more, read on!

## Blueprint

Every Pavex project has, at its core, a [`Blueprint`][Blueprint].
It's the type you use to declare the structure of your API: [routes], middlewares, [constructors], error handlers, etc.
It's the type you use to declare the structure of your API:
[routes], [middlewares], [constructors], [error handlers], [error observers], etc.

--8<-- "doc_examples/quickstart/demo-blueprint_definition.snap"

Expand Down Expand Up @@ -183,4 +184,7 @@ The `demo` project includes an example of such a test which you can use as a ref

[routes]: ../routing/index.md
[constructors]: ../dependency_injection/index.md
[middlewares]: ../middleware/index.md
[error handlers]: ../errors/error_handlers.md
[error observers]: ../errors/error_observers.md
[cargo-px]: https://github.com/LukeMathWalker/cargo-px
3 changes: 2 additions & 1 deletion examples/realworld/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions examples/realworld/api_server/Cargo.toml
Expand Up @@ -22,6 +22,7 @@ serde = { version = "1", features = ["derive"]}
tracing = "0.1"
tracing-subscriber = { version = "0.3", default-features = false, features = ["env-filter", "smallvec", "std", "registry", "tracing-log"] }
tracing-bunyan-formatter = "0.3"
pavex_tracing = { path = "../../../libs/pavex_tracing" }

[dev-dependencies]
reqwest = { version = "0.11", features = ["json"] }
Expand Down
5 changes: 3 additions & 2 deletions examples/realworld/api_server/configuration/prod.yml
@@ -1,4 +1,5 @@
server:
ip: "0.0.0.0"
database:
require_ssl: true
app:
database:
require_ssl: true
27 changes: 14 additions & 13 deletions examples/realworld/api_server/configuration/test.yml
Expand Up @@ -6,16 +6,17 @@ server:
ip: "127.0.0.1"
# The OS will assign a random port to the test server.
port: 0
auth:
# Placeholder values, they'll be replaced with real values
# generated on the fly before launching the test server.
eddsa_private_key_pem: "secret"
eddsa_public_key_pem: "secret"
# Same parameters used in /scripts/init_db.sh
database:
host: "127.0.0.1"
port: 5432
username: "postgres"
password: "password"
database_name: "conduit"
require_ssl: false
app:
auth:
# Placeholder values, they'll be replaced with real values
# generated on the fly before launching the test server.
eddsa_private_key_pem: "secret"
eddsa_public_key_pem: "secret"
# Same parameters used in /scripts/init_db.sh
database:
host: "127.0.0.1"
port: 5432
username: "postgres"
password: "password"
database_name: "conduit"
require_ssl: false
20 changes: 10 additions & 10 deletions examples/realworld/api_server/src/bin/api.rs
@@ -1,23 +1,23 @@
use anyhow::Context;
use api_server::{
configuration::load_configuration,
telemetry::{get_subscriber, init_telemetry},
};
use api_server::configuration::Config;
use api_server::telemetry::{get_subscriber, init_telemetry};
use api_server_sdk::{build_application_state, run};
use pavex::server::Server;
use pavex_tracing::fields::{error_details, error_message, ERROR_DETAILS, ERROR_MESSAGE};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
let subscriber = get_subscriber("realworld".into(), "info".into(), std::io::stdout);
init_telemetry(subscriber)?;

// We isolate all the server setup and launch logic in a separate function
// in order to have a single choke point where we make sure to log fatal errors
// to have a single choke point where we make sure to log fatal errors
// that will cause the application to exit.
if let Err(e) = _main().await {
tracing::error!(
error.msg = %e,
error.error_chain = ?e,
tracing::event!(
tracing::Level::ERROR,
{ ERROR_MESSAGE } = error_message(&e),
{ ERROR_DETAILS } = error_details(&e),
"The application is exiting due to an error"
)
}
Expand All @@ -26,8 +26,8 @@ async fn main() -> anyhow::Result<()> {
}

async fn _main() -> anyhow::Result<()> {
let config = load_configuration(None)?;
let application_state = build_application_state(&config.auth, &config.database)
let config = Config::load(None)?;
let application_state = build_application_state(&config.app)
.await
.context("Failed to build the application state")?;

Expand Down

0 comments on commit a30962d

Please sign in to comment.