Skip to content

Commit f30b3ec

Browse files
authored
Add const representation for standard header names
1 parent 4211b60 commit f30b3ec

File tree

2 files changed

+132
-40
lines changed

2 files changed

+132
-40
lines changed

async-nats/src/header.rs

Lines changed: 127 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -15,29 +15,6 @@
1515
1616
use std::{collections::HashMap, fmt, slice, str::FromStr};
1717

18-
use serde::Serialize;
19-
20-
pub const NATS_LAST_STREAM: &str = "Nats-Last-Stream";
21-
pub const NATS_LAST_CONSUMER: &str = "Nats-Last-Consumer";
22-
23-
/// Direct Get headers
24-
pub const NATS_STREAM: &str = "Nats-Stream";
25-
pub const NATS_SEQUENCE: &str = "Nats-Sequence";
26-
pub const NATS_TIME_STAMP: &str = "Nats-Time-Stamp";
27-
pub const NATS_SUBJECT: &str = "Nats-Subject";
28-
pub const NATS_LAST_SEQUENCE: &str = "Nats-Last-Sequence";
29-
30-
/// Nats-Expected-Last-Subject-Sequence
31-
pub const NATS_EXPECTED_LAST_SUBJECT_SEQUENCE: &str = "Nats-Expected-Last-Subject-Sequence";
32-
/// Message identifier used for deduplication window
33-
pub const NATS_MESSAGE_ID: &str = "Nats-Msg-Id";
34-
/// Last expected message ID for JetStream message publish
35-
pub const NATS_EXPECTED_LAST_MESSAGE_ID: &str = "Nats-Expected-Last-Msg-Id";
36-
/// Last expected sequence for JetStream message publish
37-
pub const NATS_EXPECTED_LAST_SEQUENCE: &str = "Nats-Expected-Last-Sequence";
38-
/// Expect that given message will be ingested by specified stream.
39-
pub const NATS_EXPECTED_STREAM: &str = "Nats-Expected-Stream";
40-
4118
/// A struct for handling NATS headers.
4219
/// Has a similar API to [http::header], but properly serializes and deserializes
4320
/// according to NATS requirements.
@@ -56,7 +33,7 @@ pub const NATS_EXPECTED_STREAM: &str = "Nats-Expected-Stream";
5633
/// # Ok(())
5734
/// # }
5835
/// ```
59-
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Default)]
36+
#[derive(Clone, PartialEq, Eq, Debug, Default)]
6037
pub struct HeaderMap {
6138
inner: HashMap<HeaderName, HeaderValue>,
6239
}
@@ -154,7 +131,7 @@ impl HeaderMap {
154131
buf.extend_from_slice(b"NATS/1.0\r\n");
155132
for (k, vs) in &self.inner {
156133
for v in vs.iter() {
157-
buf.extend_from_slice(k.value.as_bytes());
134+
buf.extend_from_slice(k.as_str().as_bytes());
158135
buf.extend_from_slice(b": ");
159136
buf.extend_from_slice(v.as_bytes());
160137
buf.extend_from_slice(b"\r\n");
@@ -179,7 +156,7 @@ impl HeaderMap {
179156
/// # Ok(())
180157
/// # }
181158
/// ```
182-
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Default)]
159+
#[derive(Clone, PartialEq, Eq, Debug, Default)]
183160
pub struct HeaderValue {
184161
value: Vec<String>,
185162
}
@@ -288,7 +265,7 @@ pub trait IntoHeaderName {
288265
impl IntoHeaderName for &str {
289266
fn into_header_name(self) -> HeaderName {
290267
HeaderName {
291-
value: self.to_string(),
268+
inner: HeaderRepr::Custom(self.to_string()),
292269
}
293270
}
294271
}
@@ -316,10 +293,120 @@ impl IntoHeaderValue for HeaderValue {
316293
}
317294
}
318295

319-
#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize)]
296+
macro_rules! standard_headers {
297+
(
298+
$(
299+
$(#[$docs:meta])*
300+
($variant:ident, $constant:ident, $bytes:literal);
301+
)+
302+
) => {
303+
#[allow(clippy::enum_variant_names)]
304+
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
305+
enum StandardHeader {
306+
$(
307+
$variant,
308+
)+
309+
}
310+
311+
$(
312+
$(#[$docs])*
313+
pub const $constant: HeaderName = HeaderName {
314+
inner: HeaderRepr::Standard(StandardHeader::$variant),
315+
};
316+
)+
317+
318+
impl StandardHeader {
319+
#[inline]
320+
fn as_str(&self) -> &'static str {
321+
match *self {
322+
$(
323+
StandardHeader::$variant => unsafe { std::str::from_utf8_unchecked( $bytes ) },
324+
)+
325+
}
326+
}
327+
328+
const fn from_bytes(bytes: &[u8]) -> Option<StandardHeader> {
329+
match bytes {
330+
$(
331+
$bytes => Some(StandardHeader::$variant),
332+
)+
333+
_ => None,
334+
}
335+
}
336+
}
337+
338+
#[cfg(test)]
339+
mod standard_header_tests {
340+
use super::HeaderName;
341+
use std::str::{self, FromStr};
342+
343+
const TEST_HEADERS: &'static [(&'static HeaderName, &'static [u8])] = &[
344+
$(
345+
(&super::$constant, $bytes),
346+
)+
347+
];
348+
349+
#[test]
350+
fn from_str() {
351+
for &(header, bytes) in TEST_HEADERS {
352+
let utf8 = str::from_utf8(bytes).expect("string constants isn't utf8");
353+
assert_eq!(HeaderName::from_str(utf8).unwrap(), *header);
354+
}
355+
}
356+
}
357+
}
358+
}
359+
360+
// Generate constants for all standard NATS headers.
361+
standard_headers! {
362+
/// The name of the stream the message belongs to.
363+
(NatsStream, NATS_STREAM, b"Nats-Stream");
364+
/// The sequence number of the message within the stream.
365+
(NatsSequence, NATS_SEQUENCE, b"Nats-Sequence");
366+
/// The timestamp of when the message was sent.
367+
(NatsTimeStamp, NATS_TIME_STAMP, b"Nats-Time-Stamp");
368+
/// The subject of the message, used for routing and filtering messages.
369+
(NatsSubject, NATS_SUBJECT, b"Nats-Subject");
370+
/// A unique identifier for the message.
371+
(NatsMessageId, NATS_MESSAGE_ID, b"Nats-Msg-Id");
372+
/// The last known stream the message was part of.
373+
(NatsLastStream, NATS_LAST_STREAM, b"Nats-Last-Stream");
374+
/// The last known consumer that processed the message.
375+
(NatsLastConsumer, NATS_LAST_CONSUMER, b"Nats-Last-Consumer");
376+
/// The last known sequence number of the message.
377+
(NatsLastSequence, NATS_LAST_SEQUENCE, b"Nats-Last-Sequence");
378+
/// The expected last sequence number of the subject.
379+
(NatsExpectgedLastSubjectSequence, NATS_EXPECTED_LAST_SUBJECT_SEQUENCE, b"Nats-Expected-Last-Subject-Sequence");
380+
/// The expected last message ID within the stream.
381+
(NatsExpectedLastMessageId, NATS_EXPECTED_LAST_MESSAGE_ID, b"Nats-Expected-Last-Msg-Id");
382+
/// The expected last sequence number within the stream.
383+
(NatsExpectedLastSequence, NATS_EXPECTED_LAST_SEQUENCE, b"Nats-Expected-Last-Sequence");
384+
/// The expected stream the message should be part of.
385+
(NatsExpectedStream, NATS_EXPECTED_STREAM, b"Nats-Expected-Stream");
386+
}
387+
388+
#[derive(Debug, Hash, PartialEq, Eq, Clone)]
389+
enum HeaderRepr {
390+
Standard(StandardHeader),
391+
Custom(String),
392+
}
393+
394+
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
320395
pub struct HeaderName {
321-
value: String,
396+
inner: HeaderRepr,
322397
}
398+
399+
impl HeaderName {
400+
/// Returns a `str` representation of the header.
401+
#[inline]
402+
fn as_str(&self) -> &str {
403+
match self.inner {
404+
HeaderRepr::Standard(v) => v.as_str(),
405+
HeaderRepr::Custom(ref v) => v.as_str(),
406+
}
407+
}
408+
}
409+
323410
impl FromStr for HeaderName {
324411
type Err = ParseHeaderNameError;
325412

@@ -328,27 +415,32 @@ impl FromStr for HeaderName {
328415
return Err(ParseHeaderNameError);
329416
}
330417

331-
Ok(HeaderName {
332-
value: s.to_string(),
333-
})
418+
match StandardHeader::from_bytes(s.as_ref()) {
419+
Some(v) => Ok(HeaderName {
420+
inner: HeaderRepr::Standard(v),
421+
}),
422+
None => Ok(HeaderName {
423+
inner: HeaderRepr::Custom(s.to_string()),
424+
}),
425+
}
334426
}
335427
}
336428

337429
impl fmt::Display for HeaderName {
338430
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
339-
fmt::Display::fmt(&self.value, f)
431+
fmt::Display::fmt(&self.as_str(), f)
340432
}
341433
}
342434

