// // WinnerEnvelope.swift // iris // // Created by Codex. // import Foundation struct WinnerEnvelope: Codable, Equatable { struct DebugInfo: Codable, Equatable { let reason: String let source: String } let schema: Int let generatedAt: Int let winner: Winner let debug: DebugInfo? enum CodingKeys: String, CodingKey { case schema case generatedAt = "generated_at" case winner case debug } static func allQuiet(now: Int, reason: String = "no_candidates", source: String = "engine") -> WinnerEnvelope { let winner = Winner( id: "quiet-000", type: .allQuiet, title: "All Quiet", subtitle: "No urgent updates", priority: 0.05, ttlSec: 300 ) return WinnerEnvelope(schema: 1, generatedAt: now, winner: winner, debug: .init(reason: reason, source: source)) } } enum EnvelopeValidationError: Error, LocalizedError { case invalidSchema(Int) case invalidPriority(Double) case invalidTTL(Int) var errorDescription: String? { switch self { case .invalidSchema(let schema): return "Invalid schema \(schema). Expected 1." case .invalidPriority(let priority): return "Invalid priority \(priority). Must be between 0 and 1." case .invalidTTL(let ttl): return "Invalid ttl \(ttl). Must be greater than 0." } } } func validateEnvelope(_ envelope: WinnerEnvelope) throws -> WinnerEnvelope { guard envelope.schema == 1 else { throw EnvelopeValidationError.invalidSchema(envelope.schema) } guard envelope.winner.priority >= 0.0, envelope.winner.priority <= 1.0 else { throw EnvelopeValidationError.invalidPriority(envelope.winner.priority) } guard envelope.winner.ttlSec > 0 else { throw EnvelopeValidationError.invalidTTL(envelope.winner.ttlSec) } let validatedWinner = Winner( id: envelope.winner.id, type: envelope.winner.type, title: envelope.winner.title.truncated(maxLength: TextConstraints.titleMax), subtitle: envelope.winner.subtitle.truncated(maxLength: TextConstraints.subtitleMax), priority: envelope.winner.priority, ttlSec: envelope.winner.ttlSec ) return WinnerEnvelope( schema: envelope.schema, generatedAt: envelope.generatedAt, winner: validatedWinner, debug: envelope.debug ) } enum TextConstraints { static let titleMax = 26 static let subtitleMax = 30 static let ellipsis = "..." } extension String { func truncated(maxLength: Int) -> String { guard count > maxLength else { return self } let ellipsisCount = TextConstraints.ellipsis.count guard maxLength > ellipsisCount else { return String(prefix(maxLength)) } return String(prefix(maxLength - ellipsisCount)) + TextConstraints.ellipsis } }