Skip to main content

poolster_plugin_java/
webhooks.rs

1use super::*;
2use poolster_core::engine::{Meta, Plugin, PluginContext};
3pub struct Webhooks {
4    meta: Meta,
5}
6pub fn webhooks() -> Webhooks {
7    Webhooks { meta: Meta::new() }
8}
9impl Plugin<Java> for Webhooks {
10    fn kind(&self) -> &'static str {
11        "java-webhooks"
12    }
13    fn meta(&self) -> &Meta {
14        &self.meta
15    }
16    fn generate(&self, cx: &mut PluginContext<'_, Java>) -> Result<()> {
17        let namespace = java_package_name(
18            cx.settings
19                .package_name
20                .as_deref()
21                .unwrap_or(&format!("io.poolster.{}", package_segment(&cx.api.name))),
22        );
23        cx.files.emit(GeneratedFile::new(
24            format!(
25                "src/main/java/{}/StandardWebhooks.java",
26                namespace.replace('.', "/")
27            ),
28            include_str!("../templates/webhooks.java.tmpl").replace("__PACKAGE__", &namespace),
29        )?)
30    }
31}
32
33#[cfg(test)]
34mod tests {
35    use super::*;
36    use poolster_core::engine::Packages;
37    #[test]
38    fn opt_in_emits_standard_webhook_verifier() {
39        let tree = Packages::new()
40            .package(crate::package("sdk").with(crate::sdk()).with(webhooks()))
41            .generate(&Api::default(), None)
42            .unwrap();
43        let source = tree
44            .iter()
45            .find(|(path, _)| path.ends_with("StandardWebhooks.java"))
46            .unwrap()
47            .1;
48        assert!(source.contains("HmacSHA256") || source.contains("HMACSHA256"));
49        assert!(source.contains("does not match a trusted key"));
50    }
51    #[test]
52    #[ignore = "requires native JDK17; canonical Standard Webhooks verification probes"]
53    fn native_webhook_verification_executes_canonical_vectors() {
54        let vector: serde_json::Value =
55            serde_json::from_str(include_str!("../../python/testdata/webhook-vectors.json"))
56                .unwrap();
57        let dir = tempfile::tempdir().unwrap();
58        std::fs::write(
59            dir.path().join("StandardWebhooks.java"),
60            include_str!("../templates/webhooks.java.tmpl").replace("__PACKAGE__", "probe"),
61        )
62        .unwrap();
63        std::fs::write(
64            dir.path().join("Probe.java"),
65            include_str!("../tests/fixtures/webhooks_probe.java")
66                .replace(
67                    "__BODY__",
68                    &serde_json::to_string(&vector["raw_body"]).unwrap(),
69                )
70                .replace(
71                    "__SECRET__",
72                    &serde_json::to_string(&vector["secret"]).unwrap(),
73                )
74                .replace(
75                    "__SIGNATURE__",
76                    &serde_json::to_string(&vector["headers"]["webhook-signature"]).unwrap(),
77                ),
78        )
79        .unwrap();
80        let result = std::process::Command::new("javac")
81            .args(["-d", ".", "StandardWebhooks.java", "Probe.java"])
82            .current_dir(dir.path())
83            .output()
84            .unwrap();
85        assert!(
86            result.status.success(),
87            "{}",
88            String::from_utf8_lossy(&result.stderr)
89        );
90        let result = std::process::Command::new("java")
91            .args(["-cp", ".", "probe.Probe"])
92            .current_dir(dir.path())
93            .output()
94            .unwrap();
95        assert!(
96            result.status.success(),
97            "{}{}",
98            String::from_utf8_lossy(&result.stdout),
99            String::from_utf8_lossy(&result.stderr)
100        );
101    }
102}