From 927d673ee9647c2e9382adce6ee3ec3630c22c91 Mon Sep 17 00:00:00 2001 From: Manuel Fuchs Date: Tue, 30 Jan 2024 14:25:23 +0100 Subject: [PATCH 1/3] Add MappedWrite::unwrap --- CHANGELOG.md | 4 +++ libherokubuildpack/src/write.rs | 64 +++++++++++++++++++++++++++------ 2 files changed, 57 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8339f40..e7e26fa4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `libcnb`: - Changed `Layer` interface from `&self` to `&mut self`. ([#669](https://github.com/heroku/libcnb.rs/pull/669)) +### Added + +- `libherokubuildpack`: + - `MappedWrite::unwrap` for getting the wrapped `Write` back out. ([#000](https://github.com/heroku/libcnb.rs/pull/000)) ## [0.17.0] - 2023-12-06 diff --git a/libherokubuildpack/src/write.rs b/libherokubuildpack/src/write.rs index 57a957d1..198bbb4d 100644 --- a/libherokubuildpack/src/write.rs +++ b/libherokubuildpack/src/write.rs @@ -13,12 +13,7 @@ pub fn mapped) -> Vec) + Sync + Send + 'static> marker_byte: u8, f: F, ) -> MappedWrite { - MappedWrite { - inner: w, - marker_byte, - buffer: Vec::new(), - mapping_fn: Arc::new(f), - } + MappedWrite::new(w, marker_byte, f) } /// Constructs a writer that buffers written data until an ASCII/UTF-8 newline byte (`0x0A`) is @@ -44,7 +39,17 @@ pub fn tee(a: A, b: B) -> TeeWrite { /// A mapped writer that was created with the [`mapped`] or [`line_mapped`] function. #[derive(Clone)] pub struct MappedWrite { - inner: W, + // To support unwrapping the inner `Write` while also implementing `Drop` for final cleanup, we need to wrap the + // `W` value so we can replace it in memory during unwrap. Without the wrapping `Option` we'd need to have a way + // to construct a bogus `W` value which would require additional trait bounds for `W`. `Clone` and/or `Default` + // come to mind. Not only would this clutter the API, but for most values that implement `Write`, `Clone` or + // `Default` are hard to implement correctly as they most often involve system resources such as file handles. + // + // This semantically means that a `MappedWrite` can exist without an inner `Write`, but users of `MappedWrite` can + // never construct such a `MappedWrite` as it only represents a state that happens during `MappedWrite::unwrap`. + // + // See: https://rustwiki.org/en/error-index/#E0509 + inner: Option, marker_byte: u8, buffer: Vec, mapping_fn: Arc) -> Vec) + Sync + Send>, @@ -57,10 +62,44 @@ pub struct TeeWrite { inner_b: B, } -impl MappedWrite { +impl MappedWrite +where + W: io::Write, +{ + pub fn new) -> Vec) + Sync + Send + 'static>( + w: W, + marker_byte: u8, + f: F, + ) -> MappedWrite { + MappedWrite { + inner: Some(w), + marker_byte, + buffer: Vec::new(), + mapping_fn: Arc::new(f), + } + } + + pub fn unwrap(mut self) -> W { + // See `Drop` implementation. This logic cannot be de-duplicated (i.e. by using unwrap in `Drop`) as we would + // end up in illegal states. + if self.inner.is_some() { + let _result = self.map_and_write_current_buffer(); + } + + if let Some(inner) = self.inner.take() { + inner + } else { + // Since `unwrap` is the only function that will cause `self.inner` to be `None` and `unwrap` itself + // consumes the `MappedWrite`, we can be sure that this case never happens. + unreachable!("self.inner will never be None") + } + } + fn map_and_write_current_buffer(&mut self) -> io::Result<()> { - self.inner - .write_all(&(self.mapping_fn)(mem::take(&mut self.buffer))) + match self.inner { + Some(ref mut inner) => inner.write_all(&(self.mapping_fn)(mem::take(&mut self.buffer))), + None => Ok(()), + } } } @@ -78,7 +117,10 @@ impl io::Write for MappedWrite { } fn flush(&mut self) -> io::Result<()> { - self.inner.flush() + match self.inner { + Some(ref mut inner) => inner.flush(), + None => Ok(()), + } } } From ab31bc35ca34335d5754300bc9086feeb1e66e46 Mon Sep 17 00:00:00 2001 From: Manuel Fuchs Date: Tue, 30 Jan 2024 14:28:36 +0100 Subject: [PATCH 2/3] Update CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e7e26fa4..ae20b9fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - `libherokubuildpack`: - - `MappedWrite::unwrap` for getting the wrapped `Write` back out. ([#000](https://github.com/heroku/libcnb.rs/pull/000)) + - `MappedWrite::unwrap` for getting the wrapped `Write` back out. ([#765](https://github.com/heroku/libcnb.rs/pull/765)) ## [0.17.0] - 2023-12-06 From 934ab1f3263f4f313880d56f3fd9d253a76c9d99 Mon Sep 17 00:00:00 2001 From: Manuel Fuchs Date: Tue, 30 Jan 2024 19:23:20 +0100 Subject: [PATCH 3/3] Make MappedWrite::new private --- libherokubuildpack/src/write.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libherokubuildpack/src/write.rs b/libherokubuildpack/src/write.rs index 198bbb4d..a7bd0221 100644 --- a/libherokubuildpack/src/write.rs +++ b/libherokubuildpack/src/write.rs @@ -66,7 +66,7 @@ impl MappedWrite where W: io::Write, { - pub fn new) -> Vec) + Sync + Send + 'static>( + fn new) -> Vec) + Sync + Send + 'static>( w: W, marker_byte: u8, f: F,