pdk-script 1.10.1-alpha.0

PDK Script
Documentation
// Copyright (c) 2026, Salesforce, Inc.,
// All rights reserved.
// For full license text, see the LICENSE.txt file

use crate::bindings::attributes::AttributesBindingAdapter;
use crate::constants::{
    ATTRIBUTES, ATTRIBUTES_REFERENCE, HEADERS, HEADERS_REFERENCE, LOCAL_ADDRESS, METHOD,
    QUERY_PARAMS, QUERY_PARAMS_REFERENCE, QUERY_STRING, REMOTE_ADDRESS, REQUEST_PATH, REQUEST_URI,
    SCHEME, STATUS_CODE, VERSION,
};
use crate::context::input::InputContext;
use pel::expression::Symbol;
use pel::runtime::value::Value as PelValue;
use pel::runtime::{Binding as PelBinding, Context, ValueHandler};
use pel::Reference;
use std::cell::{Ref, RefCell};
use std::collections::HashMap;

pub(crate) struct AttributesContext<'a> {
    input_context: &'a InputContext,
    binding: &'a AttributesBindingAdapter<'a>,
    headers: HeaderValueHandler<'a>,
    query: QueryValueHandler<'a>,
}

impl<'a> AttributesContext<'a> {
    pub fn new(input_context: &'a InputContext, binding: &'a AttributesBindingAdapter<'a>) -> Self {
        let headers = HeaderValueHandler::new(binding);
        let query = QueryValueHandler::new(binding);
        Self {
            input_context,
            binding,
            headers,
            query,
        }
    }
}

impl Context for AttributesContext<'_> {
    fn resolve(&self, symbol: &Symbol) -> PelBinding {
        match self.input_context.attributes() && symbol.as_str() == ATTRIBUTES {
            true => PelBinding::Available(PelValue::reference(ATTRIBUTES_REFERENCE)),
            false => PelBinding::Unknown,
        }
    }

    fn value_handler(&self, reference: Reference) -> Option<&dyn ValueHandler> {
        match reference {
            ATTRIBUTES_REFERENCE => Some(self),
            HEADERS_REFERENCE => Some(&self.headers),
            QUERY_PARAMS_REFERENCE => Some(&self.query),
            _ => None,
        }
    }
}

impl ValueHandler for AttributesContext<'_> {
    fn detach(&self) -> Option<PelValue> {
        let values = [
            (HEADERS, self.headers.detach()),
            (QUERY_PARAMS, self.query.detach()),
            (METHOD, self.binding.method()),
            (REQUEST_PATH, self.binding.path()),
            (REQUEST_URI, self.binding.uri()),
            (REMOTE_ADDRESS, self.binding.remote_address()),
            (LOCAL_ADDRESS, self.binding.local_address()),
            (QUERY_STRING, self.binding.query_string()),
            (SCHEME, self.binding.scheme()),
            (VERSION, self.binding.version()),
            (STATUS_CODE, self.binding.status_code()),
        ]
        .map(|(k, v)| (k.to_string(), v.unwrap_or_else(PelValue::null)));

        Some(PelValue::object(values.into()))
    }

    fn select_by_key(&self, key: &str) -> Option<PelValue> {
        let selection = match key {
            HEADERS => Some(PelValue::reference(HEADERS_REFERENCE)),
            QUERY_PARAMS => Some(PelValue::reference(QUERY_PARAMS_REFERENCE)),
            METHOD => self.binding.method(),
            REQUEST_PATH => self.binding.path(),
            REQUEST_URI => self.binding.uri(),
            REMOTE_ADDRESS => self.binding.remote_address(),
            LOCAL_ADDRESS => self.binding.local_address(),
            QUERY_STRING => self.binding.query_string(),
            SCHEME => self.binding.scheme(),
            VERSION => self.binding.version(),
            STATUS_CODE => self.binding.status_code(),
            _ => None,
        };
        Some(selection.unwrap_or_else(PelValue::null))
    }
}

struct HeaderValueHandler<'a> {
    binding: &'a AttributesBindingAdapter<'a>,
}

struct QueryValueHandler<'a> {
    binding: &'a AttributesBindingAdapter<'a>,
    query: RefCell<Option<HashMap<String, PelValue>>>,
}

impl<'a> QueryValueHandler<'a> {
    pub fn new(binding: &'a AttributesBindingAdapter<'a>) -> Self {
        Self {
            binding,
            query: RefCell::new(None),
        }
    }

    fn query(&self) -> Ref<'_, HashMap<String, PelValue>> {
        if self.query.borrow().is_none() {
            self.query
                .replace(Some(self.binding.extract_query_params()));
        }
        Ref::map(self.query.borrow(), |query| {
            query.as_ref().expect("query parameters are initialized")
        })
    }
}

impl ValueHandler for QueryValueHandler<'_> {
    fn detach(&self) -> Option<PelValue> {
        Some(PelValue::object(self.query().clone()))
    }

    fn select_by_key(&self, key: &str) -> Option<PelValue> {
        self.query().get(key).cloned()
    }
}

impl<'a> HeaderValueHandler<'a> {
    pub fn new(binding: &'a AttributesBindingAdapter<'a>) -> Self {
        Self { binding }
    }
}

impl ValueHandler for HeaderValueHandler<'_> {
    fn detach(&self) -> Option<PelValue> {
        Some(PelValue::object(self.binding.extract_headers()))
    }

    fn select_by_key(&self, key: &str) -> Option<PelValue> {
        self.binding.extract_header(key)
    }
}