toon_core/
lib.rs

1//! TOON-LD Core Library
2//!
3//! High-performance serializer/parser for TOON-LD (Token-Oriented Object Notation for Linked Data).
4//! Implements the TOON format v3.0 specification with JSON-LD context expansion support.
5//!
6//! # Overview
7//!
8//! TOON-LD is a text serialization format designed to minimize token count while preserving
9//! the semantic richness of JSON-LD. It achieves 40-60% token reduction compared to equivalent
10//! JSON-LD representations.
11//!
12//! # Features
13//!
14//! - **Tabular Arrays**: Arrays of objects are serialized as CSV-like tables with a header
15//! - **Primitive Arrays**: Simple value arrays use compact inline notation
16//! - **JSON-LD Context**: Full support for URI compaction/expansion via `@context`
17//! - **JSON-LD 1.1 Keywords**: Support for all standard JSON-LD keywords
18//!
19//! # Example
20//!
21//! ```
22//! use toon_core::{encode, decode};
23//!
24//! let json_ld = r#"{
25//!     "@context": {"foaf": "http://xmlns.com/foaf/0.1/"},
26//!     "http://xmlns.com/foaf/0.1/name": "Alice",
27//!     "http://xmlns.com/foaf/0.1/age": 30
28//! }"#;
29//!
30//! // Convert JSON-LD to TOON-LD
31//! let toon = encode(json_ld).unwrap();
32//! assert!(toon.contains("foaf:name: Alice"));
33//!
34//! // Convert back to JSON-LD
35//! let back = decode(&toon).unwrap();
36//! ```
37//!
38//! # Modules
39//!
40//! - [`context`]: JSON-LD context handling for URI compaction/expansion
41//! - [`error`]: Error types for TOON-LD operations
42//! - [`keywords`]: JSON-LD keyword constants and utilities
43//! - [`parser`]: TOON-LD parser implementation
44//! - [`serializer`]: TOON-LD serializer implementation
45
46pub mod context;
47pub mod error;
48pub mod keywords;
49pub mod parser;
50pub mod serializer;
51
52// Re-export main types at crate root for convenience
53pub use context::JsonLdContext;
54pub use error::{Result, ToonError};
55pub use keywords::*;
56pub use parser::ToonParser;
57pub use serializer::ToonSerializer;
58
59use serde_json::Value;
60
61/// Encode JSON-LD string to TOON-LD format
62pub fn encode(json: &str) -> Result<String> {
63    let value: serde_json::Value = serde_json::from_str(json).map_err(ToonError::from)?;
64    let context = JsonLdContext::from_value(&value);
65    let serializer = ToonSerializer::new().with_context(context);
66    serializer.serialize(&value)
67}
68
69/// Decode TOON-LD string to JSON-LD format
70pub fn decode(toon: &str) -> Result<String> {
71    let parser = ToonParser::new();
72    let value = parser.parse(toon)?;
73    serde_json::to_string_pretty(&value).map_err(ToonError::from)
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    #[test]
81    fn test_primitive_values() {
82        let serializer = ToonSerializer::new();
83        let parser = ToonParser::new();
84
85        let json = r#"{"name": "Alice", "age": 30, "active": true}"#;
86        let toon = serializer.serialize_json(json).unwrap();
87        let back = parser.parse(&toon).unwrap();
88
89        assert_eq!(back.get("name").unwrap(), "Alice");
90        assert_eq!(back.get("age").unwrap(), 30);
91        assert_eq!(back.get("active").unwrap(), true);
92    }
93
94    #[test]
95    fn test_tabular_array() {
96        let serializer = ToonSerializer::new();
97
98        let json = r#"{
99            "people": [
100                {"name": "Alice", "age": 30},
101                {"name": "Bob", "age": 25}
102            ]
103        }"#;
104
105        let toon = serializer.serialize_json(json).unwrap();
106        assert!(toon.contains("people[2]{"));
107        assert!(toon.contains("Alice"));
108        assert!(toon.contains("Bob"));
109    }
110
111    #[test]
112    fn test_primitive_array() {
113        let serializer = ToonSerializer::new();
114
115        let json = r#"{"tags": ["rust", "wasm", "python"]}"#;
116        let toon = serializer.serialize_json(json).unwrap();
117        assert!(toon.contains("tags[3]:"));
118    }
119
120    #[test]
121    fn test_quoting() {
122        let serializer = ToonSerializer::new();
123
124        // Value with comma should be quoted
125        let json = r#"{"note": "Hello, World"}"#;
126        let toon = serializer.serialize_json(json).unwrap();
127        assert!(toon.contains("\"Hello, World\""));
128    }
129
130    #[test]
131    fn test_roundtrip() {
132        let original = r#"{
133            "users": [
134                {"id": 1, "name": "Alice"},
135                {"id": 2, "name": "Bob"}
136            ],
137            "count": 2,
138            "active": true
139        }"#;
140
141        let toon = encode(original).unwrap();
142        let back_json = decode(&toon).unwrap();
143        let back: Value = serde_json::from_str(&back_json).unwrap();
144
145        assert_eq!(back.get("count").unwrap(), 2);
146        assert_eq!(back.get("active").unwrap(), true);
147    }
148
149    #[test]
150    fn test_jsonld_context() {
151        let json = r#"{
152            "@context": {
153                "foaf": "http://xmlns.com/foaf/0.1/"
154            },
155            "http://xmlns.com/foaf/0.1/name": "Alice"
156        }"#;
157
158        let toon = encode(json).unwrap();
159        assert!(toon.contains("foaf:name"));
160    }
161
162    #[test]
163    fn test_empty_array() {
164        let serializer = ToonSerializer::new();
165        let parser = ToonParser::new();
166
167        let json = r#"{"items": []}"#;
168        let toon = serializer.serialize_json(json).unwrap();
169        assert!(toon.contains("items[0]:"));
170
171        let back = parser.parse(&toon).unwrap();
172        assert!(back.get("items").unwrap().as_array().unwrap().is_empty());
173    }
174
175    #[test]
176    fn test_nested_objects() {
177        let serializer = ToonSerializer::new();
178
179        let json = r#"{
180            "person": {
181                "name": "Alice",
182                "address": {
183                    "city": "Seattle",
184                    "zip": "98101"
185                }
186            }
187        }"#;
188
189        let toon = serializer.serialize_json(json).unwrap();
190        assert!(toon.contains("person:"));
191        assert!(toon.contains("address:"));
192        assert!(toon.contains("city: Seattle"));
193    }
194
195    #[test]
196    fn test_missing_fields_in_tabular() {
197        // Disable partitioning to test union schema explicitly
198        let serializer = ToonSerializer::new().with_shape_partitioning(false);
199        let parser = ToonParser::new();
200
201        // Non-uniform array should still use tabular format with union of keys
202        let json = r#"{
203            "items": [
204                {"a": 1, "b": 2},
205                {"a": 3, "c": 4}
206            ]
207        }"#;
208
209        let toon = serializer.serialize_json(json).unwrap();
210        // Should use tabular format with union of all keys {a,b,c}
211        assert!(toon.contains("items[2]{a,b,c}:"));
212        // Missing fields should be null
213        assert!(toon.contains("1, 2, null"));
214        assert!(toon.contains("3, null, 4"));
215
216        // Verify roundtrip
217        let back = parser.parse(&toon).unwrap();
218        let items = back.get("items").unwrap().as_array().unwrap();
219        assert_eq!(items[0].get("a").unwrap(), 1);
220        assert_eq!(items[0].get("b").unwrap(), 2);
221        assert!(items[0].get("c").unwrap().is_null());
222        assert_eq!(items[1].get("a").unwrap(), 3);
223        assert!(items[1].get("b").unwrap().is_null());
224        assert_eq!(items[1].get("c").unwrap(), 4);
225    }
226
227    #[test]
228    fn test_special_characters_in_values() {
229        let serializer = ToonSerializer::new();
230        let parser = ToonParser::new();
231
232        let json = r#"{"message": "Hello: World", "note": "A|B"}"#;
233        let toon = serializer.serialize_json(json).unwrap();
234
235        // Values with : or | should be quoted
236        assert!(toon.contains("\"Hello: World\""));
237        assert!(toon.contains("\"A|B\""));
238
239        let back = parser.parse(&toon).unwrap();
240        assert_eq!(back.get("message").unwrap(), "Hello: World");
241        assert_eq!(back.get("note").unwrap(), "A|B");
242    }
243
244    #[test]
245    fn test_jsonld_id_and_type() {
246        let serializer = ToonSerializer::new();
247        let parser = ToonParser::new();
248
249        let json = r#"{
250            "@id": "http://example.org/person/1",
251            "@type": "Person",
252            "name": "Alice"
253        }"#;
254
255        let toon = serializer.serialize_json(json).unwrap();
256
257        assert!(toon.contains("@id:"));
258        assert!(toon.contains("@type: Person"));
259        assert!(toon.contains("name: Alice"));
260
261        // Verify roundtrip
262        let back = parser.parse(&toon).unwrap();
263        assert_eq!(back.get("@id").unwrap(), "http://example.org/person/1");
264        assert_eq!(back.get("@type").unwrap(), "Person");
265        assert_eq!(back.get("name").unwrap(), "Alice");
266    }
267
268    #[test]
269    fn test_jsonld_graph() {
270        let serializer = ToonSerializer::new();
271        let parser = ToonParser::new();
272
273        let json = r#"{
274            "@context": {
275                "foaf": "http://xmlns.com/foaf/0.1/"
276            },
277            "@graph": [
278                {"@id": "http://example.org/1", "@type": "Person", "foaf:name": "Alice"},
279                {"@id": "http://example.org/2", "@type": "Person", "foaf:name": "Bob"}
280            ]
281        }"#;
282
283        let toon = serializer.serialize_json(json).unwrap();
284
285        // @graph should use tabular format
286        assert!(toon.contains("@graph[2]"));
287        assert!(toon.contains("@id"));
288        assert!(toon.contains("@type"));
289
290        // Verify roundtrip
291        let back = parser.parse(&toon).unwrap();
292        let graph = back.get("@graph").unwrap().as_array().unwrap();
293        assert_eq!(graph.len(), 2);
294        assert_eq!(graph[0].get("@type").unwrap(), "Person");
295    }
296
297    #[test]
298    fn test_jsonld_type_array() {
299        let serializer = ToonSerializer::new();
300        let parser = ToonParser::new();
301
302        // @type can be an array of types
303        let json = r#"{
304            "@id": "http://example.org/1",
305            "@type": ["Person", "Agent"],
306            "name": "Alice"
307        }"#;
308
309        let toon = serializer.serialize_json(json).unwrap();
310        assert!(toon.contains("@type"));
311        assert!(toon.contains("Person"));
312        assert!(toon.contains("Agent"));
313
314        let back = parser.parse(&toon).unwrap();
315        let types = back.get("@type").unwrap().as_array().unwrap();
316        assert_eq!(types.len(), 2);
317    }
318
319    #[test]
320    fn test_jsonld_full_document() {
321        let json = r#"{
322            "@context": {
323                "foaf": "http://xmlns.com/foaf/0.1/",
324                "schema": "http://schema.org/"
325            },
326            "@id": "http://example.org/dataset",
327            "@type": "schema:Dataset",
328            "@graph": [
329                {
330                    "@id": "http://example.org/person/1",
331                    "@type": "foaf:Person",
332                    "foaf:name": "Alice",
333                    "foaf:age": 30
334                },
335                {
336                    "@id": "http://example.org/person/2",
337                    "@type": "foaf:Person",
338                    "foaf:name": "Bob",
339                    "foaf:age": 25
340                }
341            ]
342        }"#;
343
344        let toon = encode(json).unwrap();
345
346        // Context should come first
347        assert!(toon.starts_with("@context:"));
348        assert!(toon.contains("@id:"));
349        assert!(toon.contains("http://example.org/dataset"));
350        assert!(toon.contains("@type:"));
351        assert!(toon.contains("@graph[2]"));
352
353        // Roundtrip
354        let back_json = decode(&toon).unwrap();
355        let back: Value = serde_json::from_str(&back_json).unwrap();
356
357        assert_eq!(back.get("@id").unwrap(), "http://example.org/dataset");
358        let graph = back.get("@graph").unwrap().as_array().unwrap();
359        assert_eq!(graph.len(), 2);
360    }
361
362    #[test]
363    fn test_jsonld_value_with_language() {
364        let json = r#"{
365            "title": {"@value": "Bonjour", "@language": "fr"}
366        }"#;
367
368        let toon = encode(json).unwrap();
369
370        // Value nodes now use standard TOON object syntax
371        assert!(toon.contains("@value"));
372        assert!(toon.contains("Bonjour"));
373        assert!(toon.contains("@language"));
374        assert!(toon.contains("fr"));
375    }
376
377    #[test]
378    fn test_jsonld_value_with_type() {
379        let json = r#"{
380            "@context": {
381                "xsd": "http://www.w3.org/2001/XMLSchema#"
382            },
383            "date": {"@value": "2024-01-15", "@type": "http://www.w3.org/2001/XMLSchema#date"}
384        }"#;
385
386        let toon = encode(json).unwrap();
387
388        // Value nodes now use standard TOON object syntax
389        assert!(toon.contains("@value"));
390        assert!(toon.contains("2024-01-15"));
391        assert!(toon.contains("@type"));
392        assert!(toon.contains("xsd:date"));
393    }
394
395    #[test]
396    fn test_jsonld_list() {
397        let serializer = ToonSerializer::new();
398        let parser = ToonParser::new();
399
400        let json = r#"{
401            "sequence": {"@list": ["first", "second", "third"]}
402        }"#;
403
404        let toon = serializer.serialize_json(json).unwrap();
405
406        assert!(toon.contains("@list"));
407        assert!(toon.contains("first"));
408
409        let back = parser.parse(&toon).unwrap();
410        let seq = back.get("sequence").unwrap().as_object().unwrap();
411        let list = seq.get("@list").unwrap().as_array().unwrap();
412        assert_eq!(list.len(), 3);
413    }
414
415    #[test]
416    fn test_jsonld_set() {
417        let serializer = ToonSerializer::new();
418        let parser = ToonParser::new();
419
420        let json = r#"{
421            "tags": {"@set": ["rust", "wasm", "python"]}
422        }"#;
423
424        let toon = serializer.serialize_json(json).unwrap();
425
426        assert!(toon.contains("@set"));
427
428        let back = parser.parse(&toon).unwrap();
429        let tags = back.get("tags").unwrap().as_object().unwrap();
430        let set = tags.get("@set").unwrap().as_array().unwrap();
431        assert_eq!(set.len(), 3);
432    }
433
434    #[test]
435    fn test_jsonld_reverse() {
436        let serializer = ToonSerializer::new();
437        let parser = ToonParser::new();
438
439        let json = r#"{
440            "@id": "http://example.org/alice",
441            "@reverse": {
442                "foaf:knows": {"@id": "http://example.org/bob"}
443            }
444        }"#;
445
446        let toon = serializer.serialize_json(json).unwrap();
447
448        assert!(toon.contains("@reverse"));
449        assert!(toon.contains("foaf:knows"));
450
451        let back = parser.parse(&toon).unwrap();
452        assert!(back.get("@reverse").is_some());
453    }
454
455    #[test]
456    fn test_jsonld_base_and_vocab() {
457        let serializer = ToonSerializer::new();
458
459        let json = r#"{
460            "@context": {
461                "@base": "http://example.org/",
462                "@vocab": "http://schema.org/"
463            },
464            "@id": "person/1",
465            "name": "Alice"
466        }"#;
467
468        let toon = serializer.serialize_json(json).unwrap();
469
470        assert!(toon.contains("@base"));
471        assert!(toon.contains("@vocab"));
472        assert!(toon.contains("http://example.org/"));
473        assert!(toon.contains("http://schema.org/"));
474    }
475
476    // JSON-LD 1.1 keyword tests
477
478    #[test]
479    fn test_jsonld_version() {
480        let serializer = ToonSerializer::new();
481        let parser = ToonParser::new();
482
483        let json = r#"{
484            "@context": {
485                "@version": 1.1,
486                "name": "http://schema.org/name"
487            },
488            "name": "Alice"
489        }"#;
490
491        let toon = serializer.serialize_json(json).unwrap();
492
493        assert!(toon.contains("@version"));
494        assert!(toon.contains("1.1"));
495
496        let back = parser.parse(&toon).unwrap();
497        let ctx = back.get("@context").unwrap().as_object().unwrap();
498        assert!(ctx.get("@version").is_some());
499    }
500
501    #[test]
502    fn test_jsonld_direction() {
503        let serializer = ToonSerializer::new();
504        let parser = ToonParser::new();
505
506        let json = r#"{
507            "@context": {
508                "@direction": "rtl"
509            },
510            "text": "Hello"
511        }"#;
512
513        let toon = serializer.serialize_json(json).unwrap();
514
515        assert!(toon.contains("@direction"));
516        assert!(toon.contains("rtl"));
517
518        let back = parser.parse(&toon).unwrap();
519        let ctx = back.get("@context").unwrap().as_object().unwrap();
520        assert_eq!(ctx.get("@direction").unwrap(), "rtl");
521    }
522
523    #[test]
524    fn test_jsonld_value_with_direction() {
525        let json = r#"{
526            "title": {"@value": "مرحبا", "@language": "ar", "@direction": "rtl"}
527        }"#;
528
529        let toon = encode(json).unwrap();
530
531        // Value nodes now use standard TOON object syntax
532        assert!(toon.contains("@value"));
533        assert!(toon.contains("مرحبا"));
534        assert!(toon.contains("@language"));
535        assert!(toon.contains("ar"));
536        assert!(toon.contains("@direction"));
537        assert!(toon.contains("rtl"));
538    }
539
540    #[test]
541    fn test_jsonld_container() {
542        let serializer = ToonSerializer::new();
543        let parser = ToonParser::new();
544
545        let json = r#"{
546            "@context": {
547                "tags": {
548                    "@id": "http://example.org/tags",
549                    "@container": "@set"
550                }
551            },
552            "tags": ["a", "b", "c"]
553        }"#;
554
555        let toon = serializer.serialize_json(json).unwrap();
556
557        assert!(toon.contains("@container"));
558
559        let back = parser.parse(&toon).unwrap();
560        assert!(back.get("@context").is_some());
561    }
562
563    #[test]
564    fn test_jsonld_container_array() {
565        let serializer = ToonSerializer::new();
566        let parser = ToonParser::new();
567
568        let json = r#"{
569            "@container": ["@index", "@set"]
570        }"#;
571
572        let toon = serializer.serialize_json(json).unwrap();
573
574        assert!(toon.contains("@container"));
575
576        let back = parser.parse(&toon).unwrap();
577        let container = back.get("@container").unwrap().as_array().unwrap();
578        assert_eq!(container.len(), 2);
579    }
580
581    #[test]
582    fn test_jsonld_index() {
583        let serializer = ToonSerializer::new();
584        let parser = ToonParser::new();
585
586        let json = r#"{
587            "@id": "http://example.org/1",
588            "@index": "chapter1",
589            "title": "Introduction"
590        }"#;
591
592        let toon = serializer.serialize_json(json).unwrap();
593
594        assert!(toon.contains("@index"));
595        assert!(toon.contains("chapter1"));
596
597        let back = parser.parse(&toon).unwrap();
598        assert_eq!(back.get("@index").unwrap(), "chapter1");
599    }
600
601    #[test]
602    fn test_jsonld_included() {
603        let serializer = ToonSerializer::new();
604        let parser = ToonParser::new();
605
606        let json = r#"{
607            "@id": "http://example.org/main",
608            "name": "Main Resource",
609            "@included": [
610                {"@id": "http://example.org/ref1", "name": "Reference 1"},
611                {"@id": "http://example.org/ref2", "name": "Reference 2"}
612            ]
613        }"#;
614
615        let toon = serializer.serialize_json(json).unwrap();
616
617        assert!(toon.contains("@included"));
618        assert!(toon.contains("Reference 1"));
619        assert!(toon.contains("Reference 2"));
620
621        let back = parser.parse(&toon).unwrap();
622        let included = back.get("@included").unwrap().as_array().unwrap();
623        assert_eq!(included.len(), 2);
624    }
625
626    #[test]
627    fn test_jsonld_nest() {
628        let serializer = ToonSerializer::new();
629        let parser = ToonParser::new();
630
631        let json = r#"{
632            "@id": "http://example.org/1",
633            "@nest": {
634                "nested_prop": "nested_value"
635            }
636        }"#;
637
638        let toon = serializer.serialize_json(json).unwrap();
639
640        assert!(toon.contains("@nest"));
641        assert!(toon.contains("nested_prop"));
642        assert!(toon.contains("nested_value"));
643
644        let back = parser.parse(&toon).unwrap();
645        let nest = back.get("@nest").unwrap().as_object().unwrap();
646        assert_eq!(nest.get("nested_prop").unwrap(), "nested_value");
647    }
648
649    #[test]
650    fn test_jsonld_prefix() {
651        let serializer = ToonSerializer::new();
652        let parser = ToonParser::new();
653
654        let json = r#"{
655            "@context": {
656                "ex": {
657                    "@id": "http://example.org/",
658                    "@prefix": true
659                }
660            },
661            "ex:name": "Test"
662        }"#;
663
664        let toon = serializer.serialize_json(json).unwrap();
665
666        assert!(toon.contains("@prefix"));
667        assert!(toon.contains("true"));
668
669        let back = parser.parse(&toon).unwrap();
670        assert!(back.get("@context").is_some());
671    }
672
673    #[test]
674    fn test_jsonld_propagate() {
675        let serializer = ToonSerializer::new();
676        let parser = ToonParser::new();
677
678        let json = r#"{
679            "@context": {
680                "@propagate": false,
681                "name": "http://schema.org/name"
682            },
683            "name": "Alice"
684        }"#;
685
686        let toon = serializer.serialize_json(json).unwrap();
687
688        assert!(toon.contains("@propagate"));
689        assert!(toon.contains("false"));
690
691        let back = parser.parse(&toon).unwrap();
692        let ctx = back.get("@context").unwrap().as_object().unwrap();
693        assert_eq!(ctx.get("@propagate").unwrap(), false);
694    }
695
696    #[test]
697    fn test_jsonld_protected() {
698        let serializer = ToonSerializer::new();
699        let parser = ToonParser::new();
700
701        let json = r#"{
702            "@context": {
703                "name": {
704                    "@id": "http://schema.org/name",
705                    "@protected": true
706                }
707            },
708            "name": "Alice"
709        }"#;
710
711        let toon = serializer.serialize_json(json).unwrap();
712
713        assert!(toon.contains("@protected"));
714        assert!(toon.contains("true"));
715
716        let _back = parser.parse(&toon).unwrap();
717    }
718
719    #[test]
720    fn test_jsonld_import() {
721        let serializer = ToonSerializer::new();
722        let parser = ToonParser::new();
723
724        let json = r#"{
725            "@context": {
726                "@import": "http://example.org/context.jsonld"
727            },
728            "name": "Alice"
729        }"#;
730
731        let toon = serializer.serialize_json(json).unwrap();
732
733        assert!(toon.contains("@import"));
734        assert!(toon.contains("http://example.org/context.jsonld"));
735
736        let back = parser.parse(&toon).unwrap();
737        let ctx = back.get("@context").unwrap().as_object().unwrap();
738        assert!(ctx.get("@import").is_some());
739    }
740
741    #[test]
742    fn test_jsonld_none() {
743        let serializer = ToonSerializer::new();
744        let parser = ToonParser::new();
745
746        let json = r#"{
747            "@none": {"name": "Default item"}
748        }"#;
749
750        let toon = serializer.serialize_json(json).unwrap();
751
752        assert!(toon.contains("@none"));
753
754        let back = parser.parse(&toon).unwrap();
755        assert!(back.get("@none").is_some());
756    }
757
758    #[test]
759    fn test_jsonld_json_literal() {
760        let serializer = ToonSerializer::new();
761        let parser = ToonParser::new();
762
763        let json = r#"{
764            "data": {
765                "@value": {"nested": "object", "count": 42},
766                "@type": "@json"
767            }
768        }"#;
769
770        let toon = serializer.serialize_json(json).unwrap();
771
772        assert!(toon.contains("data"));
773
774        let back = parser.parse(&toon).unwrap();
775        assert!(back.get("data").is_some());
776    }
777
778    #[test]
779    fn test_jsonld_context_with_term_definitions() {
780        let json = r#"{
781            "@context": {
782                "@version": 1.1,
783                "name": {
784                    "@id": "http://schema.org/name",
785                    "@container": "@set",
786                    "@protected": true
787                },
788                "knows": {
789                    "@id": "http://xmlns.com/foaf/0.1/knows",
790                    "@type": "@id"
791                }
792            },
793            "name": ["Alice", "Alicia"],
794            "knows": "http://example.org/bob"
795        }"#;
796
797        let toon = encode(json).unwrap();
798
799        assert!(toon.contains("@context"));
800        assert!(toon.contains("@version"));
801        assert!(toon.contains("name"));
802        assert!(toon.contains("knows"));
803
804        let back_json = decode(&toon).unwrap();
805        let back: Value = serde_json::from_str(&back_json).unwrap();
806        assert!(back.get("@context").is_some());
807    }
808
809    #[test]
810    fn test_jsonld_full_1_1_document() {
811        let json = r#"{
812            "@context": {
813                "@version": 1.1,
814                "@base": "http://example.org/",
815                "@vocab": "http://schema.org/",
816                "foaf": "http://xmlns.com/foaf/0.1/",
817                "@direction": "ltr",
818                "name": {
819                    "@id": "foaf:name",
820                    "@protected": true
821                }
822            },
823            "@id": "person/1",
824            "@type": "Person",
825            "@index": "main",
826            "name": "Alice",
827            "@included": [
828                {"@id": "person/2", "name": "Bob"}
829            ]
830        }"#;
831
832        let toon = encode(json).unwrap();
833
834        assert!(toon.contains("@context"));
835        assert!(toon.contains("@version"));
836        assert!(toon.contains("@base"));
837        assert!(toon.contains("@vocab"));
838        assert!(toon.contains("@direction"));
839        assert!(toon.contains("@id"));
840        assert!(toon.contains("@type"));
841        assert!(toon.contains("@index"));
842        assert!(toon.contains("@included"));
843        assert!(toon.contains("Alice"));
844        assert!(toon.contains("Bob"));
845
846        let back_json = decode(&toon).unwrap();
847        let back: Value = serde_json::from_str(&back_json).unwrap();
848        assert!(back.get("@context").is_some());
849        assert!(back.get("@included").is_some());
850    }
851}