nginx_lint_plugin/
lib.rs

1//! Plugin SDK for building nginx-lint WASM plugins
2//!
3//! This crate provides everything needed to create custom lint rules as WASM plugins
4//! for [nginx-lint](https://github.com/walf443/nginx-lint).
5//!
6//! # Getting Started
7//!
8//! 1. Create a library crate with `crate-type = ["cdylib", "rlib"]`
9//! 2. Implement the [`Plugin`] trait
10//! 3. Register with [`export_component_plugin!`]
11//! 4. Build with `cargo build --target wasm32-unknown-unknown --release`
12//!
13//! # Modules
14//!
15//! - [`types`] - Core types: [`Plugin`], [`PluginSpec`], [`LintError`], [`Fix`],
16//!   [`ConfigExt`], [`DirectiveExt`]
17//! - [`helpers`] - Utility functions for common checks (domain names, URLs, etc.)
18//! - [`testing`] - Test runner and builder: [`testing::PluginTestRunner`], [`testing::TestCase`]
19//! - [`native`] - [`native::NativePluginRule`] adapter for running plugins without WASM
20//! - [`prelude`] - Convenient re-exports for `use nginx_lint_plugin::prelude::*`
21//!
22//! # API Versioning
23//!
24//! Plugins declare the API version they use via [`PluginSpec::api_version`].
25//! This allows the host to support multiple output formats for backward compatibility.
26//! [`PluginSpec::new()`] automatically sets the current API version ([`API_VERSION`]).
27//!
28//! # Example
29//!
30//! ```
31//! use nginx_lint_plugin::prelude::*;
32//!
33//! #[derive(Default)]
34//! struct MyRule;
35//!
36//! impl Plugin for MyRule {
37//!     fn spec(&self) -> PluginSpec {
38//!         PluginSpec::new("my-custom-rule", "custom", "My custom lint rule")
39//!             .with_severity("warning")
40//!             .with_why("Explain why this rule matters.")
41//!             .with_bad_example("server {\n    dangerous_directive on;\n}")
42//!             .with_good_example("server {\n    # dangerous_directive removed\n}")
43//!     }
44//!
45//!     fn check(&self, config: &Config, _path: &str) -> Vec<LintError> {
46//!         let mut errors = Vec::new();
47//!         let err = self.spec().error_builder();
48//!
49//!         for ctx in config.all_directives_with_context() {
50//!             if ctx.directive.is("dangerous_directive") {
51//!                 errors.push(
52//!                     err.warning_at("Avoid using dangerous_directive", ctx.directive)
53//!                 );
54//!             }
55//!         }
56//!         errors
57//!     }
58//! }
59//!
60//! // export_component_plugin!(MyRule);  // Required for WASM build
61//!
62//! // Verify it works
63//! let plugin = MyRule;
64//! let config = nginx_lint_plugin::parse_string("dangerous_directive on;").unwrap();
65//! let errors = plugin.check(&config, "test.conf");
66//! assert_eq!(errors.len(), 1);
67//! ```
68
69pub mod helpers;
70pub mod native;
71pub mod testing;
72mod types;
73
74#[cfg(feature = "container-testing")]
75pub mod container_testing;
76
77#[cfg(feature = "wit-export")]
78pub mod wasm_config;
79#[cfg(feature = "wit-export")]
80pub mod wit_guest;
81
82pub use types::*;
83
84// Re-export common types from nginx-lint-common
85pub use nginx_lint_common::parse_string;
86pub use nginx_lint_common::parser;
87
88/// Prelude module for convenient imports.
89///
90/// Importing everything from this module is the recommended way to use the SDK:
91///
92/// ```
93/// use nginx_lint_plugin::prelude::*;
94///
95/// // All core types are now available
96/// let spec = PluginSpec::new("example", "test", "Example rule");
97/// assert_eq!(spec.name, "example");
98/// ```
99///
100/// This re-exports all core types ([`Plugin`], [`PluginSpec`], [`LintError`], [`Fix`],
101/// [`Config`], [`Directive`], etc.), extension traits ([`ConfigExt`], [`DirectiveExt`]),
102/// the [`helpers`] module, and the [`export_component_plugin!`] macro.
103pub mod prelude {
104    pub use super::export_component_plugin;
105    pub use super::helpers;
106    pub use super::types::API_VERSION;
107    pub use super::types::*;
108}
109
110/// Macro to export a plugin as a WIT component
111///
112/// This generates the WIT component model exports for your plugin.
113///
114/// # Example
115///
116/// ```ignore
117/// use nginx_lint_plugin::prelude::*;
118///
119/// #[derive(Default)]
120/// struct MyPlugin;
121///
122/// impl Plugin for MyPlugin {
123///     fn spec(&self) -> PluginSpec { /* ... */ }
124///     fn check(&self, config: &Config, _path: &str) -> Vec<LintError> { /* ... */ }
125/// }
126///
127/// export_component_plugin!(MyPlugin);
128/// ```
129#[macro_export]
130macro_rules! export_component_plugin {
131    ($plugin_type:ty) => {
132        #[cfg(all(target_arch = "wasm32", feature = "wit-export"))]
133        const _: () = {
134            use $crate::wit_guest::Guest;
135
136            static PLUGIN: std::sync::OnceLock<$plugin_type> = std::sync::OnceLock::new();
137
138            fn get_plugin() -> &'static $plugin_type {
139                PLUGIN.get_or_init(|| <$plugin_type>::default())
140            }
141
142            struct ComponentExport;
143
144            impl Guest for ComponentExport {
145                fn spec() -> $crate::wit_guest::nginx_lint::plugin::types::PluginSpec {
146                    let plugin = get_plugin();
147                    let sdk_spec = $crate::Plugin::spec(plugin);
148                    $crate::wit_guest::convert_spec(sdk_spec)
149                }
150
151                fn check(
152                    config: &$crate::wit_guest::nginx_lint::plugin::config_api::Config,
153                    path: String,
154                ) -> Vec<$crate::wit_guest::nginx_lint::plugin::types::LintError> {
155                    let plugin = get_plugin();
156                    // Reconstruct parser Config from host resource handle
157                    let config = $crate::wit_guest::reconstruct_config(config);
158                    let errors = $crate::Plugin::check(plugin, &config, &path);
159                    errors
160                        .into_iter()
161                        .map($crate::wit_guest::convert_lint_error)
162                        .collect()
163                }
164            }
165
166    $crate::wit_guest::export!(ComponentExport with_types_in $crate::wit_guest);
167        };
168    };
169}