209 lines
7.5 KiB
Swift
209 lines
7.5 KiB
Swift
import Foundation
|
|
|
|
/// SPEC §5 — `TargetJob` v1. Unknown keys ignored; missing required keys fatal.
|
|
|
|
struct PrintSettings: Equatable {
|
|
var printerName: String
|
|
var mediaSize: String
|
|
var mediaType: String?
|
|
var paperSource: String?
|
|
var resolution: String?
|
|
var printQuality: String?
|
|
var scaling: Double
|
|
var centered: Bool
|
|
var forceUnmanagedColor: Bool
|
|
|
|
init(
|
|
printerName: String,
|
|
mediaSize: String,
|
|
mediaType: String? = nil,
|
|
paperSource: String? = nil,
|
|
resolution: String? = nil,
|
|
printQuality: String? = nil,
|
|
scaling: Double,
|
|
centered: Bool,
|
|
forceUnmanagedColor: Bool
|
|
) {
|
|
self.printerName = printerName
|
|
self.mediaSize = mediaSize
|
|
self.mediaType = mediaType
|
|
self.paperSource = paperSource
|
|
self.resolution = resolution
|
|
self.printQuality = printQuality
|
|
self.scaling = scaling
|
|
self.centered = centered
|
|
self.forceUnmanagedColor = forceUnmanagedColor
|
|
}
|
|
}
|
|
|
|
struct UIPolicy: Equatable {
|
|
var lockColorManagement: Bool
|
|
var allowBasicDriverChanges: Bool
|
|
}
|
|
|
|
struct TargetJob: Equatable {
|
|
var version: Int
|
|
var jobTitle: String
|
|
var files: [String]
|
|
var printSettings: PrintSettings
|
|
var uiPolicy: UIPolicy
|
|
}
|
|
|
|
enum TargetJobParser {
|
|
static func parse(data: Data) throws -> TargetJob {
|
|
let json: Any
|
|
do {
|
|
json = try JSONSerialization.jsonObject(with: data, options: [])
|
|
} catch {
|
|
throw AppError.invalidJob("JSON parse error")
|
|
}
|
|
guard let obj = json as? [String: Any] else {
|
|
throw AppError.invalidJob("root must be an object")
|
|
}
|
|
return try parse(object: obj)
|
|
}
|
|
|
|
static func parse(text: String) throws -> TargetJob {
|
|
guard let data = text.data(using: .utf8) else {
|
|
throw AppError.invalidJob("unreadable UTF-8")
|
|
}
|
|
return try parse(data: data)
|
|
}
|
|
|
|
static func parse(url: URL) throws -> TargetJob {
|
|
let data: Data
|
|
do {
|
|
data = try Data(contentsOf: url)
|
|
} catch {
|
|
throw AppError.invalidJob("cannot read \(url.path)")
|
|
}
|
|
return try parse(data: data)
|
|
}
|
|
|
|
static func parse(object: [String: Any]) throws -> TargetJob {
|
|
// Unknown keys ignored (passthrough). Required keys fatal.
|
|
guard let version = intValue(object["version"]) else {
|
|
throw AppError.invalidJob("version — required")
|
|
}
|
|
guard version == 1 else {
|
|
throw AppError.invalidJob("version — currently only 1 is accepted")
|
|
}
|
|
guard let jobTitle = stringValue(object["jobTitle"]), !jobTitle.isEmpty else {
|
|
throw AppError.invalidJob("jobTitle — required")
|
|
}
|
|
guard let filesRaw = object["files"] as? [Any], !filesRaw.isEmpty else {
|
|
throw AppError.invalidJob("files — required non-empty array")
|
|
}
|
|
var files: [String] = []
|
|
for (i, item) in filesRaw.enumerated() {
|
|
guard let s = item as? String, !s.isEmpty else {
|
|
throw AppError.invalidJob("files[\(i)] — absolute path required")
|
|
}
|
|
files.append(s)
|
|
}
|
|
guard let psObj = object["printSettings"] as? [String: Any] else {
|
|
throw AppError.invalidJob("printSettings — required")
|
|
}
|
|
guard let uiObj = object["uiPolicy"] as? [String: Any] else {
|
|
throw AppError.invalidJob("uiPolicy — required")
|
|
}
|
|
let printSettings = try parsePrintSettings(psObj)
|
|
let uiPolicy = try parseUIPolicy(uiObj)
|
|
return TargetJob(
|
|
version: 1,
|
|
jobTitle: jobTitle,
|
|
files: files,
|
|
printSettings: printSettings,
|
|
uiPolicy: uiPolicy
|
|
)
|
|
}
|
|
|
|
private static func parsePrintSettings(_ obj: [String: Any]) throws -> PrintSettings {
|
|
guard let printerName = stringValue(obj["printerName"]), !printerName.isEmpty else {
|
|
throw AppError.invalidJob("printSettings.printerName — required")
|
|
}
|
|
guard let mediaSize = stringValue(obj["mediaSize"]), !mediaSize.isEmpty else {
|
|
throw AppError.invalidJob("printSettings.mediaSize — required")
|
|
}
|
|
guard let scaling = doubleValue(obj["scaling"]) else {
|
|
throw AppError.invalidJob("printSettings.scaling — required number")
|
|
}
|
|
guard let centered = boolValue(obj["centered"]) else {
|
|
throw AppError.invalidJob("printSettings.centered — required bool")
|
|
}
|
|
guard let force = boolValue(obj["forceUnmanagedColor"]) else {
|
|
throw AppError.invalidJob("printSettings.forceUnmanagedColor — required bool")
|
|
}
|
|
return PrintSettings(
|
|
printerName: printerName,
|
|
mediaSize: mediaSize,
|
|
mediaType: stringValue(obj["mediaType"]),
|
|
paperSource: stringValue(obj["paperSource"]),
|
|
resolution: stringValue(obj["resolution"]),
|
|
printQuality: stringValue(obj["printQuality"]),
|
|
scaling: scaling,
|
|
centered: centered,
|
|
forceUnmanagedColor: force
|
|
)
|
|
}
|
|
|
|
private static func parseUIPolicy(_ obj: [String: Any]) throws -> UIPolicy {
|
|
guard let lock = boolValue(obj["lockColorManagement"]) else {
|
|
throw AppError.invalidJob("uiPolicy.lockColorManagement — required bool")
|
|
}
|
|
guard let allow = boolValue(obj["allowBasicDriverChanges"]) else {
|
|
throw AppError.invalidJob("uiPolicy.allowBasicDriverChanges — required bool")
|
|
}
|
|
return UIPolicy(lockColorManagement: lock, allowBasicDriverChanges: allow)
|
|
}
|
|
|
|
static func serialize(_ job: TargetJob) throws -> Data {
|
|
var ps: [String: Any] = [
|
|
"printerName": job.printSettings.printerName,
|
|
"mediaSize": job.printSettings.mediaSize,
|
|
"scaling": job.printSettings.scaling,
|
|
"centered": job.printSettings.centered,
|
|
"forceUnmanagedColor": job.printSettings.forceUnmanagedColor,
|
|
]
|
|
if let v = job.printSettings.mediaType { ps["mediaType"] = v }
|
|
if let v = job.printSettings.paperSource { ps["paperSource"] = v }
|
|
if let v = job.printSettings.resolution { ps["resolution"] = v }
|
|
if let v = job.printSettings.printQuality { ps["printQuality"] = v }
|
|
let obj: [String: Any] = [
|
|
"version": 1,
|
|
"jobTitle": job.jobTitle,
|
|
"files": job.files,
|
|
"printSettings": ps,
|
|
"uiPolicy": [
|
|
"lockColorManagement": job.uiPolicy.lockColorManagement,
|
|
"allowBasicDriverChanges": job.uiPolicy.allowBasicDriverChanges,
|
|
],
|
|
]
|
|
return try JSONSerialization.data(withJSONObject: obj, options: [.prettyPrinted])
|
|
}
|
|
|
|
private static func stringValue(_ any: Any?) -> String? {
|
|
if let s = any as? String { return s }
|
|
return nil
|
|
}
|
|
|
|
private static func intValue(_ any: Any?) -> Int? {
|
|
if let i = any as? Int { return i }
|
|
if let n = any as? NSNumber { return n.intValue }
|
|
return nil
|
|
}
|
|
|
|
private static func doubleValue(_ any: Any?) -> Double? {
|
|
if let d = any as? Double { return d }
|
|
if let i = any as? Int { return Double(i) }
|
|
if let n = any as? NSNumber { return n.doubleValue }
|
|
return nil
|
|
}
|
|
|
|
private static func boolValue(_ any: Any?) -> Bool? {
|
|
if let b = any as? Bool { return b }
|
|
if let n = any as? NSNumber { return n.boolValue }
|
|
return nil
|
|
}
|
|
}
|