Skip to main content

pingap_plugin/
lib.rs

1// Copyright 2024-2025 Tree xie.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use humantime::parse_duration;
16use pingap_config::PluginConf;
17use pingap_core::PluginStep;
18use snafu::Snafu;
19use std::fmt::Write;
20use std::str::FromStr;
21use std::time::Duration;
22
23#[derive(Debug, Snafu)]
24pub enum Error {
25    #[snafu(display("Plugin {category} invalid, message: {message}"))]
26    Invalid { category: String, message: String },
27    #[snafu(display("Plugin {category} not found"))]
28    NotFound { category: String },
29    #[snafu(display("Plugin {category}, base64 decode error {source}"))]
30    Base64Decode {
31        category: String,
32        source: base64::DecodeError,
33    },
34    #[snafu(display("Plugin {category}, exceed limit {value}/{max}"))]
35    Exceed {
36        category: String,
37        max: f64,
38        value: f64,
39    },
40    #[snafu(display("Plugin {category}, regex error {source}"))]
41    Regex {
42        category: String,
43        source: Box<fancy_regex::Error>,
44    },
45    #[snafu(display("Plugin {category}, base64 decode error {source}"))]
46    ParseDuration {
47        category: String,
48        source: humantime::DurationError,
49    },
50}
51
52/// Helper functions for accessing plugin configuration values
53pub fn get_str_conf(value: &PluginConf, key: &str) -> String {
54    value
55        .get(key)
56        .and_then(|v| v.as_str())
57        .unwrap_or("")
58        .to_string()
59}
60
61/// Helper functions for accessing plugin configuration values
62pub fn get_duration_conf(value: &PluginConf, key: &str) -> Option<Duration> {
63    value
64        .get(key)
65        .and_then(|v| v.as_str())
66        .and_then(|s| parse_duration(s).ok())
67}
68
69pub(crate) fn get_str_slice_conf(value: &PluginConf, key: &str) -> Vec<String> {
70    value
71        .get(key)
72        .and_then(|v| v.as_array())
73        .map(|arr| {
74            arr.iter()
75                .filter_map(|item| item.as_str())
76                .map(String::from) // same as .map(|s| s.to_string())
77                .collect()
78        })
79        .unwrap_or_default()
80}
81
82pub(crate) fn get_bool_conf(value: &PluginConf, key: &str) -> bool {
83    value.get(key).and_then(|v| v.as_bool()).unwrap_or(false)
84}
85
86pub fn get_int_conf(value: &PluginConf, key: &str) -> i64 {
87    get_int_conf_or_default(value, key, 0)
88}
89
90pub fn get_int_conf_or_default(
91    value: &PluginConf,
92    key: &str,
93    default_value: i64,
94) -> i64 {
95    value
96        .get(key)
97        .and_then(|v| v.as_integer()) // assume PluginConf value can be converted to i64
98        .unwrap_or(default_value)
99}
100
101pub(crate) fn get_step_conf(
102    value: &PluginConf,
103    default_value: PluginStep,
104) -> PluginStep {
105    value
106        .get("step")
107        .and_then(|v| v.as_str())
108        .and_then(|s| PluginStep::from_str(s).ok())
109        .unwrap_or(default_value)
110}
111
112/// Generates a unique hash key for a plugin configuration to detect changes.
113///
114/// # Arguments
115/// * `conf` - The plugin configuration to hash
116///
117/// # Returns
118/// A string containing the CRC32 hash of the sorted configuration key-value pairs
119pub fn get_hash_key(conf: &PluginConf) -> String {
120    let mut items: Vec<_> = conf.iter().collect();
121    // sort by key
122    items.sort_unstable_by_key(|(k, _)| *k);
123
124    // pre-allocate capacity to reduce subsequent memory reallocation.
125    let mut buf = String::with_capacity(256);
126    for (i, (key, value)) in items.iter().enumerate() {
127        if i > 0 {
128            buf.push('\n');
129        }
130        // use write! macro to write the formatted string directly into the buffer, avoid format! to produce temporary String.
131        // because writing to String will not fail, so it can be safely.
132        let _ = write!(&mut buf, "{key}:{value}");
133    }
134
135    let hash = crc32fast::hash(buf.as_bytes());
136    format!("{hash:X}")
137}
138
139mod accept_encoding;
140mod basic_auth;
141mod cache;
142mod combined_auth;
143mod compression;
144mod cors;
145mod csrf;
146mod directory;
147mod ip_restriction;
148mod jwt;
149mod key_auth;
150mod limit;
151mod mock;
152mod ping;
153mod redirect;
154mod referer_restriction;
155mod request_id;
156mod response_headers;
157mod sub_filter;
158mod traffic_splitting;
159mod ua_restriction;
160
161mod plugin;
162
163pub use plugin::get_plugin_factory;