@@ -87,6 +87,15 @@ struct SchemaField: Identifiable, Codable, Hashable, Sendable {
90+/// Formats a byte count that may arrive from the server as a fractional or
91+/// out-of-range Double. `Int64(_:)` from a `Double` is a *trapping* conversion —
92+/// it crashes on NaN, infinity, or values outside `Int64`'s range — so a valid
93+/// but huge JSON number like `1e30` would crash the whole decode. Guard first.
94+private func formatByteSize(_ value: Double) -> String? {
95+ guard value.isFinite, value >= 0, value < 9.223372036854776e18 else { return nil }
96+ return ByteCountFormatter.string(fromByteCount: Int64(value), countStyle: .file)
90struct StreamStats: Codable, Sendable {
99struct StreamStats: Codable, Sendable {
91 let ingestion: IngestionStats?
100 let ingestion: IngestionStats?
92 let storage: StorageStats?
101 let storage: StorageStats?
@@ -111,7 +120,7 @@ struct StreamStats: Codable, Sendable {
111 } else if let n = try? container.decode(Int.self, forKey: .size) {
120 } else if let n = try? container.decode(Int.self, forKey: .size) {
112 self.size = ByteCountFormatter.string(fromByteCount: Int64(n), countStyle: .file)
121 self.size = ByteCountFormatter.string(fromByteCount: Int64(n), countStyle: .file)
113 } else if let d = try? container.decode(Double.self, forKey: .size) {
122 } else if let d = try? container.decode(Double.self, forKey: .size) {
114− self.size = ByteCountFormatter.string(fromByteCount: Int64(d), countStyle: .file)
123+ self.size = formatByteSize(d)
116 self.size = nil
125 self.size = nil
@@ -120,7 +129,7 @@ struct StreamStats: Codable, Sendable {
120 } else if let n = try? container.decode(Int.self, forKey: .lifetime_size) {
129 } else if let n = try? container.decode(Int.self, forKey: .lifetime_size) {
121 self.lifetime_size = ByteCountFormatter.string(fromByteCount: Int64(n), countStyle: .file)
130 self.lifetime_size = ByteCountFormatter.string(fromByteCount: Int64(n), countStyle: .file)
122 } else if let d = try? container.decode(Double.self, forKey: .lifetime_size) {
131 } else if let d = try? container.decode(Double.self, forKey: .lifetime_size) {
123− self.lifetime_size = ByteCountFormatter.string(fromByteCount: Int64(d), countStyle: .file)
132+ self.lifetime_size = formatByteSize(d)
125 self.lifetime_size = nil
134 self.lifetime_size = nil
@@ -144,7 +153,7 @@ struct StreamStats: Codable, Sendable {
144 } else if let n = try? container.decode(Int.self, forKey: .size) {
153 } else if let n = try? container.decode(Int.self, forKey: .size) {
145 self.size = ByteCountFormatter.string(fromByteCount: Int64(n), countStyle: .file)
154 self.size = ByteCountFormatter.string(fromByteCount: Int64(n), countStyle: .file)
146 } else if let d = try? container.decode(Double.self, forKey: .size) {
155 } else if let d = try? container.decode(Double.self, forKey: .size) {
147− self.size = ByteCountFormatter.string(fromByteCount: Int64(d), countStyle: .file)
156+ self.size = formatByteSize(d)
149 self.size = nil
158 self.size = nil
@@ -153,7 +162,7 @@ struct StreamStats: Codable, Sendable {
153 } else if let n = try? container.decode(Int.self, forKey: .lifetime_size) {
162 } else if let n = try? container.decode(Int.self, forKey: .lifetime_size) {
154 self.lifetime_size = ByteCountFormatter.string(fromByteCount: Int64(n), countStyle: .file)
163 self.lifetime_size = ByteCountFormatter.string(fromByteCount: Int64(n), countStyle: .file)
155 } else if let d = try? container.decode(Double.self, forKey: .lifetime_size) {
164 } else if let d = try? container.decode(Double.self, forKey: .lifetime_size) {
156− self.lifetime_size = ByteCountFormatter.string(fromByteCount: Int64(d), countStyle: .file)
165+ self.lifetime_size = formatByteSize(d)
158 self.lifetime_size = nil
167 self.lifetime_size = nil