Skip to content

Commit 7756de5

Browse files
authored
Add nested PackgeIdentity.Scope and PackageIdentity.Name types (#3658)
* Add nested PackgeIdentity.Scope and PackageIdentity.Name types
1 parent aa2063f commit 7756de5

File tree

3 files changed

+308
-0
lines changed

3 files changed

+308
-0
lines changed

Sources/PackageModel/PackageIdentity.swift

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,163 @@ extension PackageIdentity: JSONMappable, JSONSerializable {
9494

9595
// MARK: -
9696

97+
extension PackageIdentity {
98+
/// Provides a namespace for related packages within a package registry.
99+
public struct Scope: LosslessStringConvertible, Hashable, Equatable, Comparable, ExpressibleByStringLiteral {
100+
public let description: String
101+
102+
public init(validating description: String) throws {
103+
guard !description.isEmpty else {
104+
throw StringError("The minimum length of a package scope is 1 character.")
105+
}
106+
107+
guard description.count <= 39 else {
108+
throw StringError("The maximum length of a package scope is 39 characters.")
109+
}
110+
111+
for (index, character) in zip(description.indices, description) {
112+
guard character.isASCII,
113+
character.isLetter ||
114+
character.isNumber ||
115+
character == "-"
116+
else {
117+
throw StringError("A package scope consists of alphanumeric characters and hyphens.")
118+
}
119+
120+
if character.isPunctuation {
121+
switch (index, description.index(after: index)) {
122+
case (description.startIndex, _):
123+
throw StringError("Hyphens may not occur at the beginning of a scope.")
124+
case (_, description.endIndex):
125+
throw StringError("Hyphens may not occur at the end of a scope.")
126+
case (_, let nextIndex) where description[nextIndex].isPunctuation:
127+
throw StringError("Hyphens may not occur consecutively within a scope.")
128+
default:
129+
continue
130+
}
131+
}
132+
}
133+
134+
self.description = description
135+
}
136+
137+
public init?(_ description: String) {
138+
guard let scope = try? Scope(validating: description) else { return nil }
139+
self = scope
140+
}
141+
142+
// MARK: - Equatable & Comparable
143+
144+
private func compare(to other: Scope) -> ComparisonResult {
145+
// Package scopes are case-insensitive (for example, `mona` ≍ `MONA`).
146+
return self.description.caseInsensitiveCompare(other.description)
147+
}
148+
149+
public static func == (lhs: Scope, rhs: Scope) -> Bool {
150+
return lhs.compare(to: rhs) == .orderedSame
151+
}
152+
153+
public static func < (lhs: Scope, rhs: Scope) -> Bool {
154+
return lhs.compare(to: rhs) == .orderedAscending
155+
}
156+
157+
public static func > (lhs: Scope, rhs: Scope) -> Bool {
158+
return lhs.compare(to: rhs) == .orderedDescending
159+
}
160+
161+
// MARK: - Hashable
162+
163+
public func hash(into hasher: inout Hasher) {
164+
hasher.combine(description.lowercased())
165+
}
166+
167+
// MARK: - ExpressibleByStringLiteral
168+
169+
public init(stringLiteral value: StringLiteralType) {
170+
try! self.init(validating: value)
171+
}
172+
}
173+
174+
/// Uniquely identifies a package in a scope
175+
public struct Name: LosslessStringConvertible, Hashable, Equatable, Comparable, ExpressibleByStringLiteral {
176+
public let description: String
177+
178+
public init(validating description: String) throws {
179+
guard !description.isEmpty else {
180+
throw StringError("The minimum length of a package name is 1 character.")
181+
}
182+
183+
guard description.count <= 100 else {
184+
throw StringError("The maximum length of a package name is 100 characters.")
185+
}
186+
187+
for (index, character) in zip(description.indices, description) {
188+
guard character.isASCII,
189+
character.isLetter ||
190+
character.isNumber ||
191+
character == "-" ||
192+
character == "_"
193+
else {
194+
throw StringError("A package name consists of alphanumeric characters, underscores, and hyphens.")
195+
}
196+
197+
if character.isPunctuation {
198+
switch (index, description.index(after: index)) {
199+
case (description.startIndex, _):
200+
throw StringError("Hyphens and underscores may not occur at the beginning of a name.")
201+
case (_, description.endIndex):
202+
throw StringError("Hyphens and underscores may not occur at the end of a name.")
203+
case (_, let nextIndex) where description[nextIndex].isPunctuation:
204+
throw StringError("Hyphens and underscores may not occur consecutively within a name.")
205+
default:
206+
continue
207+
}
208+
}
209+
}
210+
211+
self.description = description
212+
}
213+
214+
public init?(_ description: String) {
215+
guard let name = try? Name(validating: description) else { return nil }
216+
self = name
217+
}
218+
219+
// MARK: - Equatable & Comparable
220+
221+
private func compare(to other: Name) -> ComparisonResult {
222+
// Package scopes are case-insensitive (for example, `LinkedList` ≍ `LINKEDLIST`).
223+
return self.description.caseInsensitiveCompare(other.description)
224+
}
225+
226+
public static func == (lhs: Name, rhs: Name) -> Bool {
227+
return lhs.compare(to: rhs) == .orderedSame
228+
}
229+
230+
public static func < (lhs: Name, rhs: Name) -> Bool {
231+
return lhs.compare(to: rhs) == .orderedAscending
232+
}
233+
234+
public static func > (lhs: Name, rhs: Name) -> Bool {
235+
return lhs.compare(to: rhs) == .orderedDescending
236+
}
237+
238+
// MARK: - Hashable
239+
240+
public func hash(into hasher: inout Hasher) {
241+
hasher.combine(description.lowercased())
242+
}
243+
244+
// MARK: - ExpressibleByStringLiteral
245+
246+
public init(stringLiteral value: StringLiteralType) {
247+
try! self.init(validating: value)
248+
}
249+
}
250+
}
251+
252+
// MARK: -
253+
97254
struct LegacyPackageIdentity: PackageIdentityProvider, Equatable {
98255
/// A textual representation of this instance.
99256
public let description: String
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
This source file is part of the Swift.org open source project
3+
4+
Copyright (c) 2021 Apple Inc. and the Swift project authors
5+
Licensed under Apache License v2.0 with Runtime Library Exception
6+
7+
See http://swift.org/LICENSE.txt for license information
8+
See http://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
*/
10+
11+
import XCTest
12+
import Basics
13+
import TSCBasic
14+
import PackageModel
15+
16+
class PackageIdentityNameTests: XCTestCase {
17+
func testValidNames() throws {
18+
XCTAssertNoThrow(try PackageIdentity.Name(validating: "LinkedList"))
19+
XCTAssertNoThrow(try PackageIdentity.Name(validating: "Linked-List"))
20+
XCTAssertNoThrow(try PackageIdentity.Name(validating: "Linked_List"))
21+
XCTAssertNoThrow(try PackageIdentity.Name(validating: "A"))
22+
XCTAssertNoThrow(try PackageIdentity.Name(validating: "1"))
23+
XCTAssertNoThrow(try PackageIdentity.Name(validating: String(repeating: "A", count: 100)))
24+
}
25+
26+
func testInvalidNames() throws {
27+
XCTAssertThrowsError(try PackageIdentity.Name(validating: "")) { error in
28+
XCTAssertEqual(error.localizedDescription, "The minimum length of a package name is 1 character.")
29+
}
30+
31+
XCTAssertThrowsError(try PackageIdentity.Name(validating: String(repeating: "a", count: 101))) { error in
32+
XCTAssertEqual(error.localizedDescription, "The maximum length of a package name is 100 characters.")
33+
}
34+
35+
XCTAssertThrowsError(try PackageIdentity.Name(validating: "!")) { error in
36+
XCTAssertEqual(error.localizedDescription, "A package name consists of alphanumeric characters, underscores, and hyphens.")
37+
}
38+
39+
XCTAssertThrowsError(try PackageIdentity.Name(validating: "")) { error in
40+
XCTAssertEqual(error.localizedDescription, "A package name consists of alphanumeric characters, underscores, and hyphens.")
41+
}
42+
43+
XCTAssertThrowsError(try PackageIdentity.Name(validating: "🧍")) { error in
44+
XCTAssertEqual(error.localizedDescription, "A package name consists of alphanumeric characters, underscores, and hyphens.")
45+
}
46+
47+
XCTAssertThrowsError(try PackageIdentity.Name(validating: "-a")) { error in
48+
XCTAssertEqual(error.localizedDescription, "Hyphens and underscores may not occur at the beginning of a name.")
49+
}
50+
51+
XCTAssertThrowsError(try PackageIdentity.Name(validating: "_a")) { error in
52+
XCTAssertEqual(error.localizedDescription, "Hyphens and underscores may not occur at the beginning of a name.")
53+
}
54+
55+
XCTAssertThrowsError(try PackageIdentity.Name(validating: "a-")) { error in
56+
XCTAssertEqual(error.localizedDescription, "Hyphens and underscores may not occur at the end of a name.")
57+
}
58+
59+
XCTAssertThrowsError(try PackageIdentity.Name(validating: "a_")) { error in
60+
XCTAssertEqual(error.localizedDescription, "Hyphens and underscores may not occur at the end of a name.")
61+
}
62+
63+
XCTAssertThrowsError(try PackageIdentity.Name(validating: "a_-a")) { error in
64+
XCTAssertEqual(error.localizedDescription, "Hyphens and underscores may not occur consecutively within a name.")
65+
}
66+
67+
XCTAssertThrowsError(try PackageIdentity.Name(validating: "a-_a")) { error in
68+
XCTAssertEqual(error.localizedDescription, "Hyphens and underscores may not occur consecutively within a name.")
69+
}
70+
71+
XCTAssertThrowsError(try PackageIdentity.Name(validating: "a--a")) { error in
72+
XCTAssertEqual(error.localizedDescription, "Hyphens and underscores may not occur consecutively within a name.")
73+
}
74+
75+
XCTAssertThrowsError(try PackageIdentity.Name(validating: "a__a")) { error in
76+
XCTAssertEqual(error.localizedDescription, "Hyphens and underscores may not occur consecutively within a name.")
77+
}
78+
}
79+
80+
func testNamesAreCaseInsensitive() throws {
81+
let lowercase: PackageIdentity.Name = "linkedlist"
82+
let uppercase: PackageIdentity.Name = "LINKEDLIST"
83+
84+
XCTAssertEqual(lowercase, uppercase)
85+
}
86+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
This source file is part of the Swift.org open source project
3+
4+
Copyright (c) 2021 Apple Inc. and the Swift project authors
5+
Licensed under Apache License v2.0 with Runtime Library Exception
6+
7+
See http://swift.org/LICENSE.txt for license information
8+
See http://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
*/
10+
11+
import XCTest
12+
import Basics
13+
import TSCBasic
14+
import PackageModel
15+
16+
class PackageIdentityScopeTests: XCTestCase {
17+
func testValidScopes() throws {
18+
XCTAssertNoThrow(try PackageIdentity.Scope(validating: "mona"))
19+
XCTAssertNoThrow(try PackageIdentity.Scope(validating: "m-o-n-a"))
20+
XCTAssertNoThrow(try PackageIdentity.Scope(validating: "a"))
21+
XCTAssertNoThrow(try PackageIdentity.Scope(validating: "1"))
22+
XCTAssertNoThrow(try PackageIdentity.Scope(validating: String(repeating: "a", count: 39)))
23+
}
24+
25+
func testInvalidScopes() throws {
26+
XCTAssertThrowsError(try PackageIdentity.Scope(validating: "")) { error in
27+
XCTAssertEqual(error.localizedDescription, "The minimum length of a package scope is 1 character.")
28+
}
29+
30+
XCTAssertThrowsError(try PackageIdentity.Scope(validating: String(repeating: "a", count: 100))) { error in
31+
XCTAssertEqual(error.localizedDescription, "The maximum length of a package scope is 39 characters.")
32+
}
33+
34+
XCTAssertThrowsError(try PackageIdentity.Scope(validating: "!")) { error in
35+
XCTAssertEqual(error.localizedDescription, "A package scope consists of alphanumeric characters and hyphens.")
36+
}
37+
38+
XCTAssertThrowsError(try PackageIdentity.Scope(validating: "")) { error in
39+
XCTAssertEqual(error.localizedDescription, "A package scope consists of alphanumeric characters and hyphens.")
40+
}
41+
42+
XCTAssertThrowsError(try PackageIdentity.Scope(validating: "🧍")) { error in
43+
XCTAssertEqual(error.localizedDescription, "A package scope consists of alphanumeric characters and hyphens.")
44+
}
45+
46+
XCTAssertThrowsError(try PackageIdentity.Scope(validating: "-a")) { error in
47+
XCTAssertEqual(error.localizedDescription, "Hyphens may not occur at the beginning of a scope.")
48+
}
49+
50+
XCTAssertThrowsError(try PackageIdentity.Scope(validating: "a-")) { error in
51+
XCTAssertEqual(error.localizedDescription, "Hyphens may not occur at the end of a scope.")
52+
}
53+
54+
XCTAssertThrowsError(try PackageIdentity.Scope(validating: "a--a")) { error in
55+
XCTAssertEqual(error.localizedDescription, "Hyphens may not occur consecutively within a scope.")
56+
}
57+
}
58+
59+
func testScopesAreCaseInsensitive() throws {
60+
let lowercase: PackageIdentity.Scope = "mona"
61+
let uppercase: PackageIdentity.Scope = "MONA"
62+
63+
XCTAssertEqual(lowercase, uppercase)
64+
}
65+
}

0 commit comments

Comments
 (0)