@@ -10,6 +10,8 @@ import software.amazon.smithy.kotlin.codegen.core.withBlock
1010import software.amazon.smithy.kotlin.codegen.core.withInlineBlock
1111import software.amazon.smithy.kotlin.codegen.lang.KotlinTypes
1212import software.amazon.smithy.kotlin.codegen.model.getTrait
13+ import software.amazon.smithy.kotlin.codegen.service.contraints.ConstraintGenerator
14+ import software.amazon.smithy.kotlin.codegen.service.contraints.ConstraintUtilsGenerator
1315import software.amazon.smithy.kotlin.codegen.service.MediaType.ANY
1416import software.amazon.smithy.kotlin.codegen.service.MediaType.JSON
1517import software.amazon.smithy.kotlin.codegen.service.MediaType.OCTET_STREAM
@@ -104,7 +106,7 @@ internal class KtorStubGenerator(
104106 }
105107
106108 private fun renderLogging () {
107- delegator.useFileWriter(" Logging.kt" , " ${ctx.settings.pkg.name} .utils" ) { writer ->
109+ delegator.useFileWriter(" Logging.kt" , " $pkgName .utils" ) { writer ->
108110
109111 writer.withBlock(" internal fun #T.configureLogging() {" , " }" , RuntimeTypes .KtorServerCore .Application ) {
110112 withBlock(
@@ -178,20 +180,20 @@ internal class KtorStubGenerator(
178180
179181 // Generates `Authentication.kt` with Authenticator interface + configureSecurity().
180182 override fun renderAuthModule () {
181- delegator.useFileWriter(" UserPrincipal.kt" , " ${ctx.settings.pkg.name} .auth" ) { writer ->
183+ delegator.useFileWriter(" UserPrincipal.kt" , " $pkgName .auth" ) { writer ->
182184 writer.withBlock(" public data class UserPrincipal(" , " )" ) {
183185 write(" val user: String" )
184186 }
185187 }
186188
187- delegator.useFileWriter(" Validation.kt" , " ${ctx.settings.pkg.name} .auth" ) { writer ->
189+ delegator.useFileWriter(" Validation.kt" , " $pkgName .auth" ) { writer ->
188190 writer.withBlock(" public fun bearerValidation(token: String): UserPrincipal? {" , " }" ) {
189191 write(" // TODO: implement me" )
190192 write(" if (true) return UserPrincipal(#S) else return null" , " Authenticated User" )
191193 }
192194 }
193195
194- delegator.useFileWriter(" Authentication.kt" , " ${ctx.settings.pkg.name} .auth" ) { writer ->
196+ delegator.useFileWriter(" Authentication.kt" , " $pkgName .auth" ) { writer ->
195197 writer.withBlock(" internal fun #T.configureAuthentication() {" , " }" , RuntimeTypes .KtorServerCore .Application ) {
196198 write(" " )
197199 withBlock(
@@ -212,18 +214,21 @@ internal class KtorStubGenerator(
212214
213215 // For every operation request structure, create a `validate()` function file.
214216 override fun renderConstraintValidators () {
217+ ConstraintUtilsGenerator (ctx, delegator).render()
218+ operations.forEach { operation -> ConstraintGenerator (ctx, operation, delegator).render() }
215219 }
216220
217221 // Writes `Routing.kt` that maps Smithy operations → Ktor routes.
218222 override fun renderRouting () {
219223 delegator.useFileWriter(" Routing.kt" , ctx.settings.pkg.name) { writer ->
220224
221225 operations.forEach { shape ->
222- writer.addImport(" ${ctx.settings.pkg.name} .serde" , " ${shape.id.name} OperationDeserializer" )
223- writer.addImport(" ${ctx.settings.pkg.name} .serde" , " ${shape.id.name} OperationSerializer" )
224- writer.addImport(" ${ctx.settings.pkg.name} .model" , " ${shape.id.name} Request" )
225- writer.addImport(" ${ctx.settings.pkg.name} .model" , " ${shape.id.name} Response" )
226- writer.addImport(" ${ctx.settings.pkg.name} .operations" , " handle${shape.id.name} Request" )
226+ writer.addImport(" $pkgName .serde" , " ${shape.id.name} OperationDeserializer" )
227+ writer.addImport(" $pkgName .serde" , " ${shape.id.name} OperationSerializer" )
228+ writer.addImport(" $pkgName .constraints" , " check${shape.id.name} RequestConstraint" )
229+ writer.addImport(" $pkgName .model" , " ${shape.id.name} Request" )
230+ writer.addImport(" $pkgName .model" , " ${shape.id.name} Response" )
231+ writer.addImport(" $pkgName .operations" , " handle${shape.id.name} Request" )
227232 }
228233
229234 writer.withBlock(" internal fun #T.configureRouting(): Unit {" , " }" , RuntimeTypes .KtorServerCore .Application ) {
@@ -296,6 +301,11 @@ internal class KtorStubGenerator(
296301 call { readHttpLabel(shape, writer) }
297302 call { readHttpQuery(shape, writer) }
298303 }
304+ write(
305+ " try { check${shape.id.name} RequestConstraint(requestObj) } catch (ex: Exception) { throw #T(ex?.message ?: #S, ex) }" ,
306+ RuntimeTypes .KtorServerCore .BadRequestException ,
307+ " Error while validating constraints" ,
308+ )
299309 write(" val responseObj = handle${shape.id.name} Request(requestObj)" )
300310 write(" val serializer = ${shape.id.name} OperationSerializer()" )
301311 withBlock(
@@ -518,7 +528,7 @@ internal class KtorStubGenerator(
518528 }
519529
520530 private fun renderErrorHandler () {
521- delegator.useFileWriter(" ErrorHandler.kt" , " ${ctx.settings.pkg.name} .plugins" ) { writer ->
531+ delegator.useFileWriter(" ErrorHandler.kt" , " $pkgName .plugins" ) { writer ->
522532 writer.write(" @#T" , RuntimeTypes .KotlinxCborSerde .Serializable )
523533 .write(" private data class ErrorPayload(val code: Int, val message: String)" )
524534 .write(" " )
@@ -548,7 +558,7 @@ internal class KtorStubGenerator(
548558 write(" val acceptsCbor = request.#T().any { it.value == #S }" , RuntimeTypes .KtorServerRouting .requestAcceptItems, " application/cbor" )
549559 write(" val acceptsJson = request.#T().any { it.value == #S }" , RuntimeTypes .KtorServerRouting .requestAcceptItems, " application/json" )
550560 write(" " )
551- write(" val log = #T.getLogger(#S)" , RuntimeTypes .KtorLoggingSlf4j .LoggerFactory , ctx.settings.pkg.name )
561+ write(" val log = #T.getLogger(#S)" , RuntimeTypes .KtorLoggingSlf4j .LoggerFactory , pkgName )
552562 write(" log.info(#S)" , " Route Error Message: \$ {envelope.msg}" )
553563 write(" " )
554564 withBlock(" when {" , " }" ) {
@@ -589,6 +599,16 @@ internal class KtorStubGenerator(
589599 write(" call.respondEnvelope( ErrorEnvelope(status.value, message), status )" )
590600 }
591601 write(" " )
602+ withBlock(" status(#T.NotFound) { call, status ->" , " }" , RuntimeTypes .KtorServerHttp .HttpStatusCode ) {
603+ write(" val message = #S" , " Resource not found" )
604+ write(" call.respondEnvelope( ErrorEnvelope(status.value, message), status )" )
605+ }
606+ write(" " )
607+ withBlock(" status(#T.MethodNotAllowed) { call, status ->" , " }" , RuntimeTypes .KtorServerHttp .HttpStatusCode ) {
608+ write(" val message = #S" , " Method not allowed for this resource" )
609+ write(" call.respondEnvelope( ErrorEnvelope(status.value, message), status )" )
610+ }
611+ write(" " )
592612 withBlock(" #T<Throwable> { call, cause ->" , " }" , RuntimeTypes .KtorServerStatusPage .exception) {
593613 withBlock(" val status = when (cause) {" , " }" ) {
594614 write(
@@ -618,7 +638,7 @@ internal class KtorStubGenerator(
618638 }
619639
620640 private fun renderContentTypeGuard () {
621- delegator.useFileWriter(" ContentTypeGuard.kt" , " ${ctx.settings.pkg.name} .plugins" ) { writer ->
641+ delegator.useFileWriter(" ContentTypeGuard.kt" , " $pkgName .plugins" ) { writer ->
622642
623643 writer.withBlock(" private fun #T.hasBody(): Boolean {" , " }" , RuntimeTypes .KtorServerRouting .requestApplicationRequest) {
624644 write(
@@ -755,9 +775,9 @@ internal class KtorStubGenerator(
755775 override fun renderPerOperationHandlers () {
756776 operations.forEach { shape ->
757777 val name = shape.id.name
758- delegator.useFileWriter(" ${name} Operation.kt" , " ${ctx.settings.pkg.name} .operations" ) { writer ->
759- writer.addImport(" ${ctx.settings.pkg.name} .model" , " ${shape.id.name} Request" )
760- writer.addImport(" ${ctx.settings.pkg.name} .model" , " ${shape.id.name} Response" )
778+ delegator.useFileWriter(" ${name} Operation.kt" , " $pkgName .operations" ) { writer ->
779+ writer.addImport(" $pkgName .model" , " ${shape.id.name} Request" )
780+ writer.addImport(" $pkgName .model" , " ${shape.id.name} Response" )
761781
762782 writer.withBlock(" public fun handle${name} Request(req: ${name} Request): ${name} Response {" , " }" ) {
763783 write(" // TODO: implement me" )
0 commit comments