1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use {
    anyhow::{Context, Result},
    fidl_fuchsia_examples_services as fexamples,
    fuchsia_component::server::ServiceFs,
    futures::lock::Mutex,
    futures::prelude::*,
    std::{env, sync::Arc},
    tracing::*,
};

struct Account {
    /// Account owner's name.
    name: String,
    /// Account balance in cents.
    balance: i64,
}

#[fuchsia::main]
async fn main() {
    let mut args = env::args().skip(1);
    let name = args.next().expect("name arg");
    let balance = args.next().expect("balance arg").parse().expect("balance must be a number");
    info!(%name, %balance, "starting bank account provider");
    let account = Arc::new(Mutex::new(Account { name, balance }));

    let mut fs = ServiceFs::new();
    fs.dir("svc").add_unified_service(|req: fexamples::BankAccountRequest| req);
    fs.take_and_serve_directory_handle().expect("failed to serve outgoing namespace");
    fs.for_each_concurrent(None, move |request| {
        let account = account.clone();
        async move {
            match handle_request(account.clone(), request).await {
                Ok(()) => {}
                Err(err) => error!(%err, "failed to serve BankAccount request"),
            }
        }
    })
    .await;
}

async fn handle_request(
    account: Arc<Mutex<Account>>,
    request: fexamples::BankAccountRequest,
) -> Result<()> {
    match request {
        fexamples::BankAccountRequest::ReadOnly(mut stream) => {
            while let Some(request) =
                stream.try_next().await.context("failed to get next read-only request")?
            {
                let account = account.lock().await;
                match request {
                    fexamples::ReadOnlyAccountRequest::GetOwner { responder } => {
                        responder.send(&account.name).context("failed to send get_owner reply")?;
                    }
                    fexamples::ReadOnlyAccountRequest::GetBalance { responder } => {
                        responder
                            .send(account.balance)
                            .context("failed to send get_balance reply")?;
                    }
                }
            }
        }
        fexamples::BankAccountRequest::ReadWrite(mut stream) => {
            while let Some(request) =
                stream.try_next().await.context("failed to get next read-write request")?
            {
                let mut account = account.lock().await;
                match request {
                    fexamples::ReadWriteAccountRequest::GetOwner { responder } => {
                        responder.send(&account.name).context("failed to send get_owner reply")?;
                    }
                    fexamples::ReadWriteAccountRequest::GetBalance { responder } => {
                        responder
                            .send(account.balance)
                            .context("failed to send get_balance reply")?;
                    }
                    fexamples::ReadWriteAccountRequest::Debit { amount, responder } => {
                        let success = if account.balance >= amount {
                            account.balance -= amount;
                            true
                        } else {
                            false
                        };
                        info!(account = %account.name, balance = %account.balance, "balance updated");
                        responder.send(success).context("failed to send debit reply")?;
                    }
                    fexamples::ReadWriteAccountRequest::Credit { amount, responder } => {
                        account.balance += amount;
                        info!(account = %account.name, balance = %account.balance, "balance updated");
                        responder.send().context("failed to send credit reply")?;
                    }
                }
            }
        }
    }
    Ok(())
}