Skip to content

Commit 70584cc

Browse files
author
Anand Nimje
committed
Add iOS16 demo app for CowCodable
0 parents  commit 70584cc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+4419
-0
lines changed

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.DS_Store
2+
/.build
3+
/Packages
4+
xcuserdata/
5+
DerivedData/
6+
.swiftpm/configuration/registries.json
7+
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8+
.netrc

DemoApp/CowCodableDemoApp/CowCodableDemoApp.xcodeproj/project.pbxproj

Lines changed: 432 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Scheme
3+
LastUpgradeVersion = "2620"
4+
version = "1.3">
5+
<BuildAction
6+
parallelizeBuildables = "YES"
7+
buildImplicitDependencies = "YES">
8+
<BuildActionEntries>
9+
<BuildActionEntry
10+
buildForTesting = "YES"
11+
buildForRunning = "YES"
12+
buildForProfiling = "YES"
13+
buildForArchiving = "YES"
14+
buildForAnalyzing = "YES">
15+
<BuildableReference
16+
BuildableIdentifier = "primary"
17+
BlueprintIdentifier = "A10000000000000000000005"
18+
BuildableName = "CowCodableDemoApp.app"
19+
BlueprintName = "CowCodableDemoApp"
20+
ReferencedContainer = "container:CowCodableDemoApp.xcodeproj">
21+
</BuildableReference>
22+
</BuildActionEntry>
23+
</BuildActionEntries>
24+
</BuildAction>
25+
<TestAction
26+
buildConfiguration = "Debug"
27+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
28+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
29+
shouldUseLaunchSchemeArgsEnv = "YES">
30+
<Testables>
31+
</Testables>
32+
</TestAction>
33+
<LaunchAction
34+
buildConfiguration = "Debug"
35+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
36+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
37+
launchStyle = "0"
38+
useCustomWorkingDirectory = "NO"
39+
ignoresPersistentStateOnLaunch = "NO"
40+
debugDocumentVersioning = "YES"
41+
debugServiceExtension = "internal"
42+
allowLocationSimulation = "YES">
43+
<BuildableProductRunnable
44+
runnableDebuggingMode = "0">
45+
<BuildableReference
46+
BuildableIdentifier = "primary"
47+
BlueprintIdentifier = "A10000000000000000000005"
48+
BuildableName = "CowCodableDemoApp.app"
49+
BlueprintName = "CowCodableDemoApp"
50+
ReferencedContainer = "container:CowCodableDemoApp.xcodeproj">
51+
</BuildableReference>
52+
</BuildableProductRunnable>
53+
</LaunchAction>
54+
<ProfileAction
55+
buildConfiguration = "Release"
56+
shouldUseLaunchSchemeArgsEnv = "YES"
57+
savedToolIdentifier = ""
58+
useCustomWorkingDirectory = "NO"
59+
debugDocumentVersioning = "YES">
60+
<BuildableProductRunnable
61+
runnableDebuggingMode = "0">
62+
<BuildableReference
63+
BuildableIdentifier = "primary"
64+
BlueprintIdentifier = "A10000000000000000000005"
65+
BuildableName = "CowCodableDemoApp.app"
66+
BlueprintName = "CowCodableDemoApp"
67+
ReferencedContainer = "container:CowCodableDemoApp.xcodeproj">
68+
</BuildableReference>
69+
</BuildableProductRunnable>
70+
</ProfileAction>
71+
<AnalyzeAction
72+
buildConfiguration = "Debug">
73+
</AnalyzeAction>
74+
<ArchiveAction
75+
buildConfiguration = "Release"
76+
revealArchiveInOrganizer = "YES">
77+
</ArchiveAction>
78+
</Scheme>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/// File: CowCodableDemoApp.swift
2+
///
3+
/// Demo application source for the CowCodable SDK showcase.
4+
5+
import SwiftUI
6+
7+
/// CowCodable demo application entry point.
8+
///
9+
/// Purpose: bootstraps the production-style iOS SwiftUI demo client.
10+
/// Design philosophy: keep startup minimal and route all behavior through feature modules.
11+
/// Thread safety: SwiftUI scene lifecycle is managed by the framework on the main actor.
12+
/// Deterministic behavior: always launches to a single root flow with no side effects.
13+
/// Example usage: run the `CowCodableDemoApp` scheme on an iOS 16+ simulator.
14+
@main
15+
internal struct CowCodableDemoApp: App {
16+
17+
// MARK: - Body
18+
19+
internal var body: some Scene {
20+
WindowGroup {
21+
ContentView()
22+
}
23+
}
24+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/// File: DemoModels.swift
2+
///
3+
/// Demo application source for the CowCodable SDK showcase.
4+
5+
import CowCodable
6+
import Foundation
7+
8+
/// Codable model fixtures for decoding presets.
9+
///
10+
/// Purpose: define strongly typed sample decode surfaces for each corruption scenario.
11+
/// Design philosophy: keep model definitions simple and focused on resilience behavior.
12+
/// Thread safety: value types with `@CowResilient` defaults are safe for concurrent decode use.
13+
/// Deterministic behavior: fixed defaults ensure stable fallback outcomes.
14+
/// Example usage: selected in `DecodeViewModel.decodePreset` based on current preset.
15+
16+
// MARK: - Primitive
17+
18+
internal struct PrimitiveCorruptionModel: Codable {
19+
@CowResilient internal var name: String = ""
20+
@CowResilient internal var age: Int = 0
21+
@CowResilient internal var score: Double = 0
22+
@CowResilient internal var isActive: Bool = false
23+
@CowResilient internal var initial: Character = "\0"
24+
@CowResilient internal var ratio: Float = 0
25+
}
26+
27+
// MARK: - Array
28+
29+
internal struct ArrayCorruptionModel: Codable {
30+
@CowResilient internal var numbers: [Int] = []
31+
@CowResilient internal var nested: [[Int]] = []
32+
}
33+
34+
// MARK: - Dictionary
35+
36+
internal struct DictionaryCorruptionModel: Codable {
37+
@CowResilient internal var intMap: [String: Int] = [:]
38+
@CowResilient internal var boolMap: [String: Bool] = [:]
39+
@CowResilient internal var nestedMap: [String: [String: Int]] = [:]
40+
}
41+
42+
// MARK: - Nested
43+
44+
internal struct NestedComplexModel: Codable {
45+
@CowResilient internal var payload: [[String: [Int]]] = []
46+
}
47+
48+
// MARK: - Null Edge Case
49+
50+
internal struct NullEdgeCaseModel: Codable {
51+
@CowResilient internal var id: Int = 0
52+
@CowResilient internal var title: String = ""
53+
}
54+
55+
// MARK: - Overflow
56+
57+
internal struct OverflowCaseModel: Codable {
58+
@CowResilient internal var count: Int = 0
59+
@CowResilient internal var weight: Float = 0
60+
}
61+
62+
// MARK: - Strict vs Permissive
63+
64+
internal struct StrictPermissiveCaseModel: Codable {
65+
@CowResilient internal var age: Int = 0
66+
@CowResilient internal var initial: Character = "\0"
67+
@CowResilient internal var ratio: Float = 0
68+
@CowResilient internal var values: [Int] = []
69+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/// File: JSONSamples.swift
2+
///
3+
/// Demo application source for the CowCodable SDK showcase.
4+
5+
import Foundation
6+
7+
/// Preset JSON payload catalog for deterministic demo scenarios.
8+
///
9+
/// Purpose: provide reproducible input fixtures for each showcased rescue behavior.
10+
/// Design philosophy: keep examples explicit, readable, and directly editable by users.
11+
/// Thread safety: immutable enum values and strings are value-type safe.
12+
/// Deterministic behavior: each case has fixed JSON and fixed explanation text.
13+
/// Example usage: bind `JSONPreset.allCases` to a picker and read `sampleJSON`.
14+
internal enum JSONPreset: String, CaseIterable, Identifiable {
15+
case primitiveCorruption = "Primitive Corruption"
16+
case arrayCorruption = "Array Corruption"
17+
case dictionaryCorruption = "Dictionary Corruption"
18+
case nestedComplex = "Nested Complex"
19+
case nullEdgeCase = "Null Edge Case"
20+
case overflowCase = "Overflow Case"
21+
case strictVsPermissiveCase = "Strict vs Permissive Case"
22+
23+
// MARK: - Identity
24+
25+
internal var id: String {
26+
rawValue
27+
}
28+
29+
// MARK: - Sample Payloads
30+
31+
internal var sampleJSON: String {
32+
switch self {
33+
case .primitiveCorruption:
34+
return #"""
35+
{
36+
"name": 12345,
37+
"age": " 32 ",
38+
"score": "1e3",
39+
"isActive": "yes",
40+
"initial": "Z",
41+
"ratio": "NaN"
42+
}
43+
"""#
44+
case .arrayCorruption:
45+
return #"""
46+
{
47+
"numbers": [1, "2", 3.0, 3.7, "bad"],
48+
"nested": [[1, "2"], ["3", "x"], 9]
49+
}
50+
"""#
51+
case .dictionaryCorruption:
52+
return #"""
53+
{
54+
"intMap": {"a": 1, "b": "2", "c": "bad"},
55+
"boolMap": {"on": "true", "off": 0, "unknown": "maybe"},
56+
"nestedMap": {
57+
"group1": {"x": "1", "y": "bad"},
58+
"group2": {"z": 3}
59+
}
60+
}
61+
"""#
62+
case .nestedComplex:
63+
return #"""
64+
{
65+
"payload": [
66+
{"good": [1, "2", "bad"]},
67+
"fully-invalid-object",
68+
{"other": [3, 4, "5"]}
69+
]
70+
}
71+
"""#
72+
case .nullEdgeCase:
73+
return #"""
74+
{
75+
"id": null,
76+
"title": 123
77+
}
78+
"""#
79+
case .overflowCase:
80+
return #"""
81+
{
82+
"count": "999999999999999999999999",
83+
"weight": "1e1000"
84+
}
85+
"""#
86+
case .strictVsPermissiveCase:
87+
return #"""
88+
{
89+
"age": "1e3",
90+
"initial": 65,
91+
"ratio": "Infinity",
92+
"values": "7"
93+
}
94+
"""#
95+
}
96+
}
97+
98+
// MARK: - Explanations
99+
100+
internal var explanation: String {
101+
switch self {
102+
case .primitiveCorruption:
103+
return "Primitive drift across numbers, booleans, and character values."
104+
case .arrayCorruption:
105+
return "Mixed arrays demonstrate rescued values and skipped invalid elements."
106+
case .dictionaryCorruption:
107+
return "Dictionary entries illustrate key/value rescue and deterministic dropping."
108+
case .nestedComplex:
109+
return "Nested objects show multilevel rescue while preserving valid structure."
110+
case .nullEdgeCase:
111+
return "Switch null strategy to compare fail, useDefault, and skip behavior."
112+
case .overflowCase:
113+
return "Large numeric inputs show deterministic overflow handling and failures."
114+
case .strictVsPermissiveCase:
115+
return "Toggle strict and permissive to compare conversion boundaries on identical JSON."
116+
}
117+
}
118+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/// File: OutputFormatter.swift
2+
///
3+
/// Demo application source for the CowCodable SDK showcase.
4+
5+
import CowCodable
6+
import Foundation
7+
8+
/// Formatting helpers for decode output and user-facing errors.
9+
///
10+
/// Purpose: transform raw decode results into readable JSON and friendly diagnostics.
11+
/// Design philosophy: centralize text formatting so views remain presentation-only.
12+
/// Thread safety: stateless static helpers with no shared mutable storage.
13+
/// Deterministic behavior: same input always produces the same rendered output strings.
14+
/// Example usage: used by `DecodeViewModel` after each decode attempt.
15+
internal enum OutputFormatter {
16+
17+
// MARK: - Summary
18+
19+
internal struct Summary {
20+
internal let rescuedCount: Int
21+
internal let skippedCount: Int
22+
internal let failedCount: Int
23+
}
24+
25+
// MARK: - Public Helpers
26+
27+
internal static func prettyJSON(from data: Data) -> String {
28+
guard
29+
let object = try? JSONSerialization.jsonObject(with: data),
30+
let prettyData = try? JSONSerialization.data(
31+
withJSONObject: object,
32+
options: [.prettyPrinted, .sortedKeys]
33+
),
34+
let text = String(data: prettyData, encoding: .utf8)
35+
else {
36+
return String(decoding: data, as: UTF8.self)
37+
}
38+
39+
return text
40+
}
41+
42+
internal static func summary(from entries: [CowLogEntry]) -> Summary {
43+
Summary(
44+
rescuedCount: entries.filter { $0.kind == .rescued }.count,
45+
skippedCount: entries.filter { $0.kind == .skipped }.count,
46+
failedCount: entries.filter { $0.kind == .failed }.count
47+
)
48+
}
49+
50+
internal static func friendlyError(_ error: Error) -> String {
51+
if let decodingError = error as? DecodingError {
52+
return describe(decodingError)
53+
}
54+
55+
if let cowError = error as? CowDecodingError {
56+
return cowError.errorDescription ?? "CowCodable could not decode the payload."
57+
}
58+
59+
return "Decoding failed. Check JSON validity and selected strategy."
60+
}
61+
62+
// MARK: - Internal Helpers
63+
64+
private static func describe(_ decodingError: DecodingError) -> String {
65+
switch decodingError {
66+
case .typeMismatch(_, let context):
67+
return "Type mismatch at '\(pathString(context.codingPath))'. \(context.debugDescription)"
68+
case .valueNotFound(_, let context):
69+
return "Required value missing at '\(pathString(context.codingPath))'. \(context.debugDescription)"
70+
case .keyNotFound(let key, let context):
71+
return "Missing key '\(key.stringValue)' at '\(pathString(context.codingPath))'. \(context.debugDescription)"
72+
case .dataCorrupted(let context):
73+
return "Invalid JSON structure at '\(pathString(context.codingPath))'. \(context.debugDescription)"
74+
@unknown default:
75+
return "Decoding failed due to an unsupported error condition."
76+
}
77+
}
78+
79+
private static func pathString(_ path: [CodingKey]) -> String {
80+
if path.isEmpty {
81+
return "<root>"
82+
}
83+
84+
return path.map(\.stringValue).joined(separator: ".")
85+
}
86+
}

0 commit comments

Comments
 (0)