Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Sources/XCDocs/Models/DocumentationEntry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,13 @@ public struct DocumentationEntry: Codable, Hashable, Identifiable, Sendable {
self.title = title
self.content = content
}

public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(String.self, forKey: .id)
self.framework = try container.decodeIfPresent(String.self, forKey: .framework)
self.kind = try? container.decodeIfPresent(DocumentationKind.self, forKey: .kind)
self.title = try container.decodeIfPresent(String.self, forKey: .title)
self.content = try container.decodeIfPresent(String.self, forKey: .content)
}
}
2 changes: 1 addition & 1 deletion Tests/XCDocsTests/BridgeErrorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ struct BridgeErrorTests {
func formatsDescriptionForAllErrorCodes() {
let codes: [BridgeErrorCode] = [
.frameworkUnavailable, .classUnavailable, .selectorUnavailable, .assetNotFound, .invalidResponse,
.invalidEmbedding, .operationFailed, .searchFailed, .timeout,
.invalidEmbedding, .operationFailed, .invalidScore, .searchFailed, .timeout,
]

for code in codes {
Expand Down
45 changes: 45 additions & 0 deletions Tests/XCDocsTests/ModelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,24 @@ struct ModelTests {
guard #available(macOS 26, *) else { return }
assertStableIdentities()
}

@Test
func documentationEntryDecodesUnknownKindAsNil() throws {
guard #available(macOS 26, *) else { return }
try assertUnknownKindDecodesAsNil()
}

@Test
func searchResultWithNaNScoreThrowsOnJSONEncode() throws {
guard #available(macOS 26, *) else { return }
assertNaNScoreThrowsOnEncode()
}

@Test
func searchResultWithInfinityScoreThrowsOnJSONEncode() throws {
guard #available(macOS 26, *) else { return }
assertInfinityScoreThrowsOnEncode()
}
}

@available(macOS 26, *)
Expand Down Expand Up @@ -147,6 +165,33 @@ private func assertStableIdentities() {
#expect(result.id == entry.id)
}

@available(macOS 26, *)
private func assertUnknownKindDecodesAsNil() throws {
let json = """
{"id":"/doc/X","framework":null,"kind":"unknownKind","title":null,"content":null}
"""
let entry = try JSONDecoder().decode(DocumentationEntry.self, from: Data(json.utf8))
#expect(entry.kind == nil)
}

@available(macOS 26, *)
private func assertNaNScoreThrowsOnEncode() {
let result = SearchResult(
score: .nan,
entry: DocumentationEntry(id: "/doc/X", framework: nil, kind: nil, title: nil, content: nil)
)
#expect(throws: EncodingError.self) { try JSONEncoder().encode(result) }
}

@available(macOS 26, *)
private func assertInfinityScoreThrowsOnEncode() {
let result = SearchResult(
score: .infinity,
entry: DocumentationEntry(id: "/doc/X", framework: nil, kind: nil, title: nil, content: nil)
)
#expect(throws: EncodingError.self) { try JSONEncoder().encode(result) }
}

private func assertRoundTrip<T: Codable & Equatable>(_ value: T) throws {
let data = try JSONEncoder().encode(value)
let decoded = try JSONDecoder().decode(T.self, from: data)
Expand Down
Loading