greetd: use glaze for json handling

This commit is contained in:
Maximilian Seidler 2025-04-12 09:25:01 +02:00
parent 5f995d64cc
commit a1200e1d22
8 changed files with 170 additions and 402 deletions

View file

@ -72,6 +72,7 @@ find_package(Threads REQUIRED)
find_package(PkgConfig REQUIRED)
find_package(OpenGL REQUIRED COMPONENTS EGL GLES3)
find_package(hyprwayland-scanner 0.4.4 REQUIRED)
find_package(glaze REQUIRED)
pkg_check_modules(
deps
REQUIRED
@ -93,7 +94,7 @@ pkg_check_modules(
file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp")
add_executable(hyprlock ${SRCFILES})
target_link_libraries(hyprlock PRIVATE pam rt Threads::Threads PkgConfig::deps
OpenGL::EGL OpenGL::GLES3)
OpenGL::EGL OpenGL::GLES3 glaze::glaze)
# protocols
pkg_get_variable(WAYLAND_PROTOCOLS_DIR wayland-protocols pkgdatadir)
@ -150,15 +151,3 @@ install(
FILES ${CMAKE_SOURCE_DIR}/assets/example.conf
DESTINATION ${CMAKE_INSTALL_FULL_DATAROOTDIR}/hypr
RENAME hyprlock.conf)
# Tests
#
include(CTest)
add_custom_target(tests)
add_executable(test_notjson "tests/notjson.cpp")
target_link_libraries(test_notjson PRIVATE PkgConfig::deps)
add_test(
NAME "notjson"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
COMMAND test_notjson "notjson")

View file

@ -2,8 +2,9 @@
lib,
stdenv,
cmake,
pkg-config,
cairo,
glaze,
pkg-config,
libdrm,
libGL,
libxkbcommon,
@ -37,6 +38,7 @@ stdenv.mkDerivation {
buildInputs = [
cairo
glaze
libdrm
libGL
libxkbcommon

View file

@ -1,7 +1,6 @@
#include "GreetdLogin.hpp"
#include "../config/ConfigManager.hpp"
#include "../core/hyprlock.hpp"
#include "../helpers/NotJson.hpp"
#include "../helpers/Log.hpp"
#include <hyprutils/string/VarList.hpp>
@ -9,26 +8,6 @@
#include <sys/socket.h>
#include <sys/un.h>
static constexpr eGreetdAuthMessageType messageTypeFromString(const std::string_view& type) {
if (type == "visible")
return GREETD_AUTH_VISIBLE;
if (type == "secret")
return GREETD_AUTH_SECRET;
if (type == "info")
return GREETD_AUTH_INFO;
if (type == "error")
return GREETD_AUTH_ERROR;
return GREETD_AUTH_ERROR;
}
static constexpr eGreetdErrorMessageType errorTypeFromString(const std::string_view& type) {
if (type == "auth_error")
return GREETD_ERROR_AUTH;
if (type == "error")
return GREETD_ERROR;
return GREETD_ERROR;
}
static constexpr std::string getErrorString(eRequestError error) {
switch (error) {
case GREETD_REQUEST_ERROR_SEND: return "Failed to send payload to greetd";
@ -127,20 +106,26 @@ static std::string readFromSock(int fd) {
return msg;
}
static bool sendGreetdRequest(int fd, const NNotJson::SObject& request) {
static bool sendGreetdRequest(int fd, const VGreetdRequest& request) {
if (fd < 0) {
Debug::log(ERR, "[GreetdLogin] Invalid socket fd");
return false;
}
const auto PAYLOAD = NNotJson::serialize(request);
const auto GLZRESULT = glz::write_json(request);
if (!request.values.contains("response"))
Debug::log(TRACE, "[GreetdLogin] Request: {}", PAYLOAD);
else
if (!GLZRESULT.has_value()) {
const auto GLZERRORSTR = glz::format_error(GLZRESULT.error());
Debug::log(ERR, "[GreetdLogin] Failed to serialize request: {}", GLZERRORSTR);
return false;
}
if (std::holds_alternative<SGreetdPostAuthMessageResponse>(request))
Debug::log(TRACE, "[GreetdLogin] Request: REDACTED");
else
Debug::log(TRACE, "[GreetdLogin] Request: {}", GLZRESULT.value());
if (sendToSock(fd, PAYLOAD) < 0) {
if (sendToSock(fd, GLZRESULT.value()) < 0) {
Debug::log(ERR, "[GreetdLogin] Failed to send payload to greetd");
return false;
}
@ -170,7 +155,7 @@ void CGreetdLogin::init() {
});
}
std::expected<NNotJson::SObject, eRequestError> CGreetdLogin::request(const NNotJson::SObject& req) {
std::expected<VGreetdResponse, eRequestError> CGreetdLogin::request(const VGreetdRequest& req) {
if (!sendGreetdRequest(m_socketFD, req)) {
m_ok = false;
return std::unexpected(GREETD_REQUEST_ERROR_SEND);
@ -185,69 +170,55 @@ std::expected<NNotJson::SObject, eRequestError> CGreetdLogin::request(const NNot
Debug::log(TRACE, "[GreetdLogin] Response: {}", RESPONSESTR);
const auto [RESULTOBJ, ERROR] = NNotJson::parse(RESPONSESTR);
if (ERROR.status != NNotJson::SError::NOT_JSON_OK) {
Debug::log(ERR, "[GreetdLogin] Failed to parse response from greetd: {}", ERROR.message);
const auto GLZRESULT = glz::read_json<VGreetdResponse>(RESPONSESTR);
if (!GLZRESULT.has_value()) {
const auto GLZERRORSTR = glz::format_error(GLZRESULT.error(), RESPONSESTR);
Debug::log(ERR, "[GreetdLogin] Failed to parse response from greetd: {}", GLZERRORSTR);
m_ok = false;
return std::unexpected(GREETD_REQUEST_ERROR_PARSE);
}
if (!RESULTOBJ.values.contains("type")) {
Debug::log(ERR, "[GreetdLogin] Invalid greetd response");
m_ok = false;
return std::unexpected(GREETD_REQUEST_ERROR_PARSE);
return GLZRESULT.value();
}
return RESULTOBJ;
}
inline static const std::string& getStringValue(NNotJson::SObject& obj, const std::string& key) {
try {
return std::get<std::string>(obj.values[key]);
} catch (std::bad_variant_access const& ex) { RASSERT(false, "Key \"{}\" does not contain a string", key); }
};
void CGreetdLogin::handleResponse(const std::string& request, NNotJson::SObject& response) {
const auto RESPONSETYPE = getStringValue(response, "type");
if (RESPONSETYPE == "error") {
const auto ERRORTYPE = getStringValue(response, "error_type");
m_state.errorType = errorTypeFromString(ERRORTYPE);
m_state.error = getStringValue(response, "description");
Debug::log(ERR, "[GreetdLogin] Request failed: {} - {}", ERRORTYPE, m_state.error);
void CGreetdLogin::handleResponse(const VGreetdRequest& request, const VGreetdResponse& response) {
if (std::holds_alternative<SGreetdErrorResponse>(response)) {
const auto ERRORRESPONSE = std::get<SGreetdErrorResponse>(response);
m_state.errorType = ERRORRESPONSE.error_type;
m_state.error = ERRORRESPONSE.description;
Debug::log(ERR, "[GreetdLogin] Request failed: {} - {}", (int)m_state.errorType, m_state.error);
// Don't post a fail if this is a response to "cancel_session"
if (!m_state.error.empty() && request != "cancel_session")
if (!m_state.error.empty() && !std::holds_alternative<SGreetdCancelSession>(request))
g_pAuth->enqueueFail(m_state.error, AUTH_IMPL_GREETD);
// We don't have to cancel if "create_session" failed
if (request != "create_session")
if (!std::holds_alternative<SGreetdCreateSession>(request))
cancelSession();
} else if (RESPONSETYPE == "auth_message") {
const auto AUTHMESSAGETYPE = getStringValue(response, "auth_message_type");
m_state.authMessageType = messageTypeFromString(AUTHMESSAGETYPE);
m_state.message = getStringValue(response, "auth_message");
Debug::log(LOG, "[GreetdLogin] Auth message: {} - {}", AUTHMESSAGETYPE, m_state.message);
} else if (std::holds_alternative<SGreetdAuthMessageResponse>(response)) {
const auto AUTHMESSAGERESPONSE = std::get<SGreetdAuthMessageResponse>(response);
m_state.authMessageType = AUTHMESSAGERESPONSE.auth_message_type;
m_state.message = AUTHMESSAGERESPONSE.auth_message;
Debug::log(LOG, "[GreetdLogin] Auth message: {} - {}", (int)m_state.authMessageType, m_state.message);
if (m_state.authMessageType == GREETD_AUTH_ERROR && !m_state.message.empty())
g_pAuth->enqueueFail(m_state.message, AUTH_IMPL_GREETD);
} else if (RESPONSETYPE == "success") {
if (request == "create_session" || request == "post_auth_message_response")
} else if (std::holds_alternative<SGreetdSuccessResponse>(response)) {
if (std::holds_alternative<SGreetdCreateSession>(request) || std::holds_alternative<SGreetdPostAuthMessageResponse>(request))
startSessionAfterSuccess();
} else
Debug::log(ERR, "Unknown response type \"{}\"", RESPONSETYPE);
Debug::log(ERR, "Unknown response from greetd");
}
void CGreetdLogin::startSessionAfterSuccess() {
const auto SELECTEDSESSION = g_pHyprlock->getSelectedGreetdLoginSession();
Hyprutils::String::CVarList args(SELECTEDSESSION.exec, 0, ' ');
NNotJson::SObject startSession{.values = {
{"type", "start_session"},
}};
startSession.values["cmd"] = std::vector<std::string>{args.begin(), args.end()};
SGreetdStartSession startSession;
startSession.cmd = std::vector<std::string>{args.begin(), args.end()};
const auto REQUEST = VGreetdRequest{startSession};
// TODO: Is there a response for this? Should we check it?
if (!sendGreetdRequest(m_socketFD, startSession))
if (!sendGreetdRequest(m_socketFD, REQUEST))
m_ok = false;
else {
if (g_pHyprlock->m_sCurrentDesktop == "Hyprland")
@ -258,18 +229,17 @@ void CGreetdLogin::startSessionAfterSuccess() {
}
void CGreetdLogin::cancelSession() {
NNotJson::SObject cancelSession{
.values =
{
{"type", "cancel_session"},
},
};
SGreetdCancelSession cancelSession;
auto RESPONSEOPT = request(cancelSession);
if (!RESPONSEOPT.has_value())
const auto REQUEST = VGreetdRequest{cancelSession};
const auto RESPONSE = request(REQUEST);
if (!RESPONSE.has_value()) {
Debug::log(ERR, "Failed to cancel session: {}", getErrorString(RESPONSE.error()));
return;
}
handleResponse("cancel_session", RESPONSEOPT.value());
handleResponse(REQUEST, RESPONSE.value());
m_state.authMessageType = GREETD_INITIAL;
m_state.errorType = GREETD_OK;
@ -279,23 +249,20 @@ void CGreetdLogin::createSession() {
if (m_state.authMessageType != GREETD_INITIAL && m_state.errorType != GREETD_ERROR_AUTH)
Debug::log(WARN, "[GreetdLogin] Trying to create a session, but last one still active?");
NNotJson::SObject createSession = {
.values =
{
{"type", "create_session"},
{"username", m_loginUserName},
},
};
Debug::log(LOG, "Creating session for user {}", m_loginUserName);
Debug::log(INFO, "Creating session for user {}", m_loginUserName);
SGreetdCreateSession createSession;
createSession.username = m_loginUserName;
auto RESPONSEOPT = request(createSession);
if (!RESPONSEOPT.has_value()) {
Debug::log(ERR, "Failed to create session: {}", getErrorString(RESPONSEOPT.error()));
const auto REQUEST = VGreetdRequest{createSession};
const auto RESPONSE = request(REQUEST);
if (!RESPONSE.has_value()) {
Debug::log(ERR, "Failed to create session: {}", getErrorString(RESPONSE.error()));
return;
}
handleResponse("create_session", RESPONSEOPT.value());
handleResponse(REQUEST, RESPONSE.value());
}
void CGreetdLogin::processInput() {
@ -309,42 +276,37 @@ void CGreetdLogin::processInput() {
while (m_ok && (m_state.authMessageType == GREETD_AUTH_INFO || m_state.authMessageType == GREETD_AUTH_ERROR)) {
// Empty reply
NNotJson::SObject postAuthMessageResponse{
.values =
{
{"type", "post_auth_message_response"},
{"response", ""},
},
};
auto RESPONSEOPT = request(postAuthMessageResponse);
if (!RESPONSEOPT.has_value()) {
Debug::log(ERR, "Failed to create session: {}", getErrorString(RESPONSEOPT.error()));
SGreetdPostAuthMessageResponse postAuthMessageResponse;
const auto REQUEST = VGreetdRequest{postAuthMessageResponse};
const auto RESPONSE = request(REQUEST);
if (!RESPONSE.has_value()) {
Debug::log(ERR, "Failed to create session: {}", getErrorString(RESPONSE.error()));
return;
}
handleResponse("post_auth_message_response", RESPONSEOPT.value());
handleResponse(REQUEST, RESPONSE.value());
}
if (m_state.errorType != GREETD_OK) {
// TODO: this error message is not good
Debug::log(LOG, "Empty response to a info message failed!");
return;
}
NNotJson::SObject postAuthMessageResponse{
.values =
{
{"type", "post_auth_message_response"},
{"response", m_state.input},
},
};
SGreetdPostAuthMessageResponse postAuthMessageResponse;
postAuthMessageResponse.response = m_state.input;
auto RESPONSEOPT = request(postAuthMessageResponse);
if (!RESPONSEOPT.has_value()) {
Debug::log(ERR, "Failed to send auth response: {}", getErrorString(RESPONSEOPT.error()));
const auto REQUEST = VGreetdRequest{postAuthMessageResponse};
const auto RESPONSE = request(REQUEST);
if (!RESPONSE.has_value()) {
Debug::log(ERR, "Failed to send auth response: {}", getErrorString(RESPONSE.error()));
return;
}
handleResponse("post_auth_message_response", RESPONSEOPT.value());
handleResponse(REQUEST, RESPONSE.value());
};
void CGreetdLogin::waitForInput() {

View file

@ -1,34 +1,12 @@
#pragma once
#include "Auth.hpp"
#include "../helpers/NotJson.hpp"
#include "GreetdProto.hpp"
#include <condition_variable>
#include <string>
#include <expected>
#include <thread>
// GREETD PROTOCOL
enum eGreetdResponse : uint8_t {
GREETD_RESPONSE_UNKNOWN = 0xff,
GREETD_RESPONSE_SUCCESS = 0,
GREETD_RESPONSE_ERROR = 1,
GREETD_RESPONSE_AUTH = 2,
};
enum eGreetdErrorMessageType : uint8_t {
GREETD_OK = 0,
GREETD_ERROR_AUTH = 1,
GREETD_ERROR = 2,
};
enum eGreetdAuthMessageType : uint8_t {
GREETD_INITIAL = 0,
GREETD_AUTH_VISIBLE = 0,
GREETD_AUTH_SECRET = 1,
GREETD_AUTH_INFO = 2,
GREETD_AUTH_ERROR = 3,
};
// INTERNAL
enum eRequestError : uint8_t {
GREETD_REQUEST_ERROR_SEND = 0,
@ -66,7 +44,7 @@ class CGreetdLogin : public IAuthImplementation {
friend class CAuth;
private:
std::expected<NNotJson::SObject, eRequestError> request(const NNotJson::SObject& req);
std::expected<VGreetdResponse, eRequestError> request(const VGreetdRequest& req);
//
void createSession();
@ -74,7 +52,7 @@ class CGreetdLogin : public IAuthImplementation {
void recreateSession();
void startSessionAfterSuccess();
void handleResponse(const std::string& request, NNotJson::SObject& response);
void handleResponse(const VGreetdRequest& request, const VGreetdResponse& response);
void processInput();
void waitForInput();

89
src/auth/GreetdProto.hpp Normal file
View file

@ -0,0 +1,89 @@
#include <cstdint>
#include <string>
#include <vector>
#include <variant>
#include <glaze/glaze.hpp>
#include <glaze/util/string_literal.hpp>
// GREETD PROTOCOL
enum eGreetdResponse : uint8_t {
GREETD_RESPONSE_UNKNOWN = 0xff,
GREETD_RESPONSE_SUCCESS = 0,
GREETD_RESPONSE_ERROR = 1,
GREETD_RESPONSE_AUTH = 2,
};
enum eGreetdErrorMessageType : uint8_t {
GREETD_OK = 0,
GREETD_ERROR_AUTH = 1,
GREETD_ERROR = 2,
};
enum eGreetdAuthMessageType : uint8_t {
GREETD_INITIAL = 0,
GREETD_AUTH_VISIBLE = 1,
GREETD_AUTH_SECRET = 2,
GREETD_AUTH_INFO = 3,
GREETD_AUTH_ERROR = 4,
};
// REQUEST TYPES
struct SGreetdCreateSession {
std::string type = "create_session";
std::string username = "";
};
struct SGreetdPostAuthMessageResponse {
std::string type = "post_auth_message_response";
std::string response = "";
};
struct SGreetdStartSession {
std::string type = "start_session";
std::vector<std::string> cmd;
std::vector<std::string> env;
};
struct SGreetdCancelSession {
std::string type = "cancel_session";
};
// RESPONSE TYPES
struct SGreetdErrorResponse {
eGreetdErrorMessageType error_type;
std::string description;
};
struct SGreetdAuthMessageResponse {
eGreetdAuthMessageType auth_message_type;
std::string auth_message;
};
struct SGreetdSuccessResponse {
char DUMMY; // Without any field in SGreetdSuccessResponse, I get unknown_key for "type".
};
// RESPONSE and REQUEST VARIANTS
using VGreetdRequest = std::variant<SGreetdCreateSession, SGreetdPostAuthMessageResponse, SGreetdStartSession, SGreetdCancelSession>;
using VGreetdResponse = std::variant<SGreetdSuccessResponse, SGreetdErrorResponse, SGreetdAuthMessageResponse>;
template <>
struct glz::meta<eGreetdResponse> {
static constexpr auto value = enumerate("success", GREETD_RESPONSE_SUCCESS, "error", GREETD_RESPONSE_ERROR, "auth_message", GREETD_RESPONSE_AUTH);
};
template <>
struct glz::meta<eGreetdAuthMessageType> {
static constexpr auto value = enumerate("visible", GREETD_AUTH_VISIBLE, "secret", GREETD_AUTH_SECRET, "info", GREETD_AUTH_INFO, "error", GREETD_AUTH_ERROR);
};
template <>
struct glz::meta<eGreetdErrorMessageType> {
static constexpr auto value = enumerate("auth_error", GREETD_ERROR_AUTH, "error", GREETD_ERROR);
};
template <>
struct glz::meta<VGreetdResponse> {
static constexpr std::string_view tag = "type";
static constexpr std::array ids{"success", "error", "auth_message"};
};

View file

@ -1,155 +0,0 @@
#pragma once
/*
This is a "json parser" intended to parse/serialize for the greetd protocol.
It makes the following assumptions:
- Data only contains strings or vectors of strings
- Data is not nested
For example:
{
"key1": "value1",
"key2": ["value2", "value3"]
}
*/
#include <cstdio>
#include <format>
#include <hyprutils/string/String.hpp>
#include <iostream>
#include <sstream>
#include <string>
#include <sys/types.h>
#include <unordered_map>
#include <vector>
#include <variant>
namespace NNotJson {
using VJsonValue = std::variant<std::string, std::vector<std::string>>;
struct SObject {
std::unordered_map<std::string, VJsonValue> values;
};
struct SError {
enum eStatus : u_int8_t {
NOT_JSON_OK,
NOT_JSON_ERROR,
} status = NOT_JSON_OK;
std::string message = "";
};
inline std::pair<SObject, SError> parse(const std::string& data) {
static constexpr const std::string sinkChars = " \t\n\r,:{}";
SObject result{};
std::string key{};
std::vector<std::string> array;
bool parsingArray = false;
for (size_t i = 0; i < data.size(); i++) {
if (sinkChars.find(data[i]) != std::string::npos)
continue;
switch (data[i]) {
case '"': {
// find the next quote that is not escaped
size_t end = i + 1;
for (; end < data.size(); end++) {
if (data[end] == '\\') {
end++;
continue;
}
if (data[end] == '"')
break;
}
if (end == data.size())
return {result,
{
.status = SError::NOT_JSON_ERROR,
.message = "Expected closing quote, but reached end of input",
}};
std::string val{data.data() + i + 1, end - (i + 1)};
Hyprutils::String::replaceInString(val, "\\\"", "\"");
if (key.empty())
key = val;
else if (parsingArray)
array.emplace_back(val);
else {
result.values.emplace(key, val);
key.clear();
}
i = end;
} break;
case '[': {
parsingArray = true;
if (key.empty())
return {result,
{
.status = SError::NOT_JSON_ERROR,
.message = "Expected key before array",
}};
} break;
case ']': {
result.values.emplace(std::string{key}, array);
key = std::string_view{};
parsingArray = false;
array.clear();
} break;
case '\0':
return {result,
{
.status = SError::NOT_JSON_ERROR,
.message = "Encountered null byte ???",
}};
default:
return {result,
{
.status = SError::NOT_JSON_ERROR,
.message = std::format("Unexpected character \"{}\"", data[i]),
}};
}
};
return {result, {}};
}
inline std::string serializeString(const std::string& in) {
std::string escaped = in;
Hyprutils::String::replaceInString(escaped, "\"", "\\\"");
return std::format("\"{}\"", escaped);
}
inline std::string serializeArray(const std::vector<std::string>& in) {
std::stringstream result;
result << "[";
for (const auto& item : in) {
result << serializeString(item) << ",";
}
result.seekp(-1, std::ios_base::end);
result << "]";
return result.str();
}
inline std::string serialize(const SObject& obj) {
std::stringstream result;
result << "{";
for (const auto& [key, value] : obj.values) {
result << std::format("\"{}\":", key);
if (std::holds_alternative<std::string>(value))
result << serializeString(std::get<std::string>(value)) << ",";
else
result << serializeArray(std::get<std::vector<std::string>>(value)) << ",";
}
result.seekp(-1, std::ios_base::end);
result << "}";
return result.str();
}
}

View file

@ -1,65 +0,0 @@
#include <print>
#include <variant>
#include "shared.hpp"
#include "../src/helpers/NotJson.hpp"
int main() {
const auto in = R"({"type":"asdf","array":["a","b","c"]})";
int ret = 0;
auto [result, error] = NNotJson::parse(in);
EXPECT(error.status, NNotJson::SError::NOT_JSON_OK);
EXPECT(result.values.size(), 2);
EXPECT(std::holds_alternative<std::string>(result.values["type"]), true);
EXPECT(std::get<std::string>(result.values["type"]), "asdf");
EXPECT(std::holds_alternative<std::vector<std::string>>(result.values["array"]), true);
const auto vec = std::get<std::vector<std::string>>(result.values["array"]);
EXPECT(vec.size(), 3);
EXPECT(vec[0], std::string{"a"});
EXPECT(vec[1], std::string{"b"});
EXPECT(vec[2], std::string{"c"});
const auto serialized = NNotJson::serialize(result);
std::print("serialized: {}\n", serialized);
// order is not guaranteed
EXPECT(serialized == in || serialized == R"({"array":["a","b","c"],"type":"asdf"})", true);
const auto in2 = R"({"type":"auth_message","auth_message_type":"secret","auth_message":"Password: "})";
auto [result2, error2] = NNotJson::parse(in2);
EXPECT(error2.status, NNotJson::SError::NOT_JSON_OK);
EXPECT(result2.values.size(), 3);
EXPECT(std::holds_alternative<std::string>(result2.values["type"]), true);
EXPECT(std::get<std::string>(result2.values["type"]), "auth_message");
const auto in3 = R"({ "type:"asdf" })";
auto [result3, error3] = NNotJson::parse(in3);
EXPECT(error3.status, NNotJson::SError::NOT_JSON_ERROR);
EXPECT(error3.message, "Unexpected character \"a\"");
const auto in4 = R"({"type":"a\"s\"df"})";
auto [result4, error4] = NNotJson::parse(in4);
EXPECT(error4.status, NNotJson::SError::NOT_JSON_OK);
EXPECT(result4.values.size(), 1);
EXPECT(std::holds_alternative<std::string>(result4.values["type"]), true);
EXPECT(std::get<std::string>(result4.values["type"]), "a\"s\"df");
const auto serialized4 = NNotJson::serialize(result4);
EXPECT(serialized4, in4);
const auto in5 = R"({" *~@#$%^&*()_+=><?/\a":" *~@#$%^&*()_+=><?/\a"})";
auto [result5, error5] = NNotJson::parse(in5);
EXPECT(error5.status, NNotJson::SError::NOT_JSON_OK);
EXPECT(result5.values.size(), 1);
EXPECT(std::holds_alternative<std::string>(result5.values[" *~@#$%^&*()_+=><?/\\a"]), true);
EXPECT(std::get<std::string>(result5.values[" *~@#$%^&*()_+=><?/\\a"]), " *~@#$%^&*()_+=><?/\\a");
std::print("serialized5: {}\n", NNotJson::serialize(result5));
return ret;
}

View file

@ -1,32 +0,0 @@
#pragma once
#include <iostream>
namespace Colors {
constexpr const char* RED = "\x1b[31m";
constexpr const char* GREEN = "\x1b[32m";
constexpr const char* YELLOW = "\x1b[33m";
constexpr const char* BLUE = "\x1b[34m";
constexpr const char* MAGENTA = "\x1b[35m";
constexpr const char* CYAN = "\x1b[36m";
constexpr const char* RESET = "\x1b[0m";
};
#define EXPECT(expr, val) \
if (const auto& RESULT = expr; RESULT != (val)) { \
std::cout << Colors::RED << "Failed: " << Colors::RESET << #expr << ", expected " << val << " but got " << RESULT << "\n"; \
ret = 1; \
} else { \
std::cout << Colors::GREEN << "Passed " << Colors::RESET << #expr << ". Got " << val << "\n"; \
}
#define EXPECT_VECTOR2D(expr, val) \
do { \
const auto& RESULT = expr; \
const auto& EXPECTED = val; \
if (!(std::abs(RESULT.x - EXPECTED.x) < 1e-6 && std::abs(RESULT.y - EXPECTED.y) < 1e-6)) { \
std::cout << Colors::RED << "Failed: " << Colors::RESET << #expr << ", expected (" << EXPECTED.x << ", " << EXPECTED.y << ") but got (" << RESULT.x << ", " \
<< RESULT.y << ")\n"; \
ret = 1; \
} else { \
std::cout << Colors::GREEN << "Passed " << Colors::RESET << #expr << ". Got (" << RESULT.x << ", " << RESULT.y << ")\n"; \
} \
} while (0)