zio-http icon indicating copy to clipboard operation
zio-http copied to clipboard

implicitly support same encoders as decoders for `PathCodec` and `SegmentCodec`

Open dontgitit opened this issue 6 months ago • 3 comments

Is your feature request related to a problem? Please describe. Not exactly a problem, but when creating RoutePatterns, you can do something like the following:

val pattern: RoutePattern[(Int, Long, String, UUID, Boolean)] =
  Method.GET / int("someInt") / long("someLong") / string("someString") / uuid("someUuid") / boolean("someBoolean")

To create a similar route with some of those parameters bound, you can do something like:

def createPattern(someLong: Long, someUuid: UUID) = {
  // literal strings such as `someUuid.toString` are encoded via `implicit PathCodec.path`
  Method.GET / int("someInt") / PathCodec.literal(someLong.toString) / string("someString") / someUuid.toString / boolean("someBoolean")
}

note that for non-string literal values, you need to either

  1. provide implicit PathCodec
  2. use PathCodec.literal with a string value
  3. convert your value to a string first and then let it be converted to a PathCodec via the implicit PathCodec.path

Describe the solution you'd like

it feels like for every type that has Codecs available for binding (via int, long, uuid, boolean), it'd be nice if there was a matching literal encoder, for example

object PathCodec {
  implicit def literalInt(value: Int) = literal(value.toString)
  implicit def literalLong(value: Long) = literal(value.toString)
  implicit def literalBoolean(value: Boolean) = literal(value.toString)
  implicit def literalUuid(value: UUID) = literal(value.toString)
}

then the createPattern method above could look like:

def createPattern(someLong: Long, someUuid: UUID) = {
  Method.GET / int("someInt") / someLong / string("someString") / someUuid / boolean("someBoolean")
}

Describe alternatives you've considered Not sure.

Additional context I think it's also fine as is, but to me seems strange that it's not symmetrical.

dontgitit avatar Jun 08 '25 23:06 dontgitit

I get the idea, but I wonder what is your use case for

def createPattern(someLong: Long, someUuid: UUID) = {
  // literal strings such as `someUuid.toString` are encoded via `implicit PathCodec.path`
  Method.GET / int("someInt") / PathCodec.literal(someLong.toString) / string("someString") / someUuid.toString / boolean("someBoolean")
}

I am happy to find a solution, if your use case seems valid.

987Nabil avatar Jun 09 '25 10:06 987Nabil

I'm using it to build RoutePatterns as well as getting paths for a URI for redirects!

I have three projects - a project (library) that contains a shared route, a project (application) that handles the route, and another project (application) that redirects users to that route.

the shared project looks something like this:

// `ServiceProvider` is an `enumeratum` enum, and I created a custom `PathCodec` for it since we use it all the time

def serviceProvider(name: String): PathCodec[ServiceProvider] =
  string(name).transform(ServiceProvider.withName)(_.entryName)

object SharedRoutes {
  val route: RoutePattern[(ServiceProvider, UUID)] =
    Method.POST / serviceProvider("serviceProvider") / uuid("integrationId")
}

One project/application serves that route:

class ShopifyRoutes {
  val routes = Routes(
    // I'd love to use `SharedRoutes.route` here, but currently I have a separate handler for each ServiceProvider
    //  SharedRoutes.route -> Handler.fromFunctionZIO[(ServiceProvider, UUID, Request)] {
    Method.POST / ServiceProvider.Slack / uuid("integrationId") -> Handler.fromFunctionZIO[(UUID, Request)] {
      ???  
    }
  )
}

I have a different application that provides redirect URLs to the first application. I build such a URL via:

def getResourcePath(serviceProvider: ServiceProvider, resourceId: UUID): String = {
  SharedRoutes.route.encode(serviceProvider, resourceId).encode
  // or, 
  // val path = PathCodec.empty / serviceProvider / resourceId
  // path.render
}

hopefully that makes sense. both ShopifyRoutes.routes and getResourcePath could use implicits like

  implicit def path(serviceProvider: ServiceProvider): PathCodec[Unit] = PathCodec.literal(serviceProvider.entryName)
  implicit def path(uuid: UUID): PathCodec[Unit] = PathCodec.literal(uuid.toString)

to encode values into a route

dontgitit avatar Jun 10 '25 05:06 dontgitit

We will consider this concern in the design decision that will be made for the 4.0 release

987Nabil avatar Nov 30 '25 08:11 987Nabil