Skip to content

Commit 538e7dd

Browse files
Merge pull request #120 from contentstack/fix/DX-3679-improve-unit-tests
Refactor error handling
2 parents 81209c2 + f3cf0e2 commit 538e7dd

19 files changed

Lines changed: 190 additions & 142 deletions

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ DerivedData/
2121
/Packages
2222
#/*.xcodeproj
2323
xcuserdata/
24-
*/config.json
24+
2525
## Other
2626
*.moved-aside
2727
*.xccheckout

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.3.2
4+
5+
### Date: 12-Jan-2026
6+
7+
- Improved error messages
8+
39
## v2.3.1
410

511
### Date: 03-Nov-2025

ContentstackSwift.xcodeproj/project.pbxproj

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,10 @@
269269
672F769A2E55ADBE00C248D6 /* AsyncAwaitAPITest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 672F76982E55ADBE00C248D6 /* AsyncAwaitAPITest.swift */; };
270270
672F769B2E55ADBE00C248D6 /* AsyncAwaitAPITest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 672F76982E55ADBE00C248D6 /* AsyncAwaitAPITest.swift */; };
271271
6750778E2D3E256A0076A066 /* DVR in Frameworks */ = {isa = PBXBuildFile; productRef = 6750778D2D3E256A0076A066 /* DVR */; };
272+
678CBD072EF10CBA00B72390 /* ContentstackMessages.swift in Sources */ = {isa = PBXBuildFile; fileRef = 678CBD062EF10CBA00B72390 /* ContentstackMessages.swift */; };
273+
678CBD082EF10CBA00B72390 /* ContentstackMessages.swift in Sources */ = {isa = PBXBuildFile; fileRef = 678CBD062EF10CBA00B72390 /* ContentstackMessages.swift */; };
274+
678CBD092EF10CBA00B72390 /* ContentstackMessages.swift in Sources */ = {isa = PBXBuildFile; fileRef = 678CBD062EF10CBA00B72390 /* ContentstackMessages.swift */; };
275+
678CBD0A2EF10CBA00B72390 /* ContentstackMessages.swift in Sources */ = {isa = PBXBuildFile; fileRef = 678CBD062EF10CBA00B72390 /* ContentstackMessages.swift */; };
272276
67AA38EA2EB9D44800C0E2C0 /* CSDefinitionsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67AA38E92EB9D44800C0E2C0 /* CSDefinitionsTest.swift */; };
273277
67AA38EB2EB9D44800C0E2C0 /* CSDefinitionsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67AA38E92EB9D44800C0E2C0 /* CSDefinitionsTest.swift */; };
274278
67AA38EC2EB9D44800C0E2C0 /* CSDefinitionsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67AA38E92EB9D44800C0E2C0 /* CSDefinitionsTest.swift */; };
@@ -456,6 +460,7 @@
456460
47B4DC612C232A8200370CFC /* TaxonomyTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaxonomyTest.swift; sourceTree = "<group>"; };
457461
47C6EFC12C0B5B9400F0D5CF /* Taxonomy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Taxonomy.swift; sourceTree = "<group>"; };
458462
672F76982E55ADBE00C248D6 /* AsyncAwaitAPITest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncAwaitAPITest.swift; sourceTree = "<group>"; };
463+
678CBD062EF10CBA00B72390 /* ContentstackMessages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentstackMessages.swift; sourceTree = "<group>"; };
459464
67AA38E92EB9D44800C0E2C0 /* CSDefinitionsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CSDefinitionsTest.swift; sourceTree = "<group>"; };
460465
67AA38ED2EB9D46200C0E2C0 /* QueryParameterTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryParameterTest.swift; sourceTree = "<group>"; };
461466
67AA38F12EB9D46800C0E2C0 /* QueryOperationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryOperationTest.swift; sourceTree = "<group>"; };
@@ -807,6 +812,7 @@
807812
0F4FBCB12420D2CA007B8CAE /* Query */,
808813
0FFA5D60241F5561003B3AF5 /* Utilities */,
809814
0FFA5D5B241F5134003B3AF5 /* Stack.swift */,
815+
678CBD062EF10CBA00B72390 /* ContentstackMessages.swift */,
810816
67EE21DE2DDB3FFE005AC119 /* CSURLSessionDelegate.swift */,
811817
0FFA5D83241F808F003B3AF5 /* Asset.swift */,
812818
0FFA5D97241F8EB2003B3AF5 /* Entry.swift */,
@@ -1172,6 +1178,7 @@
11721178
0FB4CAC924332C5200A385B1 /* ImageOperation.swift in Sources */,
11731179
0F4A245C24224D3100159C24 /* ContentstackResponse.swift in Sources */,
11741180
0FFA5D93241F8214003B3AF5 /* EndPoint.swift in Sources */,
1181+
678CBD0A2EF10CBA00B72390 /* ContentstackMessages.swift in Sources */,
11751182
0FFA5DB4241F99F9003B3AF5 /* FatalError.swift in Sources */,
11761183
0F024674243217D800F72181 /* ImageTransformError.swift in Sources */,
11771184
0FFA5D74241F6BFA003B3AF5 /* Date.swift in Sources */,
@@ -1267,6 +1274,7 @@
12671274
0FB4CACA24332C5200A385B1 /* ImageOperation.swift in Sources */,
12681275
0F4A245D24224D3100159C24 /* ContentstackResponse.swift in Sources */,
12691276
0FFA5D94241F8214003B3AF5 /* EndPoint.swift in Sources */,
1277+
678CBD072EF10CBA00B72390 /* ContentstackMessages.swift in Sources */,
12701278
0FFA5DB6241F99F9003B3AF5 /* FatalError.swift in Sources */,
12711279
0F024675243217D800F72181 /* ImageTransformError.swift in Sources */,
12721280
0FFA5D75241F6BFA003B3AF5 /* Date.swift in Sources */,
@@ -1362,6 +1370,7 @@
13621370
0FB4CACB24332C5200A385B1 /* ImageOperation.swift in Sources */,
13631371
0F4A245E24224D3100159C24 /* ContentstackResponse.swift in Sources */,
13641372
0FFA5D95241F8214003B3AF5 /* EndPoint.swift in Sources */,
1373+
678CBD092EF10CBA00B72390 /* ContentstackMessages.swift in Sources */,
13651374
0FFA5DB8241F99F9003B3AF5 /* FatalError.swift in Sources */,
13661375
0F024676243217D800F72181 /* ImageTransformError.swift in Sources */,
13671376
0FFA5D76241F6BFA003B3AF5 /* Date.swift in Sources */,
@@ -1457,6 +1466,7 @@
14571466
0FB4CACC24332C5200A385B1 /* ImageOperation.swift in Sources */,
14581467
0F4A245F24224D3100159C24 /* ContentstackResponse.swift in Sources */,
14591468
0FFA5D96241F8214003B3AF5 /* EndPoint.swift in Sources */,
1469+
678CBD082EF10CBA00B72390 /* ContentstackMessages.swift in Sources */,
14601470
0FFA5DBA241F99F9003B3AF5 /* FatalError.swift in Sources */,
14611471
0F024677243217D800F72181 /* ImageTransformError.swift in Sources */,
14621472
0FFA5D77241F6BFA003B3AF5 /* Date.swift in Sources */,

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2012-2025 Contentstack
3+
Copyright (c) 2012-2026 Contentstack
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

