ruststream_nats/jetstream.rs
1//! `JetStream` publishing: the [`JetStreamPublish`] policy, its [`JetStreamOptions`] per-message
2//! settings, and the live [`JetStreamPublisher`].
3//!
4//! `JetStream` publishing is a different contract from Core NATS, not a mode of it: the server
5//! answers every publish with an acknowledgement, and the message may state what it expects the
6//! stream to look like. Both live on this pair, so the Core publisher keeps the fire-and-forget
7//! shape the transport actually has.
8
9// Without the `testing` feature a publisher's link has one variant; see `broker.rs`.
10#![cfg_attr(
11 not(feature = "testing"),
12 allow(clippy::infallible_destructuring_match)
13)]
14
15use std::fmt::{Debug, Formatter};
16use std::future::{Future, ready};
17use std::sync::Arc;
18
19use async_nats::jetstream::Context;
20use async_nats::jetstream::message::PublishMessage;
21use bytes::BytesMut;
22#[cfg(feature = "testing")]
23use ruststream::HeaderMap;
24#[cfg(feature = "asyncapi")]
25use ruststream::asyncapi::{Binding, Bindings};
26use ruststream::runtime::{PublishBuilder, PublishSink};
27use ruststream::{OutgoingMessage, PairError, PublishPolicy, Publisher, Take};
28#[cfg(feature = "asyncapi")]
29use serde::Serialize;
30
31use crate::broker::{ConnectedNatsBroker, Link, NatsConnection};
32#[cfg(feature = "testing")]
33use crate::in_process::{Bus, Origin, PublishMode};
34use crate::publisher::NatsPublishPolicy;
35#[cfg(feature = "asyncapi")]
36use crate::subject::JETSTREAM_EXTENSION;
37use crate::{convert::nats_parts, error::NatsError};
38
39/// The acknowledgement a `JetStream` stream returns for an accepted publish.
40pub use async_nats::jetstream::publish::PublishAck;
41
42/// The `JetStream` protocol header names, as `async-nats` writes them on the wire.
43#[cfg(feature = "testing")]
44pub(crate) const MESSAGE_ID: &str = "Nats-Msg-Id";
45#[cfg(feature = "testing")]
46pub(crate) const EXPECTED_LAST_SEQUENCE: &str = "Nats-Expected-Last-Sequence";
47#[cfg(feature = "testing")]
48pub(crate) const EXPECTED_LAST_SUBJECT_SEQUENCE: &str = "Nats-Expected-Last-Subject-Sequence";
49#[cfg(feature = "testing")]
50pub(crate) const EXPECTED_LAST_MESSAGE_ID: &str = "Nats-Expected-Last-Msg-Id";
51#[cfg(feature = "testing")]
52pub(crate) const EXPECTED_STREAM: &str = "Nats-Expected-Stream";
53
54/// What one `JetStream` publish states about itself, over and above its subject and payload.
55///
56/// Every field is optional, and a call carries only what it set. These are protocol fields the
57/// server reads, not headers of the application: a deduplication id, and the three expectations
58/// that hold an optimistic-concurrency chain together.
59///
60/// A publish that violates an expectation is refused by the stream, and the publish returns
61/// [`NatsError::JetStream`]. That is the point of stating one: the writer learns that the stream
62/// moved under it instead of appending over someone else's message.
63///
64/// The fields are public because a test names the value the publish carried, as
65/// `tb.out::<Marker>().assert_called_once().with_options(&JetStreamOptions { .. })`.
66///
67/// # Examples
68///
69/// ```
70/// # #[cfg(all(feature = "testing"))]
71/// # mod demo {
72/// use std::error::Error;
73///
74/// use ruststream::testing::TestApp;
75/// use ruststream_nats::prelude::*;
76/// use serde::{Deserialize, Serialize};
77///
78/// #[derive(Debug, PartialEq, Deserialize, Serialize, Outgoing)]
79/// struct Order {
80/// id: u64,
81/// }
82///
83/// #[derive(Serialize, Outgoing)]
84/// #[outgoing(name = "archive.orders")]
85/// struct Archived {
86/// id: u64,
87/// }
88///
89/// #[derive(OutSlot)]
90/// #[publishes(Archived)]
91/// struct Archive;
92///
93/// #[subscriber("orders.created")]
94/// async fn archive(
95/// order: &Order,
96/// Out(out): Out<impl Publisher<Options = JetStreamOptions>, Archive>,
97/// ) -> HandlerOutcome {
98/// let sent = out
99/// .message(&Archived { id: order.id })
100/// .message_id(format!("order-{}", order.id))
101/// .publish()
102/// .await;
103/// if sent.is_err() {
104/// return HandlerOutcome::retry();
105/// }
106/// HandlerOutcome::ack()
107/// }
108///
109/// pub fn app() -> impl App {
110/// RustStream::new(AppInfo::new("archive", "0.1.0"))
111/// .with_broker(NatsBroker::new("nats://localhost:4222"), |b| {
112/// b.include(archive)
113/// .out(Archive, JetStreamPublish::default().expect_stream("ARCHIVE"))
114/// .build();
115/// })
116/// }
117///
118/// pub async fn archives_once_per_order() -> Result<(), Box<dyn Error>> {
119/// let tb = TestApp::start(app()).await?;
120///
121/// tb.broker::<NatsBroker>()
122/// .message(&Order { id: 7 })
123/// .to("orders.created")
124/// .publish()
125/// .await?;
126///
127/// tb.out::<Archive>()
128/// .assert_called_once()
129/// .with_options(&JetStreamOptions {
130/// message_id: Some("order-7".into()),
131/// ..JetStreamOptions::default()
132/// });
133/// tb.shutdown().await?;
134/// Ok(())
135/// }
136/// # }
137/// # #[cfg(all(feature = "testing"))]
138/// # #[tokio::main]
139/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
140/// # demo::archives_once_per_order().await
141/// # }
142/// # #[cfg(not(all(feature = "testing")))]
143/// # fn main() {}
144/// ```
145#[derive(Debug, Clone, Default, PartialEq, Eq)]
146pub struct JetStreamOptions {
147 /// The `Nats-Msg-Id` the stream's deduplication window recognises a repeat by.
148 pub message_id: Option<String>,
149 /// Requires the stream's last sequence to be this at the moment of the publish.
150 pub expect_last_sequence: Option<u64>,
151 /// Requires the last sequence *on the published subject* to be this.
152 pub expect_last_subject_sequence: Option<u64>,
153 /// Requires the stream's last `Nats-Msg-Id` to be this.
154 pub expect_last_message_id: Option<String>,
155}
156
157impl JetStreamOptions {
158 /// Writes the fields this value set as the `JetStream` protocol headers a server reads.
159 ///
160 /// The real publisher lets `async-nats` write them; the in-process transport has no such
161 /// builder, so it writes the same headers itself, and its stream reads them as a server
162 /// does.
163 #[cfg(feature = "testing")]
164 pub(crate) fn write_headers(&self, headers: &mut HeaderMap) {
165 if let Some(id) = &self.message_id {
166 headers.insert(MESSAGE_ID, id.clone());
167 }
168 if let Some(sequence) = self.expect_last_sequence {
169 headers.insert(EXPECTED_LAST_SEQUENCE, sequence.to_string());
170 }
171 if let Some(sequence) = self.expect_last_subject_sequence {
172 headers.insert(EXPECTED_LAST_SUBJECT_SEQUENCE, sequence.to_string());
173 }
174 if let Some(id) = &self.expect_last_message_id {
175 headers.insert(EXPECTED_LAST_MESSAGE_ID, id.clone());
176 }
177 }
178
179 /// Writes the fields this value set onto one outgoing `JetStream` message.
180 pub(crate) fn apply(&self, mut message: PublishMessage) -> PublishMessage {
181 if let Some(id) = &self.message_id {
182 message = message.message_id(id);
183 }
184 if let Some(sequence) = self.expect_last_sequence {
185 message = message.expected_last_sequence(sequence);
186 }
187 if let Some(sequence) = self.expect_last_subject_sequence {
188 message = message.expected_last_subject_sequence(sequence);
189 }
190 if let Some(id) = &self.expect_last_message_id {
191 message = message.expected_last_message_id(id);
192 }
193 message
194 }
195}
196
197/// The per-message `JetStream` settings, as steps on the publish builder.
198///
199/// A handler body that adjusts one of these names this crate's prelude and bounds its slot
200/// `Out<impl Publisher<Options = JetStreamOptions>, Marker>`. That body is tied to `JetStream`,
201/// and its signature says so; a body that publishes plainly stays on the framework prelude alone.
202///
203/// The steps sit on the builder rather than on a publisher of their own, so the publish they
204/// finish still travels through the mount site's entry, with the codec and the transforms that
205/// entry named.
206///
207/// # Examples
208///
209/// ```no_run
210/// use ruststream_nats::prelude::*;
211/// # use serde::Serialize;
212/// #[derive(Serialize, Outgoing)]
213/// struct Archived {
214/// id: u64,
215/// }
216///
217/// # async fn record(
218/// # archived: &Archived,
219/// # ledger: impl Publisher<Options = JetStreamOptions>,
220/// # ) -> Result<(), Box<dyn std::error::Error>> {
221/// ledger
222/// .message(archived)
223/// .to("orders.archived")
224/// .message_id(format!("order-{}", archived.id))
225/// .expect_last_subject_sequence(41)
226/// .publish()
227/// .await?;
228/// # Ok(())
229/// # }
230/// ```
231pub trait JetStreamPublishSteps {
232 /// Tags this message with a deduplication id, so a repeat inside the stream's deduplication
233 /// window is stored once.
234 #[must_use]
235 fn message_id(self, id: impl Into<String>) -> Self;
236
237 /// Requires the stream's last sequence to be `sequence`, or the stream refuses this message.
238 #[must_use]
239 fn expect_last_sequence(self, sequence: u64) -> Self;
240
241 /// Requires the last sequence on this message's own subject to be `sequence`.
242 #[must_use]
243 fn expect_last_subject_sequence(self, sequence: u64) -> Self;
244
245 /// Requires the stream's last `Nats-Msg-Id` to be `id`.
246 #[must_use]
247 fn expect_last_message_id(self, id: impl Into<String>) -> Self;
248}
249
250/// The bound on the sink's options type is what keeps these steps off a builder over any other
251/// publisher, this crate's Core NATS one included.
252impl<Sink, Body, Enc, Hdrs, Dest> JetStreamPublishSteps
253 for PublishBuilder<Sink, Body, Enc, Hdrs, Dest>
254where
255 Sink: PublishSink<Options = JetStreamOptions>,
256{
257 fn message_id(mut self, id: impl Into<String>) -> Self {
258 self.options_mut()
259 .get_or_insert_with(JetStreamOptions::default)
260 .message_id = Some(id.into());
261 self
262 }
263
264 fn expect_last_sequence(mut self, sequence: u64) -> Self {
265 self.options_mut()
266 .get_or_insert_with(JetStreamOptions::default)
267 .expect_last_sequence = Some(sequence);
268 self
269 }
270
271 fn expect_last_subject_sequence(mut self, sequence: u64) -> Self {
272 self.options_mut()
273 .get_or_insert_with(JetStreamOptions::default)
274 .expect_last_subject_sequence = Some(sequence);
275 self
276 }
277
278 fn expect_last_message_id(mut self, id: impl Into<String>) -> Self {
279 self.options_mut()
280 .get_or_insert_with(JetStreamOptions::default)
281 .expect_last_message_id = Some(id.into());
282 self
283 }
284}
285
286/// The `JetStream` publish policy: pure declaration, constructible anywhere.
287///
288/// It carries what belongs to the publisher rather than to a message, which on `JetStream` is one
289/// thing: the stream the subject must be served by. A publisher that states
290/// `expect_stream("ORDERS")` fails loudly if its subject is routed elsewhere, instead of writing
291/// somewhere unintended.
292///
293/// The sequence and message-id expectations are not here. They belong to one message - an expected
294/// last sequence fixed for the life of a publisher can hold for at most one publish - so they are
295/// [`JetStreamOptions`] fields, set by the [`JetStreamPublishSteps`] steps at the call site.
296///
297/// # Examples
298///
299/// ```
300/// # mod demo {
301/// use ruststream_nats::prelude::*;
302/// use serde::{Deserialize, Serialize};
303///
304/// #[derive(Deserialize)]
305/// struct Payment {
306/// order: u64,
307/// }
308///
309/// #[derive(Serialize, Outgoing)]
310/// #[outgoing(name = "orders.paid")]
311/// struct Paid {
312/// order: u64,
313/// }
314///
315/// #[subscriber("payments.settled", reply)]
316/// async fn record(payment: &Payment) -> Paid {
317/// Paid { order: payment.order }
318/// }
319///
320/// #[ruststream::app]
321/// fn app() -> impl App {
322/// RustStream::new(AppInfo::new("payments", "0.1.0")).with_broker(
323/// NatsBroker::new("nats://localhost:4222"),
324/// |b| {
325/// // The reply is stored, and a subject the ORDERS stream does not serve is an error.
326/// b.include(record)
327/// .out_reply(JetStreamPublish::default().expect_stream("ORDERS"));
328/// },
329/// )
330/// }
331/// # }
332/// # fn main() {}
333/// ```
334#[derive(Debug, Clone, Default, PartialEq, Eq)]
335#[must_use]
336pub struct JetStreamPublish {
337 stream: Option<String>,
338}
339
340impl JetStreamPublish {
341 /// Requires the subject to be served by the named stream. The server rejects the publish
342 /// otherwise, so a misrouted subject surfaces as an error rather than a silent write.
343 pub fn expect_stream(mut self, stream: impl Into<String>) -> Self {
344 self.stream = Some(stream.into());
345 self
346 }
347
348 /// Applies the publisher's own declaration to one outgoing `JetStream` message.
349 fn apply(&self, message: PublishMessage) -> PublishMessage {
350 match &self.stream {
351 Some(stream) => message.expected_stream(stream),
352 None => message,
353 }
354 }
355
356 /// The same declaration as the protocol header a server reads. See
357 /// [`JetStreamOptions::write_headers`].
358 #[cfg(feature = "testing")]
359 pub(crate) fn write_headers(&self, headers: &mut HeaderMap) {
360 if let Some(stream) = &self.stream {
361 headers.insert(EXPECTED_STREAM, stream.clone());
362 }
363 }
364}
365
366impl PublishPolicy<ConnectedNatsBroker> for JetStreamPublish {
367 type Live = JetStreamPublisher;
368
369 fn pair(
370 self,
371 connected: &ConnectedNatsBroker,
372 ) -> impl Future<Output = Result<Self::Live, PairError>> {
373 ready(Ok(self.bind(connected)))
374 }
375
376 /// The stream this publisher requires, and the subject it requires that stream to serve.
377 ///
378 /// The `nats` binding has no field for either, so both travel under this crate's extension
379 /// key. `channel` is the destination the mount site resolved, and naming it next to the
380 /// stream is what makes the requirement readable: a reader sees which subject the stream has
381 /// to carry, including where a naming transform decides the address per delivery and the
382 /// channel reports none. A publisher that requires no stream has no requirement to state and
383 /// contributes nothing.
384 ///
385 /// There is no reply address here: on a `JetStream` delivery the protocol's reply field is the
386 /// acknowledgement inbox, not a request's, so a request is answered over Core NATS and
387 /// [`NatsPublish`](crate::NatsPublish) is what reports it.
388 #[cfg(feature = "asyncapi")]
389 fn channel_bindings(&self, channel: &str) -> Bindings {
390 self.stream
391 .as_deref()
392 .map(|stream| JetStreamPublishChannel {
393 expected_stream: stream,
394 subject: channel,
395 })
396 .and_then(|body| Binding::extension(JETSTREAM_EXTENSION, &body).ok())
397 .map(|binding| Bindings::new().with(binding))
398 .unwrap_or_default()
399 }
400}
401
402/// The stream a publisher requires and the subject it must serve, under this crate's extension
403/// key.
404#[cfg(feature = "asyncapi")]
405#[derive(Serialize)]
406#[serde(rename_all = "camelCase")]
407struct JetStreamPublishChannel<'a> {
408 expected_stream: &'a str,
409 subject: &'a str,
410}
411
412impl NatsPublishPolicy for JetStreamPublish {
413 fn bind(self, connected: &ConnectedNatsBroker) -> Self::Live {
414 let link = match connected.link() {
415 Link::Nats(connection) => JetStreamLink::Nats {
416 connection: Arc::clone(connection),
417 context: connection.jetstream(),
418 },
419 #[cfg(feature = "testing")]
420 Link::InProcess(bus) => JetStreamLink::InProcess(Arc::clone(bus)),
421 };
422 JetStreamPublisher { link, policy: self }
423 }
424}
425
426/// What a `JetStream` publisher speaks over: the connection and its `JetStream` context, or, under
427/// the `testing` feature, the in-process transport.
428// The size difference exists only with the `testing` feature; boxing the production variant to
429// even it out would cost every production publisher an allocation.
430#[cfg_attr(feature = "testing", allow(clippy::large_enum_variant))]
431#[derive(Clone)]
432enum JetStreamLink {
433 Nats {
434 connection: Arc<NatsConnection>,
435 context: Context,
436 },
437 #[cfg(feature = "testing")]
438 InProcess(Arc<Bus>),
439}
440
441// A production build holds the connection and the context and nothing else.
442#[cfg(not(feature = "testing"))]
443const _: () = assert!(size_of::<JetStreamLink>() == size_of::<(Arc<NatsConnection>, Context)>());
444
445/// The live `JetStream` publisher. Cheap to clone.
446///
447/// Every publish waits for the stream's acknowledgement, so a rejected message (unknown stream,
448/// violated expectation, storage failure) is an error rather than a silent drop. Like the Core
449/// publisher it aliases the connection and may outlive it: after the broker shuts down every
450/// publish reports [`NatsError::Closed`].
451///
452/// A service reaches one wherever a mount site names [`JetStreamPublish`]: a reply, an `Out` slot,
453/// or a startup hook that publishes outside any delivery.
454///
455/// # Examples
456///
457/// ```
458/// # mod demo {
459/// use std::io;
460///
461/// use ruststream::OutgoingMessage;
462/// use ruststream_nats::prelude::*;
463/// use serde::{Deserialize, Serialize};
464///
465/// #[derive(Deserialize)]
466/// struct Order {
467/// id: u64,
468/// }
469///
470/// #[derive(Serialize, Outgoing)]
471/// #[outgoing(name = "confirmations")]
472/// struct Confirmation {
473/// id: u64,
474/// }
475///
476/// #[derive(Serialize, Outgoing)]
477/// #[outgoing(name = "archive.orders")]
478/// struct Archived {
479/// id: u64,
480/// }
481///
482/// #[derive(OutSlot)]
483/// #[publishes(Archived)]
484/// struct Archive;
485///
486/// #[subscriber(JetStreamSubject::new("orders.*", "ORDERS").durable("confirmer"), reply)]
487/// async fn confirm(order: &Order) -> Confirmation {
488/// Confirmation { id: order.id }
489/// }
490///
491/// #[subscriber(JetStreamSubject::new("orders.*", "ORDERS").durable("archiver"))]
492/// async fn archive(
493/// order: &Order,
494/// Out(out): Out<impl Publisher<Options = JetStreamOptions>, Archive>,
495/// ) -> HandlerOutcome {
496/// let archived = Archived { id: order.id };
497/// if out
498/// .message(&archived)
499/// .message_id(format!("order-{}", order.id))
500/// .publish()
501/// .await
502/// .is_err()
503/// {
504/// return HandlerOutcome::retry();
505/// }
506/// HandlerOutcome::ack()
507/// }
508///
509/// #[ruststream::app]
510/// fn app() -> impl App {
511/// RustStream::new(AppInfo::new("orders", "0.1.0")).with_broker(
512/// NatsBroker::new("nats://localhost:4222"),
513/// |b| {
514/// b.include(confirm)
515/// .out_reply(JetStreamPublish::default().expect_stream("CONFIRMATIONS"));
516///
517/// b.include(archive)
518/// .out(Archive, JetStreamPublish::default().expect_stream("ARCHIVE"))
519/// .build();
520///
521/// b.after_startup(
522/// JetStreamPublish::default().expect_stream("ORDERS"),
523/// async move |publisher| -> io::Result<()> {
524/// let ack = publisher
525/// .publish_ack(OutgoingMessage::new("orders.created", br#"{"id":1}"#), None)
526/// .await
527/// .map_err(io::Error::other)?;
528/// tracing::info!(stream = %ack.stream, ack.sequence, "seeded");
529/// Ok(())
530/// },
531/// );
532/// },
533/// )
534/// }
535/// # }
536/// # fn main() {}
537/// ```
538#[derive(Clone)]
539pub struct JetStreamPublisher {
540 link: JetStreamLink,
541 policy: JetStreamPublish,
542}
543
544impl Debug for JetStreamPublisher {
545 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
546 f.debug_struct("JetStreamPublisher")
547 .field("policy", &self.policy)
548 .finish_non_exhaustive()
549 }
550}
551
552impl JetStreamPublisher {
553 /// Publishes into the stream and returns the acknowledgement: the stream the message landed
554 /// in, its sequence there, and whether the deduplication window recognised it as a duplicate.
555 ///
556 /// [`Publisher::publish`] is this call with the acknowledgement discarded. `options` is what
557 /// this one message states about itself; pass `None` to publish on the policy alone.
558 ///
559 /// # Errors
560 ///
561 /// Returns [`NatsError::Closed`] when the broker has shut down, [`NatsError::Publish`] when
562 /// the message cannot be sent, and [`NatsError::JetStream`] when the stream rejects it (no
563 /// such stream, or an expectation did not hold).
564 ///
565 /// # Cancel safety
566 ///
567 /// Not cancel-safe: dropping the future after the message is on the wire abandons the
568 /// acknowledgement, leaving the publish in an indeterminate state.
569 pub async fn publish_ack(
570 &self,
571 msg: OutgoingMessage<'_, BytesMut>,
572 options: Option<&JetStreamOptions>,
573 ) -> Result<PublishAck, NatsError> {
574 let (connection, context) = match &self.link {
575 JetStreamLink::Nats {
576 connection,
577 context,
578 } => (connection, context),
579 #[cfg(feature = "testing")]
580 JetStreamLink::InProcess(bus) => return self.publish_in_process(bus, msg, options),
581 };
582 // Checked before the send: the context caches a client clone that would happily queue a
583 // publish into a drained connection.
584 connection.live_client(msg.name())?;
585
586 let (subject, payload, headers) = nats_parts(msg)?;
587 let mut message = PublishMessage::build().payload(payload);
588 // The application's headers go on first: `headers` replaces the map, and the protocol
589 // fields below are written into it.
590 if let Some(headers) = headers {
591 message = message.headers(headers);
592 }
593 message = self.policy.apply(message);
594 if let Some(options) = options {
595 message = options.apply(message);
596 }
597
598 context
599 .send_publish(subject, message)
600 .await
601 .map_err(|err| NatsError::Publish(Box::new(err)))?
602 .await
603 .map_err(|err| NatsError::JetStream(Box::new(err)))
604 }
605}
606
607#[cfg(feature = "testing")]
608impl JetStreamPublisher {
609 /// The in-process publish: the client's half is writing the protocol headers, and the
610 /// in-process stream reads them as a server does.
611 fn publish_in_process(
612 &self,
613 bus: &Bus,
614 msg: OutgoingMessage<'_, BytesMut>,
615 options: Option<&JetStreamOptions>,
616 ) -> Result<PublishAck, NatsError> {
617 let (subject, payload, mut headers) = msg.into_parts();
618 self.policy.write_headers(&mut headers);
619 if let Some(options) = options {
620 options.write_headers(&mut headers);
621 }
622 let stored = bus
623 .publish(
624 subject,
625 payload.freeze(),
626 &headers,
627 None,
628 PublishMode::JetStream,
629 Origin::Connection,
630 )?
631 .ok_or_else(|| NatsError::JetStream("no stream found for given subject".into()))?;
632 Ok(PublishAck {
633 stream: stored.stream,
634 sequence: stored.sequence,
635 duplicate: stored.duplicate,
636 ..PublishAck::default()
637 })
638 }
639}
640
641impl Publisher for JetStreamPublisher {
642 /// The same answer the Core publisher gives: a `JetStream` publish carries a `Bytes` the
643 /// client keeps.
644 type Payload = Take;
645
646 type Error = NatsError;
647 type Options = JetStreamOptions;
648
649 /// # Cancel safety
650 ///
651 /// Not cancel-safe; see [`publish_ack`](Self::publish_ack).
652 async fn publish(
653 &self,
654 msg: OutgoingMessage<'_, BytesMut>,
655 options: Option<&Self::Options>,
656 ) -> Result<(), Self::Error> {
657 self.publish_ack(msg, options).await.map(|_ack| ())
658 }
659}