1use mlua::{FromLua, IntoLua, Lua, Result as LuaResult, Value};
4use orcs_component::{EventCategory, Status};
5use orcs_event::{Request, Signal, SignalKind, SignalResponse};
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct LuaRequest {
11 pub id: String,
13 pub operation: String,
15 pub category: String,
17 pub payload: serde_json::Value,
19}
20
21impl LuaRequest {
22 #[must_use]
24 pub fn from_request(request: &Request) -> Self {
25 Self {
26 id: request.id.to_string(),
27 operation: request.operation.clone(),
28 category: request.category.to_string(),
29 payload: request.payload.clone(),
30 }
31 }
32}
33
34impl IntoLua for LuaRequest {
35 fn into_lua(self, lua: &Lua) -> LuaResult<Value> {
36 let table = lua.create_table()?;
37 table.set("id", self.id)?;
38 table.set("operation", self.operation)?;
39 table.set("category", self.category)?;
40 let payload = serde_json_to_lua(&self.payload, lua)?;
42 table.set("payload", payload)?;
43 Ok(Value::Table(table))
44 }
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct LuaSignal {
50 pub kind: String,
52 pub scope: String,
54 pub target_id: Option<String>,
56 pub approval_id: Option<String>,
58 pub reason: Option<String>,
60}
61
62impl LuaSignal {
63 #[must_use]
65 pub fn from_signal(signal: &Signal) -> Self {
66 let (approval_id, reason) = match &signal.kind {
67 SignalKind::Approve { approval_id } => (Some(approval_id.clone()), None),
68 SignalKind::Reject {
69 approval_id,
70 reason,
71 } => (Some(approval_id.clone()), reason.clone()),
72 SignalKind::Modify { approval_id, .. } => (Some(approval_id.clone()), None),
73 _ => (None, None),
74 };
75
76 Self {
77 kind: signal_kind_to_string(&signal.kind),
78 scope: signal.scope.to_string(),
79 target_id: None,
80 approval_id,
81 reason,
82 }
83 }
84}
85
86fn signal_kind_to_string(kind: &SignalKind) -> String {
88 match kind {
89 SignalKind::Veto => "Veto".to_string(),
90 SignalKind::Cancel => "Cancel".to_string(),
91 SignalKind::Pause => "Pause".to_string(),
92 SignalKind::Resume => "Resume".to_string(),
93 SignalKind::Steer { .. } => "Steer".to_string(),
94 SignalKind::Approve { .. } => "Approve".to_string(),
95 SignalKind::Reject { .. } => "Reject".to_string(),
96 SignalKind::Modify { .. } => "Modify".to_string(),
97 }
98}
99
100impl IntoLua for LuaSignal {
101 fn into_lua(self, lua: &Lua) -> LuaResult<Value> {
102 let table = lua.create_table()?;
103 table.set("kind", self.kind)?;
104 table.set("scope", self.scope)?;
105 if let Some(id) = self.target_id {
106 table.set("target_id", id)?;
107 }
108 if let Some(id) = self.approval_id {
109 table.set("approval_id", id)?;
110 }
111 if let Some(reason) = self.reason {
112 table.set("reason", reason)?;
113 }
114 Ok(Value::Table(table))
115 }
116}
117
118#[derive(Debug, Clone)]
120pub struct LuaResponse {
121 pub success: bool,
123 pub data: Option<serde_json::Value>,
125 pub error: Option<String>,
127}
128
129impl FromLua for LuaResponse {
130 fn from_lua(value: Value, lua: &Lua) -> LuaResult<Self> {
131 match value {
132 Value::Table(table) => {
133 let success = match table.get::<Value>("success") {
137 Ok(Value::Boolean(b)) => b,
138 Ok(Value::Nil) => {
139 table.get::<String>("error").is_err()
141 }
142 _ => true,
143 };
144 let data = table
145 .get::<Value>("data")
146 .ok()
147 .and_then(|v| lua_to_json(v, lua).ok());
148 let error: Option<String> = table.get("error").ok();
149 Ok(Self {
150 success,
151 data,
152 error,
153 })
154 }
155 Value::Nil => Ok(Self {
156 success: true,
157 data: None,
158 error: None,
159 }),
160 _ => {
161 let data = lua_to_json(value, lua).ok();
163 Ok(Self {
164 success: true,
165 data,
166 error: None,
167 })
168 }
169 }
170 }
171}
172
173pub fn parse_signal_response(s: &str) -> SignalResponse {
175 match s.to_lowercase().as_str() {
176 "handled" => SignalResponse::Handled,
177 "abort" => SignalResponse::Abort,
178 _ => SignalResponse::Ignored,
179 }
180}
181
182pub fn parse_event_category(s: &str) -> Option<EventCategory> {
188 match s {
189 "Lifecycle" => Some(EventCategory::Lifecycle),
190 "Hil" => Some(EventCategory::Hil),
191 "Echo" => Some(EventCategory::Echo),
192 "UserInput" => Some(EventCategory::UserInput),
193 "Output" => Some(EventCategory::Output),
194 _ => {
195 tracing::info!(
196 category = s,
197 "Lua subscription: treating custom category as Extension"
198 );
199 Some(EventCategory::Extension {
200 namespace: "lua".to_string(),
201 kind: s.to_string(),
202 })
203 }
204 }
205}
206
207pub fn parse_status(s: &str) -> Status {
209 match s.to_lowercase().as_str() {
210 "initializing" => Status::Initializing,
211 "idle" => Status::Idle,
212 "running" => Status::Running,
213 "paused" => Status::Paused,
214 "awaitingapproval" | "awaiting_approval" => Status::AwaitingApproval,
215 "completed" => Status::Completed,
216 "error" => Status::Error,
217 "aborted" => Status::Aborted,
218 _ => Status::Idle,
219 }
220}
221
222#[cfg(test)]
227fn escape_lua_string(s: &str) -> String {
228 let mut result = String::with_capacity(s.len() + 2);
229 result.push('"');
230 for c in s.chars() {
231 match c {
232 '\\' => result.push_str("\\\\"),
233 '"' => result.push_str("\\\""),
234 '\n' => result.push_str("\\n"),
235 '\r' => result.push_str("\\r"),
236 '\t' => result.push_str("\\t"),
237 '\0' => result.push_str("\\0"),
238 '\x07' => result.push_str("\\a"),
240 '\x08' => result.push_str("\\b"),
241 '\x0C' => result.push_str("\\f"),
242 '\x0B' => result.push_str("\\v"),
243 c if c.is_control() => {
245 result.push_str(&format!("\\{:03}", c as u32));
246 }
247 c => result.push(c),
248 }
249 }
250 result.push('"');
251 result
252}
253
254#[cfg(test)]
256fn escape_lua_key(key: &str) -> String {
257 let is_valid_identifier = !key.is_empty()
259 && key
260 .chars()
261 .next()
262 .is_some_and(|c| c.is_ascii_alphabetic() || c == '_')
263 && key.chars().all(|c| c.is_ascii_alphanumeric() || c == '_');
264
265 if is_valid_identifier {
266 key.to_string()
267 } else {
268 format!("[{}]", escape_lua_string(key))
269 }
270}
271
272#[cfg(test)]
278pub fn json_to_lua(value: &serde_json::Value) -> String {
279 match value {
280 serde_json::Value::Null => "nil".to_string(),
281 serde_json::Value::Bool(b) => b.to_string(),
282 serde_json::Value::Number(n) => n.to_string(),
283 serde_json::Value::String(s) => escape_lua_string(s),
284 serde_json::Value::Array(arr) => {
285 let items: Vec<String> = arr.iter().map(json_to_lua).collect();
286 format!("{{{}}}", items.join(", "))
287 }
288 serde_json::Value::Object(obj) => {
289 let items: Vec<String> = obj
290 .iter()
291 .map(|(k, v)| format!("{} = {}", escape_lua_key(k), json_to_lua(v)))
292 .collect();
293 format!("{{{}}}", items.join(", "))
294 }
295 }
296}
297
298pub fn serde_json_to_lua(value: &serde_json::Value, lua: &Lua) -> Result<Value, mlua::Error> {
300 match value {
301 serde_json::Value::Null => Ok(Value::Nil),
302 serde_json::Value::Bool(b) => Ok(Value::Boolean(*b)),
303 serde_json::Value::Number(n) => {
304 if let Some(i) = n.as_i64() {
305 Ok(Value::Integer(i))
306 } else if let Some(f) = n.as_f64() {
307 Ok(Value::Number(f))
308 } else {
309 Err(mlua::Error::SerializeError("invalid number".into()))
310 }
311 }
312 serde_json::Value::String(s) => Ok(Value::String(lua.create_string(s)?)),
313 serde_json::Value::Array(arr) => {
314 let table = lua.create_table()?;
315 for (i, v) in arr.iter().enumerate() {
316 table.raw_set(i + 1, serde_json_to_lua(v, lua)?)?;
317 }
318 Ok(Value::Table(table))
319 }
320 serde_json::Value::Object(obj) => {
321 let table = lua.create_table()?;
322 for (k, v) in obj {
323 table.set(k.as_str(), serde_json_to_lua(v, lua)?)?;
324 }
325 Ok(Value::Table(table))
326 }
327 }
328}
329
330#[allow(clippy::only_used_in_recursion)]
332pub fn lua_to_json(value: Value, lua: &Lua) -> Result<serde_json::Value, mlua::Error> {
333 match value {
334 Value::Nil => Ok(serde_json::Value::Null),
335 Value::Boolean(b) => Ok(serde_json::Value::Bool(b)),
336 Value::Integer(i) => Ok(serde_json::Value::Number(i.into())),
337 Value::Number(n) => serde_json::Number::from_f64(n)
338 .map(serde_json::Value::Number)
339 .ok_or_else(|| mlua::Error::SerializeError("invalid number".into())),
340 Value::String(s) => Ok(serde_json::Value::String(s.to_string_lossy().to_string())),
341 Value::Table(table) => {
342 let len = table.raw_len();
344 if len > 0 {
345 let mut arr = Vec::new();
347 for i in 1..=len {
348 let v: Value = table.raw_get(i)?;
349 arr.push(lua_to_json(v, lua)?);
350 }
351 Ok(serde_json::Value::Array(arr))
352 } else {
353 let mut map = serde_json::Map::new();
355 for pair in table.pairs::<String, Value>() {
356 let (k, v) = pair?;
357 map.insert(k, lua_to_json(v, lua)?);
358 }
359 Ok(serde_json::Value::Object(map))
360 }
361 }
362 _ => Err(mlua::Error::SerializeError("unsupported type".into())),
363 }
364}
365
366#[cfg(test)]
367mod tests {
368 use super::*;
369
370 #[test]
371 fn parse_signal_response_variants() {
372 assert!(matches!(
373 parse_signal_response("Handled"),
374 SignalResponse::Handled
375 ));
376 assert!(matches!(
377 parse_signal_response("Abort"),
378 SignalResponse::Abort
379 ));
380 assert!(matches!(
381 parse_signal_response("Ignored"),
382 SignalResponse::Ignored
383 ));
384 assert!(matches!(
385 parse_signal_response("unknown"),
386 SignalResponse::Ignored
387 ));
388 }
389
390 #[test]
391 fn parse_event_category_variants() {
392 assert_eq!(parse_event_category("Echo"), Some(EventCategory::Echo));
393 assert_eq!(parse_event_category("Hil"), Some(EventCategory::Hil));
394 assert!(matches!(
396 parse_event_category("Custom"),
397 Some(EventCategory::Extension { .. })
398 ));
399 }
400
401 #[test]
402 fn parse_status_variants() {
403 assert_eq!(parse_status("idle"), Status::Idle);
404 assert_eq!(parse_status("Running"), Status::Running);
405 assert_eq!(parse_status("aborted"), Status::Aborted);
406 }
407
408 #[test]
409 fn json_to_lua_conversion() {
410 assert_eq!(json_to_lua(&serde_json::json!(null)), "nil");
411 assert_eq!(json_to_lua(&serde_json::json!(true)), "true");
412 assert_eq!(json_to_lua(&serde_json::json!(42)), "42");
413 assert_eq!(json_to_lua(&serde_json::json!("hello")), "\"hello\"");
414 }
415
416 #[test]
419 fn escape_lua_string_basic_escapes() {
420 assert_eq!(escape_lua_string(r"a\b"), r#""a\\b""#);
422 assert_eq!(escape_lua_string(r#"a"b"#), r#""a\"b""#);
424 assert_eq!(escape_lua_string("a\nb"), r#""a\nb""#);
426 assert_eq!(escape_lua_string("a\rb"), r#""a\rb""#);
428 assert_eq!(escape_lua_string("a\tb"), r#""a\tb""#);
430 assert_eq!(escape_lua_string("a\0b"), r#""a\0b""#);
432 }
433
434 #[test]
435 fn escape_lua_string_control_chars() {
436 assert_eq!(escape_lua_string("a\x07b"), r#""a\ab""#);
438 assert_eq!(escape_lua_string("a\x08b"), r#""a\bb""#);
440 assert_eq!(escape_lua_string("a\x0Cb"), r#""a\fb""#);
442 assert_eq!(escape_lua_string("a\x0Bb"), r#""a\vb""#);
444 }
445
446 #[test]
447 fn escape_lua_string_injection_attempt() {
448 let malicious = "hello\nend; os.execute('rm -rf /')--";
450 let escaped = escape_lua_string(malicious);
451 assert_eq!(escaped, r#""hello\nend; os.execute('rm -rf /')--""#);
453
454 assert!(!escaped.contains('\n'));
456 }
457
458 #[test]
459 fn escape_lua_string_unicode() {
460 assert_eq!(escape_lua_string("日本語"), "\"日本語\"");
462 assert_eq!(escape_lua_string("emoji: 🎉"), "\"emoji: 🎉\"");
463 }
464
465 #[test]
466 fn escape_lua_string_empty() {
467 assert_eq!(escape_lua_string(""), "\"\"");
468 }
469
470 #[test]
471 fn escape_lua_key_valid_identifiers() {
472 assert_eq!(escape_lua_key("foo"), "foo");
473 assert_eq!(escape_lua_key("_bar"), "_bar");
474 assert_eq!(escape_lua_key("baz123"), "baz123");
475 assert_eq!(escape_lua_key("a_b_c"), "a_b_c");
476 }
477
478 #[test]
479 fn escape_lua_key_invalid_identifiers() {
480 assert_eq!(escape_lua_key("123abc"), "[\"123abc\"]");
482 assert_eq!(escape_lua_key("foo-bar"), "[\"foo-bar\"]");
484 assert_eq!(escape_lua_key("foo bar"), "[\"foo bar\"]");
486 assert_eq!(escape_lua_key(""), "[\"\"]");
488 }
489
490 #[test]
491 fn json_to_lua_nested_structure() {
492 let nested = serde_json::json!({
493 "level1": {
494 "level2": {
495 "value": "deep"
496 }
497 }
498 });
499 let lua_str = json_to_lua(&nested);
500 assert!(lua_str.contains("level1"));
501 assert!(lua_str.contains("level2"));
502 assert!(lua_str.contains("\"deep\""));
503 }
504
505 #[test]
506 fn json_to_lua_array() {
507 let arr = serde_json::json!([1, 2, "three", null]);
508 let lua_str = json_to_lua(&arr);
509 assert_eq!(lua_str, "{1, 2, \"three\", nil}");
510 }
511
512 #[test]
513 fn json_to_lua_special_string_in_object() {
514 let obj = serde_json::json!({
515 "key": "value\nwith\nnewlines"
516 });
517 let lua_str = json_to_lua(&obj);
518 assert!(!lua_str.contains('\n'));
520 assert!(lua_str.contains(r"\n"));
521 }
522
523 #[test]
524 fn json_to_lua_execution_safety() {
525 let lua = Lua::new();
527
528 let payload = serde_json::json!({
530 "data": "test\"); os.execute(\"echo pwned"
531 });
532 let lua_code = format!("return {}", json_to_lua(&payload));
533
534 let result: mlua::Result<mlua::Table> = lua.load(&lua_code).eval();
536 assert!(result.is_ok(), "Lua code should parse safely");
537
538 let table = result.expect("should parse safely without executing injected code");
540 let data: String = table
541 .get("data")
542 .expect("should have data field in safe table");
543 assert!(data.contains("os.execute"));
544 }
545
546 #[test]
549 fn lua_to_json_empty_table() {
550 let lua = Lua::new();
551 let table = lua.create_table().expect("should create empty lua table");
552 let result =
553 lua_to_json(Value::Table(table), &lua).expect("should convert empty table to json");
554 assert_eq!(result, serde_json::json!({}));
555 }
556
557 #[test]
558 fn lua_to_json_array_table() {
559 let lua = Lua::new();
560 let table = lua.create_table().expect("should create array lua table");
561 table
562 .raw_set(1, "a")
563 .expect("should set index 1 in array table");
564 table
565 .raw_set(2, "b")
566 .expect("should set index 2 in array table");
567 let result =
568 lua_to_json(Value::Table(table), &lua).expect("should convert array table to json");
569 assert_eq!(result, serde_json::json!(["a", "b"]));
570 }
571
572 #[test]
575 fn lua_response_explicit_success() {
576 let lua = Lua::new();
577 let table = lua.create_table().expect("create table");
578 table.set("success", true).expect("set success");
579 table
580 .set("data", lua.create_table().expect("inner table"))
581 .expect("set data");
582 let resp = LuaResponse::from_lua(Value::Table(table), &lua).expect("parse response");
583 assert!(resp.success);
584 assert!(resp.data.is_some());
585 assert!(resp.error.is_none());
586 }
587
588 #[test]
589 fn lua_response_explicit_failure() {
590 let lua = Lua::new();
591 let table = lua.create_table().expect("create table");
592 table.set("success", false).expect("set success");
593 table.set("error", "boom").expect("set error");
594 let resp = LuaResponse::from_lua(Value::Table(table), &lua).expect("parse response");
595 assert!(!resp.success);
596 assert_eq!(resp.error.as_deref(), Some("boom"));
597 }
598
599 #[test]
600 fn lua_response_missing_success_with_error_infers_false() {
601 let lua = Lua::new();
605 let table = lua.create_table().expect("create table");
606 table.set("error", "llm call failed").expect("set error");
607 table.set("source", "common-agent").expect("set source");
608 let resp = LuaResponse::from_lua(Value::Table(table), &lua).expect("parse response");
609 assert!(
610 !resp.success,
611 "should infer failure from error field presence"
612 );
613 assert_eq!(resp.error.as_deref(), Some("llm call failed"));
614 }
615
616 #[test]
617 fn lua_response_missing_success_without_error_infers_true() {
618 let lua = Lua::new();
620 let table = lua.create_table().expect("create table");
621 table.set("response", "hello").expect("set response");
622 table.set("source", "common-agent").expect("set source");
623 let resp = LuaResponse::from_lua(Value::Table(table), &lua).expect("parse response");
624 assert!(resp.success, "should infer success when no error field");
625 }
626
627 #[test]
628 fn lua_response_nil_is_success() {
629 let lua = Lua::new();
630 let resp = LuaResponse::from_lua(Value::Nil, &lua).expect("parse nil response");
631 assert!(resp.success);
632 assert!(resp.data.is_none());
633 }
634
635 #[test]
636 fn lua_to_json_mixed_numbers() {
637 let lua = Lua::new();
638
639 let int_result = lua_to_json(Value::Integer(42), &lua).expect("integer conversion");
641 assert_eq!(int_result, serde_json::json!(42));
642
643 let float_result = lua_to_json(Value::Number(2.72), &lua).expect("float conversion");
645 assert!(float_result.as_f64().expect("f64") - 2.72 < 0.001);
646 }
647
648 #[test]
649 fn lua_to_json_valid_utf8_string() {
650 let lua = Lua::new();
651 let s = lua
652 .create_string("こんにちは世界")
653 .expect("create JP string");
654 let result =
655 lua_to_json(Value::String(s), &lua).expect("valid UTF-8 should convert successfully");
656 assert_eq!(result, serde_json::json!("こんにちは世界"));
657 }
658
659 #[test]
660 fn lua_to_json_invalid_utf8_string_uses_lossy() {
661 let lua = Lua::new();
662 let bytes: &[u8] = &[0x61, 0x62, 0x63, 0xE3, 0x81];
665 let s = lua.create_string(bytes).expect("create binary string");
666 let result = lua_to_json(Value::String(s), &lua)
667 .expect("invalid UTF-8 should not error; lossy conversion instead");
668 let text = result.as_str().expect("should be a string");
669 assert!(
670 text.starts_with("abc"),
671 "valid prefix preserved, got: {text}"
672 );
673 assert!(
674 text.contains('\u{FFFD}'),
675 "replacement char expected for broken bytes, got: {text}"
676 );
677 }
678
679 #[test]
680 fn lua_to_json_truncated_multibyte_in_table() {
681 let lua = Lua::new();
682 let broken: &[u8] = &[0xE3, 0x81];
685 let table = lua.create_table().expect("create table");
686 table
687 .set("message", lua.create_string(broken).expect("broken str"))
688 .expect("set message");
689 let result = lua_to_json(Value::Table(table), &lua)
690 .expect("table with invalid UTF-8 string should convert via lossy");
691 let msg = result
692 .get("message")
693 .expect("message key")
694 .as_str()
695 .expect("string value");
696 assert!(
697 msg.contains('\u{FFFD}'),
698 "replacement char expected, got: {msg}"
699 );
700 }
701
702 const UTF8_TRUNCATE_LUA: &str = r#"
707 local function utf8_truncate(s, max_bytes)
708 if #s <= max_bytes then return s end
709 local pos = max_bytes
710 while pos > 0 do
711 local b = string.byte(s, pos)
712 if b < 0x80 or b >= 0xC0 then break end
713 pos = pos - 1
714 end
715 if pos > 0 then
716 local b = string.byte(s, pos)
717 local char_len = 1
718 if b >= 0xF0 then char_len = 4
719 elseif b >= 0xE0 then char_len = 3
720 elseif b >= 0xC0 then char_len = 2
721 end
722 if pos + char_len - 1 > max_bytes then
723 return s:sub(1, pos - 1)
724 end
725 return s:sub(1, pos + char_len - 1)
726 end
727 return ""
728 end
729 return utf8_truncate
730 "#;
731
732 fn load_utf8_truncate(lua: &Lua) -> mlua::Function {
733 lua.load(UTF8_TRUNCATE_LUA)
734 .eval::<mlua::Function>()
735 .expect("load utf8_truncate")
736 }
737
738 #[test]
739 fn utf8_truncate_ascii_within_limit() {
740 let lua = Lua::new();
741 let f = load_utf8_truncate(&lua);
742 let result: String = f.call(("hello", 10)).expect("call");
743 assert_eq!(result, "hello");
744 }
745
746 #[test]
747 fn utf8_truncate_ascii_exact_limit() {
748 let lua = Lua::new();
749 let f = load_utf8_truncate(&lua);
750 let result: String = f.call(("hello", 5)).expect("call");
751 assert_eq!(result, "hello");
752 }
753
754 #[test]
755 fn utf8_truncate_ascii_over_limit() {
756 let lua = Lua::new();
757 let f = load_utf8_truncate(&lua);
758 let result: String = f.call(("hello world", 5)).expect("call");
759 assert_eq!(result, "hello");
760 }
761
762 #[test]
763 fn utf8_truncate_jp_preserves_full_chars() {
764 let lua = Lua::new();
765 let f = load_utf8_truncate(&lua);
766 let result: String = f.call(("あいう", 7)).expect("call");
769 assert_eq!(result, "あい");
770 }
771
772 #[test]
773 fn utf8_truncate_jp_exact_boundary() {
774 let lua = Lua::new();
775 let f = load_utf8_truncate(&lua);
776 let result: String = f.call(("あいう", 9)).expect("call");
778 assert_eq!(result, "あいう");
779 }
780
781 #[test]
782 fn utf8_truncate_mixed_ascii_jp() {
783 let lua = Lua::new();
784 let f = load_utf8_truncate(&lua);
785 let result: String = f.call(("abcあ", 5)).expect("call");
787 assert_eq!(result, "abc");
788 }
789
790 #[test]
791 fn utf8_truncate_4byte_emoji() {
792 let lua = Lua::new();
793 let f = load_utf8_truncate(&lua);
794 let result: String = f.call(("a😀b", 3)).expect("call");
796 assert_eq!(result, "a");
797 }
798
799 #[test]
800 fn utf8_truncate_result_is_valid_utf8() {
801 let lua = Lua::new();
802 let f = load_utf8_truncate(&lua);
803 let input = "日本語テスト";
805 for limit in 1..=18 {
806 let result: String = f.call((input, limit)).expect("call");
807 assert!(
809 result.len() <= limit,
810 "limit={limit}, got {} bytes: {result}",
811 result.len()
812 );
813 }
814 }
815}