Skip to content
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
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -80,5 +80,5 @@ Gemfile
#config file
Tests/config.json

snyk_output.json
talisman_output.json
snyk_output.log
talisman_output.log
2 changes: 2 additions & 0 deletions .talismanrc
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ fileignoreconfig:
checksum: dfabf06aeff3576c9347e52b3c494635477d81c7d121d8f1435d79f28829f4d1
- filename: ContentstackSwift.xcodeproj/project.pbxproj
checksum: 8937f832171f26061a209adcd808683f7bdfb739e7fc49aecd853d5055466251
- filename: ContentstackSwift.xcodeproj/project.pbxproj
checksum: b743f609350e19c2a05a2081f3af3f723992b9610b3b3e6aa402792cad1de2c5
version: "1.0"


Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# CHANGELOG

## v2.2.0

### Date: 01-Sep-2025

- Async/await support added

## v2.1.0

### Date: 06-Jun-2025
Expand Down
8 changes: 8 additions & 0 deletions ContentstackSwift.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,9 @@
47C6EFC52C0B5B9400F0D5CF /* Taxonomy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47C6EFC12C0B5B9400F0D5CF /* Taxonomy.swift */; };
47D561512C9EF97D00DC085D /* ContentstackUtils in Frameworks */ = {isa = PBXBuildFile; productRef = 47D561502C9EF97D00DC085D /* ContentstackUtils */; };
47D561572C9EFA5900DC085D /* DVR in Frameworks */ = {isa = PBXBuildFile; productRef = 47D561562C9EFA5900DC085D /* DVR */; };
672F76992E55ADBE00C248D6 /* AsyncAwaitAPITest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 672F76982E55ADBE00C248D6 /* AsyncAwaitAPITest.swift */; };
672F769A2E55ADBE00C248D6 /* AsyncAwaitAPITest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 672F76982E55ADBE00C248D6 /* AsyncAwaitAPITest.swift */; };
672F769B2E55ADBE00C248D6 /* AsyncAwaitAPITest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 672F76982E55ADBE00C248D6 /* AsyncAwaitAPITest.swift */; };
6750778E2D3E256A0076A066 /* DVR in Frameworks */ = {isa = PBXBuildFile; productRef = 6750778D2D3E256A0076A066 /* DVR */; };
67EE21DF2DDB4013005AC119 /* CSURLSessionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EE21DE2DDB3FFE005AC119 /* CSURLSessionDelegate.swift */; };
67EE21E02DDB4013005AC119 /* CSURLSessionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EE21DE2DDB3FFE005AC119 /* CSURLSessionDelegate.swift */; };
Expand Down Expand Up @@ -416,6 +419,7 @@
47B09C242CA952E400B8AB41 /* DVR.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = DVR.framework; sourceTree = BUILT_PRODUCTS_DIR; };
47B4DC612C232A8200370CFC /* TaxonomyTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaxonomyTest.swift; sourceTree = "<group>"; };
47C6EFC12C0B5B9400F0D5CF /* Taxonomy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Taxonomy.swift; sourceTree = "<group>"; };
672F76982E55ADBE00C248D6 /* AsyncAwaitAPITest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncAwaitAPITest.swift; sourceTree = "<group>"; };
67EE21DE2DDB3FFE005AC119 /* CSURLSessionDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CSURLSessionDelegate.swift; sourceTree = "<group>"; };
67EE222B2DE4868F005AC119 /* GlobalField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalField.swift; sourceTree = "<group>"; };
67EE22302DE58B51005AC119 /* GlobalFieldModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalFieldModel.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -666,6 +670,7 @@
0FFA5D9D241F8F9B003B3AF5 /* APITests */ = {
isa = PBXGroup;
children = (
672F76982E55ADBE00C248D6 /* AsyncAwaitAPITest.swift */,
67EE22352DE5BAF2005AC119 /* GlobalFieldAPITest.swift */,
0F50EA1C244ED88C00E5D705 /* StackCacheAPITest.swift */,
470657532B5E785C00BBFF88 /* ContentTypeQueryAPITest.swift */,
Expand Down Expand Up @@ -1146,6 +1151,7 @@
47B4DC622C232A8200370CFC /* TaxonomyTest.swift in Sources */,
0F50EA1D244ED88C00E5D705 /* StackCacheAPITest.swift in Sources */,
470657582B5E788400BBFF88 /* EntryAPITest.swift in Sources */,
672F76992E55ADBE00C248D6 /* AsyncAwaitAPITest.swift in Sources */,
0F096B14243610470094F042 /* ImageTransformTestAdditional.swift in Sources */,
0FD39D4A24237A0400E34826 /* ContentTypeQueryTest.swift in Sources */,
0FFBB44C24470C43000D2795 /* ContentStackLogTest.swift in Sources */,
Expand Down Expand Up @@ -1228,6 +1234,7 @@
47B4DC632C232A8200370CFC /* TaxonomyTest.swift in Sources */,
0FFA5D90241F8126003B3AF5 /* ContentstackConfigTest.swift in Sources */,
0F50EA1E244ED88C00E5D705 /* StackCacheAPITest.swift in Sources */,
672F769B2E55ADBE00C248D6 /* AsyncAwaitAPITest.swift in Sources */,
0F096B15243610470094F042 /* ImageTransformTestAdditional.swift in Sources */,
0FD39D4B24237A0400E34826 /* ContentTypeQueryTest.swift in Sources */,
0FFBB44D24470C43000D2795 /* ContentStackLogTest.swift in Sources */,
Expand Down Expand Up @@ -1310,6 +1317,7 @@
47B4DC642C232A8200370CFC /* TaxonomyTest.swift in Sources */,
0FFA5D91241F8127003B3AF5 /* ContentstackConfigTest.swift in Sources */,
0F50EA1F244ED88C00E5D705 /* StackCacheAPITest.swift in Sources */,
672F769A2E55ADBE00C248D6 /* AsyncAwaitAPITest.swift in Sources */,
0F096B16243610470094F042 /* ImageTransformTestAdditional.swift in Sources */,
0FD39D4C24237A0400E34826 /* ContentTypeQueryTest.swift in Sources */,
0FFBB44E24470C43000D2795 /* ContentStackLogTest.swift in Sources */,
Expand Down
23 changes: 23 additions & 0 deletions Sources/Asset.swift
Original file line number Diff line number Diff line change
Expand Up @@ -246,4 +246,27 @@ extension Asset: ResourceQueryable {
}
})
}

