Your first application

Every box office starts with a line-up. In this chapter, we'll show tonight's concerts twice: in the terminal for the staff, and on the web for everyone else. We'll keep the line-up in code for now. The container moves it into a file, and Database into PostgreSQL.

The line-up

The line-up is a singleton: an object Margaret creates once and shares with everyone who needs it.

use margaret::framework::macros::singleton;

#[singleton]
pub struct Lineup;

impl Lineup {
    pub fn tonight(&self) -> Vec<&'static str> {
        vec!["The Midnight Owls", "Paper Lanterns", "Velvet Static"]
    }
}

A command for the staff

Commands are singletons too. This one asks for the Lineup in its constructor:

use std::sync::Arc;

use margaret::framework::console::command_outcome::CommandOutcome;
use margaret::framework::macros::console_command;
use margaret::framework::macros::constructor;
use margaret::framework::macros::process;
use margaret::framework::macros::singleton;

use crate::lineup::Lineup;

#[singleton]
#[console_command(name = "lineup", description = "Shows tonight's line-up")]
pub struct ShowLineup {
    lineup: Arc<Lineup>,
}

impl ShowLineup {
    #[constructor]
    pub fn create(lineup: Arc<Lineup>) -> anyhow::Result<Self> {
        Ok(Self { lineup })
    }

    #[process]
    pub fn run(&self) -> anyhow::Result<CommandOutcome> {
        for artist in self.lineup.tonight() {
            println!("{artist}");
        }

        Ok(CommandOutcome::Succeeded)
    }
}

Register the modules as usual, and run it:

pub mod commands;
pub mod lineup;

#[rustfmt::skip]
#[path = "../margaret/mod.rs"]
pub mod margaret;
$ cargo run -- lineup
The Midnight Owls
Paper Lanterns
Velvet Static

Notice what you didn't write: argument parsing, help text, or any code that creates the Lineup. Margaret generated all of it.

A page for everyone else

Now let's put the line-up on the web. A route is a singleton with #[responds_to_http]. The HTTP method is a typed RouteMethod, so a typo in it doesn't compile:

use std::sync::Arc;

use margaret::framework::http::response::Response;
use margaret::framework::macros::constructor;
use margaret::framework::macros::process;
use margaret::framework::macros::responds_to_http;
use margaret::framework::macros::singleton;
use margaret::framework::route_method::route_method::RouteMethod;

use crate::lineup::Lineup;

#[singleton]
#[responds_to_http(method = RouteMethod::Get, path = "/concerts", server = "public")]
pub struct ListConcerts {
    lineup: Arc<Lineup>,
}

impl ListConcerts {
    #[constructor]
    pub fn create(lineup: Arc<Lineup>) -> anyhow::Result<Self> {
        Ok(Self { lineup })
    }

    #[process]
    pub fn respond(&self) -> anyhow::Result<Response> {
        Ok(Response::text(200, self.lineup.tonight().join("\n")))
    }
}

Add the serving crates to Cargo.toml, switch main to Tokio as shown in Installation, and start the server:

[dependencies]
anyhow = "1.0"
clap = "4.5"
margaret = "0.4"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
tokio-util = "0.7"
trzcina = "0.4"
$ cargo run -- serve --public-addr 127.0.0.1:8080 --public-url http://127.0.0.1:8080
$ curl http://127.0.0.1:8080/concerts
The Midnight Owls
Paper Lanterns
Velvet Static

Where did --public-addr and --public-url come from? The route said server = "public", so Margaret gave that server its own address and URL. The URL is an origin, like https://tickets.example.com, and Margaret refuses one with a path.

Break something

Let's make a mistake on purpose. Ask the route for a city parameter that its path doesn't have:

#[route_parameter(from = "city")] city: String,

Run cargo build, and Margaret refuses:

Error: Http { source: Binding { source: RouteParameterNotInPath { subject: "responder 'crate::routes::public::list_concerts::ListConcerts'", parameter: "city", path: "/concerts" } } }

That's Margaret's promise: mistakes in how your application fits together show up while you build, long before anyone buys a ticket.

Next steps

Before moving on, How Margaret works shows what Margaret generated for these two files. Then the container takes the line-up out of the code.