|
| 1 | +//! Rust-native access to workerd compatibility flags. |
| 2 | +//! |
| 3 | +//! ```ignore |
| 4 | +//! if lock.feature_flags().get_node_js_compat() { |
| 5 | +//! // Node.js compatibility behavior |
| 6 | +//! } |
| 7 | +//! ``` |
| 8 | +
|
| 9 | +use capnp::message::ReaderOptions; |
| 10 | +pub use compatibility_date_capnp::compatibility_flags; |
| 11 | + |
| 12 | +/// Provides access to the current worker's compatibility flags. |
| 13 | +/// |
| 14 | +/// Parsed once from canonical Cap'n Proto bytes during Realm construction |
| 15 | +/// and stored in the per-context [`Realm`](crate::Realm). Access via |
| 16 | +/// [`Lock::feature_flags()`](crate::Lock::feature_flags). |
| 17 | +pub struct FeatureFlags { |
| 18 | + message: capnp::message::Reader<Vec<Vec<u8>>>, |
| 19 | +} |
| 20 | + |
| 21 | +impl FeatureFlags { |
| 22 | + /// Create from canonical (single-segment, no segment table) Cap'n Proto bytes. |
| 23 | + /// |
| 24 | + /// On the C++ side, produce these via `capnp::canonicalize(reader)`. |
| 25 | + /// |
| 26 | + /// # Panics |
| 27 | + /// |
| 28 | + /// Panics if `data` is empty or not word-aligned. |
| 29 | + pub(crate) fn from_bytes(data: &[u8]) -> Self { |
| 30 | + assert!(!data.is_empty(), "FeatureFlags data must not be empty"); |
| 31 | + assert!( |
| 32 | + data.len().is_multiple_of(8), |
| 33 | + "FeatureFlags data must be word-aligned (got {} bytes)", |
| 34 | + data.len() |
| 35 | + ); |
| 36 | + let segments = vec![data.to_vec()]; |
| 37 | + let message = capnp::message::Reader::new(segments, ReaderOptions::new()); |
| 38 | + Self { message } |
| 39 | + } |
| 40 | + |
| 41 | + /// Returns the `CompatibilityFlags` reader. |
| 42 | + /// |
| 43 | + /// The reader has a getter for each flag defined in `compatibility-date.capnp` |
| 44 | + /// (e.g., `get_node_js_compat()`). |
| 45 | + /// |
| 46 | + /// # Panics |
| 47 | + /// |
| 48 | + /// Panics if the stored message has an invalid capnp root (should never happen |
| 49 | + /// when constructed via `from_bytes`). |
| 50 | + pub fn reader(&self) -> compatibility_flags::Reader<'_> { |
| 51 | + self.message |
| 52 | + .get_root::<compatibility_flags::Reader<'_>>() |
| 53 | + .expect("Invalid FeatureFlags capnp root") |
| 54 | + } |
| 55 | +} |
| 56 | + |
| 57 | +#[cfg(test)] |
| 58 | +mod tests { |
| 59 | + use super::*; |
| 60 | + |
| 61 | + /// Helper: build a `CompatibilityFlags` capnp message with the given flag setter, |
| 62 | + /// return the raw single-segment bytes (no wire-format header). |
| 63 | + fn build_flags<F>(setter: F) -> Vec<u8> |
| 64 | + where |
| 65 | + F: FnOnce(compatibility_flags::Builder<'_>), |
| 66 | + { |
| 67 | + let mut message = capnp::message::Builder::new_default(); |
| 68 | + { |
| 69 | + let flags = message.init_root::<compatibility_flags::Builder<'_>>(); |
| 70 | + setter(flags); |
| 71 | + } |
| 72 | + let output = message.get_segments_for_output(); |
| 73 | + output[0].to_vec() |
| 74 | + } |
| 75 | + |
| 76 | + #[test] |
| 77 | + fn from_bytes_roundtrip() { |
| 78 | + let bytes = build_flags(|mut f| { |
| 79 | + f.set_node_js_compat(true); |
| 80 | + }); |
| 81 | + let ff = FeatureFlags::from_bytes(&bytes); |
| 82 | + assert!(ff.reader().get_node_js_compat()); |
| 83 | + } |
| 84 | + |
| 85 | + #[test] |
| 86 | + #[should_panic(expected = "FeatureFlags data must not be empty")] |
| 87 | + fn from_bytes_empty_panics() { |
| 88 | + FeatureFlags::from_bytes(&[]); |
| 89 | + } |
| 90 | + |
| 91 | + #[test] |
| 92 | + fn default_flags_are_false() { |
| 93 | + let bytes = build_flags(|_| {}); |
| 94 | + let ff = FeatureFlags::from_bytes(&bytes); |
| 95 | + assert!(!ff.reader().get_node_js_compat()); |
| 96 | + assert!(!ff.reader().get_node_js_compat_v2()); |
| 97 | + assert!(!ff.reader().get_fetch_refuses_unknown_protocols()); |
| 98 | + } |
| 99 | + |
| 100 | + #[test] |
| 101 | + fn multiple_flags() { |
| 102 | + let bytes = build_flags(|mut f| { |
| 103 | + f.set_node_js_compat(true); |
| 104 | + f.set_node_js_compat_v2(true); |
| 105 | + f.set_fetch_refuses_unknown_protocols(false); |
| 106 | + }); |
| 107 | + let ff = FeatureFlags::from_bytes(&bytes); |
| 108 | + assert!(ff.reader().get_node_js_compat()); |
| 109 | + assert!(ff.reader().get_node_js_compat_v2()); |
| 110 | + assert!(!ff.reader().get_fetch_refuses_unknown_protocols()); |
| 111 | + } |
| 112 | + |
| 113 | + #[test] |
| 114 | + fn reader_called_multiple_times() { |
| 115 | + let bytes = build_flags(|mut f| { |
| 116 | + f.set_node_js_compat(true); |
| 117 | + }); |
| 118 | + let ff = FeatureFlags::from_bytes(&bytes); |
| 119 | + // Reader can be obtained multiple times from the same FeatureFlags. |
| 120 | + assert!(ff.reader().get_node_js_compat()); |
| 121 | + assert!(ff.reader().get_node_js_compat()); |
| 122 | + } |
| 123 | +} |
0 commit comments