// MARK: - Async/Await Implementation

/// Async version of fetch that returns the Asset directly
/// - Returns: The fetched Asset
/// - Throws: Network, decoding, or cache errors
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public func fetch<ResourceType>() async throws -> ResourceType
where ResourceType: EndpointAccessible & Decodable {
guard let uid = self.uid else { fatalError("Please provide Asset uid") }
let response: ContentstackResponse<ResourceType> = try await self.stack.fetch(
endpoint: ResourceType.endpoint,
cachePolicy: self.cachePolicy,
parameters: parameters + [QueryParameter.uid: uid],
headers: headers
)

if let resource = response.items.first {
return resource
} else {
throw SDKError.invalidUID(string: uid)
}
}
}
23 changes: 23 additions & 0 deletions Sources/ContentType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -178,4 +178,27 @@ extension ContentType: ResourceQueryable {
}
})
}

// MARK: - Async/Await Implementation

/// Async version of fetch that returns the ContentType directly
/// - Returns: The fetched ContentType
/// - Throws: Network, decoding, or cache errors
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public func fetch<ResourceType>() async throws -> ResourceType
where ResourceType: EndpointAccessible & Decodable {
guard let uid = self.uid else { fatalError("Please provide ContentType uid") }
let response: ContentstackResponse<ResourceType> = try await self.stack.fetch(
endpoint: ResourceType.endpoint,
cachePolicy: self.cachePolicy,
parameters: parameters + [QueryParameter.uid: uid],
headers: headers
)

if let resource = response.items.first {
return resource
} else {
throw SDKError.invalidUID(string: uid)
}
}
}
24 changes: 24 additions & 0 deletions Sources/Entry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -175,4 +175,28 @@ extension Entry: ResourceQueryable {
}
})
}

// MARK: - Async/Await Implementation

