iceberg-cpp
Loading...
Searching...
No Matches
string_util.h
1/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20#pragma once
21
22#include <algorithm>
23#include <cctype>
24#include <cerrno>
25#include <charconv>
26#include <ranges>
27#include <string>
28#include <string_view>
29#include <type_traits>
30#include <typeinfo>
31#include <utility>
32#include <vector>
33
34#include "iceberg/iceberg_export.h"
35#include "iceberg/result.h"
36
37namespace iceberg {
38
39template <typename T>
40concept FromChars = requires(const char* p, T& v) { std::from_chars(p, p, v); };
41
42class ICEBERG_EXPORT StringUtils {
43 public:
44 // NOTE: These convert ASCII letters only; all other bytes, including non-ASCII
45 // (multibyte UTF-8) bytes, are passed through unchanged.
46 // See https://github.com/apache/iceberg-cpp/issues/613.
47 static std::string ToLower(std::string_view str) {
48 return str | std::ranges::views::transform(ToLowerAscii) |
49 std::ranges::to<std::string>();
50 }
51
52 static std::string ToUpper(std::string_view str) {
53 return str | std::ranges::views::transform(ToUpperAscii) |
54 std::ranges::to<std::string>();
55 }
56
57 static bool EqualsIgnoreCase(std::string_view lhs, std::string_view rhs) {
58 return std::ranges::equal(
59 lhs, rhs, [](char lc, char rc) { return ToLowerAscii(lc) == ToLowerAscii(rc); });
60 }
61
62 static bool StartsWithIgnoreCase(std::string_view str, std::string_view prefix) {
63 if (str.size() < prefix.size()) {
64 return false;
65 }
66 return EqualsIgnoreCase(str.substr(0, prefix.size()), prefix);
67 }
68
70 static size_t CodePointCount(std::string_view str) {
71 size_t count = 0;
72 for (char i : str) {
73 if ((i & 0xC0) != 0x80) {
74 count++;
75 }
76 }
77 return count;
78 }
79
80 template <typename T>
81 requires std::is_arithmetic_v<T> && FromChars<T> && (!std::same_as<T, bool>)
82 static Result<T> ParseNumber(std::string_view str) {
83 T value = 0;
84 auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), value);
85 if (ec == std::errc()) [[likely]] {
86 if (ptr != str.data() + str.size()) {
87 return InvalidArgument("Failed to parse {} from string '{}': trailing characters",
88 typeid(T).name(), str);
89 }
90 return value;
91 }
92 if (ec == std::errc::invalid_argument) {
93 return InvalidArgument("Failed to parse {} from string '{}': invalid argument",
94 typeid(T).name(), str);
95 }
96 if (ec == std::errc::result_out_of_range) {
97 return InvalidArgument("Failed to parse {} from string '{}': value out of range",
98 typeid(T).name(), str);
99 }
100 std::unreachable();
101 }
102
105 static Result<std::vector<uint8_t>> HexStringToBytes(std::string_view hex);
106
107 template <typename T>
108 requires std::is_floating_point_v<T> && (!FromChars<T>)
109 static Result<T> ParseNumber(std::string_view str) {
110 T value{};
111 // strto* require null-terminated input; string_view does not guarantee it.
112 std::string owned(str);
113 const char* start = owned.c_str();
114 char* end = nullptr;
115 errno = 0;
116
117 if constexpr (std::same_as<T, float>) {
118 value = std::strtof(start, &end);
119 } else if constexpr (std::same_as<T, double>) {
120 value = std::strtod(start, &end);
121 } else {
122 value = std::strtold(start, &end);
123 }
124
125 if (end == start || end != start + static_cast<std::ptrdiff_t>(owned.size())) {
126 return InvalidArgument("Failed to parse {} from string '{}': invalid argument",
127 typeid(T).name(), str);
128 }
129 if (errno == ERANGE) {
130 return InvalidArgument("Failed to parse {} from string '{}': value out of range",
131 typeid(T).name(), str);
132 }
133 return value;
134 }
135
136 private:
137 // ASCII-only case conversion using explicit range checks rather than
138 // std::tolower/std::toupper. This is independent of the current C locale and never
139 // touches non-ASCII (high-bit) bytes, so multibyte UTF-8 sequences are preserved. It
140 // also sidesteps the undefined behavior of passing a negative char to <cctype>.
141 static constexpr char ToLowerAscii(char c) noexcept {
142 return (c >= 'A' && c <= 'Z') ? static_cast<char>(c - 'A' + 'a') : c;
143 }
144
145 static constexpr char ToUpperAscii(char c) noexcept {
146 return (c >= 'a' && c <= 'z') ? static_cast<char>(c - 'a' + 'A') : c;
147 }
148};
149
154struct ICEBERG_EXPORT StringHash {
155 using hash_type = std::hash<std::string_view>;
156 using is_transparent = void;
157
158 std::size_t operator()(std::string_view str) const { return hash_type{}(str); }
159 std::size_t operator()(const char* str) const { return hash_type{}(str); }
160 std::size_t operator()(const std::string& str) const { return hash_type{}(str); }
161};
162
164struct ICEBERG_EXPORT StringEqual {
165 using is_transparent = void;
166
167 bool operator()(std::string_view lhs, std::string_view rhs) const { return lhs == rhs; }
168 bool operator()(const std::string& lhs, const std::string& rhs) const {
169 return lhs == rhs;
170 }
171};
172
173} // namespace iceberg
Definition string_util.h:42
static size_t CodePointCount(std::string_view str)
Count the number of code points in a UTF-8 string.
Definition string_util.h:70
Definition string_util.h:40
Transparent equality function that supports std::string_view as lookup key.
Definition string_util.h:164
Transparent hash function that supports std::string_view as lookup key.
Definition string_util.h:154