Introducing the documentation, Shichao's style.

Shichao is one of those compulsive documentarians, but unlike myself
he has a much more disciplined style.  I intend to try and match it
during these tutorials.
This commit is contained in:
Elf M. Sternberg 2023-03-20 17:40:43 -07:00
parent 87efc8ee7c
commit 8b1fbec3b2
3 changed files with 271 additions and 0 deletions

View File

@ -0,0 +1,49 @@
+++
title = "Installing Rust"
date = 2023-03-20T17:37:40Z
weight = 1
+++
Since this book is about learning Rust, primarily in a microservices
environment, this chapter focuses on installing Rust and describing the tools
available to the developer.
The easiest way to install Rust is to install the [Rustup](https://rustup.rs/)
tool. It is one of those blind-trust-in-the-safety-of-the-toolchain things. For
Linux and Mac users, the command is a shell script that installs to a user's
local account:
```
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
```
Once installed, you can install Rust itself:
```
$ rustup install toolchain stable
```
You should now have Rust compiler and the Rust build and packaging tool, known
as Cargo:
```
$ rustc --version
rustc 1.68.0 (2c8cc3432 2023-03-06)
$ cargo --version
cargo 1.68.0 (115f34552 2023-02-26)
```
I also installed the following tools:
```
$ rustup component add clippy rust-src rust-docs
$ cargo install rustfmt rust-analyzer
```
- clippy: A powerful linter that provides useful advice above and beyond the
compiler's basic error checking.
- rustfmt: A formatting tool that provides a common format for most developers
- rust-analyzer: For your IDE, rust-analyzer provides the LSP (Language Server
Protocol) for Rust, giving you code completion, on-the-fly error definition,
and other luxuries.

View File

@ -0,0 +1,202 @@
+++
title = "Hello World, You Need Testing"
date = 2023-03-20T17:38:12Z
weight = 2
+++
Zero-to-Production's project is writing a web service that signs people up for
an email newsletter. The first task in the book is to set up a "Hello World!"
application server.
The book uses the [Actix-web](https://actix.rs/) web framework, but I've chosen
to implement it using [Axum](https://github.com/tokio-rs/axum) server, the
default server provided by the [Tokio](https://github.com/tokio-rs/tokio)
asynchronous runtime.
Although the book is only two years old, it is already out-of-date with respect
to some commands. `cargo add` is now provided by default. The following
commands installed the tools I'll be using:
```
cargo add --features tokio/full --features hyper/full tokio hyper \
axum tracing tracing-subscriber
```
- axum: The web server framework for Tokio.
- tokio: The Rust asynchronous runtime. Has single-threaded (select) and
multi-threaded variants.
- [hyper](https://hyper.rs/): An HTTPS request/response library, used for testing.
- [tracing](https://crates.io/crates/tracing): A debugging library that works
with Tokio.
We start by defining the core services. In the book, they're a greeter ("Hello,
World"), a greeter with a parameter ("Hello, {name}"), and a health check
(returns a HTTP 200 Code, but no body). Actix-web hands a generic Request and
expects a generic request, but Axum is more straightforward, providing
`IntoResponse` handlers for most of the basic Rust types, as well as some for
formats via Serde, Rust's standard serializing/deserializing library for
converting data from one format to another.
All of these go into `src/lib.rs`:
```
async fn health_check() -> impl IntoResponse {
(StatusCode::OK, ())
}
async fn anon_greet() -> &'static str {
"Hello World!\n"
}
async fn greet(Path(name): Path<String>) -> impl IntoResponse {
let greeting = String::from("He's dead, ") + name.as_str();
let greeting = greeting + &String::from("!\n");
(StatusCode::OK, greeting)
}
```
<aside>Axum's documentation says to [avoid using `impl
IntoResponse`](https://docs.rs/axum/latest/axum/response/index.html#regarding-impl-intoresponse)
until you understand how it really works, as it can result in confusing issues
when chaining response handlers, when a handler can return multiple types, or
when a handler can return either a type or a [`Result<T,
E>`](https://doc.rust-lang.org/std/result/), especially one with an error.</aside>
We then define the routes that our server will recognize. This is
straightforward and familiar territory:
```
fn app() -> Router {
Router::new()
.route("/", get(anon_greet))
.route("/:name", get(greet))
.route("/health_check", get(health_check))
}
```
We then define a function to *run* the core server:
```
pub async fn run() {
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
tracing::info!("listening on {}", addr);
axum::Server::bind(&addr)
.serve(app().into_make_service())
.await
.unwrap()
}
```
And finally, in a file named `src/main.rs`, we instantiate the server:
```
use ztp::run;
#[tokio::main]
async fn main() {
run().await
}
```
To make this "work," we need to define what `ztp` means, and make a distinction
between the library and the CLI program.
In the project root's `Cargo.toml` file, the first three sections are needed to
define these relationships:
```
[package]
name = "ztp"
version = "0.1.0"
edition = "2021"
[lib]
path = "src/lib.rs"
[[bin]]
path = "src/main.rs"
name = "ztp"
```
It is the `[package.name]` feature that defines how the `use` statement in
`main.rs` will find the library. The `[[bin]]` clause defines the name of the
binary when it is generated. <aside>The double brackets around the `[[bin]]`
clauses is there to emphasize to the TOML parser that there can be more than one
binary. There can be only one library per package, but it is possible for a Rust
project to have more than one package, called "crates," per project. </aside>
This project should now be runnable. In one window, type:
```
$ cargo run
```
And in another, type and see the replies:
```
$ curl http://localhost:3000/
Hello, World!
$ curl http://localhost:3000/Jim
He's dead, Jim!
$ curl -v http://localhost:3000/health_check
> GET /health_check HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/7.81.0
> Accept: */*
< HTTP/1.1 200 OK
< content-length: 0
< date: Tue, 21 Mar 2023 00:16:43 GMT
```
In the last command, the *verbose* flag shows us what we sent to the server, and
what came back. We expected a "200 OK" flag and a zero-length body, and that's
what we got.
## Testing
In order to unit-test a web server, we must spawn a copy of it in order to
exercise its functions. We'll use Tokio's `spawn` function to create a new
server, use hyper to request data from the server, and finally Rust's own native
test asserts to check that we got what we expected.
```
#[cfg(test)]
mod tests {
use super::*;
use axum::{
body::Body,
http::{Request, StatusCode},
};
use std::net::{SocketAddr, TcpListener};
#[tokio::test]
async fn the_real_deal() {
let listener = TcpListener::bind("127.0.0.1:0".parse::<SocketAddr>()
.unwrap()).unwrap();
let addr = listener.local_addr().unwrap();
tokio::spawn(async move {
axum::Server::from_tcp(listener)
.unwrap()serve(app().into_make_service()).await.unwrap();
});
let response = hyper::Client::new()
.request(
Request::builder().uri(format!("http://{}/", addr))
.body(Body::empty()).unwrap(),
)
.await
.unwrap();
let body = hyper::body::to_bytes(response.into_body()).await.unwrap();
assert_eq!(&body[..], b"Hello World!\n");
}
}
```
One interesting trick to observe in this testing is the port number specified in
the `TcpListener` call. It's zero. When the port is zero, the `TcpListener` will
request from the kernel the first-free-port. Normally, you'd want to know
exactly what port to call the server on, but in this case both ends of the
communication are aware of the port to use and we want to ensure that port isn't
hard-coded and inconveniently already in-use by someone else.

20
docs/_index.md Normal file
View File

@ -0,0 +1,20 @@
+++
title = "Zero to Production in Rust"
date = 2023-03-20T17:33:13Z
[taxonomies]
categories = ["programming language", "rust"]
[extra]
status = "Incomplete"
+++
[Zero to Production in Rust](https://www.zero2prod.com/) by Luca Palmieri is a
book that proposes to teach readers how to write a simple and straightforward
microservice using Rust.
The book was written in 2021 and is, already, somewhat out of date. Where
applicable I have documented the changes I made while working my way through the
book. Those changes include:
- Neglecting CI/CD, since I'm a sole developer
- Developing entirely in a laptop environment
- Using Axum instead of Actix-Web for the server framework