Skip to content

Commit d6b0149

Browse files
authored
Add examples for event streams (#494)
### Motivation Example projects for apple/swift-openapi-runtime#91. ### Modifications Added examples. ### Result N/A ### Test Plan N/A
1 parent f58f60d commit d6b0149

File tree

20 files changed

+469
-38
lines changed

20 files changed

+469
-38
lines changed

Examples/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,12 @@ Each of the following packages shows an end-to-end working example with the give
2828

2929
## Various content types
3030

31-
The following packages show working with various content types, such as JSON, URL-encoded request bodies, plain text, raw bytes, and multipart bodies.
31+
The following packages show working with various content types, such as JSON, URL-encoded request bodies, plain text, raw bytes, multipart bodies, as well as event streams, such as JSON Lines, JSON Sequence, and Server-sent Events.
3232

3333
- [various-content-types-client-example](./various-content-types-client-example) - A client showing how to provide and handle the various content types.
3434
- [various-content-types-server-example](./various-content-types-server-example) - A server showing how to handle and provide the various content types.
35+
- [event-streams-client-example](./event-streams-client-example) - A client showing how to provide and handle event streams.
36+
- [event-streams-server-example](./event-streams-server-example) - A server showing how to handle and provide event streams.
3537

3638
## Integrations
3739

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.DS_Store
2+
.build
3+
/Packages
4+
/*.xcodeproj
5+
xcuserdata/
6+
DerivedData/
7+
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8+
.vscode
9+
/Package.resolved
10+
.ci/
11+
.docc-build/
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// swift-tools-version:5.9
2+
//===----------------------------------------------------------------------===//
3+
//
4+
// This source file is part of the SwiftOpenAPIGenerator open source project
5+
//
6+
// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors
7+
// Licensed under Apache License v2.0
8+
//
9+
// See LICENSE.txt for license information
10+
// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors
11+
//
12+
// SPDX-License-Identifier: Apache-2.0
13+
//
14+
//===----------------------------------------------------------------------===//
15+
import PackageDescription
16+
17+
let package = Package(
18+
name: "event-streams-client-example",
19+
platforms: [.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6), .visionOS(.v1)],
20+
dependencies: [
21+
.package(url: "https://github.com/apple/swift-openapi-generator", from: "1.0.0"),
22+
.package(url: "https://github.com/apple/swift-openapi-runtime", from: "1.2.0"),
23+
.package(url: "https://github.com/apple/swift-openapi-urlsession", from: "1.0.0"),
24+
],
25+
targets: [
26+
.executableTarget(
27+
name: "EventStreamsClient",
28+
dependencies: [
29+
.product(name: "OpenAPIRuntime", package: "swift-openapi-runtime"),
30+
.product(name: "OpenAPIURLSession", package: "swift-openapi-urlsession"),
31+
],
32+
plugins: [.plugin(name: "OpenAPIGenerator", package: "swift-openapi-generator")]
33+
)
34+
]
35+
)
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Client handling event streams
2+
3+
An example project using [Swift OpenAPI Generator](https://github.com/apple/swift-openapi-generator).
4+
5+
> **Disclaimer:** This example is deliberately simplified and is intended for illustrative purposes only.
6+
7+
## Overview
8+
9+
A command-line tool that uses a generated client to show how to work with event streams, such as JSON Lines, JSON Sequence, and Server-sent Events.
10+
11+
The tool uses the [URLSession](https://developer.apple.com/documentation/foundation/urlsession) API to perform the HTTP call, wrapped in the [Swift OpenAPI URLSession Transport](https://github.com/apple/swift-openapi-urlsession).
12+
13+
The server can be started by running `event-streams-server-example` locally.
14+
15+
## Usage
16+
17+
Build and run the client CLI using:
18+
19+
```console
20+
% swift run
21+
```
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftOpenAPIGenerator open source project
4+
//
5+
// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
import OpenAPIRuntime
15+
import OpenAPIURLSession
16+
import Foundation
17+
18+
@main struct EventStreamsClient {
19+
static func main() async throws {
20+
let client = Client(serverURL: URL(string: "http://localhost:8080/api")!, transport: URLSessionTransport())
21+
do {
22+
print("Fetching greetings using JSON Lines")
23+
let response = try await client.getGreetingsStream(
24+
query: .init(name: "Example", count: 3),
25+
headers: .init(accept: [.init(contentType: .application_jsonl)])
26+
)
27+
let greetingStream = try response.ok.body.application_jsonl.asDecodedJSONLines(
28+
of: Components.Schemas.Greeting.self
29+
)
30+
for try await greeting in greetingStream { print("Got greeting: \(greeting.message)") }
31+
}
32+
do {
33+
print("Fetching greetings using JSON Sequence")
34+
let response = try await client.getGreetingsStream(
35+
query: .init(name: "Example", count: 3),
36+
headers: .init(accept: [.init(contentType: .application_json_hyphen_seq)])
37+
)
38+
let greetingStream = try response.ok.body.application_json_hyphen_seq.asDecodedJSONSequence(
39+
of: Components.Schemas.Greeting.self
40+
)
41+
for try await greeting in greetingStream { print("Got greeting: \(greeting.message)") }
42+
}
43+
do {
44+
print("Fetching greetings using Server-sent Events")
45+
let response = try await client.getGreetingsStream(
46+
query: .init(name: "Example", count: 3),
47+
headers: .init(accept: [.init(contentType: .text_event_hyphen_stream)])
48+
)
49+
let greetingStream = try response.ok.body.text_event_hyphen_stream.asDecodedServerSentEventsWithJSONData(
50+
of: Components.Schemas.Greeting.self
51+
)
52+
for try await greeting in greetingStream { print("Got greeting: \(greeting.data?.message ?? "<nil>")") }
53+
}
54+
}
55+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
generate:
2+
- types
3+
- client
4+
accessModifier: internal
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
openapi: '3.1.0'
2+
info:
3+
title: EventStreamsService
4+
version: 1.0.0
5+
servers:
6+
- url: https://example.com/api
7+
description: Example service deployment.
8+
paths:
9+
/greetings:
10+
get:
11+
operationId: getGreetingsStream
12+
parameters:
13+
- name: name
14+
required: false
15+
in: query
16+
description: The name used in the returned greeting.
17+
schema:
18+
type: string
19+
- name: count
20+
required: false
21+
in: query
22+
description: The number of greetings returned in the stream.
23+
schema:
24+
type: integer
25+
format: int32
26+
default: 10
27+
responses:
28+
'200':
29+
description: A success response with a greetings stream.
30+
content:
31+
application/jsonl: {}
32+
application/json-seq: {}
33+
text/event-stream: {}
34+
components:
35+
schemas:
36+
Greeting:
37+
type: object
38+
description: A value with the greeting contents.
39+
properties:
40+
message:
41+
type: string
42+
description: The string representation of the greeting.
43+
required:
44+
- message
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.DS_Store
2+
.build
3+
/Packages
4+
/*.xcodeproj
5+
xcuserdata/
6+
DerivedData/
7+
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8+
.vscode
9+
/Package.resolved
10+
.ci/
11+
.docc-build/
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// swift-tools-version:5.9
2+
//===----------------------------------------------------------------------===//
3+
//
4+
// This source file is part of the SwiftOpenAPIGenerator open source project
5+
//
6+
// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors
7+
// Licensed under Apache License v2.0
8+
//
9+
// See LICENSE.txt for license information
10+
// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors
11+
//
12+
// SPDX-License-Identifier: Apache-2.0
13+
//
14+
//===----------------------------------------------------------------------===//
15+
import PackageDescription
16+
17+
let package = Package(
18+
name: "event-streams-server-example",
19+
platforms: [.macOS(.v10_15)],
20+
dependencies: [
21+
.package(url: "https://github.com/apple/swift-openapi-generator", from: "1.0.0"),
22+
.package(url: "https://github.com/apple/swift-openapi-runtime", from: "1.2.0"),
23+
.package(url: "https://github.com/swift-server/swift-openapi-vapor", from: "1.0.0"),
24+
.package(url: "https://github.com/vapor/vapor", from: "4.89.0"),
25+
],
26+
targets: [
27+
.executableTarget(
28+
name: "EventStreamsServer",
29+
dependencies: [
30+
.product(name: "OpenAPIRuntime", package: "swift-openapi-runtime"),
31+
.product(name: "OpenAPIVapor", package: "swift-openapi-vapor"),
32+
.product(name: "Vapor", package: "vapor"),
33+
],
34+
plugins: [.plugin(name: "OpenAPIGenerator", package: "swift-openapi-generator")]
35+
)
36+
]
37+
)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Server supporting event streams
2+
3+
An example project using [Swift OpenAPI Generator](https://github.com/apple/swift-openapi-generator).
4+
5+
> **Disclaimer:** This example is deliberately simplified and is intended for illustrative purposes only.
6+
7+
## Overview
8+
9+
A server that uses generated server stubs to show how to work with event streams, such as JSON Lines, JSON Sequence, and Server-sent Events.
10+
11+
The tool uses the [Vapor](https://github.com/vapor/vapor) server framework to handle HTTP requests, wrapped in the [Swift OpenAPI Vapor Transport](https://github.com/swift-server/swift-openapi-vapor).
12+
13+
The CLI starts the server on `http://localhost:8080` and can be invoked by running `event-streams-client-example` or on the command line using:
14+
15+
```console
16+
% curl -N http://127.0.0.1:8080/api/greetings\?name\=CLI\&count\=3
17+
{"message":"Hey, CLI!"}
18+
{"message":"Hello, CLI!"}
19+
{"message":"Greetings, CLI!"}
20+
```
21+
22+
## Usage
23+
24+
Build and run the server CLI using:
25+
26+
```console
27+
% swift run
28+
2023-12-01T14:14:35+0100 notice codes.vapor.application : [Vapor] Server starting on http://127.0.0.1:8080
29+
...
30+
```
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftOpenAPIGenerator open source project
4+
//
5+
// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
import OpenAPIRuntime
15+
import OpenAPIVapor
16+
import Vapor
17+
18+
struct Handler: APIProtocol {
19+
private let storage: StreamStorage = .init()
20+
func getGreetingsStream(_ input: Operations.getGreetingsStream.Input) async throws
21+
-> Operations.getGreetingsStream.Output
22+
{
23+
let name = input.query.name ?? "Stranger"
24+
let count = input.query.count ?? 10
25+
let eventStream = storage.makeStream(name: name, count: count)
26+
let responseBody: Operations.getGreetingsStream.Output.Ok.Body
27+
// Default to `application/jsonl`, if no other content type requested through the `Accept` header.
28+
let chosenContentType = input.headers.accept.sortedByQuality().first ?? .init(contentType: .application_jsonl)
29+
switch chosenContentType.contentType {
30+
case .application_jsonl, .other:
31+
responseBody = .application_jsonl(
32+
.init(eventStream.asEncodedJSONLines(), length: .unknown, iterationBehavior: .single)
33+
)
34+
case .application_json_hyphen_seq:
35+
responseBody = .application_json_hyphen_seq(
36+
.init(eventStream.asEncodedJSONSequence(), length: .unknown, iterationBehavior: .single)
37+
)
38+
case .text_event_hyphen_stream:
39+
responseBody = .text_event_hyphen_stream(
40+
.init(
41+
eventStream.map { greeting in
42+
ServerSentEventWithJSONData(
43+
event: "greeting",
44+
data: greeting,
45+
id: UUID().uuidString,
46+
retry: 10_000
47+
)
48+
}
49+
.asEncodedServerSentEventsWithJSONData(),
50+
length: .unknown,
51+
iterationBehavior: .single
52+
)
53+
)
54+
}
55+
return .ok(.init(body: responseBody))
56+
}
57+
}
58+
59+
@main struct EventStreamsServer {
60+
static func main() async throws {
61+
let app = Vapor.Application()
62+
let transport = VaporTransport(routesBuilder: app)
63+
let handler = Handler()
64+
try handler.registerHandlers(on: transport, serverURL: URL(string: "/api")!)
65+
try await app.execute()
66+
}
67+
}

0 commit comments

Comments
 (0)