Skip to content

Commit 82749ca

Browse files
Merge pull request #86 from contentstack/staging
DX | 09-06-2025 | Release
2 parents fbc80b0 + 1b563c9 commit 82749ca

16 files changed

+1584
-22
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,7 @@ docs
7878
fastlane/
7979
Gemfile
8080
#config file
81-
Tests/config.json
81+
Tests/config.json
82+
83+
snyk_output.json
84+
talisman_output.json

.talismanrc

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@ fileignoreconfig:
2424
checksum: 10ca4b0b986ae6166f69d7f985ca5b06238ac70dac3dd378fbf3dbdfd966afff
2525
- filename: Contentstack.xcodeproj/xcshareddata/xcschemes/Contentstack iOS Tests.xcscheme
2626
checksum: c439f6d268ae2ea0af023daeffdff2af5928d0610f90fa14c9e6e6ce7e4b3fad
27-
version: ""
27+
- filename: ContentstackSwift.xcodeproj/project.pbxproj
28+
checksum: dfabf06aeff3576c9347e52b3c494635477d81c7d121d8f1435d79f28829f4d1
29+
- filename: ContentstackSwift.xcodeproj/project.pbxproj
30+
checksum: 8937f832171f26061a209adcd808683f7bdfb739e7fc49aecd853d5055466251
31+
version: "1.0"
2832

2933

3034

ContentstackSwift.xcodeproj/project.pbxproj

Lines changed: 54 additions & 0 deletions
Large diffs are not rendered by default.