343435
impl AsRef<[u8]> for HeaderName {
344436
fn as_ref(&self) -> &[u8] {
345-
self.value.as_bytes()
437+
self.as_str().as_bytes()
346438
}
347439
}
348440

349441
impl AsRef<str> for HeaderName {
350442
fn as_ref(&self) -> &str {
351-
self.value.as_ref()
443+
self.as_str()
352444
}
353445
}
354446

async-nats/tests/jetstream_tests.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ mod jetstream {
3131

3232
use super::*;
3333
use async_nats::connection::State;
34-
use async_nats::header::{HeaderMap, NATS_MESSAGE_ID};
34+
use async_nats::header::{self, HeaderMap, NATS_MESSAGE_ID};
3535
use async_nats::jetstream::consumer::{
3636
self, AckPolicy, DeliverPolicy, Info, OrderedPushConsumer, PullConsumer, PushConsumer,
3737
};
@@ -680,7 +680,7 @@ mod jetstream {
680680
.headers
681681
.as_ref()
682682
.unwrap()
683-
.get("Nats-Sequence")
683+
.get(header::NATS_SEQUENCE)
684684
.unwrap()
685685
.iter()
686686
.next()
@@ -739,7 +739,7 @@ mod jetstream {
739739
.headers
740740
.as_ref()
741741
.unwrap()
742-
.get("Nats-Sequence")
742+
.get(header::NATS_SEQUENCE)
743743
.unwrap()
744744
.iter()
745745
.next()
@@ -813,7 +813,7 @@ mod jetstream {
813813
.headers
814814
.as_ref()
815815
.unwrap()
816-
.get("Nats-Sequence")
816+
.get(header::NATS_SEQUENCE)
817817
.unwrap()
818818
.iter()
819819
.next()
@@ -882,7 +882,7 @@ mod jetstream {
882882
.headers
883883
.as_ref()
884884
.unwrap()
885-
.get("Nats-Sequence")
885+
.get(header::NATS_SEQUENCE)
886886
.unwrap()
887887
.iter()
888888
.next()

0 commit comments

Comments
 (0)