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(PkgConfig REQUIRED)
find_package(OpenGL REQUIRED COMPONENTS EGL GLES3) find_package(OpenGL REQUIRED COMPONENTS EGL GLES3)
find_package(hyprwayland-scanner 0.4.4 REQUIRED) find_package(hyprwayland-scanner 0.4.4 REQUIRED)
find_package(glaze REQUIRED)
pkg_check_modules( pkg_check_modules(
deps deps
REQUIRED REQUIRED
@ -93,7 +94,7 @@ pkg_check_modules(
file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp") file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp")
add_executable(hyprlock ${SRCFILES}) add_executable(hyprlock ${SRCFILES})
target_link_libraries(hyprlock PRIVATE pam rt Threads::Threads PkgConfig::deps target_link_libraries(hyprlock PRIVATE pam rt Threads::Threads PkgConfig::deps
OpenGL::EGL OpenGL::GLES3) OpenGL::EGL OpenGL::GLES3 glaze::glaze)
# protocols # protocols
pkg_get_variable(WAYLAND_PROTOCOLS_DIR wayland-protocols pkgdatadir) pkg_get_variable(WAYLAND_PROTOCOLS_DIR wayland-protocols pkgdatadir)
@ -150,15 +151,3 @@ install(
FILES ${CMAKE_SOURCE_DIR}/assets/example.conf FILES ${CMAKE_SOURCE_DIR}/assets/example.conf
DESTINATION ${CMAKE_INSTALL_FULL_DATAROOTDIR}/hypr DESTINATION ${CMAKE_INSTALL_FULL_DATAROOTDIR}/hypr
RENAME hyprlock.conf) 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, lib,
stdenv, stdenv,
cmake, cmake,
pkg-config,
cairo, cairo,
glaze,
pkg-config,
libdrm, libdrm,
libGL, libGL,
libxkbcommon, libxkbcommon,
@ -37,6 +38,7 @@ stdenv.mkDerivation {
buildInputs = [ buildInputs = [
cairo cairo
glaze
libdrm libdrm
libGL libGL
libxkbcommon libxkbcommon

View file

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

View file

@ -1,34 +1,12 @@
#pragma once #pragma once
#include "Auth.hpp" #include "Auth.hpp"
#include "../helpers/NotJson.hpp" #include "GreetdProto.hpp"
#include <condition_variable> #include <condition_variable>
#include <string> #include <string>
#include <expected> #include <expected>
#include <thread> #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 // INTERNAL
enum eRequestError : uint8_t { enum eRequestError : uint8_t {
GREETD_REQUEST_ERROR_SEND = 0, GREETD_REQUEST_ERROR_SEND = 0,
@ -66,7 +44,7 @@ class CGreetdLogin : public IAuthImplementation {
friend class CAuth; friend class CAuth;
private: private:
std::expected<NNotJson::SObject, eRequestError> request(const NNotJson::SObject& req); std::expected<VGreetdResponse, eRequestError> request(const VGreetdRequest& req);
// //
void createSession(); void createSession();
@ -74,7 +52,7 @@ class CGreetdLogin : public IAuthImplementation {
void recreateSession(); void recreateSession();
void startSessionAfterSuccess(); void startSessionAfterSuccess();
void handleResponse(const std::string& request, NNotJson::SObject& response); void handleResponse(const VGreetdRequest& request, const VGreetdResponse& response);
void processInput(); void processInput();
void waitForInput(); 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)