Skip to content

Commit bf043df

Browse files
authored
Merge pull request #93 from contentstack/feat/DX-1405-Async-await-support
Add async/await support
2 parents c0550a5 + 098a40a commit bf043df

File tree

12 files changed

+629
-2
lines changed

12 files changed

+629
-2
lines changed

.gitignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,5 +80,5 @@ Gemfile
8080
#config file
8181
Tests/config.json
8282

83-
snyk_output.json
84-
talisman_output.json
83+
snyk_output.log
84+
talisman_output.log

.talismanrc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ fileignoreconfig:
2828
checksum: dfabf06aeff3576c9347e52b3c494635477d81c7d121d8f1435d79f28829f4d1
2929
- filename: ContentstackSwift.xcodeproj/project.pbxproj
3030
checksum: 8937f832171f26061a209adcd808683f7bdfb739e7fc49aecd853d5055466251
31+
- filename: ContentstackSwift.xcodeproj/project.pbxproj
32+
checksum: b743f609350e19c2a05a2081f3af3f723992b9610b3b3e6aa402792cad1de2c5
3133
version: "1.0"
3234

3335

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# CHANGELOG
22

3+
## v2.2.0
4+
5+
### Date: 01-Sep-2025
6+
7+
- Async/await support added
8+
39
## v2.1.0
410

511
### Date: 06-Jun-2025

ContentstackSwift.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,9 @@
265265
47C6EFC52C0B5B9400F0D5CF /* Taxonomy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47C6EFC12C0B5B9400F0D5CF /* Taxonomy.swift */; };
266266
47D561512C9EF97D00DC085D /* ContentstackUtils in Frameworks */ = {isa = PBXBuildFile; productRef = 47D561502C9EF97D00DC085D /* ContentstackUtils */; };
267267
47D561572C9EFA5900DC085D /* DVR in Frameworks */ = {isa = PBXBuildFile; productRef = 47D561562C9EFA5900DC085D /* DVR */; };
268+
672F76992E55ADBE00C248D6 /* AsyncAwaitAPITest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 672F76982E55ADBE00C248D6 /* AsyncAwaitAPITest.swift */; };
269+
672F769A2E55ADBE00C248D6 /* AsyncAwaitAPITest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 672F76982E55ADBE00C248D6 /* AsyncAwaitAPITest.swift */; };
270+
672F769B2E55ADBE00C248D6 /* AsyncAwaitAPITest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 672F76982E55ADBE00C248D6 /* AsyncAwaitAPITest.swift */; };
268271
6750778E2D3E256A0076A066 /* DVR in Frameworks */ = {isa = PBXBuildFile; productRef = 6750778D2D3E256A0076A066 /* DVR */; };
269272
67EE21DF2DDB4013005AC119 /* CSURLSessionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EE21DE2DDB3FFE005AC119 /* CSURLSessionDelegate.swift */; };
270273
67EE21E02DDB4013005AC119 /* CSURLSessionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EE21DE2DDB3FFE005AC119 /* CSURLSessionDelegate.swift */; };
@@ -416,6 +419,7 @@
416419
47B09C242CA952E400B8AB41 /* DVR.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = DVR.framework; sourceTree = BUILT_PRODUCTS_DIR; };
417420
47B4DC612C232A8200370CFC /* TaxonomyTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaxonomyTest.swift; sourceTree = "<group>"; };
418421
47C6EFC12C0B5B9400F0D5CF /* Taxonomy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Taxonomy.swift; sourceTree = "<group>"; };
422+
672F76982E55ADBE00C248D6 /* AsyncAwaitAPITest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncAwaitAPITest.swift; sourceTree = "<group>"; };
419423
67EE21DE2DDB3FFE005AC119 /* CSURLSessionDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CSURLSessionDelegate.swift; sourceTree = "<group>"; };
420424
67EE222B2DE4868F005AC119 /* GlobalField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalField.swift; sourceTree = "<group>"; };
421425
67EE22302DE58B51005AC119 /* GlobalFieldModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalFieldModel.swift; sourceTree = "<group>"; };
@@ -666,6 +670,7 @@
666670
0FFA5D9D241F8F9B003B3AF5 /* APITests */ = {
667671
isa = PBXGroup;
668672
children = (
673+
672F76982E55ADBE00C248D6 /* AsyncAwaitAPITest.swift */,
669674
67EE22352DE5BAF2005AC119 /* GlobalFieldAPITest.swift */,
670675
0F50EA1C244ED88C00E5D705 /* StackCacheAPITest.swift */,
671676
470657532B5E785C00BBFF88 /* ContentTypeQueryAPITest.swift */,
@@ -1146,6 +1151,7 @@
11461151
47B4DC622C232A8200370CFC /* TaxonomyTest.swift in Sources */,
11471152
0F50EA1D244ED88C00E5D705 /* StackCacheAPITest.swift in Sources */,
11481153
470657582B5E788400BBFF88 /* EntryAPITest.swift in Sources */,
1154+
672F76992E55ADBE00C248D6 /* AsyncAwaitAPITest.swift in Sources */,
11491155
0F096B14243610470094F042 /* ImageTransformTestAdditional.swift in Sources */,
11501156
0FD39D4A24237A0400E34826 /* ContentTypeQueryTest.swift in Sources */,
11511157
0FFBB44C24470C43000D2795 /* ContentStackLogTest.swift in Sources */,
@@ -1228,6 +1234,7 @@
12281234
47B4DC632C232A8200370CFC /* TaxonomyTest.swift in Sources */,
12291235
0FFA5D90241F8126003B3AF5 /* ContentstackConfigTest.swift in Sources */,
12301236
0F50EA1E244ED88C00E5D705 /* StackCacheAPITest.swift in Sources */,
1237+
672F769B2E55ADBE00C248D6 /* AsyncAwaitAPITest.swift in Sources */,
12311238
0F096B15243610470094F042 /* ImageTransformTestAdditional.swift in Sources */,
12321239
0FD39D4B24237A0400E34826 /* ContentTypeQueryTest.swift in Sources */,
12331240
0FFBB44D24470C43000D2795 /* ContentStackLogTest.swift in Sources */,
@@ -1310,6 +1317,7 @@
13101317
47B4DC642C232A8200370CFC /* TaxonomyTest.swift in Sources */,
13111318
0FFA5D91241F8127003B3AF5 /* ContentstackConfigTest.swift in Sources */,
13121319
0F50EA1F244ED88C00E5D705 /* StackCacheAPITest.swift in Sources */,
1320+
672F769A2E55ADBE00C248D6 /* AsyncAwaitAPITest.swift in Sources */,
13131321
0F096B16243610470094F042 /* ImageTransformTestAdditional.swift in Sources */,
13141322
0FD39D4C24237A0400E34826 /* ContentTypeQueryTest.swift in Sources */,
13151323
0FFBB44E24470C43000D2795 /* ContentStackLogTest.swift in Sources */,

Sources/Asset.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,4 +246,27 @@ extension Asset: ResourceQueryable {
246246
}
247247
})
248248
}
249+
250+
// MARK: - Async/Await Implementation
251+
252+
/// Async version of fetch that returns the Asset directly
253+
/// - Returns: The fetched Asset
254+
/// - Throws: Network, decoding, or cache errors
255+
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
256+
public func fetch<ResourceType>() async throws -> ResourceType
257+
where ResourceType: EndpointAccessible & Decodable {
258+
guard let uid = self.uid else { fatalError("Please provide Asset uid") }
259+
let response: ContentstackResponse<ResourceType> = try await self.stack.fetch(
260+
endpoint: ResourceType.endpoint,
261+
cachePolicy: self.cachePolicy,
262+
parameters: parameters + [QueryParameter.uid: uid],
263+
headers: headers
264+
)
265+
266+
if let resource = response.items.first {
267+
return resource
268+
} else {
269+
throw SDKError.invalidUID(string: uid)
270+
}
271+
}
249272
}

