Skip to content

Commit 76d1387

Browse files
committed
Fix Hub API URL construction
1 parent 18ccbd2 commit 76d1387

File tree

2 files changed

+32
-7
lines changed

2 files changed

+32
-7
lines changed

Sources/Hub/HubApi.swift

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,12 @@ public extension HubApi {
258258
/// - Throws: HubClientError if the repository cannot be accessed or parsed
259259
func getFilenames(from repo: Repo, revision: String = "main", matching globs: [String] = []) async throws -> [String] {
260260
// Read repo info and only parse "siblings"
261-
let url = URL(string: "\(endpoint)/api/\(repo.type)/\(repo.id)/revision/\(revision)")!
261+
let url = URL(string: endpoint)!
262+
.appendingPathComponent("api")
263+
.appendingPathComponent(repo.type.rawValue)
264+
.appendingPathComponent(repo.id)
265+
.appendingPathComponent("revision")
266+
.appending(component: revision) // Use appending(component:) to encode slashes (e.g., "pr/1" -> "pr%2F1")
262267
let (data, _) = try await httpGet(for: url)
263268
let response = try JSONDecoder().decode(SiblingsResponse.self, from: data)
264269
let filenames = response.siblings.map { $0.rfilename }
@@ -333,7 +338,9 @@ public extension HubApi {
333338
func whoami() async throws -> Config {
334339
guard hfToken != nil else { throw Hub.HubClientError.authorizationRequired }
335340

336-
let url = URL(string: "\(endpoint)/api/whoami-v2")!
341+
let url = URL(string: endpoint)!
342+
.appendingPathComponent("api")
343+
.appendingPathComponent("whoami-v2")
337344
let (data, _) = try await httpGet(for: url)
338345

339346
let parsed = try JSONSerialization.jsonObject(with: data, options: [])
@@ -462,11 +469,12 @@ public extension HubApi {
462469
// https://huggingface.co/coreml-projects/Llama-2-7b-chat-coreml/resolve/main/tokenizer.json?download=true
463470
var url = URL(string: endpoint ?? "https://huggingface.co")!
464471
if repo.type != .models {
465-
url = url.appending(component: repo.type.rawValue)
472+
url = url.appendingPathComponent(repo.type.rawValue)
466473
}
467-
url = url.appending(path: repo.id)
468-
url = url.appending(path: "resolve/\(revision)")
469-
url = url.appending(path: relativeFilename)
474+
url = url.appendingPathComponent(repo.id)
475+
url = url.appendingPathComponent("resolve")
476+
url = url.appending(component: revision) // Use appending(component:) to encode slashes (e.g., "pr/1" -> "pr%2F1")
477+
url = url.appendingPathComponent(relativeFilename)
470478
return url
471479
}
472480

@@ -792,7 +800,10 @@ public extension HubApi {
792800

793801
func getFileMetadata(from repo: Repo, revision: String = "main", matching globs: [String] = []) async throws -> [FileMetadata] {
794802
let files = try await getFilenames(from: repo, matching: globs)
795-
let url = URL(string: "\(endpoint)/\(repo.id)/resolve/\(revision)")!
803+
let url = URL(string: endpoint)!
804+
.appendingPathComponent(repo.id)
805+
.appendingPathComponent("resolve")
806+
.appending(component: revision) // Use appending(component:) to encode slashes (e.g., "pr/1" -> "pr%2F1")
796807
var selectedMetadata: [FileMetadata] = []
797808
for file in files {
798809
let fileURL = url.appending(path: file)

Tests/HubTests/HubApiTests.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,20 @@ import XCTest
1111
class HubApiTests: XCTestCase {
1212
// TODO: use a specific revision for these tests
1313

14+
/// Test that revision values containing slashes (like "pr/1") are properly URL encoded.
15+
/// The Hub API requires "pr/1" to be encoded as "pr%2F1" - otherwise it returns 404.
16+
func testFilenameRetrievalWithPRRevision() async throws {
17+
// This repo has a PR #1 that we can use to test the pr/N revision format
18+
// If URL encoding is broken, this will fail with a 404 error
19+
let hubApi = HubApi()
20+
let filenames = try await hubApi.getFilenames(
21+
from: Hub.Repo(id: "coreml-projects/sam-2-studio"),
22+
revision: "pr/1",
23+
matching: ["*.md"]
24+
)
25+
XCTAssertFalse(filenames.isEmpty, "Should retrieve filenames from PR revision")
26+
}
27+
1428
func testFilenameRetrieval() async {
1529
do {
1630
let filenames = try await Hub.getFilenames(from: "coreml-projects/Llama-2-7b-chat-coreml")

0 commit comments

Comments
 (0)