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

Add the ability to generate Swift Concurrency implementations. #70

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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/[email protected]
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/[email protected]
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