Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ opt_in_rules:
- sorted_first_last
- sorted_imports
- static_operator
- strict_fileprivate
- strong_iboutlet
- toggle_bool
- trailing_closure
Expand Down
2 changes: 1 addition & 1 deletion Scripts/lint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ pushd $PACKAGE_DIR

if [ -z "$CI" ]; then
$MINT_RUN swiftformat .
$MINT_RUN swiftlint autocorrect
$MINT_RUN swiftlint --fix
fi

$MINT_RUN swiftformat --lint $SWIFTFORMAT_OPTIONS .
Expand Down
44 changes: 44 additions & 0 deletions Sources/Options/CodingOptions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//
// CodingOptions.swift
// SimulatorServices
//
// Created by Leo Dion.
// Copyright © 2024 BrightDigit.
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the “Software”), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//

import Foundation

public struct CodingOptions: OptionSet, Sendable {
public static let allowMappedValueDecoding: CodingOptions = .init(rawValue: 1)
public static let encodeAsMappedValue: CodingOptions = .init(rawValue: 2)

public static let `default`: CodingOptions =
[.allowMappedValueDecoding, encodeAsMappedValue]

public let rawValue: Int

public init(rawValue: Int) {
self.rawValue = rawValue
}
}
44 changes: 38 additions & 6 deletions Sources/Options/Documentation.docc/Documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ Swift Package for more powerful `Enum` types.

**Options** provides a powerful set of features for `Enum` and `OptionSet` types:

* Providing additional representations for `Enum` types besides the `RawType rawValue`
* Being able to interchange between `Enum` and `OptionSet` types
* Using an additional value type for a `Codable` `OptionSet`
- Providing additional representations for `Enum` types besides the `RawType rawValue`
- Being able to interchange between `Enum` and `OptionSet` types
- Using an additional value type for a `Codable` `OptionSet`

### Requirements

Expand Down Expand Up @@ -38,7 +38,7 @@ Use version up to `1.0`.
Let's say we are using an `Enum` for a list of popular social media networks:

```swift
enum SocialNetwork {
enum SocialNetwork : Int {
case digg
case aim
case bebo
Expand Down Expand Up @@ -69,15 +69,47 @@ struct SocialHandle {
However we also want to provide a way to have a unique set of social networks available:

```swift
struct SocialNetworks : OptionSet {
struct SocialNetworkSet : Int, OptionSet {
...
}

let user : User
let networks : SocialNetworks = user.availableNetworks()
```

Insert more text here.
We can then simply use ``Options()`` macro to generate both these types:

```swift
@Options
enum SocialNetwork : Int {
case digg
case aim
case bebo
case delicious
case eworld
case googleplus
case itunesping
case jaiku
case miiverse
case musically
case orkut
case posterous
case stumbleupon
case windowslive
case yahoo
}
```

Now we can use the newly create `SocialNetworkSet` type to store a set of values:

```swift
let networks : SocialNetworks
networks = [.aim, .delicious, .googleplus, .windowslive]
```

### Multiple Value Types

With the ``Options()`` macro, we add the ability to encode and decode values not only from their raw Integer value but also from a String. This is useful for when you want to store the values in

## Topics

Expand Down
7 changes: 6 additions & 1 deletion Sources/Options/EnumSet.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/// Generic struct for using Enums with RawValue type of Int as an Optionset
public struct EnumSet<EnumType: RawRepresentable>: OptionSet, Sendable
public struct EnumSet<EnumType: RawRepresentable>:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Colon Violation: Colons should be next to the identifier when specifying a type and next to the key in dictionary literals. (colon)

OptionSet, Sendable, ExpressibleByArrayLiteral
where EnumType.RawValue == Int {
public typealias RawValue = EnumType.RawValue

Expand All @@ -12,6 +13,10 @@ public struct EnumSet<EnumType: RawRepresentable>: OptionSet, Sendable
self.rawValue = rawValue
}

public init(arrayLiteral elements: EnumType...) {
self.init(values: elements)
}

/// Creates the EnumSet based on the values in the array.
/// - Parameter values: Array of enum values.
public init(values: [EnumType]) {
Expand Down
98 changes: 98 additions & 0 deletions Sources/Options/MappedValueRepresentable+Codable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
//
// MappedValueRepresentable+Codable.swift
// SimulatorServices
//
// Created by Leo Dion.
// Copyright © 2024 BrightDigit.
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the “Software”), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//

import Foundation

extension DecodingError {
internal static func invalidRawValue(_ rawValue: some Any) -> DecodingError {
.dataCorrupted(
.init(codingPath: [], debugDescription: "Raw Value \(rawValue) is invalid.")
)
}
}

extension SingleValueDecodingContainer {
fileprivate func decodeAsRawValue<T: MappedValueRepresentable>() throws -> T
where T.RawValue: Decodable {
let rawValue = try decode(T.RawValue.self)
guard let value = T(rawValue: rawValue) else {
throw DecodingError.invalidRawValue(rawValue)
}
return value
}

fileprivate func decodeAsMappedType<T: MappedValueRepresentable>() throws -> T
where T.RawValue: Decodable, T.MappedType: Decodable {
let mappedValues: T.MappedType
do {
mappedValues = try decode(T.MappedType.self)
} catch {
return try decodeAsRawValue()
}

let rawValue = try T.rawValue(basedOn: mappedValues)

guard let value = T(rawValue: rawValue) else {
throw DecodingError.dataCorrupted(
.init(codingPath: [], debugDescription: "Invalid Raw Value.")
)
}

return value
}
}

extension MappedValueRepresentable
where Self: Decodable, MappedType: Decodable, RawValue: Decodable {
/// Decodes the type.
/// - Parameter decoder: Decoder.
public init(from decoder: any Decoder) throws {
let container = try decoder.singleValueContainer()

if Self.codingOptions.contains(.allowMappedValueDecoding) {
self = try container.decodeAsMappedType()
} else {
self = try container.decodeAsRawValue()
}
}
}

extension MappedValueRepresentable
where Self: Encodable, MappedType: Encodable, RawValue: Encodable {
/// Encoding the type.
/// - Parameter decoder: Encodes.
public func encode(to encoder: any Encoder) throws {
var container = encoder.singleValueContainer()
if Self.codingOptions.contains(.encodeAsMappedValue) {
try container.encode(mappedValue())
} else {
try container.encode(rawValue)
}
}
}
14 changes: 10 additions & 4 deletions Sources/Options/MappedValueRepresentable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@

public protocol MappedValueRepresentable: RawRepresentable, CaseIterable, Sendable {
associatedtype MappedType = String

static var codingOptions: CodingOptions {
get
}

/// Gets the raw value based on the MappedType.
/// - Parameter value: MappedType value.
/// - Returns: The raw value of the enumeration based on the `MappedType `value.
Expand All @@ -41,17 +46,18 @@ public protocol MappedValueRepresentable: RawRepresentable, CaseIterable, Sendab
}

extension MappedValueRepresentable {
/// Options regarding how the type can be decoded or encoded.
public static var codingOptions: CodingOptions {
.default
}

/// Gets the mapped value of the enumeration.
/// - Parameter rawValue: The raw value of the enumeration
/// which pretains to its index in the `mappedValues` Array.
/// - Throws: `MappedValueCollectionRepresentedError.valueNotFound`
/// if the raw value (i.e. index) is outside the range of the `mappedValues` array.
/// - Returns:
/// The Mapped Type value based on the value in the array at the raw value index.

/// Gets the mapped value of the enumeration.

/// - Returns: The `MappedType` value
public func mappedValue() throws -> MappedType {
try Self.mappedValue(basedOn: rawValue)
}
Expand Down
6 changes: 4 additions & 2 deletions Tests/OptionsTests/EnumSetTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,10 @@ internal final class EnumSetTests: XCTestCase {

internal func testInitValues() {
let values: [MockCollectionEnum] = [.a, .b, .c]
let set = EnumSet(values: values)
XCTAssertEqual(set.rawValue, 7)
let setA = EnumSet(values: values)
XCTAssertEqual(setA.rawValue, 7)
let setB: MockCollectionEnumSet = [.a, .b, .c]
XCTAssertEqual(setB.rawValue, 7)
}

internal func testArray() {
Expand Down
14 changes: 14 additions & 0 deletions Tests/OptionsTests/MappedValueCollectionRepresentedTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,18 @@ internal final class MappedValueCollectionRepresentedTests: XCTestCase {

XCTAssertEqual(caughtError, .valueNotFound)
}

internal func testCodable() throws {
let encoder: JSONEncoder = .init()
let decoder: JSONDecoder = .init()
let enumValue = MockCollectionEnum.a
let stringValue = try String(data: encoder.encode(enumValue), encoding: .utf8)
let actualStringValue = try MockCollectionEnum.mappedValue(basedOn: enumValue.rawValue)
XCTAssertEqual(stringValue, "\"\(actualStringValue)\"")
XCTAssertEqual(stringValue, "\"a\"")
let expectedStringValue = "a"
let data = "\"\(expectedStringValue)\"".data(using: .utf8) ?? .init()
let actualValue = try decoder.decode(MockCollectionEnum.self, from: data)
XCTAssertEqual(actualValue, .a)
}
}
2 changes: 1 addition & 1 deletion Tests/OptionsTests/Mocks/MockCollectionEnum.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import Options
#if swift(>=5.10)
// swiftlint:disable identifier_name
@Options
internal enum MockCollectionEnum: Int, Sendable {
internal enum MockCollectionEnum: Int, Sendable, Codable {
case a
case b
case c
Expand Down