Skip to content

Commit

Permalink
Merge pull request #70 from amzn/swift_concurrency_implementations_ge…
Browse files Browse the repository at this point in the history
…neration

Add the ability to generate Swift Concurrency implementations.
  • Loading branch information
tachyonics authored Jan 5, 2023
2 parents c898d55 + 9841c68 commit 40631a9
Show file tree
Hide file tree
Showing 6 changed files with 234 additions and 46 deletions.
37 changes: 37 additions & 0 deletions .github/workflows/swift.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: build

on:
push:
branches: [ main, service-model-swift-code-generate-2.x, service-model-swift-code-generate-1.x ]
pull_request:
branches: [ main, service-model-swift-code-generate-2.x, service-model-swift-code-generate-1.x ]

jobs:
Build:
name: Swift ${{ matrix.swift }} on ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-22.04]
swift: ["5.7.2"]
runs-on: ${{ matrix.os }}
steps:
- uses: swift-actions/setup-swift@v1.21.0
with:
swift-version: ${{ matrix.swift }}
- uses: actions/checkout@v2
- name: Build
run: swift build -c release
Build20:
name: Swift ${{ matrix.swift }} on ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-20.04]
swift: ["5.6.3", "5.5.3"]
runs-on: ${{ matrix.os }}
steps:
- uses: swift-actions/setup-swift@v1.21.0
with:
swift-version: ${{ matrix.swift }}
- uses: actions/checkout@v2
- name: Build
run: swift build -c release
9 changes: 3 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
<p align="center">
<a href="https://travis-ci.com/amzn/service-model-swift-code-generate">
<img src="https://travis-ci.com/amzn/service-model-swift-code-generate.svg?branch=master" alt="Build - Master Branch">
<a href="https://github.com/amzn/service-model-swift-code-generate/actions">
<img src="https://github.com/amzn/service-model-swift-code-generate/actions/workflows/swift.yml/badge.svg?branch=service-model-swift-code-generate-2.x" alt="Build - service-model-swift-code-generate-2.x Branch">
</a>
<a href="http://swift.org">
<img src="https://img.shields.io/badge/swift-5.1|5.2|5.3-orange.svg?style=flat" alt="Swift 5.1, 5.2 and 5.3 Tested">
<img src="https://img.shields.io/badge/swift-5.5|5.6|5.7-orange.svg?style=flat" alt="Swift 5.5, 5.6 and 5.7 Tested">
</a>
<img src="https://img.shields.io/badge/ubuntu-16.04|18.04|20.04-yellow.svg?style=flat" alt="Ubuntu 16.04, 18.04 and 20.04 Tested">
<img src="https://img.shields.io/badge/CentOS-8-yellow.svg?style=flat" alt="CentOS 8 Tested">
<img src="https://img.shields.io/badge/AmazonLinux-2-yellow.svg?style=flat" alt="Amazon Linux 2 Tested">
<a href="https://gitter.im/SmokeServerSide">
<img src="https://img.shields.io/badge/chat-on%20gitter-ee115e.svg?style=flat" alt="Join the Smoke Server Side community on gitter">
</a>
Expand Down
3 changes: 3 additions & 0 deletions Sources/ServiceModelCodeGeneration/ModelClientDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ public protocol ModelClientDelegate {
public enum ClientType {
/// A protocol with the specified name
case `protocol`(name: String)
/// A protocol with the specified name, also conforming to the specified protocol
case `protocolWithConformance`(name: String, conformingProtocolName: String)
/// A struct with the specified name and conforming to the specified protocol
case `struct`(name: String, genericParameters: [(typeName: String, conformingTypeName: String?)], conformingProtocolName: String)
}
Expand All @@ -111,6 +113,7 @@ public struct AsyncResultType {
public enum InvokeType: String {
case sync = "Sync"
case async = "Async"
case asyncFunction = "AsyncFunction"
}

public extension ModelClientDelegate {
Expand Down
50 changes: 38 additions & 12 deletions Sources/ServiceModelGenerate/ClientProtocolDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ import Foundation
import ServiceModelCodeGeneration
import ServiceModelEntities

public enum APIShape {
case syncAndCallback
case structuredConcurrency
}

/**
A ModelClientDelegate that can be used to generate a
Client protocol from a Service Model.
Expand All @@ -28,19 +33,29 @@ public struct ClientProtocolDelegate: ModelClientDelegate {
public let asyncResultType: AsyncResultType?
public let baseName: String
public let typeDescription: String
public let protocolAPIShape: APIShape

/**
Initializer.

- Parameters:
- baseName: The base name of the Service.
- protocolAPIShape: The shape of the APIs to create with this protocol
- asyncResultType: The name of the result type to use for async functions.
*/
public init(baseName: String, asyncResultType: AsyncResultType? = nil) {
public init(baseName: String, protocolAPIShape: APIShape = .syncAndCallback,
asyncResultType: AsyncResultType? = nil) {
self.baseName = baseName
self.asyncResultType = asyncResultType
self.clientType = .protocol(name: "\(baseName)ClientProtocol")
switch protocolAPIShape {
case .syncAndCallback:
self.clientType = .protocolWithConformance(name: "\(baseName)ClientProtocol",
conformingProtocolName: "\(baseName)ClientProtocolV2")
case .structuredConcurrency:
self.clientType = .protocol(name: "\(baseName)ClientProtocolV2")
}
self.typeDescription = "Client Protocol for the \(baseName) service."
self.protocolAPIShape = protocolAPIShape
}

public func getFileDescription(isGenerator: Bool) -> String {
Expand All @@ -59,16 +74,27 @@ public struct ClientProtocolDelegate: ModelClientDelegate {
fileBuilder: FileBuilder,
sortedOperations: [(String, OperationDescription)],
isGenerator: Bool) {
// for each of the operations
for (name, operationDescription) in sortedOperations {
codeGenerator.addOperation(fileBuilder: fileBuilder, name: name,
operationDescription: operationDescription,
delegate: delegate, invokeType: .sync,
forTypeAlias: true, isGenerator: isGenerator)
codeGenerator.addOperation(fileBuilder: fileBuilder, name: name,
operationDescription: operationDescription,
delegate: delegate, invokeType: .async,
forTypeAlias: true, isGenerator: isGenerator)
switch self.protocolAPIShape {
case .syncAndCallback:
// for each of the operations
for (name, operationDescription) in sortedOperations {
codeGenerator.addOperation(fileBuilder: fileBuilder, name: name,
operationDescription: operationDescription,
delegate: delegate, invokeType: .sync,
forTypeAlias: true, isGenerator: isGenerator)
codeGenerator.addOperation(fileBuilder: fileBuilder, name: name,
operationDescription: operationDescription,
delegate: delegate, invokeType: .async,
forTypeAlias: true, isGenerator: isGenerator)
}
case .structuredConcurrency:
// for each of the operations
for (name, operationDescription) in sortedOperations {
codeGenerator.addOperation(fileBuilder: fileBuilder, name: name,
operationDescription: operationDescription,
delegate: delegate, invokeType: .asyncFunction,
forTypeAlias: true, isGenerator: isGenerator)
}
}
}

Expand Down
58 changes: 47 additions & 11 deletions Sources/ServiceModelGenerate/MockClientDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,19 @@ public struct MockClientDelegate: ModelClientDelegate {
public let isThrowingMock: Bool
public let clientType: ClientType
public let typeDescription: String
public let clientAPIShape: APIShape

/**
Initializer.

- Parameters:
- baseName: The base name of the Service.
- isThrowingMock: true to generate a throwing mock; false for a normal mock
- clientAPIShape: The shape of the APIs to create with this mock
- asyncResultType: The name of the result type to use for async functions.
*/
public init(baseName: String, isThrowingMock: Bool,
clientAPIShape: APIShape = .syncAndCallback,
asyncResultType: AsyncResultType? = nil) {
self.baseName = baseName
self.isThrowingMock = isThrowingMock
Expand All @@ -54,8 +57,16 @@ public struct MockClientDelegate: ModelClientDelegate {
+ "returns the `__default` property of its return type."
}

self.clientType = .struct(name: name, genericParameters: [],
conformingProtocolName: "\(baseName)ClientProtocol")
switch clientAPIShape {
case .syncAndCallback:
self.clientType = .struct(name: name, genericParameters: [],
conformingProtocolName: "\(baseName)ClientProtocol")
case .structuredConcurrency:
self.clientType = .struct(name: name + "V2", genericParameters: [],
conformingProtocolName: "\(baseName)ClientProtocolV2")
}

self.clientAPIShape = clientAPIShape
}

public func getFileDescription(isGenerator: Bool) -> String {
Expand All @@ -79,8 +90,13 @@ public struct MockClientDelegate: ModelClientDelegate {
}

let variableName = name.upperToLowerCamelCase
fileBuilder.appendLine("\(variableName)Async: \(name.startingWithUppercase)AsyncType? = nil,")
fileBuilder.appendLine("\(variableName)Sync: \(name.startingWithUppercase)SyncType? = nil\(postfix)")
switch self.clientAPIShape {
case .syncAndCallback:
fileBuilder.appendLine("\(variableName)Async: \(name.startingWithUppercase)AsyncType? = nil,")
fileBuilder.appendLine("\(variableName)Sync: \(name.startingWithUppercase)SyncType? = nil\(postfix)")
case .structuredConcurrency:
fileBuilder.appendLine("\(variableName): \(name.startingWithUppercase)FunctionType? = nil\(postfix)")
}
}

public func addCommonFunctions(codeGenerator: ServiceModelCodeGenerator,
Expand All @@ -95,8 +111,14 @@ public struct MockClientDelegate: ModelClientDelegate {
// for each of the operations
for (name, _) in sortedOperations {
let variableName = name.upperToLowerCamelCase
fileBuilder.appendLine("let \(variableName)AsyncOverride: \(name.startingWithUppercase)AsyncType?")
fileBuilder.appendLine("let \(variableName)SyncOverride: \(name.startingWithUppercase)SyncType?")

switch self.clientAPIShape {
case .syncAndCallback:
fileBuilder.appendLine("let \(variableName)AsyncOverride: \(name.startingWithUppercase)AsyncType?")
fileBuilder.appendLine("let \(variableName)SyncOverride: \(name.startingWithUppercase)SyncType?")
case .structuredConcurrency:
fileBuilder.appendLine("let \(variableName)Override: \(name.startingWithUppercase)FunctionType?")
}
}
fileBuilder.appendEmptyLine()

Expand Down Expand Up @@ -144,8 +166,13 @@ public struct MockClientDelegate: ModelClientDelegate {
// for each of the operations
for (name, _) in sortedOperations {
let variableName = name.upperToLowerCamelCase
fileBuilder.appendLine("self.\(variableName)AsyncOverride = \(variableName)Async")
fileBuilder.appendLine("self.\(variableName)SyncOverride = \(variableName)Sync")
switch self.clientAPIShape {
case .syncAndCallback:
fileBuilder.appendLine("self.\(variableName)AsyncOverride = \(variableName)Async")
fileBuilder.appendLine("self.\(variableName)SyncOverride = \(variableName)Sync")
case .structuredConcurrency:
fileBuilder.appendLine("self.\(variableName)Override = \(variableName)")
}
}

fileBuilder.decIndent()
Expand Down Expand Up @@ -194,7 +221,7 @@ public struct MockClientDelegate: ModelClientDelegate {

let declarationPrefix: String
switch invokeType {
case .sync:
case .sync, .asyncFunction:
declarationPrefix = "return"
case .async:
declarationPrefix = "let result ="
Expand Down Expand Up @@ -225,7 +252,7 @@ public struct MockClientDelegate: ModelClientDelegate {
operationName: operationName, hasInput: hasInput)

switch invokeType {
case .sync:
case .sync, .asyncFunction:
fileBuilder.appendLine("throw error")
case .async:
if hasOutput {
Expand All @@ -244,18 +271,25 @@ public struct MockClientDelegate: ModelClientDelegate {

let customFunctionParameters: String
let customFunctionPostfix: String
let asyncPrefix: String
switch invokeType {
case .async:
customFunctionPostfix = "Async"
customFunctionParameters = hasInput ? "input, completion" : "completion"
asyncPrefix = ""
case .sync:
customFunctionPostfix = "Sync"
customFunctionParameters = hasInput ? "input" : ""
asyncPrefix = ""
case .asyncFunction:
customFunctionPostfix = ""
customFunctionParameters = hasInput ? "input" : ""
asyncPrefix = "await "
}

fileBuilder.appendLine("""
if let \(variableName)\(customFunctionPostfix)Override = \(variableName)\(customFunctionPostfix)Override {
return try \(variableName)\(customFunctionPostfix)Override(\(customFunctionParameters))
return try \(asyncPrefix)\(variableName)\(customFunctionPostfix)Override(\(customFunctionParameters))
}
""")
fileBuilder.appendEmptyLine()
Expand All @@ -267,6 +301,8 @@ public struct MockClientDelegate: ModelClientDelegate {
return name
case .struct(name: _, genericParameters: _, conformingProtocolName: let conformingProtocolName):
return conformingProtocolName
case .protocolWithConformance(name: let name, conformingProtocolName: _):
return name
}
}
}
Loading

0 comments on commit 40631a9

Please sign in to comment.