Sources/Asset.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ extension Asset: ResourceQueryable {
228228
/// ```
229229
public func fetch<ResourceType>(_ completion: @escaping (Result<ResourceType, Error>, ResponseType) -> Void)
230230
where ResourceType: EndpointAccessible, ResourceType: Decodable {
231-
guard let uid = self.uid else { fatalError("Please provide Asset uid") }
231+
guard let uid = self.uid else { fatalError(ContentstackMessages.assetUIDRequired) }
232232
self.stack.fetch(endpoint: ResourceType.endpoint,
233233
cachePolicy: self.cachePolicy,
234234
parameters: parameters + [QueryParameter.uid: uid],
@@ -255,7 +255,7 @@ extension Asset: ResourceQueryable {
255255
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
256256
public func fetch<ResourceType>() async throws -> ResourceType
257257
where ResourceType: EndpointAccessible & Decodable {
258-
guard let uid = self.uid else { fatalError("Please provide Asset uid") }
258+
guard let uid = self.uid else { fatalError(ContentstackMessages.assetUIDRequired) }
259259
let response: ContentstackResponse<ResourceType> = try await self.stack.fetch(
260260
endpoint: ResourceType.endpoint,
261261
cachePolicy: self.cachePolicy,

Sources/ContentType.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public class ContentType: CachePolicyAccessible {
4545
///```
4646
public func entry(uid: String? = nil) -> Entry {
4747
if self.uid == nil {
48-
fatalError("Please provide ContentType uid")
48+
fatalError(ContentstackMessages.contentTypeUIDRequired)
4949
}
5050
return Entry(uid, contentType: self)
5151
}
@@ -160,7 +160,7 @@ extension ContentType: ResourceQueryable {
160160
/// ```
161161
public func fetch<ResourceType>(_ completion: @escaping (Result<ResourceType, Error>, ResponseType) -> Void)
162162
where ResourceType: EndpointAccessible, ResourceType: Decodable {
163-
guard let uid = self.uid else { fatalError("Please provide ContentType uid") }
163+
guard let uid = self.uid else { fatalError(ContentstackMessages.contentTypeUIDRequired) }
164164
self.stack.fetch(endpoint: ResourceType.endpoint,
165165
cachePolicy: self.cachePolicy,
166166
parameters: parameters + [QueryParameter.uid: uid],
@@ -187,7 +187,7 @@ extension ContentType: ResourceQueryable {
187187
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
188188
public func fetch<ResourceType>() async throws -> ResourceType
189189
where ResourceType: EndpointAccessible & Decodable {
190-
guard let uid = self.uid else { fatalError("Please provide ContentType uid") }
190+
guard let uid = self.uid else { fatalError(ContentstackMessages.contentTypeUIDRequired) }
191191
let response: ContentstackResponse<ResourceType> = try await self.stack.fetch(
192192
endpoint: ResourceType.endpoint,
193193
cachePolicy: self.cachePolicy,

Sources/ContentstackMessages.swift

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
//
2+
// ContentstackMessages.swift
3+
// Contentstack
4+
//
5+
// Created by Contentstack on 16/12/25.
6+
//
7+
8+
import Foundation
9+
10+
/// Centralized location for all user-facing messages in the Contentstack SDK.
11+
/// This includes error messages, validation messages, and informational messages.
12+
internal enum ContentstackMessages {
13+
14+
// MARK: - UID Required Messages
15+
16+
static let assetUIDRequired = "Asset UID is required. Provide a valid Asset UID and try again."
17+
static let contentTypeUIDRequired = "Content Type UID is required. Provide a valid Content Type UID and try again."
18+
static let entryUIDRequired = "Entry UID is required. Provide a valid Entry UID and try again."
19+
static let globalFieldUIDRequired = "Global Field UID is required. Provide a valid Global Field UID and try again."
20+
21+
// MARK: - URL Validation Messages
22+
23+
static let invalidURLString = "The URL string is not valid. Provide a valid URL and try again."
24+
25+
// MARK: - Sync Messages
26+
27+
static let syncTokenConflict = "Sync Token and Pagination Token cannot be used together. Provide only one token and try again."
28+
29+
// MARK: - Image Transform Messages
30+
31+
static let qualityParameterRange = """
32+
The value for Quality parameters can be entered in \
33+
any whole number (taken as a percentage) between 1 and 100.
34+
"""
35+
36+
static let dprParameterRange = """
37+
The value for dpr parameter could be a whole number (between 0 and 10000) \
38+
or any decimal number (between 0.0 and 9999.9999...).
39+
"""
40+
41+
static let blurParameterRange = """
42+
The value for blur parameter could be a whole decimal number (between 1 and 1000).
43+
"""
44+
45+
static let saturationParameterRange = """
46+
The value for saturation parameter could be a whole decimal number (between -100 and 100).
47+
"""
48+
49+
static let contrastParameterRange = """
50+
The value for contrast parameter could be a whole decimal number (between -100 and 100).
51+
"""
52+
53+
static let brightnessParameterRange = """
54+
The value for brightness parameter could be a whole decimal number (between -100 and 100).
55+
"""
56+
57+
static let sharpenParameterRange = """
58+
The value for `amount` parameter could be a whole decimal number (between 0 and 10). \
59+
The value for `radius` parameter could be a whole decimal number (between 1 and 1000). \
60+
The value for `threshold` parameter could be a whole decimal number (between 0 and 255).
61+
"""
62+
63+
static let cropAspectRatioRequired = """
64+
Along with the crop parameter aspect-ration, \
65+
you also need to specify either the width or height parameter or both \
66+
in the API request to return an output image with the correct dimensions.
67+
"""
68+
69+
static let canvasAspectRatioRequired = """
70+
Along with the canvas parameter aspect-ration, \
71+
you also need to specify either the width or height parameter or both \
72+
in the API request to return an output image with the correct dimensions.
73+
"""
74+
75+
static let invalidHexColor = """
76+
Invalid Hexadecimal value, \
77+
it should be 3-digit or 6-digit hexadecimal value.
78+
"""
79+
80+
static let invalidRGBColor = """
81+
Invalid Red or Blue or Green or alpha value, \
82+
the value ranging anywhere between 0 and 255 for each.
83+
"""
84+
85+
static let invalidRGBAColor = """
86+
Invalid Red or Blue or Green or alpha value, \
87+
the value ranging anywhere between 0 and 255 for each \
88+
and the alpha value with 0.0 being fully transparent \
89+
and 1.0 being completely opaque.
90+
"""
91+
92+
static let duplicateImageTransform = """
93+
Cannot specify two instances of ImageTransform of the same case.\
94+
i.e. `[.format(.png), .format(.jpg)]` is invalid.
95+
"""
96+
97+
static let invalidImageOptions = """
98+
The SDK was unable to generate a valid URL for the given ImageOptions. \
99+
Please contact the maintainer on Github with a copy of the query
100+
"""
101+
102+
// MARK: - Internal/Debug Messages
103+
104+
static let unsupportedEndpointType = "Unsupported endpoint type encountered during response decoding"
105+
}
106+

Sources/ContentstackResponse.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ where ItemType: EndpointAccessible & Decodable {
108108
}
109109

110110
default:
111-
print("sync")
111+
ContentstackLogger.log(.error, message: ContentstackMessages.unsupportedEndpointType)
112112
}
113113
}
114114
}

Sources/Entry.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public class Entry: EntryQueryable, CachePolicyAccessible {
5454
/// }
5555
public func query() -> Query {
5656
if self.contentType.uid == nil {
57-
fatalError("Please provide ContentType uid")
57+
fatalError(ContentstackMessages.contentTypeUIDRequired)
5858
}
5959
let query = Query(contentType: self.contentType)
6060
if let uid = self.uid {
@@ -85,7 +85,7 @@ public class Entry: EntryQueryable, CachePolicyAccessible {
8585
public func query<EntryType>(_ entry: EntryType.Type) -> QueryOn<EntryType>
8686
where EntryType: EntryDecodable & FieldKeysQueryable {
8787
if self.contentType.uid == nil {
88-
fatalError("Please provide ContentType uid")
88+
fatalError(ContentstackMessages.contentTypeUIDRequired)
8989
}
9090
let query = QueryOn<EntryType>(contentType: self.contentType)
9191
if let uid = self.uid {
@@ -156,7 +156,7 @@ extension Entry: ResourceQueryable {
156156
/// ```
157157
public func fetch<ResourceType>(_ completion: @escaping (Result<ResourceType, Error>, ResponseType) -> Void)
158158
where ResourceType: EndpointAccessible, ResourceType: Decodable {
159-
guard let uid = self.uid else { fatalError("Please provide Entry uid") }
159+
guard let uid = self.uid else { fatalError(ContentstackMessages.entryUIDRequired) }
160160
self.stack.fetch(endpoint: ResourceType.endpoint,
161161
cachePolicy: self.cachePolicy,
162162
parameters: parameters + [QueryParameter.uid: uid,
@@ -184,7 +184,7 @@ extension Entry: ResourceQueryable {
184184
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
185185
public func fetch<ResourceType>() async throws -> ResourceType
186186
where ResourceType: EndpointAccessible & Decodable {
187-
guard let uid = self.uid else { fatalError("Please provide Entry uid") }
187+
guard let uid = self.uid else { fatalError(ContentstackMessages.entryUIDRequired) }
188188
let response: ContentstackResponse<ResourceType> = try await self.stack.fetch(
189189
endpoint: ResourceType.endpoint,
190190
cachePolicy: self.cachePolicy,

Sources/GlobalField.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ extension GlobalField: ResourceQueryable {
6262
/// ```
6363
public func fetch<ResourceType>(_ completion: @escaping (Result<ResourceType, Error>, ResponseType) -> Void)
6464
where ResourceType: EndpointAccessible & Decodable {
65-
guard let uid = self.uid else { fatalError("Please provide Global Field uid") }
65+
guard let uid = self.uid else { fatalError(ContentstackMessages.globalFieldUIDRequired) }
6666
self.stack.fetch(endpoint: ResourceType.endpoint,
6767
cachePolicy: self.cachePolicy,
6868
parameters: parameters + [QueryParameter.uid: uid],
@@ -89,7 +89,7 @@ extension GlobalField: ResourceQueryable {
8989
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
9090
public func fetch<ResourceType>() async throws -> ResourceType
9191
where ResourceType: EndpointAccessible & Decodable {
92-
guard let uid = self.uid else { fatalError("Please provide Global Field uid") }
92+
guard let uid = self.uid else { fatalError(ContentstackMessages.globalFieldUIDRequired) }
9393
let response: ContentstackResponse<ResourceType> = try await self.stack.fetch(
9494
endpoint: ResourceType.endpoint,
9595
cachePolicy: self.cachePolicy,

0 commit comments

Comments
 (0)