Skip to content

Commit

Permalink
Add example package using shared types in client and server
Browse files Browse the repository at this point in the history
  • Loading branch information
simonjbeaumont committed Jul 15, 2024
1 parent 9385754 commit b701955
Show file tree
Hide file tree
Showing 13 changed files with 259 additions and 0 deletions.
11 changes: 11 additions & 0 deletions Examples/shared-types-client-server-example/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.DS_Store
.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.vscode
/Package.resolved
.ci/
.docc-build/
55 changes: 55 additions & 0 deletions Examples/shared-types-client-server-example/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// swift-tools-version:5.9
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftOpenAPIGenerator open source project
//
// Copyright (c) 2024 Apple Inc. and the SwiftOpenAPIGenerator project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import PackageDescription

let package = Package(
name: "shared-types-client-server-example",
platforms: [.macOS(.v13)],
products: [
.executable(name: "hello-world-client", targets: ["Client"]),
.executable(name: "hello-world-server", targets: ["Server"]),
],
dependencies: [
.package(url: "https://github.com/apple/swift-openapi-generator", from: "1.0.0"),
.package(url: "https://github.com/apple/swift-openapi-runtime", from: "1.0.0"),
.package(url: "https://github.com/apple/swift-openapi-urlsession", from: "1.0.0"),
.package(url: "https://github.com/swift-server/swift-openapi-hummingbird", from: "1.0.0"),
],
targets: [
.target(
name: "Types",
dependencies: [.product(name: "OpenAPIRuntime", package: "swift-openapi-runtime")],
plugins: [.plugin(name: "OpenAPIGenerator", package: "swift-openapi-generator")]
),
.executableTarget(
name: "Client",
dependencies: [
"Types",
.product(name: "OpenAPIRuntime", package: "swift-openapi-runtime"),
.product(name: "OpenAPIURLSession", package: "swift-openapi-urlsession"),
],
plugins: [.plugin(name: "OpenAPIGenerator", package: "swift-openapi-generator")]
),
.executableTarget(
name: "Server",
dependencies: [
"Types",
.product(name: "OpenAPIRuntime", package: "swift-openapi-runtime"),
.product(name: "OpenAPIHummingbird", package: "swift-openapi-hummingbird"),
],
plugins: [.plugin(name: "OpenAPIGenerator", package: "swift-openapi-generator")]
),
]
)
40 changes: 40 additions & 0 deletions Examples/shared-types-client-server-example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Common types between client and server modules

An example project using [Swift OpenAPI Generator](https://github.com/apple/swift-openapi-generator).

> **Disclaimer:** This example is deliberately simplified and is intended for illustrative purposes only.
## Overview

This example shows how you can structure a Swift package to share the types
from an OpenAPI document between a client and server module by having a common
target that runs the generator in `types` mode only.

This allows you to write extensions or other helper functions that use these
types and use them in both the client and server code.

## Usage

Build and run the server using:

```console
% swift run hello-world-server
Build complete!
...
info HummingBird : [HummingbirdCore] Server started and listening on 127.0.0.1:8080
```

Then, in another terminal window, run the client:

```console
% swift run hello-world-client
Build complete!
+––––––––––––––––––+
|+––––––––––––––––+|
||Hello, Stranger!||
|+––––––––––––––––+|
+––––––––––––––––––+
```

Note how the message is boxed twice: once by the server and once by the client,
both using an extension on a shared type, defined in the `Types` module.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftOpenAPIGenerator open source project
//
// Copyright (c) 2024 Apple Inc. and the SwiftOpenAPIGenerator project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import OpenAPIRuntime
import OpenAPIURLSession
import Foundation

@main struct HelloWorldURLSessionClient {
static func main() async throws {
let client = Client(serverURL: URL(string: "http://localhost:8080/api")!, transport: URLSessionTransport())
let response = try await client.getGreeting()
print(try response.ok.body.json.boxed().message)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
generate:
- client
accessModifier: internal
additionalImports:
- Types
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftOpenAPIGenerator open source project
//
// Copyright (c) 2024 Apple Inc. and the SwiftOpenAPIGenerator project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import OpenAPIRuntime
import OpenAPIHummingbird
import Hummingbird
import Foundation
import Types

struct Handler: APIProtocol {
func getGreeting(_ input: Operations.getGreeting.Input) async throws -> Operations.getGreeting.Output {
let name = input.query.name ?? "Stranger"
let message = Components.Schemas.Greeting(message: "Hello, \(name)!")
return .ok(.init(body: .json(message.boxed())))
}
}

@main struct HelloWorldHummingbirdServer {
static func main() async throws {
let app = Hummingbird.HBApplication()
let transport = HBOpenAPITransport(app)
let handler = Handler()
try handler.registerHandlers(on: transport, serverURL: URL(string: "/api")!)
try await app.asyncRun()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
generate:
- server
accessModifier: internal
additionalImports:
- Types
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftOpenAPIGenerator open source project
//
// Copyright (c) 2024 Apple Inc. and the SwiftOpenAPIGenerator project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

extension Components.Schemas.Greeting {
package func boxed(maxBoxWidth: Int = 80) -> Self {
// Reflow the text.
let maxTextLength = maxBoxWidth - 4
var reflowedLines = [Substring]()
for var line in message.split(whereSeparator: \.isNewline) {
while !line.isEmpty {
let prefix = line.prefix(maxTextLength)
reflowedLines.append(prefix)
line = line.dropFirst(prefix.count)
}
}

// Determine the box size (might be smaller than max).
let longestLineCount = reflowedLines.map(\.count).max()!
let horizontalEdge = "+\(String(repeating: "", count: longestLineCount))+"

var boxedMessageLines = [String]()
boxedMessageLines.reserveCapacity(reflowedLines.count + 2)
boxedMessageLines.append(horizontalEdge)
for line in reflowedLines {
boxedMessageLines.append("|\(line.padding(toLength: longestLineCount, withPad: " ", startingAt: 0))|")
}
boxedMessageLines.append(horizontalEdge)
return Self(message: boxedMessageLines.joined(separator: "\n"))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
generate:
- types
accessModifier: package
36 changes: 36 additions & 0 deletions Examples/shared-types-client-server-example/Sources/openapi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
openapi: '3.1.0'
info:
title: GreetingService
version: 1.0.0
servers:
- url: https://example.com/api
description: Example service deployment.
paths:
/greet:
get:
operationId: getGreeting
parameters:
- name: name
required: false
in: query
description: The name used in the returned greeting.
schema:
type: string
responses:
'200':
description: A success response with a greeting.
content:
application/json:
schema:
$ref: '#/components/schemas/Greeting'
components:
schemas:
Greeting:
type: object
description: A value with the greeting contents.
properties:
message:
type: string
description: The string representation of the greeting.
required:
- message

0 comments on commit b701955

Please sign in to comment.