/// Async version of fetch that returns the Entry directly
/// - Returns: The fetched Entry
/// - Throws: Network, decoding, or cache errors
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public func fetch<ResourceType>() async throws -> ResourceType
where ResourceType: EndpointAccessible & Decodable {
guard let uid = self.uid else { fatalError("Please provide Entry uid") }
let response: ContentstackResponse<ResourceType> = try await self.stack.fetch(
endpoint: ResourceType.endpoint,
cachePolicy: self.cachePolicy,
parameters: parameters + [QueryParameter.uid: uid,
QueryParameter.contentType: self.contentType.uid!],
headers: headers
)

if let resource = response.items.first {
return resource
} else {
throw SDKError.invalidUID(string: uid)
}
}
}
38 changes: 38 additions & 0 deletions Sources/GlobalField.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,29 @@ extension GlobalField: ResourceQueryable {
}
})
}

// MARK: - Async/Await Implementation for fetch

/// Async version of fetch that returns the GlobalField directly
/// - Returns: The fetched GlobalField
/// - Throws: Network, decoding, or cache errors
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public func fetch<ResourceType>() async throws -> ResourceType
where ResourceType: EndpointAccessible & Decodable {
guard let uid = self.uid else { fatalError("Please provide Global Field uid") }
let response: ContentstackResponse<ResourceType> = try await self.stack.fetch(
endpoint: ResourceType.endpoint,
cachePolicy: self.cachePolicy,
parameters: parameters + [QueryParameter.uid: uid],
headers: headers
)

if let resource = response.items.first {
return resource
} else {
throw SDKError.invalidUID(string: uid)
}
}
}

extension GlobalField : Queryable{
Expand All @@ -92,4 +115,19 @@ extension GlobalField : Queryable{
cachePolicy: self.cachePolicy, parameters: parameters, headers: headers, then: completion)
}

// MARK: - Async/Await Implementation

/// Async implementation of find for GlobalField
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public func find<ResourceType>() async throws -> ContentstackResponse<ResourceType>
where ResourceType: Decodable & EndpointAccessible {
if self.queryParameter.count > 0,
let query = self.queryParameter.jsonString {
self.parameters[QueryParameter.query] = query
}
return try await self.stack.fetch(endpoint: ResourceType.endpoint,
cachePolicy: self.cachePolicy,
parameters: parameters,
headers: headers)
}
}
30 changes: 30 additions & 0 deletions Sources/QueryProtocols.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,22 @@ extension BaseQuery {
self.stack.fetch(endpoint: ResourceType.endpoint,
cachePolicy: self.cachePolicy, parameters: parameters, headers: headers, then: completion)
}

/// Async version of find that returns the ContentstackResponse directly
/// - Returns: The ContentstackResponse with the requested resources
/// - Throws: Network, decoding, or cache errors
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public func find<ResourceType>() async throws -> ContentstackResponse<ResourceType>
where ResourceType: Decodable & EndpointAccessible {
if self.queryParameter.count > 0,
let query = self.queryParameter.jsonString {
self.parameters[QueryParameter.query] = query
}
return try await self.stack.fetch(endpoint: ResourceType.endpoint,
cachePolicy: self.cachePolicy,
parameters: parameters,
headers: headers)
}
}
/// A concrete implementation of BaseQuery which serves as the base class for `Query`,
/// `ContentTypeQuery` and `AssetQuery`.
Expand Down Expand Up @@ -557,6 +573,13 @@ public protocol ResourceQueryable {
/// - completion: A handler which will be called on completion of the operation.
func fetch<ResourceType>(_ completion: @escaping ResultsHandler<ResourceType>)
where ResourceType: Decodable & EndpointAccessible

/// Async version of fetch that returns the resource directly
/// - Returns: The fetched resource
/// - Throws: Network, decoding, or cache errors
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
func fetch<ResourceType>() async throws -> ResourceType
where ResourceType: Decodable & EndpointAccessible
}

/// The base Queryable protocol to find collections for content types, assets, and entries.
Expand All @@ -567,4 +590,11 @@ public protocol Queryable {
/// - completion: A handler which will be called on completion of the operation.
func find<ResourceType>(_ completion: @escaping ResultsHandler<ContentstackResponse<ResourceType>>)
where ResourceType: Decodable & EndpointAccessible

/// Async version of find that returns the ContentstackResponse directly
/// - Returns: The ContentstackResponse with the requested resources
/// - Throws: Network, decoding, or cache errors
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
func find<ResourceType>() async throws -> ContentstackResponse<ResourceType>
where ResourceType: Decodable & EndpointAccessible
}
83 changes: 83 additions & 0 deletions Sources/Stack.swift
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,29 @@ public class Stack: CachePolicyAccessible {
performDataTask(dataTask!, request: request, cachePolicy: cachePolicy, then: completion)
}

