Skip to content

glz::json_t trait support #391

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .github/actions/install/stephenberry-glaze/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: Install Glaze
description: Install Glaze header-only library for building test applications
inputs:
version:
description: The desired Glaze version to install
required: false
default: "5.5.4"
runs:
using: composite
steps:
- run: |
cd /tmp
wget https://github.com/stephenberry/glaze/archive/refs/tags/v${{ inputs.version }}.tar.gz
tar -zxf /tmp/v${{ inputs.version }}.tar.gz
cd glaze-${{ inputs.version }}
cmake . -B build
cd build
sudo cmake --install .
shell: bash
1 change: 1 addition & 0 deletions .github/workflows/jwt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ jobs:
- uses: ./.github/actions/install/danielaparker-jsoncons
- uses: ./.github/actions/install/boost-json
- uses: ./.github/actions/install/open-source-parsers-jsoncpp
- uses: ./.github/actions/install/stephenberry-glaze

- name: configure
run: cmake --preset coverage
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/traits.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ jobs:
- { name: "nlohmann-json", tag: "3.12.0", version: "v3.12.0" }
- { name: "kazuho-picojson", tag: "111c9be5188f7350c2eac9ddaedd8cca3d7bf394", version: "111c9be" }
- { name: "open-source-parsers-jsoncpp", tag: "1.9.6", version: "v1.9.6" }
- { name: "stephenberry-glaze", tag: "5.5.5", version: "v5.5.5" }
steps:
- uses: actions/checkout@v4
- uses: lukka/get-cmake@latest
Expand Down Expand Up @@ -56,6 +57,11 @@ jobs:
with:
version: ${{matrix.target.tag}}

- if: matrix.target.name == 'stephenberry-glaze'
uses: ./.github/actions/install/stephenberry-glaze
with:
version: ${{matrix.target.tag}}

- name: test
working-directory: example/traits
run: |
Expand Down
6 changes: 6 additions & 0 deletions example/traits/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,9 @@ if(TARGET jsoncpp_static)
add_executable(open-source-parsers-jsoncpp open-source-parsers-jsoncpp.cpp)
target_link_libraries(open-source-parsers-jsoncpp jsoncpp_static jwt-cpp::jwt-cpp)
endif()

find_package(glaze CONFIG)
if(TARGET glaze::glaze)
add_executable(stephenberry-glaze stephenberry-glaze.cpp)
target_link_libraries(stephenberry-glaze glaze::glaze jwt-cpp::jwt-cpp)
endif()
65 changes: 65 additions & 0 deletions example/traits/stephenberry-glaze.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#include "jwt-cpp/traits/stephenberry-glaze/traits.h"
#include <chrono>
#include <iostream>
#include <sstream>
#include <vector>

