├── .gitignore ├── .travis.yml ├── LICENSE.md ├── README.md ├── bson.hxml ├── haxelib.json ├── src └── bson │ ├── Bson.hx │ ├── BsonDecoder.hx │ ├── BsonDocument.hx │ ├── BsonEncoder.hx │ ├── BsonType.hx │ └── ObjectId.hx ├── submit.sh ├── tests.hxml └── tests ├── Base.hx ├── RunTests.hx ├── TestComplex.hx ├── TestDecoder.hx └── TestEncoder.hx /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | ref 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: trusty 3 | 4 | language: haxe 5 | 6 | os: 7 | - linux 8 | - osx 9 | 10 | haxe: 11 | - "3.2.1" 12 | - development 13 | 14 | matrix: 15 | allow_failures: 16 | - haxe: "3.2.1" 17 | 18 | install: 19 | - haxelib install travix 20 | - haxelib run travix install 21 | 22 | script: 23 | - haxelib run travix interp 24 | - haxelib run travix neko 25 | - haxelib run travix python 26 | - haxelib run travix node 27 | - haxelib run travix flash 28 | - haxelib run travix java 29 | - haxelib run travix cpp 30 | - haxelib run travix cs 31 | - haxelib run travix php -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Kevin Leung 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BSON [![Build Status](https://travis-ci.org/kevinresol/bson.svg?branch=master)](https://travis-ci.org/kevinresol/bson) 2 | 3 | Pure Haxe implementation of the BSON spec http://bsonspec.org/ 4 | 5 | Largely inspired by and partially ported from the following repos: 6 | - https://github.com/MattTuttle/mongo-haxe-driver 7 | - https://bitbucket.org/yar3333/haxe-mongomod -------------------------------------------------------------------------------- /bson.hxml: -------------------------------------------------------------------------------- 1 | -cp tests 2 | -main RunTests 3 | -js bin/test.js 4 | -lib bson 5 | -lib hxnodejs -------------------------------------------------------------------------------- /haxelib.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bson", 3 | "url": "https://github.com/kevinresol/bson", 4 | "license": "MIT", 5 | "tags": ["cross", "bson", "serialization"], 6 | "description": "Pure Haxe implementation of the BSON spec", 7 | "contributors": ["kevinresol"], 8 | "version": "0.1.0", 9 | "releasenote": "Initial release", 10 | "classPath": "src", 11 | "dependencies": {} 12 | } -------------------------------------------------------------------------------- /src/bson/Bson.hx: -------------------------------------------------------------------------------- 1 | package bson; 2 | 3 | import haxe.io.Bytes; 4 | 5 | class Bson { 6 | 7 | public inline static function encode(o:Dynamic):Bytes 8 | return BsonEncoder.encode(o); 9 | 10 | public inline static function encodeMultiple(o:Array):Bytes 11 | return BsonEncoder.encodeMultiple(o); 12 | 13 | public inline static function decode(bytes:Bytes):Dynamic 14 | return BsonDecoder.decode(bytes); 15 | 16 | public inline static function decodeMultiple(bytes:Bytes, num = 1):Dynamic 17 | return BsonDecoder.decodeMultiple(bytes, num); 18 | } -------------------------------------------------------------------------------- /src/bson/BsonDecoder.hx: -------------------------------------------------------------------------------- 1 | package bson; 2 | 3 | import haxe.Int64; 4 | import haxe.io.Bytes; 5 | import haxe.io.BytesInput; 6 | import bson.BsonType; 7 | 8 | using Reflect; 9 | 10 | class BsonDecoder { 11 | 12 | public static inline function decode(b:Bytes):Dynamic { 13 | return new BsonInput(b).readObject(); 14 | } 15 | 16 | public static inline function decodeMultiple(b:Bytes, num = 1):Array { 17 | var input = new BsonInput(b); 18 | return [for(i in 0...num) input.readObject()]; 19 | } 20 | } 21 | 22 | private class BsonInput extends BytesInput { 23 | 24 | public function readObject():Dynamic { 25 | var length = readInt32() - 4; // exclude the 32-bit length value 26 | var o = {}; 27 | while(length > 0) { 28 | var type = readByte(); 29 | length --; 30 | if(type == BTerminate) 31 | return o; 32 | var field = readField(type); 33 | o.setField(field.key, field.value); 34 | length -= field.length; 35 | } 36 | return o; 37 | } 38 | 39 | public function readArray():Array { 40 | var length = readInt32() - 4; // exclude the 32-bit length value 41 | var array = []; 42 | while(length > 0) { 43 | var type = readByte(); 44 | length --; 45 | if(type == BTerminate) 46 | return array; 47 | var field = readField(type); 48 | array[Std.parseInt(field.key)] = field.value; 49 | length -= field.length; 50 | } 51 | return array; 52 | } 53 | 54 | public function readField(type:BsonType) { 55 | var startPos = position; 56 | var key = readUntil(BTerminate); 57 | var value:Dynamic = switch type { 58 | 59 | case BDouble: 60 | readDouble(); 61 | 62 | case BString | BJavascript: 63 | var len = readInt32(); 64 | var v = readString(len - 1); 65 | readByte(); // consume terminator 66 | v; 67 | 68 | case BDocument: 69 | readObject(); 70 | 71 | case BArray: 72 | readArray(); 73 | 74 | case BBinary: 75 | var len = readInt32(); 76 | var subtype:BsonSubType = readByte(); 77 | read(len); 78 | 79 | case BObjectId: 80 | (read(12):ObjectId); 81 | 82 | case BBool: 83 | readByte() == 1; 84 | 85 | case BDate: 86 | Date.fromTime(readUInt64()); 87 | 88 | case BNull: 89 | null; 90 | 91 | case BRegEx: 92 | var pattern = readUntil(BTerminate); 93 | var options = readUntil(BTerminate); 94 | new EReg(pattern, options); 95 | 96 | case BJavascriptWithScope: 97 | throw 'not implemented'; 98 | 99 | case BInt32: 100 | readInt32(); 101 | 102 | case BTimestamp: 103 | readInt64(); 104 | 105 | case BInt64: 106 | // var v = readInt64(); 107 | // v.high * 4294967296.0 + (v.low > 0 ? v.low : 4294967296.0 + v.low); 108 | readInt64(); 109 | 110 | case BMinKey: 111 | 'Min'; 112 | 113 | case BMaxKey: 114 | 'Max'; 115 | 116 | default: 117 | throw 'Unkown type: $type'; 118 | } 119 | 120 | return { 121 | key: key, 122 | value: value, 123 | length: position - startPos, 124 | } 125 | } 126 | 127 | public function readInt64() { 128 | var low = readInt32(); 129 | var high = readInt32(); 130 | return Int64.make(high, low); 131 | } 132 | public function readUInt32():UInt { 133 | var low = readInt16() & 0xffff; 134 | var high = readInt16() & 0xffff; 135 | return low + high * 65536; 136 | } 137 | public function readUInt64() { 138 | var low = readUInt32(); 139 | var high = readUInt32(); 140 | return low + high * 4294967296.0; 141 | } 142 | } 143 | 144 | 145 | -------------------------------------------------------------------------------- /src/bson/BsonDocument.hx: -------------------------------------------------------------------------------- 1 | package bson; 2 | 3 | import haxe.macro.Context; 4 | import haxe.macro.Expr; 5 | 6 | @:forward 7 | abstract BsonDocument(BsonDocumentBase) { 8 | 9 | public inline function new() this = new BsonDocumentBase(); 10 | 11 | public static inline function is(v:Dynamic) 12 | return Std.is(v, BsonDocumentBase); 13 | 14 | #if (haxe_ver >= 3.3) 15 | @:from 16 | public static macro function fromObject(e:ExprOf<{}>):ExprOf { 17 | var exprs = []; 18 | switch e.expr { 19 | case EObjectDecl(fields): 20 | for(field in fields) { 21 | var key = field.field; 22 | var expr = field.expr; 23 | exprs.push(macro doc.add($v{key}, $expr)); 24 | } 25 | case EMeta({name:':this'}, _): 26 | switch Context.follow(Context.typeof(e)) { 27 | case TAnonymous(_.get() => a): 28 | // sort the fields based on the pos 29 | a.fields.sort(function(f1, f2) { 30 | var p1 = parsePos(f1.pos); 31 | var p2 = parsePos(f2.pos); 32 | var r = Reflect.compare(p1.line, p2.line); 33 | if(r != 0) return r; 34 | return Reflect.compare(p1.min, p2.min); 35 | }); 36 | 37 | exprs.push(macro var o = $e); 38 | for(f in a.fields){ 39 | var key = f.name; 40 | exprs.push(macro doc.add($v{key}, o.$key)); 41 | } 42 | default: 43 | throw 'assert'; 44 | } 45 | case EBlock([]): 46 | // do nothing 47 | default: 48 | throw 'assert'; 49 | } 50 | return macro @:pos(e.pos) { 51 | var doc = new bson.BsonDocument(); 52 | $b{exprs}; 53 | doc; 54 | }; 55 | } 56 | #end 57 | 58 | static function parsePos(pos:Dynamic) { 59 | var s = Std.string(pos).split(':'); 60 | var line = Std.parseInt(s[1]); 61 | var s = s[2].split(s[2].indexOf('lines') == -1 ? 'characters ' : 'lines ')[1].split('-'); 62 | var min = Std.parseInt(s[0]); 63 | var max = Std.parseInt(s[1]); 64 | return { 65 | line: line, 66 | min: min, 67 | max: max, 68 | } 69 | } 70 | } 71 | 72 | class BsonDocumentBase { 73 | 74 | var items:Array<{key:String, value:Dynamic}>; 75 | 76 | public function new() { 77 | items = []; 78 | } 79 | 80 | public inline function add(key:String, value:Dynamic) { 81 | items.push({key:key, value:value}); 82 | } 83 | 84 | public inline function iterator() 85 | return items.iterator(); 86 | 87 | public inline function toString() 88 | return '{' + [for(i in items) '${i.key}:${i.value}'].join(',') + '}'; 89 | } 90 | -------------------------------------------------------------------------------- /src/bson/BsonEncoder.hx: -------------------------------------------------------------------------------- 1 | package bson; 2 | 3 | import haxe.Int64; 4 | import haxe.ds.StringMap; 5 | import haxe.io.Bytes; 6 | import haxe.io.BytesOutput; 7 | import bson.BsonType; 8 | using Reflect; 9 | 10 | class BsonEncoder { 11 | public static function encode(o:Dynamic):Bytes { 12 | var bson = new BsonOutput(); 13 | if(Type.typeof(o) == TObject) 14 | bson.writeObject(o); 15 | else if(BsonDocument.is(o)) 16 | bson.writeBsonDocument(o); 17 | else 18 | throw "Can only encode object or BsonDocument"; // TODO: handle class instances and maps? 19 | return bson.getBytes(); 20 | } 21 | 22 | public static function encodeMultiple(arr:Array):Bytes { 23 | var bson = new BsonOutput(); 24 | for(o in arr) { 25 | if(Type.typeof(o) == TObject) 26 | bson.writeObject(o); 27 | else if(BsonDocument.is(o)) 28 | bson.writeBsonDocument(o); 29 | else 30 | throw "Can only encode object or BsonDocument"; // TODO: handle class instances and maps? 31 | } 32 | return bson.getBytes(); 33 | } 34 | } 35 | 36 | private class BsonOutput extends BytesOutput { 37 | public function writeHeader(key:String, type:BsonType) { 38 | writeByte(type); 39 | writeString(key); 40 | writeByte(BTerminate); 41 | } 42 | 43 | public function writeUInt32(n:Float) { 44 | var high = Std.int(n / 65536.0); 45 | var low = Math.round(n - high * 65536.0); 46 | writeUInt16(low); 47 | writeUInt16(high); 48 | } 49 | 50 | public function writeUInt64(n:Float) { 51 | var high = Math.ffloor(n / 4294967296.0); 52 | var low = n - high * 4294967296.0; 53 | writeUInt32(low); 54 | writeUInt32(high); 55 | } 56 | 57 | public function writeKeyValue(key:String, value:Dynamic) { 58 | if(value == null) writeHeader(key, BNull); 59 | else switch Type.typeof(value) { 60 | case TBool: 61 | writeHeader(key, BBool); 62 | writeByte(value ? 0x01 : 0x00); 63 | 64 | case TInt: 65 | writeHeader(key, BInt32); 66 | writeInt32(value); 67 | 68 | case TFloat: 69 | writeHeader(key, BDouble); 70 | writeDouble(value); 71 | 72 | case _ if(Std.is(value, String)): 73 | var value:String = cast value; 74 | writeHeader(key, BString); 75 | writeInt32(value.length + 1); 76 | writeString(value); 77 | writeByte(BTerminate); 78 | 79 | case _ if(Int64.is(value)): 80 | var value:Int64 = cast value; 81 | writeHeader(key, BInt64); 82 | writeInt32(value.low); 83 | writeInt32(value.high); 84 | 85 | case _ if(Std.is(value, Date)): 86 | var value:Date = cast value; 87 | writeHeader(key, BDate); 88 | writeUInt64(value.getTime()); 89 | 90 | case _ if(ObjectId.is(value)): 91 | var value:ObjectId = cast value; 92 | writeHeader(key, BObjectId); 93 | write(value.bytes); 94 | 95 | case _ if(Std.is(value, Array)): 96 | writeHeader(key, BArray); 97 | writeArray(value); 98 | 99 | case _ if(Std.is(value, StringMap)): 100 | writeHeader(key, BDocument); 101 | writeMap(value); 102 | 103 | case _ if(BsonDocument.is(value)): 104 | writeHeader(key, BDocument); 105 | writeBsonDocument(value); 106 | 107 | case _ if(value.isObject()): 108 | writeHeader(key, BDocument); 109 | writeObject(value); 110 | 111 | } 112 | } 113 | 114 | public function writeArray(array:Array) { 115 | var bson = new BsonOutput(); 116 | for(i in 0...array.length) 117 | bson.writeKeyValue(Std.string(i), array[i]); 118 | bson.writeByte(BTerminate); 119 | var bytes = bson.getBytes(); 120 | writeInt32(bytes.length + 4); // include the 32-bit length value itself 121 | write(bytes); 122 | } 123 | 124 | public function writeObject(o:Dynamic) { 125 | var bson = new BsonOutput(); 126 | for(key in o.fields()) { 127 | var value = o.field(key); 128 | if(!Reflect.isFunction(value)) 129 | bson.writeKeyValue(key, value); 130 | } 131 | bson.writeByte(BTerminate); 132 | var bytes = bson.getBytes(); 133 | writeInt32(bytes.length + 4); // include the 32-bit length value itself 134 | write(bytes); 135 | } 136 | 137 | public function writeMap(o:Map) { 138 | var bson = new BsonOutput(); 139 | for(key in o.keys()) { 140 | var value = o.field(key); 141 | bson.writeKeyValue(key, value); 142 | } 143 | bson.writeByte(BTerminate); 144 | var bytes = bson.getBytes(); 145 | writeInt32(bytes.length + 4); // include the 32-bit length value itself 146 | write(bytes); 147 | } 148 | 149 | public function writeBsonDocument(b:BsonDocument) { 150 | var bson = new BsonOutput(); 151 | for(item in b) 152 | bson.writeKeyValue(item.key, item.value); 153 | bson.writeByte(BTerminate); 154 | var bytes = bson.getBytes(); 155 | writeInt32(bytes.length + 4); // include the 32-bit length value itself 156 | write(bytes); 157 | } 158 | } -------------------------------------------------------------------------------- /src/bson/BsonType.hx: -------------------------------------------------------------------------------- 1 | package bson; 2 | 3 | @:enum 4 | abstract BsonType(Int) from Int to Int { 5 | var BTerminate = 0x00; 6 | var BDouble = 0x01; 7 | var BString = 0x02; 8 | var BDocument = 0x03; 9 | var BArray = 0x04; 10 | var BBinary = 0x05; 11 | // var BUndefined = 0x06; // deprecated per bson spec 12 | var BObjectId = 0x07; 13 | var BBool = 0x08; 14 | var BDate = 0x09; 15 | var BNull = 0x0A; 16 | var BRegEx = 0x0B; 17 | // var BDBPointer = 0x0C; // deprecated per bson spec 18 | var BJavascript = 0x0D; 19 | // var = 0x0E; // deprecated per bson spec 20 | var BJavascriptWithScope = 0x0F; 21 | var BInt32 = 0x10; 22 | var BTimestamp = 0x11; 23 | var BInt64 = 0x12; 24 | var BMinKey = 0xFF; 25 | var BMaxKey = 0x7F; 26 | } 27 | 28 | @:enum 29 | abstract BsonSubType(Int) from Int to Int { 30 | var BSGeneric = 0x00; 31 | var BSFunction = 0x01; 32 | var BSBinary = 0x02; 33 | var BSUuidOld = 0x03; 34 | var BSUuid = 0x04; 35 | var BSMd5 = 0x05; 36 | var BSUserDefined = 0x80; 37 | } -------------------------------------------------------------------------------- /src/bson/ObjectId.hx: -------------------------------------------------------------------------------- 1 | package bson; 2 | 3 | import haxe.io.Bytes; 4 | import haxe.io.BytesOutput; 5 | import haxe.crypto.Md5; 6 | 7 | @:forward 8 | abstract ObjectId(ObjectIdBase) from ObjectIdBase to ObjectIdBase { 9 | 10 | public inline function new(?str:String) this = str == null ? new ObjectIdBase() : fromString(str); 11 | 12 | public static inline function is(o:Dynamic):Bool 13 | return Std.is(o, ObjectIdBase); 14 | 15 | @:from 16 | public static function fromString(s:String):ObjectId { 17 | if(s.length != 24) throw "String ObjectId should be of length 24"; 18 | var bytes = Bytes.alloc(12); 19 | for(i in 0...12) bytes.set(i, Std.parseInt('0x' + s.substr(i << 1, 2))); 20 | return new ObjectIdBase(bytes); 21 | } 22 | 23 | @:from 24 | public inline static function fromBytes(b:Bytes):ObjectId { 25 | return new ObjectIdBase(b); 26 | } 27 | 28 | @:op(A == B) 29 | public inline function eq(b:ObjectId):Bool 30 | return this.bytes.toHex() == b.bytes.toHex(); 31 | 32 | @:op(A > B) 33 | public inline function gt(b:ObjectId):Bool 34 | return Reflect.compare(this.valueOf(), b.valueOf()) > 0; 35 | } 36 | 37 | private class ObjectIdBase { 38 | 39 | static var pid = Std.random(1 << 16); 40 | static var sequence = Std.random(1 << 24); 41 | static var machine = Bytes.ofString(Md5.encode( 42 | #if php 43 | try sys.net.Host.localhost() catch(e:Dynamic) 'php' 44 | #elseif hl 45 | try sys.net.Host.localhost() catch(e:Dynamic) 'hl' 46 | #elseif sys 47 | sys.net.Host.localhost() 48 | #else 49 | 'flash' 50 | #end 51 | )); 52 | 53 | public var bytes(default, null):Bytes; 54 | 55 | public function new(?bytes:Bytes) { 56 | if(bytes != null) this.bytes = bytes; 57 | else { 58 | var out = new BytesOutput(); 59 | out.bigEndian = true; 60 | out.writeInt32(Math.floor(Date.now().getTime() / 1000)); 61 | out.writeBytes(machine, 0, 3); 62 | out.writeUInt16(pid); 63 | out.writeUInt24(sequence++); 64 | if(sequence > 0xffffff) sequence = 0; 65 | this.bytes = out.getBytes(); 66 | } 67 | } 68 | 69 | public function toString():String { 70 | return 'ObjectID("${bytes.toHex()}")'; 71 | } 72 | 73 | public inline function getTimeStamp() 74 | return Date.fromTime(bytes.getInt32(0) * 1000); 75 | 76 | public inline function valueOf() 77 | return bytes.toHex(); 78 | } 79 | -------------------------------------------------------------------------------- /submit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | zip -r temp.zip haxelib.json src README.md 4 | haxelib submit temp.zip 5 | rm temp.zip -------------------------------------------------------------------------------- /tests.hxml: -------------------------------------------------------------------------------- 1 | -cp ./tests/ 2 | -main RunTests 3 | -dce full -------------------------------------------------------------------------------- /tests/Base.hx: -------------------------------------------------------------------------------- 1 | package; 2 | 3 | import bson.*; 4 | import haxe.Int64; 5 | import haxe.io.Bytes; 6 | import haxe.unit.TestCase; 7 | 8 | using StringTools; 9 | 10 | class Base extends TestCase { 11 | 12 | // {_id: ObjectId("572ae3afe378209d612b3643"), a: 'b'} 13 | static inline var STRING = "1F,00,00,00,07,5F,69,64,00,57,2A,E3,AF,E3,78,20,9D,61,2B,36,43,02,61,00,02,00,00,00,62,00,00"; 14 | 15 | // {_id: ObjectId("572b4d524dd01c6e90e069eb"), a: Date.fromTime(1462455634589)} 16 | #if (neko || python) // no ms precision 17 | static inline var DATE = "21,00,00,00,07,5F,69,64,00,57,2B,4D,52,4D,D0,1C,6E,90,E0,69,EB,09,61,00,50,08,26,81,54,01,00,00,00"; 18 | #else 19 | static inline var DATE = "21,00,00,00,07,5F,69,64,00,57,2B,4D,52,4D,D0,1C,6E,90,E0,69,EB,09,61,00,9D,0A,26,81,54,01,00,00,00"; 20 | #end 21 | 22 | // {_id: ObjectId("572b443e4dd01c6e90e069ea"), a: Int64.make(0x01, 0x23456789)} 23 | static inline var INT64 = "21,00,00,00,07,5F,69,64,00,57,2B,44,3E,4D,D0,1C,6E,90,E0,69,EA,12,61,00,89,67,45,23,01,00,00,00,00"; 24 | 25 | function compare(a:Dynamic, b:Dynamic, ?pos:haxe.PosInfos) { 26 | 27 | if(ObjectId.is(a)) { 28 | 29 | if(!ObjectId.is(b)) fail('b is not object id'); 30 | var a:ObjectId = cast a; 31 | var b:ObjectId = cast b; 32 | assertTrue(a == b); 33 | 34 | } else if(Std.is(a, String)) { 35 | 36 | if(!Std.is(b, String)) fail('b is not string'); 37 | assertEquals(a, b); 38 | 39 | } else if(Std.is(a, Date)) { 40 | 41 | if(!Std.is(b, Date)) fail('b is not date'); 42 | var a:Date = cast a; 43 | var b:Date = cast b; 44 | assertDate(a, b); 45 | 46 | } else if (Std.is(a, Array)) { 47 | 48 | if(!Std.is(b, Array)) fail('b is not array'); 49 | if(a.length != b.length) fail('not same length'); 50 | for(i in 0...a.length) compare(a[i], b[i]); 51 | 52 | } else if(Int64.is(a)) { 53 | 54 | #if !java 55 | if(!Int64.is(b)) fail('b is not int64'); 56 | #end 57 | var a:Int64 = cast a; 58 | var b:Int64 = cast b; 59 | assertTrue(a == b); 60 | 61 | } else if(Reflect.isObject(a)) { 62 | 63 | if(!Reflect.isObject(b)) fail('b is not object'); 64 | var keys = Reflect.fields(a); 65 | if(keys.length != Reflect.fields(b).length) fail('not same number of keys'); 66 | for(key in keys) compare(Reflect.field(a, key), Reflect.field(b, key)); 67 | 68 | } else { 69 | assertEquals(a, b); 70 | } 71 | } 72 | 73 | function assertBytes(s:String, b:Bytes, ?pos:haxe.PosInfos) { 74 | assertEquals(s.replace(',' ,'').toLowerCase(), b.toHex()); 75 | } 76 | 77 | function assertDate(a:Date, b:Date, ?pos:haxe.PosInfos) { 78 | #if (neko || cs) 79 | assertEquals(Std.int(a.getTime() / 1000), Std.int(b.getTime() / 1000)); 80 | #else 81 | assertEquals(a.getTime(), b.getTime()); 82 | #end 83 | } 84 | 85 | function fail( reason:String, ?c : haxe.PosInfos ) : Void { 86 | currentTest.done = true; 87 | currentTest.success = false; 88 | currentTest.error = reason; 89 | currentTest.posInfos = c; 90 | throw currentTest; 91 | } 92 | 93 | function hexToBytes(str:String) { 94 | var s = str.split(','); 95 | var bytes = Bytes.alloc(s.length); 96 | for(i in 0...s.length) bytes.set(i, Std.parseInt('0x${s[i]}')); 97 | return bytes; 98 | } 99 | } -------------------------------------------------------------------------------- /tests/RunTests.hx: -------------------------------------------------------------------------------- 1 | package; 2 | 3 | 4 | import haxe.unit.TestRunner; 5 | import haxe.unit.TestCase; 6 | 7 | #if flash 8 | import flash.system.System.exit; 9 | #else 10 | import Sys.exit; 11 | #end 12 | 13 | using StringTools; 14 | 15 | class RunTests { 16 | 17 | 18 | static function main() { 19 | 20 | var t = new TestRunner(); 21 | t.add(new TestEncoder()); 22 | t.add(new TestDecoder()); 23 | t.add(new TestComplex()); 24 | exit(t.run() ? 0 : 500); 25 | } 26 | 27 | 28 | 29 | 30 | } -------------------------------------------------------------------------------- /tests/TestComplex.hx: -------------------------------------------------------------------------------- 1 | package; 2 | 3 | import bson.*; 4 | import haxe.Int64; 5 | 6 | class TestComplex extends Base { 7 | 8 | function testComplex() { 9 | 10 | var data = { 11 | _id: new ObjectId(), 12 | title: 'My awesome post', 13 | hol: [10, 2, 20.5], 14 | int64: Int64.ofInt(1), 15 | lint64: Int64.make(1<<31, 0), // smallest int64 16 | options: { 17 | delay: 1.565, 18 | test: true, 19 | nested: [ 20 | { 21 | going: 'deeper', 22 | mining: -35 23 | } 24 | ] 25 | }, 26 | date: Date.now(), 27 | buggyDate: Date.fromTime(1462535772414), 28 | int: 21474, 29 | float: 2147483647.0, 30 | monkey: null, 31 | bool: true 32 | }; 33 | var encoded = Bson.encode(data); 34 | // trace([for(i in 0...encoded.length) encoded.get(i).hex(2)].join(",")); 35 | var decoded = Bson.decode(encoded); 36 | compare(data, decoded); 37 | } 38 | } -------------------------------------------------------------------------------- /tests/TestDecoder.hx: -------------------------------------------------------------------------------- 1 | package; 2 | 3 | import bson.*; 4 | import haxe.Int64; 5 | 6 | class TestDecoder extends Base { 7 | 8 | 9 | function testDecodeString() { 10 | var bytes = hexToBytes(Base.STRING); 11 | var decoded = Bson.decode(bytes); 12 | assertEquals('b', decoded.a); 13 | var id:ObjectId = decoded._id; 14 | assertEquals('572ae3afe378209d612b3643', id.valueOf()); 15 | } 16 | 17 | function testDecodeDate() { 18 | var bytes = hexToBytes(Base.DATE); 19 | var decoded = Bson.decode(bytes); 20 | assertTrue(Std.is(decoded.a, Date)); 21 | 22 | var d:Date = decoded.a; 23 | assertDate(Date.fromTime(1462455634589), d); 24 | var id:ObjectId = decoded._id; 25 | assertEquals('572b4d524dd01c6e90e069eb', id.valueOf()); 26 | } 27 | 28 | #if !java 29 | // Int64 is broken on java 30 | // see https://github.com/HaxeFoundation/haxe/issues/5204 31 | function testDecodeInt64() { 32 | var bytes = hexToBytes(Base.INT64); 33 | var decoded = Bson.decode(bytes); 34 | assertTrue(Int64.is(decoded.a)); 35 | var i:Int64 = decoded.a; 36 | assertEquals(0x01, i.high); 37 | assertEquals(0x23456789, i.low); 38 | var id:ObjectId = decoded._id; 39 | assertEquals('572b443e4dd01c6e90e069ea', id.valueOf()); 40 | } 41 | #end 42 | } -------------------------------------------------------------------------------- /tests/TestEncoder.hx: -------------------------------------------------------------------------------- 1 | package; 2 | 3 | import bson.*; 4 | import haxe.Int64; 5 | 6 | class TestEncoder extends Base { 7 | 8 | function testEncodeString() { 9 | var data:BsonDocument = { 10 | _id: new ObjectId('572ae3afe378209d612b3643'), 11 | a: 'b', 12 | } 13 | assertBytes(Base.STRING, Bson.encode(data)); 14 | } 15 | 16 | function testEncodeDate() { 17 | var data:BsonDocument = { 18 | _id: new ObjectId("572b4d524dd01c6e90e069eb"), 19 | a: Date.fromTime(1462455634589), 20 | } 21 | assertBytes(Base.DATE, Bson.encode(data)); 22 | } 23 | 24 | #if !java 25 | // Int64 is broken on java 26 | // see https://github.com/HaxeFoundation/haxe/issues/5204 27 | function testEncodeInt64() { 28 | var data:BsonDocument = { 29 | _id: new ObjectId("572b443e4dd01c6e90e069ea"), 30 | a: Int64.make(0x01, 0x23456789) 31 | } 32 | assertBytes(Base.INT64, Bson.encode(data)); 33 | } 34 | #end 35 | } --------------------------------------------------------------------------------