Sources/ContentType.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,4 +178,27 @@ extension ContentType: ResourceQueryable {
178178
}
179179
})
180180
}
181+
182+
// MARK: - Async/Await Implementation
183+
184+
/// Async version of fetch that returns the ContentType directly
185+
/// - Returns: The fetched ContentType
186+
/// - Throws: Network, decoding, or cache errors
187+
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
188+
public func fetch<ResourceType>() async throws -> ResourceType
189+
where ResourceType: EndpointAccessible & Decodable {
190+
guard let uid = self.uid else { fatalError("Please provide ContentType uid") }
191+
let response: ContentstackResponse<ResourceType> = try await self.stack.fetch(
192+
endpoint: ResourceType.endpoint,
193+
cachePolicy: self.cachePolicy,
194+
parameters: parameters + [QueryParameter.uid: uid],
195+
headers: headers
196+
)
197+
198+
if let resource = response.items.first {
199+
return resource
200+
} else {
201+
throw SDKError.invalidUID(string: uid)
202+
}
203+
}
181204
}

Sources/Entry.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,4 +175,28 @@ extension Entry: ResourceQueryable {
175175
}
176176
})
177177
}
178+
179+
// MARK: - Async/Await Implementation
180+
181+
/// Async version of fetch that returns the Entry directly
182+
/// - Returns: The fetched Entry
183+
/// - Throws: Network, decoding, or cache errors
184+
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
185+
public func fetch<ResourceType>() async throws -> ResourceType
186+
where ResourceType: EndpointAccessible & Decodable {
187+
guard let uid = self.uid else { fatalError("Please provide Entry uid") }
188+
let response: ContentstackResponse<ResourceType> = try await self.stack.fetch(
189+
endpoint: ResourceType.endpoint,
190+
cachePolicy: self.cachePolicy,
191+
parameters: parameters + [QueryParameter.uid: uid,
192+
QueryParameter.contentType: self.contentType.uid!],
193+
headers: headers
194+
)
195+
196+
if let resource = response.items.first {
197+
return resource
198+
} else {
199+
throw SDKError.invalidUID(string: uid)
200+
}
201+
}
178202
}

