summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/bin/web.rs13
-rw-r--r--src/twelve.rs68
-rw-r--r--src/view.rs107
3 files changed, 85 insertions, 103 deletions
diff --git a/src/bin/web.rs b/src/bin/web.rs
index 3186207..6913263 100644
--- a/src/bin/web.rs
+++ b/src/bin/web.rs
@@ -23,14 +23,9 @@ async fn main() -> Result {
let service = view::make_service()?;
- let app_factory = move ||
- App::new()
- .configure(|cfg| service(cfg));
-
- HttpServer::new(app_factory)
- .bind(port)?
- .run()
- .await?;
-
+ let app_factory = move || App::new().configure(|cfg| service(cfg));
+
+ HttpServer::new(app_factory).bind(port)?.run().await?;
+
Ok(())
}
diff --git a/src/twelve.rs b/src/twelve.rs
index ea63bf4..08bccf0 100644
--- a/src/twelve.rs
+++ b/src/twelve.rs
@@ -1,27 +1,27 @@
//! A [twelve-factor application][1] reads [its configuration][2] from the environment.
-//!
+//!
//! In many cases, "read" directly maps to the target binary inspecting the
//! OS-provided environment dictionary. This module provides supporting tools
//! for reading configuration data from the environment, via `std::env`, and
//! converting it to useful types.
-//!
+//!
//! [1]: https://12factor.net/
//! [2]: https://12factor.net/config
use std::env;
use std::io;
-use std::net::{Ipv4Addr, Ipv6Addr, IpAddr, SocketAddr, ToSocketAddrs};
+use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, ToSocketAddrs};
use std::num;
use thiserror::Error;
/// Errors that can arise when reading a port number from the environment.
-///
+///
/// For convenience when returning errors into `main`, this type can be
/// converted to std::io::Error.
#[derive(Error, Debug)]
pub enum Error {
/// PORT was set, but contained a non-unicode value that sys::env can't parse.
- ///
+ ///
/// For obvious reasons, this cannot be converted to a port number. Rather
/// than ignoring this error, we report it, so that misconfiguration can be
/// detected early.
@@ -31,7 +31,7 @@ pub enum Error {
source: env::VarError,
},
/// PORT was set, but was set to a non-numeric value.FnOnce
- ///
+ ///
/// PORT can only be used to select a port number if numeric. Rather than
/// ignoring this error, we report it, so that misconfiguration can be
/// detected early.
@@ -39,18 +39,18 @@ pub enum Error {
ParseError {
#[from]
source: num::ParseIntError,
- }
+ },
}
/// A listen address consisting of only a port number.
-///
+///
/// Listening on this address will bind to both the ip4 and ip6 addresses on the
/// current host, assuming both ip4 and ip6 are supported.
#[derive(Debug, Clone)]
pub struct PortAddr {
/// When used in an std::net::SocketAddr context, this is the port number to
/// bind on.
- port: u16
+ port: u16,
}
fn v4(port_addr: &PortAddr) -> SocketAddr {
@@ -65,28 +65,25 @@ impl ToSocketAddrs for PortAddr {
type Iter = std::vec::IntoIter<SocketAddr>;
fn to_socket_addrs(&self) -> io::Result<Self::Iter> {
- let addrs = vec![
- v6(self),
- v4(self),
- ];
+ let addrs = vec![v6(self), v4(self)];
Ok(addrs.into_iter())
}
}
/// Query the environment for a port number.
-///
+///
/// This will read the PORT environment variable. If set, it will use the value
/// (as a number). If it's unset, then this will use the passed `default_port`
/// number to choose the app's default port. If the PORT environment variable
/// is set but cannot be interpreted as a port number, this will return an error
/// indicating why, to assist the user in correcting their configuration.
/// # Examples
-///
+///
/// ```
/// use std::net::TcpListener;
/// mod twelve;
-///
+///
/// // Listen on port 3000 (or $PORT if set), on global ip4 and ip6 interfaces.
/// let port = twelve::port(3000)?;
/// let listener = TcpListener::bind(port);
@@ -100,9 +97,7 @@ pub fn port(default_port: u16) -> Result<PortAddr, Error> {
},
};
- Ok(PortAddr{
- port,
- })
+ Ok(PortAddr { port })
}
#[cfg(test)]
@@ -127,31 +122,28 @@ mod tests {
#[quickcheck]
fn port_addr_as_socket_addr_has_v4(addr: PortAddr) -> bool {
- let socket_addrs = addr.to_socket_addrs()
- .unwrap()
- .collect::<Vec<_>>();
+ let socket_addrs = addr.to_socket_addrs().unwrap().collect::<Vec<_>>();
- socket_addrs.iter()
+ socket_addrs
+ .iter()
.any(|&socket_addr| socket_addr.is_ipv4())
}
#[quickcheck]
fn port_addr_as_socket_addr_has_v6(addr: PortAddr) -> bool {
- let socket_addrs = addr.to_socket_addrs()
- .unwrap()
- .collect::<Vec<_>>();
+ let socket_addrs = addr.to_socket_addrs().unwrap().collect::<Vec<_>>();
- socket_addrs.iter()
+ socket_addrs
+ .iter()
.any(|&socket_addr| socket_addr.is_ipv6())
}
#[quickcheck]
fn port_addr_as_socket_addr_all_have_port(addr: PortAddr) -> bool {
- let socket_addrs = addr.to_socket_addrs()
- .unwrap()
- .collect::<Vec<_>>();
+ let socket_addrs = addr.to_socket_addrs().unwrap().collect::<Vec<_>>();
- socket_addrs.iter()
+ socket_addrs
+ .iter()
.all(|&socket_addr| socket_addr.port() == addr.port)
}
@@ -172,16 +164,14 @@ mod tests {
// attempts to accelerate testing by running multiple threads, but this
// causes race conditions as test A stomps on state used by test B.
// Serialize tests through a mutex.
- //
+ //
// Huge hack.
static ref ENV_MUTEX: Mutex<Runner> = Mutex::new(Runner::default());
}
// Runs a body with ENV_MUTEX locked. Easier to write.
fn env_locked<T>(f: impl FnOnce() -> T) -> T {
- ENV_MUTEX.lock()
- .unwrap()
- .run(f)
+ ENV_MUTEX.lock().unwrap().run(f)
}
#[quickcheck]
@@ -193,8 +183,7 @@ mod tests {
env_locked(|| {
env::set_var("PORT", env_port.to_string());
- let read_port = port(default_port)
- .unwrap();
+ let read_port = port(default_port).unwrap();
TestResult::from_bool(read_port.port == env_port)
})
@@ -220,8 +209,7 @@ mod tests {
env_locked(|| {
env::remove_var("PORT");
- let read_port = port(default_port)
- .unwrap();
+ let read_port = port(default_port).unwrap();
read_port.port == default_port
})
@@ -239,4 +227,4 @@ mod tests {
assert!(result.is_err());
})
}
-} \ No newline at end of file
+}
diff --git a/src/view.rs b/src/view.rs
index eb61ba9..5f2e3d9 100644
--- a/src/view.rs
+++ b/src/view.rs
@@ -1,54 +1,54 @@
//! HTML resources that help users troubleshoot problems.
-//!
+//!
//! This provides a single endpoint, as well as necessary application data to
//! power it. The endpoint can be mounted on an actix_web App using the exposed
//! `make_service(…)` function.
-//!
+//!
//! # Examples
-//!
+//!
//! ```
//! let service = view::make_service()?;
//! let app_factory = move ||
//! App::new()
//! .configure(|cfg| service(cfg));
-//!
+//!
//! HttpServer::new(app_factory)
//! .bind(port)?
//! .run()
//! .await?;
//! ```
-//!
+//!
//! # Endpoints
-//!
+//!
//! * `/` (`GET`): an HTML page suggesting one thing to check.
-//!
+//!
//! Takes an optional `item` URL parameter, which must be an integer between 0
//! and the number of options available (not provided). If `item` is provided,
//! this endpoint returns a fixed result (the `item`th suggestion in the
//! backing data); otherwise, it returns a randomly-selected result, for
//! fortuitous suggesting.
-//!
+//!
//! The returned page is always `text/html` on success. Invalid `item` indices
//! will return an error.
-//!
+//!
//! # Data
-//!
+//!
//! This module creates a data item in the configured application, consisting of
//! a list of strings loaded from a YAML constant. The data comes from a file in
//! this module parsed at compile time — our target deployment environments
//! don't support modifying it without triggering a rebuild anyways. It's parsed
//! on startup, however, and invalid data can cause `make_service` to fail.
-//!
+//!
//! When adding suggestions, add them at the end. This will ensure that existing
//! links to existing items are not invalidated or changed - the `item`
//! parameter to the `/` endpoint is a literal index into this list.
-use actix_web::{get, error, web, Responder};
-use maud::{DOCTYPE, html, Markup, PreEscaped};
-use pulldown_cmark::{Parser, Options, html};
-use rand::thread_rng;
+use actix_web::{error, get, web, Responder};
+use maud::{html, Markup, PreEscaped, DOCTYPE};
+use pulldown_cmark::{html, Options, Parser};
use rand::seq::SliceRandom;
-use serde::{Serialize, Deserialize};
+use rand::thread_rng;
+use serde::{Deserialize, Serialize};
use serde_urlencoded::ser;
use std::iter;
use thiserror::Error;
@@ -100,18 +100,13 @@ struct ItemQuery {
impl From<&usize> for ItemQuery {
fn from(idx: &usize) -> Self {
- ItemQuery {
- item: Some(*idx),
- }
+ ItemQuery { item: Some(*idx) }
}
}
type MarkupResult = Result<Markup, error::Error>;
-fn page(
- head: impl FnOnce() -> MarkupResult,
- body: impl FnOnce() -> MarkupResult,
-) -> MarkupResult {
+fn page(head: impl FnOnce() -> MarkupResult, body: impl FnOnce() -> MarkupResult) -> MarkupResult {
Ok(html! {
(DOCTYPE)
html {
@@ -126,7 +121,7 @@ fn page(
}
fn stylesheet() -> Markup {
- html!{
+ html! {
style {
(PreEscaped("
body {
@@ -165,7 +160,11 @@ fn og_card(title: &str, description: &str) -> Markup {
}
}
-fn suggestion_link(req: &impl Urls, query: ItemQuery, body: impl FnOnce() -> MarkupResult) -> MarkupResult {
+fn suggestion_link(
+ req: &impl Urls,
+ query: ItemQuery,
+ body: impl FnOnce() -> MarkupResult,
+) -> MarkupResult {
Ok(html! {
p {
a href=( req.index_url(query)? ) { (body()?) }
@@ -187,23 +186,27 @@ fn github_badge(repo: &str) -> Markup {
fn index_view(req: impl Urls, idx: &usize, thing: &Thing) -> MarkupResult {
page(
- || Ok(html! {
- title { (thing.markdown) }
- (stylesheet())
- (og_card("Troubleshooting suggestion", &thing.markdown))
- }),
- || Ok(html! {
- section {
- (PreEscaped(&thing.html))
- (suggestion_link(&req, ItemQuery::default(), || Ok(html! {
- "That wasn't it, suggest something else."
- }))?)
- (suggestion_link(&req, ItemQuery::from(idx), || Ok(html! {
- "Share this troubleshooting suggestion."
- }))?)
- }
- (github_badge("ojacobson/things-to-check"))
- })
+ || {
+ Ok(html! {
+ title { (thing.markdown) }
+ (stylesheet())
+ (og_card("Troubleshooting suggestion", &thing.markdown))
+ })
+ },
+ || {
+ Ok(html! {
+ section {
+ (PreEscaped(&thing.html))
+ (suggestion_link(&req, ItemQuery::default(), || Ok(html! {
+ "That wasn't it, suggest something else."
+ }))?)
+ (suggestion_link(&req, ItemQuery::from(idx), || Ok(html! {
+ "Share this troubleshooting suggestion."
+ }))?)
+ }
+ (github_badge("ojacobson/things-to-check"))
+ })
+ },
)
}
@@ -223,8 +226,7 @@ async fn index(
None => return Err(error::ErrorNotFound("Not found")),
};
- Ok(index_view(req, index, thing)?
- .with_header("Cache-Control", "no-store"))
+ Ok(index_view(req, index, thing)?.with_header("Cache-Control", "no-store"))
}
const THINGS: &str = include_str!("things-to-check.yml");
@@ -243,10 +245,7 @@ impl From<String> for Thing {
let mut html = String::new();
html::push_html(&mut html, parser);
- Thing{
- markdown,
- html,
- }
+ Thing { markdown, html }
}
}
@@ -257,10 +256,11 @@ fn load_things(src: &str) -> serde_yaml::Result<Things> {
let raw_things: Vec<String> = serde_yaml::from_str(src)?;
Ok(Things(
- raw_things.into_iter()
+ raw_things
+ .into_iter()
.map(Thing::from)
.enumerate()
- .collect()
+ .collect(),
))
}
@@ -270,18 +270,17 @@ pub enum Error {
/// Indicates that the included YAML was invalid in some way. This is only
/// fixable by recompiling the program with correct YAML.
#[error("Unable to load Things To Check YAML: {0}")]
- DeserializeError(#[from] serde_yaml::Error)
+ DeserializeError(#[from] serde_yaml::Error),
}
/// Set up an instance of this service.
-///
+///
/// The returned function will configure any actix-web App with the necessary
/// state to tell people how to troubleshoot problems.
pub fn make_service() -> Result<impl Fn(&mut web::ServiceConfig) + Clone, Error> {
let things = load_things(THINGS)?;
Ok(move |cfg: &mut web::ServiceConfig| {
- cfg.data(things.clone())
- .service(index);
+ cfg.data(things.clone()).service(index);
})
}