Repository: bottlerocket-os/bottlerocket
File: sources/api/apiserver/src/server/mod.rs (Rust)

Code before:
                .arg("--ready")
                .arg("--no-block")
                .status()
                .context(error::SystemdNotifySnafu)?
                .success(),
            error::SystemdNotifyStatusSnafu
        );
        env::remove_var("NOTIFY_SOCKET");
    } else {
        info!("NOTIFY_SOCKET not set, not calling systemd-notify");
    }
    Ok(())
}

/// We want the key=val form of 'set' to be as simple as possible; we don't want users to have to
/// annotate or structure their input too much just to tell us the data type, but unfortunately
/// knowledge of the data type is required to deserialize with the current datastore ser/de code.
///
/// To simplify usage, we use some heuristics to determine the type of each input.  We try to parse
/// each value as a number and boolean, and if those fail, we assume a string.  (API communication
/// is in JSON form, limiting the set of types; the API doesn't allow arrays or null, and "objects"
/// (maps) are represented natively through our nested tree-like settings structure.)
///
/// If this oes wrong -- for example the user wants a string "42" -- we'll get a deserialization
/// error, and can print a clear error and request the user use JSON input form to handle
/// situations with more complex types.g
///
/// If you have an idea for how to improve deserialization so we don't have to do this, please say!
fn massage_set_input(input_map: HashMap<Key, String>) -> Result<HashMap<Key, String>> {
    // Deserialize the given value into the matching Rust type.  When we find a matching type, we
    // serialize back out to the data store format, which is required to build a Settings object
    // through the data store deserialization code.
    let mut massaged_map = HashMap::with_capacity(input_map.len());
    for (key, in_val) in input_map {
        let serialized = if let Ok(b) = serde_json::from_str::<bool>(&in_val) {
            serialize_scalar(&b).context(error::SerializeSnafu)?
        } else if let Ok(u) = serde_json::from_str::<u64>(&in_val) {
            trace!("Serializing scalar of type u64");
            serialize_scalar(&u).context(error::SerializeSnafu)?
        } else if let Ok(f) = serde_json::from_str::<f64>(&in_val) {
            trace!("Serializing scalar of type f64");
            serialize_scalar(&f).context(error::SerializeSnafu)?
        } else {
            trace!("Serializing scalar of type string");
            // No deserialization, already a string, just serialize
            serialize_scalar(&in_val).context(error::SerializeSnafu)?
        };
        massaged_map.insert(key, serialized);
    }
    trace!("Massaged key=value input: {:#?}", massaged_map);
    Ok(massaged_map)

Review comment (bug):
```suggestion
/// If this goes wrong -- for example the user wants a string "42" -- we'll get a deserialization
/// error, and can print a clear error and request the user use JSON input form to handle
/// situations with more complex types.
```

Code after:
                .arg("--no-block")
                .status()
                .context(error::SystemdNotifySnafu)?
                .success(),
            error::SystemdNotifyStatusSnafu
        );
        env::remove_var("NOTIFY_SOCKET");
    } else {
        info!("NOTIFY_SOCKET not set, not calling systemd-notify");
    }
    Ok(())
}

// Router

/// This is the primary interface of the module.  It defines the server and application that actix
/// spawns for requests.  It creates a shared datastore handle that can be used by handler methods
/// to interface with the controller.
pub async fn serve<P1, P2, P3>(
    socket_path: P1,
    datastore_path: P2,
    threads: usize,
    socket_gid: Option<Gid>,
    exec_socket_path: P3,
) -> Result<()>
where
    P1: AsRef<Path>,
    P2: AsRef<Path>,
    P3: Into<PathBuf>,
{
    // SharedData gives us a convenient way to make data available to handler methods when it
    // doesn't come from the request itself.  It's easier than the ownership tricks required to
    // pass parameters to the handler methods.
    let shared_data = web::Data::new(SharedData {
        ds: sync::RwLock::new(FilesystemDataStore::new(datastore_path)),
        exec_socket_path: exec_socket_path.into(),
    });

    let http_server = HttpServer::new(move || {
        App::new()
            // This makes the data store available to API methods merely by having a Data
            // parameter.
            .app_data(shared_data.clone())
            // Retrieve the full API model; not all data is writable, so we only support GET.
            .route("/", web::get().to(get_model))
            .service(
                web::scope("/settings")
                    .route("", web::get().to(get_settings))
                    .route("", web::patch().to(patch_settings))
                    .route("/keypair", web::patch().to(patch_settings_key_pair)),
            )