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