// MARK: - Async/Await Support

/// Async version of fetchUrl that returns the result directly
/// - Parameters:
/// - url: The URL to fetch
/// - headers: HTTP headers to include in the request
/// - cachePolicy: The cache policy to use
/// - Returns: A tuple containing the data and response type
/// - Throws: Network or cache errors
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
private func fetchUrl(_ url: URL, headers: [String: String], cachePolicy: CachePolicy) async throws -> (Data, ResponseType) {
return try await withCheckedThrowingContinuation { continuation in
fetchUrl(url, headers: headers, cachePolicy: cachePolicy) { result, responseType in
switch result {
case .success(let data):
continuation.resume(returning: (data, responseType))
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
}

internal func fetch<ResourceType>(endpoint: Endpoint,
cachePolicy: CachePolicy,
parameters: Parameters = [:],
Expand All @@ -284,6 +307,25 @@ public class Stack: CachePolicyAccessible {
}
})
}

/// Async version of fetch that returns the decoded resource directly
/// - Parameters:
/// - endpoint: The API endpoint to fetch from
/// - cachePolicy: The cache policy to use
/// - parameters: Query parameters
/// - headers: HTTP headers
/// - Returns: The decoded resource
/// - Throws: Network, decoding, or cache errors
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
internal func fetch<ResourceType>(endpoint: Endpoint,
cachePolicy: CachePolicy,
parameters: Parameters = [:],
headers: [String: String] = [:]) async throws -> ResourceType
where ResourceType: Decodable {
let url = self.url(endpoint: endpoint, parameters: parameters)
let (data, _) = try await fetchUrl(url, headers: headers, cachePolicy: cachePolicy)
return try self.jsonDecoder.decode(ResourceType.self, from: data)
}

private func performDataTask(_ dataTask: URLSessionDataTask,
request: URLRequest,
Expand Down Expand Up @@ -397,4 +439,45 @@ extension Stack {
}
}
}

/// Async version of sync that returns the SyncStack directly
/// - Parameters:
/// - syncStack: The relevant `SyncStack` to perform the subsequent sync on.
/// Defaults to a new empty instance of `SyncStack`.
/// - syncTypes: `SyncableTypes` that can be sync.
/// - Returns: The SyncStack with synced data
/// - Throws: Network or decoding errors
///
/// Example usage:
///```
/// let stack = Contentstack.stack(apiKey: apiKey,
/// deliveryToken: deliveryToken,
/// environment: environment)
///
/// do {
/// let syncStack = try await stack.sync()
/// let items = syncStack.items
/// } catch {
/// print(error)
/// }
///```
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public func sync(_ syncStack: SyncStack = SyncStack(),
syncTypes: [SyncStack.SyncableTypes] = [.all]) async throws -> SyncStack {
var parameter = syncStack.parameter
if syncStack.isInitialSync {
for syncType in syncTypes {
parameter = parameter + syncType.parameters
}
}
let url = self.url(endpoint: SyncStack.endpoint, parameters: parameter)
let (data, _) = try await fetchUrl(url, headers: [:], cachePolicy: .networkOnly)
let result = try self.jsonDecoder.decode(SyncStack.self, from: data)

if result.hasMorePages {
return try await sync(result, syncTypes: syncTypes)
}

return result
}
}
22 changes: 22 additions & 0 deletions Sources/Taxonomy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,26 @@ extension Taxonomy: ResourceQueryable {
}
})
}

// MARK: - Async/Await Implementation for fetch

/// Async version of fetch that returns the Taxonomy directly
/// - Returns: The fetched Taxonomy
/// - Throws: Network, decoding, or cache errors
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public func fetch<ResourceType>() async throws -> ResourceType
where ResourceType: EndpointAccessible & Decodable {
let response: ContentstackResponse<ResourceType> = try await self.stack.fetch(
endpoint: ResourceType.endpoint,
cachePolicy: self.cachePolicy,
parameters: parameters,
headers: headers
)

if let resource = response.items.first {
return resource
} else {
throw SDKError.invalidURL(string: "Something went wrong.")
}
}
}
Loading