Skip to content
Merged
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
14 changes: 11 additions & 3 deletions .github/actions/cmake/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,19 @@ runs:
env:
BUILD_TYPE: "${{ inputs.build_type }}"
run: |
# Conan names its single-config presets after the lowercased build type.
# Conan names its build and test presets after the lowercased build type. With a
# single-config generator the configure preset shares that name; with the multi-config
# Visual Studio one on Windows there is a single `conan-default` configure preset.
PRESET="conan-$(echo "${BUILD_TYPE}" | tr '[:upper:]' '[:lower:]')"
cmake --preset "${PRESET}" \
CONFIGURE_PRESET="${PRESET}"
BUILD_DIR="build/${BUILD_TYPE}"
if [[ "${RUNNER_OS}" == "Windows" ]]; then
CONFIGURE_PRESET="conan-default"
BUILD_DIR="build"
fi
cmake --preset "${CONFIGURE_PRESET}" \
-Drpcspec_tests=ON \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON
# Export both for later steps: ctest needs the preset, clang-tidy the build dir.
echo "RPCSPEC_PRESET=${PRESET}" >>"${GITHUB_ENV}"
echo "RPCSPEC_BUILD_DIR=build/${BUILD_TYPE}" >>"${GITHUB_ENV}"
echo "RPCSPEC_BUILD_DIR=${BUILD_DIR}" >>"${GITHUB_ENV}"
10 changes: 8 additions & 2 deletions .github/actions/conan/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,17 @@ runs:
BUILD_TYPE: "${{ inputs.build_type }}"
CONAN_BUILD_OPTION: "${{ inputs.force_conan_source_build == 'true' && '*' || 'missing' }}"
run: |
# `cmake_layout` puts the generators under build/<build_type>/generators, which is
# what the conan-<build_type> CMake presets below point at, so no -of is passed here.
# `cmake_layout` puts the generators where the conan-* CMake presets point, so no -of
# is passed here: build/<build_type>/generators for single-config generators, and
# build/generators for the multi-config Visual Studio one on Windows.
EXTRA_CONF=()
if [[ "${RUNNER_OS}" == "Windows" ]]; then
EXTRA_CONF=(-c "tools.cmake.cmaketoolchain:generator=Visual Studio 18 2026")
fi
conan \
install . \
-b "${CONAN_BUILD_OPTION}" \
-o '&:tests=True' \
-s "build_type=${BUILD_TYPE}" \
"${EXTRA_CONF[@]}" \
--profile:all ci
10 changes: 9 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,18 @@ jobs:
compiler: apple-clang
build_type: Debug
container: ""
- os: [self-hosted, Windows, dev-box-windows-2026]
compiler: msvc
build_type: Release
container: ""
- os: [self-hosted, Windows, dev-box-windows-2026]
compiler: msvc
build_type: Debug
container: ""

uses: ./.github/workflows/reusable-build-test.yml
with:
runs_on: ${{ matrix.os }}
runs_on: ${{ toJSON(matrix.os) }}
container: ${{ matrix.container }}
compiler: ${{ matrix.compiler }}
build_type: ${{ matrix.build_type }}
8 changes: 4 additions & 4 deletions .github/workflows/reusable-build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
workflow_call:
inputs:
runs_on:
description: Runner to run the job on
description: Runner label or list of labels to run the job on, as JSON
required: true
type: string

Expand All @@ -14,7 +14,7 @@ on:
type: string

compiler:
description: 'Compiler to build with ("gcc", "clang" or "apple-clang")'
description: 'Compiler to build with ("gcc", "clang", "apple-clang" or "msvc")'
required: true
type: string

Expand All @@ -30,12 +30,12 @@ defaults:
jobs:
build-and-test:
name: Build and test
runs-on: ${{ inputs.runs_on }}
runs-on: ${{ fromJSON(inputs.runs_on) }}
container: ${{ inputs.container != '' && fromJson(inputs.container) || null }}

steps:
- name: Cleanup workspace
if: ${{ runner.os == 'macOS' }}
if: ${{ runner.os == 'macOS' || runner.os == 'Windows' }}
uses: XRPLF/actions/cleanup-workspace@c7d9ce5ebb03c752a354889ecd870cadfc2b1cd4

- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand Down
8 changes: 7 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.20)
project(xrpl-rpc-spec VERSION 0.1.18 LANGUAGES CXX)
project(xrpl-rpc-spec VERSION 0.1.19 LANGUAGES CXX)

add_library(rpcspec INTERFACE)
add_library(rpcspec::rpcspec ALIAS rpcspec)
Expand All @@ -18,6 +18,12 @@ find_package(Boost REQUIRED COMPONENTS json)