Sources/GlobalField.swift

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,29 @@ extension GlobalField: ResourceQueryable {
8080
}
8181
})
8282
}
83+
84+
// MARK: - Async/Await Implementation for fetch
85+
86+
/// Async version of fetch that returns the GlobalField directly
87+
/// - Returns: The fetched GlobalField
88+
/// - Throws: Network, decoding, or cache errors
89+
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
90+
public func fetch<ResourceType>() async throws -> ResourceType
91+
where ResourceType: EndpointAccessible & Decodable {
92+
guard let uid = self.uid else { fatalError("Please provide Global Field uid") }
93+
let response: ContentstackResponse<ResourceType> = try await self.stack.fetch(
94+
endpoint: ResourceType.endpoint,
95+
cachePolicy: self.cachePolicy,
96+
parameters: parameters + [QueryParameter.uid: uid],
97+
headers: headers
98+
)
99+
100+
if let resource = response.items.first {
101+
return resource
102+
} else {
103+
throw SDKError.invalidUID(string: uid)
104+
}
105+
}
83106
}
84107

85108
extension GlobalField : Queryable{
@@ -92,4 +115,19 @@ extension GlobalField : Queryable{
92115
cachePolicy: self.cachePolicy, parameters: parameters, headers: headers, then: completion)
93116
}
94117

118+
// MARK: - Async/Await Implementation
119+
120+
/// Async implementation of find for GlobalField
121+
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
122+
public func find<ResourceType>() async throws -> ContentstackResponse<ResourceType>
123+
where ResourceType: Decodable & EndpointAccessible {
124+
if self.queryParameter.count > 0,
125+
let query = self.queryParameter.jsonString {
126+
self.parameters[QueryParameter.query] = query
127+
}
128+
return try await self.stack.fetch(endpoint: ResourceType.endpoint,
129+
cachePolicy: self.cachePolicy,
130+
parameters: parameters,
131+
headers: headers)
132+
}
95133
}

Sources/QueryProtocols.swift

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,22 @@ extension BaseQuery {
7575
self.stack.fetch(endpoint: ResourceType.endpoint,
7676
cachePolicy: self.cachePolicy, parameters: parameters, headers: headers, then: completion)
7777
}
78+
79+
/// Async version of find that returns the ContentstackResponse directly
80+
/// - Returns: The ContentstackResponse with the requested resources
81+
/// - Throws: Network, decoding, or cache errors
82+
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
83+
public func find<ResourceType>() async throws -> ContentstackResponse<ResourceType>
84+
where ResourceType: Decodable & EndpointAccessible {
85+
if self.queryParameter.count > 0,
86+
let query = self.queryParameter.jsonString {
87+
self.parameters[QueryParameter.query] = query
88+
}
89+
return try await self.stack.fetch(endpoint: ResourceType.endpoint,
90+
cachePolicy: self.cachePolicy,
91+
parameters: parameters,
92+
headers: headers)
93+
}
7894
}
7995
/// A concrete implementation of BaseQuery which serves as the base class for `Query`,
8096
/// `ContentTypeQuery` and `AssetQuery`.
@@ -557,6 +573,13 @@ public protocol ResourceQueryable {
557573
/// - completion: A handler which will be called on completion of the operation.
558574
func fetch<ResourceType>(_ completion: @escaping ResultsHandler<ResourceType>)
559575
where ResourceType: Decodable & EndpointAccessible
576+
577+
/// Async version of fetch that returns the resource directly
578+
/// - Returns: The fetched resource
579+
/// - Throws: Network, decoding, or cache errors
580+
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
581+
func fetch<ResourceType>() async throws -> ResourceType
582+
where ResourceType: Decodable & EndpointAccessible
560583
}
561584

