Skip to content

Commit

Permalink
add: RestJSONProtocolGenerator
Browse files Browse the repository at this point in the history
  • Loading branch information
phani-srikar committed Jul 23, 2020
1 parent 287a259 commit 7206651
Show file tree
Hide file tree
Showing 18 changed files with 1,080 additions and 0 deletions.
13 changes: 13 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.DS_Store
/dist
/ideaSDK
/android-studio/sdk
out/
/tmp
workspace.xml
*.versionsBackup
.gradle/
build/
*.iml
.idea/
local.properties
55 changes: 55 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
plugins {
kotlin("jvm") version "1.3.72" apply false
}

allprojects {
repositories {
mavenLocal()
mavenCentral()
jcenter()
}
}

val ktlint by configurations.creating
val ktlintVersion: String by project
dependencies {
ktlint("com.pinterest:ktlint:$ktlintVersion")
}

val lintPaths = listOf(
"codegen/**/*.kt"
)

tasks.register<JavaExec>("ktlint") {
description = "Check Kotlin code style."
group = "Verification"
classpath = configurations.getByName("ktlint")
main = "com.pinterest.ktlint.Main"
args = lintPaths
}

//tasks.named("check") {
// dependsOn(":ktlint")
//}

tasks.register<JavaExec>("ktlintFormat") {
description = "Auto fix Kotlin code style violations"
group = "formatting"
classpath = configurations.getByName("ktlint")
main = "com.pinterest.ktlint.Main"
args = listOf("-F") + lintPaths
}
40 changes: 40 additions & 0 deletions codegen/protocol-test-codegen/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
import software.amazon.smithy.gradle.tasks.SmithyBuild

plugins {
id("software.amazon.smithy") version "0.5.0"
}

dependencies {
implementation("software.amazon.smithy:smithy-aws-protocol-tests:1.0.5")
compile(project(":smithy-aws-swift-codegen"))
}

// This project doesn't produce a JAR.
tasks["jar"].enabled = false

// Run the SmithyBuild task manually since this project needs the built JAR
// from smithy-aws-swift-codegen.
tasks["smithyBuildJar"].enabled = false

tasks.create<SmithyBuild>("buildSdk") {
addRuntimeClasspath = true
}

// Run the `buildSdk` automatically.
tasks["build"].finalizedBy(tasks["buildSdk"])

// TODO:: ensure built artifacts are put into the SDK's folders
26 changes: 26 additions & 0 deletions codegen/protocol-test-codegen/smithy-build.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"version": "1.0",
"projections": {
"aws-restjson": {
"transforms": [
{
"name": "includeServices",
"args": {
"services": ["aws.protocoltests.restjson#RestJson"]
}
}
],
"plugins": {
"swift-codegen": {
"service": "aws.protocoltests.restjson#RestJson",
"module": "AWSRestJsonTestSDK",
"moduleVersion": "1.0",
"gitRepo": "https://github.com/aws-amplify/amplify-codegen.git",
"author": "Amazon Web Services",
"homepage": "https://docs.amplify.aws/",
"swiftVersion": "5.1.0"
}
}
}
}
}
72 changes: 72 additions & 0 deletions codegen/smithy-aws-swift-codegen/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
plugins {
kotlin("jvm")
jacoco
}

//description = "Generates Swift code from Smithy models"
//extra["displayName"] = "AWS:: Smithy :: Swift :: Codegen"
//extra["moduleName"] = "software.amazon.smithy.aws.swift.codegen"

group = "software.amazon.smithy"
version = "0.1.0"

val smithyVersion: String by project
val kotestVersion: String by project

dependencies {
implementation(kotlin("stdlib-jdk8"))
api("software.amazon.smithy:smithy-swift-codegen:0.1.0")
api("software.amazon.smithy:smithy-aws-traits:$smithyVersion")
implementation("software.amazon.smithy:smithy-protocol-test-traits:$smithyVersion")
testImplementation("org.junit.jupiter:junit-jupiter:5.6.1")
testImplementation("io.kotest:kotest-assertions-core-jvm:$kotestVersion")
}

// Reusable license copySpec
val licenseSpec = copySpec {
from("${project.rootDir}/LICENSE")
from("${project.rootDir}/NOTICE")
}

// Configure jars to include license related info
tasks.jar {
metaInf.with(licenseSpec)
inputs.property("moduleName", project.name)
manifest {
attributes["Automatic-Module-Name"] = project.name
}
}

tasks.test {
useJUnitPlatform()
testLogging {
events("passed", "skipped", "failed")
showStandardStreams = true
}
}

// Configure jacoco (code coverage) to generate an HTML report
tasks.jacocoTestReport {
reports {
xml.isEnabled = false
csv.isEnabled = false
html.destination = file("$buildDir/reports/jacoco")
}
}

// Always run the jacoco test report after testing.
tasks["test"].finalizedBy(tasks["jacocoTestReport"])
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package software.amazon.smithy.aws.swift.codegen

import software.amazon.smithy.aws.traits.protocols.RestJson1Trait
import software.amazon.smithy.model.shapes.ShapeId

/**
* Handles generating the aws.rest-json protocol for services.
*
* @inheritDoc
* @see RestJsonProtocolGenerator
*/

