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::warn!(
196 category = s,
197 known = "Lifecycle, Hil, Echo, UserInput, Output",
198 "Unknown event category in Lua subscription, treating as Extension"
199 );
200 Some(EventCategory::Extension {
201 namespace: "lua".to_string(),
202 kind: s.to_string(),
203 })
204 }
205 }
206}
207
208pub fn parse_status(s: &str) -> Status {
210 match s.to_lowercase().as_str() {
211 "initializing" => Status::Initializing,
212 "idle" => Status::Idle,
213 "running" => Status::Running,
214 "paused" => Status::Paused,
215 "awaitingapproval" | "awaiting_approval" => Status::AwaitingApproval,
216 "completed" => Status::Completed,
217 "error" => Status::Error,
218 "aborted" => Status::Aborted,
219 _ => Status::Idle,
220 }
221}
222
223#[cfg(test)]
228fn escape_lua_string(s: &str) -> String {
229 let mut result = String::with_capacity(s.len() + 2);
230 result.push('"');
231 for c in s.chars() {
232 match c {
233 '\\' => result.push_str("\\\\"),
234 '"' => result.push_str("\\\""),
235 '\n' => result.push_str("\\n"),
236 '\r' => result.push_str("\\r"),
237 '\t' => result.push_str("\\t"),
238 '\0' => result.push_str("\\0"),
239 '\x07' => result.push_str("\\a"),
241 '\x08' => result.push_str("\\b"),
242 '\x0C' => result.push_str("\\f"),
243 '\x0B' => result.push_str("\\v"),
244 c if c.is_control() => {
246 result.push_str(&format!("\\{:03}", c as u32));
247 }
248 c => result.push(c),
249 }
250 }
251 result.push('"');
252 result
253}
254
255#[cfg(test)]
257fn escape_lua_key(key: &str) -> String {
258 let is_valid_identifier = !key.is_empty()
260 && key
261 .chars()
262 .next()
263 .is_some_and(|c| c.is_ascii_alphabetic() || c == '_')
264 && key.chars().all(|c| c.is_ascii_alphanumeric() || c == '_');
265
266 if is_valid_identifier {
267 key.to_string()
268 } else {
269 format!("[{}]", escape_lua_string(key))
270 }
271}
272
273#[cfg(test)]
279pub fn json_to_lua(value: &serde_json::Value) -> String {
280 match value {
281 serde_json::Value::Null => "nil".to_string(),
282 serde_json::Value::Bool(b) => b.to_string(),
283 serde_json::Value::Number(n) => n.to_string(),
284 serde_json::Value::String(s) => escape_lua_string(s),
285 serde_json::Value::Array(arr) => {
286 let items: Vec<String> = arr.iter().map(json_to_lua).collect();
287 format!("{{{}}}", items.join(", "))
288 }
289 serde_json::Value::Object(obj) => {
290 let items: Vec<String> = obj
291 .iter()
292 .map(|(k, v)| format!("{} = {}", escape_lua_key(k), json_to_lua(v)))
293 .collect();
294 format!("{{{}}}", items.join(", "))
295 }
296 }
297}
298
299pub fn serde_json_to_lua(value: &serde_json::Value, lua: &Lua) -> Result<Value, mlua::Error> {
301 match value {
302 serde_json::Value::Null => Ok(Value::Nil),
303 serde_json::Value::Bool(b) => Ok(Value::Boolean(*b)),
304 serde_json::Value::Number(n) => {
305 if let Some(i) = n.as_i64() {
306 Ok(Value::Integer(i))
307 } else if let Some(f) = n.as_f64() {
308 Ok(Value::Number(f))
309 } else {
310 Err(mlua::Error::SerializeError("invalid number".into()))
311 }
312 }
313 serde_json::Value::String(s) => Ok(Value::String(lua.create_string(s)?)),
314 serde_json::Value::Array(arr) => {
315 let table = lua.create_table()?;
316 for (i, v) in arr.iter().enumerate() {
317 table.raw_set(i + 1, serde_json_to_lua(v, lua)?)?;
318 }
319 Ok(Value::Table(table))
320 }
321 serde_json::Value::Object(obj) => {
322 let table = lua.create_table()?;
323 for (k, v) in obj {
324 table.set(k.as_str(), serde_json_to_lua(v, lua)?)?;
325 }
326 Ok(Value::Table(table))
327 }
328 }
329}
330
331#[allow(clippy::only_used_in_recursion)]
333pub fn lua_to_json(value: Value, lua: &Lua) -> Result<serde_json::Value, mlua::Error> {
334 match value {
335 Value::Nil => Ok(serde_json::Value::Null),
336 Value::Boolean(b) => Ok(serde_json::Value::Bool(b)),
337 Value::Integer(i) => Ok(serde_json::Value::Number(i.into())),
338 Value::Number(n) => serde_json::Number::from_f64(n)
339 .map(serde_json::Value::Number)
340 .ok_or_else(|| mlua::Error::SerializeError("invalid number".into())),
341 Value::String(s) => Ok(serde_json::Value::String(s.to_string_lossy().to_string())),
342 Value::Table(table) => {
343 let len = table.raw_len();
345 if len > 0 {
346 let mut arr = Vec::new();
348 for i in 1..=len {
349 let v: Value = table.raw_get(i)?;
350 arr.push(lua_to_json(v, lua)?);
351 }
352 Ok(serde_json::Value::Array(arr))
353 } else {
354 let mut map = serde_json::Map::new();
356 for pair in table.pairs::<String, Value>() {
357 let (k, v) = pair?;
358 map.insert(k, lua_to_json(v, lua)?);
359 }
360 Ok(serde_json::Value::Object(map))
361 }
362 }
363 _ => Err(mlua::Error::SerializeError("unsupported type".into())),
364 }
365}
366
367#[cfg(test)]
368mod tests {
369 use super::*;
370
371 #[test]
372 fn parse_signal_response_variants() {
373 assert!(matches!(
374 parse_signal_response("Handled"),
375 SignalResponse::Handled
376 ));
377 assert!(matches!(
378 parse_signal_response("Abort"),
379 SignalResponse::Abort
380 ));
381 assert!(matches!(
382 parse_signal_response("Ignored"),
383 SignalResponse::Ignored
384 ));
385 assert!(matches!(
386 parse_signal_response("unknown"),
387 SignalResponse::Ignored
388 ));
389 }
390
391 #[test]
392 fn parse_event_category_variants() {
393 assert_eq!(parse_event_category("Echo"), Some(EventCategory::Echo));
394 assert_eq!(parse_event_category("Hil"), Some(EventCategory::Hil));
395 assert!(matches!(
397 parse_event_category("Custom"),
398 Some(EventCategory::Extension { .. })
399 ));
400 }
401
402 #[test]
403 fn parse_status_variants() {
404 assert_eq!(parse_status("idle"), Status::Idle);
405 assert_eq!(parse_status("Running"), Status::Running);
406 assert_eq!(parse_status("aborted"), Status::Aborted);
407 }
408
409 #[test]
410 fn json_to_lua_conversion() {
411 assert_eq!(json_to_lua(&serde_json::json!(null)), "nil");
412 assert_eq!(json_to_lua(&serde_json::json!(true)), "true");
413 assert_eq!(json_to_lua(&serde_json::json!(42)), "42");
414 assert_eq!(json_to_lua(&serde_json::json!("hello")), "\"hello\"");
415 }
416
417 #[test]
420 fn escape_lua_string_basic_escapes() {
421 assert_eq!(escape_lua_string(r"a\b"), r#""a\\b""#);
423 assert_eq!(escape_lua_string(r#"a"b"#), r#""a\"b""#);
425 assert_eq!(escape_lua_string("a\nb"), r#""a\nb""#);
427 assert_eq!(escape_lua_string("a\rb"), r#""a\rb""#);
429 assert_eq!(escape_lua_string("a\tb"), r#""a\tb""#);
431 assert_eq!(escape_lua_string("a\0b"), r#""a\0b""#);
433 }
434
435 #[test]
436 fn escape_lua_string_control_chars() {
437 assert_eq!(escape_lua_string("a\x07b"), r#""a\ab""#);
439 assert_eq!(escape_lua_string("a\x08b"), r#""a\bb""#);
441 assert_eq!(escape_lua_string("a\x0Cb"), r#""a\fb""#);
443 assert_eq!(escape_lua_string("a\x0Bb"), r#""a\vb""#);
445 }
446
447 #[test]
448 fn escape_lua_string_injection_attempt() {
449 let malicious = "hello\nend; os.execute('rm -rf /')--";
451 let escaped = escape_lua_string(malicious);
452 assert_eq!(escaped, r#""hello\nend; os.execute('rm -rf /')--""#);
454
455 assert!(!escaped.contains('\n'));
457 }
458
459 #[test]
460 fn escape_lua_string_unicode() {
461 assert_eq!(escape_lua_string("日本語"), "\"日本語\"");
463 assert_eq!(escape_lua_string("emoji: 🎉"), "\"emoji: 🎉\"");
464 }
465
466 #[test]
467 fn escape_lua_string_empty() {
468 assert_eq!(escape_lua_string(""), "\"\"");
469 }
470
471 #[test]
472 fn escape_lua_key_valid_identifiers() {
473 assert_eq!(escape_lua_key("foo"), "foo");
474 assert_eq!(escape_lua_key("_bar"), "_bar");
475 assert_eq!(escape_lua_key("baz123"), "baz123");
476 assert_eq!(escape_lua_key("a_b_c"), "a_b_c");
477 }
478
479 #[test]
480 fn escape_lua_key_invalid_identifiers() {
481 assert_eq!(escape_lua_key("123abc"), "[\"123abc\"]");
483 assert_eq!(escape_lua_key("foo-bar"), "[\"foo-bar\"]");
485 assert_eq!(escape_lua_key("foo bar"), "[\"foo bar\"]");
487 assert_eq!(escape_lua_key(""), "[\"\"]");
489 }
490
491 #[test]
492 fn json_to_lua_nested_structure() {
493 let nested = serde_json::json!({
494 "level1": {
495 "level2": {
496 "value": "deep"
497 }
498 }
499 });
500 let lua_str = json_to_lua(&nested);
501 assert!(lua_str.contains("level1"));
502 assert!(lua_str.contains("level2"));
503 assert!(lua_str.contains("\"deep\""));
504 }
505
506 #[test]
507 fn json_to_lua_array() {
508 let arr = serde_json::json!([1, 2, "three", null]);
509 let lua_str = json_to_lua(&arr);
510 assert_eq!(lua_str, "{1, 2, \"three\", nil}");
511 }
512
513 #[test]
514 fn json_to_lua_special_string_in_object() {
515 let obj = serde_json::json!({
516 "key": "value\nwith\nnewlines"
517 });
518 let lua_str = json_to_lua(&obj);
519 assert!(!lua_str.contains('\n'));
521 assert!(lua_str.contains(r"\n"));
522 }
523
524 #[test]
525 fn json_to_lua_execution_safety() {
526 let lua = Lua::new();
528
529 let payload = serde_json::json!({
531 "data": "test\"); os.execute(\"echo pwned"
532 });
533 let lua_code = format!("return {}", json_to_lua(&payload));
534
535 let result: mlua::Result<mlua::Table> = lua.load(&lua_code).eval();
537 assert!(result.is_ok(), "Lua code should parse safely");
538
539 let table = result.expect("should parse safely without executing injected code");
541 let data: String = table
542 .get("data")
543 .expect("should have data field in safe table");
544 assert!(data.contains("os.execute"));
545 }
546
547 #[test]
550 fn lua_to_json_empty_table() {
551 let lua = Lua::new();
552 let table = lua.create_table().expect("should create empty lua table");
553 let result =
554 lua_to_json(Value::Table(table), &lua).expect("should convert empty table to json");
555 assert_eq!(result, serde_json::json!({}));
556 }
557
558 #[test]
559 fn lua_to_json_array_table() {
560 let lua = Lua::new();
561 let table = lua.create_table().expect("should create array lua table");
562 table
563 .raw_set(1, "a")
564 .expect("should set index 1 in array table");
565 table
566 .raw_set(2, "b")
567 .expect("should set index 2 in array table");
568 let result =
569 lua_to_json(Value::Table(table), &lua).expect("should convert array table to json");
570 assert_eq!(result, serde_json::json!(["a", "b"]));
571 }
572
573 #[test]
576 fn lua_response_explicit_success() {
577 let lua = Lua::new();
578 let table = lua.create_table().expect("create table");
579 table.set("success", true).expect("set success");
580 table
581 .set("data", lua.create_table().expect("inner table"))
582 .expect("set data");
583 let resp = LuaResponse::from_lua(Value::Table(table), &lua).expect("parse response");
584 assert!(resp.success);
585 assert!(resp.data.is_some());
586 assert!(resp.error.is_none());
587 }
588
589 #[test]
590 fn lua_response_explicit_failure() {
591 let lua = Lua::new();
592 let table = lua.create_table().expect("create table");
593 table.set("success", false).expect("set success");
594 table.set("error", "boom").expect("set error");
595 let resp = LuaResponse::from_lua(Value::Table(table), &lua).expect("parse response");
596 assert!(!resp.success);
597 assert_eq!(resp.error.as_deref(), Some("boom"));
598 }
599
600 #[test]
601 fn lua_response_missing_success_with_error_infers_false() {
602 let lua = Lua::new();
606 let table = lua.create_table().expect("create table");
607 table.set("error", "llm call failed").expect("set error");
608 table.set("source", "llm-worker").expect("set source");
609 let resp = LuaResponse::from_lua(Value::Table(table), &lua).expect("parse response");
610 assert!(
611 !resp.success,
612 "should infer failure from error field presence"
613 );
614 assert_eq!(resp.error.as_deref(), Some("llm call failed"));
615 }
616
617 #[test]
618 fn lua_response_missing_success_without_error_infers_true() {
619 let lua = Lua::new();
621 let table = lua.create_table().expect("create table");
622 table.set("response", "hello").expect("set response");
623 table.set("source", "llm-worker").expect("set source");
624 let resp = LuaResponse::from_lua(Value::Table(table), &lua).expect("parse response");
625 assert!(resp.success, "should infer success when no error field");
626 }
627
628 #[test]
629 fn lua_response_nil_is_success() {
630 let lua = Lua::new();
631 let resp = LuaResponse::from_lua(Value::Nil, &lua).expect("parse nil response");
632 assert!(resp.success);
633 assert!(resp.data.is_none());
634 }
635
636 #[test]
637 fn lua_to_json_mixed_numbers() {
638 let lua = Lua::new();
639
640 let int_result = lua_to_json(Value::Integer(42), &lua).expect("integer conversion");
642 assert_eq!(int_result, serde_json::json!(42));
643
644 let float_result = lua_to_json(Value::Number(2.72), &lua).expect("float conversion");
646 assert!(float_result.as_f64().expect("f64") - 2.72 < 0.001);
647 }
648
649 #[test]
650 fn lua_to_json_valid_utf8_string() {
651 let lua = Lua::new();
652 let s = lua
653 .create_string("こんにちは世界")
654 .expect("create JP string");
655 let result =
656 lua_to_json(Value::String(s), &lua).expect("valid UTF-8 should convert successfully");
657 assert_eq!(result, serde_json::json!("こんにちは世界"));
658 }
659
660 #[test]
661 fn lua_to_json_invalid_utf8_string_uses_lossy() {
662 let lua = Lua::new();
663 let bytes: &[u8] = &[0x61, 0x62, 0x63, 0xE3, 0x81];
666 let s = lua.create_string(bytes).expect("create binary string");
667 let result = lua_to_json(Value::String(s), &lua)
668 .expect("invalid UTF-8 should not error; lossy conversion instead");
669 let text = result.as_str().expect("should be a string");
670 assert!(
671 text.starts_with("abc"),
672 "valid prefix preserved, got: {text}"
673 );
674 assert!(
675 text.contains('\u{FFFD}'),
676 "replacement char expected for broken bytes, got: {text}"
677 );
678 }
679
680 #[test]
681 fn lua_to_json_truncated_multibyte_in_table() {
682 let lua = Lua::new();
683 let broken: &[u8] = &[0xE3, 0x81];
686 let table = lua.create_table().expect("create table");
687 table
688 .set("message", lua.create_string(broken).expect("broken str"))
689 .expect("set message");
690 let result = lua_to_json(Value::Table(table), &lua)
691 .expect("table with invalid UTF-8 string should convert via lossy");
692 let msg = result
693 .get("message")
694 .expect("message key")
695 .as_str()
696 .expect("string value");
697 assert!(
698 msg.contains('\u{FFFD}'),
699 "replacement char expected, got: {msg}"
700 );
701 }
702
703 const UTF8_TRUNCATE_LUA: &str = r#"
708 local function utf8_truncate(s, max_bytes)
709 if #s <= max_bytes then return s end
710 local pos = max_bytes
711 while pos > 0 do
712 local b = string.byte(s, pos)
713 if b < 0x80 or b >= 0xC0 then break end
714 pos = pos - 1
715 end
716 if pos > 0 then
717 local b = string.byte(s, pos)
718 local char_len = 1
719 if b >= 0xF0 then char_len = 4
720 elseif b >= 0xE0 then char_len = 3
721 elseif b >= 0xC0 then char_len = 2
722 end
723 if pos + char_len - 1 > max_bytes then
724 return s:sub(1, pos - 1)
725 end
726 return s:sub(1, pos + char_len - 1)
727 end
728 return ""
729 end
730 return utf8_truncate
731 "#;
732
733 fn load_utf8_truncate(lua: &Lua) -> mlua::Function {
734 lua.load(UTF8_TRUNCATE_LUA)
735 .eval::<mlua::Function>()
736 .expect("load utf8_truncate")
737 }
738
739 #[test]
740 fn utf8_truncate_ascii_within_limit() {
741 let lua = Lua::new();
742 let f = load_utf8_truncate(&lua);
743 let result: String = f.call(("hello", 10)).expect("call");
744 assert_eq!(result, "hello");
745 }
746
747 #[test]
748 fn utf8_truncate_ascii_exact_limit() {
749 let lua = Lua::new();
750 let f = load_utf8_truncate(&lua);
751 let result: String = f.call(("hello", 5)).expect("call");
752 assert_eq!(result, "hello");
753 }
754
755 #[test]
756 fn utf8_truncate_ascii_over_limit() {
757 let lua = Lua::new();
758 let f = load_utf8_truncate(&lua);
759 let result: String = f.call(("hello world", 5)).expect("call");
760 assert_eq!(result, "hello");
761 }
762
763 #[test]
764 fn utf8_truncate_jp_preserves_full_chars() {
765 let lua = Lua::new();
766 let f = load_utf8_truncate(&lua);
767 let result: String = f.call(("あいう", 7)).expect("call");
770 assert_eq!(result, "あい");
771 }
772
773 #[test]
774 fn utf8_truncate_jp_exact_boundary() {
775 let lua = Lua::new();
776 let f = load_utf8_truncate(&lua);
777 let result: String = f.call(("あいう", 9)).expect("call");
779 assert_eq!(result, "あいう");
780 }
781
782 #[test]
783 fn utf8_truncate_mixed_ascii_jp() {
784 let lua = Lua::new();
785 let f = load_utf8_truncate(&lua);
786 let result: String = f.call(("abcあ", 5)).expect("call");
788 assert_eq!(result, "abc");
789 }
790
791 #[test]
792 fn utf8_truncate_4byte_emoji() {
793 let lua = Lua::new();
794 let f = load_utf8_truncate(&lua);
795 let result: String = f.call(("a😀b", 3)).expect("call");
797 assert_eq!(result, "a");
798 }
799
800 #[test]
801 fn utf8_truncate_result_is_valid_utf8() {
802 let lua = Lua::new();
803 let f = load_utf8_truncate(&lua);
804 let input = "日本語テスト";
806 for limit in 1..=18 {
807 let result: String = f.call((input, limit)).expect("call");
808 assert!(
810 result.len() <= limit,
811 "limit={limit}, got {} bytes: {result}",
812 result.len()
813 );
814 }
815 }
816}