diff --git a/build.sbt b/build.sbt index d8cbd6b417..6d0f5e69d0 100644 --- a/build.sbt +++ b/build.sbt @@ -415,7 +415,7 @@ lazy val zioHttpTestkit = (project in file("zio-http-testkit")) testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework"), libraryDependencies ++= netty ++ Seq( `zio`, - `zio-test`, + "dev.zio" %% "zio-test" % ZioVersion, `zio-test-sbt`, ), ) diff --git a/docs/concepts/dev-mode.md b/docs/concepts/dev-mode.md new file mode 100644 index 0000000000..044655c6c7 --- /dev/null +++ b/docs/concepts/dev-mode.md @@ -0,0 +1,125 @@ +# Dev / Preprod / Prod Modes + +ZIO HTTP provides a simple built-in notion of application "mode" so you can adapt behavior (e.g. enable extra diagnostics in development, stricter settings in production, other routes, different error handling) without wiring your own config keys everywhere. + +The available modes are: + +- `Mode.Dev` (default if nothing is configured) +- `Mode.Preprod` (a staging / pre‑production environment) +- `Mode.Prod` (production) + +## Reading the Current Mode + +Use any of the following helpers: + +```scala +import zio.http.Mode + +// Full value +def m: Mode = Mode.current + +// Convenience booleans +val isDev = Mode.isDev +val isPreprod = Mode.isPreprod +val isProd = Mode.isProd +``` + +## Configuring the Mode + +The mode is determined in this precedence order: + +1. JVM System Property: `-Dzio.http.mode=` +2. Environment Variable: `ZIO_HTTP_MODE=` +3. Fallback: `dev` + +Examples: + +```bash +# Using a JVM system property +sbt "run -Dzio.http.mode=preprod" + +# Using an environment variable (takes effect if the system property is NOT set) +ZIO_HTTP_MODE=prod sbt run +``` + +Unknown values cause a warning on stderr and the mode falls back to `dev`: + +## Typical Use Cases + +You can branch on the mode to enable / disable features: + +```scala +import zio._ +import zio.http._ + +val extraRoutes: Routes[Any, Nothing] = + if (Mode.isDev) SwaggerUI.routes("docs", OpenAPIGen.empty) + else Routes.empty + +val baseRoutes: Routes[Any, Nothing] = Routes( + Method.GET / "health" -> handler(Response.ok) +) + +val appRoutes = baseRoutes ++ extraRoutes +``` + +Or adapt server config: + +```scala +val serverConfig = + if (Mode.isProd) Server.Config.default + .leakDetection(false) + .requestDecompression(true) + else Server.Config.default + .leakDetection(true) // extra visibility in dev + .maxThreads(4) // keep lighter in local dev +``` + +## Testing Modes + +Inside tests you generally want to *temporarily* switch the mode to verify conditional behavior. The testkit provides aspects in `zio.http.HttpTestAspect`: + +- `HttpTestAspect.devMode` +- `HttpTestAspect.preprodMode` +- `HttpTestAspect.prodMode` + +Each aspect sets the mode for the duration of the test, restoring the previous mode afterward. This allows you to write tests that depend on specific modes without affecting other tests. + +Example: + +```scala +import zio.test._ +import zio.http._ + +object ModeExamplesSpec extends ZIOSpecDefault { + def spec = suite("ModeExamplesSpec")( + test("enables preprod logic") { + assertTrue(Mode.current == Mode.Preprod) + } @@ HttpTestAspect.preprodMode, + + test("enables prod logic") { + assertTrue(Mode.isProd) + } @@ HttpTestAspect.prodMode, + ) @@ TestAspect.sequential // IMPORTANT, see below +} +``` + +### Why `TestAspect.sequential`? + +The mode is stored per JVM. When you apply different mode aspects to multiple tests in the **same suite**, running them in parallel could cause races (e.g. one test reads prod while another just switched to preprod). Adding `@@ TestAspect.sequential` ensures the suite’s tests execute one after another so each mode override is isolated. + +If every test suite uses only one mode (or you wrap all tests in a single aspect at the suite level), sequential execution is not strictly necessary. It is required only when multiple tests in the same suite each apply different mode aspects. + +## Quick Reference + +| Task | How | +|------|-----| +| Read current mode | `Mode.current` | +| Check if dev | `Mode.isDev` | +| Run in preprod | `-Dzio.http.mode=preprod` or `ZIO_HTTP_MODE=preprod` | +| Override in a test | `test("...") { ... } @@ HttpTestAspect.prodMode` | +| Avoid race conditions | Apply `@@ TestAspect.sequential` to suite when multiple mode aspects are used | + +## When *Not* to Use Mode + +For complex environment-dependent configuration (database URLs, secrets, feature flags) prefer a dedicated configuration service (e.g. `zio-config`). diff --git a/website/sidebars.js b/website/sidebars.js index 827b3ebff9..d4e6fbef8b 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -160,7 +160,7 @@ const sidebars = { type: "category", collapsed: false, label: "Concepts", - items: ["concepts/routing", "concepts/middleware", "concepts/endpoint"], + items: ["concepts/routing", "concepts/middleware", "concepts/endpoint", "concepts/dev-mode"], }, ], diff --git a/zio-http-benchmarks/src/main/scala-2.13/zio/http/benchmarks/EndpointBenchmark.scala b/zio-http-benchmarks/src/main/scala-2.13/zio/http/benchmarks/EndpointBenchmark.scala index e21187dd6b..a187bdca58 100644 --- a/zio-http-benchmarks/src/main/scala-2.13/zio/http/benchmarks/EndpointBenchmark.scala +++ b/zio-http-benchmarks/src/main/scala-2.13/zio/http/benchmarks/EndpointBenchmark.scala @@ -70,7 +70,7 @@ import sttp.tapir.{Endpoint => TEndpoint, endpoint => tendpoint, path => tpath, // [info] EndpointBenchmark.benchmarkSmallDataZioCollect thrpt 2 701.566 ops/s @State(Scope.Thread) -@BenchmarkMode(Array(Mode.Throughput)) +@BenchmarkMode(Array(org.openjdk.jmh.annotations.Mode.Throughput)) @OutputTimeUnit(TimeUnit.SECONDS) class EndpointBenchmark { // implicit val actorSystem: ActorSystem = ActorSystem("api-benchmark-actor-system") diff --git a/zio-http-benchmarks/src/main/scala/benchmark/FormToQueryBenchmark.scala b/zio-http-benchmarks/src/main/scala/benchmark/FormToQueryBenchmark.scala index 4a3ddaf395..72bdb81315 100644 --- a/zio-http-benchmarks/src/main/scala/benchmark/FormToQueryBenchmark.scala +++ b/zio-http-benchmarks/src/main/scala/benchmark/FormToQueryBenchmark.scala @@ -9,7 +9,7 @@ import zio.http._ import org.openjdk.jmh.annotations._ @State(org.openjdk.jmh.annotations.Scope.Thread) -@BenchmarkMode(Array(Mode.Throughput)) +@BenchmarkMode(Array(org.openjdk.jmh.annotations.Mode.Throughput)) @OutputTimeUnit(TimeUnit.SECONDS) class FormToQueryBenchmark { diff --git a/zio-http-benchmarks/src/main/scala/benchmark/MethodLookupBenchmark.scala b/zio-http-benchmarks/src/main/scala/benchmark/MethodLookupBenchmark.scala index 722195fb10..931f3a56be 100644 --- a/zio-http-benchmarks/src/main/scala/benchmark/MethodLookupBenchmark.scala +++ b/zio-http-benchmarks/src/main/scala/benchmark/MethodLookupBenchmark.scala @@ -10,7 +10,7 @@ import zio.http.endpoint.Endpoint import org.openjdk.jmh.annotations._ @State(Scope.Thread) -@BenchmarkMode(Array(Mode.Throughput)) +@BenchmarkMode(Array(org.openjdk.jmh.annotations.Mode.Throughput)) @OutputTimeUnit(TimeUnit.SECONDS) class MethodLookupBenchmark { diff --git a/zio-http-benchmarks/src/main/scala/benchmark/RoundtripBenchmark.scala b/zio-http-benchmarks/src/main/scala/benchmark/RoundtripBenchmark.scala index c1e08b6f58..63cbcdbf40 100644 --- a/zio-http-benchmarks/src/main/scala/benchmark/RoundtripBenchmark.scala +++ b/zio-http-benchmarks/src/main/scala/benchmark/RoundtripBenchmark.scala @@ -11,7 +11,7 @@ import zio.http._ import org.openjdk.jmh.annotations._ @State(org.openjdk.jmh.annotations.Scope.Thread) -@BenchmarkMode(Array(Mode.Throughput)) +@BenchmarkMode(Array(org.openjdk.jmh.annotations.Mode.Throughput)) @OutputTimeUnit(TimeUnit.SECONDS) class RoundtripBenchmark { diff --git a/zio-http-benchmarks/src/main/scala/benchmark/RoutesBenchmark.scala b/zio-http-benchmarks/src/main/scala/benchmark/RoutesBenchmark.scala index 9d912cd0a9..b3df325815 100644 --- a/zio-http-benchmarks/src/main/scala/benchmark/RoutesBenchmark.scala +++ b/zio-http-benchmarks/src/main/scala/benchmark/RoutesBenchmark.scala @@ -10,7 +10,7 @@ import zio.http.{Handler, Method, Request, Routes} import org.openjdk.jmh.annotations._ @State(Scope.Thread) -@BenchmarkMode(Array(Mode.Throughput)) +@BenchmarkMode(Array(org.openjdk.jmh.annotations.Mode.Throughput)) @OutputTimeUnit(TimeUnit.SECONDS) class RoutesBenchmark { diff --git a/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/CachedDateHeaderBenchmark.scala b/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/CachedDateHeaderBenchmark.scala index 3386eb534b..9d74129e83 100644 --- a/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/CachedDateHeaderBenchmark.scala +++ b/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/CachedDateHeaderBenchmark.scala @@ -8,7 +8,7 @@ import zio.http.internal.DateEncoding import org.openjdk.jmh.annotations._ @State(Scope.Benchmark) -@BenchmarkMode(Array(Mode.AverageTime)) +@BenchmarkMode(Array(org.openjdk.jmh.annotations.Mode.AverageTime)) @OutputTimeUnit(TimeUnit.NANOSECONDS) @Threads(16) @Fork(1) diff --git a/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/ClientBenchmark.scala b/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/ClientBenchmark.scala index e55d8f73ee..b9242d3e33 100644 --- a/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/ClientBenchmark.scala +++ b/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/ClientBenchmark.scala @@ -12,7 +12,7 @@ import org.openjdk.jmh.annotations._ @nowarn @State(org.openjdk.jmh.annotations.Scope.Benchmark) -@BenchmarkMode(Array(Mode.Throughput)) +@BenchmarkMode(Array(org.openjdk.jmh.annotations.Mode.Throughput)) @OutputTimeUnit(TimeUnit.SECONDS) @Warmup(iterations = 3, time = 3) @Measurement(iterations = 3, time = 3) diff --git a/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/CookieDecodeBenchmark.scala b/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/CookieDecodeBenchmark.scala index 3c2301d32c..34796258ee 100644 --- a/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/CookieDecodeBenchmark.scala +++ b/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/CookieDecodeBenchmark.scala @@ -8,7 +8,7 @@ import zio.http.{Cookie, Path} import org.openjdk.jmh.annotations._ @State(Scope.Thread) -@BenchmarkMode(Array(Mode.Throughput)) +@BenchmarkMode(Array(org.openjdk.jmh.annotations.Mode.Throughput)) @OutputTimeUnit(TimeUnit.SECONDS) class CookieDecodeBenchmark { val random = new scala.util.Random() diff --git a/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/HttpCollectEval.scala b/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/HttpCollectEval.scala index 75516fa33f..05cb76283f 100644 --- a/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/HttpCollectEval.scala +++ b/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/HttpCollectEval.scala @@ -7,7 +7,7 @@ import zio.http._ import org.openjdk.jmh.annotations._ @State(Scope.Thread) -@BenchmarkMode(Array(Mode.Throughput)) +@BenchmarkMode(Array(org.openjdk.jmh.annotations.Mode.Throughput)) @OutputTimeUnit(TimeUnit.SECONDS) class HttpCollectEval { private val MAX = 10000 diff --git a/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/HttpCombineEval.scala b/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/HttpCombineEval.scala index 50a0b71cd6..aee07fdad3 100644 --- a/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/HttpCombineEval.scala +++ b/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/HttpCombineEval.scala @@ -7,7 +7,7 @@ import zio.http._ import org.openjdk.jmh.annotations._ @State(Scope.Thread) -@BenchmarkMode(Array(Mode.Throughput)) +@BenchmarkMode(Array(org.openjdk.jmh.annotations.Mode.Throughput)) @OutputTimeUnit(TimeUnit.SECONDS) class HttpCombineEval { private val req = Request.get("/foo") diff --git a/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/HttpNestedFlatMapEval.scala b/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/HttpNestedFlatMapEval.scala index 2842b6d646..595117f544 100644 --- a/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/HttpNestedFlatMapEval.scala +++ b/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/HttpNestedFlatMapEval.scala @@ -7,7 +7,7 @@ import zio.http._ import org.openjdk.jmh.annotations._ @State(Scope.Thread) -@BenchmarkMode(Array(Mode.Throughput)) +@BenchmarkMode(Array(org.openjdk.jmh.annotations.Mode.Throughput)) @OutputTimeUnit(TimeUnit.SECONDS) class HttpNestedFlatMapEval { diff --git a/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/HttpRouteTextPerf.scala b/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/HttpRouteTextPerf.scala index 633b28d10e..3b0b10e283 100644 --- a/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/HttpRouteTextPerf.scala +++ b/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/HttpRouteTextPerf.scala @@ -6,10 +6,10 @@ import zio._ import zio.http._ -import org.openjdk.jmh.annotations.{Scope => JScope, _} +import org.openjdk.jmh.annotations.{Mode, Scope => JScope, _} @State(JScope.Thread) -@BenchmarkMode(Array(Mode.Throughput)) +@BenchmarkMode(Array(org.openjdk.jmh.annotations.Mode.Throughput)) @OutputTimeUnit(TimeUnit.SECONDS) class HttpRouteTextPerf { diff --git a/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/ProbeContentTypeBenchmark.scala b/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/ProbeContentTypeBenchmark.scala index 253f12260d..da02b62d8a 100644 --- a/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/ProbeContentTypeBenchmark.scala +++ b/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/ProbeContentTypeBenchmark.scala @@ -10,7 +10,7 @@ import zio.http.MediaType import org.openjdk.jmh.annotations._ @State(Scope.Thread) -@BenchmarkMode(Array(Mode.Throughput)) +@BenchmarkMode(Array(org.openjdk.jmh.annotations.Mode.Throughput)) @OutputTimeUnit(TimeUnit.SECONDS) class ProbeContentTypeBenchmark { diff --git a/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/SchemeDecodeBenchmark.scala b/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/SchemeDecodeBenchmark.scala index 1671e0b44d..aab14462cb 100644 --- a/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/SchemeDecodeBenchmark.scala +++ b/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/SchemeDecodeBenchmark.scala @@ -6,7 +6,7 @@ import zio.http.Scheme import org.openjdk.jmh.annotations._ @State(Scope.Thread) -@BenchmarkMode(Array(Mode.Throughput)) +@BenchmarkMode(Array(org.openjdk.jmh.annotations.Mode.Throughput)) @OutputTimeUnit(TimeUnit.SECONDS) class SchemeDecodeBenchmark { private val MAX = 1000000 diff --git a/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/ServerInboundHandlerBenchmark.scala b/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/ServerInboundHandlerBenchmark.scala index 8f594614c6..d8da2d0578 100644 --- a/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/ServerInboundHandlerBenchmark.scala +++ b/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/ServerInboundHandlerBenchmark.scala @@ -10,7 +10,7 @@ import org.openjdk.jmh.annotations._ import sttp.client3.{HttpURLConnectionBackend, UriContext, basicRequest} @State(org.openjdk.jmh.annotations.Scope.Thread) -@BenchmarkMode(Array(Mode.Throughput)) +@BenchmarkMode(Array(org.openjdk.jmh.annotations.Mode.Throughput)) @OutputTimeUnit(TimeUnit.SECONDS) class ServerInboundHandlerBenchmark { private val random = scala.util.Random diff --git a/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/UtilBenchmark.scala b/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/UtilBenchmark.scala index 37e005e9f7..3e003fa405 100644 --- a/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/UtilBenchmark.scala +++ b/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/UtilBenchmark.scala @@ -9,7 +9,7 @@ import io.netty.handler.codec.http.DefaultHttpHeaders import org.openjdk.jmh.annotations._ @State(Scope.Thread) -@BenchmarkMode(Array(Mode.AverageTime)) +@BenchmarkMode(Array(org.openjdk.jmh.annotations.Mode.AverageTime)) @OutputTimeUnit(TimeUnit.NANOSECONDS) @Fork(1) @Warmup(iterations = 3, time = 3) diff --git a/zio-http-benchmarks/src/main/scala/zio/http/DateEncodingBenchmark.scala b/zio-http-benchmarks/src/main/scala/zio/http/DateEncodingBenchmark.scala index bdf56891df..c4180997f2 100644 --- a/zio-http-benchmarks/src/main/scala/zio/http/DateEncodingBenchmark.scala +++ b/zio-http-benchmarks/src/main/scala/zio/http/DateEncodingBenchmark.scala @@ -10,7 +10,7 @@ import io.netty.handler.codec.DateFormatter import org.openjdk.jmh.annotations._ @State(org.openjdk.jmh.annotations.Scope.Thread) -@BenchmarkMode(Array(Mode.Throughput)) +@BenchmarkMode(Array(org.openjdk.jmh.annotations.Mode.Throughput)) @OutputTimeUnit(TimeUnit.SECONDS) class DateEncodingBenchmark { diff --git a/zio-http-benchmarks/src/main/scala/zio/http/QueryEncodingBenchmark.scala b/zio-http-benchmarks/src/main/scala/zio/http/QueryEncodingBenchmark.scala index 48b0ac747a..a4a9e39d61 100644 --- a/zio-http-benchmarks/src/main/scala/zio/http/QueryEncodingBenchmark.scala +++ b/zio-http-benchmarks/src/main/scala/zio/http/QueryEncodingBenchmark.scala @@ -9,7 +9,7 @@ import zio.http.netty.NettyQueryParamEncoding import org.openjdk.jmh.annotations._ @State(org.openjdk.jmh.annotations.Scope.Thread) -@BenchmarkMode(Array(Mode.Throughput)) +@BenchmarkMode(Array(org.openjdk.jmh.annotations.Mode.Throughput)) @OutputTimeUnit(TimeUnit.SECONDS) class QueryEncodingBenchmark { diff --git a/zio-http-testkit/src/main/scala/zio/http/HttpTestAspect.scala b/zio-http-testkit/src/main/scala/zio/http/HttpTestAspect.scala new file mode 100644 index 0000000000..bba605359c --- /dev/null +++ b/zio-http-testkit/src/main/scala/zio/http/HttpTestAspect.scala @@ -0,0 +1,26 @@ +package zio.http + +import zio._ +import zio.test._ + +object HttpTestAspect { + + private def withMode(mode: Mode): TestAspectAtLeastR[Scope] = + TestAspect.aroundWith( + ZIO.succeed { + val previous = Mode.current + java.lang.System.setProperty("zio.http.mode", mode.toString) + previous + }, + )((restorePrevious: Mode) => ZIO.succeed(java.lang.System.setProperty("zio.http.mode", restorePrevious.toString))) + + val devMode: TestAspectAtLeastR[Scope] = + withMode(Mode.Dev) + + val prodMode: TestAspectAtLeastR[Scope] = + withMode(Mode.Prod) + + val preprodMode: TestAspectAtLeastR[Scope] = + withMode(Mode.Preprod) + +} diff --git a/zio-http-testkit/src/test/scala/zio/http/HttpTestAspectSpec.scala b/zio-http-testkit/src/test/scala/zio/http/HttpTestAspectSpec.scala new file mode 100644 index 0000000000..0f3ba3a155 --- /dev/null +++ b/zio-http-testkit/src/test/scala/zio/http/HttpTestAspectSpec.scala @@ -0,0 +1,14 @@ +package zio.http + +import zio.test._ + +object HttpTestAspectSpec extends ZIOSpecDefault { + def spec = suite("HttpTestAspectSpec")( + test("Preprod is enabled vai test aspect") { + assertTrue(Mode.current == Mode.Preprod) + } @@ HttpTestAspect.preprodMode, + test("Prod is enabled via test aspect") { + assertTrue(Mode.current == Mode.Prod) + } @@ HttpTestAspect.prodMode, + ) @@ TestAspect.sequential +} diff --git a/zio-http/jvm/src/test/scala/zio/http/ModeSpec.scala b/zio-http/jvm/src/test/scala/zio/http/ModeSpec.scala new file mode 100644 index 0000000000..130ddd23cc --- /dev/null +++ b/zio-http/jvm/src/test/scala/zio/http/ModeSpec.scala @@ -0,0 +1,11 @@ +package zio.http + +import zio.test._ + +object ModeSpec extends ZIOSpecDefault { + override def spec = suite("ModeSpec")( + test("Mode should be Test") { + assertTrue(Mode.Dev.isActive) + }, + ) +} diff --git a/zio-http/shared/src/main/scala/zio/http/Mode.scala b/zio-http/shared/src/main/scala/zio/http/Mode.scala new file mode 100644 index 0000000000..e7d72ab065 --- /dev/null +++ b/zio-http/shared/src/main/scala/zio/http/Mode.scala @@ -0,0 +1,36 @@ +package zio.http + +sealed trait Mode extends Product with Serializable { self => + def isActive: Boolean = Mode.current == self +} + +object Mode { + + def current: Mode = { + val prop = java.lang.System.getProperty("zio.http.mode") + val env = java.lang.System.getenv("ZIO_HTTP_MODE") + if (prop != null) fromString(prop) + else if (env != null) fromString(env) + else Dev + } + + private def fromString(str: String): Mode = + if (str.equalsIgnoreCase("dev")) Dev + else if (str.equalsIgnoreCase("preprod")) Preprod + else if (str.equalsIgnoreCase("prod")) Prod + else { + Console.err.println( + s"[WARN] Unknown mode '$str', supported modes are 'dev', 'preprod' and 'prod'. Falling back to 'dev'.", + ) + Dev + } + + def isDev: Boolean = current == Dev + def isPreprod: Boolean = current == Preprod + def isProd: Boolean = current == Prod + + case object Dev extends Mode + case object Preprod extends Mode + case object Prod extends Mode + +}