class AWSRestJson1ProtocolGenerator : RestJsonProtocolGenerator() {
override val defaultContentType: String = "application/json"
override val protocol: ShapeId = RestJson1Trait.ID
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package software.amazon.smithy.aws.swift.codegen


import software.amazon.smithy.swift.codegen.integration.SwiftIntegration
import software.amazon.smithy.swift.codegen.integration.ProtocolGenerator

/**
* Integration that registers protocol generators this package provides
*/
class AddProtocols : SwiftIntegration {
/**
* Gets the sort order of the customization from -128 to 127, with lowest
* executed first.
*
* @return Returns the sort order, defaults to -10.
*/
override val order: Byte = -10

override val protocolGenerators: List<ProtocolGenerator> = listOf(AWSRestJson1ProtocolGenerator())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package software.amazon.smithy.aws.swift.codegen

import software.amazon.smithy.model.knowledge.HttpBinding
import software.amazon.smithy.model.knowledge.HttpBindingIndex
import software.amazon.smithy.model.knowledge.OperationIndex
import software.amazon.smithy.model.shapes.MemberShape
import software.amazon.smithy.model.shapes.OperationShape
import software.amazon.smithy.model.shapes.ShapeId
import software.amazon.smithy.swift.codegen.ServiceGenerator
import software.amazon.smithy.swift.codegen.integration.HttpBindingProtocolGenerator
import software.amazon.smithy.swift.codegen.integration.HttpFeature
import software.amazon.smithy.swift.codegen.integration.HttpRequestEncoder
import software.amazon.smithy.swift.codegen.integration.ProtocolGenerator


/**
* Shared base protocol generator for all AWS JSON protocol variants
*/
abstract class RestJsonProtocolGenerator : HttpBindingProtocolGenerator() {

override fun generateProtocolUnitTests(ctx: ProtocolGenerator.GenerationContext) {}

override fun generateSerializers(ctx: ProtocolGenerator.GenerationContext) {
// Generate extension on input requests to implement Codable protocol
val inputShapesWithHttpBindings:MutableSet<ShapeId> = mutableSetOf()
for (operation in getHttpBindingOperations(ctx)) {
if (operation.input.isPresent) {
val inputShapeId = operation.input.get()
if (inputShapesWithHttpBindings.contains(inputShapeId)) {
// The input shape is referenced by more than one operation
continue
}
renderInputRequestConformanceToCodable(ctx, operation)
inputShapesWithHttpBindings.add(inputShapeId)
}
}
super.generateSerializers(ctx)
}

override fun getHttpFeatures(ctx: ProtocolGenerator.GenerationContext): List<HttpFeature> {
val features = super.getHttpFeatures(ctx).toMutableList()
val jsonFeatures = listOf(JSONRequestEncoder())
features.addAll(jsonFeatures)
return features
}

private fun renderInputRequestConformanceToCodable(ctx: ProtocolGenerator.GenerationContext, op: OperationShape) {
if (op.input.isEmpty()) {
return
}
val inputShape = ctx.model.expectShape(op.input.get())
val opIndex = ctx.model.getKnowledge(OperationIndex::class.java)
val inputShapeName = ServiceGenerator.getOperationInputShapeName(ctx.symbolProvider, opIndex, op)
val requestPayloadMembers = getRequestPayloadMembers(ctx, op)

if (requestPayloadMembers.isNotEmpty()) {
ctx.delegator.useShapeWriter(inputShape) { writer ->
writer.openBlock("extension ${inputShapeName!!.get()}: Codable {", "}") {
writer.openBlock("private enum CodingKeys: String, CodingKey {", "}") {
// TODO:: handle the case when encoding name is different from member name
writer.write("case ${requestPayloadMembers.joinToString(separator = ", ")}")
}
}
writer.write("")
}
}
}

private fun getRequestPayloadMembers(ctx: ProtocolGenerator.GenerationContext,
op: OperationShape): MutableSet<String> {
val requestPayloadMembers = mutableSetOf<String>()
val bindingIndex = ctx.model.getKnowledge(HttpBindingIndex::class.java)
val requestBindings = bindingIndex.getRequestBindings(op)
val httpPayloadBinding = requestBindings.values.firstOrNull { it.location == HttpBinding.Location.PAYLOAD }

/* Unbound document members that should be serialized into the document format for the protocol.
*/
val documentMemberBindings = requestBindings.values
.filter { it.location == HttpBinding.Location.DOCUMENT }
.sortedBy { it.memberName }
if (httpPayloadBinding != null) {
val memberName = httpPayloadBinding.member.memberName
requestPayloadMembers.add(memberName)
} else if (documentMemberBindings.isNotEmpty()) {
val documentMemberShapes = documentMemberBindings.map { it.member }
val documentMemberShapesSortedByName: List<MemberShape> = documentMemberShapes.sortedBy { ctx.symbolProvider.toMemberName(it) }
if (documentMemberShapesSortedByName.isNotEmpty()) {
for (member in documentMemberShapesSortedByName) {
val memberName = ctx.symbolProvider.toMemberName(member)
requestPayloadMembers.add(memberName)
}
}
}
return requestPayloadMembers
}
}


class JSONRequestEncoder : HttpRequestEncoder("JsonEncoder") {}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
software.amazon.smithy.aws.swift.codegen.AddProtocols
Loading

0 comments on commit 7206651

Please sign in to comment.