Skip to content

Instantly share code, notes, and snippets.

@ryotapoi
Last active March 17, 2020 09:09
Show Gist options
  • Save ryotapoi/2c6fce5cf79b39bf49157b8fa886664f to your computer and use it in GitHub Desktop.
Save ryotapoi/2c6fce5cf79b39bf49157b8fa886664f to your computer and use it in GitHub Desktop.
URLQueryItemをカッコよく使いたい
import Foundation
// MARK: - URLQueryItem Key
class URLQueryItemKeys {
init() {}
}
class URLQueryItemKey<Value : URLQueryItemValueCompatible> : URLQueryItemKeys {
let name: String
init(_ name: String) {
self.name = name
super.init()
}
}
// MARK: - URLQueryItem Value
enum URLQueryItemValueCompatibleError : Error {
case mustNotBeNil
case convert
}
protocol URLQueryItemValueCompatible {
init(urlQueryItemValue: String?) throws
func toURLQueryItemValue() -> String?
}
// MARK: - URLQueryItem Compatible Value
extension Int : URLQueryItemValueCompatible {
init(urlQueryItemValue: String?) throws {
guard let value = urlQueryItemValue else { throw URLQueryItemValueCompatibleError.mustNotBeNil }
guard let int = Int(value) else { throw URLQueryItemValueCompatibleError.convert }
self = int
}
func toURLQueryItemValue() -> String? {
String(self)
}
}
extension Double : URLQueryItemValueCompatible {
init(urlQueryItemValue: String?) throws {
guard let value = urlQueryItemValue else { throw URLQueryItemValueCompatibleError.mustNotBeNil }
guard let double = Double(value) else { throw URLQueryItemValueCompatibleError.convert }
self = double
}
func toURLQueryItemValue() -> String? {
String(self)
}
}
extension Float : URLQueryItemValueCompatible {
init(urlQueryItemValue: String?) throws {
guard let value = urlQueryItemValue else { throw URLQueryItemValueCompatibleError.mustNotBeNil }
guard let float = Float(value) else { throw URLQueryItemValueCompatibleError.convert }
self = float
}
func toURLQueryItemValue() -> String? {
String(self)
}
}
extension Bool : URLQueryItemValueCompatible {
init(urlQueryItemValue: String?) throws {
guard let value = urlQueryItemValue else { throw URLQueryItemValueCompatibleError.mustNotBeNil }
guard let bool = Bool(value) else { throw URLQueryItemValueCompatibleError.convert }
self = bool
}
func toURLQueryItemValue() -> String? {
String(self)
}
}
extension String : URLQueryItemValueCompatible {
init(urlQueryItemValue: String?) throws {
guard let value = urlQueryItemValue else { throw URLQueryItemValueCompatibleError.mustNotBeNil }
self = value
}
func toURLQueryItemValue() -> String? {
self
}
}
extension Optional : URLQueryItemValueCompatible where Wrapped : URLQueryItemValueCompatible {
init(urlQueryItemValue: String?) throws {
if let value = urlQueryItemValue {
self = .some(try Wrapped(urlQueryItemValue: value))
} else {
self = nil
}
}
func toURLQueryItemValue() -> String? {
switch self {
case .some(let wrapped):
return wrapped.toURLQueryItemValue()
case .none:
return nil
}
}
}
// MARK: - URLQueryItem Extension
extension Array where Element == URLQueryItem {
func value<Value>(for key: URLQueryItemKey<Value>) throws -> Value {
let value = first(where: { $0.name == key.name })?.value
return try Value(urlQueryItemValue: value)
}
}
extension Optional where Wrapped == Array<URLQueryItem> {
func value<Value>(for key: URLQueryItemKey<Value>) throws -> Value {
switch self {
case .some(let array):
return try array.value(for: key)
case .none:
return try Value(urlQueryItemValue: nil)
}
}
}
extension URLQueryItem {
init<Key, Value: URLQueryItemValueCompatible>(key: Key, value: Value) where Key : URLQueryItemKey<Value> {
self.init(name: key.name, value: value.toURLQueryItemValue())
}
}
import Foundation
// type safe QueryItem key
extension URLQueryItemKeys {
static let string = URLQueryItemKey<String>("string")
static let int = URLQueryItemKey<Int>("int")
static let bool = URLQueryItemKey<Bool>("bool")
static let floatOptional = URLQueryItemKey<Float?>("floatOptional")
static let doubleOptional = URLQueryItemKey<Double?>("doubleOptional")
static let notOptionalButNothing = URLQueryItemKey<String>("notOptionalButNothing")
}
// access QueryItem
let url = URL(string: "https://foobar.com/path?string=stringValue&int=238&bool=true&floatOptional=123.789")!
let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false)!
do {
try urlComponents.queryItems.value(for: .string)
try urlComponents.queryItems.value(for: .int)
try urlComponents.queryItems.value(for: .bool)
try urlComponents.queryItems.value(for: .floatOptional)
try urlComponents.queryItems.value(for: .doubleOptional)
try urlComponents.queryItems.value(for: .notOptionalButNothing)
} catch {
print(error)
}
// create URL with QueryItem
var schemeComponents = URLComponents()
schemeComponents.scheme = "myapp"
schemeComponents.host = "edit"
schemeComponents.path = "/add"
schemeComponents.queryItems = [
.init(key: .string, value: "abc"),
.init(key: .int, value: 5),
.init(key: .bool, value: false),
.init(key: .floatOptional, value: 32.55),
.init(key: .doubleOptional, value: nil),
].compactMap { $0 }
let scheme = schemeComponents.url
print(scheme)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment