Repository: TootSDK/TootSDK
Pull request #223 (2023-12-12 16:56:40)

fix: handle Friendica returning [] instead of nil for contact account
Added a decoder for Instance, handles the case of Friendica (example cambrian.social) returning [] instead of nil for no contact account, resulting in this error

```
[TootSDK bug] There was an error decoding incoming data:
Type 'Dictionary<String, Any>' mismatch:Expected to decode Dictionary<String, Any> but found an array instead.
codingPath:[CodingKeys(stringValue: "contactAccount", intValue: nil)].
```



Patch:
diff --git a/Package.swift b/Package.swift
index d791b09..1098f66 100644
--- a/Package.swift
+++ b/Package.swift
@@ -52,6 +52,7 @@ let package = Package(
                 .copy("Resources/familiar_followers_nofollowers.json"),
                 .copy("Resources/featured_tag.json"),
                 .copy("Resources/featured_tag_unused.json"),
+                .copy("Resources/instance_friendica_nocontact.json"),
                 .copy("Resources/list.json"),
                 .copy("Resources/post no emojis.json"),
                 .copy("Resources/post with emojis and attachments.json"),
diff --git a/Sources/TootSDK/Models/Instance.swift b/Sources/TootSDK/Models/Instance.swift
index 920d0f3..9f03edf 100644
--- a/Sources/TootSDK/Models/Instance.swift
+++ b/Sources/TootSDK/Models/Instance.swift
@@ -155,6 +155,27 @@ public struct Instance: Codable, Hashable {
 
         // swiftlint:enable nesting
     }
+
+    public init(from decoder: Decoder) throws {
+        let container = try decoder.container(keyedBy: CodingKeys.self)
+        self.uri = try? container.decodeIfPresent(String.self, forKey: .uri)
+        self.title = try? container.decodeIfPresent(String.self, forKey: .title)
+        self.description = try? container.decodeIfPresent(String.self, forKey: .description)
+        self.shortDescription = try? container.decodeIfPresent(String.self, forKey: .shortDescription)
+        self.email = try? container.decodeIfPresent(String.self, forKey: .email)
+        self.version = try container.decode(String.self, forKey: .version)
+        self.languages = try? container.decodeIfPresent([String].self, forKey: .languages)
+        self.registrations = try? container.decodeIfPresent(Bool.self, forKey: .registrations)
+        self.approvalRequired = try? container.decodeIfPresent(Bool.self, forKey: .approvalRequired)
+        self.invitesEnabled = try? container.decodeIfPresent(Bool.self, forKey: .invitesEnabled)
+        self.urls = try? container.decodeIfPresent(Instance.InstanceURLs.self, forKey: .urls)
+        self.stats = try container.decode(Stats.self, forKey: .stats)
+        self.thumbnail = try? container.decodeIfPresent(String.self, forKey: .thumbnail)
+        self.configuration = try? container.decodeIfPresent(Configuration.self, forKey: .configuration)
+        // also handles some friendica instances returning []
+        self.contactAccount = try? container.decodeIfPresent(Account.self, forKey: .contactAccount)
+        self.rules = try? container.decodeIfPresent([InstanceRule].self, forKey: .rules)
+    }
 }
 
 public extension Instance {


Test patch:
diff --git a/Tests/TootSDKTests/InstanceTests.swift b/Tests/TootSDKTests/InstanceTests.swift
new file mode 100644
index 0000000..dca8fef
--- /dev/null
+++ b/Tests/TootSDKTests/InstanceTests.swift
@@ -0,0 +1,24 @@
+import XCTest
+
+@testable import TootSDK
+
+final class InstanceTests: XCTestCase {
+  func testFriendicaNoContact() throws {
+    // arrange
+    let json = localContent("instance_friendica_nocontact")
+    let decoder = TootDecoder()
+
+    // act
+    let result = try decoder.decode(Instance.self, from: json)
+
+    // assert
+    XCTAssertNotNil(result)
+    XCTAssertNil(result.contactAccount)
+    XCTAssertEqual(result.languages, ["fr"])
+    XCTAssertEqual(result.version, "2.8.0 (compatible; Friendica 2023.05)")
+    XCTAssertEqual(result.uri, "social.thisworksonmycomputer.local")
+    XCTAssertEqual(result.title, "Social")
+    XCTAssertEqual(result.invitesEnabled, false)
+    XCTAssertEqual(result.registrations, true)
+  }
+}
diff --git a/Tests/TootSDKTests/Resources/instance_friendica_nocontact.json b/Tests/TootSDKTests/Resources/instance_friendica_nocontact.json
new file mode 100644
index 0000000..02f1708
--- /dev/null
+++ b/Tests/TootSDKTests/Resources/instance_friendica_nocontact.json
@@ -0,0 +1,22 @@
+{
+  "description": "",
+  "languages": ["fr"],
+  "stats": {
+    "domain_count": 0,
+    "user_count": 0,
+    "status_count": 0
+  },
+  "version": "2.8.0 (compatible; Friendica 2023.05)",
+  "uri": "social.thisworksonmycomputer.local",
+  "short_description": "",
+  "invites_enabled": false,
+  "rules": [],
+  "title": "Social",
+  "urls": null,
+  "approval_required": false,
+  "max_toot_chars": 200000,
+  "thumbnail": "https://social.thisworksonmycomputer.local/images/friendica-banner.jpg",
+  "contact_account": [],
+  "email": "",
+  "registrations": true
+}