133 lines
4.3 KiB
Swift
133 lines
4.3 KiB
Swift
//
|
|
// GeminiResponse.swift
|
|
// Gemini
|
|
//
|
|
// Created by Shadowfacts on 7/12/20.
|
|
//
|
|
|
|
import Foundation
|
|
import UniformTypeIdentifiers
|
|
|
|
struct GeminiResponse {
|
|
let status: StatusCode
|
|
let meta: String
|
|
let body: Data?
|
|
|
|
// Helpers
|
|
var rawMimeType: String? {
|
|
guard status.isSuccess else { return nil }
|
|
return meta.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
}
|
|
var mimeType: String? {
|
|
guard let rawMimeType = rawMimeType else { return nil }
|
|
return rawMimeType.split(separator: ";").first?.trimmingCharacters(in: .whitespaces)
|
|
}
|
|
var mimeTypeParameters: [String: String]? {
|
|
guard let rawMimeType = rawMimeType else { return nil }
|
|
return rawMimeType.split(separator: ";").dropFirst().reduce(into: [String: String]()) { (parameters, parameter) in
|
|
let parts = parameter.split(separator: "=").map { $0.trimmingCharacters(in: .whitespaces) }
|
|
precondition(parts.count == 2)
|
|
parameters[parts[0].lowercased()] = parts[1]
|
|
}
|
|
}
|
|
@available(macOS 10.16, *)
|
|
var utiType: UTType? {
|
|
guard let mimeType = mimeType else { return nil }
|
|
return UTType.types(tag: mimeType, tagClass: .mimeType, conformingTo: nil).first
|
|
}
|
|
|
|
var bodyText: String? {
|
|
guard let body = body, let parameters = mimeTypeParameters else { return nil }
|
|
let encoding: String.Encoding
|
|
switch parameters["charset"]?.lowercased() {
|
|
case nil, "utf-8":
|
|
// The Gemini spec defines UTF-8 to be the default charset.
|
|
encoding = .utf8
|
|
case "us-ascii":
|
|
encoding = .ascii
|
|
default:
|
|
// todo: log warning
|
|
encoding = .utf8
|
|
}
|
|
return String(data: body, encoding: encoding)
|
|
}
|
|
}
|
|
|
|
extension GeminiResponse {
|
|
enum StatusCode: Int {
|
|
// All statuses and subtypes
|
|
case input = 10
|
|
case sensitiveInput = 11
|
|
case success = 20
|
|
case temporaryRedirect = 30
|
|
case permanentRedirect = 31
|
|
case temporaryFailure = 40
|
|
case serverUnavailable = 41
|
|
case cgiError = 42
|
|
case proxyError = 43
|
|
case slowDown = 44
|
|
case permanentFailure = 50
|
|
case notFound = 51
|
|
case gone = 52
|
|
case proxyRequestRefused = 53
|
|
case badRequest = 59
|
|
case clientCertificateRequested = 60
|
|
case certificateNotAuthorised = 61
|
|
case certificateNotValid = 62
|
|
|
|
// Status type helpers
|
|
var isInput: Bool { rawValue / 10 == 1 }
|
|
var isSuccess: Bool { rawValue / 10 == 2 }
|
|
var isRedirect: Bool { rawValue / 10 == 3 }
|
|
var isTemporaryFailure: Bool { rawValue / 10 == 4 }
|
|
var isPermanentFailure: Bool { rawValue / 10 == 5 }
|
|
var isClientCertificateRequired: Bool { rawValue / 10 == 6 }
|
|
|
|
// Other helpers
|
|
var isFailure: Bool { isTemporaryFailure || isPermanentFailure }
|
|
}
|
|
}
|
|
|
|
extension GeminiResponse.StatusCode: CustomStringConvertible {
|
|
var description: String {
|
|
switch self {
|
|
case .input:
|
|
return "input"
|
|
case .sensitiveInput:
|
|
return "sensitiveInput"
|
|
case .success:
|
|
return "success"
|
|
case .temporaryRedirect:
|
|
return "temporaryRedirect"
|
|
case .permanentRedirect:
|
|
return "permanentRedirect"
|
|
case .temporaryFailure:
|
|
return "temporaryFailure"
|
|
case .serverUnavailable:
|
|
return "serverUnavailable"
|
|
case .cgiError:
|
|
return "cgiError"
|
|
case .proxyError:
|
|
return "proxyError"
|
|
case .slowDown:
|
|
return "slowDown"
|
|
case .permanentFailure:
|
|
return "permanentFailure"
|
|
case .notFound:
|
|
return "notFound"
|
|
case .gone:
|
|
return "gone"
|
|
case .proxyRequestRefused:
|
|
return "proxyRequestRefused"
|
|
case .badRequest:
|
|
return "badRequest"
|
|
case .clientCertificateRequested:
|
|
return "clientCertificateRequested"
|
|
case .certificateNotAuthorised:
|
|
return "certificateNotAuthorised"
|
|
case .certificateNotValid:
|
|
return "certificateNotValid"
|
|
}
|
|
}
|
|
}
|