Skip to content

Commit

Permalink
Merge pull request #35 from JosephDuffy/setvalue-any-partial
Browse files Browse the repository at this point in the history
Update `setValue(_:for:)` to support any partial
  • Loading branch information
JosephDuffy authored Jul 8, 2019
2 parents e100864 + ad252a9 commit c8a8106
Show file tree
Hide file tree
Showing 6 changed files with 224 additions and 15 deletions.
2 changes: 1 addition & 1 deletion Sources/Partial/PartialConvertible.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ public protocol PartialConvertible {
/// ```
///
/// - Parameter partial: The partial to retrieve values from
init(partial: Partial<Self>) throws
init<PartialType: PartialProtocol>(partial: PartialType) throws where PartialType.Wrapped == Self

}
62 changes: 51 additions & 11 deletions Sources/Partial/PartialProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,26 +31,66 @@ public protocol PartialProtocol {

extension PartialProtocol {

/// Attempts to update the stored value for the given key path by unwrapping the
/// provided partial value. If the unwrapping fails the error will be thrown.
/// Attempts to update the stored value for the given key path by unwrapping the provided partial value. If the
/// unwrapping fails the error will be thrown.
///
/// - Parameter value: A partial wrapping a value of type `Value`.
/// - Parameter partial: A partial wrapping a value of type `Value`.
/// - Parameter keyPath: A key path from `Wrapped` to a property of type `Value`.
mutating func setValue<Value>(_ partial: Partial<Value>, for keyPath: KeyPath<Wrapped, Value>) throws where Value: PartialConvertible {
let value = try partial.unwrappedValue()
/// - Parameter unwrapper: A closure that will be called to unwrap the passed partial.
mutating func setValue<Value, PartialType>(
_ partial: PartialType,
for keyPath: KeyPath<Wrapped, Value>,
unwrapper: (_ partial: PartialType) throws -> Value
) rethrows where PartialType: PartialProtocol, PartialType.Wrapped == Value {
let value = try unwrapper(partial)
setValue(value, for: keyPath)
}

/// Attempts to update the stored value for the given key path by unwrapping the
/// provided partial value. If the unwrapping fails the error will be thrown.
/// Attempts to update the stored value for the given key path by unwrapping the provided partial value. If the
/// unwrapping fails the error will be thrown.
///
/// - Parameter value: A partial wrapping a value of type `Value?`.
/// - Parameter keyPath: A key path from `Wrapped` to a property of type `Value`.
mutating func setValue<Value>(_ partial: Partial<Value>, for keyPath: KeyPath<Wrapped, Value?>) throws where Value: PartialConvertible {
let value = try partial.unwrappedValue()
/// - Parameter partial: A partial wrapping a value of type `Value`.
/// - Parameter keyPath: A key path from `Wrapped` to a property of type `Value?`.
/// - Parameter unwrapper: A closure that will be called to unwrap the passed partial.
mutating func setValue<Value, PartialType>(
_ partial: PartialType,
for keyPath: KeyPath<Wrapped, Value?>,
unwrapper: (_ partial: PartialType) throws -> Value
) rethrows where PartialType: PartialProtocol, PartialType.Wrapped == Value {
let value = try unwrapper(partial)
setValue(value, for: keyPath)
}

/// Attempts to update the stored value for the given key path by unwrapping the provided partial value. If the
/// unwrapping fails the error will be thrown.
///
/// - Parameter partial: A partial wrapping a value of type `Value`.
/// - Parameter keyPath: A key path from `Wrapped` to a property of type `Value`.
/// - Parameter unwrapper: A closure that will be called to unwrap the passed partial. Defaults to the
/// `init(partial:)` function of `Value`
mutating func setValue<Value, PartialType>(
_ partial: PartialType,
for keyPath: KeyPath<Wrapped, Value>,
customUnwrapper unwrapper: (_ partial: PartialType) throws -> Value = Value.init(partial:)
) rethrows where PartialType: PartialProtocol, PartialType.Wrapped == Value, PartialType.Wrapped: PartialConvertible {
try setValue(partial, for: keyPath, unwrapper: unwrapper)
}

/// Attempts to update the stored value for the given key path by unwrapping the provided partial value. If the
/// unwrapping fails the error will be thrown.
///
/// - Parameter partial: A partial wrapping a value of type `Value`.
/// - Parameter keyPath: A key path from `Wrapped` to a property of type `Value?`.
/// - Parameter unwrapper: A closure that will be called to unwrap the passed partial. Defaults to the
/// `init(partial:)` function of `Value`
mutating func setValue<Value, PartialType>(
_ partial: PartialType,
for keyPath: KeyPath<Wrapped, Value?>,
customUnwrapper unwrapper: (_ partial: PartialType) throws -> Value = Value.init(partial:)
) rethrows where PartialType: PartialProtocol, PartialType.Wrapped == Value, PartialType.Wrapped: PartialConvertible {
try setValue(partial, for: keyPath, unwrapper: unwrapper)
}

/// Retrieve or set a value for the given key path. Returns `nil` if the value has not been set. If the value is set
/// to nil it will remove the value.
///
Expand Down
3 changes: 2 additions & 1 deletion Tests/PartialTests/Models/StringWrapper.swift
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import Partial

struct StringWrapper: PartialConvertible, Hashable, ExpressibleByStringLiteral {

let string: String

init(stringLiteral value: String) {
self.string = value
}

init(partial: Partial<StringWrapper>) throws {
init<PartialType: PartialProtocol>(partial: PartialType) throws where PartialType.Wrapped == StringWrapper {
string = try partial.value(for: \.string)
}
}
2 changes: 1 addition & 1 deletion Tests/PartialTests/Models/StringWrapperWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ struct StringWrapperWrapper: PartialConvertible, Hashable {
self.optionalStringWrapper = optionalStringWrapper
}

init(partial: Partial<StringWrapperWrapper>) throws {
init<PartialType: PartialProtocol>(partial: PartialType) throws where PartialType.Wrapped == StringWrapperWrapper {
stringWrapper = try partial.value(for: \.stringWrapper)
optionalStringWrapper = try partial.value(for: \.optionalStringWrapper)
}
Expand Down
86 changes: 85 additions & 1 deletion Tests/PartialTests/Tests/Partial+PartialConvertibleTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,48 @@ final class Partial_PartialConvertibleTests: QuickSpec {
expect(partial[keyPath]) == unwrappedValue
}
}

context("set with a custom unwrapper") {
context("that throws an error") {
enum TestError: Error {
case testError
}

var thrownError: Error!

beforeEach {
do {
try partial.setValue(Partial<StringWrapper>(), for: keyPath) { _ in
throw TestError.testError
}
} catch {
thrownError = error
}
}

afterEach {
thrownError = nil
}

it("should throw errors thrown by the unwrapper") {
expect(thrownError).to(matchError(TestError.testError))
}
}

context("that returns a value") {
var returnedValue: StringWrapper!

beforeEach {
returnedValue = "returned value"
partial.setValue(Partial<StringWrapper>(), for: keyPath) { _ in
return returnedValue
}
}
it("should set the key path to the value returned by the unwrapper") {
expect(partial[keyPath]) == returnedValue
}
}
}
}

context("an optional key path") {
Expand All @@ -83,7 +125,7 @@ final class Partial_PartialConvertibleTests: QuickSpec {
partial.setValue(initialValue, for: keyPath)
}

context("set no an incomplete Partial") {
context("set to an incomplete Partial") {
var thrownError: Error?

beforeEach {
Expand Down Expand Up @@ -132,6 +174,48 @@ final class Partial_PartialConvertibleTests: QuickSpec {
expect(partial[keyPath]) == unwrappedValue
}
}

context("set with a custom unwrapper") {
context("that throws an error") {
enum TestError: Error {
case testError
}

var thrownError: Error!

beforeEach {
do {
try partial.setValue(Partial<StringWrapper>(), for: keyPath) { _ in
throw TestError.testError
}
} catch {
thrownError = error
}
}

afterEach {
thrownError = nil
}

it("should throw errors thrown by the unwrapper") {
expect(thrownError).to(matchError(TestError.testError))
}
}

context("that returns a value") {
var returnedValue: StringWrapper!

beforeEach {
returnedValue = "returned value"
partial.setValue(Partial<StringWrapper>(), for: keyPath) { _ in
return returnedValue
}
}
it("should set the key path to the value returned by the unwrapper") {
expect(partial[keyPath]) == returnedValue
}
}
}
}

context("unwrappedValue()") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,48 @@ final class PartialBuilder_PartialConvertibleTests: QuickSpec {
expect(builder[keyPath]) == unwrappedValue
}
}

context("set with a custom unwrapper") {
context("that throws an error") {
enum TestError: Error {
case testError
}

var thrownError: Error!

beforeEach {
do {
try builder.setValue(Partial<StringWrapper>(), for: keyPath) { _ in
throw TestError.testError
}
} catch {
thrownError = error
}
}

afterEach {
thrownError = nil
}

it("should throw errors thrown by the unwrapper") {
expect(thrownError).to(matchError(TestError.testError))
}
}

context("that returns a value") {
var returnedValue: StringWrapper!

beforeEach {
returnedValue = "returned value"
builder.setValue(Partial<StringWrapper>(), for: keyPath) { _ in
return returnedValue
}
}
it("should set the key path to the value returned by the unwrapper") {
expect(builder[keyPath]) == returnedValue
}
}
}
}

context("an optional key path") {
Expand Down Expand Up @@ -132,6 +174,48 @@ final class PartialBuilder_PartialConvertibleTests: QuickSpec {
expect(builder[keyPath]) == unwrappedValue
}
}

context("set with a custom unwrapper") {
context("that throws an error") {
enum TestError: Error {
case testError
}

var thrownError: Error!

beforeEach {
do {
try builder.setValue(Partial<StringWrapper>(), for: keyPath) { _ in
throw TestError.testError
}
} catch {
thrownError = error
}
}

afterEach {
thrownError = nil
}

it("should throw errors thrown by the unwrapper") {
expect(thrownError).to(matchError(TestError.testError))
}
}

context("that returns a value") {
var returnedValue: StringWrapper!

beforeEach {
returnedValue = "returned value"
builder.setValue(Partial<StringWrapper>(), for: keyPath) { _ in
return returnedValue
}
}
it("should set the key path to the value returned by the unwrapper") {
expect(builder[keyPath]) == returnedValue
}
}
}
}

context("unwrappedValue()") {
Expand Down

0 comments on commit c8a8106

Please sign in to comment.