From e8814b3f44c05c716e3bd69d96cf7a7618303864 Mon Sep 17 00:00:00 2001 From: harshtech123 <139060630+harshtech123@users.noreply.github.com> Date: Sat, 9 Nov 2024 10:58:35 +0000 Subject: [PATCH] added a Schema based query param access in QueryGetters\QueryModifier --- .../zio/http/internal/QueryGetters.scala | 46 +++++++++---------- .../zio/http/internal/QueryModifier.scala | 28 ++++++----- 2 files changed, 40 insertions(+), 34 deletions(-) diff --git a/zio-http/shared/src/main/scala/zio/http/internal/QueryGetters.scala b/zio-http/shared/src/main/scala/zio/http/internal/QueryGetters.scala index 957098c37e..bc841286f9 100644 --- a/zio-http/shared/src/main/scala/zio/http/internal/QueryGetters.scala +++ b/zio-http/shared/src/main/scala/zio/http/internal/QueryGetters.scala @@ -32,24 +32,26 @@ trait QueryGetters[+A] { self: QueryOps[A] => queryParameters.getAll(key) /** - * Retrieves all typed query parameter values having the specified name. + * Retrieves all typed query parameter values having the specified name, with schema validation. */ - def queryParamsTo[T](key: String)(implicit codec: TextCodec[T]): Either[QueryParamsError, Chunk[T]] = + def queryParamsTo[T](key: String, schema: Option[TextCodec[T]] = None) + (implicit codec: TextCodec[T]): Either[QueryParamsError, Chunk[T]] = for { params <- if (hasQueryParam(key)) Right(queryParams(key)) else Left(QueryParamsError.Missing(key)) - (failed, typed) = params.partitionMap(p => codec.decode(p).toRight(p)) + validatedCodec = schema.getOrElse(codec) + (failed, typed) = params.partitionMap(p => validatedCodec.decode(p).toRight(p)) result <- NonEmptyChunk .fromChunk(failed) - .map(fails => QueryParamsError.Malformed(key, codec, fails)) + .map(fails => QueryParamsError.Malformed(key, validatedCodec, fails)) .toLeft(typed) } yield result /** - * Retrieves all typed query parameter values having the specified name as - * ZIO. + * Retrieves all typed query parameter values having the specified name as ZIO, with schema validation. */ - def queryParamsToZIO[T](key: String)(implicit codec: TextCodec[T]): IO[QueryParamsError, Chunk[T]] = - ZIO.fromEither(queryParamsTo[T](key)) + def queryParamsToZIO[T](key: String, schema: Option[TextCodec[T]] = None) + (implicit codec: TextCodec[T]): IO[QueryParamsError, Chunk[T]] = + ZIO.fromEither(queryParamsTo[T](key, schema)) /** * Retrieves the first query parameter value having the specified name. @@ -58,44 +60,42 @@ trait QueryGetters[+A] { self: QueryOps[A] => if (hasQueryParam(key)) Some(queryParams(key).head) else None /** - * Retrieves the first typed query parameter value having the specified name. + * Retrieves the first typed query parameter value having the specified name, with schema validation. */ - def queryParamTo[T](key: String)(implicit codec: TextCodec[T]): Either[QueryParamsError, T] = for { + def queryParamTo[T](key: String, schema: Option[TextCodec[T]] = None) + (implicit codec: TextCodec[T]): Either[QueryParamsError, T] = for { param <- queryParam(key).toRight(QueryParamsError.Missing(key)) - typedParam <- codec.decode(param).toRight(QueryParamsError.Malformed(key, codec, NonEmptyChunk(param))) + validatedCodec = schema.getOrElse(codec) + typedParam <- validatedCodec.decode(param).toRight(QueryParamsError.Malformed(key, validatedCodec, NonEmptyChunk(param))) } yield typedParam /** - * Retrieves the first typed query parameter value having the specified name - * as ZIO. + * Retrieves the first typed query parameter value having the specified name as ZIO, with schema validation. */ - def queryParamToZIO[T](key: String)(implicit codec: TextCodec[T]): IO[QueryParamsError, T] = - ZIO.fromEither(queryParamTo[T](key)) + def queryParamToZIO[T](key: String, schema: Option[TextCodec[T]] = None) + (implicit codec: TextCodec[T]): IO[QueryParamsError, T] = + ZIO.fromEither(queryParamTo[T](key, schema)) /** - * Retrieves all query parameter values having the specified name, or else - * uses the default iterable. + * Retrieves all query parameter values having the specified name, or else uses the default iterable. */ def queryParamsOrElse(key: String, default: => Iterable[String]): Chunk[String] = if (hasQueryParam(key)) queryParams(key) else Chunk.fromIterable(default) /** - * Retrieves all query parameter values having the specified name, or else - * uses the default iterable. + * Retrieves all query parameter values having the specified name, or else uses the default iterable. */ def queryParamsToOrElse[T](key: String, default: => Iterable[T])(implicit codec: TextCodec[T]): Chunk[T] = queryParamsTo[T](key).getOrElse(Chunk.fromIterable(default)) /** - * Retrieves the first query parameter value having the specified name, or - * else uses the default value. + * Retrieves the first query parameter value having the specified name, or else uses the default value. */ def queryParamOrElse(key: String, default: => String): String = queryParam(key).getOrElse(default) /** - * Retrieves the first typed query parameter value having the specified name, - * or else uses the default value. + * Retrieves the first typed query parameter value having the specified name, or else uses the default value. */ def queryParamToOrElse[T](key: String, default: => T)(implicit codec: TextCodec[T]): T = queryParamTo[T](key).getOrElse(default) diff --git a/zio-http/shared/src/main/scala/zio/http/internal/QueryModifier.scala b/zio-http/shared/src/main/scala/zio/http/internal/QueryModifier.scala index 77996c462e..974a9a1945 100644 --- a/zio-http/shared/src/main/scala/zio/http/internal/QueryModifier.scala +++ b/zio-http/shared/src/main/scala/zio/http/internal/QueryModifier.scala @@ -19,31 +19,37 @@ package zio.http.internal import zio.Chunk import zio.http.QueryParams +import zio.http.codec.TextCodec trait QueryModifier[+A] { self: QueryOps[A] with A => /** - * Combines two collections of query parameters together. If there are - * duplicate keys, the values from both sides are preserved, in order from - * left-to-right. + * Combines two collections of query parameters together. If there are duplicate keys, + * the values from both sides are preserved, in order from left-to-right. */ def ++(that: QueryParams): A = updateQueryParams(params => QueryParams.fromEntries(params.seq ++ that.seq: _*)) /** - * Adds the specified key/value pair to the query parameters. + * Adds the specified key/value pair to the query parameters with optional schema validation. */ - def addQueryParam(key: String, value: String): A = - addQueryParams(key, Chunk(value)) + def addQueryParam[T](key: String, value: T, schema: Option[TextCodec[T]] = None)(implicit codec: TextCodec[T]): Either[QueryParamsError, A] = { + val validatedCodec = schema.getOrElse(codec) + validatedCodec.encode(value) match { + case Left(_) => Left(QueryParamsError.Malformed(key, validatedCodec, NonEmptyChunk(value.toString))) + case Right(encodedValue) => Right(addQueryParams(key, Chunk(encodedValue))) + } + } /** - * Adds the specified key/value pairs to the query parameters. + * Adds the specified key/value pairs to the query parameters with optional schema validation. */ - def addQueryParams(key: String, value: Chunk[String]): A = - updateQueryParams(params => params ++ QueryParams(key -> value)) + def addQueryParams[T](key: String, values: Chunk[T], schema: Option[TextCodec[T]] = None)(implicit codec: TextCodec[T]): Either[QueryParamsError, A] = { + val validatedCodec = schema.getOrElse(codec) + val encodedValues = values.map(validatedCodec.encode(_).toOption.getOrElse("")) - def addQueryParams(values: String): A = - updateQueryParams(params => params ++ QueryParams.decode(values)) + Right(updateQueryParams(params => params ++ QueryParams(key -> encodedValues))) + } /** * Removes the specified key from the query parameters.