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

Code before:
        // When retrieving from /settings, the settings prefix is implied, so we add it if it
        // wasn't given.
        let with_prefix = format!("settings.{}", prefix);
        if !prefix.starts_with("settings") {
            prefix = &with_prefix;
        }
        controller::get_settings_prefix(&*datastore, prefix, &Committed::Live)
            .map(|opt| opt.unwrap_or_default())
    } else {
        controller::get_settings(&*datastore, &Committed::Live)
    }?;

    Ok(SettingsResponse(settings))
}

/// Prints a more specific message before exiting through usage().
fn usage_msg<S: AsRef<str>>(msg: S) -> ! {
    eprintln!("{}\n", msg.as_ref());
    process::exit(2);
}

/// Constructs a Key Pair Map from the
fn construct_key_pair_map(settings_key_pair_vec: &Vec<String>) -> HashMap<Key, String> {
    let mut settings_key_pair_map = HashMap::new();
    for settings_key_pair in settings_key_pair_vec {
        let (raw_key, value) = settings_key_pair.split_once('=').unwrap();

        let mut key = Key::new(KeyType::Data, raw_key).unwrap_or_else(|_| {
            usage_msg(format!("Given key '{}' is not a valid format", raw_key))
        });

        // Add "settings" prefix if the user didn't give a known prefix, to ease usage
        let key_prefix = &key.segments()[0];
        if key_prefix != "settings" {
            let mut segments = key.segments().clone();
            segments.insert(0, "settings".to_string());
            key = Key::from_segments(KeyType::Data, &segments)
                .expect("Adding prefix to key resulted in invalid key?!");
        }

        settings_key_pair_map.insert(key, value.to_string());
    }
    trace!("Key=Value map: {:#?}", settings_key_pair_map);
    settings_key_pair_map
}

/// Apply the requested settings to the pending data store
async fn patch_settings(
    settings: web::Json<Settings>,
    query: web::Query<HashMap<String, String>>,
    data: web::Data<SharedData>,

Review comment (suggestion):
can't `unwrap` here (or in any of the transplanted code) or we end up panicking the server for bad input

Code after:
    let settings_model = datastore::deserialization::from_map(&settings_key_pair_map)
        .context(error::DeserializeMapSnafu)?;
    controller::set_settings(&mut *datastore, &settings_model, transaction)?;
    Ok(HttpResponse::NoContent().finish()) // 204
}

async fn get_transaction_list(data: web::Data<SharedData>) -> Result<TransactionListResponse> {
    let datastore = data.ds.read().ok().context(error::DataStoreLockSnafu)?;
    let data = controller::list_transactions(&*datastore)?;
    Ok(TransactionListResponse(data))
}

/// Get any pending settings in the given transaction, or the "default" transaction if unspecified.
async fn get_transaction(
    query: web::Query<HashMap<String, String>>,
    data: web::Data<SharedData>,
) -> Result<SettingsResponse> {
    let transaction = transaction_name(&query);
    let datastore = data.ds.read().ok().context(error::DataStoreLockSnafu)?;
    let data = controller::get_transaction(&*datastore, transaction)?;
    Ok(SettingsResponse(data))
}

/// Delete the given transaction, or the "default" transaction if unspecified.
async fn delete_transaction(
    query: web::Query<HashMap<String, String>>,
    data: web::Data<SharedData>,
) -> Result<ChangedKeysResponse> {
    let transaction = transaction_name(&query);
    let mut datastore = data.ds.write().ok().context(error::DataStoreLockSnafu)?;
    let deleted = controller::delete_transaction(&mut *datastore, transaction)?;
    Ok(ChangedKeysResponse(deleted))
}

/// Save settings changes from the given transaction, or the "default" transaction if unspecified,
/// to the live data store.  Returns the list of changed keys.
async fn commit_transaction(
    query: web::Query<HashMap<String, String>>,
    data: web::Data<SharedData>,
) -> Result<ChangedKeysResponse> {
    let transaction = transaction_name(&query);
    let mut datastore = data.ds.write().ok().context(error::DataStoreLockSnafu)?;

    let changes = controller::commit_transaction(&mut *datastore, transaction)?;

    if changes.is_empty() {
        return error::CommitWithNoPendingSnafu.fail();
    }

    Ok(ChangedKeysResponse(changes))
}