Swift 3 errors with additional data - swift

In Swift 3, what is the recommended way to put (potentially lots of) additional information in an error/exception that the catcher can use to solve/handle the problem? In all the examples I've seen, they use enums with associated values, and that seems overly cumbersome/verbose for lots of information.
Specifically, I am writing a simple parser and want a place to store the affected line and column numbers (and potentially other information in the future), but without requiring that every handler explicitly declare those as associated values, as that would be a burden on the caller.
At this point I can basically see two ways of doing this, neither of which seems particularly elegant and both of which require defining two different things:
Define an outer enum error that represents the type of error, and for each case accept a parameter that is an object that contains the additional exception details, or
Use the object as the actual Error and pass in a case from an enum to its constructor to represent the actual error condition.
Both of these feel somewhat unclean to me though as they take two separate concepts to represent a simple idea, an error, and I'm just wondering if there's a nicer way to do this.
Are there any conventions or recommended ways to handle errors that need to contain potentially lots of additional information?

I don't know if there is a "recommended" way, perhaps someone else can
answer that or provide a better solution.
But one possible approach would be to use a struct (with properties) as the error type and use optional properties for values which need
not be provided. Example:
struct ParserError: Error {
enum Reason {
case invalidCharacter
case unexpectedEOF
}
let reason: Reason
let line: Int?
let column: Int?
init(reason: Reason, line: Int? = nil, column: Int? = nil) {
self.reason = reason
self.line = line
self.column = column
}
}
One might also want to adopt the LocalizedError protocol to
provide sensible error descriptions even if the
concrete error type is not known by the catcher (compare How to provide a localized description with an Error type in Swift?):
extension ParserError: LocalizedError {
public var errorDescription: String? {
var description: String
switch reason {
case .invalidCharacter:
description = "Invalid Character in input file"
case .unexpectedEOF:
description = "Unexpected end of file"
}
if let line = line {
description += ", line \(line)"
}
if let column = column {
description += ", column \(column)"
}
return description
}
}
Usage example:
func parse() throws {
// Throw error with line number, but without column:
throw ParserError(reason: .invalidCharacter, line: 13)
}
do {
try parse()
} catch let error {
print(error.localizedDescription)
}
Output:
Invalid Character in input file, line 13

Related

Forcing an Encoder's UnkeyedEncodingContainer to only contain one type of value