target_link_libraries(rpcspec INTERFACE Boost::json)

# Boost's headers ask MSVC to auto-link version-mangled library names that Conan's packages
# don't use. Link the real targets only, as xrpld does.
if(TARGET Boost::disable_autolinking)
target_link_libraries(rpcspec INTERFACE Boost::disable_autolinking)
endif()

# The spec proper must not name a JSON library: consumers supply the view types it reads
# requests through (see include/rpcspec/backends for the one shipped implementation, and
# the ObjectViewFor customization point in Concepts.hpp). Checked here so the invariant
Expand Down
10 changes: 10 additions & 0 deletions conan/profiles/ci
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,13 @@ include(default)
user.package:libc_version=2.31
tools.info.package_id:confs+=["user.package:libc_version"]
{% endif %}

{% if os == "Windows" %}
{# rippled is the only project publishing Windows binaries; any Boost option that differs from #}
{# its build is a different package ID, and Boost then builds from source here. #}
[options]
boost/*:without_cobalt=True
boost/*:without_context=False
boost/*:without_coroutine=True
boost/*:without_coroutine2=False
{% endif %}
10 changes: 5 additions & 5 deletions include/admissionspec/Types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ struct AdmissionDecision
* @return The comparison result.
*/
bool
operator<=>(AdmissionDecision const&) const = default;
operator==(AdmissionDecision const&) const = default;
};

/**
Expand Down Expand Up @@ -210,7 +210,7 @@ struct FixedString
* @return The comparison result.
*/
bool
operator<=>(FixedString const&) const = default;
operator==(FixedString const&) const = default;
};

template <size_t N>
Expand All @@ -230,7 +230,7 @@ struct SizeTier
* @return The comparison result.
*/
bool
operator<=>(SizeTier const&) const = default;
operator==(SizeTier const&) const = default;
};

/**
Expand Down Expand Up @@ -265,7 +265,7 @@ struct SizeCostRamp
* @return The comparison result.
*/
bool
operator<=>(SizeCostRamp const&) const = default;
operator==(SizeCostRamp const&) const = default;
};

template <size_t N>
Expand Down Expand Up @@ -343,7 +343,7 @@ struct Tunable
* @return The comparison result.
*/
bool
operator<=>(Tunable const&) const = default;
operator==(Tunable const&) const = default;
};

/**
Expand Down
12 changes: 6 additions & 6 deletions include/rpcspec/Converters.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,13 @@ struct AccountIdConverter
if (not fieldView.isString())
{
return std::unexpected{rpc::Status{
rpc::RippledError::RpcInvalidParams, std::string{fieldView.key()} + "NotString"}};
rpc::XrpldError::RpcInvalidParams, std::string{fieldView.key()} + "NotString"}};
}
auto id = detail::accountFromStringStrict(std::string{fieldView.asString()});
if (not id.has_value())
{
return std::unexpected{rpc::Status{
rpc::RippledError::RpcActMalformed, std::string{fieldView.key()} + "Malformed"}};
rpc::XrpldError::RpcActMalformed, std::string{fieldView.key()} + "Malformed"}};
}
return *id;
}
Expand Down Expand Up @@ -204,7 +204,7 @@ struct JsonBoolConverterT
if constexpr (Strict)
{
if (not fieldView.isBool())
return std::unexpected{rpc::Status{rpc::RippledError::RpcInvalidParams}};
return std::unexpected{rpc::Status{rpc::XrpldError::RpcInvalidParams}};
return JsonBool{fieldView.asBool()};
}
else
Expand Down Expand Up @@ -261,7 +261,7 @@ struct Uint32Converter
parse(View const& fieldView) const
{
if (not fieldView.isUint32())
return std::unexpected{rpc::Status{rpc::RippledError::RpcInvalidParams}};
return std::unexpected{rpc::Status{rpc::XrpldError::RpcInvalidParams}};
return fieldView.asUint32();
}
};
Expand Down Expand Up @@ -293,7 +293,7 @@ struct StringConverter
parse(View const& fieldView) const
{
if (not fieldView.isString())
return std::unexpected{rpc::Status{rpc::RippledError::RpcInvalidParams}};
return std::unexpected{rpc::Status{rpc::XrpldError::RpcInvalidParams}};
return std::string{fieldView.asString()};
}
};
Expand Down Expand Up @@ -335,7 +335,7 @@ struct AccountIdActMalformedConverter
id.has_value())
return *id;
}
return std::unexpected{rpc::Status{rpc::RippledError::RpcActMalformed}};
return std::unexpected{rpc::Status{rpc::XrpldError::RpcActMalformed}};
}
};

Expand Down
24 changes: 12 additions & 12 deletions include/rpcspec/Errors.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,24 +51,24 @@ enum class ClioError {
#endif

/**
* @brief Clio uses compatible Rippled error codes for most RPC errors.
* @brief xrpld's RPC error codes, which Clio also uses for most of its errors.
*/
using RippledError = xrpl::ErrorCodeI;
using XrpldError = xrpl::ErrorCodeI;

