/// Error handling with the Result type. import Prim "mo:⛔"; import P "Prelude"; import Order "Order"; module { /// `Result` is the type used for returning and propagating errors. It /// is a type with the variants, `#ok(Ok)`, representing success and containing /// a value, and `#err(Err)`, representing error and containing an error value. /// /// The simplest way of working with `Result`s is to pattern match on them: /// /// For example, given a function `createUser(user : User) : Result` /// where `String` is an error message we could use it like so: /// ```motoko no-repl /// switch(createUser(myUser)) { /// case (#ok(id)) { Debug.print("Created new user with id: " # id) }; /// case (#err(msg)) { Debug.print("Failed to create user with the error: " # msg) }; /// } /// ``` public type Result = { #ok : Ok; #err : Err }; // Compares two Result's for equality. public func equal( eqOk : (Ok, Ok) -> Bool, eqErr : (Err, Err) -> Bool, r1 : Result, r2 : Result ) : Bool { switch (r1, r2) { case (#ok(ok1), #ok(ok2)) { eqOk(ok1, ok2) }; case (#err(err1), #err(err2)) { eqErr(err1, err2) }; case _ { false } } }; // Compares two Results. `#ok` is larger than `#err`. This ordering is // arbitrary, but it lets you for example use Results as keys in ordered maps. public func compare( compareOk : (Ok, Ok) -> Order.Order, compareErr : (Err, Err) -> Order.Order, r1 : Result, r2 : Result ) : Order.Order { switch (r1, r2) { case (#ok(ok1), #ok(ok2)) { compareOk(ok1, ok2) }; case (#err(err1), #err(err2)) { compareErr(err1, err2) }; case (#ok(_), _) { #greater }; case (#err(_), _) { #less } } }; /// Allows sequencing of `Result` values and functions that return /// `Result`'s themselves. /// ```motoko /// import Result "mo:base/Result"; /// type Result = Result.Result; /// func largerThan10(x : Nat) : Result = /// if (x > 10) { #ok(x) } else { #err("Not larger than 10.") }; /// /// func smallerThan20(x : Nat) : Result = /// if (x < 20) { #ok(x) } else { #err("Not smaller than 20.") }; /// /// func between10And20(x : Nat) : Result = /// Result.chain(largerThan10(x), smallerThan20); /// /// assert(between10And20(15) == #ok(15)); /// assert(between10And20(9) == #err("Not larger than 10.")); /// assert(between10And20(21) == #err("Not smaller than 20.")); /// ``` public func chain( x : Result, y : R1 -> Result ) : Result { switch x { case (#err(e)) { #err(e) }; case (#ok(r)) { y(r) } } }; /// Flattens a nested Result. /// /// ```motoko /// import Result "mo:base/Result"; /// assert(Result.flatten(#ok(#ok(10))) == #ok(10)); /// assert(Result.flatten(#err("Wrong")) == #err("Wrong")); /// assert(Result.flatten(#ok(#err("Wrong"))) == #err("Wrong")); /// ``` public func flatten( result : Result, Error> ) : Result { switch result { case (#ok(ok)) { ok }; case (#err(err)) { #err(err) } } }; /// Maps the `Ok` type/value, leaving any `Error` type/value unchanged. public func mapOk( x : Result, f : Ok1 -> Ok2 ) : Result { switch x { case (#err(e)) { #err(e) }; case (#ok(r)) { #ok(f(r)) } } }; /// Maps the `Err` type/value, leaving any `Ok` type/value unchanged. public func mapErr( x : Result, f : Error1 -> Error2 ) : Result { switch x { case (#err(e)) { #err(f(e)) }; case (#ok(r)) { #ok(r) } } }; /// Create a result from an option, including an error value to handle the `null` case. /// ```motoko /// import Result "mo:base/Result"; /// assert(Result.fromOption(?42, "err") == #ok(42)); /// assert(Result.fromOption(null, "err") == #err("err")); /// ``` public func fromOption(x : ?R, err : E) : Result { switch x { case (?x) { #ok(x) }; case null { #err(err) } } }; /// Create an option from a result, turning all #err into `null`. /// ```motoko /// import Result "mo:base/Result"; /// assert(Result.toOption(#ok(42)) == ?42); /// assert(Result.toOption(#err("err")) == null); /// ``` public func toOption(r : Result) : ?R { switch r { case (#ok(x)) { ?x }; case (#err(_)) { null } } }; /// Applies a function to a successful value, but discards the result. Use /// `iterate` if you're only interested in the side effect `f` produces. /// /// ```motoko /// import Result "mo:base/Result"; /// var counter : Nat = 0; /// Result.iterate(#ok(5), func (x : Nat) { counter += x }); /// assert(counter == 5); /// Result.iterate(#err("Wrong"), func (x : Nat) { counter += x }); /// assert(counter == 5); /// ``` public func iterate(res : Result, f : Ok -> ()) { switch res { case (#ok(ok)) { f(ok) }; case _ {} } }; // Whether this Result is an `#ok` public func isOk(r : Result) : Bool { switch r { case (#ok(_)) { true }; case (#err(_)) { false } } }; // Whether this Result is an `#err` public func isErr(r : Result) : Bool { switch r { case (#ok(_)) { false }; case (#err(_)) { true } } }; /// Asserts that its argument is an `#ok` result, traps otherwise. public func assertOk(r : Result) { switch (r) { case (#err(_)) { assert false }; case (#ok(_)) {} } }; /// Asserts that its argument is an `#err` result, traps otherwise. public func assertErr(r : Result) { switch (r) { case (#err(_)) {}; case (#ok(_)) assert false } }; /// Converts an upper cased `#Ok`, `#Err` result type into a lowercased `#ok`, `#err` result type. /// On the IC, a common convention is to use `#Ok` and `#Err` as the variants of a result type, /// but in Motoko, we use `#ok` and `#err` instead. public func fromUpper( result : { #Ok: Ok; #Err: Err } ) : Result { switch result { case (#Ok(ok)) { #ok(ok) }; case (#Err(err)) { #err(err) } } }; /// Converts a lower cased `#ok`, `#err` result type into an upper cased `#Ok`, `#Err` result type. /// On the IC, a common convention is to use `#Ok` and `#Err` as the variants of a result type, /// but in Motoko, we use `#ok` and `#err` instead. public func toUpper( result : Result ) : { #Ok: Ok; #Err: Err } { switch result { case (#ok(ok)) { #Ok(ok) }; case (#err(err)) { #Err(err) } } }; }