fix: add base Option and Result implement, add unit tests
This commit is contained in:
parent
596d84720b
commit
1664f86179
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
test/test
|
||||
.vscode
|
45
src/error.h
Normal file
45
src/error.h
Normal file
@ -0,0 +1,45 @@
|
||||
#ifndef NEER_ERROR_H
|
||||
#define NEER_ERROR_H
|
||||
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
#define ERR(fmt,...) Error(nerr::sfmt("%s:%d: " fmt, __FILE__, __LINE__, ##__VA_ARGS__))
|
||||
|
||||
namespace nerr
|
||||
{
|
||||
template <typename... Args>
|
||||
std::string sfmt(const std::string &format, Args... args)
|
||||
{
|
||||
int size_s = std::snprintf(nullptr, 0, format.c_str(), args...) + 1; // Extra space for '\0'
|
||||
if (size_s <= 0)
|
||||
{
|
||||
throw std::runtime_error("Error during formatting string.");
|
||||
}
|
||||
auto size = static_cast<size_t>(size_s);
|
||||
std::unique_ptr<char[]> buf(new char[size]);
|
||||
std::snprintf(buf.get(), size, format.c_str(), args...);
|
||||
return std::string(buf.get(), buf.get() + size - 1); // We don't want the '\0' inside
|
||||
}
|
||||
|
||||
class Error : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
Error(const std::string &s) : runtime_error(s){};
|
||||
Error(const char *s) : runtime_error(s){};
|
||||
Error(): runtime_error(""){};
|
||||
|
||||
inline friend bool operator==(const Error& l, const Error& r)
|
||||
{
|
||||
return std::string(l.what()) == std::string(r.what());
|
||||
}
|
||||
|
||||
inline Error operator+(const Error& other)
|
||||
{
|
||||
return Error(std::string(this->what()) + "\n" + other.what());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
10
src/nerr.h
Normal file
10
src/nerr.h
Normal file
@ -0,0 +1,10 @@
|
||||
#ifndef NEER_H
|
||||
#define NEER_H
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
#include "error.h"
|
||||
#include "option.h"
|
||||
#include "result.h"
|
||||
|
||||
#endif
|
92
src/option.h
Normal file
92
src/option.h
Normal file
@ -0,0 +1,92 @@
|
||||
#ifndef NEER_OPTION_H
|
||||
#define NEER_OPTION_H
|
||||
|
||||
#define None(T) (nerr::Option<T>())
|
||||
#define Some(v) (nerr::Option(v))
|
||||
|
||||
namespace nerr
|
||||
{
|
||||
template <typename T, typename E> class Result;
|
||||
|
||||
template <typename T>
|
||||
class Option
|
||||
{
|
||||
|
||||
public:
|
||||
Option(T obj) : m_some(obj), m_notnull(true){};
|
||||
Option() : m_notnull(false){};
|
||||
|
||||
Option<T>& operator=(const T& value) {insert(value); return *this;};
|
||||
inline friend bool operator==(const Option<T>& l, const Option<T>& r) {
|
||||
if(!l.m_notnull && !r.m_notnull)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if(l.m_notnull && r.m_notnull)
|
||||
{
|
||||
return l.m_some == r.m_some;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
inline bool is_none(){return !m_notnull;};
|
||||
inline bool is_some(){return m_notnull;};
|
||||
inline T& unwrap()
|
||||
{
|
||||
if(m_notnull)
|
||||
{
|
||||
return m_some;
|
||||
}
|
||||
throw Error("Object is None");
|
||||
}
|
||||
inline T& get_or_insert(T value)
|
||||
{
|
||||
if(!m_notnull)
|
||||
{
|
||||
insert(value);
|
||||
}
|
||||
return m_some;
|
||||
}
|
||||
inline T& insert(T value)
|
||||
{
|
||||
m_some = value;
|
||||
m_notnull = true;
|
||||
return m_some;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
inline Result<T, E> ok_or(E err)
|
||||
{
|
||||
if(m_notnull)
|
||||
{
|
||||
return Result<T, E>(m_some);
|
||||
}
|
||||
return Result<T, E>(err);
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
inline Option<U> map(const std::function<U(T)>& fn)
|
||||
{
|
||||
if(is_some())
|
||||
{
|
||||
return fn(m_some);
|
||||
}
|
||||
return Option<U>();
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
inline U map_or(U defv, const std::function<U(T)>& fn)
|
||||
{
|
||||
if(is_some())
|
||||
{
|
||||
return fn(m_some);
|
||||
}
|
||||
return defv;
|
||||
}
|
||||
|
||||
private:
|
||||
T m_some;
|
||||
bool m_notnull;
|
||||
};
|
||||
}
|
||||
#endif
|
82
src/result.h
Normal file
82
src/result.h
Normal file
@ -0,0 +1,82 @@
|
||||
#ifndef NEER_RESULT_H
|
||||
#define NEER_RESULT_H
|
||||
//#define Ok(v) (nerr::Result(v))
|
||||
//#define Err(e) (nerr::Result(e))
|
||||
|
||||
namespace nerr
|
||||
{
|
||||
//template <typename T> class Option;
|
||||
|
||||
template <typename T, typename E>
|
||||
class Result
|
||||
{
|
||||
|
||||
public:
|
||||
Result(T obj) : m_result(obj) {};
|
||||
Result(E err) { m_error.insert(err); };
|
||||
Result(){};
|
||||
|
||||
inline Result<T,E>& operator=(const T& value) {m_result = value; m_error = None(E); return *this;};
|
||||
inline Result<T,E>& operator=(const E& err) {m_error.insert(err); return *this;};
|
||||
|
||||
inline bool is_ok(){return m_error.is_none();};
|
||||
inline bool is_err(){return m_error.is_some();};
|
||||
|
||||
inline T& unwrap()
|
||||
{
|
||||
if(is_ok())
|
||||
{
|
||||
return m_result;
|
||||
}
|
||||
throw m_error.unwrap();
|
||||
}
|
||||
|
||||
inline T& expect(E e)
|
||||
{
|
||||
if(is_ok())
|
||||
{
|
||||
return m_result;
|
||||
}
|
||||
throw e + m_error.unwrap();
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
inline Result<U,E> map(const std::function<U(T)>& fn)
|
||||
{
|
||||
if(is_ok())
|
||||
{
|
||||
return fn(m_result);
|
||||
}
|
||||
return m_error.unwrap();
|
||||
}
|
||||
|
||||
template<typename E1>
|
||||
inline Result<T,E1> map_err(const std::function<E1(E)>& fn)
|
||||
{
|
||||
if(!is_ok())
|
||||
{
|
||||
return fn(m_error.unwrap());
|
||||
}
|
||||
return m_result;
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
inline U map_or(U defv, const std::function<U(T)>& fn)
|
||||
{
|
||||
if(is_ok())
|
||||
{
|
||||
return fn(m_result);
|
||||
}
|
||||
return defv;
|
||||
}
|
||||
|
||||
Option<E> err() { return m_error; }
|
||||
Option<T> ok() { return is_ok()?Option<T>(m_result): Option<T>(); }
|
||||
|
||||
private:
|
||||
T m_result;
|
||||
Option<E> m_error;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
168
test/test.cpp
Normal file
168
test/test.cpp
Normal file
@ -0,0 +1,168 @@
|
||||
#include <string>
|
||||
#include "../src/nerr.h"
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
|
||||
|
||||
|
||||
using namespace std;
|
||||
using namespace nerr;
|
||||
|
||||
#define assert(v,fmt,...) if(!(v)) { \
|
||||
throw Error(nerr::sfmt("ASSERT ERROR %s:%d: " fmt, __FILE__, __LINE__, ##__VA_ARGS__)); \
|
||||
}
|
||||
|
||||
|
||||
void test(const char* desc, const std::function<void()>& lambda)
|
||||
{
|
||||
std::cout << "Run test: " << desc << "..........";
|
||||
try {
|
||||
lambda();
|
||||
std::cout << "OK" << std::endl;
|
||||
}
|
||||
catch(nerr::Error e)
|
||||
{
|
||||
std::cout << "FAILED" << std::endl;
|
||||
std::cout << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
Result<string, Error> test_return()
|
||||
{
|
||||
return string("Error");
|
||||
}
|
||||
|
||||
int main(int argc, char const *argv[])
|
||||
{
|
||||
test("Test Option", [](){
|
||||
Option<string> opt;
|
||||
assert(opt.is_none(), "Option is not empty");
|
||||
assert(! opt.is_some(), "Option has some value");
|
||||
string text = "This is some data";
|
||||
opt = text;
|
||||
assert(opt.is_some(), "Option shall has some value");
|
||||
assert(opt.unwrap() == text,"Value mistmatch");
|
||||
});
|
||||
|
||||
test("Option -> Result convert", [](){
|
||||
Option<string> opt;
|
||||
Result<string, Error> ret = opt.ok_or(ERR("Option is null"));
|
||||
assert(ret.is_err(), "Object should containe error object");
|
||||
string err = ret.err().unwrap().what();
|
||||
assert( err.find("Option is null") != string::npos, "Error message mistmatch");
|
||||
|
||||
opt = "abc";
|
||||
ret = opt.ok_or(ERR("Option is null"));
|
||||
assert(ret.is_ok(), "Object should contain data");
|
||||
assert(ret.unwrap() == "abc", "Data mistmatch");
|
||||
});
|
||||
|
||||
test("Option map", [](){
|
||||
auto fn = [](int x){
|
||||
return "Hello";
|
||||
};
|
||||
|
||||
Option<int> opt;
|
||||
Option<string> opt1 = opt.map<string>(fn);
|
||||
assert(opt.is_none() == opt1.is_none(), "Nothing shall be changed");
|
||||
|
||||
opt = 2;
|
||||
opt1 = opt.map<string>(fn);
|
||||
assert(opt1.unwrap() == "Hello", "Value mismatch");
|
||||
});
|
||||
|
||||
|
||||
test("Option map or", [](){
|
||||
auto fn = [](int x){
|
||||
return ++x;
|
||||
};
|
||||
|
||||
Option<int> opt;
|
||||
int v = opt.map_or<int>(0, fn);
|
||||
assert(v == 0, "Value mismatch");
|
||||
|
||||
opt = 2;
|
||||
v = opt.map_or<int>(0, fn);
|
||||
assert(v == 3, "Value mismatch %d",v);
|
||||
});
|
||||
|
||||
test("Test Result", []{
|
||||
Result<string, Error> ret = ERR("Error");
|
||||
assert(ret.is_err(), "Object should containe error object");
|
||||
ret = string("Hello");
|
||||
assert(ret.is_ok(), "Object should contain data");
|
||||
assert(ret.unwrap() == "Hello", "Data mistmatch");
|
||||
});
|
||||
|
||||
test("Result->option convert",[](){
|
||||
Result<string, Error> ret = ERR("Error");
|
||||
Option<string> opt_v = ret.ok();
|
||||
assert(opt_v.is_none(), "Value shall not be present");
|
||||
Option<Error> opt_err = ret.err();
|
||||
assert(opt_err.is_some(), "Erreur shall not be none");
|
||||
|
||||
ret = string("Hello");
|
||||
opt_v = ret.ok();
|
||||
assert(opt_v.is_some(), "Value shall be present");
|
||||
opt_err = ret.err();
|
||||
assert(opt_err.is_none(), "Erreur shall be none");
|
||||
});
|
||||
|
||||
test("Result expect", [](){
|
||||
try{
|
||||
Result<string, Error> ret = ERR("Error");
|
||||
auto s = ret.expect(ERR("Expect message"));
|
||||
assert(false, "Should not happend");
|
||||
}
|
||||
catch(Error e)
|
||||
{
|
||||
assert(true, "%s", e.what());
|
||||
}
|
||||
});
|
||||
|
||||
test("Result map", [](){
|
||||
auto err = ERR("Error");
|
||||
auto fn = [](string data){
|
||||
cout << data << endl;
|
||||
return 1;
|
||||
};
|
||||
Result<string, Error> ret = err;
|
||||
Result<int, Error> ret1 = ret.map<int>(fn);
|
||||
assert(ret1.is_err(), "mapped result shall be an error");
|
||||
assert(ret1.err() == ret.err(), "Error object mistmatch");
|
||||
|
||||
ret = string("Hello");
|
||||
ret1 = ret.map<int>(fn);
|
||||
assert(ret1.is_ok(), "mapped result shall not be an error");
|
||||
assert(ret1.unwrap() == 1, "Value mistmatch");
|
||||
});
|
||||
|
||||
|
||||
test("Result map err", [](){
|
||||
auto fn = [](int x){
|
||||
cout << "Error code is " << x << endl;
|
||||
return "Hello";
|
||||
};
|
||||
Result<bool, int> ret = true;
|
||||
Result<bool, string> ret1 = ret.map_err<string>(fn);
|
||||
assert(ret1.unwrap() == true, "Value mistmatch");
|
||||
|
||||
ret = 10;
|
||||
ret1 = ret.map_err<string>(fn);
|
||||
assert(ret1.err().unwrap() == "Hello", "Value mistmatch");
|
||||
});
|
||||
|
||||
test("Result map or", [](){
|
||||
auto fn = [](int x){
|
||||
return ++x;
|
||||
};
|
||||
|
||||
Result<int, Error> ret = ERR("Error");
|
||||
int v = ret.map_or<int>(0, fn);
|
||||
assert(v == 0, "Value mismatch");
|
||||
|
||||
ret = 2;
|
||||
v = ret.map_or<int>(0, fn);
|
||||
assert(v == 3, "Value mismatch %d",v);
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue
Block a user