The text to deserialize
The deserialized Ion Text value
Test struct deserialization
import mir.ion.value; static struct Book { string title; bool wouldRecommend; string description; uint numberOfNovellas; double price; float weight; string[] tags; } static immutable textData = ` { "title": "A Hero of Our Time", "wouldRecommend": true, "description": "", "numberOfNovellas": 5, "price": 7.99, "weight": 6.88, "tags": ["russian", "novel", "19th century"] }`; Book book = deserializeText!Book(textData); assert(book.description.length == 0); assert(book.numberOfNovellas == 5); assert(book.price == 7.99); assert(book.tags.length == 3); assert(book.tags[0] == "russian"); assert(book.tags[1] == "novel"); assert(book.tags[2] == "19th century"); assert(book.title == "A Hero of Our Time"); assert(book.weight == 6.88f); assert(book.wouldRecommend);
Test @nogc struct deserialization
import mir.ion.value; import mir.bignum.decimal; import mir.small_string; import mir.small_array; import mir.conv : to; static struct Book { SmallString!64 title; bool wouldRecommend; SmallString!64 description; uint numberOfNovellas; Decimal!1 price; double weight; SmallArray!(SmallString!(16), 10) tags; } static immutable textData = ` { "title": "A Hero of Our Time", "wouldRecommend": true, "description": "", "numberOfNovellas": 5, "price": 7.99, "weight": 6.88, "tags": ["russian", "novel", "19th century"] }`; Book book = deserializeText!Book(textData); assert(book.description.length == 0); assert(book.numberOfNovellas == 5); assert(book.price.to!double == 7.99); assert(book.tags.length == 3); assert(book.tags[0] == "russian"); assert(book.tags[1] == "novel"); assert(book.tags[2] == "19th century"); assert(book.title == "A Hero of Our Time"); assert(book.weight == 6.88f); assert(book.wouldRecommend);
Test that strings are being de-serialized properly
import mir.test: should; import mir.ion.stream; import mir.ion.conv : text2ion; import mir.ser.text; void test(const(char)[] ionData, const(char)[] expected) { const(char)[] output = ionData.text2ion.IonValueStream.serializeText; output.should == expected; } test(`"hello"`, `"hello"`); test(`"hello\x20world"`, `"hello world"`); test(`"hello\u2248world"`, `"hello≈world"`); test(`"hello\U0001F44Dworld"`, `"hello👍world"`);
Test that timestamps are de-serialized properly
1 import mir.test; 2 import mir.ion.stream; 3 import mir.ion.conv : text2ion; 4 import mir.ion.value : IonTimestamp; 5 import std.datetime.date : TimeOfDay; 6 import mir.timestamp : Timestamp; 7 void test(const(char)[] ionData, Timestamp expected) 8 { 9 foreach(symbolTable, scope ionValue; ionData.text2ion.IonValueStream) { 10 Timestamp t = ionValue.get!(IonTimestamp).get; 11 t.should == expected; 12 } 13 } 14 15 void testFail(const(char)[] ionData, Timestamp expected) 16 { 17 foreach(symbolTable, scope ionValue; ionData.text2ion.IonValueStream) { 18 Timestamp t = ionValue.get!(IonTimestamp).get; 19 assert(expected != t); 20 } 21 } 22 23 test("2001-01T", Timestamp(2001, 1)); 24 test("2001-01-02", Timestamp(2001, 1, 2)); 25 test("2001-01-02T", Timestamp(2001, 1, 2)); 26 test("2001-01-02T03:04", Timestamp(2001, 1, 2, 3, 4)); 27 test("2001-01-02T03:04Z", Timestamp(2001, 1, 2, 3, 4).withOffset(0)); 28 test("2001-01-02T03:04+00:00", Timestamp(2001, 1, 2, 3, 4).withOffset(0)); 29 test("2001-01-02T03:05+00:01", Timestamp(2001, 1, 2, 3, 4).withOffset(1)); 30 test("2001-01-02T05:05+02:01", Timestamp(2001, 1, 2, 3, 4).withOffset(2*60+1)); 31 test("2001-01-02T03:04:05", Timestamp(2001, 1, 2, 3, 4, 5)); 32 test("2001-01-02T03:04:05Z", Timestamp(2001, 1, 2, 3, 4, 5).withOffset(0)); 33 test("2001-01-02T03:04:05+00:00", Timestamp(2001, 1, 2, 3, 4, 5).withOffset(0)); 34 test("2001-01-02T03:05:05+00:01", Timestamp(2001, 1, 2, 3, 4, 5).withOffset(1)); 35 test("2001-01-02T05:05:05+02:01", Timestamp(2001, 1, 2, 3, 4, 5).withOffset(2*60+1)); 36 test("2001-01-02T03:04:05.666", Timestamp(2001, 1, 2, 3, 4, 5, -3, 666)); 37 test("2001-01-02T03:04:05.666Z", Timestamp(2001, 1, 2, 3, 4, 5, -3, 666).withOffset(0)); 38 test("2001-01-02T03:04:05.666666Z", Timestamp(2001, 1, 2, 3, 4, 5, -6, 666_666).withOffset(0)); 39 test("2001-01-02T03:54:05.666+00:50", Timestamp(2001, 1, 2, 3, 4, 5, -3, 666).withOffset(50)); 40 test("2001-01-02T03:54:05.666666+00:50", Timestamp(2001, 1, 2, 3, 4, 5, -6, 666_666).withOffset(50)); 41 42 // Time of day tests 43 test("03:04", Timestamp(0, 0, 0, 3, 4)); 44 test("03:04Z", Timestamp(0, 0, 0, 3, 4).withOffset(0)); 45 test("03:04+00:00", Timestamp(0, 0, 0, 3, 4).withOffset(0)); 46 test("03:05+00:01", Timestamp(0, 0, 0, 3, 4).withOffset(1)); 47 test("05:05+02:01", Timestamp(0, 0, 0, 3, 4).withOffset(2*60+1)); 48 test("03:04:05", Timestamp(0, 0, 0, 3, 4, 5)); 49 test("03:04:05Z", Timestamp(0, 0, 0, 3, 4, 5).withOffset(0)); 50 test("03:04:05+00:00", Timestamp(0, 0, 0, 3, 4, 5).withOffset(0)); 51 test("03:05:05+00:01", Timestamp(0, 0, 0, 3, 4, 5).withOffset(1)); 52 test("05:05:05+02:01", Timestamp(0, 0, 0, 3, 4, 5).withOffset(2*60+1)); 53 test("03:04:05.666", Timestamp(0, 0, 0, 3, 4, 5, -3, 666)); 54 test("03:04:05.666Z", Timestamp(0, 0, 0, 3, 4, 5, -3, 666).withOffset(0)); 55 test("03:04:05.666666Z", Timestamp(0, 0, 0, 3, 4, 5, -6, 666_666).withOffset(0)); 56 test("03:54:05.666+00:50", Timestamp(0, 0, 0, 3, 4, 5, -3, 666).withOffset(50)); 57 test("03:54:05.666666+00:50", Timestamp(0, 0, 0, 3, 4, 5, -6, 666_666).withOffset(50)); 58 59 // Mir doesn't like 03:04 only (as technically it's less precise then TimeOfDay)... ugh 60 test("03:04:05", Timestamp(TimeOfDay(3, 4, 5))); 61 test("03:04:05Z", Timestamp(TimeOfDay(3, 4, 5)).withOffset(0)); 62 test("03:04:05+00:00", Timestamp(TimeOfDay(3, 4, 5)).withOffset(0)); 63 test("03:05:05+00:01", Timestamp(TimeOfDay(3, 4, 5)).withOffset(1)); 64 test("05:05:05+02:01", Timestamp(TimeOfDay(3, 4, 5)).withOffset(2*60+1)); 65 66 testFail("2001-01-02T03:04+00:50", Timestamp(2001, 1, 2, 3, 4)); 67 testFail("2001-01-02T03:04:05+00:50", Timestamp(2001, 1, 2, 3, 4, 5)); 68 testFail("2001-01-02T03:04:05.666Z", Timestamp(2001, 1, 2, 3, 4, 5).withOffset(0)); 69 testFail("2001-01-02T03:54:05.666+00:50", Timestamp(2001, 1, 2, 3, 4, 5)); 70 71 // Fake timestamps for Duration encoding 72 import core.time : weeks, days, hours, minutes, seconds, hnsecs; 73 test("0005-02-88T07:40:04.9876543", Timestamp(5.weeks + 2.days + 7.hours + 40.minutes + 4.seconds + 9876543.hnsecs)); 74 test("0005-02-99T07:40:04.9876543", Timestamp(-5.weeks - 2.days - 7.hours - 40.minutes - 4.seconds - 9876543.hnsecs));
Test that binary literals are de-serialized properly.
import mir.ion.value : IonUInt; import mir.ion.stream; import mir.ion.conv : text2ion; void test(const(char)[] ionData, uint val) { foreach(symbolTable, scope ionValue; ionData.text2ion.IonValueStream) { auto v = ionValue.get!(IonUInt); assert(v.get!uint == val); } } test("0b00001", 0b1); test("0b10101", 0b10101); test("0b11111", 0b11111); test("0b111111111111111111111", 0b1111_1111_1111_1111_1111_1); test("0b1_1111_1111_1111_1111_1111", 0b1_1111_1111_1111_1111_1111);
Test that signed / unsigned integers are de-serialized properly.
import mir.ion.value : IonUInt, IonNInt; import mir.ion.stream; import mir.ion.conv : text2ion; void test(const(char)[] ionData, ulong val) { foreach(symbolTable, scope ionValue; ionData.text2ion.IonValueStream) { auto v = ionValue.get!(IonUInt); assert(v.get!ulong == val); } } void testNeg(const(char)[] ionData, ulong val) { foreach(symbolTable, scope ionValue; ionData.text2ion.IonValueStream) { auto v = ionValue.get!(IonNInt); assert(v.get!long == -val); } } test("0xabc_def", 0xabc_def); test("0xabcdef", 0xabcdef); test("0xDEADBEEF", 0xDEADBEEF); test("0xDEADBEEF", 0xDEAD_BEEF); test("0xDEAD_BEEF", 0xDEAD_BEEF); test("0xDEAD_BEEF", 0xDEADBEEF); test("0x0123456789", 0x0123456789); test("0x0123456789abcdef", 0x0123456789abcdef); test("0x0123_4567_89ab_cdef", 0x0123_4567_89ab_cdef); testNeg("-0xabc_def", 0xabc_def); testNeg("-0xabc_def", 0xabc_def); testNeg("-0xabcdef", 0xabcdef); testNeg("-0xDEADBEEF", 0xDEADBEEF); testNeg("-0xDEADBEEF", 0xDEAD_BEEF); testNeg("-0xDEAD_BEEF", 0xDEAD_BEEF); testNeg("-0xDEAD_BEEF", 0xDEADBEEF); testNeg("-0x0123456789", 0x0123456789); testNeg("-0x0123456789abcdef", 0x0123456789abcdef); testNeg("-0x0123_4567_89ab_cdef", 0x0123_4567_89ab_cdef);
Test that infinity & negative infinity are deserialized properly.
import mir.test: should; import mir.ion.value : IonFloat; import mir.ion.conv : text2ion; import mir.ion.stream; void test(const(char)[] ionData, float expected) { foreach(symbolTable, scope ionValue; ionData.text2ion.IonValueStream) { auto v = ionValue.get!(IonFloat); v.get!float.should == expected; } } test("-inf", -float.infinity); test("+inf", float.infinity);
Test that NaN is deserialized properly.
import mir.ion.value; import mir.ion.conv : text2ion; import mir.ion.stream; alias isNaN = x => x != x; void test(const(char)[] ionData) { foreach(symbolTable, scope ionValue; ionData.text2ion.IonValueStream) { auto v = ionValue.get!(IonFloat); assert(isNaN(v.get!float)); } } test("nan");
Test that signed / unsigned integers and decimals and floats are all deserialized properly.
import mir.test: should; import mir.ion.value; import mir.ion.stream; import mir.ion.conv : text2ion; void test_uint(const(char)[] ionData, ulong expected) { foreach(symbolTable, scope ionValue; ionData.text2ion.IonValueStream) { auto v = ionValue.get!(IonUInt); v.get!ulong.should == expected; } } void test_nint(const(char)[] ionData, long expected) { foreach(symbolTable, scope ionValue; ionData.text2ion.IonValueStream) { auto v = ionValue.get!(IonNInt); v.get!long.should == expected; } } void test_dec(const(char)[] ionData, double expected) { foreach(symbolTable, scope ionValue; ionData.text2ion.IonValueStream) { auto v = ionValue.get!(IonDecimal); v.get!double.should == expected; } } void test_float(const(char)[] ionData, float expected) { foreach(symbolTable, scope ionValue; ionData.text2ion.IonValueStream) { auto v = ionValue.get!(IonFloat); v.get!float.should == expected; } } test_uint("123", 123); test_nint("-123", -123); test_dec("123.123123", 123.123123); test_dec("123.123123", 123.123123); test_dec("123.123123d0", 123.123123); test_dec("123.123123d0", 123.123123); test_dec("-123.123123", -123.123123); test_dec("-123.123123d0", -123.123123); test_dec("18446744073709551615.", 1844_6744_0737_0955_1615.0); test_dec("-18446744073709551615.", -1844_6744_0737_0955_1615.0); test_dec("18446744073709551616.", 1844_6744_0737_0955_1616.0); test_dec("-18446744073709551616.", -1844_6744_0737_0955_1616.0); test_float("123.456789e-6", 123.456789e-6); test_float("-123.456789e-6", -123.456789e-6);
Test that quoted / unquoted symbols are deserialized properly.
import mir.ion.value; import mir.ion.conv : text2ion; import mir.ion.stream; void test(const(char)[] ionData, string symbol) { foreach (symbolTable, val; ionData.text2ion.IonValueStream) { auto sym = val.get!(IonSymbolID).get; assert(symbol == symbolTable[sym]); } } test("$0", "$0"); test("$ion", "$ion"); test("$ion_1_0", "$ion_1_0"); test("name", "name"); test("version", "version"); test("imports", "imports"); test("symbols", "symbols"); test("max_id", "max_id"); test("$ion_shared_symbol_table", "$ion_shared_symbol_table"); test("hello", "hello"); test("world", "world"); test("'foobaz'", "foobaz"); test("'👍'", "👍"); test("' '", " "); test("'\\U0001F44D'", "👍"); test("'\\u2248'", "\u2248"); test("'true'", "true"); test("'false'", "false"); test("'nan'", "nan"); test("'null'", "null");
Test that all variations of the "null" value are deserialized properly.
import mir.ion.value; import mir.ion.stream; import mir.ion.conv : text2ion; void test(const(char)[] ionData, IonTypeCode nullType) { foreach(symbolTable, scope ionValue; ionData.text2ion.IonValueStream) { auto v = ionValue.get!(IonNull); assert(v.code == nullType); } } test("null", IonTypeCode.null_); test("null.bool", IonTypeCode.bool_); test("null.int", IonTypeCode.uInt); test("null.float", IonTypeCode.float_); test("null.decimal", IonTypeCode.decimal); test("null.timestamp", IonTypeCode.timestamp); test("null.symbol", IonTypeCode.symbol); test("null.string", IonTypeCode.string); test("null.blob", IonTypeCode.blob); test("null.clob", IonTypeCode.clob); test("null.list", IonTypeCode.list); test("null.struct", IonTypeCode.struct_); test("null.sexp", IonTypeCode.sexp);
Test that blobs are getting de-serialized correctly.
import mir.ion.value; import mir.ion.stream; import mir.ion.conv : text2ion; import mir.lob; void test(const(char)[] ionData, ubyte[] blobData) { foreach(symbolTable, scope ionValue; ionData.text2ion.IonValueStream) { auto v = ionValue.get!(Blob); assert(v.data == blobData); } } test("{{ SGVsbG8sIHdvcmxkIQ== }}", cast(ubyte[])"Hello, world!"); test("{{ R29vZCBhZnRlcm5vb24hIPCfkY0= }}", cast(ubyte[])"Good afternoon! 👍");
Test that long/short clobs are getting de-serialized correctly.
import mir.ion.value; import mir.ion.stream; import mir.ion.conv : text2ion; import mir.lob; void test(const(char)[] ionData, const(char)[] blobData) { foreach(symbolTable, scope ionValue; ionData.text2ion.IonValueStream) { auto v = ionValue.get!(Clob); assert(v.data == blobData); } } test(`{{ "This is a short clob." }}`, "This is a short clob."); test(` {{ '''This is a long clob,''' ''' which spans over multiple lines,''' ''' and can have a theoretically infinite length.''' }}`, "This is a long clob, which spans over multiple lines, and can have a theoretically infinite length."); test(`{{ '''Long clobs can also have their data contained in one value, but spread out across multiple lines.''' }}`, "Long clobs can also have their data contained in one value,\n but spread out across multiple lines."); test(`{{ '''Or, you can have multiple values on the same line,''' ''' like this!'''}}`, "Or, you can have multiple values on the same line, like this!");
Test that structs are getting de-serialized properly
import mir.test: should; import mir.ion.stream; import mir.ion.conv : text2ion; import mir.ser.text; void test(const(char)[] ionData, const(char)[] expected) { auto v = ionData.text2ion.IonValueStream.serializeText; v.should == expected; } test(`1`, `1`); test(`test::1`, `test::1`); test(`{"test":"world", test: false, 'test': usd::123.456, '''test''': "asdf"}`, `{test:"world",test:false,test:usd::123.456,test:"asdf"}`); test(`{'''foo''' '''bar''': "foobar"}`, `{foobar:"foobar"}`); test(`{a: 1, b: 2}`, `{a:1,b:2}`); test(`{}`, `{}`);
Test that sexps are getting de-serialized properly.
import mir.test: should; import mir.ion.stream; import mir.ion.conv : text2ion; import mir.ser.text; void test(const(char)[] ionData, const(char)[] expected) { auto v = ionData.text2ion.IonValueStream.serializeText; v.should == expected; } test(`(this is a sexp list)`, "(this is a sexp list)"); test(`('+' '++' '+-+' '-++' '-' '--' '---' -3 - 3 '--' 3 '--'3 )`, "('+' '++' '+-+' '-++' '-' '--' '---' -3 '-' 3 '--' 3 '--' 3)"); test(`(a_plus_plus_plus_operator::+++ a_3::3)`, `(a_plus_plus_plus_operator::'+++' a_3::3)`); test(`(& (% -[42, 3]+(2)-))`, `('&' ('%' '-' [42,3] '+' (2) '-'))`);
Test that arrays are getting de-serialized properly.
import mir.test: should; import mir.ion.stream; import mir.ion.conv : text2ion; import mir.ser.text; void test(const(char)[] ionData, const(char)[] expected) { auto v = ionData.text2ion.IonValueStream.serializeText; v.should == expected; } test(`[hello, world]`, `[hello,world]`); test(`[this::is::an::annotated::symbol, this::is::annotated::123.456]`, `[this::is::an::annotated::symbol,this::is::annotated::123.456]`); test(`[date::of::birth::0001-01-01T00:00:00.0+00:00, date::of::birth::1970-01-01T]`, `[date::of::birth::0001-01-01T00:00:00.0Z,date::of::birth::1970-01-01]`); test(`['hello', "hello", '''hello''', '''hello ''''''world''']`, `[hello,"hello","hello","hello world"]`); test(`[0x123_456, 0xF00D_BAD]`, `[1193046,251714477]`);
Test that annotations work with symbols
import mir.test: should; import mir.ion.stream; import mir.ser.text; import mir.ion.conv : text2ion; void test(const(char)[] ionData, const(char)[] expected) { auto v = ionData.text2ion.IonValueStream.serializeText; v.should == expected; } test(`'test'::'hello'::'world'`, "test::hello::world"); test(`foo::bar`, "foo::bar"); test(`foo::'bar'`, "foo::bar"); test(`'foo'::bar`, "foo::bar"); test(`'foo bar'::cash`, "'foo bar'::cash"); test(`'foo\U0001F44D'::'baz\U0001F44D'`, "'foo\U0001F44D'::'baz\U0001F44D'"); test(`'\u2248'::'\u2248'`, "'\u2248'::'\u2248'"); test(`'\u2248'::foo`, "'\u2248'::foo");
Test that annotations work with floats
import mir.test: should; import mir.ion.stream; import mir.ion.conv : text2ion; import mir.ser.text; void test(const(char)[] ionData, const(char)[] expected) { auto v = ionData.text2ion.IonValueStream.serializeText; v.should == expected; } test(`usd::10.50e0`, "usd::10.5"); test(`'Value is good \U0001F44D'::12.34e0`, "'Value is good \U0001F44D'::12.34"); test(`'null'::150.00e0`, "'null'::150.0");
Test that annotations work with decimals
import mir.test: should; import mir.ion.stream; import mir.ion.conv : text2ion; import mir.ser.text; void test(const(char)[] ionData, const(char)[] expected) { auto v = ionData.text2ion.IonValueStream.serializeText; v.should == expected; } test(`Types::Speed::MetersPerSecondSquared::9.81`, "Types::Speed::MetersPerSecondSquared::9.81"); test(`Rate::USD::GBP::12.345`, "Rate::USD::GBP::12.345"); test(`usd::10.50d0`, "usd::10.50"); test(`'Value is good \U0001F44D'::12.34d0`, "'Value is good \U0001F44D'::12.34"); test(`'null'::150.00d0`, "'null'::150.00"); test(`'Cool'::27182818284590450000000000d-25`, "Cool::2.7182818284590450000000000"); test(`mass::2.718281828459045d0`, "mass::2.718281828459045"); test(`weight::0.000000027182818284590450000000000d+8`, "weight::2.7182818284590450000000000"); test(`coeff::-0.000000027182818284590450000000000d+8`, "coeff::-2.7182818284590450000000000");
Test that annotations work with strings
import mir.test: should; import mir.ion.stream; import mir.ion.conv : text2ion; import mir.ser.text; void test(const(char)[] ionData, const(char)[] expected) { auto v = ionData.text2ion.IonValueStream.serializeText; v.should == expected; } test(`Password::"Super Secure Password"`, `Password::"Super Secure Password"`); test(`Magic::String::"Hello, world!"`, `Magic::String::"Hello, world!"`); test(`SSH::PublicKey::'''ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDNrMk7QmmmNIusf10CwHQHs6Z9HJIiuknwoqtQLzEPxdMnNHKJexNnfF5QQ2v84BBhVjxvPgSqhdcVMEFy8PrGu44MqhK/cV6BGx430v2FnArWDO+9LUSd+3iwMJVZUQgZGtjSLAkZO+NOSPWZ+W0SODGgUfbNVu35GjVoA2+e1lOINUe22oZPnaD+gpJGUOx7j5JqpCblBZntvZyOjTPl3pc52rIGfxi1TYJnDXjqX76OinZceBzp5Oh0oUTrPbu55ig+b8bd4HtzLWxcqXBCnsw0OAKsAiXfLlBcrgZUsoAP9unrcqsqoJ2qEEumdsPqcpJakpO7/n0lMP6lRdSZ'''`, `SSH::PublicKey::"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDNrMk7QmmmNIusf10CwHQHs6Z9HJIiuknwoqtQLzEPxdMnNHKJexNnfF5QQ2v84BBhVjxvPgSqhdcVMEFy8PrGu44MqhK/cV6BGx430v2FnArWDO+9LUSd+3iwMJVZUQgZGtjSLAkZO+NOSPWZ+W0SODGgUfbNVu35GjVoA2+e1lOINUe22oZPnaD+gpJGUOx7j5JqpCblBZntvZyOjTPl3pc52rIGfxi1TYJnDXjqX76OinZceBzp5Oh0oUTrPbu55ig+b8bd4HtzLWxcqXBCnsw0OAKsAiXfLlBcrgZUsoAP9unrcqsqoJ2qEEumdsPqcpJakpO7/n0lMP6lRdSZ"`);
Deserialize an Ion Text value to a D value.