-
Notifications
You must be signed in to change notification settings - Fork 68
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
Default handling #1315
Default handling #1315
Changes from 4 commits
b360577
a8b545b
0df9929
1accdd7
02ad146
87881d6
60ccc6c
107eb97
6fe578c
eb5f1b9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
{ | ||
"openapi": "3.0.2", | ||
"info": { | ||
"title": "ServiceWithNullsAndDefaults", | ||
"version": "1.0.0" | ||
}, | ||
"paths": { | ||
"/operation/{requiredLabel}": { | ||
"post": { | ||
"operationId": "Operation", | ||
"requestBody": { | ||
"content": { | ||
"application/json": { | ||
"schema": { | ||
"$ref": "#/components/schemas/OperationRequestContent" | ||
} | ||
} | ||
}, | ||
"required": true | ||
}, | ||
"parameters": [ | ||
{ | ||
"name": "requiredLabel", | ||
"in": "path", | ||
"schema": { | ||
"type": "string", | ||
"default": "required-label-with-default" | ||
}, | ||
"required": true | ||
}, | ||
{ | ||
"name": "optional-query", | ||
"in": "query", | ||
"schema": { | ||
"type": "string" | ||
} | ||
}, | ||
{ | ||
"name": "optional-query-with-default", | ||
"in": "query", | ||
"schema": { | ||
"type": "string", | ||
"default": "optional-query-with-default" | ||
} | ||
}, | ||
{ | ||
"name": "required-query-with-default", | ||
"in": "query", | ||
"schema": { | ||
"type": "string", | ||
"default": "required-query-with-default" | ||
} | ||
}, | ||
{ | ||
"name": "optional-header", | ||
"in": "header", | ||
"schema": { | ||
"type": "string" | ||
} | ||
}, | ||
{ | ||
"name": "optional-header-with-default", | ||
"in": "header", | ||
"schema": { | ||
"type": "string", | ||
"default": "optional-header-with-default" | ||
} | ||
}, | ||
{ | ||
"name": "required-header-with-default", | ||
"in": "header", | ||
"schema": { | ||
"type": "string", | ||
"default": "required-header-with-default" | ||
}, | ||
"required": true | ||
} | ||
], | ||
"responses": { | ||
"200": { | ||
"description": "Operation 200 response", | ||
"headers": { | ||
"optional-header": { | ||
"schema": { | ||
"type": "string" | ||
} | ||
}, | ||
"optional-header-with-default": { | ||
"schema": { | ||
"type": "string", | ||
"default": "optional-header-with-default" | ||
} | ||
}, | ||
"required-header-with-default": { | ||
"schema": { | ||
"type": "string", | ||
"default": "required-header-with-default" | ||
}, | ||
"required": true | ||
} | ||
}, | ||
"content": { | ||
"application/json": { | ||
"schema": { | ||
"$ref": "#/components/schemas/OperationResponseContent" | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}, | ||
"components": { | ||
"schemas": { | ||
"OperationRequestContent": { | ||
"type": "object", | ||
"properties": { | ||
"optional": { | ||
"type": "string" | ||
}, | ||
"optionalWithDefault": { | ||
"type": "string", | ||
"default": "optional-default" | ||
}, | ||
"requiredWithDefault": { | ||
"type": "string", | ||
"default": "required-default" | ||
} | ||
}, | ||
"required": [ | ||
"requiredWithDefault" | ||
] | ||
}, | ||
"OperationResponseContent": { | ||
"type": "object", | ||
"properties": { | ||
"optional": { | ||
"type": "string" | ||
}, | ||
"optionalWithDefault": { | ||
"type": "string", | ||
"default": "optional-default" | ||
}, | ||
"requiredWithDefault": { | ||
"type": "string", | ||
"default": "required-default" | ||
} | ||
}, | ||
"required": [ | ||
"requiredWithDefault" | ||
] | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package smithy4s.example | ||
|
||
import smithy4s.Hints | ||
import smithy4s.Schema | ||
import smithy4s.ShapeId | ||
import smithy4s.ShapeTag | ||
import smithy4s.schema.Schema.string | ||
import smithy4s.schema.Schema.struct | ||
|
||
final case class OperationInput(optionalWithDefault: String = "optional-default", requiredLabel: String = "required-label-with-default", requiredWithDefault: String = "required-default", optionalHeaderWithDefault: String = "optional-header-with-default", requiredHeaderWithDefault: String = "required-header-with-default", optionalQueryWithDefault: String = "optional-query-with-default", requiredQueryWithDefault: String = "required-query-with-default", optional: Option[String] = None, optionalHeader: Option[String] = None, optionalQuery: Option[String] = None) | ||
|
||
object OperationInput extends ShapeTag.Companion[OperationInput] { | ||
val id: ShapeId = ShapeId("smithy4s.example", "OperationInput") | ||
|
||
val hints: Hints = Hints( | ||
smithy.api.Input(), | ||
) | ||
|
||
implicit val schema: Schema[OperationInput] = struct( | ||
string.field[OperationInput]("optionalWithDefault", _.optionalWithDefault).addHints(smithy.api.Default(smithy4s.Document.fromString("optional-default"))), | ||
string.required[OperationInput]("requiredLabel", _.requiredLabel).addHints(smithy.api.Default(smithy4s.Document.fromString("required-label-with-default")), smithy.api.HttpLabel()), | ||
string.required[OperationInput]("requiredWithDefault", _.requiredWithDefault).addHints(smithy.api.Default(smithy4s.Document.fromString("required-default"))), | ||
string.field[OperationInput]("optionalHeaderWithDefault", _.optionalHeaderWithDefault).addHints(smithy.api.Default(smithy4s.Document.fromString("optional-header-with-default")), smithy.api.HttpHeader("optional-header-with-default")), | ||
string.required[OperationInput]("requiredHeaderWithDefault", _.requiredHeaderWithDefault).addHints(smithy.api.Default(smithy4s.Document.fromString("required-header-with-default")), smithy.api.HttpHeader("required-header-with-default")), | ||
string.field[OperationInput]("optionalQueryWithDefault", _.optionalQueryWithDefault).addHints(smithy.api.Default(smithy4s.Document.fromString("optional-query-with-default")), smithy.api.HttpQuery("optional-query-with-default")), | ||
string.field[OperationInput]("requiredQueryWithDefault", _.requiredQueryWithDefault).addHints(smithy.api.Default(smithy4s.Document.fromString("required-query-with-default")), smithy.api.HttpQuery("required-query-with-default")), | ||
string.optional[OperationInput]("optional", _.optional), | ||
string.optional[OperationInput]("optionalHeader", _.optionalHeader).addHints(smithy.api.HttpHeader("optional-header")), | ||
string.optional[OperationInput]("optionalQuery", _.optionalQuery).addHints(smithy.api.HttpQuery("optional-query")), | ||
){ | ||
OperationInput.apply | ||
}.withId(id).addHints(hints) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package smithy4s.example | ||
|
||
import smithy4s.Hints | ||
import smithy4s.Schema | ||
import smithy4s.ShapeId | ||
import smithy4s.ShapeTag | ||
import smithy4s.schema.Schema.string | ||
import smithy4s.schema.Schema.struct | ||
|
||
final case class OperationOutput(optionalWithDefault: String = "optional-default", requiredWithDefault: String = "required-default", optionalHeaderWithDefault: String = "optional-header-with-default", requiredHeaderWithDefault: String = "required-header-with-default", optional: Option[String] = None, optionalHeader: Option[String] = None) | ||
|
||
object OperationOutput extends ShapeTag.Companion[OperationOutput] { | ||
val id: ShapeId = ShapeId("smithy4s.example", "OperationOutput") | ||
|
||
val hints: Hints = Hints( | ||
smithy.api.Output(), | ||
) | ||
|
||
implicit val schema: Schema[OperationOutput] = struct( | ||
string.field[OperationOutput]("optionalWithDefault", _.optionalWithDefault).addHints(smithy.api.Default(smithy4s.Document.fromString("optional-default"))), | ||
string.required[OperationOutput]("requiredWithDefault", _.requiredWithDefault).addHints(smithy.api.Default(smithy4s.Document.fromString("required-default"))), | ||
string.field[OperationOutput]("optionalHeaderWithDefault", _.optionalHeaderWithDefault).addHints(smithy.api.Default(smithy4s.Document.fromString("optional-header-with-default")), smithy.api.HttpHeader("optional-header-with-default")), | ||
string.required[OperationOutput]("requiredHeaderWithDefault", _.requiredHeaderWithDefault).addHints(smithy.api.Default(smithy4s.Document.fromString("required-header-with-default")), smithy.api.HttpHeader("required-header-with-default")), | ||
string.optional[OperationOutput]("optional", _.optional), | ||
string.optional[OperationOutput]("optionalHeader", _.optionalHeader).addHints(smithy.api.HttpHeader("optional-header")), | ||
){ | ||
OperationOutput.apply | ||
}.withId(id).addHints(hints) | ||
} |
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 ServiceWithNullsAndDefaultsGen[F[_, _, _, _, _]] { | ||
self => | ||
|
||
def operation(input: OperationInput): F[OperationInput, Nothing, OperationOutput, Nothing, Nothing] | ||
|
||
def transform: Transformation.PartiallyApplied[ServiceWithNullsAndDefaultsGen[F]] = Transformation.of[ServiceWithNullsAndDefaultsGen[F]](this) | ||
} | ||
|
||
object ServiceWithNullsAndDefaultsGen extends Service.Mixin[ServiceWithNullsAndDefaultsGen, ServiceWithNullsAndDefaultsOperation] { | ||
|
||
val id: ShapeId = ShapeId("smithy4s.example", "ServiceWithNullsAndDefaults") | ||
val version: String = "1.0.0" | ||
|
||
val hints: Hints = Hints( | ||
alloy.SimpleRestJson(), | ||
) | ||
|
||
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[ServiceWithNullsAndDefaultsOperation, _, _, _, _, _]] = Vector( | ||
ServiceWithNullsAndDefaultsOperation.Operation, | ||
) | ||
|
||
def input[I, E, O, SI, SO](op: ServiceWithNullsAndDefaultsOperation[I, E, O, SI, SO]): I = op.input | ||
def ordinal[I, E, O, SI, SO](op: ServiceWithNullsAndDefaultsOperation[I, E, O, SI, SO]): Int = op.ordinal | ||
override def endpoint[I, E, O, SI, SO](op: ServiceWithNullsAndDefaultsOperation[I, E, O, SI, SO]) = op.endpoint | ||
class Constant[P[-_, +_, +_, +_, +_]](value: P[Any, Nothing, Nothing, Nothing, Nothing]) extends ServiceWithNullsAndDefaultsOperation.Transformed[ServiceWithNullsAndDefaultsOperation, P](reified, const5(value)) | ||
type Default[F[+_]] = Constant[smithy4s.kinds.stubs.Kind1[F]#toKind5] | ||
def reified: ServiceWithNullsAndDefaultsGen[ServiceWithNullsAndDefaultsOperation] = ServiceWithNullsAndDefaultsOperation.reified | ||
def mapK5[P[_, _, _, _, _], P1[_, _, _, _, _]](alg: ServiceWithNullsAndDefaultsGen[P], f: PolyFunction5[P, P1]): ServiceWithNullsAndDefaultsGen[P1] = new ServiceWithNullsAndDefaultsOperation.Transformed(alg, f) | ||
def fromPolyFunction[P[_, _, _, _, _]](f: PolyFunction5[ServiceWithNullsAndDefaultsOperation, P]): ServiceWithNullsAndDefaultsGen[P] = new ServiceWithNullsAndDefaultsOperation.Transformed(reified, f) | ||
def toPolyFunction[P[_, _, _, _, _]](impl: ServiceWithNullsAndDefaultsGen[P]): PolyFunction5[ServiceWithNullsAndDefaultsOperation, P] = ServiceWithNullsAndDefaultsOperation.toPolyFunction(impl) | ||
|
||
} | ||
|
||
sealed trait ServiceWithNullsAndDefaultsOperation[Input, Err, Output, StreamedInput, StreamedOutput] { | ||
def run[F[_, _, _, _, _]](impl: ServiceWithNullsAndDefaultsGen[F]): F[Input, Err, Output, StreamedInput, StreamedOutput] | ||
def ordinal: Int | ||
def input: Input | ||
def endpoint: Endpoint[ServiceWithNullsAndDefaultsOperation, Input, Err, Output, StreamedInput, StreamedOutput] | ||
} | ||
|
||
object ServiceWithNullsAndDefaultsOperation { | ||
|
||
object reified extends ServiceWithNullsAndDefaultsGen[ServiceWithNullsAndDefaultsOperation] { | ||
def operation(input: OperationInput): Operation = Operation(input) | ||
} | ||
class Transformed[P[_, _, _, _, _], P1[_ ,_ ,_ ,_ ,_]](alg: ServiceWithNullsAndDefaultsGen[P], f: PolyFunction5[P, P1]) extends ServiceWithNullsAndDefaultsGen[P1] { | ||
def operation(input: OperationInput): P1[OperationInput, Nothing, OperationOutput, Nothing, Nothing] = f[OperationInput, Nothing, OperationOutput, Nothing, Nothing](alg.operation(input)) | ||
} | ||
|
||
def toPolyFunction[P[_, _, _, _, _]](impl: ServiceWithNullsAndDefaultsGen[P]): PolyFunction5[ServiceWithNullsAndDefaultsOperation, P] = new PolyFunction5[ServiceWithNullsAndDefaultsOperation, P] { | ||
def apply[I, E, O, SI, SO](op: ServiceWithNullsAndDefaultsOperation[I, E, O, SI, SO]): P[I, E, O, SI, SO] = op.run(impl) | ||
} | ||
final case class Operation(input: OperationInput) extends ServiceWithNullsAndDefaultsOperation[OperationInput, Nothing, OperationOutput, Nothing, Nothing] { | ||
def run[F[_, _, _, _, _]](impl: ServiceWithNullsAndDefaultsGen[F]): F[OperationInput, Nothing, OperationOutput, Nothing, Nothing] = impl.operation(input) | ||
def ordinal: Int = 0 | ||
def endpoint: smithy4s.Endpoint[ServiceWithNullsAndDefaultsOperation,OperationInput, Nothing, OperationOutput, Nothing, Nothing] = Operation | ||
} | ||
object Operation extends smithy4s.Endpoint[ServiceWithNullsAndDefaultsOperation,OperationInput, Nothing, OperationOutput, Nothing, Nothing] { | ||
val schema: OperationSchema[OperationInput, Nothing, OperationOutput, Nothing, Nothing] = Schema.operation(ShapeId("smithy4s.example", "Operation")) | ||
.withInput(OperationInput.schema) | ||
.withOutput(OperationOutput.schema) | ||
.withHints(smithy.api.Http(method = smithy.api.NonEmptyString("POST"), uri = smithy.api.NonEmptyString("/operation/{requiredLabel}"), code = 200)) | ||
def wrap(input: OperationInput): Operation = Operation(input) | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -211,14 +211,42 @@ object Metadata { | |
*/ | ||
type Encoder[A] = smithy4s.codecs.Encoder[Metadata, A] | ||
|
||
object Encoder extends CachedEncoderCompilerImpl(awsHeaderEncoding = false) { | ||
private final class MetadataEncoder( | ||
awsHeaderEncoding: Boolean, | ||
explicitDefaultsEncoding: Boolean | ||
) extends CachedEncoderCompilerImpl( | ||
awsHeaderEncoding = awsHeaderEncoding, | ||
explicitDefaultsEncoding = explicitDefaultsEncoding | ||
) { | ||
type Compiler = CachedSchemaCompiler[Encoder] | ||
} | ||
|
||
object Encoder | ||
extends CachedEncoderCompilerImpl( | ||
awsHeaderEncoding = false, | ||
explicitDefaultsEncoding = false | ||
) { | ||
type Compiler = CachedSchemaCompiler[Encoder] | ||
|
||
def make( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not a fan of this. Also, the UX I'd like would be : Also this |
||
awsHeaderEncoding: Boolean, | ||
explicitDefaultsEncoding: Boolean | ||
): Metadata.Encoder.Compiler = new MetadataEncoder( | ||
awsHeaderEncoding = awsHeaderEncoding, | ||
explicitDefaultsEncoding = explicitDefaultsEncoding | ||
) | ||
} | ||
|
||
private[smithy4s] object AwsEncoder | ||
extends CachedEncoderCompilerImpl(awsHeaderEncoding = true) | ||
extends CachedEncoderCompilerImpl( | ||
awsHeaderEncoding = true, | ||
explicitDefaultsEncoding = false | ||
) | ||
|
||
private[http] class CachedEncoderCompilerImpl(awsHeaderEncoding: Boolean) | ||
extends CachedSchemaCompiler.DerivingImpl[Encoder] { | ||
private[http] class CachedEncoderCompilerImpl( | ||
awsHeaderEncoding: Boolean, | ||
explicitDefaultsEncoding: Boolean | ||
) extends CachedSchemaCompiler.DerivingImpl[Encoder] { | ||
|
||
type Aux[A] = internals.MetaEncode[A] | ||
|
||
|
@@ -240,7 +268,8 @@ object Metadata { | |
} | ||
val schemaVisitor = new SchemaVisitorMetadataWriter( | ||
cache, | ||
commaDelimitedEncoding = awsHeaderEncoding | ||
commaDelimitedEncoding = awsHeaderEncoding, | ||
explicitDefaultsEncoding = explicitDefaultsEncoding | ||
) | ||
schemaVisitor(schema) match { | ||
case StructureMetaEncode(f) if awsHeaderEncoding => { (a: A) => | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly to my comment here, this method should be removed in favour of a
withExplicitDefaultsEncoding(explicitDefaults: Boolean)
on theEncoder
.This implies the creation of public interface. You can take this as inspiration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is one problem with the above approach, currently, the following doesn't compile:
This is because
JsoniterCodecCompiler
inherits abstract typeCache
type, wheareas inCachedSchemaCompiler.Impl
it is set totype Cache = CompilationCache[Aux]
. So the information about cache is lost when calling methods fromJsoniterCodecCompiler
.Is it a known limitation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not a bug, it's a feature 😄
If you want to compile a codec without cache, you should be able to call
Otherwise, if you wanted to use a shared cache across several codec compilations, you would have to do :