Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for sparse collections in query params #1513

Open
wants to merge 13 commits into
base: series/0.19
Choose a base branch
from
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ Thank you!

# 0.19.0

## Update HttpUri model to allow optional query parameters in [1513](https://github.com/disneystreaming/smithy4s/pull/1513)

Change the type of query parameters in `HttpUri` from `queryParams: Map[String, Seq[String]]` to `queryParams: Map[String, Seq[Option[String]]]` to allow
modeling of sparse collections.

## Documentation fix

Prevent documentation from being generated for case class when the field are not generated because they're annotated with `@streaming`
Expand Down
2 changes: 1 addition & 1 deletion modules/aws-http4s/src/smithy4s/aws/AwsClient.scala
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ object AwsClient {
host = Some(s"$endpointPrefix.$region.amazonaws.com"),
port = None,
path = IndexedSeq.empty,
queryParams = Map.empty,
queryParams = IndexedSeq.empty,
pathParams = None
)
// Uri.unsafeFromString(s"https://$endpointPrefix.$region.amazonaws.com/")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
{
"openapi": "3.0.2",
"info": {
"title": "ServiceWithSparseQueryParams",
"version": "1.0"
},
"paths": {
"/operation/sparse-query-params": {
"get": {
"operationId": "GetOperation",
"parameters": [
{
"name": "foo",
"in": "query",
"style": "form",
"schema": {
"type": "array",
"items": {
"type": "string"
}
},
"explode": true,
"required": true
}
],
"responses": {
"200": {
"description": "GetOperation 200 response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GetOperationResponseContent"
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"GetOperationResponseContent": {
"type": "object",
"properties": {
"foo": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"foo"
]
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package smithy4s.example

import smithy4s.Endpoint
import smithy4s.Hints
import smithy4s.Schema
import smithy4s.Service
import smithy4s.ShapeId
import smithy4s.Transformation
import smithy4s.kinds.PolyFunction5
import smithy4s.kinds.toPolyFunction5.const5
import smithy4s.schema.OperationSchema

trait ServiceWithSparseQueryParamsGen[F[_, _, _, _, _]] {
self =>

def getOperation(foo: List[Option[String]]): F[SparseQueryInput, Nothing, SparseQueryOutput, Nothing, Nothing]

def transform: Transformation.PartiallyApplied[ServiceWithSparseQueryParamsGen[F]] = Transformation.of[ServiceWithSparseQueryParamsGen[F]](this)
}

object ServiceWithSparseQueryParamsGen extends Service.Mixin[ServiceWithSparseQueryParamsGen, ServiceWithSparseQueryParamsOperation] {

val id: ShapeId = ShapeId("smithy4s.example", "ServiceWithSparseQueryParams")
val version: String = "1.0"

val hints: Hints = Hints(
alloy.SimpleRestJson(),
).lazily

def apply[F[_]](implicit F: Impl[F]): F.type = F

object ErrorAware {
def apply[F[_, _]](implicit F: ErrorAware[F]): F.type = F
type Default[F[+_, +_]] = Constant[smithy4s.kinds.stubs.Kind2[F]#toKind5]
}

val endpoints: Vector[smithy4s.Endpoint[ServiceWithSparseQueryParamsOperation, _, _, _, _, _]] = Vector(
ServiceWithSparseQueryParamsOperation.GetOperation,
)

def input[I, E, O, SI, SO](op: ServiceWithSparseQueryParamsOperation[I, E, O, SI, SO]): I = op.input
def ordinal[I, E, O, SI, SO](op: ServiceWithSparseQueryParamsOperation[I, E, O, SI, SO]): Int = op.ordinal
override def endpoint[I, E, O, SI, SO](op: ServiceWithSparseQueryParamsOperation[I, E, O, SI, SO]) = op.endpoint
class Constant[P[-_, +_, +_, +_, +_]](value: P[Any, Nothing, Nothing, Nothing, Nothing]) extends ServiceWithSparseQueryParamsOperation.Transformed[ServiceWithSparseQueryParamsOperation, P](reified, const5(value))
type Default[F[+_]] = Constant[smithy4s.kinds.stubs.Kind1[F]#toKind5]
def reified: ServiceWithSparseQueryParamsGen[ServiceWithSparseQueryParamsOperation] = ServiceWithSparseQueryParamsOperation.reified
def mapK5[P[_, _, _, _, _], P1[_, _, _, _, _]](alg: ServiceWithSparseQueryParamsGen[P], f: PolyFunction5[P, P1]): ServiceWithSparseQueryParamsGen[P1] = new ServiceWithSparseQueryParamsOperation.Transformed(alg, f)
def fromPolyFunction[P[_, _, _, _, _]](f: PolyFunction5[ServiceWithSparseQueryParamsOperation, P]): ServiceWithSparseQueryParamsGen[P] = new ServiceWithSparseQueryParamsOperation.Transformed(reified, f)
def toPolyFunction[P[_, _, _, _, _]](impl: ServiceWithSparseQueryParamsGen[P]): PolyFunction5[ServiceWithSparseQueryParamsOperation, P] = ServiceWithSparseQueryParamsOperation.toPolyFunction(impl)

}

sealed trait ServiceWithSparseQueryParamsOperation[Input, Err, Output, StreamedInput, StreamedOutput] {
def run[F[_, _, _, _, _]](impl: ServiceWithSparseQueryParamsGen[F]): F[Input, Err, Output, StreamedInput, StreamedOutput]
def ordinal: Int
def input: Input
def endpoint: Endpoint[ServiceWithSparseQueryParamsOperation, Input, Err, Output, StreamedInput, StreamedOutput]
}

object ServiceWithSparseQueryParamsOperation {

object reified extends ServiceWithSparseQueryParamsGen[ServiceWithSparseQueryParamsOperation] {
def getOperation(foo: List[Option[String]]): GetOperation = GetOperation(SparseQueryInput(foo))
}
class Transformed[P[_, _, _, _, _], P1[_ ,_ ,_ ,_ ,_]](alg: ServiceWithSparseQueryParamsGen[P], f: PolyFunction5[P, P1]) extends ServiceWithSparseQueryParamsGen[P1] {
def getOperation(foo: List[Option[String]]): P1[SparseQueryInput, Nothing, SparseQueryOutput, Nothing, Nothing] = f[SparseQueryInput, Nothing, SparseQueryOutput, Nothing, Nothing](alg.getOperation(foo))
}

def toPolyFunction[P[_, _, _, _, _]](impl: ServiceWithSparseQueryParamsGen[P]): PolyFunction5[ServiceWithSparseQueryParamsOperation, P] = new PolyFunction5[ServiceWithSparseQueryParamsOperation, P] {
def apply[I, E, O, SI, SO](op: ServiceWithSparseQueryParamsOperation[I, E, O, SI, SO]): P[I, E, O, SI, SO] = op.run(impl)
}
final case class GetOperation(input: SparseQueryInput) extends ServiceWithSparseQueryParamsOperation[SparseQueryInput, Nothing, SparseQueryOutput, Nothing, Nothing] {
def run[F[_, _, _, _, _]](impl: ServiceWithSparseQueryParamsGen[F]): F[SparseQueryInput, Nothing, SparseQueryOutput, Nothing, Nothing] = impl.getOperation(input.foo)
def ordinal: Int = 0
def endpoint: smithy4s.Endpoint[ServiceWithSparseQueryParamsOperation,SparseQueryInput, Nothing, SparseQueryOutput, Nothing, Nothing] = GetOperation
}
object GetOperation extends smithy4s.Endpoint[ServiceWithSparseQueryParamsOperation,SparseQueryInput, Nothing, SparseQueryOutput, Nothing, Nothing] {
val schema: OperationSchema[SparseQueryInput, Nothing, SparseQueryOutput, Nothing, Nothing] = Schema.operation(ShapeId("smithy4s.example", "GetOperation"))
.withInput(SparseQueryInput.schema)
.withOutput(SparseQueryOutput.schema)
.withHints(smithy.api.Http(method = smithy.api.NonEmptyString("GET"), uri = smithy.api.NonEmptyString("/operation/sparse-query-params"), code = 200))
def wrap(input: SparseQueryInput): GetOperation = GetOperation(input)
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package smithy4s.example

import smithy4s.Hints
import smithy4s.Schema
import smithy4s.ShapeId
import smithy4s.ShapeTag
import smithy4s.schema.Schema.struct

final case class SparseQueryInput(foo: List[Option[String]])

object SparseQueryInput extends ShapeTag.Companion[SparseQueryInput] {
val id: ShapeId = ShapeId("smithy4s.example", "SparseQueryInput")

val hints: Hints = Hints.empty

// constructor using the original order from the spec
private def make(foo: List[Option[String]]): SparseQueryInput = SparseQueryInput(foo)

implicit val schema: Schema[SparseQueryInput] = struct(
SparseStringList.underlyingSchema.required[SparseQueryInput]("foo", _.foo).addHints(smithy.api.HttpQuery("foo")),
)(make).withId(id).addHints(hints)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package smithy4s.example

import smithy4s.Hints
import smithy4s.Schema
import smithy4s.ShapeId
import smithy4s.ShapeTag
import smithy4s.schema.Schema.struct

final case class SparseQueryOutput(foo: List[Option[String]])

object SparseQueryOutput extends ShapeTag.Companion[SparseQueryOutput] {
val id: ShapeId = ShapeId("smithy4s.example", "SparseQueryOutput")

val hints: Hints = Hints.empty

// constructor using the original order from the spec
private def make(foo: List[Option[String]]): SparseQueryOutput = SparseQueryOutput(foo)

implicit val schema: Schema[SparseQueryOutput] = struct(
SparseStringList.underlyingSchema.required[SparseQueryOutput]("foo", _.foo),
)(make).withId(id).addHints(hints)
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ package object example {
val PizzaAdminService = PizzaAdminServiceGen
type FooService[F[_]] = smithy4s.kinds.FunctorAlgebra[FooServiceGen, F]
val FooService = FooServiceGen
type ServiceWithSparseQueryParams[F[_]] = smithy4s.kinds.FunctorAlgebra[ServiceWithSparseQueryParamsGen, F]
val ServiceWithSparseQueryParams = ServiceWithSparseQueryParamsGen
type KVStore[F[_]] = smithy4s.kinds.FunctorAlgebra[KVStoreGen, F]
val KVStore = KVStoreGen
type ObjectService[F[_]] = smithy4s.kinds.FunctorAlgebra[ObjectServiceGen, F]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ final class HttpRequestSpec extends FunSuite {
Some("example.com"),
None,
IndexedSeq.empty,
Map.empty,
IndexedSeq.empty,
None
)
val request = HttpRequest(HttpMethod.GET, uri, Map.empty, "")
Expand Down
Loading