refactor: consolidate UUID formatting into shared String extension

Created UUIDFormatting.swift with .normalizedUUID and .uuidWithDashes
String extensions, replacing 4 duplicate formatUuidWithDashes() methods
and 6+ inline .replacingOccurrences(of: "-", with: "").uppercased() calls
across Api.swift, BeaconScanner.swift, ScanView.swift,
ServicePointListView.swift, BeaconBanList.swift, and BeaconProvisioner.swift.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Schwifty 2026-03-21 10:02:58 +00:00
parent 8b413020ff
commit 237ac38557
7 changed files with 48 additions and 45 deletions

View file

@ -155,7 +155,7 @@ class Api {
var result: [String: Int] = [:]
for item in items {
guard let uuid = ((item["UUID"] ?? item["uuid"] ?? item["BeaconUUID"] ?? item["BEACONUUID"]) as? String)?
.replacingOccurrences(of: "-", with: "").uppercased(),
.normalizedUUID,
let beaconId = parseIntValue(item["BeaconID"] ?? item["BEACONID"]) else {
continue
}
@ -175,7 +175,7 @@ class Api {
return beacons.compactMap { b in
guard let uuid = ((b["UUID"] ?? b["uuid"]) as? String)?
.replacingOccurrences(of: "-", with: "").uppercased() else {
.normalizedUUID else {
return nil
}
return BeaconLookupResult(
@ -204,7 +204,7 @@ class Api {
let beaconId = parseIntValue(b["BeaconID"] ?? b["BEACONID"] ?? b["ID"]) ?? 0
let name = ((b["Name"] ?? b["NAME"] ?? b["BeaconName"] ?? b["BEACONNAME"]) as? String) ?? ""
let uuid = ((b["UUID"] ?? b["uuid"]) as? String)?
.replacingOccurrences(of: "-", with: "").uppercased() ?? ""
.normalizedUUID ?? ""
let isActive = parseBool(b["IsActive"] ?? b["ISACTIVE"] ?? true)
return BeaconInfo(beaconId: beaconId, name: name, uuid: uuid, isActive: isActive)
}
@ -322,7 +322,7 @@ class Api {
DebugLog.shared.log("[API] getBeaconConfig parsed: uuid=\(uuid) major=\(major) minor=\(minor) measuredPower=\(measuredPower) advInterval=\(advInterval) txPower=\(txPower)")
return BeaconConfigResponse(
uuid: uuid.replacingOccurrences(of: "-", with: "").uppercased(),
uuid: uuid.normalizedUUID,
major: UInt16(major),
minor: UInt16(minor),
measuredPower: Int8(clamping: measuredPower),
@ -430,7 +430,7 @@ class Api {
return BusinessNamespace(
shardId: parseIntValue(json["ShardID"] ?? json["SHARDID"]) ?? 0,
uuid: uuid,
uuidClean: uuid.replacingOccurrences(of: "-", with: "").uppercased(),
uuidClean: uuid.normalizedUUID,
major: UInt16(major),
alreadyAllocated: parseBool(json["AlreadyAllocated"] ?? json["ALREADYALLOCATED"])
)

View file

@ -38,7 +38,7 @@ enum BeaconBanList {
/// Check if a UUID is on the ban list.
static func isBanned(_ uuid: String) -> Bool {
let normalized = uuid.replacingOccurrences(of: "-", with: "").uppercased()
let normalized = uuid.normalizedUUID
// Check full UUID match
if BANNED_FULL_UUIDS[normalized] != nil { return true }
@ -52,7 +52,7 @@ enum BeaconBanList {
/// Get the reason a UUID is banned, or nil if not banned.
static func getBanReason(_ uuid: String) -> String? {
let normalized = uuid.replacingOccurrences(of: "-", with: "").uppercased()
let normalized = uuid.normalizedUUID
// Check full UUID match first
if let reason = BANNED_FULL_UUIDS[normalized] { return reason }
@ -64,16 +64,9 @@ enum BeaconBanList {
return nil
}
/// Format a raw UUID string (32 hex chars) into standard UUID format with dashes.
/// Format a raw UUID string into standard UUID format with dashes.
/// Delegates to String.uuidWithDashes (UUIDFormatting.swift).
static func formatUuid(_ uuid: String) -> String {
let hex = uuid.replacingOccurrences(of: "-", with: "").uppercased()
guard hex.count == 32 else { return uuid }
let s = hex
let i0 = s.startIndex
let i8 = s.index(i0, offsetBy: 8)
let i12 = s.index(i0, offsetBy: 12)
let i16 = s.index(i0, offsetBy: 16)
let i20 = s.index(i0, offsetBy: 20)
return "\(s[i0..<i8])-\(s[i8..<i12])-\(s[i12..<i16])-\(s[i16..<i20])-\(s[i20...])"
uuid.uuidWithDashes
}
}

View file

@ -826,7 +826,7 @@ class BeaconProvisioner: NSObject, ObservableObject {
// MARK: - Helpers
private func hexStringToData(_ hex: String) -> Data? {
let clean = hex.replacingOccurrences(of: "-", with: "").uppercased()
let clean = hex.normalizedUUID
guard clean.count == 32 else { return nil }
var data = Data()

View file

@ -121,7 +121,7 @@ class BeaconScanner: NSObject, ObservableObject, CLLocationManagerDelegate {
let rssiValue = beacon.rssi
guard rssiValue >= BeaconScanner.MIN_RSSI && rssiValue < 0 else { continue }
let uuid = beacon.uuid.uuidString.replacingOccurrences(of: "-", with: "").uppercased()
let uuid = beacon.uuid.uuidString.normalizedUUID
let major = beacon.major.uint16Value
let minor = beacon.minor.uint16Value
let key = "\(uuid)|\(major)|\(minor)"
@ -148,13 +148,6 @@ struct DetectedBeacon {
/// Format for clipboard (for pasting into manufacturer beacon config apps)
func copyableConfig() -> String {
// Format UUID with dashes for standard display
let formattedUuid = formatUuidWithDashes(uuid)
return "UUID: \(formattedUuid)\nMajor: \(major)\nMinor: \(minor)"
}
private func formatUuidWithDashes(_ raw: String) -> String {
guard raw.count == 32 else { return raw }
let chars = Array(raw)
return "\(String(chars[0..<8]))-\(String(chars[8..<12]))-\(String(chars[12..<16]))-\(String(chars[16..<20]))-\(String(chars[20..<32]))"
return "UUID: \(uuid.uuidWithDashes)\nMajor: \(major)\nMinor: \(minor)"
}
}

View file

@ -808,7 +808,7 @@ struct ScanView: View {
do {
// Format UUID with dashes for API call
let uuidWithDashes = formatUuidWithDashes(beacon.uuid)
let uuidWithDashes = beacon.uuid.uuidWithDashes
let result = try await Api.shared.resolveBusiness(uuid: uuidWithDashes, major: beacon.major)
await MainActor.run {
beaconOwnership[key] = (businessId: result.businessId, businessName: result.businessName)
@ -895,7 +895,7 @@ struct ScanView: View {
case .success(let macAddress):
do {
// Use MAC address as hardware ID, fallback to iBeacon UUID if unavailable
let uuidWithDashes = formatUuidWithDashes(config.uuid)
let uuidWithDashes = config.uuid.uuidWithDashes
let hardwareId = macAddress ?? uuidWithDashes
DebugLog.shared.log("[ScanView] Registering beacon - MAC: \(macAddress ?? "nil"), hardwareId: \(hardwareId), uuid: \(uuidWithDashes)")
try await Api.shared.registerBeaconHardware(
@ -978,7 +978,7 @@ struct ScanView: View {
// Register in backend (use UUID with dashes for API)
do {
// Use MAC address as hardware ID, fallback to iBeacon UUID if unavailable
let uuidWithDashes = formatUuidWithDashes(config.uuid)
let uuidWithDashes = config.uuid.uuidWithDashes
let hardwareId = macAddress ?? uuidWithDashes
DebugLog.shared.log("[ScanView] Registering beacon - MAC: \(macAddress ?? "nil"), hardwareId: \(hardwareId), uuid: \(uuidWithDashes)")
try await Api.shared.registerBeaconHardware(
@ -1039,10 +1039,5 @@ struct ScanView: View {
provisioningError = error
}
private func formatUuidWithDashes(_ raw: String) -> String {
let clean = raw.replacingOccurrences(of: "-", with: "").uppercased()
guard clean.count == 32 else { return raw }
let chars = Array(clean)
return "\(String(chars[0..<8]))-\(String(chars[8..<12]))-\(String(chars[12..<16]))-\(String(chars[16..<20]))-\(String(chars[20..<32]))"
}
// UUID formatting now handled by String.uuidWithDashes (UUIDFormatting.swift)
}

View file

@ -58,10 +58,10 @@ struct ServicePointListView: View {
.font(.caption)
.foregroundColor(.secondary)
Spacer()
Text(formatUuidWithDashes(ns.uuid))
Text(ns.uuid.uuidWithDashes)
.font(.system(.caption, design: .monospaced))
Button {
UIPasteboard.general.string = formatUuidWithDashes(ns.uuid)
UIPasteboard.general.string = ns.uuid.uuidWithDashes
} label: {
Image(systemName: "doc.on.doc")
.font(.caption)
@ -217,7 +217,7 @@ struct ServicePointListView: View {
HStack {
VStack(alignment: .leading) {
Text("UUID").font(.caption2).foregroundColor(.secondary)
Text(formatUuidWithDashes(ns.uuid))
Text(ns.uuid.uuidWithDashes)
.font(.system(.caption2, design: .monospaced))
}
}
@ -333,11 +333,6 @@ struct ServicePointListView: View {
return maxNumber + 1
}
private func formatUuidWithDashes(_ raw: String) -> String {
let clean = raw.replacingOccurrences(of: "-", with: "").uppercased()
guard clean.count == 32 else { return raw }
let chars = Array(clean)
return "\(String(chars[0..<8]))-\(String(chars[8..<12]))-\(String(chars[12..<16]))-\(String(chars[16..<20]))-\(String(chars[20..<32]))"
}
// UUID formatting now handled by String.uuidWithDashes (UUIDFormatting.swift)
}

View file

@ -0,0 +1,27 @@
import Foundation
// MARK: - UUID Formatting Utilities
// Consolidated UUID normalization and formatting logic.
// Previously duplicated across Api.swift, BeaconScanner.swift, ScanView.swift,
// ServicePointListView.swift, and BeaconBanList.swift.
extension String {
/// Normalize a UUID string: strip dashes, uppercase.
/// Input: "e2c56db5-dffb-48d2-b060-d0f5a71096e0" or "E2C56DB5DFFB48D2B060D0F5A71096E0"
/// Output: "E2C56DB5DFFB48D2B060D0F5A71096E0"
var normalizedUUID: String {
replacingOccurrences(of: "-", with: "").uppercased()
}
/// Format a 32-char hex string into standard UUID format (8-4-4-4-12).
/// Input: "E2C56DB5DFFB48D2B060D0F5A71096E0"
/// Output: "E2C56DB5-DFFB-48D2-B060-D0F5A71096E0"
/// Returns the original string unchanged if it's not exactly 32 hex chars after normalization.
var uuidWithDashes: String {
let clean = normalizedUUID
guard clean.count == 32 else { return self }
let c = Array(clean)
return "\(String(c[0..<8]))-\(String(c[8..<12]))-\(String(c[12..<16]))-\(String(c[16..<20]))-\(String(c[20..<32]))"
}
}