import Foundation final class ValidationLog { private enum LogLevel: String { case error = "ERROR" case warning = "WARNING" case info = "INFO" } init() { } private func add(_ type: LogLevel, item: ContentError) { let errorText: String if let err = item.error { errorText = ", Error: \(err.localizedDescription)" } else { errorText = "" } print("[\(type.rawValue)][\(item.source)] \(item.reason)\(errorText)") } func add(error: ContentError) { add(.error, item: error) } func add(error reason: String, source: String, error: Error? = nil) { add(error: .init(reason: reason, source: source, error: error)) } func add(warning: ContentError) { add(.warning, item: warning) } func add(warning reason: String, source: String, error: Error? = nil) { add(warning: .init(reason: reason, source: source, error: error)) } func add(info: ContentError) { add(.info, item: info) } func add(info reason: String, source: String, error: Error? = nil) { add(info: .init(reason: reason, source: source, error: error)) } @discardableResult func unused(_ value: Optional, _ name: String, source: String) -> Optional { if value != nil { add(info: "Unused property '\(name)'", source: source) } return nil } func unknown(property: String, source: String) { add(info: "Unknown property '\(property)'", source: source) } func required(_ value: Optional, name: String, source: String) -> Optional { guard let value = value else { add(error: "Missing property '\(name)'", source: source) return nil } return value } func unexpected(_ value: Optional, name: String, source: String) -> Optional { if let value = value { add(error: "Unexpected property '\(name)' = '\(value)'", source: source) return nil } return nil } func missing(_ file: String, requiredBy source: String) { print("[ERROR] Missing file '\(file)' required by \(source)") } func failedToOpen(_ file: String, requiredBy source: String, error: Error?) { print("[ERROR] Failed to open file '\(file)' required by \(source): \(error?.localizedDescription ?? "No error provided")") } func state(_ raw: String?, source: String) -> PageState { guard let raw = raw else { return .standard } guard let state = PageState(rawValue: raw) else { add(warning: "Invalid 'state' '\(raw)', using 'standard'", source: source) return .standard } return state } func headerType(_ raw: String?, source: String) -> HeaderType { guard let raw = raw else { return .left } guard let type = HeaderType(rawValue: raw) else { add(warning: "Invalid 'headerType' '\(raw)', using 'left'", source: source) return .left } return type } func thumbnailStyle(_ raw: String?, source: String) -> ThumbnailStyle { guard let raw = raw else { return .large } guard let style = ThumbnailStyle(rawValue: raw) else { add(warning: "Invalid 'thumbnailStyle' '\(raw)', using 'large'", source: source) return .large } return style } func linkPreviewThumbnail(customFile: String?, for language: String, in folder: URL, source: String) -> String? { if let customFile = customFile { let customFileUrl: URL if customFile.starts(with: "/") { customFileUrl = URL(fileURLWithPath: customFile) } else { customFileUrl = folder.appendingPathComponent(customFile) } guard customFileUrl.exists else { missing(customFile, requiredBy: "property 'linkPreviewImage' in metadata of \(source)") return nil } return customFile } guard let thumbnail = Element.findThumbnail(for: language, in: folder) else { // Link preview images are not necessarily required return nil } return thumbnail } func moreLinkText(_ elementText: String?, parent parentText: String?, source: String) -> String { if let elementText = elementText { return elementText } let standard = Element.LocalizedMetadata.moreLinkDefaultText guard let parentText = parentText, parentText != standard else { add(error: "Missing property 'moreLinkText'", source: source) return standard } return parentText } func date(from string: String?, property: String, source: String) -> Date? { guard let string = string else { return nil } guard let date = ValidationLog.metadataDate.date(from: string) else { add(warning: "Invalid date string '\(string)' for property '\(property)'", source: source) return nil } return date } private static let metadataDate: DateFormatter = { let df = DateFormatter() df.dateFormat = "dd.MM.yy" return df }() }