harn-vm 0.10.42

Async bytecode virtual machine for the Harn programming language
Documentation
use std::collections::BTreeMap;

use chrono::Utc;

use super::client::HttpRequestParts;
use super::{string_option, vm_error};
use crate::value::{VmDictExt, VmError, VmValue};

pub(super) async fn apply(
    options: &crate::value::DictMap,
    final_url: &str,
    parts: &mut HttpRequestParts,
) -> Result<(), VmError> {
    let Some(value) = options.get("aws_sigv4") else {
        return Ok(());
    };
    let VmValue::Dict(spec) = value else {
        return Err(vm_error("http: aws_sigv4 must be a dict"));
    };
    let service = string_option(spec, "service")
        .filter(|value| !value.trim().is_empty())
        .ok_or_else(|| vm_error("http: aws_sigv4.service is required"))?;
    let region_override = string_option(spec, "region");
    let region =
        crate::llm::providers::bedrock::resolve_live_region(region_override.as_deref()).await?;
    let credentials =
        crate::llm::providers::bedrock::resolve_aws_credentials(region.as_str()).await?;
    let headers = parts
        .headers
        .iter()
        .map(|(name, value)| {
            value
                .to_str()
                .map(|value| (name.as_str().to_string(), value.to_string()))
                .map_err(|_| vm_error(format!("http: header {} is not valid text", name.as_str())))
        })
        .collect::<Result<BTreeMap<_, _>, _>>()?;
    let signed = crate::aws_sigv4::sign(crate::aws_sigv4::AwsSigV4Input {
        credentials: &credentials,
        method: parts.method.as_str(),
        url: final_url,
        service: service.as_str(),
        region: region.as_str(),
        headers: &headers,
        body: parts.body.as_deref().unwrap_or_default().as_bytes(),
        timestamp: Utc::now(),
    })
    .map_err(|error| vm_error(format!("http: aws_sigv4 signing failed: {error}")))?;

    let mut signed_headers = reqwest::header::HeaderMap::new();
    let mut recorded_headers = crate::value::DictMap::new();
    for (name, value) in signed.headers {
        let header_name = reqwest::header::HeaderName::from_bytes(name.as_bytes())
            .map_err(|_| vm_error(format!("http: signer returned invalid header name {name}")))?;
        let header_value = reqwest::header::HeaderValue::from_str(value.as_str())
            .map_err(|_| vm_error(format!("http: signer returned invalid header {name}")))?;
        recorded_headers.put_str(header_name.as_str(), value.as_str());
        signed_headers.insert(header_name, header_value);
    }
    parts.headers = signed_headers;
    parts.recorded_headers = recorded_headers;
    Ok(())
}