int main() {
using sec = std::chrono::seconds;
using min = std::chrono::minutes;

using traits = jwt::traits::stephenberry_glaze;
using claim = jwt::basic_claim<traits>;

// Parse raw JSON into claim
claim from_raw_json;
std::istringstream iss{R"##({"api":{"array":[1,2,3],"null":null}})##"};
from_raw_json = jwt::basic_claim<jwt::traits::stephenberry_glaze>(
*glz::read_json<jwt::traits::stephenberry_glaze::value_type>(iss.str()));
// iss >> from_raw_json; // no >> for glaze

// Example claim sets
claim::set_t list{"once", "twice"};
std::vector<int64_t> big_numbers{727663072LL, 770979831LL, 427239169LL, 525936436LL};

// JWT creation
const auto time = jwt::date::clock::now();
const auto token = jwt::create<traits>()
.set_type("JWT")
.set_issuer("auth.mydomain.io")
.set_audience("mydomain.io")
.set_issued_at(time)
.set_not_before(time)
.set_expires_at(time + min{2} + sec{15})
.set_payload_claim("boolean", true)
.set_payload_claim("integer", 12345)
.set_payload_claim("precision", 12.3456789)
.set_payload_claim("strings", claim(list))
.set_payload_claim("array", claim{big_numbers.begin(), big_numbers.end()})
.set_payload_claim("object", from_raw_json)
.sign(jwt::algorithm::none{});

// Decode
const auto decoded = jwt::decode<traits>(token);

// Access array inside the payload object
const auto array =
traits::as_array(decoded.get_payload_claim("object").to_json().get_object()["api"].get_object()["array"]);
// std::cout << "payload /object/api/array = " << array << '\n';
std::cout << "payload /object/api/array = [ ";
for (size_t i = 0; i < array.size(); ++i) {
std::cout << array[i].dump().value_or("error");
if (i + 1 < array.size()) std::cout << ", ";
}
std::cout << " ]\n";

// Verification
jwt::verify<traits>()
.allow_algorithm(jwt::algorithm::none{})
.with_issuer("auth.mydomain.io")
.with_audience("mydomain.io")
.with_claim("object", from_raw_json)
.verify(decoded);

return 0;
}
91 changes: 91 additions & 0 deletions include/jwt-cpp/traits/stephenberry-glaze/defaults.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#ifndef JWT_CPP_STEPHENBERRY_GLAZE_DEFAULTS_H
#define JWT_CPP_STEPHENBERRY_GLAZE_DEFAULTS_H

#ifndef JWT_DISABLE_PICOJSON
#define JWT_DISABLE_PICOJSON
#endif

#include "traits.h"

namespace jwt {
/**
* \brief a class to store a generic [glz::json_t](https://github.com/stephenberry/glaze/) value as claim
*
* This type is the specialization of the \ref basic_claim class which
* uses the standard template types.
*/
using claim = basic_claim<traits::stephenberry_glaze>;

/**
* Create a verifier using the default clock
* \return verifier instance
*/
inline verifier<default_clock, traits::stephenberry_glaze> verify() {
return verify<default_clock, traits::stephenberry_glaze>(default_clock{});
}

/**
* Create a builder using the default clock
* \return builder instance to create a new token
*/
inline builder<default_clock, traits::stephenberry_glaze> create() {
return builder<default_clock, traits::stephenberry_glaze>(default_clock{});
}

#ifndef JWT_DISABLE_BASE64
/**
* Decode a token
* \param token Token to decode
* \return Decoded token
* \throw std::invalid_argument Token is not in correct format
* \throw std::runtime_error Base64 decoding failed or invalid json
*/
inline decoded_jwt<traits::stephenberry_glaze> decode(const std::string& token) {
return decoded_jwt<traits::stephenberry_glaze>(token);
}
#endif

/**
* Decode a token
* \tparam Decode is callable, taking a string_type and returns a string_type.
* It should ensure the padding of the input and then base64url decode and
* return the results.
* \param token Token to decode
* \param decode The token to parse
* \return Decoded token
* \throw std::invalid_argument Token is not in correct format
* \throw std::runtime_error Base64 decoding failed or invalid json
*/
template<typename Decode>
decoded_jwt<traits::stephenberry_glaze> decode(const std::string& token, Decode decode) {
return decoded_jwt<traits::stephenberry_glaze>(token, decode);
}

/**
* Parse a jwk
* \param token JWK Token to parse
* \return Parsed JWK
* \throw std::runtime_error Token is not in correct format
*/
inline jwk<traits::stephenberry_glaze> parse_jwk(const traits::stephenberry_glaze::string_type& token) {
return jwk<traits::stephenberry_glaze>(token);
}

/**
* Parse a jwks
* \param token JWKs Token to parse
* \return Parsed JWKs
* \throw std::runtime_error Token is not in correct format
*/
inline jwks<traits::stephenberry_glaze> parse_jwks(const traits::stephenberry_glaze::string_type& token) {
return jwks<traits::stephenberry_glaze>(token);
}

/**
* This type is the specialization of the \ref verify_ops::verify_context class which
* uses the standard template types.
*/
using verify_context = verify_ops::verify_context<traits::stephenberry_glaze>;
} // namespace jwt

