Skip to content

Commit 8264bcf

Browse files
committed
st: review nits N1-N7
N1 Expected operator*/operator-> are not UB on an error: operator* is noexcept + std::get so it terminates; operator-> returns nullptr. Correct the docs to say so. N2 Drop redundant unit prose ("in milliseconds"/"in seconds") from std::chrono fields/params in Policies, Consumer (AckPolicy) and the sendTimeout setter; the type already states the unit. (ProducerConfig's int64 sendTimeoutMs keeps its "milliseconds" note - it is not a chrono type.) N3 decodeBigEndian: replace the dead `i < data.size()` guard (all codecs length-check first) with an assert of that precondition. N4 ProtobufNativeSchema: guard the size_t->int narrowing in encode/decode, rejecting messages larger than INT_MAX instead of passing a wrapped size. N5 OutgoingMessage: one-line note for the usesView<->payloadView invariant. N7 Wrap the SerDeFor concept in clang-format off/on so clang-format-11 stops mangling the `{ expr } -> Concept;` compound requirements. N6 Normalize config-struct field docs to the dominant /** */-before style (OutgoingMessage, CheckpointConsumerConfig, Stream/QueueConsumerConfig); enum-value ///< trailing docs are left as-is. Verified: clang-format-11 clean; examples compile (clang); N3 runtime test and N4 (protobuf stub) pass on clang and gcc. Signed-off-by: Matteo Merli <mmerli@apache.org>
1 parent db63962 commit 8264bcf

9 files changed

Lines changed: 107 additions & 88 deletions

File tree

include/pulsar/st/CheckpointConsumer.h

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,18 @@ namespace pulsar::st {
4545
* not construct this directly.
4646
*/
4747
struct CheckpointConsumerConfig {
48-
std::string topic; ///< Scalable topic to read. REQUIRED; no default.
49-
Checkpoint startPosition =
50-
Checkpoint::latest(); ///< Position to start from. Default `Checkpoint::latest()`.
51-
std::optional<std::string>
52-
consumerGroup; ///< Consumer group to join. Unset (default) => ungrouped, reads every segment.
53-
std::optional<std::string>
54-
consumerName; ///< Human-readable consumer name. Unset (default) => auto-generated.
55-
Properties properties; ///< Free-form key/value metadata attached to the consumer. Default empty.
56-
SchemaInfo schema; ///< Schema descriptor; filled in from `Schema<T>` by the builder.
48+
/** Scalable topic to read. REQUIRED; no default. */
49+
std::string topic;
50+
/** Position to start from. Default `Checkpoint::latest()`. */
51+
Checkpoint startPosition = Checkpoint::latest();
52+
/** Consumer group to join. Unset (default) => ungrouped, reads every segment. */
53+
std::optional<std::string> consumerGroup;
54+
/** Human-readable consumer name. Unset (default) => auto-generated. */
55+
std::optional<std::string> consumerName;
56+
/** Free-form key/value metadata attached to the consumer. Default empty. */
57+
Properties properties;
58+
/** Schema descriptor; filled in from `Schema<T>` by the builder. */
59+
SchemaInfo schema;
5760
};
5861

5962
template <typename T>

include/pulsar/st/Consumer.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,10 @@ enum class SubscriptionInitialPosition
5252
* optional and fall back to the client default when unset.
5353
*/
5454
struct AckPolicy {
55-
/** Time window over which acknowledgments are batched before being sent, in milliseconds; 0 acks
55+
/** Time window over which acknowledgments are batched before being sent; 0 acks
5656
* immediately. Unset uses the client default. */
5757
std::optional<std::chrono::milliseconds> groupTime = std::nullopt;
58-
/** Delay before a negatively-acknowledged message is redelivered, in milliseconds. QueueConsumer only.
58+
/** Delay before a negatively-acknowledged message is redelivered. QueueConsumer only.
5959
* Unset uses the client default. */
6060
std::optional<std::chrono::milliseconds> negativeAckRedeliveryDelay = std::nullopt;
6161
};

include/pulsar/st/Expected.h

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,10 @@ class [[nodiscard]] Expected {
139139
/**
140140
* Unchecked access to the contained value.
141141
*
142-
* Unlike `value()`, this never throws and performs no check. Behaviour is
143-
* undefined if this holds an error; verify with `operator bool` first.
142+
* Performs no check and is `noexcept`. It does not throw on an error; instead,
143+
* because it reads the wrong `std::variant` alternative through a `noexcept`
144+
* boundary, accessing the value when this holds an error terminates the program.
145+
* Verify with `operator bool` first.
144146
*
145147
* @return a reference to the contained value (lvalue or rvalue per ref-qualifier).
146148
*/
@@ -153,10 +155,10 @@ class [[nodiscard]] Expected {
153155
/**
154156
* Unchecked member access to the contained value.
155157
*
156-
* Behaviour is undefined if this holds an error; verify with `operator bool`
157-
* first.
158+
* Returns `nullptr` if this holds an error (so `e->member` would then dereference
159+
* a null pointer); verify with `operator bool` first.
158160
*
159-
* @return a pointer to the contained value.
161+
* @return a pointer to the contained value, or `nullptr` if this holds an error.
160162
*/
161163
const T* operator->() const noexcept { return std::get_if<0>(&storage_); }
162164
/** @copydoc operator->() const */

include/pulsar/st/Policies.h

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -90,21 +90,21 @@ struct MemorySize {
9090
struct ConnectionPolicy {
9191
/** Number of physical connections opened to each broker. Unset uses the client default. */
9292
std::optional<int> connectionsPerBroker = std::nullopt;
93-
/** Maximum time to wait for a TCP/TLS connection to be established, in milliseconds. Unset uses the
93+
/** Maximum time to wait for a TCP/TLS connection to be established. Unset uses the
9494
* client default. */
9595
std::optional<std::chrono::milliseconds> connectionTimeout = std::nullopt;
96-
/** Maximum time to wait for a broker request (e.g. produce/consume control ops) to complete, in
97-
* milliseconds. Unset uses the client default. */
96+
/** Maximum time to wait for a broker request (e.g. produce/consume control ops) to complete. Unset
97+
* uses the client default. */
9898
std::optional<std::chrono::milliseconds> operationTimeout = std::nullopt;
99-
/** Interval between keep-alive pings sent on an idle connection, in seconds. Unset uses the client
99+
/** Interval between keep-alive pings sent on an idle connection. Unset uses the client
100100
* default. */
101101
std::optional<std::chrono::seconds> keepAliveInterval = std::nullopt;
102102
/** Maximum number of concurrent topic-lookup requests in flight. Unset uses the client default. */
103103
std::optional<int> maxLookupRequests = std::nullopt;
104104
/** Maximum number of lookup redirects to follow before failing a lookup. Unset uses the client default.
105105
*/
106106
std::optional<int> maxLookupRedirects = std::nullopt;
107-
/** Time an idle pooled connection may stay open before being closed, in milliseconds. Unset uses the
107+
/** Time an idle pooled connection may stay open before being closed. Unset uses the
108108
* client default. */
109109
std::optional<std::chrono::milliseconds> maxConnectionIdleTime = std::nullopt;
110110
/** Advertised listener name for broker discovery (multi-listener deployments). Unset uses the
@@ -120,9 +120,9 @@ struct ConnectionPolicy {
120120
* applies its built-in default for that bound.
121121
*/
122122
struct BackoffPolicy {
123-
/** Delay before the first reconnection attempt, in milliseconds. Unset uses the client default. */
123+
/** Delay before the first reconnection attempt. Unset uses the client default. */
124124
std::optional<std::chrono::milliseconds> initialBackoff = std::nullopt;
125-
/** Upper bound on the backoff delay as it grows across retries, in milliseconds. Unset uses the client
125+
/** Upper bound on the backoff delay as it grows across retries. Unset uses the client
126126
* default. */
127127
std::optional<std::chrono::milliseconds> maxBackoff = std::nullopt;
128128
};
@@ -161,7 +161,7 @@ struct TlsPolicy {
161161
* optional and the client supplies a built-in default when it is unset.
162162
*/
163163
struct TransactionPolicy {
164-
/** Default lifetime of a transaction before it is automatically aborted, in milliseconds. Unset uses the
164+
/** Default lifetime of a transaction before it is automatically aborted. Unset uses the
165165
* client default. */
166166
std::optional<std::chrono::milliseconds> timeout = std::nullopt;
167167
};

include/pulsar/st/Producer.h

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,10 @@ struct ProducerConfig {
101101
* This is the encoded, schema-agnostic form of a message: the typed value has
102102
* already been serialized to `payload` bytes. The builder fills these fields from
103103
* its fluent setters and hands the result to the producer core for publishing.
104+
*
105+
* Invariant (maintained by `MessageBuilder`): when `usesView` is true the payload is
106+
* read from `payloadView` (which must stay valid until the send completes); otherwise
107+
* it is read from `payload`.
104108
*/
105109
struct OutgoingMessage {
106110
/** Encoded message payload — the value serialized to bytes through `Schema<T>`.
@@ -115,12 +119,16 @@ struct OutgoingMessage {
115119
std::optional<std::string> key;
116120
/** Per-message user metadata. Empty by default. */
117121
Properties properties;
118-
std::optional<Timestamp> eventTime; ///< Application event time; unset (nullopt) by default.
119-
std::optional<int64_t> sequenceId; ///< Explicit sequence id; unset = auto-assign.
120-
std::optional<Timestamp> deliverAt; ///< Scheduled delivery time; unset = deliver immediately.
122+
/** Application event time; unset (nullopt) by default. */
123+
std::optional<Timestamp> eventTime;
124+
/** Explicit sequence id; unset = auto-assign. */
125+
std::optional<int64_t> sequenceId;
126+
/** Scheduled delivery time; unset = deliver immediately. */
127+
std::optional<Timestamp> deliverAt;
121128
/** Target clusters for geo-replication; empty applies the topic's default. */
122129
std::vector<std::string> replicationClusters;
123-
std::optional<Transaction> transaction; ///< Enlisting transaction; unset = non-transactional.
130+
/** Enlisting transaction; unset = non-transactional. */
131+
std::optional<Transaction> transaction;
124132
};
125133

126134
template <typename T>
@@ -451,8 +459,7 @@ class ProducerBuilder {
451459
* Set the per-message send timeout: how long a send may stay unacknowledged
452460
* before failing.
453461
*
454-
* @param d the timeout, in milliseconds. Optional; when unset the SDK default
455-
* applies.
462+
* @param d the timeout. Optional; when unset the SDK default applies.
456463
* @return `*this`, for chaining.
457464
*/
458465
ProducerBuilder& sendTimeout(std::chrono::milliseconds d) {

include/pulsar/st/ProtobufNativeSchema.h

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include <pulsar/st/Schema.h>
2525

2626
#include <cstddef>
27+
#include <limits>
2728
#include <span>
2829
#include <string>
2930
#include <type_traits>
@@ -41,12 +42,17 @@ struct ProtobufNativeSerDe {
4142
"protobufNativeSchema<T> requires T to be a generated protobuf Message");
4243
SchemaInfo info() const { return pulsar::createProtobufNativeSchema(T::descriptor()); }
4344
Expected<void> encode(const T& value, std::vector<std::byte>& out) const {
44-
out.resize(value.ByteSizeLong());
45-
if (!value.SerializeToArray(out.data(), static_cast<int>(out.size())))
45+
const std::size_t size = value.ByteSizeLong();
46+
if (size > static_cast<std::size_t>(std::numeric_limits<int>::max()))
47+
return unexpected(pulsar::ResultInvalidMessage, "protobuf message too large to serialize");
48+
out.resize(size);
49+
if (!value.SerializeToArray(out.data(), static_cast<int>(size)))
4650
return unexpected(pulsar::ResultInvalidMessage, "failed to serialize protobuf message");
4751
return {};
4852
}
4953
Expected<T> decode(std::span<const std::byte> data) const {
54+
if (data.size() > static_cast<std::size_t>(std::numeric_limits<int>::max()))
55+
return unexpected(pulsar::ResultInvalidMessage, "protobuf message too large to parse");
5056
T message;
5157
if (message.ParseFromArray(data.data(), static_cast<int>(data.size()))) return message;
5258
return unexpected(pulsar::ResultInvalidMessage, "failed to parse protobuf message");

include/pulsar/st/QueueConsumer.h

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -55,38 +55,38 @@ namespace pulsar::st {
5555
* connecting. Fields not selected by `useNamespace` are ignored.
5656
*/
5757
struct QueueConsumerConfig {
58-
/// Selects namespace mode over single-topic mode. When `false` (the default),
59-
/// `topic` is used; when `true`, `namespaceName` (and `propertyFilters`) apply.
58+
/** Selects namespace mode over single-topic mode. When `false` (the default),
59+
* `topic` is used; when `true`, `namespaceName` (and `propertyFilters`) apply. */
6060
bool useNamespace = false;
61-
/// Fully-qualified topic name. Used only when `useNamespace == false`. Mutually
62-
/// exclusive with `namespaceName`.
61+
/** Fully-qualified topic name. Used only when `useNamespace == false`. Mutually
62+
* exclusive with `namespaceName`. */
6363
std::string topic; // when !useNamespace
64-
/// Namespace name (`tenant/namespace`). Used only when `useNamespace == true`.
65-
/// Subscribes to all scalable topics in the namespace with live membership.
64+
/** Namespace name (`tenant/namespace`). Used only when `useNamespace == true`.
65+
* Subscribes to all scalable topics in the namespace with live membership. */
6666
std::string namespaceName; // when useNamespace
67-
/// Namespace mode only: AND filters matched against topic properties to select
68-
/// which topics in the namespace are included. Empty means no filtering (all
69-
/// topics). Ignored in single-topic mode.
67+
/** Namespace mode only: AND filters matched against topic properties to select
68+
* which topics in the namespace are included. Empty means no filtering (all
69+
* topics). Ignored in single-topic mode. */
7070
Properties propertyFilters; // namespace mode: AND filters over topic properties
71-
/// REQUIRED. Subscription name shared by all consumers of this subscription.
71+
/** REQUIRED. Subscription name shared by all consumers of this subscription. */
7272
std::string subscriptionName; // REQUIRED
73-
/// Where the subscription starts when it is first created. Default
74-
/// `SubscriptionInitialPosition::Latest` (skip the backlog). Has no effect once
75-
/// the subscription already exists.
73+
/** Where the subscription starts when it is first created. Default
74+
* `SubscriptionInitialPosition::Latest` (skip the backlog). Has no effect once
75+
* the subscription already exists. */
7676
SubscriptionInitialPosition initialPosition = SubscriptionInitialPosition::Latest;
77-
/// Optional consumer name (useful for diagnostics and metrics). Default unset, in
78-
/// which case the broker assigns one.
77+
/** Optional consumer name (useful for diagnostics and metrics). Default unset, in
78+
* which case the broker assigns one. */
7979
std::optional<std::string> consumerName;
80-
/// Acknowledgment tuning (e.g. the ack-grouping/batching window and negative-ack
81-
/// redelivery delay). Default-constructed `AckPolicy` when unset.
80+
/** Acknowledgment tuning (e.g. the ack-grouping/batching window and negative-ack
81+
* redelivery delay). Default-constructed `AckPolicy` when unset. */
8282
AckPolicy ackPolicy;
83-
/// Optional dead-letter policy: route messages to a dead-letter topic after
84-
/// repeated redelivery. Default unset (no dead-lettering).
83+
/** Optional dead-letter policy: route messages to a dead-letter topic after
84+
* repeated redelivery. Default unset (no dead-lettering). */
8585
std::optional<DeadLetterPolicy> deadLetterPolicy;
86-
/// Arbitrary client-side consumer properties (reported in topic stats). Default empty.
86+
/** Arbitrary client-side consumer properties (reported in topic stats). Default empty. */
8787
Properties properties;
88-
/// Schema descriptor for the value type `T`. Populated automatically by the builder
89-
/// from the `Schema<T>` it was constructed with.
88+
/** Schema descriptor for the value type `T`. Populated automatically by the builder
89+
* from the `Schema<T>` it was constructed with. */
9090
SchemaInfo schema;
9191
};
9292

include/pulsar/st/Schema.h

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include <pulsar/st/Error.h>
2525
#include <pulsar/st/Expected.h>
2626

27+
#include <cassert>
2728
#include <concepts>
2829
#include <cstddef>
2930
#include <cstdint>
@@ -81,16 +82,15 @@ using BytesView = std::span<const std::byte>;
8182
* @tparam S the candidate SerDe type.
8283
* @tparam T the value type the SerDe handles.
8384
*/
85+
// clang-format off
8486
template <typename S, typename T>
8587
concept SerDeFor = requires(const S& serde, const T& value, std::span<const std::byte> data,
8688
std::vector<std::byte>& out) {
87-
{ serde.info() }
88-
->std::convertible_to<SchemaInfo>;
89-
{ serde.encode(value, out) }
90-
->std::convertible_to<Expected<void>>;
91-
{ serde.decode(data) }
92-
->std::convertible_to<Expected<T>>;
89+
{ serde.info() } -> std::convertible_to<SchemaInfo>;
90+
{ serde.encode(value, out) } -> std::convertible_to<Expected<void>>;
91+
{ serde.decode(data) } -> std::convertible_to<Expected<T>>;
9392
};
93+
// clang-format on
9494

9595
/**
9696
* `Schema<T>` is the typed seam of the API: `Producer<T>`, `Consumer<T>` and
@@ -223,8 +223,9 @@ inline void encodeBigEndian(U value, std::vector<std::byte>& out) {
223223
template <typename U>
224224
inline U decodeBigEndian(std::span<const std::byte> data) {
225225
static_assert(std::is_integral_v<U>, "integral only");
226+
assert(data.size() >= sizeof(U) && "callers validate the payload length before decoding");
226227
std::make_unsigned_t<U> u = 0;
227-
for (std::size_t i = 0; i < sizeof(U) && i < data.size(); ++i) {
228+
for (std::size_t i = 0; i < sizeof(U); ++i) {
228229
u = (u << 8) | std::to_integer<unsigned char>(data[i]);
229230
}
230231
return static_cast<U>(u);

0 commit comments

Comments
 (0)