#if defined(RPCSPEC_IS_CLIO)
/**
* @brief Clio operates on a combination of Rippled and custom Clio error codes.
* @brief Clio operates on a combination of xrpld and custom Clio error codes.
*
* @see RippledError For xrpld error codes
* @see XrpldError For xrpld error codes
* @see ClioError For custom clio error codes
*/
using CombinedError = std::variant<RippledError, ClioError>;
using CombinedError = std::variant<XrpldError, ClioError>;
#else
/**
* @brief In xrpld builds the only error surface is xrpld's own; there are no Clio codes.
* @see RippledError For xrpld error codes
* @see XrpldError For xrpld error codes
*/
using CombinedError = std::variant<RippledError>;
using CombinedError = std::variant<XrpldError>;
#endif

// TODO: these are possibly worth unifying at some point instead of trying to keep separated and
Expand Down Expand Up @@ -373,23 +373,23 @@ struct Status
*/
operator bool() const
{
if (auto err = std::get_if<RippledError>(&code))
if (auto err = std::get_if<XrpldError>(&code))
return *err != xrpl::RpcSuccess;

return true;
}

/**
* @brief Returns true if the @ref rpc::Status contains the desired @ref
* rpc::RippledError
* rpc::XrpldError
*
* @param other The @ref rpc::RippledError to match
* @param other The @ref rpc::XrpldError to match
* @return true if status matches given error; false otherwise
*/
bool
operator==(RippledError other) const
operator==(XrpldError other) const
{
if (auto err = std::get_if<RippledError>(&code))
if (auto err = std::get_if<XrpldError>(&code))
return *err == other;

return false;
Expand Down
19 changes: 14 additions & 5 deletions include/rpcspec/FieldSpec.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
#include <rpcspec/Concepts.hpp>
#include <rpcspec/Types.hpp>

#include <cstddef>
#include <string_view>
#include <tuple>
#include <utility>

namespace rpc::spec {

Expand Down Expand Up @@ -148,11 +150,7 @@ struct FieldSpec
[[nodiscard]] consteval auto
operator|(Item item) const
{
return std::apply(
[&](auto const&... existing) {
return FieldSpec<Items..., Item>{key, existing..., item};
},
items);
return appendItem(item, std::index_sequence_for<Items...>{});
}

/**
Expand Down Expand Up @@ -225,6 +223,17 @@ struct FieldSpec
runChecks(items, childView, out);
return out;
}

private:
// Expanded with an index sequence rather than std::apply and a lambda: the lambda is not an
// immediate function, so calling a consteval constructor from it relies on P2564, which
// MSVC does not implement.
template <typename Item, std::size_t... Is>
[[nodiscard]] consteval auto
appendItem(Item item, std::index_sequence<Is...>) const
{
return FieldSpec<Items..., Item>{key, std::get<Is>(items)..., item};
}
};

/**
Expand Down
14 changes: 12 additions & 2 deletions include/rpcspec/RpcSpec.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -282,12 +282,22 @@ RpcSpec(Fs...) -> RpcSpec<Fs...>;
* @param extra Additional fields appended after the base fields.
* @return A new `RpcSpec` combining base and extra fields.
*/
namespace detail {

template <typename... Existing, std::size_t... Is, typename... Extra>
[[nodiscard]] consteval auto
extendUntyped(RpcSpec<Existing...> const& base, std::index_sequence<Is...>, Extra... extra)
{
return RpcSpec{std::get<Is>(base.fields)..., extra...};
}

} // namespace detail

template <typename... Existing, typename... Extra>
[[nodiscard]] consteval auto
extend(RpcSpec<Existing...> const& base, Extra... extra)
{
return std::apply(
[&](auto const&... existing) { return RpcSpec{existing..., extra...}; }, base.fields);
return detail::extendUntyped(base, std::index_sequence_for<Existing...>{}, extra...);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion include/rpcspec/Section.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ struct Section
if (not fieldView.present())
return {};
if (not fieldView.isObject())
return std::unexpected{rpc::Status{rpc::RippledError::RpcInvalidParams}};
return std::unexpected{rpc::Status{rpc::XrpldError::RpcInvalidParams}};

MaybeError result{};
std::apply(
Expand Down
Loading
Loading