diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9644cf8..df8f649 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,23 +13,12 @@ jobs: fail-fast: false matrix: image: - - swift:5.2-focal - - swift:5.2-centos8 - - swift:5.3-focal - - swift:5.3-centos8 - - swift:5.4-focal - - swift:5.4-centos8 - - swift:5.5-focal - - swift:5.5-centos8 - - swift:5.6-focal -# - swift:5.7-focal -# - swift:5.8-focal -# - swift:5.9-focal -# - swift:5.10-focal + - swift:6.0 + - swift:6.1 container: ${{ matrix.image }} steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v5 - name: Run tests run: swift test --enable-test-discovery osx: @@ -39,6 +28,6 @@ jobs: uses: maxim-lobanov/setup-xcode@v1 with: { 'xcode-version': 'latest' } - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v5 - name: Run tests run: swift test --enable-test-discovery diff --git a/JSONAPI.podspec b/JSONAPI.podspec deleted file mode 100644 index 4a22764..0000000 --- a/JSONAPI.podspec +++ /dev/null @@ -1,141 +0,0 @@ -# -# Be sure to run `pod spec lint JSONAPI.podspec' to ensure this is a -# valid spec and to remove all comments including this before submitting the spec. -# -# To learn more about Podspec attributes see https://docs.cocoapods.org/specification.html -# To see working Podspecs in the CocoaPods repo see https://github.com/CocoaPods/Specs/ -# - -Pod::Spec.new do |spec| - - # ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # - # - # These will help people to find your library, and whilst it - # can feel like a chore to fill in it's definitely to your advantage. The - # summary should be tweet-length, and the description more in depth. - # - - spec.name = "MP-JSONAPI" - spec.version = "5.1.0" - spec.summary = "Swift Codable JSON API framework." - - # This description is used to generate tags and improve search results. - # * Think: What does it do? Why did you write it? What is the focus? - # * Try to keep it short, snappy and to the point. - # * Write the description between the DESC delimiters below. - # * Finally, don't worry about the indent, CocoaPods strips it! - spec.description = <<-DESC - A Swift package for encoding to- and decoding from JSON API compliant requests and responses. - -See the JSON API Spec here: https://jsonapi.org/format/ - DESC - - spec.homepage = "https://github.com/mattpolzin/JSONAPI" - # spec.screenshots = "www.example.com/screenshots_1.gif", "www.example.com/screenshots_2.gif" - - - # ――― Spec License ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # - # - # Licensing your code is important. See https://choosealicense.com for more info. - # CocoaPods will detect a license file if there is a named LICENSE* - # Popular ones are 'MIT', 'BSD' and 'Apache License, Version 2.0'. - # - - # spec.license = "MIT" - spec.license = { :type => "MIT", :file => "LICENSE.txt" } - - - # ――― Author Metadata ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # - # - # Specify the authors of the library, with email addresses. Email addresses - # of the authors are extracted from the SCM log. E.g. $ git log. CocoaPods also - # accepts just a name if you'd rather not provide an email address. - # - # Specify a social_media_url where others can refer to, for example a twitter - # profile URL. - # - - spec.author = { "Mathew Polzin" => "matt.polzin@gmail.com" } - # Or just: spec.author = "Mathew Polzin" - # spec.authors = { "Mathew Polzin" => "matt.polzin@gmail.com" } - # spec.social_media_url = "https://twitter.com/Mathew Polzin" - - # ――― Platform Specifics ――――――――――――――――――――――――――――――――――――――――――――――――――――――― # - # - # If this Pod runs only on iOS or OS X, then specify the platform and - # the deployment target. You can optionally include the target after the platform. - # - - # spec.platform = :ios - # spec.platform = :ios, "5.0" - - # When using multiple platforms - spec.ios.deployment_target = "8.0" - spec.osx.deployment_target = "10.9" - # spec.watchos.deployment_target = "2.0" - # spec.tvos.deployment_target = "9.0" - - - # ――― Source Location ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # - # - # Specify the location from where the source should be retrieved. - # Supports git, hg, bzr, svn and HTTP. - # - - spec.source = { :git => "https://github.com/mattpolzin/JSONAPI.git", :tag => "#{spec.version}" } - - - # ――― Source Code ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # - # - # CocoaPods is smart about how it includes source code. For source files - # giving a folder will include any swift, h, m, mm, c & cpp files. - # For header files it will include any header in the folder. - # Not including the public_header_files will make all headers public. - # - - spec.source_files = "Sources/JSONAPI/**/*.{swift}" - # spec.exclude_files = "Classes/Exclude" - - # spec.public_header_files = "Classes/**/*.h" - - - # ――― Resources ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # - # - # A list of resources included with the Pod. These are copied into the - # target bundle with a build phase script. Anything else will be cleaned. - # You can preserve files from being cleaned, please don't preserve - # non-essential files like tests, examples and documentation. - # - - # spec.resource = "icon.png" - # spec.resources = "Resources/*.png" - - # spec.preserve_paths = "FilesToSave", "MoreFilesToSave" - - - # ――― Project Linking ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # - # - # Link your library with frameworks, or libraries. Libraries do not include - # the lib prefix of their name. - # - - # spec.frameworks = "SomeFramework", "AnotherFramework" - - # spec.library = "iconv" - # spec.libraries = "iconv", "xml2" - - - # ――― Project Settings ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # - # - # If your library depends on compiler flags you can set them in the xcconfig hash - # where they will only apply to your library. If you depend on other Podspecs - # you can include multiple dependencies to ensure it works. - - spec.swift_version = "5.2" - spec.module_name = "JSONAPI" - # spec.requires_arc = true - - # spec.xcconfig = { "HEADER_SEARCH_PATHS" => "$(SDKROOT)/usr/include/libxml2" } - spec.dependency "Poly", "~> 2.6" - -end diff --git a/Package.resolved b/Package.resolved index f7c521e..f0e1825 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,16 +1,15 @@ { - "object": { - "pins": [ - { - "package": "Poly", - "repositoryURL": "https://github.com/mattpolzin/Poly.git", - "state": { - "branch": null, - "revision": "99e2e8b575620369be52fe348c0dd72028e3674c", - "version": "2.8.0" - } + "originHash" : "54aae326cbf0090b80ab7860957be502f5396744f5579cf522be7cb3ffa67caf", + "pins" : [ + { + "identity" : "poly", + "kind" : "remoteSourceControl", + "location" : "https://github.com/mattpolzin/Poly.git", + "state" : { + "revision" : "37c942daa23ab373ce05ef87f42103342e59cf3a", + "version" : "3.0.0" } - ] - }, - "version": 1 + } + ], + "version" : 3 } diff --git a/Package.swift b/Package.swift index 81b978c..642091f 100644 --- a/Package.swift +++ b/Package.swift @@ -1,12 +1,12 @@ -// swift-tools-version:5.2 +// swift-tools-version:6.0 import PackageDescription let package = Package( name: "JSONAPI", platforms: [ - .macOS(.v10_10), - .iOS(.v10) + .macOS(.v10_15), + .iOS(.v13) ], products: [ .library( @@ -17,7 +17,7 @@ let package = Package( targets: ["JSONAPITesting"]) ], dependencies: [ - .package(url: "https://github.com/mattpolzin/Poly.git", .upToNextMajor(from: "2.8.0")), + .package(url: "https://github.com/mattpolzin/Poly.git", .upToNextMajor(from: "3.0.0")), ], targets: [ .target( @@ -33,5 +33,5 @@ let package = Package( name: "JSONAPITestingTests", dependencies: ["JSONAPI", "JSONAPITesting"]) ], - swiftLanguageVersions: [.v5] + swiftLanguageModes: [.v5, .v6] ) diff --git a/README.md b/README.md index 67946cd..873a186 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # JSONAPI -[![MIT license](http://img.shields.io/badge/license-MIT-lightgrey.svg)](http://opensource.org/licenses/MIT) [![Swift 5.2+](http://img.shields.io/badge/Swift-5.2+-blue.svg)](https://swift.org) [![Tests](https://github.com/mattpolzin/JSONAPI/actions/workflows/tests.yml/badge.svg)](https://github.com/mattpolzin/JSONAPI/actions/workflows/tests.yml) +[![MIT license](http://img.shields.io/badge/license-MIT-lightgrey.svg)](http://opensource.org/licenses/MIT) [![Swift 6.0+](http://img.shields.io/badge/Swift-6.0+-blue.svg)](https://swift.org) [![Tests](https://github.com/mattpolzin/JSONAPI/actions/workflows/tests.yml/badge.svg)](https://github.com/mattpolzin/JSONAPI/actions/workflows/tests.yml) A Swift package for encoding to- and decoding from **JSON API** compliant requests and responses. @@ -58,35 +58,15 @@ If you find something wrong with this library and it isn't already mentioned und ## Dev Environment ### Prerequisites -1. Swift 5.2+ -2. Swift Package Manager, Xcode 11+, or Cocoapods +1. Swift 6.0+ +2. Swift Package Manager ### Swift Package Manager Just include the following in your package's dependencies and add `JSONAPI` to the dependencies for any of your targets. ```swift -.package(url: "https://github.com/mattpolzin/JSONAPI.git", from: "5.1.0") +.package(url: "https://github.com/mattpolzin/JSONAPI.git", from: "6.0.0") ``` -### Xcode project -With Xcode 11+, you can open the folder containing this repository. There is no need for an Xcode project, but you can generate one with `swift package generate-xcodeproj`. - -### CocoaPods -To use this framework in your project via Cocoapods, add the following dependencies to your Podfile. -```ruby -pod 'Poly', :git => 'https://github.com/mattpolzin/Poly.git' -pod 'MP-JSONAPI', :git => 'https://github.com/mattpolzin/JSONAPI.git' -``` - -### Carthage -This library does not support the Carthage package manager. This is intentional to avoid an additional dependency on Xcode and the Xcode's project files as their format changes throughout versions (in addition to the complexity of maintaining different shared schemes for each supported operating system). - -The difference between supporting and not supporting Carthage is the difference between maintaining an Xcode project with at least one shared build scheme; I encourage those that need Carthage support to fork this repository and add support to their fork by committing an Xcode project (you can generate one as described in the [Xcode project](#xcode-project) section above). Once an Xcode project is generated, you need to mark at least one scheme as [shared](https://github.com/Carthage/Carthage#share-your-xcode-schemes). - -### Running the Playground -To run the included Playground files, create an Xcode project using Swift Package Manager, then create an Xcode Workspace in the root of the repository and add both the generated Xcode project and the playground to the Workspace. - -Note that Playground support for importing non-system Frameworks is still a bit touchy as of Swift 4.2. Sometimes building, cleaning and building, or commenting out and then uncommenting import statements (especially in the` Entities.swift` Playground Source file) can get things working for me when I am getting an error about `JSONAPI` not being found. - ## Deeper Dive - [Project Status](./documentation/project-status.md) - [Server & Client Example](./documentation/examples/client-server-example.md) diff --git a/Sources/JSONAPI/Document/APIDescription.swift b/Sources/JSONAPI/Document/APIDescription.swift index cb417a6..123d056 100644 --- a/Sources/JSONAPI/Document/APIDescription.swift +++ b/Sources/JSONAPI/Document/APIDescription.swift @@ -21,6 +21,8 @@ public struct APIDescription: APIDescriptionType { } } +extension APIDescription: Sendable where Meta: Sendable {} + /// Can be used as `APIDescriptionType` for Documents that do not /// have any API Description (a.k.a. "JSON:API Object"). public struct NoAPIDescription: APIDescriptionType, CustomStringConvertible { @@ -33,6 +35,8 @@ public struct NoAPIDescription: APIDescriptionType, CustomStringConvertible { public var description: String { return "No JSON:API Object" } } +extension NoAPIDescription: Sendable {} + extension APIDescription { private enum CodingKeys: String, CodingKey { case version diff --git a/Sources/JSONAPI/Document/Document.swift b/Sources/JSONAPI/Document/Document.swift index 3673493..402cc6e 100644 --- a/Sources/JSONAPI/Document/Document.swift +++ b/Sources/JSONAPI/Document/Document.swift @@ -186,6 +186,10 @@ public struct Document(_ other: Document.Body.Data, combiningMetaWith metaMerge: (MetaType, MetaType) -> MetaType, diff --git a/Sources/JSONAPI/Document/Includes.swift b/Sources/JSONAPI/Document/Includes.swift index b76225e..8eb5000 100644 --- a/Sources/JSONAPI/Document/Includes.swift +++ b/Sources/JSONAPI/Document/Includes.swift @@ -45,6 +45,8 @@ public struct Includes: Encodable, Equatable { } } +extension Includes: Sendable where I: Sendable {} + extension Includes: Decodable where I: Decodable { public init(from decoder: Decoder) throws { var container = try decoder.unkeyedContainer() diff --git a/Sources/JSONAPI/Document/ResourceBody.swift b/Sources/JSONAPI/Document/ResourceBody.swift index 0986a4f..88b8d44 100644 --- a/Sources/JSONAPI/Document/ResourceBody.swift +++ b/Sources/JSONAPI/Document/ResourceBody.swift @@ -65,6 +65,8 @@ public struct SingleResourceBody: Codable, Eq self.source = source } - public struct Source: Codable, Equatable { + public struct Source: Codable, Equatable, Sendable { /// a JSON Pointer [RFC6901] to the associated entity in the request document [e.g. "/data" for a primary data object, or "/data/attributes/title" for a specific attribute]. public let pointer: String? /// which URI query parameter caused the error @@ -70,6 +70,8 @@ public struct BasicJSONAPIErrorPayload: Codable, Eq } } +extension BasicJSONAPIErrorPayload: Sendable where IdType: Sendable {} + /// `BasicJSONAPIError` optionally decodes many possible fields /// specified by the JSON:API 1.0 Spec. It gives no type-guarantees of what /// will be non-nil, but could provide good diagnostic information when @@ -100,4 +102,4 @@ public struct BasicJSONAPIErrorPayload: Codable, Eq /// with non-nil values in a flattened way. There will be no `source` key /// but there will be `pointer` and `parameter` keys (if those values /// are non-nil). -public typealias BasicJSONAPIError = GenericJSONAPIError> +public typealias BasicJSONAPIError = GenericJSONAPIError> diff --git a/Sources/JSONAPI/Error/GenericJSONAPIError.swift b/Sources/JSONAPI/Error/GenericJSONAPIError.swift index 473caac..457e4f7 100644 --- a/Sources/JSONAPI/Error/GenericJSONAPIError.swift +++ b/Sources/JSONAPI/Error/GenericJSONAPIError.swift @@ -8,7 +8,7 @@ /// `GenericJSONAPIError` can be used to specify whatever error /// payload you expect to need to parse in responses and handle any /// other payload structure as `.unknownError`. -public enum GenericJSONAPIError: JSONAPIError, CustomStringConvertible { +public enum GenericJSONAPIError: JSONAPIError, CustomStringConvertible { case unknownError case error(ErrorPayload) diff --git a/Sources/JSONAPI/JSONAPICodingError.swift b/Sources/JSONAPI/JSONAPICodingError.swift index da0705d..8cf889b 100644 --- a/Sources/JSONAPI/JSONAPICodingError.swift +++ b/Sources/JSONAPI/JSONAPICodingError.swift @@ -13,7 +13,7 @@ public enum JSONAPICodingError: Swift.Error { case missingOrMalformedMetadata(path: [CodingKey]) case missingOrMalformedLinks(path: [CodingKey]) - public enum Quantity: String, Equatable { + public enum Quantity: String, Equatable, Sendable { case one case many diff --git a/Sources/JSONAPI/Meta/Links.swift b/Sources/JSONAPI/Meta/Links.swift index ab2b7e1..bbe7335 100644 --- a/Sources/JSONAPI/Meta/Links.swift +++ b/Sources/JSONAPI/Meta/Links.swift @@ -9,7 +9,7 @@ public protocol Links: Codable, Equatable {} /// Use NoLinks where no links should belong to a JSON API component -public struct NoLinks: Links, CustomStringConvertible { +public struct NoLinks: Links, CustomStringConvertible, Sendable { public static var none: NoLinks { return NoLinks() } public init() {} @@ -28,6 +28,8 @@ public struct Link: Equatable, Coda } } +extension Link: Sendable where Meta: Sendable, URL: Sendable {} + extension Link where Meta == NoMetadata { public init(url: URL) { self.init(url: url, meta: .none) diff --git a/Sources/JSONAPI/Meta/Meta.swift b/Sources/JSONAPI/Meta/Meta.swift index 7018cd6..51aec42 100644 --- a/Sources/JSONAPI/Meta/Meta.swift +++ b/Sources/JSONAPI/Meta/Meta.swift @@ -21,7 +21,7 @@ extension Optional: Meta where Wrapped: Meta {} /// Use this type when you want to specify not to encode or decode any metadata /// for a type. -public struct NoMetadata: Meta, CustomStringConvertible { +public struct NoMetadata: Meta, CustomStringConvertible, Sendable { public static var none: NoMetadata { return NoMetadata() } public init() { } diff --git a/Sources/JSONAPI/Resource/Attribute.swift b/Sources/JSONAPI/Resource/Attribute.swift index 5b0ed41..e39b884 100644 --- a/Sources/JSONAPI/Resource/Attribute.swift +++ b/Sources/JSONAPI/Resource/Attribute.swift @@ -34,6 +34,8 @@ public struct TransformedAttribute { // If we are using the identity transform, we can skip the transform and guarantee no // error is thrown. @@ -82,6 +84,8 @@ public struct Attribute: AttributeType { } } +extension Attribute: Sendable where RawValue: Sendable {} + extension Attribute: CustomStringConvertible { public var description: String { return "Attribute<\(String(describing: RawValue.self))>(\(String(describing: value)))" diff --git a/Sources/JSONAPI/Resource/Id.swift b/Sources/JSONAPI/Resource/Id.swift index 2fc4bac..d8a727c 100644 --- a/Sources/JSONAPI/Resource/Id.swift +++ b/Sources/JSONAPI/Resource/Id.swift @@ -31,7 +31,7 @@ extension String: RawIdType {} /// A type that can be used as the `MaybeRawId` for a `ResourceObject` that does not /// have an Id (most likely because it was created by a client and the server will be responsible /// for assigning it an Id). -public struct Unidentified: MaybeRawId, CustomStringConvertible { +public struct Unidentified: MaybeRawId, CustomStringConvertible, Sendable { public init() {} public var description: String { return "Unidentified" } @@ -97,6 +97,8 @@ public struct Id: Equa } } +extension Id: Sendable where RawType: Sendable {} + extension Id: Hashable where RawType: RawIdType { public func hash(into hasher: inout Hasher) { hasher.combine(ObjectIdentifier(Self.self)) diff --git a/Sources/JSONAPI/Resource/Poly+PrimaryResource.swift b/Sources/JSONAPI/Resource/Poly+PrimaryResource.swift index 1443610..ef2a340 100644 --- a/Sources/JSONAPI/Resource/Poly+PrimaryResource.swift +++ b/Sources/JSONAPI/Resource/Poly+PrimaryResource.swift @@ -20,7 +20,7 @@ public typealias EncodableJSONPoly = Poly & EncodablePrimaryResource public typealias EncodablePolyWrapped = Encodable & Equatable public typealias CodablePolyWrapped = EncodablePolyWrapped & Decodable -extension Poly0: CodablePrimaryResource { +extension Poly0: @retroactive Encodable, @retroactive Decodable { public init(from decoder: Decoder) throws { throw JSONAPICodingError.illegalDecoding("Attempted to decode Poly0, which should represent a thing that is not expected to be found in a document.", path: decoder.codingPath) } @@ -30,6 +30,8 @@ extension Poly0: CodablePrimaryResource { } } +extension Poly0: CodablePrimaryResource {} + // MARK: - 1 type extension Poly1: EncodablePrimaryResource, OptionalEncodablePrimaryResource where A: EncodablePolyWrapped {} diff --git a/Sources/JSONAPI/Resource/Relationship.swift b/Sources/JSONAPI/Resource/Relationship.swift index 4a2c3fa..bccf453 100644 --- a/Sources/JSONAPI/Resource/Relationship.swift +++ b/Sources/JSONAPI/Resource/Relationship.swift @@ -60,6 +60,12 @@ public struct ToOneRelationship, NoMetadata, NoLinks, Include2, NoAPIDescription, BasicJSONAPIError> -fileprivate struct TestMetadata: JSONAPI.Meta, CustomStringConvertible { +fileprivate struct TestMetadata: JSONAPI.Meta, CustomStringConvertible, Sendable { let total: Int var description: String { @@ -151,7 +151,7 @@ fileprivate struct TestMetadata: JSONAPI.Meta, CustomStringConvertible { } } -fileprivate struct TestLinks: JSONAPI.Links { +fileprivate struct TestLinks: JSONAPI.Links, Sendable { let link: Link } diff --git a/Tests/JSONAPITestingTests/Comparisons/IncludesCompareTests.swift b/Tests/JSONAPITestingTests/Comparisons/IncludesCompareTests.swift index b25dd11..bd5ca90 100644 --- a/Tests/JSONAPITestingTests/Comparisons/IncludesCompareTests.swift +++ b/Tests/JSONAPITestingTests/Comparisons/IncludesCompareTests.swift @@ -224,13 +224,13 @@ private typealias TestType1 = ResourceObject let age: Attribute let favoriteColor: Attribute } - struct Relationships: JSONAPI.Relationships { + struct Relationships: JSONAPI.Relationships & Sendable { let bestFriend: ToOneRelationship let parents: ToManyRelationship } diff --git a/Tests/JSONAPITestingTests/Test Helpers/String+CreatableRawIdType.swift b/Tests/JSONAPITestingTests/Test Helpers/String+CreatableRawIdType.swift index dd3c8f7..282325a 100644 --- a/Tests/JSONAPITestingTests/Test Helpers/String+CreatableRawIdType.swift +++ b/Tests/JSONAPITestingTests/Test Helpers/String+CreatableRawIdType.swift @@ -7,7 +7,7 @@ import JSONAPI -private var uniqueStringCounter = 0 +nonisolated(unsafe) private var uniqueStringCounter = 0 extension String: CreatableRawIdType { public static func unique() -> String { diff --git a/Tests/JSONAPITests/Document/DocumentDecodingErrorTests.swift b/Tests/JSONAPITests/Document/DocumentDecodingErrorTests.swift index 0100a07..8783794 100644 --- a/Tests/JSONAPITests/Document/DocumentDecodingErrorTests.swift +++ b/Tests/JSONAPITests/Document/DocumentDecodingErrorTests.swift @@ -224,7 +224,7 @@ extension DocumentDecodingErrorTests { case unknownError case basic(BasicError) - struct BasicError: Codable, Equatable { + struct BasicError: Codable, Equatable, Sendable { let code: Int let description: String } diff --git a/Tests/JSONAPITests/Error/GenericJSONAPIErrorTests.swift b/Tests/JSONAPITests/Error/GenericJSONAPIErrorTests.swift index d645c23..6f095cc 100644 --- a/Tests/JSONAPITests/Error/GenericJSONAPIErrorTests.swift +++ b/Tests/JSONAPITests/Error/GenericJSONAPIErrorTests.swift @@ -132,7 +132,7 @@ final class GenericJSONAPIErrorTests: XCTestCase { } } -private struct TestPayload: Codable, Equatable, ErrorDictType { +private struct TestPayload: Codable, Equatable, ErrorDictType, Sendable { let hello: String let world: Int? diff --git a/Tests/JSONAPITests/Test Helpers/String+CreatableRawIdType.swift b/Tests/JSONAPITests/Test Helpers/String+CreatableRawIdType.swift index dd3c8f7..282325a 100644 --- a/Tests/JSONAPITests/Test Helpers/String+CreatableRawIdType.swift +++ b/Tests/JSONAPITests/Test Helpers/String+CreatableRawIdType.swift @@ -7,7 +7,7 @@ import JSONAPI -private var uniqueStringCounter = 0 +nonisolated(unsafe) private var uniqueStringCounter = 0 extension String: CreatableRawIdType { public static func unique() -> String {