Skip to content

Commit 7a5e0f0

Browse files
Merge pull request #28 from contentstack/development
staging PRs
2 parents 80eff61 + c79c19d commit 7a5e0f0

9 files changed

Lines changed: 526 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
## [1.4.0] - 2026-03-30
6+
7+
### Enhancement
8+
9+
- **Variant utility** — read variant aliases and emit `data-csvariants` JSON (single or many entries).
10+
- **`VariantUtilityError`** on invalid input.
11+
12+
## [1.3.4] - 2025-01-17
13+
14+
### Changed
15+
16+
- Deployment targets updated.
17+
- General enhancements and latest platform support.
18+
19+
## [1.3.3] - 2024-05-17
20+
21+
### Added
22+
23+
- Privacy manifest file.
24+
25+
### Changed
26+
27+
- Updated tests.
28+
29+
## [1.3.2] - 2024-03-28
30+
31+
### Added
32+
33+
- Support for the **fragment** tag in JSON RTE.
34+
35+
## [1.3.1] - 2023-11-20
36+
37+
### Fixed
38+
39+
- Image linking issue.
40+
41+
## [1.3.0] - 2023-05-26
42+
43+
### Added
44+
45+
- Nested asset support.
46+
- Break tag support.
47+
48+
## [1.2.1] - 2022-09-09
49+
50+
### Fixed
51+
52+
- Swift Package warning (exclude warning from package removed).
53+
54+
## [1.2.0] - 2021-08-10
55+
56+
### Added
57+
58+
- JSON RTE to HTML support for the **GQL API**.
59+
60+
## [1.1.2] - 2021-07-16
61+
62+
### Added
63+
64+
- JSON RTE content to HTML parsing support.
65+
66+
## [1.1.1] - 2021-04-09
67+
68+
### Changed
69+
70+
- Deployment target updates.
71+
72+
### Removed
73+
74+
- XC Framework (issue resolved).
75+
76+
## [1.1.0] - 2021-04-06
77+
78+
### Fixed
79+
80+
- Swift Package duplicate naming for ContentstackUtils.
81+
82+
## [1.0.0] - 2021-04-06
83+
84+
### Added
85+
86+
- Embedded items feature support.
87+
- `includeEmbeddedItems` in Entry and Query modules.
88+
- Utils SDK support in the SDK.