As part of a custom Encoder, I am coding an UnkeyedEncodingContainer. However, the specific format I am making it for asks that all elements of an array be of the same type. Specifically, arrays can contain :
Integers one same size
Floats or Doubles
Other arrays (not necessarily all containing the same kinds of elements)
Objects
Here is the type of answer I need : The basis of an UnkeyedEncodingContainer implementation that conforms to the protocol, and enforces that all elements be of one same type among the above specified ones.
As requested, here are examples of things that should or should not be encodable :
var valid1 = []
var valid2 = [3, 3, 5, 9]
var valid3 = ["string", "array"]
var invalid1 = [3, "test"]
var invalid2 = [5, []]
var invalid3 = [[3, 5], {"hello" : 3}]
// These may not all even be valid Swift arrays, they are only
// intended as examples
As an example, here is the best I have come up with, which does not work :
The UnkeyedEncodingContainer contains a function, checkCanEncode, and an instance variable, ElementType :
var elementType : ElementType {
if self.count == 0 {
return .None
} else {
return self.storage[0].containedType
}
}
func checkCanEncode(_ value : Any?, compatibleElementTypes : [ElementType]) throws {
guard compatibleElementTypes.contains(self.elementType) || self.elementType == .None else {
let context = EncodingError.Context(
codingPath: self.nestedCodingPath,
debugDescription: "Cannot encode value to an array of \(self.elementType)s"
)
throw EncodingError.invalidValue(value as Any, context)
}
}
// I know the .None is weird and could be replaced by an optional,
// but it is useful as its rawValue is 0. The Encoder has to encode
// the rawValue of the ElementType at some point, so using an optional
// would actually be more complicated
Everything is then encoded as a contained singleValueContainer :
func encode<T>(_ value: T) throws where T : Encodable {
let container = self.nestedSingleValueContainer()
try container.encode(value)
try checkCanEncode(value, compatibleElementTypes: [container.containedType])
}
// containedType is an instance variable of SingleValueContainer that is set
// when a value is encoded into it
But this causes an issue when it comes to nestedContainer and nestedUnkeyedContainer : (used for stored dictionaries and arrays respectively)
// This violates the protocol, this function should not be able to throw
func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type) throws -> KeyedEncodingContainer<NestedKey> where NestedKey : CodingKey {
let container = KeyedContainer<NestedKey>(
codingPath: self.nestedCodingPath,
userInfo: self.userInfo
)
try checkCanEncode(container, compatibleElementTypes: [.Dictionary])
self.storage.append(container)
return KeyedEncodingContainer(container)
}
As you can see, since I need checkCanEncode to know whether it is even possible to create a NestedContainer in the first place (because if the array already has stuff inside that aren't dictionaries, then adding dictionaries to it is invalid), I have to make the function throw. But this breaks the UnkeyedEncodingContainer protocol which demands non-throwing versions.
But I can't just handle the error inside the function ! If something tries to put an array inside an array of integers, it must fail. Therefore this is an invalid solution.
Additional remarks :
Checking after having encoded the values already feels sketchy, but checking only when producing the final encoded payload is definitely a violation of the "No Zombies" principle (fail as soon as the program enters an invalid state) which I would rather avoid. However if no better solution is possible I may accept it as a last resort.
One other solution I have thought about is encoding the array as a dictionary with numbered keys, since dictionaries in this format may contain mixed types. However this is likely to pose decoding issues, so once again, it is a last resort.
You will be advised not to edit other people’s questions. If you have edits to suggest please do so in the comments, otherwise mind your own business
Unless anyone has a better idea, here is the best I could come up with :
Do not enforce that all elements be of the same type inside the UnkeyedEncodingContainer
If all elements are the same type, encode it as an array
If elements have varying types, encode it as a dictionary with integers as keys
This is completely fine as far as the encoding format goes, has minimal costs and only slightly complicates decoding (check whether keys contain integers) and greatly widens how many different Swift object will be compatible with the format.
Note : Remember that the "real" encoding step where the data is generated is not actually part of the protocol. That is where I am proposing the shenanigans should take place 😈
I don't know whether this helps you but in Swift you can overload functions. This means that you can declare functions with the same signature but with different parameter types or constraints. The compiler will take always the right choice. It's much more efficient than a type check at runtime.
The first method is called if the array conforms to Encodable
func encode<T: Encodable>(object: [T]) throws -> Data {
return try JSONEncoder().encode(object)
}
Otherwise the second method is called. If the array cannot even be encoded with JSONSerialization you can add custom encoding logic.
func encode<T>(object: [T]) throws -> Data {
do {
return try JSONSerialization.data(withJSONObject: object)
} catch {
// do custom encoding and return Data
return try myCustomEncoding(object)
}
}
This example
let array = [["1":1, "2":2]]
try encode(object: array)
calls the first method.
On the other hand this – even if the actual type is not heterogenous – calls the second method
let array : [[String:Any]] = [["1":1, "2":2]]
try encode(object: array)

Swift 5 storing and passing KeyPaths

Let's say I have the following class:
class User: NSObject {
var name = "Fred"
var age = 24
var email = "fred#freddy.com"
var married = false
}
I want to be able to write a generic function that takes in a list of KeyPaths for a known class type, read the values and print to screen. The problem is, the I can't get the following code to compile as the type of the KeyPath's Value is not known, and will be different for each time. What do I have to do to make this work generically?
Consider the following:
struct KeyPathProperties<T> {
var name: String
var relatedKeyPaths: [KeyPath<T, Any>]
}
extension KeyPath where Root == User {
var properties: KeyPathProperties<Root> {
switch self {
case \Root.name:
return KeyPathProperties(name: "name", relatedKeyPaths: [\Root.age, \Root.email])
default:
fatalError("Unknown key path")
}
}
}
This line fails to compile:
return KeyPathProperties(name: "name", relatedKeyPaths: [\Root.age, \Root.email])
with this error:
Cannot convert value of type 'KeyPath<User, Int>' to expected element type 'KeyPath<User, Any>'
This is what I wish to be able to do, for instance:
let myUser = User()
var keyPathProps = KeyPathProperties(name: "name", relatedKeyPaths: [\User.age, \User.email])
for keyPath in props.relatedKeyPaths {
print("Value: \(myUser[keyPath: keyPath])")
}
The above won't compile of course. Essentially I want to store keyPaths in an array at runtime, so I can generically at some point in time get values out of the User. I need to know if I can re-write the above in some way where the compiler can safely and correctly determine the type of the keyPath's value at runtime.
This is a conceptual use case for a much more complex architectural issue I'm trying to solve with hopefully less code.
MORE INFORMATION:
At runtime I wish to keep track of the properties that get modified - these properties are held in a modifiedProps array in each object / instance. At some point at runtime, I wish to be able to enumerate over this array of KeyPaths and print their values like so:
for modifiedKeyPath in self.modifiedProps {
print ("\(self[keyPath: modifiedKeyPath])"
}
In short - I need to be able to capture the generic type of the KeyPath within KeyPathProperties. How do I achieve this?
SIDE NOTE: I can already easily achieve this by using Swift 3 style string based KeyPaths (by adding #objc to the class properties). I can store an array of keyPaths as strings and later do:
let someKeyPath = #keyPath(User.email)
...
myUser.value(forKeyPath: someKeyPath)
I just cannot do this with Swift 4 KeyPaths generically.
The error tells you what your misconception is:
Cannot convert value of type 'KeyPath<User, Int>'
to expected element type 'KeyPath<User, Any>'
You seem to think that you can use a KeyPath<User, Int> where a KeyPath<User, Any> is expected, ostensibly on the grounds that an Int is an Any. But that's not true. These are generic types, and generic types are not covariant — that is, there is no substitution principle for generics based on their parameterized types. The two types are effectively unrelated.
If you need an array of key paths regardless of their parameterized types, you would need an array of PartialKeyPath or AnyKeyPath. It seems that in your use case the root object is the same throughout, so presumably you want PartialKeyPath.

How to Define Generic “Invalid“ Values for Different Types

In my app, I am using Integers, Doubles, Floats, and CGFloats to represent a number of different values. According to my app’s semantic, these values may become “invalid“, a state which I represent using a reserved value, i. e. -1. The simplest approach to make this usable in code would be this:
anIntVariable = -1
aFloatVariable = -1
aDoubleVariable = -1.0
...
To get away from this convention driven approach and increase readability and adaptability I defined a number of extensions:
extension Int {
static var invalid = -1
}
extension Float {
static var invalid = -1.0
}
extension Double {
static var invalid = -1.0
}
...
So the above code would now read:
anIntVariable = .invalid
aFloatVariable = .invalid
aDoubleVariable = .invalid
...
It does work. However, I’m not really happy with this approach. Does anyone of you have an idea for a better way of expressing this?
To add some complexity, in addition to simple types like Int, Float, or Double, I also use Measurement based types like this:
let length = Measurement(value: .invalid, unit: UnitLength.baseUnit())
Extra bonus point if you find a way to include “invalid“ measurements in your solution as well...
Thanks for helping!
Some Additional Thoughts
I know I could use optionals with nil meaning “invalid”. In this case, however, you’d have additional overhead with conditional unwrapping... Also, using nil as “invalid” is yet another convention.
It isn’t better or worse, just different. Apple uses “invalid” values in its own APIs, i. e. the NSTableViewmethod row(for:) will return -1 if the view is not in the table view. I agree, however, that this very method perfectly illustrates that returning an optional would make a lot of sense...
I'd use optionals for that.
If you want lack of value and invalid value to be different states in your app, i'd suggest creating a wrapper for your values:
enum Validatable<T> {
case valid(T)
case invalid
}
And use it like that:
let validValue : Validatable<Int> = .valid(5)
let invalidValue : Validatable<Int> = .invalid
var validOptionalDouble : Validatable<Double?> = .valid(nil)
validOptionalDouble = .valid(5.0)
let measurement : Validatable<Measurement> = .invalid
etc.
You can then check for value by switch on that enum to access the associated value like this:
switch validatableValue {
case .valid(let value):
//do something with value
case .invalid:
//handle invalid state
}
or
if case .valid(let value) = validatableValue {
//handle valid state
}
etc

Argument labels do not match any availble overloads

I'm trying to create an anagram tester, and I'm pretty sure the code I have should work, but I'm getting an error 'Argument labels '(_:)' do not match any available overloads' I've looked at the other posts regarding the same error, but I'm still not sure what this means or how to fix it.
var anagram1 : String!
var anagram2 : String!
var failure : Bool = false
var counter : Int = 0
print("Please enter first word: ")
anagram1 = readLine()
print("Please enter Second word: ")
anagram2 = readLine()
if anagram1.count == anagram2.count {
for i in anagram1.characters{
if (!failure){
failure = true
for y in anagram2.characters {
counter += 1
if i == y {
failure = false
anagram2.remove(at: String.Index(counter)) // error here
}
}
}
else {
print("these words are not anagrams")
break;
}
}
if (!failure) {
print("these words ARE anagrams")
}
}
else{
print ("these words aren't even the same length you fucking nonce")
}
To answer your first question: the error message Argument labels '(_:)' do not match any available overloads means that you've given a function parameter names or types that don't match anything Swift knows about.
The compiler is also trying to tell you what parameters to look at. '(_:)' says that you're calling a function with an unlabeled parameter. (That means a value without any parameter name. A common example of a function that would look like this is print("something"). In Swift documentation, this would look like print(_:).
Finally, overloads are ways to call a function with different information. Again using the print function as an example, you can call it multiple ways. A couple of the most common overloads would be:
// print something, followed by a newline character
print("something")
// print something, but stay on the same line
// (end with an empty string instead of the default newline character)
print("something", terminator: "")
Documented, these might look like print(_:) and print(_:, terminator:).
Note: these are broken down for explanation. The actual Swift documentation shows func print(_: Any..., separator: String, terminator: String) which covers a number of different overloads!
Looking at the line where the error occurs, you see a function call and an initializer (which is essentially a function). Documented, the way you've entered the parameters, the functions would look like: remove(at:) and String.Index(_:).
String.Index(_:) matches the parameters of the error message, so that's where your error is. There is no overload of the String.Index initializer that takes an unnamed parameter.
To fix this error, you need to find the correct way to create a String.Index parameter for the remove(at:) function. One way might be to try something like this:
for y in anagram2.characters.enumerated() {
// `y` now represents a `tuple`: (offset: Int, element: Character)
// so, you don't need `counter` anymore; use `offset` instead
if i == y.element { //`i` is a Character, so it can compare to `element`
...
let yIndex: String.Index = anagram2.index(anagram2.startIndex, offsetBy: y.offset)
anagram2.remove(at: yIndex)
...
}
}
However, there are other issues with your code that will cause further errors.
For one, you're looping through a string (anagram2) and trying to change it at the same time - not a good thing to do.
Good luck to you in solving the anagram problem!
Thanks for the help Leo but I found a way of doing it :)
if anagram1.count == anagram2.count {
for i in anagram1.characters{
if (!failure){
counter = -1
failure = true
for y in anagram2.characters {
counter += 1
if i == y {
failure = false
if counter < anagram2.count {
anagram2.remove(at: (anagram2.index(anagram2.startIndex, offsetBy: counter)))
break;
}
}
}
}

Mocking a String-class method in Swift

I've got some Swift 2.0 code, for which I'm trying to achieve 100% code coverage. I'm doing some JSON handling, part of which looks like so:
guard let jsonData = jsonText.dataUsingEncoding(NSUTF8StringEncoding) else {
throw ErrorCode.JSONEncodingFailure
}
I don't think there's a real-world case in which any string can't be encoded as UTF-8, but I don't want to open myself up to a crashing error either, so that code must remain. How can I mock the jsonText object to return nil for dataUsingEncoding()?
The closest I've come is to subclass NSString like so:
public class BadString: NSString {
public override var length: Int {
get {
return 5
}
}
public override func characterAtIndex(index: Int) -> unichar {
return 0
}
public override func dataUsingEncoding(encoding: NSStringEncoding) -> NSData? {
return nil
}
}
Here's the problem, though. In order for my mock implementation to be used, I have to define jsonText (a function parameter) as an NSString, rather than String, which feels wrong for an all-Swift codebase. With it defined as a Swift String, I have to cast my BadString to that type, and it uses the String implementation instead of my own.
Is there another (clean) way to achieve this?
You will be hard-pressed to find a string that cannot be encoded using UTF-8! As long as you know that is the encoding you will be using, I would suggest that you not worry about testing the "encoding failure" case.
However, if you still desire to test it then I recommend making one of the following changes in order to allow you to do so:
(1) change the way you are thinking about the 'failure': if you know that the string you are encoding will always be non-empty, then broaden the guard to also require that the encoded data has length > 0, e.g. =>
guard let jsonData = jsonText.dataUsingEncoding(NSUTF8StringEncoding)
where jsonData.length > 0 else {
throw ErrorCode.JSONEncodingFailure
}
...using this idea, you can now use an empty string for jsonText and trigger this code path (assuming that an empty string would also satisfy your definition of 'failure' here)
(2) store your string encoding value in a variable (let's call it stringEncoding) that you can access during your test setup, and then test this using incompatible values for jsonText and stringEncoding, e.g. =>
var jsonText = "🙈"
let stringEncoding = NSASCIIStringEncoding
...I guarantee that jsonText.dataUsingEncoding(stringEncoding) will return nil in this case :)
Happy Testing! I hope this helps!