// Glaze Library // For the license information refer to glaze.hpp #pragma once #include #include #include #include #include "glaze/concepts/container_concepts.hpp" #include "glaze/core/context.hpp" #include "glaze/util/inline.hpp" namespace glz { // Primary template for buffer traits // Handles resizable buffers (std::string, std::vector, etc.) template struct buffer_traits { static constexpr bool is_resizable = resizable>; static constexpr bool has_bounded_capacity = !is_resizable && has_size>; static constexpr bool is_output_streaming = false; // True for output buffers that support incremental flushing static constexpr bool is_input_streaming = false; // True for input buffers that support incremental refilling GLZ_ALWAYS_INLINE static constexpr size_t capacity(const Buffer& b) noexcept { if constexpr (is_resizable) { return std::numeric_limits::max(); // Effectively unlimited } else if constexpr (has_size>) { return b.size(); } else { return std::numeric_limits::max(); } } // Attempt to ensure buffer can hold `needed` bytes // Returns true if successful, false if buffer cannot accommodate GLZ_ALWAYS_INLINE static bool ensure_capacity(Buffer& b, size_t needed) noexcept(not is_resizable) { if constexpr (is_resizable) { if (needed > b.size()) { // 2× growth amortizes repeated reallocations to O(n) total cost. b.resize(2 * needed); } return true; } else { return capacity(b) >= needed; } } // Finalize buffer to actual written size GLZ_ALWAYS_INLINE static void finalize(Buffer& b, size_t written) noexcept(not is_resizable) { if constexpr (is_resizable) { b.resize(written); } // For fixed buffers: no-op, count is returned in result } // Flush written data to underlying storage (for streaming buffers) // Default: no-op for regular buffers GLZ_ALWAYS_INLINE static void flush([[maybe_unused]] Buffer& b, [[maybe_unused]] size_t written) noexcept {} }; // Concept to check if a buffer type supports output streaming (flushing) template concept is_output_streaming = buffer_traits>::is_output_streaming; // Flush helper for streaming output buffers template GLZ_ALWAYS_INLINE void flush_buffer(B&& b, size_t written) noexcept { buffer_traits>::flush(b, written); } // Concept to check if a buffer type supports input streaming (refilling) template concept is_input_streaming = buffer_traits>::is_input_streaming; // Concept to check if a buffer type has bounded capacity (like std::array, std::span) // Note: Bounded buffers must be at least 512 bytes for reliable serialization. // Smaller buffers will return error_code::buffer_overflow. template concept has_bounded_capacity = buffer_traits>::has_bounded_capacity; // Refill helper for streaming input buffers // Returns true if buffer has data available after refill, false if EOF template GLZ_ALWAYS_INLINE bool refill_buffer(B&& b) noexcept { if constexpr (is_input_streaming) { return buffer_traits>::refill(b); } else { return false; // Non-streaming buffers cannot refill } } // Consume helper for streaming input buffers // Marks bytes as consumed after successful parsing template GLZ_ALWAYS_INLINE void consume_buffer(B&& b, size_t bytes) noexcept { if constexpr (is_input_streaming) { buffer_traits>::consume(b, bytes); } } // Specialization for raw char pointers // Raw pointers have unknown capacity and must trust the caller template <> struct buffer_traits { static constexpr bool is_resizable = false; static constexpr bool has_bounded_capacity = false; static constexpr bool is_output_streaming = false; static constexpr bool is_input_streaming = false; GLZ_ALWAYS_INLINE static constexpr size_t capacity(char*) noexcept { return std::numeric_limits::max(); } GLZ_ALWAYS_INLINE static constexpr bool ensure_capacity(char*, size_t) noexcept { return true; // Trust caller } GLZ_ALWAYS_INLINE static constexpr void finalize(char*, size_t) noexcept {} }; // Specialization for std::span template struct buffer_traits> { static constexpr bool is_resizable = false; static constexpr bool has_bounded_capacity = true; static constexpr bool is_output_streaming = false; static constexpr bool is_input_streaming = false; GLZ_ALWAYS_INLINE static constexpr size_t capacity(const std::span& b) noexcept { return b.size(); } GLZ_ALWAYS_INLINE static constexpr bool ensure_capacity(const std::span& b, size_t needed) noexcept { return needed <= b.size(); } GLZ_ALWAYS_INLINE static constexpr void finalize(std::span&, size_t) noexcept {} }; // Specialization for std::array template struct buffer_traits> { static constexpr bool is_resizable = false; static constexpr bool has_bounded_capacity = true; static constexpr bool is_output_streaming = false; static constexpr bool is_input_streaming = false; static constexpr size_t static_capacity = N; GLZ_ALWAYS_INLINE static constexpr size_t capacity(const std::array&) noexcept { return N; } GLZ_ALWAYS_INLINE static constexpr bool ensure_capacity(std::array&, size_t needed) noexcept { return needed <= N; } GLZ_ALWAYS_INLINE static constexpr void finalize(std::array&, size_t) noexcept {} }; // Unified buffer space checking for write operations // Handles resizable buffers (resize), bounded buffers (error on overflow), and raw pointers (trust caller) template GLZ_ALWAYS_INLINE bool ensure_space(is_context auto& ctx, B& b, size_t required) noexcept(not vector_like>) { using Buffer = std::remove_cvref_t; if constexpr (vector_like) { if (required > b.size()) [[unlikely]] { // 2× growth amortizes repeated reallocations to O(n) total cost. b.resize(2 * required); } return true; } else if constexpr (has_bounded_capacity) { if (required > buffer_traits::capacity(b)) [[unlikely]] { ctx.error = error_code::buffer_overflow; return false; } return true; } else { return true; // Raw pointer or other - trust caller } } }