matomo-rs 0.1.0

Async client for the Matomo Reporting API, focused on data export and migration
Documentation
use crate::params::{IdSite, Limit, Period, Segment};

/// A reusable, standalone bag of API call parameters.
///
/// It owns the `(key, value)` form fields for a single Matomo API call,
/// excluding `module`/`format`/auth which the client injects. Serializing it
/// to its key-value pairs is reused both by the live dispatch path and by
/// `API.getBulkRequest` composition.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Params {
    fields: Vec<(String, String)>,
}

impl Params {
    pub fn new() -> Self {
        Params::default()
    }

    /// Set an arbitrary param. Replaces any existing value for `key`.
    pub fn set(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
        let key = key.into();
        self.fields.retain(|(k, _)| k != &key);
        self.fields.push((key, value.into()));
        self
    }

    pub fn id_site(self, id_site: impl Into<IdSite>) -> Self {
        self.set("idSite", id_site.into().to_param())
    }

    pub fn period(self, period: Period) -> Self {
        let (p, date) = period.to_params();
        self.set("period", p).set("date", date)
    }

    pub fn segment(self, segment: impl Into<Segment>) -> Self {
        self.set("segment", segment.into().as_str().to_string())
    }

    pub fn limit(self, limit: Limit) -> Self {
        self.set("filter_limit", limit.to_param())
    }

    pub fn offset(self, offset: u32) -> Self {
        self.set("filter_offset", offset.to_string())
    }

    /// The raw form fields for this call (without module/format/auth).
    pub fn fields(&self) -> &[(String, String)] {
        &self.fields
    }

    /// Encode as a query string for embedding inside `API.getBulkRequest`'s
    /// `urls[]` parameters (e.g. `method=Foo.bar&idSite=1`).
    pub fn to_bulk_query(&self, method: &str) -> String {
        let mut pairs: Vec<(&str, &str)> = vec![("method", method)];
        for (k, v) in &self.fields {
            pairs.push((k.as_str(), v.as_str()));
        }
        let encoded: Vec<String> = pairs
            .iter()
            .map(|(k, v)| format!("{}={}", urlencode(k), urlencode(v)))
            .collect();
        encoded.join("&")
    }
}

fn urlencode(s: &str) -> String {
    url::form_urlencoded::byte_serialize(s.as_bytes()).collect()
}