1pub mod native;
7pub mod ports;
8pub mod resources;
9pub mod script;
10pub mod util;
11
12use async_trait::async_trait;
13use std::collections::HashMap;
14use std::sync::Arc;
15
16use crate::context::{Context, GatewayError};
17use ports::PortSpec;
18use resources::PluginResources;
19
20#[derive(Debug)]
22pub struct PluginOutput {
23 pub context: Context,
25 pub port: Option<&'static str>,
28}
29
30impl PluginOutput {
31 pub fn success(context: Context) -> Self {
33 Self {
34 context,
35 port: None,
36 }
37 }
38
39 pub fn on_port(context: Context, port: &'static str) -> Self {
41 Self {
42 context,
43 port: Some(port),
44 }
45 }
46}
47
48pub type PluginResult = Result<PluginOutput, PluginExecutionError>;
50
51#[derive(Debug)]
54pub struct PluginExecutionError {
55 pub context: Context,
58 pub error: GatewayError,
60}
61
62#[async_trait]
73pub trait Plugin: Send + Sync {
74 fn plugin_type(&self) -> &str;
78
79 async fn execute(&self, ctx: Context) -> PluginResult;
102
103 fn reads_response_body(&self) -> bool {
112 true
113 }
114
115 fn cache_target(&self) -> Option<crate::traffic::CacheTarget> {
123 None
124 }
125}
126
127pub const KNOWN_PLUGIN_TYPES: &[&str] = &[
131 "proxy-rewrite",
132 "upstream",
133 "aws-lambda",
134 "azure-functions",
135 "openwhisk",
136 "openfunction",
137 "error-handler",
138 "listener",
139 "client",
140 "cors",
141 "rate-limit",
142 "limit-conn",
143 "api-breaker",
144 "proxy-cache",
145 "limit-count",
146 "proxy-mirror",
147 "ip-restriction",
148 "consumer-restriction",
149 "acl",
150 "attach-consumer-label",
151 "ua-restriction",
152 "referer-restriction",
153 "uri-blocker",
154 "csrf",
155 "request-size-limit",
156 "key-auth",
157 "basic-auth",
158 "jwt-auth",
159 "hmac-auth",
160 "jwe-decrypt",
161 "multi-auth",
162 "forward-auth",
163 "opa",
164 "opentelemetry",
165 "zipkin",
166 "skywalking",
167 "prometheus",
168 "ldap-auth",
169 "wolf-rbac",
170 "cas-auth",
171 "authz-casbin",
172 "authz-keycloak",
173 "authz-casdoor",
174 "openid-connect",
175 "dingtalk-auth",
176 "feishu-auth",
177 "logging",
178 "http-logger",
179 "loki-logger",
180 "splunk-hec-logging",
181 "datadog",
182 "loggly",
183 "tcp-logger",
184 "udp-logger",
185 "syslog",
186 "file-logger",
187 "error-log-logger",
188 "google-cloud-logging",
189 "skywalking-logger",
190 "elasticsearch-logger",
191 "clickhouse-logger",
192 "sls-logger",
193 "tencent-cloud-cls",
194 "lago",
195 "request-id",
196 "real-ip",
197 "redirect",
198 "echo",
199 "fault-injection",
200 "workflow",
201 "condition",
202 "traffic-label",
203 "set-vars",
204 "traffic-split",
205 "mocking",
206 "response-rewrite",
207 "gzip",
208 "brotli",
209 "error-page",
210 "exit-transformer",
211 "data-mask",
212 "request-validation",
213 "body-transformer",
214 "degraphql",
215 "oas-validator",
216 "serverless-pre-function",
217 "serverless-post-function",
218 "script",
219 "store-get",
220 "store-set",
221 "store-delete",
222 "store-incr",
223];
224
225pub fn create_plugin(
235 node_type: &str,
236 config: &HashMap<String, serde_json::Value>,
237 resources: &Arc<PluginResources>,
238) -> Result<Box<dyn Plugin>, String> {
239 match node_type {
240 "proxy-rewrite" => Ok(Box::new(
241 native::proxy_rewrite::ProxyRewritePlugin::from_config(config)?,
242 )),
243 "upstream" => Ok(Box::new(native::upstream::UpstreamPlugin::from_config(
244 config, resources,
245 )?)),
246 "aws-lambda" => Ok(Box::new(native::aws_lambda::AwsLambdaPlugin::from_config(
247 config, resources,
248 )?)),
249 "azure-functions" => Ok(Box::new(
250 native::azure_functions::AzureFunctionsPlugin::from_config(config, resources)?,
251 )),
252 "openwhisk" => Ok(Box::new(native::openwhisk::OpenWhiskPlugin::from_config(
253 config, resources,
254 )?)),
255 "openfunction" => Ok(Box::new(
256 native::openfunction::OpenFunctionPlugin::from_config(config, resources)?,
257 )),
258 "error-handler" => Ok(Box::new(
259 native::error_handler::ErrorHandlerPlugin::from_config(config)?,
260 )),
261 "listener" => Ok(Box::new(native::listener::ListenerPlugin)),
262 "client" => Ok(Box::new(native::client::ClientPlugin)),
263 "cors" => Ok(Box::new(native::cors::CorsPlugin::from_config(config)?)),
264 "rate-limit" => Ok(Box::new(native::rate_limit::RateLimitPlugin::from_config(
265 config,
266 )?)),
267 "limit-conn" => Ok(Box::new(native::limit_conn::LimitConnPlugin::from_config(
268 config, resources,
269 )?)),
270 "api-breaker" => Ok(Box::new(
271 native::api_breaker::ApiBreakerPlugin::from_config(config, resources)?,
272 )),
273 "proxy-cache" => Ok(Box::new(
274 native::proxy_cache::ProxyCachePlugin::from_config(config, resources)?,
275 )),
276 "limit-count" => Ok(Box::new(
277 native::limit_count::LimitCountPlugin::from_config(config, resources)?,
278 )),
279 "proxy-mirror" => Ok(Box::new(
280 native::proxy_mirror::ProxyMirrorPlugin::from_config(config, resources)?,
281 )),
282 "ip-restriction" => Ok(Box::new(
283 native::ip_restriction::IpRestrictionPlugin::from_config(config)?,
284 )),
285 "consumer-restriction" => Ok(Box::new(
286 native::consumer_restriction::ConsumerRestrictionPlugin::from_config(config)?,
287 )),
288 "acl" => Ok(Box::new(native::acl::AclPlugin::from_config(config)?)),
289 "attach-consumer-label" => Ok(Box::new(
290 native::attach_consumer_label::AttachConsumerLabelPlugin::from_config(config)?,
291 )),
292 "ua-restriction" => Ok(Box::new(
293 native::ua_restriction::UaRestrictionPlugin::from_config(config)?,
294 )),
295 "referer-restriction" => Ok(Box::new(
296 native::referer_restriction::RefererRestrictionPlugin::from_config(config)?,
297 )),
298 "uri-blocker" => Ok(Box::new(
299 native::uri_blocker::UriBlockerPlugin::from_config(config)?,
300 )),
301 "csrf" => Ok(Box::new(native::csrf::CsrfPlugin::from_config(config)?)),
302 "request-size-limit" => Ok(Box::new(
303 native::request_size_limit::RequestSizeLimitPlugin::from_config(config)?,
304 )),
305 "key-auth" => Ok(Box::new(native::key_auth::KeyAuthPlugin::from_config(
306 config, resources,
307 )?)),
308 "basic-auth" => Ok(Box::new(native::basic_auth::BasicAuthPlugin::from_config(
309 config, resources,
310 )?)),
311 "jwt-auth" => Ok(Box::new(native::jwt_auth::JwtAuthPlugin::from_config(
312 config, resources,
313 )?)),
314 "hmac-auth" => Ok(Box::new(native::hmac_auth::HmacAuthPlugin::from_config(
315 config, resources,
316 )?)),
317 "jwe-decrypt" => Ok(Box::new(
318 native::jwe_decrypt::JweDecryptPlugin::from_config(config, resources)?,
319 )),
320 "multi-auth" => Ok(Box::new(native::multi_auth::MultiAuthPlugin::from_config(
321 config, resources,
322 )?)),
323 "forward-auth" => Ok(Box::new(
324 native::forward_auth::ForwardAuthPlugin::from_config(config, resources)?,
325 )),
326 "opa" => Ok(Box::new(native::opa::OpaPlugin::from_config(
327 config, resources,
328 )?)),
329 "opentelemetry" => Ok(Box::new(
330 native::opentelemetry::OpenTelemetryPlugin::from_config(config, resources)?,
331 )),
332 "zipkin" => Ok(Box::new(native::zipkin::ZipkinPlugin::from_config(
333 config, resources,
334 )?)),
335 "skywalking" => Ok(Box::new(native::skywalking::SkywalkingPlugin::from_config(
336 config, resources,
337 )?)),
338 "prometheus" => Ok(Box::new(native::prometheus::PrometheusPlugin::from_config(
339 config, resources,
340 )?)),
341 "ldap-auth" => Ok(Box::new(native::ldap_auth::LdapAuthPlugin::from_config(
342 config, resources,
343 )?)),
344 "wolf-rbac" => Ok(Box::new(native::wolf_rbac::WolfRbacPlugin::from_config(
345 config, resources,
346 )?)),
347 "cas-auth" => Ok(Box::new(native::cas_auth::CasAuthPlugin::from_config(
348 config, resources,
349 )?)),
350 "authz-casbin" => Ok(Box::new(
351 native::authz_casbin::AuthzCasbinPlugin::from_config(config, resources)?,
352 )),
353 "authz-keycloak" => Ok(Box::new(
354 native::authz_keycloak::AuthzKeycloakPlugin::from_config(config, resources)?,
355 )),
356 "authz-casdoor" => Ok(Box::new(
357 native::authz_casdoor::AuthzCasdoorPlugin::from_config(config, resources)?,
358 )),
359 "openid-connect" => Ok(Box::new(
360 native::openid_connect::OpenidConnectPlugin::from_config(config, resources)?,
361 )),
362 "dingtalk-auth" => Ok(Box::new(
363 native::dingtalk_auth::DingtalkAuthPlugin::from_config(config, resources)?,
364 )),
365 "feishu-auth" => Ok(Box::new(
366 native::feishu_auth::FeishuAuthPlugin::from_config(config, resources)?,
367 )),
368 "logging" => Ok(Box::new(native::logging::LoggingPlugin::from_config(
369 config,
370 )?)),
371 "http-logger" => Ok(Box::new(
372 native::http_logger::HttpLoggerPlugin::from_config(config, resources)?,
373 )),
374 "loki-logger" => Ok(Box::new(
375 native::loki_logger::LokiLoggerPlugin::from_config(config, resources)?,
376 )),
377 "splunk-hec-logging" => Ok(Box::new(
378 native::splunk_hec_logging::SplunkHecLoggingPlugin::from_config(config, resources)?,
379 )),
380 "datadog" => Ok(Box::new(native::datadog::DatadogPlugin::from_config(
381 config, resources,
382 )?)),
383 "loggly" => Ok(Box::new(native::loggly::LogglyPlugin::from_config(
384 config, resources,
385 )?)),
386 "tcp-logger" => Ok(Box::new(native::tcp_logger::TcpLoggerPlugin::from_config(
387 config,
388 )?)),
389 "udp-logger" => Ok(Box::new(native::udp_logger::UdpLoggerPlugin::from_config(
390 config,
391 )?)),
392 "syslog" => Ok(Box::new(native::syslog::SyslogPlugin::from_config(config)?)),
393 "file-logger" => Ok(Box::new(
394 native::file_logger::FileLoggerPlugin::from_config(config)?,
395 )),
396 "error-log-logger" => Ok(Box::new(
397 native::error_log_logger::ErrorLogLoggerPlugin::from_config(config)?,
398 )),
399 "google-cloud-logging" => Ok(Box::new(
400 native::google_cloud_logging::GoogleCloudLoggingPlugin::from_config(config, resources)?,
401 )),
402 "skywalking-logger" => Ok(Box::new(
403 native::skywalking_logger::SkywalkingLoggerPlugin::from_config(config, resources)?,
404 )),
405 "elasticsearch-logger" => Ok(Box::new(
406 native::elasticsearch_logger::ElasticsearchLoggerPlugin::from_config(
407 config, resources,
408 )?,
409 )),
410 "clickhouse-logger" => Ok(Box::new(
411 native::clickhouse_logger::ClickhouseLoggerPlugin::from_config(config, resources)?,
412 )),
413 "sls-logger" => Ok(Box::new(native::sls_logger::SlsLoggerPlugin::from_config(
414 config, resources,
415 )?)),
416 "tencent-cloud-cls" => Ok(Box::new(
417 native::tencent_cloud_cls::TencentCloudClsPlugin::from_config(config, resources)?,
418 )),
419 "lago" => Ok(Box::new(native::lago::LagoPlugin::from_config(
420 config, resources,
421 )?)),
422 "request-id" => Ok(Box::new(native::request_id::RequestIdPlugin::from_config(
423 config,
424 )?)),
425 "real-ip" => Ok(Box::new(native::real_ip::RealIpPlugin::from_config(
426 config,
427 )?)),
428 "redirect" => Ok(Box::new(native::redirect::RedirectPlugin::from_config(
429 config,
430 )?)),
431 "echo" => Ok(Box::new(native::echo::EchoPlugin::from_config(config)?)),
432 "fault-injection" => Ok(Box::new(
433 native::fault_injection::FaultInjectionPlugin::from_config(config)?,
434 )),
435 "workflow" => Ok(Box::new(native::workflow::WorkflowPlugin::from_config(
436 config, resources,
437 )?)),
438 "traffic-label" => Ok(Box::new(
439 native::traffic_label::TrafficLabelPlugin::from_config(config)?,
440 )),
441 "set-vars" => Ok(Box::new(native::set_vars::SetVarsPlugin::from_config(
442 config,
443 )?)),
444 "traffic-split" => Ok(Box::new(
445 native::traffic_split::TrafficSplitPlugin::from_config(config, resources)?,
446 )),
447 "mocking" => Ok(Box::new(native::mocking::MockingPlugin::from_config(
448 config,
449 )?)),
450 "response-rewrite" => Ok(Box::new(
451 native::response_rewrite::ResponseRewritePlugin::from_config(config)?,
452 )),
453 "gzip" => Ok(Box::new(native::gzip::GzipPlugin::from_config(config)?)),
454 "brotli" => Ok(Box::new(native::brotli::BrotliPlugin::from_config(config)?)),
455 "error-page" => Ok(Box::new(native::error_page::ErrorPagePlugin::from_config(
456 config,
457 )?)),
458 "exit-transformer" => Ok(Box::new(
459 native::exit_transformer::ExitTransformerPlugin::from_config(config)?,
460 )),
461 "data-mask" => Ok(Box::new(native::data_mask::DataMaskPlugin::from_config(
462 config,
463 )?)),
464 "request-validation" => Ok(Box::new(
465 native::request_validation::RequestValidationPlugin::from_config(config)?,
466 )),
467 "condition" => Ok(Box::new(native::condition::ConditionPlugin::from_config(
468 config,
469 )?)),
470 "body-transformer" => Ok(Box::new(
471 native::body_transformer::BodyTransformerPlugin::from_config(config)?,
472 )),
473 "degraphql" => Ok(Box::new(native::degraphql::DegraphqlPlugin::from_config(
474 config,
475 )?)),
476 "oas-validator" => Ok(Box::new(
477 native::oas_validator::OasValidatorPlugin::from_config(config)?,
478 )),
479 "serverless-pre-function" => Ok(Box::new(
480 native::serverless_pre_function::ServerlessPreFunctionPlugin::from_config(config)?,
481 )),
482 "serverless-post-function" => Ok(Box::new(
483 native::serverless_post_function::ServerlessPostFunctionPlugin::from_config(config)?,
484 )),
485 "script" => Ok(Box::new(script::ScriptPlugin::from_config(config)?)),
486 "store-get" => Ok(Box::new(native::store_get::StoreGetPlugin::from_config(
487 config, resources,
488 )?)),
489 "store-set" => Ok(Box::new(native::store_set::StoreSetPlugin::from_config(
490 config, resources,
491 )?)),
492 "store-delete" => Ok(Box::new(
493 native::store_delete::StoreDeletePlugin::from_config(config, resources)?,
494 )),
495 "store-incr" => Ok(Box::new(native::store_incr::StoreIncrPlugin::from_config(
496 config, resources,
497 )?)),
498 _ => Err(format!("Unknown plugin type: {}", node_type)),
499 }
500}
501
502pub fn port_spec(plugin_type: &str) -> Option<&'static PortSpec> {
508 match plugin_type {
509 "listener" => Some(&ports::LISTENER_SPEC),
510 "client" => Some(&ports::CLIENT_SPEC),
511 "cors" => Some(&ports::CORS_SPEC),
512 "redirect" => Some(&ports::REDIRECT_SPEC),
513 "fault-injection" => Some(&ports::FAULT_INJECTION_SPEC),
514 "script" => Some(&ports::SCRIPT_SPEC),
515 "key-auth" | "basic-auth" | "jwt-auth" | "hmac-auth" | "jwe-decrypt" | "multi-auth"
516 | "ldap-auth" | "forward-auth" | "opa" | "wolf-rbac" => Some(&ports::AUTH_SPEC),
517 "cas-auth" | "openid-connect" | "authz-casdoor" | "dingtalk-auth" | "feishu-auth" => {
518 Some(&ports::INTERACTIVE_AUTH_SPEC)
519 }
520 "authz-casbin" | "authz-keycloak" => Some(&ports::AUTH_SPEC),
521 "acl"
522 | "ip-restriction"
523 | "ua-restriction"
524 | "referer-restriction"
525 | "consumer-restriction"
526 | "uri-blocker"
527 | "csrf"
528 | "request-size-limit"
529 | "request-validation"
530 | "oas-validator" => Some(&ports::DENY_SPEC),
531 "rate-limit" | "limit-conn" | "limit-count" => Some(&ports::LIMIT_SPEC),
532 "api-breaker" => Some(&ports::BREAKER_SPEC),
533 "workflow" => Some(&ports::WORKFLOW_SPEC),
534 "condition" => Some(&ports::CONDITION_SPEC),
535 "traffic-split" => Some(&ports::TRAFFIC_SPLIT_SPEC),
536 "proxy-cache" => Some(&ports::PROXY_CACHE_SPEC),
537 "store-get" => Some(&ports::STORE_GET_SPEC),
538 _ if KNOWN_PLUGIN_TYPES.contains(&plugin_type) => Some(&ports::DEFAULT_SPEC),
539 _ => None,
540 }
541}
542
543#[cfg(test)]
544mod tests {
545 use super::*;
546
547 #[test]
550 fn test_known_plugin_types_matches_factory() {
551 let factory: std::collections::BTreeSet<String> = include_str!("mod.rs")
552 .lines()
553 .filter_map(|line| {
554 let line = line.trim();
555 let rest = line.strip_prefix('"')?;
556 let (name, tail) = rest.split_once('"')?;
557 tail.trim_start()
558 .starts_with("=>")
559 .then(|| name.to_string())
560 })
561 .collect();
562 let listed: std::collections::BTreeSet<String> =
563 KNOWN_PLUGIN_TYPES.iter().map(|s| s.to_string()).collect();
564 assert_eq!(
565 listed, factory,
566 "KNOWN_PLUGIN_TYPES drifted from create_plugin"
567 );
568 }
569}