Reputation: 14815
I'm adding better error handling to a web API framework I've inherited. Currently it does a bunch of forced casting, which will cause crashes the moment the data doesn't match what's expected.
I'd like to replace all uses of as!
with a function that checks the type and throws an exception (with a detailed description) on failure.
This is what I've gotten to so far:
func checkType<T>(type: AnyClass, value: T?, name: String) throws -> T {
guard let value = value else {
let message = "[\(name)] Expected \(type), but value was nil"
throw PlaidsterError.InvalidType(message)
}
guard value.dynamicType.self == type else {
let message = "[\(name)] Expected \(type), but it was an \(value.dynamicType.self) with value: \(value)"
throw PlaidsterError.InvalidType(message)
}
return value
}
But this has multiple problems. It can only take object types, it fails if the class doesn't match exactly (for example NSString fails because it's actually a __NSCFString), and it can't work with any type i.e. String, Int, Double...
I can't seem to find a synonym of AnyClass
for Any
value types, which is the first issue. Also it appears that it's not possible to do something like value.dynamicType.self is type
because it says that type
is not a type.
Is it possible to do what I'm trying to do? Is there a better way to do this kind of type checking without tons of boilerplate code scattered all over the parsing code?
My goal is to get something like this or even simpler:
public struct PlaidCategory {
// MARK: Properties
public let id: String
public let hierarchy: [String]
public let type: String
// MARK: Initialization
public init(category: [String: Any]) throws {
id = try checkType(String.self, value: category["id"], name: "id")
hierarchy = try checkType([String].self, value: category["hierarchy"], name: "hierarchy")
type = try checkType(String.self, value: category["type"], name: "type")
}
}
Upvotes: 1
Views: 958
Reputation: 47876
You can write something like this with another generic type parameter.
func checkType<T, U>(type: U.Type, value: T?, name: String) throws -> U {
guard let value = value else {
let message = "[\(name)] Expected \(type), but value was nil"
throw PlaidsterError.InvalidType(message)
}
guard let result = value as? U else {
let message = "[\(name)] Expected \(type), but it was an \(value.dynamicType) with value: \(value)"
throw PlaidsterError.InvalidType(message)
}
return result
}
And use it as:
do {
let n: Any = 3
let t = try checkType(Int.self, value: n, name: "n")
print(t) //->3
} catch let error {
print(error)
}
do {
let n: Any = "x"
let t = try checkType(Int.self, value: n, name: "n")
print(t)
} catch let error {
print(error) //->InvalidType("[n] Expected Int, but it was an _NSContiguousString with value: x")
}
I hope this will work for your PlaidCategory.init(category:)
, but not tested.
One more. If you use it only where return type is inferable, you have no need to pass type
parameter.
func checkType<T, U>(value: T?, name: String) throws -> U {
guard let value = value else {
let message = "[\(name)] Expected \(U.self), but value was nil"
throw PlaidsterError.InvalidType(message)
}
guard let result = value as? U else {
let message = "[\(name)] Expected \(U.self), but it was an \(value.dynamicType) with value: \(value)"
throw PlaidsterError.InvalidType(message)
}
return result
}
And use it as:
do {
let n: Any = "x"
let t: Int = try checkType(n, name: "n")
print(t)
} catch let error {
print(error) //->InvalidType("[n] Expected Int, but it was an String with value: x")
}
or:
public struct PlaidCategory {
// MARK: Properties
public let id: String
public let hierarchy: [String]
public let type: String
// MARK: Initialization
public init(category: [String: Any]) throws {
id = try checkType(category["id"], name: "id")
hierarchy = try checkType(category["hierarchy"], name: "hierarchy")
type = try checkType(category["type"], name: "type")
}
}
Upvotes: 3