fix: add base Option and Result implement, add unit tests

This commit is contained in:
DL 2024-02-27 17:57:44 +01:00
parent 596d84720b
commit 1664f86179
6 changed files with 399 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
test/test
.vscode

45
src/error.h Normal file
View 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
View 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
View 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
View 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
View 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);
});
}