Skip to content

Commit 31b18d5

Browse files
feature: Improves formatting requirements
1 parent ed83ec1 commit 31b18d5

File tree

12 files changed

+157
-39
lines changed

12 files changed

+157
-39
lines changed

Sources/Haystack/Coord.swift

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ public struct Coord: Val {
66
public let lat: Double
77
public let lng: Double
88

9-
public init(lat: Double, lng: Double) {
9+
public init(lat: Double, lng: Double) throws {
10+
guard -90 <= lat, lat <= 90, -180 <= lng, lng <= 180 else {
11+
throw CoordError.invalidCoordinates(lat: lat, lng: lng)
12+
}
1013
self.lat = lat
1114
self.lng = lng
1215
}
@@ -38,8 +41,10 @@ extension Coord: Codable {
3841
)
3942
}
4043

41-
self.lat = try container.decode(Double.self, forKey: .lat)
42-
self.lng = try container.decode(Double.self, forKey: .lng)
44+
try self.init(
45+
lat: container.decode(Double.self, forKey: .lat),
46+
lng: container.decode(Double.self, forKey: .lng)
47+
)
4348
} else {
4449
throw DecodingError.typeMismatch(
4550
Self.self,
@@ -58,3 +63,7 @@ extension Coord: Codable {
5863
try container.encode(lng, forKey: .lng)
5964
}
6065
}
66+
67+
public enum CoordError: Error {
68+
case invalidCoordinates(lat: Double, lng: Double)
69+
}

Sources/Haystack/IO/ZincReader.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ public class ZincReader {
104104
try consume(.comma)
105105
let longitude = try consumeNumber()
106106
try consume(.rparen)
107-
return Coord(lat: latitude.val, lng: longitude.val)
107+
return try Coord(lat: latitude.val, lng: longitude.val)
108108
}
109109

110110
private func parseXStr(_ id: String) throws -> XStr {
@@ -114,7 +114,7 @@ public class ZincReader {
114114
try consume(.lparen)
115115
let val = try consumeString()
116116
try consume(.rparen)
117-
return XStr(type: id, val: val)
117+
return try XStr(type: id, val: val)
118118
}
119119

120120
private func parseLiteral() throws -> any Val {
@@ -123,7 +123,7 @@ public class ZincReader {
123123
guard let refVal = curVal as? String, let dis = peekVal as? String else {
124124
throw ZincReaderError.InvalidRef
125125
}
126-
val = Ref(refVal, dis: dis)
126+
val = try Ref(refVal, dis: dis)
127127
try consume(.ref)
128128
}
129129
try consume()

Sources/Haystack/IO/ZincTokenizer.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ class ZincTokenizer {
291291
try consume("^")
292292
var s = ""
293293
while true {
294-
if let char = try? cur.character(), Ref.isIdChar(char) {
294+
if let char = try? cur.character(), char.isIdChar {
295295
try s.append(String(cur.character()))
296296
try consume()
297297
} else {
@@ -301,22 +301,22 @@ class ZincTokenizer {
301301
guard !s.isEmpty else {
302302
throw ZincTokenizerError.invalidEmptySymbol
303303
}
304-
self.val = Symbol(s)
304+
self.val = try Symbol(s)
305305
return .symbol
306306
}
307307

308308
private func ref() throws -> ZincToken {
309309
try consume("@")
310310
var s = ""
311311
while true {
312-
if let char = try? cur.character(), Ref.isIdChar(char) {
312+
if let char = try? cur.character(), char.isIdChar {
313313
try s.append(String(cur.character()))
314314
try consume()
315315
} else {
316316
break
317317
}
318318
}
319-
self.val = Ref(s)
319+
self.val = try Ref(s)
320320
return .ref
321321
}
322322

Sources/Haystack/Ref.swift

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,15 @@ import Foundation
33
public struct Ref: Val {
44
public static var valType: ValType { .Ref }
55

6-
static func isIdChar(_ char: Character) -> Bool {
7-
return
8-
"a" <= char && char <= "z" ||
9-
"A" <= char && char <= "Z" ||
10-
"0" <= char && char <= "9" ||
11-
char == "_" ||
12-
char == ":" ||
13-
char == "-" ||
14-
char == "." ||
15-
char == "~"
16-
}
17-
186
public let val: String
197
public let dis: String?
208

21-
public init(_ val: String, dis: String? = nil) {
9+
public init(_ val: String, dis: String? = nil) throws {
10+
for char in val {
11+
guard char.isIdChar else {
12+
throw RefError.invalidCharacterInRef(char, val)
13+
}
14+
}
2215
self.val = val
2316
self.dis = dis
2417
}
@@ -54,8 +47,10 @@ extension Ref {
5447
)
5548
}
5649

57-
self.val = try container.decode(String.self, forKey: .val)
58-
self.dis = try container.decode(String?.self, forKey: .dis)
50+
try self.init(
51+
container.decode(String.self, forKey: .val),
52+
dis: container.decode(String?.self, forKey: .dis)
53+
)
5954
} else {
6055
throw DecodingError.typeMismatch(
6156
Self.self,
@@ -74,3 +69,8 @@ extension Ref {
7469
try container.encode(dis, forKey: .dis)
7570
}
7671
}
72+
73+
public enum RefError: Error {
74+
case invalidCharacterInRef(Character, String)
75+
}
76+

Sources/Haystack/Symbol.swift

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@ public struct Symbol: Val {
55

66
public let val: String
77

8-
public init(_ val: String) {
8+
public init(_ val: String) throws {
9+
for char in val {
10+
guard char.isIdChar else {
11+
throw SymbolError.invalidCharacterInSymbol(char, val)
12+
}
13+
}
914
self.val = val
1015
}
1116

@@ -35,7 +40,7 @@ extension Symbol {
3540
)
3641
}
3742

38-
self.val = try container.decode(String.self, forKey: .val)
43+
try self.init(container.decode(String.self, forKey: .val))
3944
} else {
4045
throw DecodingError.typeMismatch(
4146
Self.self,
@@ -53,3 +58,8 @@ extension Symbol {
5358
try container.encode(val, forKey: .val)
5459
}
5560
}
61+
62+
public enum SymbolError: Error {
63+
case leadingCharacterIsNotLowerCase(String)
64+
case invalidCharacterInSymbol(Character, String)
65+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import Foundation
2+
3+
extension Character {
4+
var isIdChar: Bool {
5+
return
6+
"a" <= self && self <= "z" ||
7+
"A" <= self && self <= "Z" ||
8+
"0" <= self && self <= "9" ||
9+
self == "_" ||
10+
self == ":" ||
11+
self == "-" ||
12+
self == "." ||
13+
self == "~"
14+
}
15+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import Foundation
2+
3+
extension String {
4+
func validateTagName() throws {
5+
guard let firstChar = self.first else {
6+
throw TagNameError.cannotBeEmptyString
7+
}
8+
guard firstChar.isLowercase else {
9+
throw TagNameError.leadingCharacterIsNotLowerCase(self)
10+
}
11+
for char in self {
12+
guard char.isTagChar else {
13+
throw TagNameError.invalidCharacter(char, self)
14+
}
15+
}
16+
}
17+
}
18+
19+
extension Character {
20+
var isTagChar: Bool {
21+
return
22+
"a" <= self && self <= "z" ||
23+
"A" <= self && self <= "Z" ||
24+
"0" <= self && self <= "9" ||
25+
self == "_"
26+
}
27+
}
28+
29+
enum TagNameError: Error {
30+
case cannotBeEmptyString
31+
case leadingCharacterIsNotLowerCase(String)
32+
case invalidCharacter(Character, String)
33+
}

Sources/Haystack/XStr.swift

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ public struct XStr: Val {
66
public let type: String
77
public let val: String
88

9-
public init(type: String, val: String) {
9+
public init(type: String, val: String) throws {
10+
try type.validateXStrTypeName()
1011
self.type = type
1112
self.val = val
1213
}
@@ -38,8 +39,10 @@ extension XStr {
3839
)
3940
}
4041

41-
self.type = try container.decode(String.self, forKey: .type)
42-
self.val = try container.decode(String.self, forKey: .val)
42+
try self.init(
43+
type: container.decode(String.self, forKey: .type),
44+
val: container.decode(String.self, forKey: .val)
45+
)
4346
} else {
4447
throw DecodingError.typeMismatch(
4548
Self.self,
@@ -58,3 +61,26 @@ extension XStr {
5861
try container.encode(val, forKey: .val)
5962
}
6063
}
64+
65+
extension String {
66+
func validateXStrTypeName() throws {
67+
guard let firstChar = self.first else {
68+
throw XStrError.cannotBeEmptyString
69+
}
70+
guard firstChar.isUppercase else {
71+
throw XStrError.leadingCharacterIsNotUpperCase(self)
72+
}
73+
for char in self {
74+
guard char.isTagChar else {
75+
throw XStrError.invalidCharacter(char, self)
76+
}
77+
}
78+
}
79+
}
80+
81+
enum XStrError: Error {
82+
case cannotBeEmptyString
83+
case leadingCharacterIsNotUpperCase(String)
84+
case invalidCharacter(Character, String)
85+
}
86+

Tests/HaystackTests/CoordTests.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,15 @@ import XCTest
22
import Haystack
33

44
final class CoordTests: XCTestCase {
5+
func testInit() throws {
6+
try XCTAssertThrowsError(Coord(lat: -91, lng: 0))
7+
try XCTAssertThrowsError(Coord(lat: 91, lng: 0))
8+
try XCTAssertThrowsError(Coord(lat: 0, lng: -181))
9+
try XCTAssertThrowsError(Coord(lat: 0, lng: 181))
10+
}
11+
512
func testJsonCoding() throws {
6-
let value = Coord(lat: 40, lng: -111.84)
13+
let value = try Coord(lat: 40, lng: -111.84)
714
let jsonString = #"{"_kind":"coord","lat":40,"lng":-111.84}"#
815

916
let encodedData = try JSONEncoder().encode(value)
@@ -21,7 +28,7 @@ final class CoordTests: XCTestCase {
2128

2229
func testToZinc() throws {
2330
XCTAssertEqual(
24-
Coord(lat: 40, lng: -111.84).toZinc(),
31+
try Coord(lat: 40, lng: -111.84).toZinc(),
2532
"C(40.0,-111.84)"
2633
)
2734
}

Tests/HaystackTests/RefTests.swift

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,14 @@ import XCTest
22
import Haystack
33

44
final class RefTests: XCTestCase {
5+
func testInit() throws {
6+
try XCTAssertThrowsError(Ref("123 abc"))
7+
try XCTAssertThrowsError(Ref("123$abc"))
8+
try XCTAssertThrowsError(Ref("123%abc"))
9+
}
10+
511
func testJsonCoding() throws {
6-
let value = Ref("123-abc", dis: "Name")
12+
let value = try Ref("123-abc", dis: "Name")
713
let jsonString = #"{"_kind":"ref","val":"123-abc","dis":"Name"}"#
814

915
let encodedData = try JSONEncoder().encode(value)
@@ -21,11 +27,11 @@ final class RefTests: XCTestCase {
2127

2228
func testToZinc() throws {
2329
XCTAssertEqual(
24-
Ref("123-abc", dis: "Name").toZinc(),
30+
try Ref("123-abc", dis: "Name").toZinc(),
2531
"@123-abc Name"
2632
)
2733
XCTAssertEqual(
28-
Ref("123-abc").toZinc(),
34+
try Ref("123-abc").toZinc(),
2935
"@123-abc"
3036
)
3137
}

0 commit comments

Comments
 (0)