#endif // JWT_CPP_STEPHENBERRY_GLAZE_DEFAULTS_H
84 changes: 84 additions & 0 deletions include/jwt-cpp/traits/stephenberry-glaze/traits.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#ifndef JWT_CPP_STEPHENBERRY_GLAZE_TRAITS_H
#define JWT_CPP_STEPHENBERRY_GLAZE_TRAITS_H

#include "jwt-cpp/jwt.h"
#include <glaze/glaze.hpp>

namespace jwt {
/**
* \brief Namespace containing all the json_trait implementations for a jwt::basic_claim.
*/
namespace traits {
struct stephenberry_glaze {
using json = glz::json_t;
using value_type = json; // ← ключевое
using object_type = json::object_t; // map<string, json_t>
using array_type = json::array_t; // vector<json_t>
using string_type = std::string;
using number_type = double;
using integer_type = std::int64_t;
using boolean_type = bool;

static jwt::json::type get_type(const value_type& val) {
using jwt::json::type;

if (val.is_object()) return type::object;
if (val.is_array()) return type::array;
if (val.is_string()) return type::string;
if (val.is_boolean()) return type::boolean;

// Если у json_t нет отдельного integer-типа:
if (val.is_number()) return type::number;

if (val.is_null()) throw std::logic_error("invalid type: null");
throw std::logic_error("invalid type");
}

static object_type as_object(const value_type& val) {
if (!val.is_object()) throw std::bad_cast();
return std::get<object_type>(val.data);
}

static array_type as_array(const value_type& val) {
if (!val.is_array()) throw std::bad_cast();
return std::get<array_type>(val.data);
}

static string_type as_string(const value_type& val) {
if (!val.is_string()) throw std::bad_cast();
return std::get<std::string>(val.data);
}

static number_type as_number(const value_type& val) {
if (!val.is_number()) throw std::bad_cast();
return std::get<double>(val.data);
}

static integer_type as_integer(const value_type& val) {
if (!val.is_number()) throw std::bad_cast();
double d = std::get<double>(val.data);
// optional: ensure it's an exact int64
auto i = static_cast<integer_type>(d);
if (static_cast<double>(i) != d) throw std::bad_cast();
return i;
}

static boolean_type as_boolean(const value_type& val) {
if (!val.is_boolean()) throw std::bad_cast();
return std::get<bool>(val.data);
}

static bool parse(value_type& val, string_type str) {
if (auto r = glz::read_json(val, str); r) { return false; }
return true;
}

static string_type serialize(const value_type& val) {
if (auto r = glz::write_json(val); r) return *r;
throw std::runtime_error("serialize failed");
}
};
} // namespace traits
} // namespace jwt

#endif // JWT_CPP_STEPHENBERRY_GLAZE_TRAITS_H
8 changes: 8 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ if(TARGET jsoncpp_static)
list(APPEND TEST_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/traits/OspJsoncppTest.cpp)
endif()

find_package(glaze CONFIG)
if(TARGET glaze::glaze)
list(APPEND TEST_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/traits/StephenberryGlazeTest.cpp)
endif()

add_executable(jwt-cpp-test ${TEST_SOURCES})

# NOTE: Don't use space inside a generator expression here, because the function prematurely breaks the expression into
Expand All @@ -69,6 +74,9 @@ else()
if(TARGET jsoncpp_static)
target_link_libraries(jwt-cpp-test PRIVATE jsoncpp_static)
endif()
if (TARGET glaze::glaze)
target_link_libraries(jwt-cpp-test PRIVATE glaze::glaze)
endif()
endif()
target_link_libraries(jwt-cpp-test PRIVATE jwt-cpp nlohmann_json::nlohmann_json
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:${CMAKE_DL_LIBS}>)
Expand Down
Loading
Loading