import Foundation /// JSON response from `calcpad_eval_line` — wraps a single `CalcResult`. struct FFIResponse: Decodable { let schemaVersion: String let result: FFICalcResult enum CodingKeys: String, CodingKey { case schemaVersion = "schema_version" case result } } /// JSON response from `calcpad_eval_sheet` — wraps an array of `CalcResult`. struct FFISheetResponse: Decodable { let schemaVersion: String let results: [FFICalcResult] enum CodingKeys: String, CodingKey { case schemaVersion = "schema_version" case results } } /// A complete calculation result returned by the Rust engine. struct FFICalcResult: Decodable { let value: FFICalcValue let metadata: FFIResultMetadata } /// Metadata attached to every evaluation result. struct FFIResultMetadata: Decodable { let span: FFISpan let resultType: String let display: String let rawValue: Double? enum CodingKeys: String, CodingKey { case span case resultType = "result_type" case display case rawValue = "raw_value" } } /// Source span (byte offsets). struct FFISpan: Decodable { let start: Int let end: Int } /// Tagged union of possible calculation values. /// Rust serializes with `#[serde(tag = "kind")]`. enum FFICalcValue: Decodable { case number(value: Double) case unitValue(value: Double, unit: String) case currencyValue(amount: Double, currency: String) case dateTime(date: String) case timeDelta(days: Int64, description: String) case boolean(value: Bool) case error(message: String) enum CodingKeys: String, CodingKey { case kind case value, unit, amount, currency, date, days, description, message, span } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let kind = try container.decode(String.self, forKey: .kind) switch kind { case "Number": let value = try container.decode(Double.self, forKey: .value) self = .number(value: value) case "UnitValue": let value = try container.decode(Double.self, forKey: .value) let unit = try container.decode(String.self, forKey: .unit) self = .unitValue(value: value, unit: unit) case "CurrencyValue": let amount = try container.decode(Double.self, forKey: .amount) let currency = try container.decode(String.self, forKey: .currency) self = .currencyValue(amount: amount, currency: currency) case "DateTime": let date = try container.decode(String.self, forKey: .date) self = .dateTime(date: date) case "TimeDelta": let days = try container.decode(Int64.self, forKey: .days) let desc = try container.decode(String.self, forKey: .description) self = .timeDelta(days: days, description: desc) case "Boolean": let value = try container.decode(Bool.self, forKey: .value) self = .boolean(value: value) case "Error": let message = try container.decode(String.self, forKey: .message) self = .error(message: message) default: self = .error(message: "Unknown result kind: \(kind)") } } /// Whether this value represents an error from the Rust engine. var isError: Bool { if case .error = self { return true } return false } }