1use crate::util::DisplayAsHexBytesLimitted;
2use crate::util::DisplayErrorLocation;
3use crate::util::FfiSafeBytes;
4use crate::util::FfiSafeStr;
5use std::borrow::Cow;
6use std::cell::OnceCell;
7use std::collections::HashMap;
8use std::io::Cursor;
9use tarantool::error::BoxError;
10use tarantool::error::TarantoolErrorCode;
11use tarantool::error::TarantoolErrorCode::InvalidMsgpack;
12use tarantool::ffi::uuid::tt_uuid;
13use tarantool::msgpack::skip_value;
14use tarantool::unwrap_ok_or;
15use tarantool::uuid::Uuid;
16
17pub struct Context<'a> {
30 request_id: Uuid,
31 path: &'a str,
32 plugin_name: &'a str,
33 service_name: &'a str,
34 plugin_version: &'a str,
35
36 raw: &'a [u8],
38
39 named_fields: OnceCell<Result<ContextNamedFields<'a>, BoxError>>,
41}
42
43type ContextNamedFields<'a> = HashMap<Cow<'a, str>, ContextValue<'a>>;
44
45impl<'a> Context<'a> {
46 #[inline]
47 pub(crate) fn new(context: &'a FfiSafeContext) -> Self {
48 let path = unsafe { context.path.as_str() };
50 let plugin_name = unsafe { context.plugin_name.as_str() };
51 let service_name = unsafe { context.service_name.as_str() };
52 let plugin_version = unsafe { context.plugin_version.as_str() };
53 let raw = unsafe { context.raw.as_bytes() };
54 Self {
55 request_id: Uuid::from_tt_uuid(context.request_id),
56 path,
57 plugin_name,
58 service_name,
59 plugin_version,
60 raw,
61 named_fields: OnceCell::new(),
62 }
63 }
64
65 #[inline(always)]
66 pub fn request_id(&self) -> Uuid {
67 self.request_id
68 }
69
70 #[inline(always)]
71 pub fn path(&self) -> &str {
72 self.path
73 }
74
75 #[inline(always)]
76 pub fn plugin_name(&self) -> &str {
77 self.plugin_name
78 }
79
80 #[inline(always)]
81 pub fn service_name(&self) -> &str {
82 self.service_name
83 }
84
85 #[inline(always)]
86 pub fn plugin_version(&self) -> &str {
87 self.plugin_version
88 }
89
90 #[inline(always)]
91 pub fn raw(&self) -> &[u8] {
92 self.raw
93 }
94
95 #[inline(always)]
97 pub fn set(
98 &mut self,
99 field: impl Into<Cow<'static, str>>,
100 value: impl Into<ContextValue<'static>>,
101 ) -> Result<Option<ContextValue<'a>>, BoxError> {
102 self.get_named_fields()?;
105
106 let res: &mut Result<ContextNamedFields, BoxError> = self
107 .named_fields
108 .get_mut()
109 .expect("just made sure it's there");
110 let named_fields: &mut ContextNamedFields = res
111 .as_mut()
112 .expect("if it was an error we would've returned early");
113 let old_value = named_fields.insert(field.into(), value.into());
114 Ok(old_value)
115 }
116
117 #[inline(always)]
118 pub fn get(&self, field: &str) -> Result<Option<&ContextValue<'a>>, BoxError> {
119 let named_fields = self.get_named_fields()?;
120 Ok(named_fields.get(field))
121 }
122
123 #[inline]
124 pub fn get_named_fields(&self) -> Result<&ContextNamedFields<'a>, BoxError> {
125 self.named_fields
126 .get_or_init(|| decode_msgpack_string_fields(self.raw))
127 .as_ref()
128 .map_err(Clone::clone)
129 }
130}
131
132impl std::fmt::Debug for Context<'_> {
133 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
134 let mut s = f.debug_struct("Context");
135 let mut s = s
136 .field("request_id", &DebugUuid(self.request_id))
137 .field("path", &self.path)
138 .field("plugin_name", &self.plugin_name)
139 .field("service_name", &self.service_name)
140 .field("plugin_version", &self.plugin_version);
141 if let Ok(named_fields) = self.get_named_fields() {
142 for (key, value) in named_fields {
143 s = s.field(key, value);
144 }
145 s.finish()
146 } else {
147 s.finish_non_exhaustive()
148 }
149 }
150}
151
152tarantool::define_enum_with_introspection! {
153 pub enum ContextFieldId {
155 RequestId = 1,
156 PluginName = 2,
157 ServiceName = 3,
158 PluginVersion = 4,
159 }
160}
161
162#[inline]
163fn decode_msgpack_string_fields<'a>(raw: &'a [u8]) -> Result<ContextNamedFields<'a>, BoxError> {
164 decode_msgpack_string_fields_impl(raw).map_err(|e| log_context_decoding_error(raw, e))
165}
166
167fn decode_msgpack_string_fields_impl<'a>(
168 raw: &'a [u8],
169) -> Result<ContextNamedFields<'a>, BoxError> {
170 let mut buffer = Cursor::new(raw);
171
172 let count = rmp::decode::read_map_len(&mut buffer).map_err(invalid_msgpack)? as usize;
173
174 let mut named_fields = ContextNamedFields::with_capacity(count);
175 for i in 0..count {
176 let Ok(marker) = rmp::decode::read_marker(&mut buffer) else {
177 #[rustfmt::skip]
178 return Err(BoxError::new(InvalidMsgpack, format!("not enough values in mapping: expected {count}, got {i}")));
179 };
180 if let Some(_field_id) = read_rest_of_uint(marker, &mut buffer)? {
181 skip_value(&mut buffer).map_err(invalid_msgpack)?;
184 } else if let Some(field_name) = read_rest_of_str(marker, &mut buffer)? {
185 let value = rmp_serde::decode::from_read(&mut buffer)
187 .map_err(|e| decoding_field(field_name, invalid_msgpack(e)))?;
188 named_fields.insert(Cow::Borrowed(field_name), value);
189 } else {
190 #[rustfmt::skip]
192 return Err(BoxError::new(InvalidMsgpack, format!("context field must be positive integer or string, got {marker:?}")));
193 }
194 }
195
196 Ok(named_fields)
197}
198
199#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
204#[serde(untagged)]
205pub enum ContextValue<'a> {
206 Bool(bool),
207 Int(i64),
208 Float(f64),
209 String(Cow<'a, str>),
210 Array(Vec<ContextValue<'a>>),
211}
212
213impl ContextValue<'_> {
214 #[inline(always)]
215 pub fn bool(&self) -> Option<bool> {
216 match self {
217 &Self::Bool(v) => Some(v),
218 _ => None,
219 }
220 }
221
222 #[inline(always)]
223 pub fn int(&self) -> Option<i64> {
224 match self {
225 &Self::Int(v) => Some(v),
226 _ => None,
227 }
228 }
229
230 #[inline(always)]
231 pub fn float(&self) -> Option<f64> {
232 match self {
233 &Self::Float(v) => Some(v),
234 &Self::Int(v) => Some(v as _),
235 _ => None,
236 }
237 }
238
239 #[inline(always)]
240 pub fn str(&self) -> Option<&str> {
241 match self {
242 Self::String(v) => Some(v),
243 _ => None,
244 }
245 }
246
247 #[inline(always)]
248 pub fn array(&self) -> Option<&[Self]> {
249 match self {
250 Self::Array(v) => Some(&*v),
251 _ => None,
252 }
253 }
254
255 pub fn is_owned(&self) -> bool {
259 match self {
260 Self::Bool { .. } => true,
261 Self::Int { .. } => true,
262 Self::Float { .. } => true,
263 Self::String(v) => matches!(v, Cow::Owned { .. }),
264 Self::Array(v) => v.iter().all(Self::is_owned),
265 }
266 }
267
268 pub fn into_owned(self) -> ContextValue<'static> {
271 match self {
272 Self::Bool(v) => ContextValue::Bool(v),
273 Self::Int(v) => ContextValue::Int(v),
274 Self::Float(v) => ContextValue::Float(v),
275 Self::String(v) => ContextValue::String(Cow::Owned(v.into())),
279 Self::Array(v) => ContextValue::Array(v.into_iter().map(Self::into_owned).collect()),
280 }
281 }
282}
283
284impl From<bool> for ContextValue<'_> {
285 #[inline(always)]
286 fn from(v: bool) -> Self {
287 Self::Bool(v)
288 }
289}
290
291#[rustfmt::skip]
292pub mod impl_int {
293 use super::*;
294 impl From<i8> for ContextValue<'_> { #[inline(always)] fn from(v: i8) -> Self { Self::Int(v as _) } }
295 impl From<i16> for ContextValue<'_> { #[inline(always)] fn from(v: i16) -> Self { Self::Int(v as _) } }
296 impl From<i32> for ContextValue<'_> { #[inline(always)] fn from(v: i32) -> Self { Self::Int(v as _) } }
297 impl From<i64> for ContextValue<'_> { #[inline(always)] fn from(v: i64) -> Self { Self::Int(v as _) } }
298 impl From<isize> for ContextValue<'_> { #[inline(always)] fn from(v: isize) -> Self { Self::Int(v as _) } }
299 impl From<u8> for ContextValue<'_> { #[inline(always)] fn from(v: u8) -> Self { Self::Int(v as _) } }
300 impl From<u16> for ContextValue<'_> { #[inline(always)] fn from(v: u16) -> Self { Self::Int(v as _) } }
301 impl From<u32> for ContextValue<'_> { #[inline(always)] fn from(v: u32) -> Self { Self::Int(v as _) } }
302 impl From<u64> for ContextValue<'_> { #[inline(always)] fn from(v: u64) -> Self { Self::Int(v as _) } }
303 impl From<usize> for ContextValue<'_> { #[inline(always)] fn from(v: usize) -> Self { Self::Int(v as _) } }
304}
305
306impl From<f32> for ContextValue<'_> {
307 #[inline(always)]
308 fn from(v: f32) -> Self {
309 Self::Float(v as _)
310 }
311}
312
313impl From<f64> for ContextValue<'_> {
314 #[inline(always)]
315 fn from(v: f64) -> Self {
316 Self::Float(v)
317 }
318}
319
320impl<'a> From<&'a str> for ContextValue<'a> {
321 #[inline(always)]
322 fn from(s: &'a str) -> Self {
323 Self::String(s.into())
324 }
325}
326
327impl From<String> for ContextValue<'_> {
328 #[inline(always)]
329 fn from(s: String) -> Self {
330 Self::String(s.into())
331 }
332}
333
334impl<'a> From<Cow<'a, str>> for ContextValue<'a> {
335 #[inline(always)]
336 fn from(s: Cow<'a, str>) -> Self {
337 Self::String(s)
338 }
339}
340
341#[derive(Copy, Clone)]
349#[repr(C)]
350pub struct FfiSafeContext {
351 pub request_id: tt_uuid,
352 pub path: FfiSafeStr,
353 pub plugin_name: FfiSafeStr,
354 pub service_name: FfiSafeStr,
355 pub plugin_version: FfiSafeStr,
356
357 raw: FfiSafeBytes,
359}
360
361impl FfiSafeContext {
362 #[inline]
363 pub fn for_local_call(
364 request_id: Uuid,
365 path: &str,
366 plugin: &str,
367 service: &str,
368 version: &str,
369 raw: &[u8],
370 ) -> Self {
371 Self {
372 request_id: request_id.to_tt_uuid(),
373 path: path.into(),
374 plugin_name: plugin.into(),
375 service_name: service.into(),
376 plugin_version: version.into(),
377
378 raw: raw.into(),
379 }
380 }
381
382 #[inline(always)]
383 pub fn decode_msgpack(path: &str, raw: &[u8]) -> Result<Self, ()> {
384 Self::decode_msgpack_impl(path, raw)
385 .map_err(|e| e.set_last())
388 }
389
390 pub fn decode_msgpack_impl(path: &str, raw: &[u8]) -> Result<Self, BoxError> {
391 let mut request_id = None;
392 let mut plugin_name = None;
393 let mut service_name = None;
394 let mut plugin_version = None;
395
396 let mut buffer = Cursor::new(raw);
397 let count = unwrap_ok_or!(rmp::decode::read_map_len(&mut buffer),
398 Err(e) => return Err(BoxError::new(InvalidMsgpack, format!("expected a map: {e}")))
399 );
400 for i in 0..count {
401 let Ok(marker) = rmp::decode::read_marker(&mut buffer) else {
402 #[rustfmt::skip]
403 return Err(BoxError::new(InvalidMsgpack, format!("not enough entries in mapping: expected {count}, got {i}")));
404 };
405 if let Some(field_id) = read_rest_of_uint(marker, &mut buffer)? {
406 match ContextFieldId::try_from(field_id) {
408 Ok(field @ ContextFieldId::RequestId) => {
409 #[rustfmt::skip]
410 let v: Uuid = rmp_serde::decode::from_read(&mut buffer).map_err(|e| decoding_field(&field, invalid_msgpack(e)))?;
411 request_id = Some(v);
412 }
413 Ok(field @ ContextFieldId::PluginName) => {
414 #[rustfmt::skip]
415 let v = read_str(&mut buffer).map_err(|e| decoding_field(&field, e))?;
416 plugin_name = Some(v);
417 }
418 Ok(field @ ContextFieldId::ServiceName) => {
419 #[rustfmt::skip]
420 let v = read_str(&mut buffer).map_err(|e| decoding_field(&field, e))?;
421 service_name = Some(v);
422 }
423 Ok(field @ ContextFieldId::PluginVersion) => {
424 #[rustfmt::skip]
425 let v = read_str(&mut buffer).map_err(|e| decoding_field(&field, e))?;
426 plugin_version = Some(v);
427 }
428 Err(unknown_field_id) => {
429 #[rustfmt::skip]
430 tarantool::say_verbose!("ignoring unknown context field with integer id {unknown_field_id} (0x{unknown_field_id:02x})");
431 skip_value(&mut buffer).map_err(invalid_msgpack)?;
432 }
433 }
434 } else if let Some(_field_name) = read_rest_of_str(marker, &mut buffer)? {
435 skip_value(&mut buffer).map_err(invalid_msgpack)?;
438 } else {
439 #[rustfmt::skip]
441 return Err(BoxError::new(InvalidMsgpack, format!("context field must be positive integer or string, got {marker:?}")));
442 }
443 }
444
445 let end_index = buffer.position() as usize;
446 if end_index > raw.len() {
447 #[rustfmt::skip]
448 return Err(BoxError::new(InvalidMsgpack, format!("expected more data after {}", DisplayAsHexBytesLimitted(raw))));
449 } else if end_index < raw.len() {
450 let tail = &raw[end_index..];
451 #[rustfmt::skip]
452 return Err(BoxError::new(InvalidMsgpack, format!("unexpected data after context: {}", DisplayAsHexBytesLimitted(tail))));
453 }
454
455 let Some(request_id) = request_id else {
456 return Err(invalid_msgpack("context must contain a request_id"));
457 };
458
459 let Some(plugin_name) = plugin_name else {
460 return Err(invalid_msgpack("context must contain a plugin_name"));
461 };
462
463 let Some(service_name) = service_name else {
464 return Err(invalid_msgpack("context must contain a service_name"));
465 };
466
467 let Some(plugin_version) = plugin_version else {
468 return Err(invalid_msgpack("context must contain a plugin_version"));
469 };
470
471 Ok(Self {
472 request_id: request_id.to_tt_uuid(),
473 path: path.into(),
474 plugin_name: plugin_name.into(),
475 service_name: service_name.into(),
476 plugin_version: plugin_version.into(),
477 raw: raw.into(),
478 })
479 }
480}
481
482impl std::fmt::Debug for FfiSafeContext {
483 #[inline]
484 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
485 f.debug_struct("FfiSafeContext")
486 .field(
487 "request_id",
488 &DebugUuid(Uuid::from_tt_uuid(self.request_id)),
489 )
490 .field("path", &self.path)
491 .field("plugin_name", &self.plugin_name)
492 .field("service_name", &self.service_name)
493 .field("plugin_version", &self.plugin_version)
494 .finish_non_exhaustive()
495 }
496}
497
498fn read_str<'a>(buffer: &mut Cursor<&'a [u8]>) -> Result<&'a str, BoxError> {
503 let length = rmp::decode::read_str_len(buffer).map_err(invalid_msgpack)? as usize;
504
505 str_from_cursor(length, buffer)
506}
507
508fn read_rest_of_str<'a>(
509 marker: rmp::Marker,
510 buffer: &mut Cursor<&'a [u8]>,
511) -> Result<Option<&'a str>, BoxError> {
512 use rmp::decode::RmpRead as _;
513
514 let length;
515 match marker {
516 rmp::Marker::FixStr(v) => {
517 length = v as usize;
518 }
519 rmp::Marker::Str8 => {
520 length = buffer.read_data_u8().map_err(invalid_msgpack)? as usize;
521 }
522 rmp::Marker::Str16 => {
523 length = buffer.read_data_u16().map_err(invalid_msgpack)? as usize;
524 }
525 rmp::Marker::Str32 => {
526 length = buffer.read_data_u32().map_err(invalid_msgpack)? as usize;
527 }
528 _ => return Ok(None),
529 }
530
531 str_from_cursor(length, buffer).map(Some)
532}
533
534#[inline]
535fn str_from_cursor<'a>(length: usize, buffer: &mut Cursor<&'a [u8]>) -> Result<&'a str, BoxError> {
536 let start_index = buffer.position() as usize;
537 let data = *buffer.get_ref();
538 let remaining_length = data.len() - start_index;
539 if remaining_length < length {
540 return Err(BoxError::new(
541 TarantoolErrorCode::InvalidMsgpack,
542 format!("expected a string of length {length}, got {remaining_length}"),
543 ));
544 }
545
546 let end_index = start_index + length;
547 let res = std::str::from_utf8(&data[start_index..end_index]).map_err(invalid_msgpack)?;
548 buffer.set_position(end_index as _);
549 Ok(res)
550}
551
552fn read_rest_of_uint(
553 marker: rmp::Marker,
554 buffer: &mut Cursor<&[u8]>,
555) -> Result<Option<u64>, BoxError> {
556 use rmp::decode::RmpRead as _;
557 match marker {
558 rmp::Marker::FixPos(v) => Ok(Some(v as _)),
559 rmp::Marker::U8 => {
560 let v = buffer.read_data_u8().map_err(invalid_msgpack)?;
561 Ok(Some(v as _))
562 }
563 rmp::Marker::U16 => {
564 let v = buffer.read_data_u16().map_err(invalid_msgpack)?;
565 Ok(Some(v as _))
566 }
567 rmp::Marker::U32 => {
568 let v = buffer.read_data_u32().map_err(invalid_msgpack)?;
569 Ok(Some(v as _))
570 }
571 rmp::Marker::U64 => {
572 let v = buffer.read_data_u64().map_err(invalid_msgpack)?;
573 Ok(Some(v as _))
574 }
575 _ => Ok(None),
576 }
577}
578
579#[track_caller]
580fn log_context_decoding_error(raw: &[u8], e: BoxError) -> BoxError {
581 let location = DisplayErrorLocation(&e);
582 if raw.len() <= 512 {
583 let raw = DisplayAsHexBytesLimitted(raw);
584 tarantool::say_error!("failed to decode context from msgpack ({raw}): {location}{e}");
585 } else {
586 tarantool::say_error!(
587 "failed to decode context from msgpack (too big to display): {location}{e}"
588 );
589 }
590 e
591}
592
593#[inline(always)]
594#[track_caller]
595fn decoding_field(field: &(impl std::fmt::Debug + ?Sized), error: BoxError) -> BoxError {
596 BoxError::new(
597 error.error_code(),
598 format!("failed decoding field {field:?}: {}", error.message()),
599 )
600}
601
602#[inline(always)]
603#[track_caller]
604fn invalid_msgpack(error: impl ToString) -> BoxError {
605 BoxError::new(TarantoolErrorCode::InvalidMsgpack, error.to_string())
606}
607
608struct DebugUuid(Uuid);
610impl std::fmt::Debug for DebugUuid {
611 #[inline(always)]
612 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
613 write!(f, "{}", self.0)
614 }
615}
616
617#[cfg(feature = "internal_test")]
622mod tests {
623 use super::*;
624
625 #[tarantool::test]
626 fn decode_context() {
627 let e = FfiSafeContext::decode_msgpack_impl("path", b"\x90").unwrap_err();
631 #[rustfmt::skip]
632 assert_eq!(e.to_string(), "InvalidMsgpack: expected a map: the type decoded isn't match with the expected one");
633
634 let e = FfiSafeContext::decode_msgpack_impl("path", b"\x80").unwrap_err();
635 #[rustfmt::skip]
636 assert_eq!(e.to_string(), "InvalidMsgpack: context must contain a request_id");
637
638 let e = FfiSafeContext::decode_msgpack_impl("path", b"\x87").unwrap_err();
639 #[rustfmt::skip]
640 assert_eq!(e.to_string(), "InvalidMsgpack: not enough entries in mapping: expected 7, got 0");
641
642 let mut data = Vec::new();
643 data.extend(b"\x84");
644 data.extend(b"\x01\xd8\x020123456789abcdef");
645 data.extend(b"\x02\xa6plugin");
646 data.extend(b"\x03\xa7service");
647 data.extend(b"\x04\xa51.2.3");
648 data.extend(b"unexpected-data");
649 let e = FfiSafeContext::decode_msgpack_impl("path", &data).unwrap_err();
650 #[rustfmt::skip]
651 assert_eq!(e.to_string(), r#"InvalidMsgpack: unexpected data after context: b"unexpected-data""#);
652
653 let mut data = Vec::new();
654 data.extend(b"\x85");
655 data.extend(b"\x01\xd8\x02");
656 let e = FfiSafeContext::decode_msgpack_impl("path", &data).unwrap_err();
657 #[rustfmt::skip]
658 assert_eq!(e.to_string(), "InvalidMsgpack: failed decoding field RequestId: IO error while reading data: unexpected end of file");
659
660 let mut data = Vec::new();
661 data.extend(b"\x85");
662 data.extend(b"\x01\xd8\x020123456789abcdef");
663 data.extend(b"\x02\xa6plugin");
664 data.extend(b"\x03\xa7service");
665 data.extend(b"\x04\xa51.2.3");
666 data.extend(b"\xa3foo\xa4");
667 let e = FfiSafeContext::decode_msgpack_impl("path", &data).unwrap_err();
668 #[rustfmt::skip]
669 assert_eq!(e.to_string(), "InvalidMsgpack: msgpack read error: failed to read MessagePack data");
670
671 let mut data = Vec::new();
672 data.extend(b"\x85");
673 data.extend(b"\x01\xd8\x020123456789abcdef");
674 data.extend(b"\x02\xa6plugin");
675 data.extend(b"\x03\xa7service");
676 data.extend(b"\x04\xa51.2.3");
677 data.extend(b"\xa3foo\x80");
678 let context = FfiSafeContext::decode_msgpack_impl("path", &data).unwrap();
679 let context = Context::new(&context);
680 let e = context.get("foo").unwrap_err();
681 #[rustfmt::skip]
682 assert_eq!(e.to_string(), r#"InvalidMsgpack: failed decoding field "foo": data did not match any variant of untagged enum ContextValue"#);
683
684 let mut data = Vec::new();
688 data.extend(b"\x89");
689 data.extend(b"\x01\xd8\x020123456789abcdef");
690 data.extend(b"\x04\xa51.2.3");
691 data.extend(b"\x03\xa7service");
692 data.extend(b"\x02\xa6plugin");
693 data.extend(b"\xa3foo\xa3bar");
694 data.extend(b"\xa4bool\xc3");
695 data.extend(b"\xa3int\x45");
696 data.extend(b"\xa5float\xcb\x40\x09\x1e\xb8\x51\xeb\x85\x1f");
697 data.extend(b"\xa5array\x93\x01\xa3two\x03");
698
699 let context = FfiSafeContext::decode_msgpack_impl("path", &data).unwrap();
700 let context = Context::new(&context);
701 assert_eq!(
702 context.request_id,
703 Uuid::try_from_slice(b"0123456789abcdef").unwrap()
704 );
705 assert_eq!(context.path, "path");
706 assert_eq!(context.plugin_name, "plugin");
707 assert_eq!(context.service_name, "service");
708 assert_eq!(context.plugin_version, "1.2.3");
709 assert!(context.named_fields.get().is_none());
710
711 assert_eq!(
712 *context.get("foo").unwrap().unwrap(),
713 ContextValue::from("bar")
714 );
715 assert_eq!(
716 *context.get("bool").unwrap().unwrap(),
717 ContextValue::from(true)
718 );
719 assert_eq!(
720 *context.get("int").unwrap().unwrap(),
721 ContextValue::from(69)
722 );
723 assert_eq!(
724 *context.get("float").unwrap().unwrap(),
725 ContextValue::from(3.14)
726 );
727 assert_eq!(
728 *context.get("array").unwrap().unwrap(),
729 ContextValue::Array(vec![
730 ContextValue::from(1),
731 ContextValue::from("two"),
732 ContextValue::from(3)
733 ])
734 );
735 assert!(context.get("bar").unwrap().is_none());
736 }
737
738 #[tarantool::test]
739 fn context_get_set() {
740 let mut data = Vec::new();
741 data.extend(b"\x84");
742 data.extend(b"\x01\xd8\x020123456789abcdef");
743 data.extend(b"\x04\xa51.2.3");
744 data.extend(b"\x03\xa7service");
745 data.extend(b"\x02\xa6plugin");
746 let context = FfiSafeContext::decode_msgpack_impl("path", &data).unwrap();
747 let mut context = Context::new(&context);
748
749 assert!(context.get("bar").unwrap().is_none());
750
751 let old_value = context.set("bar", "string").unwrap();
752 assert!(old_value.is_none());
753 assert_eq!(
754 *context.get("bar").unwrap().unwrap(),
755 ContextValue::from("string")
756 );
757
758 let old_value = context.set("bar", 0xba5).unwrap().unwrap();
759 assert_eq!(old_value, ContextValue::from("string"));
760 assert_eq!(
761 *context.get("bar").unwrap().unwrap(),
762 ContextValue::from(0xba5)
763 );
764
765 assert!(!old_value.is_owned());
766 assert!(old_value.into_owned().is_owned());
767 }
768}