Skip to content
Open
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
10 changes: 10 additions & 0 deletions Sources/ContainerPersistence/MemorySize.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,16 @@ public struct MemorySize: Codable, Sendable, Equatable, CustomStringConvertible
]

public var formatted: String {
// Only whole-number sizes can be represented in the stored unit without
// loss. A fractional value (e.g. "1.5gb") would truncate to "1gb" and
// silently drop memory when re-encoded, so emit the exact byte count
// instead. Binary units are integer multiples of a byte, so this keeps
// the encode/decode round-trip lossless while leaving whole-number
// output (e.g. "1gb", "2048mb") unchanged.
guard measurement.value == measurement.value.rounded() else {
let bytes = UInt64(measurement.converted(to: .bytes).value.rounded())
return "\(bytes)\(Self.unitLabels[.bytes] ?? "b")"
}
let value = Int64(measurement.value)
let label = Self.unitLabels[measurement.unit] ?? "unknown"
return "\(value)\(label)"
Expand Down
9 changes: 9 additions & 0 deletions Tests/ContainerAPIClientTests/MemorySizeTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@ struct MemorySizeTests {
#expect(original == decoded)
}

@Test(arguments: ["1.5gb", "0.5gb", "2.25tb", "1.5mb"])
func testRoundTripEncodingFractional(input: String) throws {
let original = try MemorySize(input)
let data = try JSONEncoder().encode(original)
let decoded = try JSONDecoder().decode(MemorySize.self, from: data)
#expect(original == decoded)
#expect(original.toUInt64(unit: .bytes) == decoded.toUInt64(unit: .bytes))
}

@Test func testDecodingFromString() throws {
let json = Data("\"512kb\"".utf8)
let decoded = try JSONDecoder().decode(MemorySize.self, from: json)
Expand Down