blob: cb087c255fb67e63b05292b59bc28c90002b7f48 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
|
//
// LocationIdentifier.swift
// MullvadVPN
//
// Created by Andrew Bulhak on 2025-01-24.
// Copyright © 2025 Mullvad VPN AB. All rights reserved.
//
extension REST {
// locations are currently always "aa-bbb" for some country code aa and city code bbb. Should this change, this type can be extended.
public struct LocationIdentifier: Sendable {
public let country: String
public let city: String
fileprivate static func parse(_ input: String) -> (String, String)? {
let components = input.split(separator: "-")
guard components.count == 2 else { return nil }
return (String(components[0]), String(components[1]))
}
}
}
extension REST.LocationIdentifier: RawRepresentable {
public var rawValue: String { "\(country)-\(city)" }
public init?(rawValue: String) {
guard let parsed = Self.parse(rawValue) else { return nil }
(country, city) = parsed
}
}
extension REST.LocationIdentifier: ExpressibleByStringLiteral {
public init(stringLiteral value: StringLiteralType) {
guard let parsed = Self.parse(value) else {
// this is ugly, but it will only ever be called for
// code initialised from a literal in code, and
// never from real-world input, so it'll have to do.
fatalError("Invalid LocationIdentifier: \(value)")
}
(country, city) = parsed
}
}
// Allow LocationIdentifier to code to/from JSON Strings
extension REST.LocationIdentifier: Codable {
enum ParsingError: Error {
case malformed
}
public init(from decoder: any Decoder) throws {
let container = try decoder.singleValueContainer()
guard let parsed = Self.parse(try container.decode(String.self)) else {
throw ParsingError.malformed
}
(country, city) = parsed
}
public func encode(to encoder: any Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(rawValue)
}
}
// As the location's values are Substrings of the same String, to which they maintain references, we use the base String for holistic operations such as equality and hashing
extension REST.LocationIdentifier: Hashable {
public static func == (lhs: REST.LocationIdentifier, rhs: REST.LocationIdentifier) -> Bool {
lhs.rawValue == rhs.rawValue
}
public func hash(into hasher: inout Hasher) {
hasher.combine(rawValue)
}
}
|