Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

keep the original URL when decoding Download/ExternalLocationReference #621

Merged
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ public struct ExternalLocationReference: RenderReference, URLReference {

public private(set) var type: RenderReferenceType = .externalLocation

public var identifier: RenderReferenceIdentifier
public let identifier: RenderReferenceIdentifier

let url: String

enum CodingKeys: String, CodingKey {
case type
Expand All @@ -34,21 +36,21 @@ public struct ExternalLocationReference: RenderReference, URLReference {

public init(identifier: RenderReferenceIdentifier) {
self.identifier = identifier
self.url = identifier.identifier
}

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)

self.identifier = try container.decode(RenderReferenceIdentifier.self, forKey: .identifier)
self.url = try container.decode(String.self, forKey: .url)
self.type = try container.decode(RenderReferenceType.self, forKey: .type)
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(type.rawValue, forKey: .type)
try container.encode(identifier, forKey: .identifier)

// Enter the given URL verbatim into the Render JSON
try container.encode(identifier.identifier, forKey: .url)
try container.encode(url, forKey: .url)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,15 @@ public struct DownloadReference: RenderReference, URLReference, Equatable {

/// The location of the downloadable resource.
public var url: URL


/// Indicates whether the ``url`` property was loaded from the regular initializer or from the
/// `Decodable` initializer.
///
/// This is used during encoding to determine whether to filter ``url`` through the
ethan-kusters marked this conversation as resolved.
Show resolved Hide resolved
/// `renderURL(for:)` method. In case the URL was loaded from JSON, we don't want to modify it
/// further after a round-trip.
private var urlWasDecoded = false

/// The SHA512 hash value for the resource.
public var checksum: String?

Expand Down Expand Up @@ -60,15 +68,41 @@ public struct DownloadReference: RenderReference, URLReference, Equatable {
public init(identifier: RenderReferenceIdentifier, renderURL url: URL, sha512Checksum: String) {
self.init(identifier: identifier, renderURL: url, checksum: sha512Checksum)
}


enum CodingKeys: CodingKey {
case type
case identifier
case url
case checksum
}

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.type = try container.decode(RenderReferenceType.self, forKey: .type)
self.identifier = try container.decode(RenderReferenceIdentifier.self, forKey: .identifier)
self.url = try container.decode(URL.self, forKey: .url)
self.urlWasDecoded = true
QuietMisdreavus marked this conversation as resolved.
Show resolved Hide resolved
self.checksum = try container.decodeIfPresent(String.self, forKey: .checksum)
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(type.rawValue, forKey: .type)
try container.encode(identifier, forKey: .identifier)
try container.encodeIfPresent(checksum, forKey: .checksum)

// Render URL
try container.encode(renderURL(for: url), forKey: .url)
if !urlWasDecoded {
try container.encode(renderURL(for: url), forKey: .url)
} else {
try container.encode(url, forKey: .url)
}
}

static public func ==(lhs: DownloadReference, rhs: DownloadReference) -> Bool {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Off-topic but I wonder if it's possible to make an @EquatableIgnored macro like: https://developer.apple.com/documentation/observation/observationignored...

lhs.identifier == rhs.identifier
&& lhs.url == rhs.url
&& lhs.checksum == rhs.checksum
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
{
"schemaVersion" : {
"major" : 1,
"minor" : 0,
"patch" : 0
},
"seeAlsoSections" : [ ],
"metadata" : {
"platforms" : [
{
"name" : "macOS",
"introducedAt" : "10.15"
}
],
"modules" : [
{ "name" : "MyKit" }
],
"title" : "Wifi Access",
"roleHeading" : "Plist Key"
},
"abstract" : [
{
"type" : "text",
"text" : "A "
},
{
"type" : "codeVoice",
"code" : "WiFi access"
},
{
"type" : "text",
"text" : " abstract description."
}
],
"sections" : [
],
"identifier" : {
"url" : "doc:\/\/org.swift.docc.example\/plist\/wifiaccess",
"interfaceLanguage": "swift"
},
"hierarchy" : {
"paths" : [["doc:\/\/org.swift.docc.example\/plist\/wifiaccess"]]
},
"topicSections" : [
],
"kind" : "symbol",
"references" : {
"doc:\/\/org.swift.docc.example\/downloads\/sample.zip": {
"identifier": "ExternalLocation.zip",
"url": "https://example.com/ExternalLocation.zip",
"type": "externalLocation"
},
"doc:\/\/org.swift.docc.example\/plist\/wifiaccess": {
"abstract" : [
{
"text" : "WiFi access",
"type" : "text"
}
],
"identifier" : "doc:\/\/org.swift.docc.example\/plist\/wifiaccess",
"kind" : "symbol",
"title" : "WiFi Access",
"type" : "topic",
"url" : "\/documentation\/mykit"
}
},
"sampleCodeDownload": {
"action": {
"identifier": "doc:\/\/org.swift.docc.example\/downloads\/sample.zip",
"isActive": true,
"overridingTitle": "Download",
"type": "reference"
}
},
"primaryContentSections" : [
{
"kind" : "content",
"content" : [
{
"anchor" : "discussion",
"level" : 2,
"type" : "heading",
"text" : "Discussion"
},
{
"type" : "paragraph",
"inlineContent" : [
{
"type" : "text",
"text" : "Use "
},
{
"type" : "codeVoice",
"code" : "Wifi access"
},
{
"type" : "text",
"text" : " to secure wifi access for your app."
}
]
}
]
}
],
"variants": [{
"paths" : ["\/plist\/wifiaccess"],
"traits" : []
}]
}

51 changes: 51 additions & 0 deletions Tests/SwiftDocCTests/Rendering/SampleDownloadTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -240,4 +240,55 @@ class SampleDownloadTests: XCTestCase {
let reference = try XCTUnwrap(renderNode.references[identifier.identifier])
XCTAssert(reference is ExternalLocationReference)
}

/// Ensure that a DownloadReference where the URL is different from the reference identifier
/// can still round-trip through an ExternalLocationReference with the URL and reference identifier intact.
func testRoundTripWithDifferentUrl() throws {
ethan-kusters marked this conversation as resolved.
Show resolved Hide resolved
let baseReference = DownloadReference(identifier: .init("DownloadReference.zip"), renderURL: .init(string: "https://example.com/DownloadReference.zip")!, checksum: nil)

let encoder = JSONEncoder()
let decoder = JSONDecoder()

let encodedReference = try encoder.encode(baseReference)

let interimReference = try decoder.decode(ExternalLocationReference.self, from: encodedReference)
let interimEncodedReference = try encoder.encode(interimReference)

let roundTripReference = try decoder.decode(DownloadReference.self, from: interimEncodedReference)

XCTAssertEqual(baseReference, roundTripReference)
}

/// Ensure that an ExternalLocationReference loaded from JSON continues to encode the same
/// information after being decoded and re-encoded.
func testRoundTripExternalLocationFromFixture() throws {
let downloadSymbolURL = Bundle.module.url(
forResource: "external-location-custom-url", withExtension: "json",
subdirectory: "Rendering Fixtures")!

let originalData = try Data(contentsOf: downloadSymbolURL)
let originalRenderNode = try RenderNode.decode(fromJSON: originalData)

let encodedRenderNode = try JSONEncoder().encode(originalRenderNode)
let symbol = try RenderNode.decode(fromJSON: encodedRenderNode)

//
// Sample Download Details
//

guard let section = symbol.sampleDownload else {
XCTFail("Download section not decoded")
return
}

guard case RenderInlineContent.reference(let identifier, _, _, _) = section.action else {
XCTFail("Could not decode action reference")
return
}

XCTAssertEqual(identifier.identifier, "doc://org.swift.docc.example/downloads/sample.zip")

let externalReference = try XCTUnwrap(symbol.references[identifier.identifier] as? ExternalLocationReference)
XCTAssertEqual(externalReference.url, "https://example.com/ExternalLocation.zip")
}
}