Skip to content

Commit b9c0d42

Browse files
committed
Swift Recombee API Client
0 parents  commit b9c0d42

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+4531
-0
lines changed

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 Recombee
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

Package.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// swift-tools-version:5.7
2+
import PackageDescription
3+
4+
let package = Package(
5+
name: "RecombeeClient",
6+
platforms: [
7+
.iOS(.v14),
8+
.tvOS(.v14),
9+
.macOS(.v11),
10+
.watchOS(.v7),
11+
],
12+
products: [
13+
.library(
14+
name: "RecombeeClient",
15+
targets: ["RecombeeClient"]
16+
),
17+
],
18+
dependencies: [
19+
.package(url: "https://github.com/Flight-School/AnyCodable.git", from: "0.6.7"),
20+
],
21+
targets: [
22+
.target(
23+
name: "RecombeeClient",
24+
dependencies: ["AnyCodable"],
25+
path: "Sources"
26+
),
27+
.testTarget(
28+
name: "RecombeeClientTests",
29+
dependencies: ["RecombeeClient"],
30+
path: "Tests"
31+
),
32+
]
33+
)

README.md

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
<div align="center">
2+
<img
3+
src="https://raw.githubusercontent.com/recombee/.github/refs/heads/main/assets/mark.svg"
4+
width="64px"
5+
align="center"
6+
alt="Recombee"
7+
/>
8+
<br />
9+
<h1>Recombee Swift API Client</h1>
10+
</div>
11+
12+
<p align="center">
13+
<a href="https://swiftpackageindex.com/recombee/swift-api-client" rel="nofollow"><img src="https://img.shields.io/badge/SwiftPM-Compatible-brightgreen.svg" alt="SwiftPM"></a>
14+
<a href="https://opensource.org/licenses/MIT" rel="nofollow"><img src="https://img.shields.io/github/license/recombee/swift-api-client" alt="License"></a>
15+
</p>
16+
17+
<div align="center">
18+
<a href="https://docs.recombee.com/swift_client">Documentation</a>
19+
<span>&nbsp;&nbsp;&nbsp;&nbsp;</span>
20+
<a href="https://github.com/recombee/swift-api-client/issues/new">Issues</a>
21+
<span>&nbsp;&nbsp;&nbsp;&nbsp;</span>
22+
<a href="mailto:[email protected]">Support</a>
23+
<br />
24+
</div>
25+
26+
## ✨ Features
27+
28+
- Thin Swift wrapper around the Recombee API
29+
- Supports following endpoints: [Interactions](https://docs.recombee.com/api#user-item-interactions), [Recommendations](https://docs.recombee.com/api#recommendations), [Search](https://docs.recombee.com/api#search), and more
30+
- Fully async/await enabled for modern iOS development
31+
- Typed request/response interfaces with JSON decoding helpers
32+
33+
## 🚀 Getting Started
34+
35+
Add the dependency to your Xcode project using **Swift Package Manager**:
36+
37+
1. In Xcode, go to `File → Add Packages`
38+
2. Enter the repository URL:
39+
40+
```
41+
https://github.com/recombee/swift-api-client
42+
```
43+
44+
3. Choose the latest version and confirm
45+
46+
Alternatively, in `Package.swift`:
47+
48+
```swift
49+
.package(url: "https://github.com/recombee/swift-api-client", from: "5.0.0")
50+
```
51+
52+
Then add to your target dependencies:
53+
54+
```swift
55+
.target(
56+
name: "MyApp",
57+
dependencies: ["RecombeeClient"]
58+
)
59+
```
60+
61+
## 🏗️ Example
62+
63+
You can send user-item interactions and receive recommendations like this:
64+
65+
```swift
66+
import RecombeeClient
67+
68+
// Initialize the API client with the ID of your database and the associated PUBLIC token
69+
let client = RecombeeClient(
70+
databaseId: "your-database-id",
71+
publicToken: "...db-public-token...",
72+
region: .euWest // the region of your database
73+
)
74+
75+
do {
76+
// Send interactions
77+
try await client.send(
78+
AddDetailView(
79+
userId: "user-4395",
80+
itemId: "item-129",
81+
recommId: "23eaa09b-0e24-4487-ba9c-8e255feb01bb"
82+
)
83+
)
84+
85+
// Request recommendations
86+
let response = try await client.send(
87+
RecommendItemsToUser(
88+
userId: "user-x",
89+
count: 10,
90+
scenario: "homepage-top-for-you",
91+
returnProperties: true,
92+
includedProperties: ["title"]
93+
)
94+
)
95+
96+
// `recommId` needs to be sent with interactions based on recommendations
97+
print("recommId: \(response.recommId)")
98+
99+
// The `recomms` object contains the `id` (and `values` if `returnProperties` is true)
100+
for item in response.recomms {
101+
let title = item.values?["title"] as? String ?? "(unknown)"
102+
print("ID: \(item.id), Title: \(title)")
103+
}
104+
105+
} catch let error as ClientError {
106+
print("ClientError: \(error.errorDescription ?? "Unknown error")")
107+
// Ideally, provide a fallback if an error occurs...
108+
} catch {
109+
print("Unexpected error: \(error.localizedDescription)")
110+
}
111+
```
112+
113+
## 📝 Documentation
114+
115+
Discover the full [Swift API Client documentation](https://docs.recombee.com/swift_client) for comprehensive guides and examples.
116+
117+
For a complete breakdown of all endpoints and their responses, check out our [API Reference](https://docs.recombee.com/api).
118+
119+
## 🤝 Contributing
120+
121+
We welcome all contributions—whether it’s fixing a bug, improving documentation, or suggesting a new feature.
122+
123+
To contribute, simply fork the repository, make your changes, and submit a pull request. Be sure to provide a clear description of your changes.
124+
125+
Thanks for helping make this project better!
126+
127+
## 🔧 Troubleshooting
128+
129+
Are you having issues? We recommend checking [our documentation](https://docs.recombee.com/swift_client) to see if it contains a possible solution.
130+
131+
If you want to reach out, you can either [open a GitHub issue](https://github.com/recombee/swift-api-client/issues/new) or send an email to [email protected].
132+
133+
134+
We're happy to help!
135+
136+
## 📄 License
137+
138+
The Recombee Swift API Client is provided under the [MIT License](https://opensource.org/licenses/MIT).
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import Foundation
2+
3+
/// An error type representing various failure scenarios encountered when communicating with the Recombee API.
4+
public enum ClientError: LocalizedError {
5+
/// The request exceeded the configured timeout duration.
6+
///
7+
/// - Parameters:
8+
/// - request: The request that timed out.
9+
/// - seconds: The timeout interval in seconds.
10+
case timeout(request: (any Request)?, seconds: TimeInterval)
11+
12+
/// The API responded with a non-success HTTP status code (e.g. 400, 500).
13+
///
14+
/// - Parameters:
15+
/// - request: The request that caused the error.
16+
/// - statusCode: The HTTP status code returned by the API.
17+
/// - message: An optional message extracted from the API error response.
18+
case responseError(request: (any Request)?, statusCode: Int, message: String)
19+
20+
/// A network-related error occurred (e.g. connection failure).
21+
///
22+
/// - Parameters:
23+
/// - request: The request that caused the error.
24+
/// - underlyingError: The underlying `Error` from the networking layer.
25+
case networkError(request: (any Request)?, underlyingError: Error)
26+
27+
/// Failed to decode the response returned by the API.
28+
///
29+
/// - Parameters:
30+
/// - request: The request that caused the error.
31+
/// - underlyingError: The error thrown during decoding.
32+
case decodingError(request: (any Request)?, underlyingError: Error)
33+
34+
/// An unspecified or unrecognized error occurred.
35+
///
36+
/// - Parameter request: The request that caused the error.
37+
case unknownError(request: (any Request)?)
38+
39+
/// A user-readable description of the error.
40+
public var errorDescription: String? {
41+
switch self {
42+
case let .timeout(_, seconds):
43+
return "The request timed out after \(Int(seconds)) seconds."
44+
case let .responseError(_, statusCode, message):
45+
return """
46+
The API returned a non-success status code
47+
- Status Code: \(statusCode)
48+
- Message: \(message)
49+
"""
50+
case let .networkError(_, error):
51+
return "A network error occurred: \(error.localizedDescription)"
52+
case let .decodingError(_, error):
53+
return "Failed to decode the response: \(error.localizedDescription)"
54+
case .unknownError:
55+
return "An unknown error occurred."
56+
}
57+
}
58+
59+
/// A short explanation of why the operation failed.
60+
public var failureReason: String? {
61+
switch self {
62+
case .timeout:
63+
return "The API did not respond within the timeout interval."
64+
case let .responseError(_, statusCode, _):
65+
return "The API returned a non-success status code: \(statusCode)."
66+
case .networkError:
67+
return "A network-related issue occurred."
68+
case .decodingError:
69+
return "The data could not be processed."
70+
case .unknownError:
71+
return "The error is unidentifiable."
72+
}
73+
}
74+
75+
/// The request that caused this error, if available.
76+
///
77+
/// Useful for debugging batch responses or identifying which specific request failed.
78+
public var request: (any Request)? {
79+
switch self {
80+
case let .timeout(request, _),
81+
let .responseError(request, _, _),
82+
let .networkError(request, _),
83+
let .decodingError(request, _),
84+
let .unknownError(request):
85+
return request
86+
}
87+
}
88+
}

0 commit comments

Comments
 (0)