ContentstackUtils.xcodeproj/project.pbxproj

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@
6565
0FFF2F2A2668FC54003E9DBF /* NodeType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FFF2F292668FC54003E9DBF /* NodeType.swift */; };
6666
0FFF2F382668FE85003E9DBF /* Node.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FFF2F372668FE85003E9DBF /* Node.swift */; };
6767
64F522132BF5F3F300AE6E0F /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 64F522122BF5F3F300AE6E0F /* PrivacyInfo.xcprivacy */; };
68+
6749AC902F714E26007282C5 /* variantsEntries.json in Resources */ = {isa = PBXBuildFile; fileRef = 6749AC8F2F714E26007282C5 /* variantsEntries.json */; };
69+
6749AC922F714E2F007282C5 /* variantsSingleEntry.json in Resources */ = {isa = PBXBuildFile; fileRef = 6749AC912F714E2F007282C5 /* variantsSingleEntry.json */; };
70+
6749AC942F714E36007282C5 /* VariantUtilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6749AC932F714E36007282C5 /* VariantUtilityTests.swift */; };
6871
OBJ_22 /* ContentstackUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_9 /* ContentstackUtils.swift */; };
6972
OBJ_29 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_6 /* Package.swift */; };
7073
OBJ_40 /* ContentstackUtilsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_12 /* ContentstackUtilsTests.swift */; };
@@ -138,6 +141,10 @@
138141
0FFF2F292668FC54003E9DBF /* NodeType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeType.swift; sourceTree = "<group>"; };
139142
0FFF2F372668FE85003E9DBF /* Node.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Node.swift; sourceTree = "<group>"; };
140143
64F522122BF5F3F300AE6E0F /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
144+
6749AC8F2F714E26007282C5 /* variantsEntries.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = variantsEntries.json; sourceTree = "<group>"; };
145+
6749AC912F714E2F007282C5 /* variantsSingleEntry.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = variantsSingleEntry.json; sourceTree = "<group>"; };
146+
6749AC932F714E36007282C5 /* VariantUtilityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VariantUtilityTests.swift; sourceTree = "<group>"; };
147+
6749AC952F715507007282C5 /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = "<group>"; };
141148
"ContentstackUtils::ContentstackUtils::Product" /* ContentstackUtils.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ContentstackUtils.framework; sourceTree = BUILT_PRODUCTS_DIR; };
142149
"ContentstackUtils::ContentstackUtilsTests::Product" /* ContentstackUtilsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; path = ContentstackUtilsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
143150
OBJ_12 /* ContentstackUtilsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentstackUtilsTests.swift; sourceTree = "<group>"; };
@@ -321,6 +328,9 @@
321328
0F07E62E25244DB5003E0BD1 /* StringExtensionTests.swift */,
322329
0F5E484B2525DB600038C16B /* TestClient.swift */,
323330
0FEC0B3A254FEC60008D4E66 /* MetadataTests.swift */,
331+
6749AC8F2F714E26007282C5 /* variantsEntries.json */,
332+
6749AC912F714E2F007282C5 /* variantsSingleEntry.json */,
333+
6749AC932F714E36007282C5 /* VariantUtilityTests.swift */,
324334
0F7142C325514A6F00C18A61 /* ContentstackUtilsArrayTest.swift */,
325335
0F7142C52551684600C18A61 /* ContentstackUtilsCustomRendertest.swift */,
326336
0F579540266A50D40082815C /* MarkTypeTest.swift */,
@@ -347,6 +357,7 @@
347357
64F522122BF5F3F300AE6E0F /* PrivacyInfo.xcprivacy */,
348358
0FAA3EBD26A1C65B00173FA9 /* ContentstackUtils.podspec */,
349359
OBJ_6 /* Package.swift */,
360+
6749AC952F715507007282C5 /* CHANGELOG.md */,
350361
0F7142C725517A4900C18A61 /* README.md */,
351362
0FA3D58E252228E300E58179 /* Scripts */,
352363
OBJ_7 /* Sources */,
@@ -471,7 +482,9 @@
471482
isa = PBXResourcesBuildPhase;
472483
buildActionMask = 2147483647;
473484
files = (
485+
6749AC922F714E2F007282C5 /* variantsSingleEntry.json in Resources */,
474486
64F522132BF5F3F300AE6E0F /* PrivacyInfo.xcprivacy in Resources */,
487+
6749AC902F714E26007282C5 /* variantsEntries.json in Resources */,
475488
0F5E484E2525DDD70038C16B /* EntryEmbedded.json in Resources */,
476489
);
477490
runOnlyForDeploymentPostprocessing = 0;
@@ -556,6 +569,7 @@
556569
0FFD88D7266DDD1900BA5919 /* ContentstackUtilsJsonToHtmlTest.swift in Sources */,
557570
0F07E62F25244DB5003E0BD1 /* StringExtensionTests.swift in Sources */,
558571
0F7142C425514A6F00C18A61 /* ContentstackUtilsArrayTest.swift in Sources */,
572+
6749AC942F714E36007282C5 /* VariantUtilityTests.swift in Sources */,
559573
0FFD88EE266DE1A600BA5919 /* NodeParser.swift in Sources */,
560574
0FFD88F7266DE1FB00BA5919 /* JsonNodes.swift in Sources */,
561575
0F00785B26A5A0EB00FC4925 /* GQLJsonToHtml.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) 2016-2025 Contentstack
3+
Copyright (c) 2016-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/ContentstackUtils/ContentstackUtils.swift

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import Foundation
22

33
public struct ContentstackUtils {
44

5+
public enum VariantUtilityError: Error {
6+
case invalidArgument(String)
7+
}
8+
59
public struct GQL {
610
public static func jsonToHtml(rte document: [String: Any?], _ option: Option = Option()) throws -> Any {
711
do {
@@ -72,6 +76,79 @@ public struct ContentstackUtils {
7276
public static func jsonToHtml(node document: Node, _ option: Option = Option()) -> String {
7377
return nodeChildrenToHtml(children: document.children, option)
7478
}
79+
80+
public static func getVariantAliases(entry: [String: Any], contentTypeUid: String) throws -> [String: Any] {
81+
82+
try validateContentTypeUid(contentTypeUid)
83+
84+
guard let uid = entry["uid"] as? String, !uid.isEmpty else{
85+
throw VariantUtilityError.invalidArgument("entry uid is required.")
86+
}
87+
88+
guard let publish = entry["publish_details"] as? [String: Any],
89+
let variants = publish["variants"] as? [String: Any] else{
90+
return [
91+
"entry_uid": uid,
92+
"contenttype_uid": contentTypeUid,
93+
"variants": [] as [String]
94+
]
95+
}
96+
var aliases : [String] = []
97+
for(_, value) in variants {
98+
if let obj = value as? [String: Any],
99+
let alias = obj["alias"] as? String {
100+
aliases.append(alias)
101+
}
102+
}
103+
104+
return [
105+
"entry_uid": uid,
106+
"contenttype_uid": contentTypeUid,
107+
"variants": aliases
108+
]
109+
}
110+
111+
public static func getVariantAliases(entries: [[String: Any]], contentTypeUid: String) throws -> [[String: Any]] {
112+
try validateContentTypeUid(contentTypeUid)
113+
return try entries.map { entry in
114+
try getVariantAliases(entry: entry, contentTypeUid: contentTypeUid)
115+
}
116+
}
117+
118+
public static func getDataCsvariantsAttribute(entry: [String: Any]?, contentTypeUid: String) throws -> [String: Any]{
119+
guard let e = entry else {
120+
return ["data-csvariants": "[]"]
121+
}
122+
123+
let payload = try getVariantAliases(entry: e, contentTypeUid: contentTypeUid)
124+
let s = try jsonString(for: [payload])
125+
return ["data-csvariants": s]
126+
127+
}
128+
129+
public static func getDataCsvariantsAttribute(entries: [[String: Any]], contentTypeUid: String) throws -> [String: Any]{
130+
try validateContentTypeUid(contentTypeUid)
131+
let payloads = try getVariantAliases(entries: entries, contentTypeUid: contentTypeUid)
132+
let s = try jsonString(for: payloads)
133+
return ["data-csvariants": s]
134+
}
135+
136+
137+
private static func validateContentTypeUid(_ contentTypeUid: String) throws {
138+
if contentTypeUid.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
139+
throw VariantUtilityError.invalidArgument("contentTypeUid must not be empty")
140+
}
141+
}
142+
143+
private static func jsonString(for array: [[String: Any]]) throws -> String{
144+
let data = try JSONSerialization.data(withJSONObject: array, options: [])
145+
guard let json = String(data: data, encoding: .utf8) else {
146+
throw VariantUtilityError.invalidArgument("Failed to encode JSON string")
147+
}
148+
return json
149+
}
150+
151+
75152

76153
static private func nodeChildrenToHtml(children nodes:[Node], _ option: Option) -> String {
77154
nodes.map{ (node) -> String in
@@ -156,3 +233,4 @@ public struct ContentstackUtils {
156233
return nil
157234
}
158235
}
236+

Tests/ContentstackUtilsTests/TestClient.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,25 @@ class TestDecodable {
2626
static func getMultilevelEmbed() -> ContentBlock? {
2727
return decode("EntryEmbedded")
2828
}
29+
30+
/// Loads a JSON object from a file (e.g. `variantsEntries` → `variantsEntries.json`).
31+
/// Tries the test bundle first (Xcode), then the directory containing this source file (SwiftPM `swift test`).
32+
static func loadJSONObject(named fileName: String) throws -> [String: Any] {
33+
let url: URL
34+
if let path = Bundle(for: TestDecodable.self).path(forResource: fileName, ofType: "json") {
35+
url = URL(fileURLWithPath: path)
36+
} else {
37+
let testsDir = URL(fileURLWithPath: #filePath).deletingLastPathComponent()
38+
let fallback = testsDir.appendingPathComponent("\(fileName).json")
39+
guard FileManager.default.fileExists(atPath: fallback.path) else {
40+
throw NSError(domain: "TestDecodable", code: 1, userInfo: [NSLocalizedDescriptionKey: "Missing json: \(fileName).json"])
41+
}
42+
url = fallback
43+
}
44+
let data = try Data(contentsOf: url, options: .mappedIfSafe)
45+
guard let root = try JSONSerialization.jsonObject(with: data) as? [String: Any] else {
46+
throw NSError(domain: "TestDecodable", code: 2, userInfo: [NSLocalizedDescriptionKey: "Root is not a JSON object"])
47+
}
48+
return root
49+
}
2950
}

0 commit comments

Comments
 (0)