// // Version 2 of pagesFromData from Flattenin' Your Mappenin' // http://robnapier.net/flatmap // import Foundation infix operator >>== {} func >>== <T,U>(x: T, f:T -> Result<U>) -> Result<U> { return x.flatMap(f) } func pagesFromData(data: NSData) -> Result<[Page]> { return asJSON(data) >>== asJSONArray >>== secondElement >>== asStringList >>== asPages } func asJSON(data: NSData) -> Result<JSON> { var error: NSError? let json: AnyObject? = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions(0), error: &error) switch (json, error) { case (_, .Some(let error)): return .Failure(error) case (.Some(let json), _): return .Success(Box(json)) default: fatalError("Received neither JSON nor an error") return .Failure(NSError()) } } func asJSONArray(json: JSON) -> Result<JSONArray> { if let array = json as? JSONArray { return .Success(Box(array)) } else { return .Failure(NSError(localizedDescription: "Expected array. Got: \(json)")) } } func secondElement(array: JSONArray) -> Result<JSON> { if array.count < 2 { return .Failure(NSError(localizedDescription:"Could not get second element. Array too short: \(array.count)")) } return .Success(Box(array[1])) } func asStringList(array: JSON) -> Result<[String]> { if let string = array as? [String] { return .Success(Box(string)) } else { return .Failure(NSError(localizedDescription: "Unexpected string list: \(array)")) } } func asPages(titles: [String]) -> Result<[Page]> { return .Success(Box(titles.map { Page(title: $0) })) } enum Result<A> { case Success(Box<A>) case Failure(NSError) func flatMap<B>(f:A -> Result<B>) -> Result<B> { switch self { case Success(let value): return f(value.unbox) case Failure(let error): return .Failure(error) } } } final class Box<T> { let unbox: T init(_ value: T) { self.unbox = value } } extension Result: Printable { var description: String { switch self { case .Success(let box): return "Success: \(box.unbox)" case .Failure(let error): return "Failure: \(error.localizedDescription)" } } } struct Page { let title: String } extension Page: Printable { var description: String { return title } } typealias JSON = AnyObject typealias JSONArray = [JSON] extension NSError { convenience init(localizedDescription: String) { self.init(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: localizedDescription]) } } func asJSONData(string: NSString) -> NSData { return string.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)! } let goodPagesJson = asJSONData("[\"a\",[\"Animal\",\"Association football\",\"Arthropod\",\"Australia\",\"AllMusic\",\"African American (U.S. Census)\",\"Album\",\"Angiosperms\",\"Actor\",\"American football\",\"Austria\",\"Argentina\",\"American Civil War\",\"Administrative divisions of Iran\",\"Alternative rock\"]]") pagesFromData(goodPagesJson).description let corruptJson = asJSONData("a\",[\"Animal\",\"Association football\",\"Arthropod\",\"Australia\",\"AllMusic\",\"African American (U.S. Census)\",\"Album\",\"Angiosperms\",\"Actor\",\"American football\",\"Austria\",\"Argentina\",\"American Civil War\",\"Administrative divisions of Iran\",\"Alternative rock\"]]") pagesFromData(corruptJson).description