Skip to main content

dkit_core/format/
properties.rs

1use std::io::{Read, Write};
2
3use indexmap::IndexMap;
4
5use crate::format::{FormatReader, FormatWriter};
6use crate::value::Value;
7
8/// Java `.properties` 포맷 Reader
9///
10/// `key=value`, `key: value`, `key value` 형식의 Java properties 파일을 파싱하여
11/// flat Object(`{ "key": "value" }`)로 변환한다.
12///
13/// - `#` 또는 `!` 주석 지원
14/// - 빈 줄 무시
15/// - `\` 줄 연속 (multiline value)
16/// - Unicode 이스케이프: `\uXXXX`
17/// - 키의 `.` 구분자는 유지 (flat 모델)
18pub struct PropertiesReader;
19
20impl PropertiesReader {
21    /// 이스케이프 시퀀스를 처리한다.
22    fn unescape(s: &str) -> String {
23        let mut result = String::with_capacity(s.len());
24        let mut chars = s.chars();
25        while let Some(c) = chars.next() {
26            if c == '\\' {
27                match chars.next() {
28                    Some('n') => result.push('\n'),
29                    Some('t') => result.push('\t'),
30                    Some('r') => result.push('\r'),
31                    Some('\\') => result.push('\\'),
32                    Some('=') => result.push('='),
33                    Some(':') => result.push(':'),
34                    Some(' ') => result.push(' '),
35                    Some('#') => result.push('#'),
36                    Some('!') => result.push('!'),
37                    Some('u') => {
38                        // Unicode escape: \uXXXX
39                        let hex: String = chars.by_ref().take(4).collect();
40                        if hex.len() == 4 {
41                            if let Ok(code) = u32::from_str_radix(&hex, 16) {
42                                if let Some(ch) = char::from_u32(code) {
43                                    result.push(ch);
44                                    continue;
45                                }
46                            }
47                        }
48                        // 유효하지 않은 unicode escape는 원본 유지
49                        result.push_str("\\u");
50                        result.push_str(&hex);
51                    }
52                    Some(other) => {
53                        // 알 수 없는 이스케이프는 문자만 유지
54                        result.push(other);
55                    }
56                    None => {
57                        // 문자열 끝의 \는 무시
58                    }
59                }
60            } else {
61                result.push(c);
62            }
63        }
64        result
65    }
66
67    /// 키-값 구분자 위치를 찾는다 (이스케이프되지 않은 `=`, `:`, 또는 공백).
68    /// Java properties 사양에 따라: 먼저 이스케이프되지 않은 `=` 또는 `:`를 찾고,
69    /// 없으면 첫 번째 공백을 구분자로 사용한다.
70    fn find_separator(line: &str) -> Option<(usize, usize)> {
71        let bytes = line.as_bytes();
72
73        // 1단계: 이스케이프되지 않은 `=` 또는 `:` 찾기
74        let mut escaped = false;
75        for (i, &b) in bytes.iter().enumerate() {
76            if escaped {
77                escaped = false;
78                continue;
79            }
80            if b == b'\\' {
81                escaped = true;
82                continue;
83            }
84            if b == b'=' || b == b':' {
85                // 구분자 뒤의 선행 공백 건너뛰기
86                let value_start = line[i + 1..]
87                    .find(|c: char| c != ' ' && c != '\t')
88                    .map_or(line.len(), |pos| i + 1 + pos);
89                return Some((i, value_start));
90            }
91        }
92
93        // 2단계: 이스케이프되지 않은 첫 공백 찾기
94        escaped = false;
95        for (i, &b) in bytes.iter().enumerate() {
96            if escaped {
97                escaped = false;
98                continue;
99            }
100            if b == b'\\' {
101                escaped = true;
102                continue;
103            }
104            if b == b' ' || b == b'\t' {
105                let value_start = line[i..]
106                    .find(|c: char| c != ' ' && c != '\t')
107                    .map_or(line.len(), |pos| i + pos);
108                return Some((i, value_start));
109            }
110        }
111
112        None
113    }
114
115    /// 논리적 줄을 결합한다 (줄 연속 `\` 처리).
116    fn join_logical_lines(input: &str) -> Vec<String> {
117        let mut logical_lines = Vec::new();
118        let mut current = String::new();
119        let mut continuation = false;
120
121        for line in input.lines() {
122            if continuation {
123                // 연속 줄: 선행 공백 제거 후 이어붙임
124                current.push_str(line.trim_start());
125            } else {
126                if !current.is_empty() {
127                    logical_lines.push(std::mem::take(&mut current));
128                }
129                current = line.to_string();
130            }
131
132            // 줄 끝에 홀수 개의 `\`가 있으면 줄 연속
133            let trailing_backslashes = current.bytes().rev().take_while(|&b| b == b'\\').count();
134            if trailing_backslashes % 2 == 1 {
135                // 마지막 `\` 제거
136                current.truncate(current.len() - 1);
137                continuation = true;
138            } else {
139                continuation = false;
140            }
141        }
142
143        if !current.is_empty() {
144            logical_lines.push(current);
145        }
146
147        logical_lines
148    }
149}
150
151impl FormatReader for PropertiesReader {
152    fn read(&self, input: &str) -> anyhow::Result<Value> {
153        let mut map = IndexMap::new();
154        let logical_lines = Self::join_logical_lines(input);
155
156        for (line_num, line) in logical_lines.iter().enumerate() {
157            let trimmed = line.trim_start();
158
159            // 빈 줄 무시
160            if trimmed.is_empty() {
161                continue;
162            }
163
164            // 주석 무시 (`#` 또는 `!`)
165            if trimmed.starts_with('#') || trimmed.starts_with('!') {
166                continue;
167            }
168
169            // 키-값 분리
170            match Self::find_separator(trimmed) {
171                Some((key_end, value_start)) => {
172                    let raw_key = &trimmed[..key_end];
173                    let raw_value = if value_start <= trimmed.len() {
174                        &trimmed[value_start..]
175                    } else {
176                        ""
177                    };
178                    let key = Self::unescape(raw_key.trim_end());
179                    let value = Self::unescape(raw_value);
180
181                    if key.is_empty() {
182                        return Err(crate::error::DkitError::ParseErrorAt {
183                            format: "Properties".to_string(),
184                            source: "empty key".to_string().into(),
185                            line: line_num + 1,
186                            column: 1,
187                            line_text: line.to_string(),
188                        }
189                        .into());
190                    }
191
192                    map.insert(key, Value::String(value));
193                }
194                None => {
195                    // 구분자 없음 = 빈 값의 키
196                    let key = Self::unescape(trimmed);
197                    if !key.is_empty() {
198                        map.insert(key, Value::String(String::new()));
199                    }
200                }
201            }
202        }
203
204        Ok(Value::Object(map))
205    }
206
207    fn read_from_reader(&self, mut reader: impl Read) -> anyhow::Result<Value> {
208        let mut input = String::new();
209        reader
210            .read_to_string(&mut input)
211            .map_err(|e| crate::error::DkitError::ParseError {
212                format: "Properties".to_string(),
213                source: Box::new(e),
214            })?;
215        self.read(&input)
216    }
217}
218
219/// Java `.properties` 포맷 Writer
220///
221/// flat Object를 `key=value` 형식의 Java properties 파일로 출력한다.
222pub struct PropertiesWriter;
223
224impl PropertiesWriter {
225    /// 키를 이스케이프한다.
226    fn escape_key(key: &str) -> String {
227        let mut result = String::with_capacity(key.len());
228        for (i, c) in key.chars().enumerate() {
229            match c {
230                ' ' => {
231                    if i == 0 {
232                        result.push_str("\\ ");
233                    } else {
234                        result.push(' ');
235                    }
236                }
237                '=' => result.push_str("\\="),
238                ':' => result.push_str("\\:"),
239                '\\' => result.push_str("\\\\"),
240                '#' => result.push_str("\\#"),
241                '!' => result.push_str("\\!"),
242                '\t' => result.push_str("\\t"),
243                '\n' => result.push_str("\\n"),
244                '\r' => result.push_str("\\r"),
245                c if !(' '..='~').contains(&c) => {
246                    // Non-ASCII → \uXXXX
247                    for unit in c.encode_utf16(&mut [0u16; 2]) {
248                        result.push_str(&format!("\\u{:04X}", unit));
249                    }
250                }
251                _ => result.push(c),
252            }
253        }
254        result
255    }
256
257    /// 값을 이스케이프한다.
258    fn escape_value(value: &str) -> String {
259        let mut result = String::with_capacity(value.len());
260        for (i, c) in value.chars().enumerate() {
261            match c {
262                ' ' if i == 0 => result.push_str("\\ "),
263                '\\' => result.push_str("\\\\"),
264                '\t' => result.push_str("\\t"),
265                '\n' => result.push_str("\\n"),
266                '\r' => result.push_str("\\r"),
267                c if !(' '..='~').contains(&c) => {
268                    for unit in c.encode_utf16(&mut [0u16; 2]) {
269                        result.push_str(&format!("\\u{:04X}", unit));
270                    }
271                }
272                _ => result.push(c),
273            }
274        }
275        result
276    }
277
278    /// Value를 문자열로 변환한다.
279    fn value_to_string(value: &Value) -> String {
280        match value {
281            Value::String(s) => s.clone(),
282            Value::Null => String::new(),
283            Value::Bool(b) => b.to_string(),
284            Value::Integer(n) => n.to_string(),
285            Value::Float(f) => f.to_string(),
286            Value::Array(_) | Value::Object(_) => serde_json::to_string(value).unwrap_or_default(),
287        }
288    }
289}
290
291impl FormatWriter for PropertiesWriter {
292    fn write(&self, value: &Value) -> anyhow::Result<String> {
293        match value {
294            Value::Object(map) => {
295                let mut output = String::new();
296                for (key, val) in map {
297                    let escaped_key = Self::escape_key(key);
298                    let raw_value = Self::value_to_string(val);
299                    let escaped_value = Self::escape_value(&raw_value);
300                    output.push_str(&format!("{}={}\n", escaped_key, escaped_value));
301                }
302                Ok(output)
303            }
304            _ => {
305                anyhow::bail!(
306                    "Properties format requires an Object value (flat key-value pairs). Got: {}",
307                    match value {
308                        Value::Null => "null",
309                        Value::Bool(_) => "boolean",
310                        Value::Integer(_) => "integer",
311                        Value::Float(_) => "float",
312                        Value::String(_) => "string",
313                        Value::Array(_) => "array",
314                        _ => "unknown",
315                    }
316                );
317            }
318        }
319    }
320
321    fn write_to_writer(&self, value: &Value, mut writer: impl Write) -> anyhow::Result<()> {
322        let output = self.write(value)?;
323        writer
324            .write_all(output.as_bytes())
325            .map_err(|e| crate::error::DkitError::WriteError {
326                format: "Properties".to_string(),
327                source: Box::new(e),
328            })?;
329        Ok(())
330    }
331}
332
333#[cfg(test)]
334mod tests {
335    use super::*;
336
337    // --- PropertiesReader 테스트 ---
338
339    #[test]
340    fn test_reader_simple_key_value() {
341        let reader = PropertiesReader;
342        let input = "name=Alice\nage=30\n";
343        let v = reader.read(input).unwrap();
344        let obj = v.as_object().unwrap();
345        assert_eq!(obj.get("name"), Some(&Value::String("Alice".to_string())));
346        assert_eq!(obj.get("age"), Some(&Value::String("30".to_string())));
347    }
348
349    #[test]
350    fn test_reader_colon_separator() {
351        let reader = PropertiesReader;
352        let input = "name: Alice\nage: 30\n";
353        let v = reader.read(input).unwrap();
354        let obj = v.as_object().unwrap();
355        assert_eq!(obj.get("name"), Some(&Value::String("Alice".to_string())));
356        assert_eq!(obj.get("age"), Some(&Value::String("30".to_string())));
357    }
358
359    #[test]
360    fn test_reader_space_separator() {
361        let reader = PropertiesReader;
362        let input = "name Alice\nage 30\n";
363        let v = reader.read(input).unwrap();
364        let obj = v.as_object().unwrap();
365        assert_eq!(obj.get("name"), Some(&Value::String("Alice".to_string())));
366        assert_eq!(obj.get("age"), Some(&Value::String("30".to_string())));
367    }
368
369    #[test]
370    fn test_reader_comments_hash() {
371        let reader = PropertiesReader;
372        let input = "# This is a comment\nkey=value\n";
373        let v = reader.read(input).unwrap();
374        let obj = v.as_object().unwrap();
375        assert_eq!(obj.len(), 1);
376        assert_eq!(obj.get("key"), Some(&Value::String("value".to_string())));
377    }
378
379    #[test]
380    fn test_reader_comments_exclamation() {
381        let reader = PropertiesReader;
382        let input = "! This is a comment\nkey=value\n";
383        let v = reader.read(input).unwrap();
384        let obj = v.as_object().unwrap();
385        assert_eq!(obj.len(), 1);
386    }
387
388    #[test]
389    fn test_reader_empty_lines() {
390        let reader = PropertiesReader;
391        let input = "key1=val1\n\n\nkey2=val2\n";
392        let v = reader.read(input).unwrap();
393        let obj = v.as_object().unwrap();
394        assert_eq!(obj.len(), 2);
395    }
396
397    #[test]
398    fn test_reader_empty_value() {
399        let reader = PropertiesReader;
400        let input = "key=\n";
401        let v = reader.read(input).unwrap();
402        let obj = v.as_object().unwrap();
403        assert_eq!(obj.get("key"), Some(&Value::String(String::new())));
404    }
405
406    #[test]
407    fn test_reader_key_without_separator() {
408        let reader = PropertiesReader;
409        // key 뒤에 구분자가 없으면 빈 값
410        let input = "lonely_key\n";
411        let v = reader.read(input).unwrap();
412        let obj = v.as_object().unwrap();
413        assert_eq!(obj.get("lonely_key"), Some(&Value::String(String::new())));
414    }
415
416    #[test]
417    fn test_reader_multiline_value() {
418        let reader = PropertiesReader;
419        let input = "message=Hello \\\n    World\n";
420        let v = reader.read(input).unwrap();
421        let obj = v.as_object().unwrap();
422        assert_eq!(
423            obj.get("message"),
424            Some(&Value::String("Hello World".to_string()))
425        );
426    }
427
428    #[test]
429    fn test_reader_multiline_multiple_continuations() {
430        let reader = PropertiesReader;
431        let input = "long=line1 \\\nline2 \\\nline3\n";
432        let v = reader.read(input).unwrap();
433        let obj = v.as_object().unwrap();
434        assert_eq!(
435            obj.get("long"),
436            Some(&Value::String("line1 line2 line3".to_string()))
437        );
438    }
439
440    #[test]
441    fn test_reader_unicode_escape() {
442        let reader = PropertiesReader;
443        let input = "greeting=\\u0048\\u0065\\u006C\\u006C\\u006F\n";
444        let v = reader.read(input).unwrap();
445        let obj = v.as_object().unwrap();
446        assert_eq!(
447            obj.get("greeting"),
448            Some(&Value::String("Hello".to_string()))
449        );
450    }
451
452    #[test]
453    fn test_reader_escaped_separator_in_key() {
454        let reader = PropertiesReader;
455        let input = "key\\=with\\=equals=value\n";
456        let v = reader.read(input).unwrap();
457        let obj = v.as_object().unwrap();
458        assert_eq!(
459            obj.get("key=with=equals"),
460            Some(&Value::String("value".to_string()))
461        );
462    }
463
464    #[test]
465    fn test_reader_escaped_special_chars() {
466        let reader = PropertiesReader;
467        let input = "path=C\\:\\\\Users\\\\test\n";
468        let v = reader.read(input).unwrap();
469        let obj = v.as_object().unwrap();
470        assert_eq!(
471            obj.get("path"),
472            Some(&Value::String("C:\\Users\\test".to_string()))
473        );
474    }
475
476    #[test]
477    fn test_reader_dotted_keys() {
478        let reader = PropertiesReader;
479        let input = "app.db.host=localhost\napp.db.port=5432\n";
480        let v = reader.read(input).unwrap();
481        let obj = v.as_object().unwrap();
482        assert_eq!(
483            obj.get("app.db.host"),
484            Some(&Value::String("localhost".to_string()))
485        );
486        assert_eq!(
487            obj.get("app.db.port"),
488            Some(&Value::String("5432".to_string()))
489        );
490    }
491
492    #[test]
493    fn test_reader_whitespace_around_separator() {
494        let reader = PropertiesReader;
495        let input = "key = value\n";
496        let v = reader.read(input).unwrap();
497        let obj = v.as_object().unwrap();
498        assert_eq!(obj.get("key"), Some(&Value::String("value".to_string())));
499    }
500
501    #[test]
502    fn test_reader_duplicate_keys_last_wins() {
503        let reader = PropertiesReader;
504        let input = "key=first\nkey=second\n";
505        let v = reader.read(input).unwrap();
506        let obj = v.as_object().unwrap();
507        assert_eq!(obj.get("key"), Some(&Value::String("second".to_string())));
508    }
509
510    #[test]
511    fn test_reader_empty_input() {
512        let reader = PropertiesReader;
513        let v = reader.read("").unwrap();
514        assert!(v.as_object().unwrap().is_empty());
515    }
516
517    #[test]
518    fn test_reader_only_comments() {
519        let reader = PropertiesReader;
520        let input = "# comment 1\n! comment 2\n";
521        let v = reader.read(input).unwrap();
522        assert!(v.as_object().unwrap().is_empty());
523    }
524
525    #[test]
526    fn test_reader_from_reader() {
527        let reader = PropertiesReader;
528        let input = b"key=value" as &[u8];
529        let v = reader.read_from_reader(input).unwrap();
530        let obj = v.as_object().unwrap();
531        assert_eq!(obj.get("key"), Some(&Value::String("value".to_string())));
532    }
533
534    #[test]
535    fn test_reader_newline_escape_in_value() {
536        let reader = PropertiesReader;
537        let input = "msg=line1\\nline2\n";
538        let v = reader.read(input).unwrap();
539        let obj = v.as_object().unwrap();
540        assert_eq!(
541            obj.get("msg"),
542            Some(&Value::String("line1\nline2".to_string()))
543        );
544    }
545
546    #[test]
547    fn test_reader_tab_escape_in_value() {
548        let reader = PropertiesReader;
549        let input = "data=col1\\tcol2\n";
550        let v = reader.read(input).unwrap();
551        let obj = v.as_object().unwrap();
552        assert_eq!(
553            obj.get("data"),
554            Some(&Value::String("col1\tcol2".to_string()))
555        );
556    }
557
558    #[test]
559    fn test_reader_realistic_i18n() {
560        let reader = PropertiesReader;
561        let input = r#"# Application messages
562app.title=My Application
563app.greeting=Welcome, {0}!
564app.error.notfound=Page not found
565app.error.server=Internal server error
566"#;
567        let v = reader.read(input).unwrap();
568        let obj = v.as_object().unwrap();
569        assert_eq!(obj.len(), 4);
570        assert_eq!(
571            obj.get("app.title"),
572            Some(&Value::String("My Application".to_string()))
573        );
574        assert_eq!(
575            obj.get("app.greeting"),
576            Some(&Value::String("Welcome, {0}!".to_string()))
577        );
578    }
579
580    // --- PropertiesWriter 테스트 ---
581
582    #[test]
583    fn test_writer_simple() {
584        let writer = PropertiesWriter;
585        let v = Value::Object({
586            let mut m = IndexMap::new();
587            m.insert("name".to_string(), Value::String("Alice".to_string()));
588            m.insert("age".to_string(), Value::String("30".to_string()));
589            m
590        });
591        let output = writer.write(&v).unwrap();
592        assert!(output.contains("name=Alice\n"));
593        assert!(output.contains("age=30\n"));
594    }
595
596    #[test]
597    fn test_writer_special_chars_in_key() {
598        let writer = PropertiesWriter;
599        let v = Value::Object({
600            let mut m = IndexMap::new();
601            m.insert("key=with".to_string(), Value::String("value".to_string()));
602            m
603        });
604        let output = writer.write(&v).unwrap();
605        assert!(output.contains("key\\=with=value\n"));
606    }
607
608    #[test]
609    fn test_writer_non_ascii() {
610        let writer = PropertiesWriter;
611        let v = Value::Object({
612            let mut m = IndexMap::new();
613            m.insert(
614                "greeting".to_string(),
615                Value::String("こんにちは".to_string()),
616            );
617            m
618        });
619        let output = writer.write(&v).unwrap();
620        assert!(output.contains("greeting="));
621        assert!(output.contains("\\u"));
622    }
623
624    #[test]
625    fn test_writer_newline_in_value() {
626        let writer = PropertiesWriter;
627        let v = Value::Object({
628            let mut m = IndexMap::new();
629            m.insert("msg".to_string(), Value::String("line1\nline2".to_string()));
630            m
631        });
632        let output = writer.write(&v).unwrap();
633        assert!(output.contains("msg=line1\\nline2\n"));
634    }
635
636    #[test]
637    fn test_writer_non_string_values() {
638        let writer = PropertiesWriter;
639        let v = Value::Object({
640            let mut m = IndexMap::new();
641            m.insert("count".to_string(), Value::Integer(42));
642            m.insert("rate".to_string(), Value::Float(3.14));
643            m.insert("enabled".to_string(), Value::Bool(true));
644            m.insert("empty".to_string(), Value::Null);
645            m
646        });
647        let output = writer.write(&v).unwrap();
648        assert!(output.contains("count=42\n"));
649        assert!(output.contains("rate=3.14\n"));
650        assert!(output.contains("enabled=true\n"));
651        assert!(output.contains("empty=\n"));
652    }
653
654    #[test]
655    fn test_writer_non_object_error() {
656        let writer = PropertiesWriter;
657        let result = writer.write(&Value::String("hello".to_string()));
658        assert!(result.is_err());
659    }
660
661    #[test]
662    fn test_writer_to_writer() {
663        let writer = PropertiesWriter;
664        let v = Value::Object({
665            let mut m = IndexMap::new();
666            m.insert("key".to_string(), Value::String("value".to_string()));
667            m
668        });
669        let mut buf = Vec::new();
670        writer.write_to_writer(&v, &mut buf).unwrap();
671        let output = String::from_utf8(buf).unwrap();
672        assert_eq!(output, "key=value\n");
673    }
674
675    #[test]
676    fn test_writer_leading_space_in_value() {
677        let writer = PropertiesWriter;
678        let v = Value::Object({
679            let mut m = IndexMap::new();
680            m.insert("key".to_string(), Value::String(" leading".to_string()));
681            m
682        });
683        let output = writer.write(&v).unwrap();
684        assert!(output.contains("key=\\ leading\n"));
685    }
686
687    // --- 왕복 변환 테스트 ---
688
689    #[test]
690    fn test_roundtrip_simple() {
691        let input = "name=Alice\nage=30\n";
692        let reader = PropertiesReader;
693        let writer = PropertiesWriter;
694
695        let value = reader.read(input).unwrap();
696        let output = writer.write(&value).unwrap();
697        let value2 = reader.read(&output).unwrap();
698
699        assert_eq!(value, value2);
700    }
701
702    #[test]
703    fn test_roundtrip_dotted_keys() {
704        let input = "app.db.host=localhost\napp.db.port=5432\n";
705        let reader = PropertiesReader;
706        let writer = PropertiesWriter;
707
708        let value = reader.read(input).unwrap();
709        let output = writer.write(&value).unwrap();
710        let value2 = reader.read(&output).unwrap();
711
712        assert_eq!(value, value2);
713    }
714
715    #[test]
716    fn test_roundtrip_special_chars() {
717        let reader = PropertiesReader;
718        let writer = PropertiesWriter;
719
720        let v = Value::Object({
721            let mut m = IndexMap::new();
722            m.insert("key=eq".to_string(), Value::String("val:colon".to_string()));
723            m.insert("path".to_string(), Value::String("C:\\Users".to_string()));
724            m
725        });
726
727        let output = writer.write(&v).unwrap();
728        let value2 = reader.read(&output).unwrap();
729        assert_eq!(v, value2);
730    }
731
732    #[test]
733    fn test_roundtrip_unicode() {
734        let reader = PropertiesReader;
735        let writer = PropertiesWriter;
736
737        let v = Value::Object({
738            let mut m = IndexMap::new();
739            m.insert("msg".to_string(), Value::String("Hello 세계".to_string()));
740            m
741        });
742
743        let output = writer.write(&v).unwrap();
744        let value2 = reader.read(&output).unwrap();
745        assert_eq!(v, value2);
746    }
747
748    #[test]
749    fn test_roundtrip_newlines() {
750        let reader = PropertiesReader;
751        let writer = PropertiesWriter;
752
753        let v = Value::Object({
754            let mut m = IndexMap::new();
755            m.insert(
756                "multiline".to_string(),
757                Value::String("line1\nline2\nline3".to_string()),
758            );
759            m
760        });
761
762        let output = writer.write(&v).unwrap();
763        let value2 = reader.read(&output).unwrap();
764        assert_eq!(v, value2);
765    }
766}