Sources/CSURLSessionDelegate.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//
2+
// CSURLSessionDelegate.swift
3+
// ContentstackSwift
4+
//
5+
// Created by Reeshika Hosmani on 19/05/25.
6+
//
7+
8+
import Foundation
9+
10+
/// Protocol for SSL pinning customization in Contentstack SDK
11+
@objc public protocol CSURLSessionDelegate: NSObjectProtocol, URLSessionDelegate {
12+
13+
/// Tells the delegate that the session received an authentication challenge.
14+
/// - Parameters:
15+
/// - session: The session that received the authentication challenge.
16+
/// - challenge: An object that contains the request for authentication.
17+
/// - completionHandler: A handler that your delegate method must call.
18+
@objc func urlSession(_ session: URLSession,
19+
didReceive challenge: URLAuthenticationChallenge,
20+
completionHandler: @escaping @Sendable (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
21+
22+
/// Tells the delegate that the session task received an authentication challenge.
23+
/// - Parameters:
24+
/// - session: The session containing the task that received the authentication challenge.
25+
/// - task: The task that received the authentication challenge.
26+
/// - challenge: An object that contains the request for authentication.
27+
/// - completionHandler: A handler that your delegate method must call.
28+
@objc optional func urlSession(_ session: URLSession,
29+
task: URLSessionTask,
30+
didReceive challenge: URLAuthenticationChallenge,
31+
completionHandler: @escaping @Sendable (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
32+
}

Sources/ContentstackConfig.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ public struct ContentstackConfig {
2626
/// The configuration for the URLSession.
2727
/// Note that HTTP headers will be overwritten internally by the SDK so that requests can be authorized correctly.
2828
public var sessionConfiguration: URLSessionConfiguration = .default
29+
30+
/// Delegate for handling SSL pinning and URL session customization
31+
public var urlSessionDelegate: CSURLSessionDelegate?
2932

3033
/// Computed version of the user agent, including OS name and version
3134
internal func userAgentString() -> String {

Sources/ContentstackResponse.swift

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ private protocol HomogeneousResponse: ResponseParams {
1919
}
2020

2121
internal enum ResponseCodingKeys: String, CodingKey {
22-
case entries, entry, assets, asset, skip, limit, errors, count
22+
case entries, entry, assets, asset, skip, limit, errors, count, globalFields, globalField
2323
case contentTypes = "content_types", contentType = "content_type"
2424
}
2525

@@ -90,6 +90,23 @@ where ItemType: EndpointAccessible & Decodable {
9090
}
9191
self.items = taxonomies
9292
}
93+
case .globalfields:
94+
// Decode entire response as [String: AnyDecodable] using singleValueContainer
95+
let fullResponseContainer = try decoder.singleValueContainer()
96+
let fullResponse = try fullResponseContainer.decode([String: AnyDecodable].self)
97+
98+
if let globalFieldsArray = fullResponse["global_fields"]?.value as? [[String: Any]] {
99+
for item in globalFieldsArray {
100+
let data = try JSONSerialization.data(withJSONObject: item, options: [])
101+
let model = try JSONDecoder().decode(ItemType.self, from: data)
102+
self.items.append(model)
103+
}
104+
} else if let globalField = fullResponse["global_field"]?.value as? [String: Any] {
105+
let data = try JSONSerialization.data(withJSONObject: globalField, options: [])
106+
let model = try JSONDecoder().decode(ItemType.self, from: data)
107+
self.items = [model]
108+
}
109+
93110
default:
94111
print("sync")
95112
}

Sources/EndPoint.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ public enum Endpoint: String {
2222
case sync = "stacks/sync"
2323

2424
case taxnomies = "taxonomies"
25+
26+
case globalfields = "global_fields"
2527
/// The path component string for the current endpoint.
2628
public var pathComponent: String {
2729
return rawValue

Sources/GlobalField.swift

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
//
2+
// GlobalField.swift
3+
// ContentstackSwift
4+
//
5+
// Created by Reeshika Hosmani on 26/05/25.
6+
//
7+
8+
import Foundation
9+
10+
public class GlobalField: CachePolicyAccessible{
11+
12+
public var cachePolicy: CachePolicy = .networkOnly
13+
/// URI Parameters
14+
internal var parameters: Parameters = [:]
15+
internal var headers: [String: String] = [:]
16+
internal var stack: Stack
17+
/// Unique ID of the global_field of which you wish to retrieve the details.
18+
internal var uid: String?
19+
/// Query Parameters
20+
public var queryParameter: [String: Any] = [:]
21+
22+
internal required init(stack: Stack) {
23+
self.stack = stack
24+
}
25+
internal required init(_ uid: String?, stack: Stack) {
26+
self.uid = uid
27+
self.stack = stack
28+
}
29+
30+
public func includeBranch() -> GlobalField {
31+
self.parameters[QueryParameter.includeBranch] = true
32+
return self
33+
}
34+
35+
public func includeGlobalFieldSchema() -> GlobalField {
36+
self.parameters[QueryParameter.includeGlobalFieldSchema] = true
37+
return self
38+
}
39+
40+
}
41+
42+
extension GlobalField: ResourceQueryable {
43+
/// This call fetches the latest version of a specific `Global Field` of a particular stack.
44+
/// - Parameters:
45+
/// - completion: A handler which will be called on completion of the operation.
46+
///
47+
/// Example usage:
48+
/// ```
49+
/// let stack = Contentstack.stack(apiKey: apiKey,
50+
/// deliveryToken: deliveryToken,
51+
/// environment: environment)
52+
///
53+
/// stack.globalField
54+
/// .fetch { (result: Result<GlobalFieldModel, Error>, response: ResponseType) in
55+
/// switch result {
56+
/// case .success(let model):
57+
/// //Model retrive from API
58+
/// case .failure(let error):
59+
/// //Error Message
60+
/// }
61+
/// }
62+
/// ```
63+
public func fetch<ResourceType>(_ completion: @escaping (Result<ResourceType, Error>, ResponseType) -> Void)
64+
where ResourceType: EndpointAccessible & Decodable {
65+
guard let uid = self.uid else { fatalError("Please provide Global Field uid") }
66+
self.stack.fetch(endpoint: ResourceType.endpoint,
67+
cachePolicy: self.cachePolicy,
68+
parameters: parameters + [QueryParameter.uid: uid],
69+
headers: headers,
70+
then: { (result: Result<ContentstackResponse<ResourceType>, Error>, response: ResponseType) in
71+
switch result {
72+
case .success(let contentStackResponse):
73+
if let resource = contentStackResponse.items.first {
74+
completion(.success(resource), response)
75+
} else {
76+
completion(.failure(SDKError.invalidUID(string: uid)), response)
77+
}
78+
case .failure(let error):
79+
completion(.failure(error), response)
80+
}
81+
})
82+
}
83+
}
84+
85+
extension GlobalField : Queryable{
86+
public func find<ResourceType>(_ completion: @escaping ResultsHandler<ContentstackResponse<ResourceType>>) where ResourceType :Decodable & EndpointAccessible {
87+
if self.queryParameter.count > 0,
88+
let query = self.queryParameter.jsonString {
89+
self.parameters[QueryParameter.query] = query
90+
}
91+
self.stack.fetch(endpoint: ResourceType.endpoint,
92+
cachePolicy: self.cachePolicy, parameters: parameters, headers: headers, then: completion)
93+
}
94+
95+
}

Sources/GlobalFieldModel.swift

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
//
2+
// GlobalFieldModel.swift
3+
// ContentstackSwift
4+
//
5+
// Created by Reeshika Hosmani on 27/05/25.
6+
//
7+
8+
import Foundation
9+
10+
// Helper for decoding [String: Any] and other nested unknown types
11+
public struct AnyDecodable: Decodable {
12+
public let value: Any
13+
14+
public init(from decoder: Decoder) throws {
15+
let container = try decoder.singleValueContainer()
16+
17+
if let int = try? container.decode(Int.self) {
18+
value = int
19+
} else if let double = try? container.decode(Double.self) {
20+
value = double
21+
} else if let bool = try? container.decode(Bool.self) {
22+
value = bool
23+
} else if let string = try? container.decode(String.self) {
24+
value = string
25+
} else if let array = try? container.decode([AnyDecodable].self) {
26+
value = array.map { $0.value }
27+
} else if let dict = try? container.decode([String: AnyDecodable].self) {
28+
value = dict.mapValues { $0.value }
29+
} else {
30+
value = NSNull()
31+
}
32+
}
33+
}
34+
35+
public protocol GlobalFieldDecodable: GlobalFields, FieldKeysQueryable, EndpointAccessible, Decodable {}
36+
37+
public final class GlobalFieldModel : GlobalFieldDecodable {
38+
public var title: String
39+
40+
public var uid: String
41+
42+
public var createdAt: Date?
43+
44+
public var updatedAt: Date?
45+
46+
public var schema: [[String: Any]] = []
47+
48+
public var description: String?
49+
50+
public var maintainRevisions: Bool?
51+
52+
public var inbuiltClass: Bool?
53+
54+
public var version: Int?
55+
56+
public var branch: String?
57+
58+
public var lastActivity: [String: Any] = [:]
59+
60+
public enum FieldKeys: String, CodingKey {
61+
case title, uid, description
62+
case createdAt = "created_at"
63+
case updatedAt = "updated_at"
64+
case maintainRevisions = "maintain_revisions"
65+
case version = "_version"
66+
case branch = "_branch"
67+
case lastActivity = "last_activity"
68+
case inbuiltClass = "inbuilt_class"
69+
case schema
70+
}
71+
72+
public enum QueryableCodingKey: String, CodingKey {
73+
case uid, title, description
74+
case createdAt = "created_at"
75+
case updatedAt = "updated_at"
76+
}
77+
78+
// public required init(from decoder: Decoder) throws {
79+
// let container = try decoder.container(keyedBy: FieldKeys.self)
80+
// uid = try container.decode(String.self, forKey: .uid)
81+
// title = try container.decode(String.self, forKey: .title)
82+
// description = try? container.decodeIfPresent(String.self, forKey: .description)
83+
// createdAt = try? container.decode(Date.self, forKey: .createdAt)
84+
// updatedAt = try? container.decode(Date.self, forKey: .updatedAt)
85+
// maintainRevisions = try? container.decode(Bool.self, forKey: .maintainRevisions)
86+
// version = try? container.decode(Int.self, forKey: .version)
87+
// branch = try? container.decode(String.self, forKey: .branch)
88+
// inbuiltClass = try? container.decode(Bool.self, forKey: .inbuiltClass)
89+
// let containerFields = try? decoder.container(keyedBy: JSONCodingKeys.self)
90+
// let globalFieldSchema = try containerFields?.decode(Dictionary<String, Any>.self)
91+
// if let schema = globalFieldSchema?["schema"] as? [[String: Any]] {
92+
// self.schema = schema
93+
// }
94+
//// if let decodedSchema = try? container.decodeIfPresent([ [String: AnyDecodable] ].self, forKey: .schema) {
95+
//// self.schema = decodedSchema.map { $0.mapValues { $0.value } }
96+
//// }
97+
// if let decodedLastActivity = try? container.decodeIfPresent([String: AnyDecodable].self, forKey: .lastActivity) {
98+
// lastActivity = decodedLastActivity.mapValues { $0.value }
99+
// }
100+
// }
101+
102+
103+
public required init(from decoder: Decoder) throws {
104+
let container = try decoder.container(keyedBy: FieldKeys.self)
105+
106+
uid = try container.decode(String.self, forKey: .uid)
107+
title = try container.decode(String.self, forKey: .title)
108+
description = try? container.decodeIfPresent(String.self, forKey: .description)
109+
createdAt = try? container.decode(Date.self, forKey: .createdAt)
110+
updatedAt = try? container.decode(Date.self, forKey: .updatedAt)
111+
maintainRevisions = try? container.decode(Bool.self, forKey: .maintainRevisions)
112+
version = try? container.decode(Int.self, forKey: .version)
113+
branch = try? container.decode(String.self, forKey: .branch)
114+
inbuiltClass = try? container.decode(Bool.self, forKey: .inbuiltClass)
115+
116+
// ✅ Decode schema directly and safely
117+
if let decodedSchema = try? container.decodeIfPresent([[String: AnyDecodable]].self, forKey: .schema) {
118+
self.schema = decodedSchema.map { $0.mapValues { $0.value } }
119+
}
120+
121+
if let decodedLastActivity = try? container.decodeIfPresent([String: AnyDecodable].self, forKey: .lastActivity) {
122+
lastActivity = decodedLastActivity.mapValues { $0.value }
123+
}
124+
}
125+
}
126+
127+
extension GlobalFieldModel : EndpointAccessible {
128+
public static var endpoint: Endpoint {
129+
return .globalfields
130+
}
131+
}

Sources/QueryParameter.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ internal enum QueryParameter {
6868
internal static let includeFallback = "include_fallback"
6969

7070
internal static let includeEmbeddedItems = "include_embedded_items"
71+
72+
internal static let includeBranch = "include_branch"
73+
74+
internal static let includeGlobalFieldSchema = "include_global_field_schema"
7175
}
7276

7377
extension Query {

0 commit comments

Comments
 (0)