1use crate::compiler::prelude::*;
2use crate::value::value::simdutf_bytes_utf8_lossy;
3use chrono::Timelike;
4#[cfg(feature = "enable_system_functions")]
5use prost::Message;
6use prost_reflect::{DynamicMessage, FieldDescriptor, Kind, MapKey, MessageDescriptor};
7use std::collections::HashMap;
8
9#[derive(Default, Debug, Clone, Eq, PartialEq)]
10pub struct Options {
11 pub use_json_names: bool,
12}
13
14fn convert_value_raw(
18 value: Value,
19 kind: &Kind,
20 options: &Options,
21) -> Result<prost_reflect::Value, String> {
22 let kind_str = value.kind_str().to_owned();
23 match (value, kind) {
24 (Value::Boolean(b), Kind::Bool) => Ok(prost_reflect::Value::Bool(b)),
25 (Value::Bytes(b), Kind::Bytes) => Ok(prost_reflect::Value::Bytes(b)),
26 (Value::Bytes(b), Kind::String) => Ok(prost_reflect::Value::String(
27 simdutf_bytes_utf8_lossy(&b).into_owned(),
28 )),
29 (Value::Bytes(b), Kind::Enum(descriptor)) => {
30 let string = simdutf_bytes_utf8_lossy(&b);
31 match descriptor
32 .values()
33 .find(|v| v.name().eq_ignore_ascii_case(&string))
34 {
35 Some(d) => Ok(prost_reflect::Value::EnumNumber(d.number())),
36 None => Err(format!(
37 "Enum `{}` has no value that matches string '{}'",
38 descriptor.full_name(),
39 string
40 )),
41 }
42 }
43 (Value::Float(f), Kind::Double) => Ok(prost_reflect::Value::F64(f.into_inner())),
44 (Value::Float(f), Kind::Float) => Ok(prost_reflect::Value::F32(f.into_inner() as f32)),
45 (Value::Bytes(b), Kind::Double) => {
46 let string = simdutf_bytes_utf8_lossy(&b);
47 let val = string
48 .parse::<f64>()
49 .map_err(|e| format!("Cannot parse `{string}` as double: {e}"))?;
50 Ok(prost_reflect::Value::F64(val))
51 }
52 (Value::Bytes(b), Kind::Float) => {
53 let string = simdutf_bytes_utf8_lossy(&b);
54 let val = string
55 .parse::<f32>()
56 .map_err(|e| format!("Cannot parse `{string}` as float: {e}"))?;
57 Ok(prost_reflect::Value::F32(val))
58 }
59 (Value::Integer(i), Kind::Int32) => Ok(prost_reflect::Value::I32(i as i32)),
60 (Value::Integer(i), Kind::Int64) => Ok(prost_reflect::Value::I64(i)),
61 (Value::Integer(i), Kind::Sint32) => Ok(prost_reflect::Value::I32(i as i32)),
62 (Value::Integer(i), Kind::Sint64) => Ok(prost_reflect::Value::I64(i)),
63 (Value::Integer(i), Kind::Sfixed32) => Ok(prost_reflect::Value::I32(i as i32)),
64 (Value::Integer(i), Kind::Sfixed64) => Ok(prost_reflect::Value::I64(i)),
65 (Value::Integer(i), Kind::Uint32) => Ok(prost_reflect::Value::U32(i as u32)),
66 (Value::Integer(i), Kind::Uint64) => Ok(prost_reflect::Value::U64(i as u64)),
67 (Value::Integer(i), Kind::Fixed32) => Ok(prost_reflect::Value::U32(i as u32)),
68 (Value::Integer(i), Kind::Fixed64) => Ok(prost_reflect::Value::U64(i as u64)),
69 (Value::Integer(i), Kind::Double) => Ok(prost_reflect::Value::F64(i as f64)),
70 (Value::Integer(i), Kind::Enum(_)) => Ok(prost_reflect::Value::EnumNumber(i as i32)),
71 (Value::Bytes(b), Kind::Int32 | Kind::Sfixed32 | Kind::Sint32) => {
72 let string = simdutf_bytes_utf8_lossy(&b);
73 let number: i32 = string
74 .parse()
75 .map_err(|e| format!("Can't convert '{string}' to i32: {e}"))?;
76 Ok(prost_reflect::Value::I32(number))
77 }
78 (Value::Bytes(b), Kind::Int64 | Kind::Sfixed64 | Kind::Sint64) => {
79 let string = simdutf_bytes_utf8_lossy(&b);
80 let number: i64 = string
81 .parse()
82 .map_err(|e| format!("Can't convert '{string}' to i64: {e}"))?;
83 Ok(prost_reflect::Value::I64(number))
84 }
85 (Value::Bytes(b), Kind::Uint32 | Kind::Fixed32) => {
86 let string = simdutf_bytes_utf8_lossy(&b);
87 let number: u32 = string
88 .parse()
89 .map_err(|e| format!("Can't convert '{string}' to u32: {e}"))?;
90 Ok(prost_reflect::Value::U32(number))
91 }
92 (Value::Bytes(b), Kind::Uint64 | Kind::Fixed64) => {
93 let string = simdutf_bytes_utf8_lossy(&b);
94 let number: u64 = string
95 .parse()
96 .map_err(|e| format!("Can't convert '{string}' to u64: {e}"))?;
97 Ok(prost_reflect::Value::U64(number))
98 }
99 (Value::Object(o), Kind::Message(message_descriptor)) => {
100 if message_descriptor.is_map_entry() {
101 let value_field = message_descriptor
102 .get_field_by_name("value")
103 .ok_or("Internal error with proto map processing")?;
104 let mut map: HashMap<MapKey, prost_reflect::Value> = HashMap::new();
105 for (key, val) in o.into_iter() {
106 match convert_value(&value_field, val, options) {
107 Ok(prost_val) => {
108 map.insert(MapKey::String(key.into()), prost_val);
109 }
110 Err(e) => return Err(e),
111 }
112 }
113 Ok(prost_reflect::Value::Map(map))
114 } else {
115 Ok(prost_reflect::Value::Message(encode_message(
117 message_descriptor,
118 Value::Object(o),
119 options,
120 )?))
121 }
122 }
123 (Value::Regex(r), Kind::String) => Ok(prost_reflect::Value::String(r.as_str().to_owned())),
124 (Value::Regex(r), Kind::Bytes) => Ok(prost_reflect::Value::Bytes(r.as_bytes())),
125 (Value::Timestamp(t), Kind::Int64) => Ok(prost_reflect::Value::I64(t.timestamp_micros())),
126 (Value::Timestamp(t), Kind::Message(descriptor))
127 if descriptor.full_name() == "google.protobuf.Timestamp" =>
128 {
129 let mut message = DynamicMessage::new(descriptor.clone());
130 message
131 .try_set_field_by_name("seconds", prost_reflect::Value::I64(t.timestamp()))
132 .map_err(|e| format!("Error setting 'seconds' field: {e}"))?;
133 message
134 .try_set_field_by_name("nanos", prost_reflect::Value::I32(t.nanosecond() as i32))
135 .map_err(|e| format!("Error setting 'nanos' field: {e}"))?;
136 Ok(prost_reflect::Value::Message(message))
137 }
138 (Value::Boolean(b), Kind::String) => Ok(prost_reflect::Value::String(b.to_string())),
139 (Value::Integer(i), Kind::String) => Ok(prost_reflect::Value::String(i.to_string())),
140 (Value::Float(f), Kind::String) => Ok(prost_reflect::Value::String(f.to_string())),
141 (Value::Timestamp(t), Kind::String) => Ok(prost_reflect::Value::String(t.to_string())),
142 _ => Err(format!(
143 "Cannot encode `{kind_str}` into protobuf `{kind:?}`",
144 )),
145 }
146}
147
148fn convert_value(
150 field_descriptor: &FieldDescriptor,
151 value: Value,
152 options: &Options,
153) -> Result<prost_reflect::Value, String> {
154 if let Value::Array(a) = value {
155 if field_descriptor.cardinality() == prost_reflect::Cardinality::Repeated {
156 let repeated: Result<Vec<prost_reflect::Value>, String> = a
157 .into_iter()
158 .map(|v| convert_value_raw(v, &field_descriptor.kind(), options))
159 .collect();
160 Ok(prost_reflect::Value::List(repeated?))
161 } else {
162 Err("Cannot encode array into a non-repeated protobuf field".into())
163 }
164 } else {
165 convert_value_raw(value, &field_descriptor.kind(), options)
166 }
167}
168
169pub fn encode_message(
194 message_descriptor: &MessageDescriptor,
195 value: Value,
196 options: &Options,
197) -> Result<DynamicMessage, String> {
198 let mut message = DynamicMessage::new(message_descriptor.clone());
199 if let Value::Object(map) = value {
200 for field in message_descriptor.fields() {
201 let field_name = if options.use_json_names {
202 field.json_name()
203 } else {
204 field.name()
205 };
206 match map.get(field_name) {
207 None | Some(Value::Null) => message.clear_field(&field),
208 Some(value) => message
209 .try_set_field(
210 &field,
211 convert_value(&field, value.clone(), options)
212 .map_err(|e| format!("Error converting {field_name} field: {e}"))?,
213 )
214 .map_err(|e| format!("Error setting {field_name} field: {e}"))?,
215 }
216 }
217 Ok(message)
218 } else {
219 Err("ProtobufSerializer only supports serializing objects".into())
220 }
221}
222
223#[cfg(feature = "enable_system_functions")]
224pub(crate) fn encode_proto(descriptor: &MessageDescriptor, value: Value) -> Resolved {
225 let message = encode_message(descriptor, value, &Options::default())?;
226 let mut buf = Vec::new();
227 message
228 .encode(&mut buf)
229 .map_err(|e| format!("Error encoding protobuf message: {e}"))?;
230 Ok(Value::Bytes(Bytes::from(buf)))
231}
232
233#[cfg(test)]
234mod tests {
235 use super::*;
236 use crate::protobuf::descriptor::get_message_descriptor;
237 use crate::protobuf::parse::parse_proto;
238 use crate::value;
239 use bytes::Bytes;
240 use chrono::DateTime;
241 use ordered_float::NotNan;
242 use prost_reflect::MapKey;
243 use std::collections::{BTreeMap, HashMap};
244 use std::path::PathBuf;
245 use std::{env, fs};
246
247 fn test_data_dir() -> PathBuf {
248 PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap()).join("tests/data/protobuf")
249 }
250
251 macro_rules! mfield {
252 ($m:expr_2021, $f:expr_2021) => {
253 $m.get_field_by_name($f).unwrap().into_owned()
254 };
255 }
256
257 fn test_message_descriptor(message_type: &str) -> MessageDescriptor {
258 let path = test_data_dir().join("test/v1/test.desc");
259 get_message_descriptor(&path, &format!("test.v1.{message_type}")).unwrap()
260 }
261
262 fn test_protobuf3_descriptor() -> MessageDescriptor {
263 let path = test_data_dir().join("test_protobuf3/v1/test_protobuf3.desc");
264 get_message_descriptor(&path, "test_protobuf3.v1.Person").unwrap()
265 }
266
267 #[test]
268 fn test_encode_integers() {
269 let message = encode_message(
270 &test_message_descriptor("Integers"),
271 Value::Object(BTreeMap::from([
272 ("i32".into(), Value::Integer(-1234)),
273 ("i64".into(), Value::Integer(-9876)),
274 ("u32".into(), Value::Integer(1234)),
275 ("u64".into(), Value::Integer(9876)),
276 ])),
277 &Options::default(),
278 )
279 .unwrap();
280 assert_eq!(Some(-1234), mfield!(message, "i32").as_i32());
281 assert_eq!(Some(-9876), mfield!(message, "i64").as_i64());
282 assert_eq!(Some(1234), mfield!(message, "u32").as_u32());
283 assert_eq!(Some(9876), mfield!(message, "u64").as_u64());
284 }
285
286 #[test]
287 fn test_encode_integers_from_bytes() {
288 let message = encode_message(
289 &test_message_descriptor("Integers"),
290 Value::Object(BTreeMap::from([
291 ("i32".into(), Value::Bytes(Bytes::from("-1234"))),
292 ("i64".into(), Value::Bytes(Bytes::from("-9876"))),
293 ("u32".into(), Value::Bytes(Bytes::from("1234"))),
294 ("u64".into(), Value::Bytes(Bytes::from("9876"))),
295 ])),
296 &Options::default(),
297 )
298 .unwrap();
299 assert_eq!(Some(-1234), mfield!(message, "i32").as_i32());
300 assert_eq!(Some(-9876), mfield!(message, "i64").as_i64());
301 assert_eq!(Some(1234), mfield!(message, "u32").as_u32());
302 assert_eq!(Some(9876), mfield!(message, "u64").as_u64());
303 }
304
305 #[test]
306 fn test_encode_floats() {
307 let message = encode_message(
308 &test_message_descriptor("Floats"),
309 Value::Object(BTreeMap::from([
310 ("d".into(), Value::Float(NotNan::new(11.0).unwrap())),
311 ("f".into(), Value::Float(NotNan::new(2.0).unwrap())),
312 ])),
313 &Options::default(),
314 )
315 .unwrap();
316 assert_eq!(Some(11.0), mfield!(message, "d").as_f64());
317 assert_eq!(Some(2.0), mfield!(message, "f").as_f32());
318 }
319
320 #[test]
321 fn test_encode_bytes_as_float() {
322 let message = encode_message(
323 &test_message_descriptor("Floats"),
324 Value::Object(BTreeMap::from([
325 ("d".into(), Value::Bytes(Bytes::from("11.0"))),
326 ("f".into(), Value::Bytes(Bytes::from("2.0"))),
327 ])),
328 &Options::default(),
329 )
330 .unwrap();
331 assert_eq!(Some(11.0), mfield!(message, "d").as_f64());
332 assert_eq!(Some(2.0), mfield!(message, "f").as_f32());
333 }
334
335 #[test]
336 fn test_encode_integer_as_double() {
337 let message = encode_message(
338 &test_message_descriptor("Floats"),
339 Value::Object(BTreeMap::from([("d".into(), Value::Integer(42))])),
340 &Options::default(),
341 )
342 .unwrap();
343 assert_eq!(Some(42.0), mfield!(message, "d").as_f64());
344 }
345
346 #[test]
347 fn test_encode_bytes() {
348 let bytes = Bytes::from(vec![0, 1, 2, 3]);
349 let message = encode_message(
350 &test_message_descriptor("Bytes"),
351 Value::Object(BTreeMap::from([
352 ("text".into(), Value::Bytes(Bytes::from("vector"))),
353 ("binary".into(), Value::Bytes(bytes.clone())),
354 ])),
355 &Options::default(),
356 )
357 .unwrap();
358 assert_eq!(Some("vector"), mfield!(message, "text").as_str());
359 assert_eq!(Some(&bytes), mfield!(message, "binary").as_bytes());
360 }
361
362 #[test]
363 fn test_encode_map() {
364 let message = encode_message(
365 &test_message_descriptor("Map"),
366 Value::Object(BTreeMap::from([
367 (
368 "names".into(),
369 Value::Object(BTreeMap::from([
370 ("forty-four".into(), Value::Integer(44)),
371 ("one".into(), Value::Integer(1)),
372 ])),
373 ),
374 (
375 "people".into(),
376 Value::Object(BTreeMap::from([(
377 "mark".into(),
378 Value::Object(BTreeMap::from([
379 ("nickname".into(), Value::Bytes(Bytes::from("jeff"))),
380 ("age".into(), Value::Integer(22)),
381 ])),
382 )])),
383 ),
384 ])),
385 &Options::default(),
386 )
387 .unwrap();
388 assert_eq!(
390 Some(&HashMap::from([
391 (
392 MapKey::String("forty-four".into()),
393 prost_reflect::Value::I32(44),
394 ),
395 (MapKey::String("one".into()), prost_reflect::Value::I32(1),),
396 ])),
397 mfield!(message, "names").as_map()
398 );
399 let people = mfield!(message, "people").as_map().unwrap().to_owned();
401 assert_eq!(1, people.len());
402 assert_eq!(
403 Some("jeff"),
404 mfield!(
405 people[&MapKey::String("mark".into())].as_message().unwrap(),
406 "nickname"
407 )
408 .as_str()
409 );
410 assert_eq!(
411 Some(22),
412 mfield!(
413 people[&MapKey::String("mark".into())].as_message().unwrap(),
414 "age"
415 )
416 .as_u32()
417 );
418 }
419
420 #[test]
421 fn test_encode_enum() {
422 let message = encode_message(
423 &test_message_descriptor("Enum"),
424 Value::Object(BTreeMap::from([
425 (
426 "breakfast".into(),
427 Value::Bytes(Bytes::from("fruit_tomato")),
428 ),
429 ("dinner".into(), Value::Bytes(Bytes::from("FRUIT_OLIVE"))),
430 ("lunch".into(), Value::Integer(0)),
431 ])),
432 &Options::default(),
433 )
434 .unwrap();
435 assert_eq!(Some(2), mfield!(message, "breakfast").as_enum_number());
436 assert_eq!(Some(0), mfield!(message, "lunch").as_enum_number());
437 assert_eq!(Some(1), mfield!(message, "dinner").as_enum_number());
438 }
439
440 #[test]
441 fn test_encode_timestamp() {
442 let message = encode_message(
443 &test_message_descriptor("Timestamp"),
444 Value::Object(BTreeMap::from([(
445 "morning".into(),
446 Value::Timestamp(
447 DateTime::from_timestamp(8675, 309).expect("could not compute timestamp"),
448 ),
449 )])),
450 &Options::default(),
451 )
452 .unwrap();
453 let timestamp = mfield!(message, "morning").as_message().unwrap().clone();
454 assert_eq!(Some(8675), mfield!(timestamp, "seconds").as_i64());
455 assert_eq!(Some(309), mfield!(timestamp, "nanos").as_i32());
456 }
457
458 #[test]
459 fn test_encode_repeated_primitive() {
460 let message = encode_message(
461 &test_message_descriptor("RepeatedPrimitive"),
462 Value::Object(BTreeMap::from([(
463 "numbers".into(),
464 Value::Array(vec![
465 Value::Integer(8),
466 Value::Integer(6),
467 Value::Integer(4),
468 ]),
469 )])),
470 &Options::default(),
471 )
472 .unwrap();
473 let list = mfield!(message, "numbers").as_list().unwrap().to_vec();
474 assert_eq!(3, list.len());
475 assert_eq!(Some(8), list[0].as_i64());
476 assert_eq!(Some(6), list[1].as_i64());
477 assert_eq!(Some(4), list[2].as_i64());
478 }
479
480 #[test]
481 fn test_encode_repeated_message() {
482 let message = encode_message(
483 &test_message_descriptor("RepeatedMessage"),
484 Value::Object(BTreeMap::from([(
485 "messages".into(),
486 Value::Array(vec![
487 Value::Object(BTreeMap::from([(
488 "text".into(),
489 Value::Bytes(Bytes::from("vector")),
490 )])),
491 Value::Object(BTreeMap::from([("index".into(), Value::Integer(4444))])),
492 Value::Object(BTreeMap::from([
493 ("text".into(), Value::Bytes(Bytes::from("protobuf"))),
494 ("index".into(), Value::Integer(1)),
495 ])),
496 ]),
497 )])),
498 &Options::default(),
499 )
500 .unwrap();
501 let list = mfield!(message, "messages").as_list().unwrap().to_vec();
502 assert_eq!(3, list.len());
503 assert_eq!(
504 Some("vector"),
505 mfield!(list[0].as_message().unwrap(), "text").as_str()
506 );
507 assert!(!list[0].as_message().unwrap().has_field_by_name("index"));
508 assert!(!list[1].as_message().unwrap().has_field_by_name("t4ext"));
509 assert_eq!(
510 Some(4444),
511 mfield!(list[1].as_message().unwrap(), "index").as_u32()
512 );
513 assert_eq!(
514 Some("protobuf"),
515 mfield!(list[2].as_message().unwrap(), "text").as_str()
516 );
517 assert_eq!(
518 Some(1),
519 mfield!(list[2].as_message().unwrap(), "index").as_u32()
520 );
521 }
522
523 #[test]
524 fn test_encode_value_as_string() {
525 let mut message = encode_message(
526 &test_message_descriptor("Bytes"),
527 Value::Object(BTreeMap::from([("text".into(), Value::Boolean(true))])),
528 &Options::default(),
529 )
530 .unwrap();
531 assert_eq!(Some("true"), mfield!(message, "text").as_str());
532 message = encode_message(
533 &test_message_descriptor("Bytes"),
534 Value::Object(BTreeMap::from([("text".into(), Value::Integer(123))])),
535 &Options::default(),
536 )
537 .unwrap();
538 assert_eq!(Some("123"), mfield!(message, "text").as_str());
539 message = encode_message(
540 &test_message_descriptor("Bytes"),
541 Value::Object(BTreeMap::from([(
542 "text".into(),
543 Value::Float(NotNan::new(45.67).unwrap()),
544 )])),
545 &Options::default(),
546 )
547 .unwrap();
548 assert_eq!(Some("45.67"), mfield!(message, "text").as_str());
549 message = encode_message(
550 &test_message_descriptor("Bytes"),
551 Value::Object(BTreeMap::from([(
552 "text".into(),
553 Value::Timestamp(
554 DateTime::from_timestamp(8675, 309).expect("could not compute timestamp"),
555 ),
556 )])),
557 &Options::default(),
558 )
559 .unwrap();
560 assert_eq!(
561 Some("1970-01-01 02:24:35.000000309 UTC"),
562 mfield!(message, "text").as_str()
563 );
564 }
565
566 fn read_pb_file(protobuf_bin_message_path: &str) -> String {
567 fs::read_to_string(test_data_dir().join(protobuf_bin_message_path)).unwrap()
568 }
569
570 #[test]
571 fn test_parse_files() {
572 let value = value!({ name: "Someone", phones: [{number: "123-456"}] });
573 let path = test_data_dir().join("test_protobuf/v1/test_protobuf.desc");
574 let descriptor = get_message_descriptor(&path, "test_protobuf.v1.Person").unwrap();
575 let expected_value = value!(read_pb_file("test_protobuf/v1/input/person_someone.pb"));
576 let encoded_value = encode_proto(&descriptor, value.clone());
577 assert!(
578 encoded_value.is_ok(),
579 "Failed to encode proto: {:?}",
580 encoded_value.unwrap_err()
581 ); let encoded_value = encoded_value.unwrap();
583 assert_eq!(expected_value.as_bytes(), encoded_value.as_bytes());
584
585 let parsed_value = parse_proto(&descriptor, encoded_value);
587 assert!(
588 parsed_value.is_ok(),
589 "Failed to parse proto: {:?}",
590 parsed_value.unwrap_err()
591 );
592 let parsed_value = parsed_value.unwrap();
593 assert_eq!(value, parsed_value)
594 }
595
596 #[test]
597 fn test_parse_proto3() {
598 let value =
599 value!({name: "Someone",phones: [{number: "123-456", type: "PHONE_TYPE_MOBILE"}]});
600 let descriptor = test_protobuf3_descriptor();
601 let expected_value = value!(read_pb_file("test_protobuf3/v1/input/person_someone.pb"));
602 let encoded_value = encode_proto(&descriptor, value.clone());
603 assert!(
604 encoded_value.is_ok(),
605 "Failed to encode proto: {:?}",
606 encoded_value.unwrap_err()
607 ); let encoded_value = encoded_value.unwrap();
609 assert_eq!(encoded_value.as_bytes(), expected_value.as_bytes());
610
611 let parsed_value = parse_proto(&descriptor, encoded_value);
613 assert!(
614 parsed_value.is_ok(),
615 "Failed to parse proto: {:?}",
616 parsed_value.unwrap_err()
617 );
618 let parsed_value = parsed_value.unwrap();
619 assert_eq!(value, parsed_value)
620 }
621
622 #[test]
623 fn test_encode_with_default_options() {
624 let value = value!({
625 name: "Someone",
626 job_description: "Software Engineer"
627 });
628 let descriptor = test_protobuf3_descriptor();
629
630 let message = encode_message(&descriptor, value, &Options::default()).unwrap();
631
632 assert_eq!(Some("Someone"), mfield!(message, "name").as_str());
633 assert_eq!(
634 Some("Software Engineer"),
635 mfield!(message, "job_description").as_str()
636 );
637 }
638
639 #[test]
640 fn test_encode_with_json_names() {
641 let value = value!({
642 name: "Someone",
643 jobDescription: "Software Engineer"
644 });
645 let descriptor = test_protobuf3_descriptor();
646
647 let message = encode_message(
648 &descriptor,
649 value,
650 &Options {
651 use_json_names: true,
652 },
653 )
654 .unwrap();
655
656 assert_eq!(Some("Someone"), mfield!(message, "name").as_str());
657 assert_eq!(
658 Some("Software Engineer"),
659 mfield!(message, "job_description").as_str()
660 );
661 }
662}