File
Blob: src/rust/jsg/feature_flags.rs
| 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 | } |