562585
/// The base Queryable protocol to find collections for content types, assets, and entries.
@@ -567,4 +590,11 @@ public protocol Queryable {
567590
/// - completion: A handler which will be called on completion of the operation.
568591
func find<ResourceType>(_ completion: @escaping ResultsHandler<ContentstackResponse<ResourceType>>)
569592
where ResourceType: Decodable & EndpointAccessible
593+
594+
/// Async version of find that returns the ContentstackResponse directly
595+
/// - Returns: The ContentstackResponse with the requested resources
596+
/// - Throws: Network, decoding, or cache errors
597+
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
598+
func find<ResourceType>() async throws -> ContentstackResponse<ResourceType>
599+
where ResourceType: Decodable & EndpointAccessible
570600
}

Sources/Stack.swift

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,29 @@ public class Stack: CachePolicyAccessible {
263263
performDataTask(dataTask!, request: request, cachePolicy: cachePolicy, then: completion)
264264
}
265265

266+
// MARK: - Async/Await Support
267+
268+
/// Async version of fetchUrl that returns the result directly
269+
/// - Parameters:
270+
/// - url: The URL to fetch
271+
/// - headers: HTTP headers to include in the request
272+
/// - cachePolicy: The cache policy to use
273+
/// - Returns: A tuple containing the data and response type
274+
/// - Throws: Network or cache errors
275+
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
276+
private func fetchUrl(_ url: URL, headers: [String: String], cachePolicy: CachePolicy) async throws -> (Data, ResponseType) {
277+
return try await withCheckedThrowingContinuation { continuation in
278+
fetchUrl(url, headers: headers, cachePolicy: cachePolicy) { result, responseType in
279+
switch result {
280+
case .success(let data):
281+
continuation.resume(returning: (data, responseType))
282+
case .failure(let error):
283+
continuation.resume(throwing: error)
284+
}
285+
}
286+
}
287+
}
288+
266289
internal func fetch<ResourceType>(endpoint: Endpoint,
267290
cachePolicy: CachePolicy,
268291
parameters: Parameters = [:],
@@ -284,6 +307,25 @@ public class Stack: CachePolicyAccessible {
284307
}
285308
})
286309
}
310+
311+
/// Async version of fetch that returns the decoded resource directly
312+
/// - Parameters:
313+
/// - endpoint: The API endpoint to fetch from
314+
/// - cachePolicy: The cache policy to use
315+
/// - parameters: Query parameters
316+
/// - headers: HTTP headers
317+
/// - Returns: The decoded resource
318+
/// - Throws: Network, decoding, or cache errors
319+
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
320+
internal func fetch<ResourceType>(endpoint: Endpoint,
321+
cachePolicy: CachePolicy,
322+
parameters: Parameters = [:],
323+
headers: [String: String] = [:]) async throws -> ResourceType
324+
where ResourceType: Decodable {
325+
let url = self.url(endpoint: endpoint, parameters: parameters)
326+
let (data, _) = try await fetchUrl(url, headers: headers, cachePolicy: cachePolicy)
327+
return try self.jsonDecoder.decode(ResourceType.self, from: data)
328+
}
287329

288330
private func performDataTask(_ dataTask: URLSessionDataTask,
289331
request: URLRequest,
@@ -397,4 +439,45 @@ extension Stack {
397439
}
398440
}
399441
}
442+
443+
/// Async version of sync that returns the SyncStack directly
444+
/// - Parameters:
445+
/// - syncStack: The relevant `SyncStack` to perform the subsequent sync on.
446+
/// Defaults to a new empty instance of `SyncStack`.
447+
/// - syncTypes: `SyncableTypes` that can be sync.
448+
/// - Returns: The SyncStack with synced data
449+
/// - Throws: Network or decoding errors
450+
///
451+
/// Example usage:
452+
///```
453+
/// let stack = Contentstack.stack(apiKey: apiKey,
454+
/// deliveryToken: deliveryToken,
455+
/// environment: environment)
456+
///
457+
/// do {
458+
/// let syncStack = try await stack.sync()
459+
/// let items = syncStack.items
460+
/// } catch {
461+
/// print(error)
462+
/// }
463+
///```
464+
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
465+
public func sync(_ syncStack: SyncStack = SyncStack(),
466+
syncTypes: [SyncStack.SyncableTypes] = [.all]) async throws -> SyncStack {
467+
var parameter = syncStack.parameter
468+
if syncStack.isInitialSync {
469+
for syncType in syncTypes {
470+
parameter = parameter + syncType.parameters
471+
}
472+
}
473+
let url = self.url(endpoint: SyncStack.endpoint, parameters: parameter)
474+
let (data, _) = try await fetchUrl(url, headers: [:], cachePolicy: .networkOnly)
475+
let result = try self.jsonDecoder.decode(SyncStack.self, from: data)
476+
477+
if result.hasMorePages {
478+
return try await sync(result, syncTypes: syncTypes)
479+
}
480+
481+
return result
482+
}
400483
}

0 commit comments

Comments
 (0)