introduce zstd for compression - #4185
Conversation
✅ Deploy Preview for zio-http ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
There was a problem hiding this comment.
Pull request overview
Adds server-side zstd response compression support (Accept-Encoding negotiation via Netty HttpContentCompressor), aligning with the existing ability to accept Content-Encoding: zstd when users provide the JNI dependency.
Changes:
- Introduces
CompressionOptions.ZstdplusZstdConfigand aCompressionOptions.zstd(...)constructor. - Extends
CompressionOptions.configparsing to recognize"type" = "zstd"and read zstd-specific parameters. - Adds Netty conversion support for zstd via
StandardCompressionOptions.zstd(...).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
zio-http/shared/src/main/scala/zio/http/Server.scala |
Adds zstd compression option/config and updates config parsing to support selecting zstd. |
zio-http/jvm/src/main/scala/zio/http/netty/model/Conversions.scala |
Maps the new zstd compression option into Netty StandardCompressionOptions.zstd(...). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
✅ Deploy Preview for zio-http ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
@gipeshka Please fix the build. Rebase might just do it |
348ecb6 to
dfcbdf6
Compare
|
@987Nabil thanks for letting me know, it's fixed now |
987Nabil
left a comment
There was a problem hiding this comment.
Found a functional bug in the zstd defaults — see inline comment. The blockSize/maxEncodeSize defaults are swapped relative to Netty's actual ZstdConstants. With the current values, CompressionOptions.zstd() (no args) sets maxEncodeSize = 65536, so any response body that compresses to more than 64 KB will throw (maxEncodeSize bounds the largest compressible object), while blockSize = Integer.MAX_VALUE is backwards too. Everything else (Conversions.scala mapping, param order into StandardCompressionOptions.zstd(level, blockSize, maxEncodeSize), config parsing wiring, CI/Mima) checks out — this is the one blocking issue.
19bf52c to
31d500d
Compare
| typ.toLowerCase match { | ||
| case "gzip" => gzip(level, bits, mem) | ||
| case "deflate" => deflate(level, bits, mem) | ||
| case "brotli" => brotli(quantity, lgwin, mode) | ||
| case "zstd" => zstd(level, block, maxencode) |
There was a problem hiding this comment.
makes sense. @987Nabil what would be the preferred way of solving it? Would isolating level for zstd under a dedicated config prefix like zstdlevel work?
There was a problem hiding this comment.
@987Nabil I've adjusted it via optional. Please have a look
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.
Suppressed comments (3)
zio-http/shared/src/main/scala/zio/http/Server.scala:422
- All sibling public compression factories document that their defaults correspond to Netty's default option object, but the newly added zstd factory omits that API documentation. Document the same default relationship here so callers can understand the three otherwise non-obvious tuning parameters.
def zstd(
zio-http/shared/src/main/scala/zio/http/Server.scala:321
- The new supported encoding is not represented by the typed header API.
Header.AcceptEncodingstill exposes zstd only asUnknown, andHeader.ContentEncoding.parsedropszstd, so a response compressed by this option cannot be observed throughresponse.header(Header.ContentEncoding). Add Zstd variants to both header algebras, update their parsers/rendering, and cover them inHeaderSpec.
final case class Zstd(cfg: ZstdConfig) extends CompressionOptions { val name = "zstd" }
zio-http/jvm/src/main/scala/zio/http/netty/model/Conversions.scala:174
- This new conversion branch has no end-to-end coverage, although
ResponseCompressionSpecverifies the existing response codecs. Add a test-scoped zstd-jni dependency and an integration test that enables only this option, requestsAccept-Encoding: zstd, verifiesContent-Encoding: zstd, and decompresses the body; a test-scoped dependency would not impose JNI on library consumers.
case CompressionOptions.Zstd(cfg) =>
StandardCompressionOptions.zstd(cfg.level, cfg.blockSize, cfg.maxEncodeSize)
141aaa5 to
fdbbd4c
Compare
There was a problem hiding this comment.
🟡 Changes recommended
The Zstandard path lacks integration coverage, and its configuration keys need normalization.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 2
- Review effort level: Balanced
There was a problem hiding this comment.
🔵 Needs a closer look
Unknown compression types still cause an untyped MatchError during configuration loading.
Review details
Suppressed comments (1)
zio-http/shared/src/main/scala/zio/http/Server.scala:471
- The algorithm-specific
leveldefaults are fixed, but an unsupportedtypestill falls through this partial match and throwsMatchErrorwhile loading configuration. Parse withmapOrFailand returnInvalidDatafor unknown values so configuration failures stay typed and actionable.
case "zstd" => zstd(level.getOrElse(ZstdConfig.DefaultLevel), block, maxencode)
- Files reviewed: 2/2 changed files
- Comments generated: 0 new
- Review effort level: Balanced
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
8d6267e to
bcbb9d4
Compare
| case CompressionOptions.Brotli(cfg) => | ||
| StandardCompressionOptions.brotli(cfg.quality, cfg.lgwin, brotliModeToJava(cfg.mode)) | ||
| case CompressionOptions.Zstd(cfg) => | ||
| StandardCompressionOptions.zstd(cfg.level, cfg.blockSize, cfg.maxEncodeSize) |
There was a problem hiding this comment.
Please add an end-to-end test for this new path. A test-scoped zstd-jni dependency can configure only CompressionOptions.zstd(), request Accept-Encoding: zstd, assert the response encoding, and decompress the body. Including a response larger than 64 KiB would also protect the corrected blockSize/maxEncodeSize defaults.
| @@ -344,6 +344,7 @@ object Server extends ServerPlatformSpecific { | |||
| final case class GZip(cfg: DeflateConfig) extends CompressionOptions { val name = "gzip" } | |||
There was a problem hiding this comment.
Adding Zstd here makes it a supported response content coding, but the typed header API is not updated: Header.ContentEncoding.parse("zstd") currently fails, and AcceptEncoding can represent it only as Unknown. Please add typed Zstd variants to both header algebras, update parsing/rendering, and cover them in HeaderSpec.
| @@ -432,20 +445,30 @@ object Server extends ServerPlatformSpecific { | |||
| ): CompressionOptions = | |||
There was a problem hiding this comment.
Could this public factory get Scaladoc like the gzip/deflate/brotli factories above? In particular, document the Netty defaults and that callers must provide the optional zstd-jni runtime dependency; otherwise selecting this option fails during Netty option construction.
Addressing #4184.
Not including zstd jni, to use this I assume one would have to manually enable it (just like one has to do it now for zio-http to start accepting zstd
content-encoding)