maxiaoshan пре 2 година
родитељ
комит
51c54e27a4
100 измењених фајлова са 23038 додато и 0 уклоњено
  1. 9 0
      .gitignore
  2. 5 0
      filterkey/src/config.json
  3. 46 0
      filterkey/src/main.go
  4. 9 0
      src/README.md
  5. 450 0
      src/classification/classification.go
  6. 11 0
      src/classification/classification_test.go
  7. 376 0
      src/config.json
  8. 0 0
      src/github.com/dlclark/regexp2.zip
  9. 18 0
      src/github.com/gorilla/context/.travis.yml
  10. 27 0
      src/github.com/gorilla/context/LICENSE
  11. 7 0
      src/github.com/gorilla/context/README.md
  12. 143 0
      src/github.com/gorilla/context/context.go
  13. 161 0
      src/github.com/gorilla/context/context_test.go
  14. 82 0
      src/github.com/gorilla/context/doc.go
  15. 0 0
      src/github.com/gorilla/gorilla.zip
  16. 20 0
      src/github.com/gorilla/mux/.travis.yml
  17. 27 0
      src/github.com/gorilla/mux/LICENSE
  18. 242 0
      src/github.com/gorilla/mux/README.md
  19. 49 0
      src/github.com/gorilla/mux/bench_test.go
  20. 26 0
      src/github.com/gorilla/mux/context_gorilla.go
  21. 40 0
      src/github.com/gorilla/mux/context_gorilla_test.go
  22. 24 0
      src/github.com/gorilla/mux/context_native.go
  23. 32 0
      src/github.com/gorilla/mux/context_native_test.go
  24. 206 0
      src/github.com/gorilla/mux/doc.go
  25. 495 0
      src/github.com/gorilla/mux/mux.go
  26. 1439 0
      src/github.com/gorilla/mux/mux_test.go
  27. 710 0
      src/github.com/gorilla/mux/old_test.go
  28. 312 0
      src/github.com/gorilla/mux/regexp.go
  29. 634 0
      src/github.com/gorilla/mux/route.go
  30. 18 0
      src/github.com/gorilla/securecookie/.travis.yml
  31. 27 0
      src/github.com/gorilla/securecookie/LICENSE
  32. 78 0
      src/github.com/gorilla/securecookie/README.md
  33. 61 0
      src/github.com/gorilla/securecookie/doc.go
  34. 25 0
      src/github.com/gorilla/securecookie/fuzz.go
  35. 47 0
      src/github.com/gorilla/securecookie/fuzz/gencorpus.go
  36. 646 0
      src/github.com/gorilla/securecookie/securecookie.go
  37. 274 0
      src/github.com/gorilla/securecookie/securecookie_test.go
  38. 21 0
      src/github.com/gorilla/sessions/.travis.yml
  39. 27 0
      src/github.com/gorilla/sessions/LICENSE
  40. 79 0
      src/github.com/gorilla/sessions/README.md
  41. 199 0
      src/github.com/gorilla/sessions/doc.go
  42. 102 0
      src/github.com/gorilla/sessions/lex.go
  43. 241 0
      src/github.com/gorilla/sessions/sessions.go
  44. 201 0
      src/github.com/gorilla/sessions/sessions_test.go
  45. 270 0
      src/github.com/gorilla/sessions/store.go
  46. 73 0
      src/github.com/gorilla/sessions/store_test.go
  47. 16 0
      src/go.mongodb.org/mongo-driver/.errcheck-excludes
  48. 911 0
      src/go.mongodb.org/mongo-driver/.evergreen/config.yml
  49. 8 0
      src/go.mongodb.org/mongo-driver/.evergreen/krb5.config
  50. 12 0
      src/go.mongodb.org/mongo-driver/.gitignore
  51. 3 0
      src/go.mongodb.org/mongo-driver/.gitmodules
  52. 64 0
      src/go.mongodb.org/mongo-driver/.lint-whitelist
  53. 37 0
      src/go.mongodb.org/mongo-driver/CONTRIBUTING.md
  54. 367 0
      src/go.mongodb.org/mongo-driver/Gopkg.lock
  55. 58 0
      src/go.mongodb.org/mongo-driver/Gopkg.toml
  56. 201 0
      src/go.mongodb.org/mongo-driver/LICENSE
  57. 147 0
      src/go.mongodb.org/mongo-driver/Makefile
  58. 193 0
      src/go.mongodb.org/mongo-driver/README.md
  59. 1336 0
      src/go.mongodb.org/mongo-driver/THIRD-PARTY-NOTICES
  60. 75 0
      src/go.mongodb.org/mongo-driver/benchmark/bson.go
  61. 123 0
      src/go.mongodb.org/mongo-driver/benchmark/bson_document.go
  62. 88 0
      src/go.mongodb.org/mongo-driver/benchmark/bson_map.go
  63. 103 0
      src/go.mongodb.org/mongo-driver/benchmark/bson_struct.go
  64. 35 0
      src/go.mongodb.org/mongo-driver/benchmark/bson_test.go
  65. 306 0
      src/go.mongodb.org/mongo-driver/benchmark/bson_types.go
  66. 29 0
      src/go.mongodb.org/mongo-driver/benchmark/canary.go
  67. 12 0
      src/go.mongodb.org/mongo-driver/benchmark/canary_test.go
  68. 226 0
      src/go.mongodb.org/mongo-driver/benchmark/harness.go
  69. 154 0
      src/go.mongodb.org/mongo-driver/benchmark/harness_case.go
  70. 69 0
      src/go.mongodb.org/mongo-driver/benchmark/harness_main.go
  71. 140 0
      src/go.mongodb.org/mongo-driver/benchmark/harness_results.go
  72. 142 0
      src/go.mongodb.org/mongo-driver/benchmark/multi.go
  73. 13 0
      src/go.mongodb.org/mongo-driver/benchmark/multi_test.go
  74. 174 0
      src/go.mongodb.org/mongo-driver/benchmark/single.go
  75. 14 0
      src/go.mongodb.org/mongo-driver/benchmark/single_test.go
  76. 134 0
      src/go.mongodb.org/mongo-driver/bson/benchmark_test.go
  77. 60 0
      src/go.mongodb.org/mongo-driver/bson/bson.go
  78. 91 0
      src/go.mongodb.org/mongo-driver/bson/bson_1_8.go
  79. 371 0
      src/go.mongodb.org/mongo-driver/bson/bson_corpus_spec_test.go
  80. 113 0
      src/go.mongodb.org/mongo-driver/bson/bson_test.go
  81. 163 0
      src/go.mongodb.org/mongo-driver/bson/bsoncodec/bsoncodec.go
  82. 145 0
      src/go.mongodb.org/mongo-driver/bson/bsoncodec/bsoncodec_test.go
  83. 1014 0
      src/go.mongodb.org/mongo-driver/bson/bsoncodec/default_value_decoders.go
  84. 2870 0
      src/go.mongodb.org/mongo-driver/bson/bsoncodec/default_value_decoders_test.go
  85. 648 0
      src/go.mongodb.org/mongo-driver/bson/bsoncodec/default_value_encoders.go
  86. 1436 0
      src/go.mongodb.org/mongo-driver/bson/bsoncodec/default_value_encoders_test.go
  87. 61 0
      src/go.mongodb.org/mongo-driver/bson/bsoncodec/doc.go
  88. 65 0
      src/go.mongodb.org/mongo-driver/bson/bsoncodec/mode.go
  89. 110 0
      src/go.mongodb.org/mongo-driver/bson/bsoncodec/pointer_codec.go
  90. 14 0
      src/go.mongodb.org/mongo-driver/bson/bsoncodec/proxy.go
  91. 384 0
      src/go.mongodb.org/mongo-driver/bson/bsoncodec/registry.go
  92. 359 0
      src/go.mongodb.org/mongo-driver/bson/bsoncodec/registry_test.go
  93. 367 0
      src/go.mongodb.org/mongo-driver/bson/bsoncodec/struct_codec.go
  94. 47 0
      src/go.mongodb.org/mongo-driver/bson/bsoncodec/struct_codec_test.go
  95. 119 0
      src/go.mongodb.org/mongo-driver/bson/bsoncodec/struct_tag_parser.go
  96. 73 0
      src/go.mongodb.org/mongo-driver/bson/bsoncodec/struct_tag_parser_test.go
  97. 80 0
      src/go.mongodb.org/mongo-driver/bson/bsoncodec/types.go
  98. 33 0
      src/go.mongodb.org/mongo-driver/bson/bsonrw/bsonrw_test.go
  99. 847 0
      src/go.mongodb.org/mongo-driver/bson/bsonrw/bsonrwtest/bsonrwtest.go
  100. 389 0
      src/go.mongodb.org/mongo-driver/bson/bsonrw/copier.go

+ 9 - 0
.gitignore

@@ -0,0 +1,9 @@
+pkg
+bin
+*/pkg
+*.exe
+*.log
+*/src/src
+*.data
+*/bin
+.idea

+ 5 - 0
filterkey/src/config.json

@@ -0,0 +1,5 @@
+{	
+	"mgoaddr":"192.168.3.207:27080",
+	"mgosize":10,
+	"dbname":"biaozhu"
+}

+ 46 - 0
filterkey/src/main.go

@@ -0,0 +1,46 @@
+package main
+
+import (
+	//"fmt"
+	"log"
+	"os"
+	"qfw/util"
+	"strings"
+	"tools"
+)
+
+func main() {
+	//	sess := tools.Mgo.GetMgoConn()
+	//	defer tools.Mgo.DestoryMongoConn(sess)
+	//	sess.DB(tools.Mgo.DbName).C
+	res, _ := tools.Mgo.Find("rc_rule", `{"s_classid":"592f8c14e13823494a0ef74d"}`, nil, nil, false, -1, -1)
+	m := map[string]bool{}
+	m2 := map[string]bool{}
+	n := 0
+	all := 0
+	for _, obj := range *res {
+		keys := util.ObjArrToStringArr(obj["s_rule"].([]interface{}))
+		for _, k := range keys {
+			//k := k[1 : len(k)-1]
+			k = strings.Replace(strings.Replace(k, "(", "", -1), ")", "", -1)
+			ks := strings.Split(k, "|")
+			for _, k1 := range ks {
+				all++
+				if m[k1] {
+					n++
+					m2[k1] = true
+				} else {
+					m[k1] = true
+				}
+			}
+		}
+	}
+	f, _ := os.Create("test.log")
+
+	log.Println("n,len(m2), all, len(m):", n, len(m2), all, len(m))
+	//	for k, _ := range m2 {
+	//fmt.Println(k)
+	//f.WriteString(k + "\n")
+	//}
+	f.Close()
+}

+ 9 - 0
src/README.md

@@ -0,0 +1,9 @@
+dev1.0分支
+1.修改分类程序在信息处理链位置,查询extracttype为0的信息
+2.对infoformat为2的信息预处理默认为拟建
+3.修改所有信息extracttype为1
+
+dev2.0.1分支
+1.增加数据监控
+2.config.json中的招标(zhaobiao)配置项只是为了测试时使用
+/opt/mnt/rz_createindex/classification2.0

+ 450 - 0
src/classification/classification.go

@@ -0,0 +1,450 @@
+package classification
+
+import (
+	"fmt"
+	//"qfw/util"
+	"regexp"
+	"strconv"
+	"strings"
+	"task"
+)
+
+const (
+	SPLIT = "___"
+)
+
+//var REG_A_RULE = regexp.MustCompile(`([\\+\\|\\(\\)\\^]|[0-9]+)`)
+
+var REG_A_RULE = regexp.MustCompile(`([\\+\\|\\(\\)\\^])`)
+var REG_NUM = regexp.MustCompile(`[0-9]+`)
+
+//分类,支持多条规则,返回文本!!!!只提供单条测试
+func ClassificationText(text string, rules [][]string) (t string, rs [][]string, rulepos int, res bool) {
+	//fmt.Println("rr---", len(rules), len(rules[0]))
+	rs = rules
+	for n, rule := range rs {
+		if len(rule) == 0 || len(rule) > 1 { //规则
+			t, rs[n], res = ComputeText(text, rule, 1)
+			if res {
+				//fmt.Println("规则-----", rule, t, rs, n, res)
+				return t, rs, n, res
+			}
+		} else if len(rule) == 1 { //正则
+			r := rule[0]
+			rss := []rune(r)
+			ru := string(rss[1 : len(rss)-1])
+			rulereg, err := regexp.Compile(ru)
+			if err != nil {
+				continue
+			}
+			textArr := rulereg.FindAllString(text, -1)
+			rs[n] = []string{fmt.Sprintf("<r class='_rf'>%s</r>", ru)}
+			if len(textArr) > 0 {
+				//fmt.Println("正则---", rulereg, ru)
+				rs[n] = []string{fmt.Sprintf("<r class='_rt'>%s</r>", ru)}
+				text = strings.Replace(text, textArr[0], fmt.Sprintf("<r class='_and'>%s</r>", textArr[0]), 1)
+				return text, rules, n, true
+			}
+		}
+	}
+	return
+}
+
+func ComputeText(text string, rule []string, num int) (string, []string, bool) {
+	var andB, orB, oB []bool
+	//fmt.Println("rule----", rule, len(rule))
+	op := ""
+	for pos := 0; pos < len(rule); pos++ {
+		tmp := rule[pos]
+		//fmt.Println("---------", pos, "tmp----", tmp, "num----", num, len(rule)-1, pos == len(rule)-1)
+		if tmp == "+" || tmp == "^" || tmp == "|" {
+			op = tmp
+			continue
+		} else if REG_NUM.MatchString(tmp) && len(tmp) == 1 {
+			continue
+		} else {
+			tmpresult := false
+			if tmp == "(" { //找出子表达式,仅限一层,现在修改为多层逻辑
+				tmpexp := []string{}
+				pos++
+				isRight := 0
+				thispos := pos
+				for ; pos < len(rule); pos++ {
+					//fmt.Println(pos, "rule[pos]===", rule[pos])
+					if rule[pos] == "(" {
+						isRight++
+						tmpexp = append(tmpexp, rule[pos])
+					} else if rule[pos] == ")" {
+						//fmt.Println("+++++++++isRight", isRight)
+						if isRight > 0 {
+							isRight--
+							tmpexp = append(tmpexp, rule[pos])
+						} else {
+							num = 1
+							if (pos + 1) < len(rule) {
+								t := rule[pos+1]
+								//fmt.Println("+++++++++数字", t)
+								if REG_NUM.MatchString(t) {
+									num, _ = strconv.Atoi(t)
+								}
+							}
+							break
+						}
+					} else {
+						tmpexp = append(tmpexp, rule[pos])
+					}
+				}
+				//fmt.Println("1----------------", text)
+				//fmt.Println("2----------------", tmpexp)
+				text, tmpexp, tmpresult = ComputeText(text, tmpexp, num)
+				for tmpos, tmprule := range tmpexp {
+					rule[thispos+tmpos] = tmprule
+				}
+				//fmt.Println("3----------------", rule)
+			} else {
+				tmpresult = strings.Index(text, tmp) > -1
+				//增加对rule的处理
+				sclass := "f"
+				if tmpresult {
+					sclass = "t"
+				}
+				rule[pos] = fmt.Sprintf("<r class='_r%s'>%s</r>", sclass, tmp)
+				//fmt.Println("========", rule[pos])
+			}
+			if op == "" {
+				if (pos+1) < len(rule) && rule[pos+1] == "|" {
+					op = "|"
+				} else {
+					op = "+"
+				}
+			}
+			tclass := ""
+			if op == "+" {
+				if andB == nil {
+					andB = []bool{}
+				}
+				andB = append(andB, tmpresult)
+				tclass = "_and"
+
+			} else if op == "|" {
+				if orB == nil {
+					orB = []bool{}
+				}
+				orB = append(orB, tmpresult)
+				tclass = "_or"
+
+			} else if op == "^" {
+				if oB == nil {
+					oB = []bool{}
+				}
+				oB = append(oB, tmpresult)
+				tclass = "_opp"
+			}
+			//fmt.Println("tclass====", tclass, "---ob", oB, "---orb", orB)
+			if tclass != "" && tmp != "(" && tmp != ")" {
+				//fmt.Println("text=======", text)
+				//fmt.Println("old=======", tmp)
+				//fmt.Println("new=======", fmt.Sprintf("<r class='_and'>%s</r>", tmp))
+				text = strings.Replace(text, tmp, fmt.Sprintf("<r class='_and'>%s</r>", tmp), 1)
+				//fmt.Println("text3=======", text)
+			}
+		}
+	}
+	//fmt.Println("textfinish====", text, "num----", num)
+	//fmt.Println("匹配个数----", len(orB), CheckBool(orB, "|", num))
+	tres := CheckBool(andB, "+", 0) && CheckBool(orB, "|", num) && !CheckBool(oB, "^", 0)
+	return text, rule, tres
+}
+
+//分类,支持多条规则,有一个满足就返回
+func Classification(text string, rules [][]string) (bool, []string) {
+	var arr []string
+	ok := false
+	for _, rule := range rules {
+		if len(rule) == 0 || len(rule) > 1 { //规则
+			ok, arr = Compute(text, rule, 1)
+			if ok {
+				return ok, arr
+			}
+		} else if len(rule) == 1 { //正则
+			ruleArr := strings.Split(rule[0], "'")
+			rulereg := regexp.MustCompile(ruleArr[1])
+			textArr := rulereg.FindAllString(text, -1)
+			if len(textArr) > 0 {
+				return true, rule
+			}
+		}
+	}
+	return ok, arr
+}
+
+//规则或者正则识别
+func NewAnalyRules(text string, rules []interface{}) (bool, []string) {
+	var arr []string
+	ok := false
+	for _, r := range rules {
+		switch r.(type) {
+		case string: //规则
+			//fmt.Println("规则---", text, r.(string))
+			strRuleArr := AnalyRule(r.(string)) //解析规则
+			ok, arr = Compute(text, strRuleArr, 1)
+			if ok {
+				return ok, arr
+			}
+		case *regexp.Regexp: //正则
+			ruleReg := r.(*regexp.Regexp)
+			textArr := ruleReg.FindAllString(text, -1)
+			//fmt.Println("正则---", text, ruleReg, textArr)
+			if len(textArr) > 0 {
+				regStr := []string{ruleReg.String()}
+				return true, regStr
+			}
+		default:
+			continue
+		}
+	}
+	return ok, arr
+}
+
+func AnalyRules(rules []string) [][]string {
+	res := [][]string{}
+	for i := 0; i < len(rules); i++ {
+		if strings.HasPrefix(rules[i], "'") && strings.HasSuffix(rules[i], "'") { //正则
+			res = append(res, []string{rules[i]})
+		} else { //规则
+			res = append(res, AnalyRule(rules[i]))
+		}
+	}
+	return res
+}
+
+//解析规则
+func AnalyRule(rule string) []string {
+	res := []string{}
+	ruleNew := REG_A_RULE.ReplaceAllString(rule, fmt.Sprintf("%s${1}%s", SPLIT, SPLIT))
+	ruleNewArr := strings.Split(ruleNew, SPLIT)
+	for _, key := range ruleNewArr {
+		if key != "" {
+			res = append(res, key)
+			//fmt.Println("规则数组---", res)
+		}
+	}
+	return res
+}
+
+//计算
+func Compute(text string, rule []string, num int) (bool, []string) {
+	var andB, orB, oB []bool
+	op := ""
+	arr := []string{}
+	for pos := 0; pos < len(rule); pos++ {
+		tmp := rule[pos]
+		if tmp == "+" || tmp == "^" || tmp == "|" {
+			op = tmp
+			continue
+		} else if REG_NUM.MatchString(tmp) && len(tmp) == 1 {
+			continue
+		} else {
+			tmpresult := false
+			if tmp == "(" { //找出子表达式,仅限一层,现在修改为多层逻辑
+				tmpexp := []string{}
+				pos++
+				isRight := 0
+				for ; pos < len(rule); pos++ {
+					if rule[pos] == "(" {
+						isRight++
+						tmpexp = append(tmpexp, rule[pos])
+					} else if rule[pos] == ")" {
+						if isRight > 0 {
+							isRight--
+							tmpexp = append(tmpexp, rule[pos])
+						} else {
+							num = 1
+							if (pos + 1) < len(rule) {
+								t := rule[pos+1]
+								if REG_NUM.MatchString(t) {
+									num, _ = strconv.Atoi(t)
+								}
+							}
+							break
+						}
+					} else {
+						tmpexp = append(tmpexp, rule[pos])
+					}
+				}
+				var arr1 []string
+				tmpresult, arr1 = Compute(text, tmpexp, num)
+				if len(arr1) > 0 {
+					arr = append(arr, arr1...)
+				}
+			} else {
+				tmpresult = strings.Index(text, tmp) > -1
+				if op != "^" && tmpresult {
+					arr = append(arr, tmp)
+				}
+			}
+			if op == "" {
+				if (pos+1) < len(rule) && rule[pos+1] == "|" {
+					op = "|"
+				} else {
+					op = "+"
+				}
+			}
+			if op == "+" {
+				if andB == nil {
+					andB = []bool{}
+				}
+				andB = append(andB, tmpresult)
+
+			} else if op == "|" {
+				if orB == nil {
+					orB = []bool{}
+				}
+				orB = append(orB, tmpresult)
+			} else if op == "^" {
+				if oB == nil {
+					oB = []bool{}
+				}
+				oB = append(oB, tmpresult)
+			}
+		}
+	}
+	tres := CheckBool(andB, "+", 0) && CheckBool(orB, "|", num) && !CheckBool(oB, "^", 0)
+	if !tres {
+		arr = []string{}
+	}
+	return tres, arr
+}
+
+func CheckBool(bs []bool, flag string, num int) bool {
+	if flag == "+" {
+		if bs == nil {
+			return true
+		} else if len(bs) == 0 {
+			return false
+		} else {
+			b1 := bs[0]
+			i := 0
+			for _, v := range bs {
+				if i > 0 {
+					b1 = b1 && v
+				}
+				i++
+			}
+			return b1
+		}
+	} else if flag == "|" {
+		if bs == nil {
+			return true
+		} else if len(bs) == 0 {
+			return false
+		} else {
+			i := 0
+			for _, v := range bs {
+				if v {
+					i++
+				}
+			}
+			return i >= num
+		}
+	} else {
+		if bs == nil {
+			return false
+		} else if len(bs) == 0 {
+			return false
+		} else {
+			b1 := bs[0]
+			i := 0
+			for _, v := range bs {
+				if i > 0 {
+					b1 = b1 || v
+				}
+				i++
+			}
+			return b1
+		}
+	}
+}
+
+func ClassText(b, b_reg bool, pos int, s_rule []string, rulearr [][]string, s_con string) (string, [][]string) {
+	//util.Debug(b, pos, rulearr, len(s_rule), len(rulearr), s_con)
+	ruleMap := make([]map[string]bool, len(rulearr))
+	for i, r := range rulearr {
+		tmp := map[string]bool{}
+		for _, rtmp := range r {
+			tmp[rtmp] = true
+			if b && i == pos {
+				if b_reg {
+					reg := regexp.MustCompile(rtmp)
+					matchText := reg.FindString(s_con)
+					s_con = reg.ReplaceAllString(matchText, "<r class='_and'>"+matchText+"</r>")
+				} else {
+					s_con = strings.ReplaceAll(s_con, rtmp, "<r class='_and'>"+rtmp+"</r>")
+				}
+			}
+			if !b && pos == -1 {
+				if b_reg {
+					reg := regexp.MustCompile(rtmp)
+					matchText := reg.FindString(s_con)
+					s_con = reg.ReplaceAllString(matchText, "<r class='_and'>"+matchText+"</r>")
+				} else {
+					s_con = strings.ReplaceAll(s_con, rtmp, "<r class='_and'>"+rtmp+"</r>")
+				}
+			}
+		}
+		ruleMap[i] = tmp
+	}
+	//util.Debug("s_con---", len(ruleMap), s_con)
+	rules := [][]string{}
+	for k, rule := range s_rule {
+		tmpRuleArr := strings.Split(rule, "^")
+		hasRmWord := len(tmpRuleArr) == 2 //是否有排除词
+		ruleResult1 := ""
+		for j, tmprule := range tmpRuleArr {
+			//util.Debug("tmprule---", tmprule)
+			isReg := false
+			ruleTextArr := task.REG.FindAllString(tmprule, -1)
+			if strings.HasPrefix(tmprule, "'") && strings.HasSuffix(tmprule, "'") { //正则
+				tmprule = tmprule[1 : len(tmprule)-1]
+				isReg = true
+				ruleTextArr = []string{tmprule}
+			}
+			//util.Debug("ruleTextArr---", ruleTextArr)
+			ruleResult2 := ""
+			for _, ruleText := range ruleTextArr {
+				indexArr := task.REG1.FindAllStringIndex(ruleText, -1)
+				//start := indexArr[0][0]
+				end := indexArr[0][1]
+				ruleText1 := ruleText[1 : end-1]
+				if isReg {
+					ruleText1 = ruleText
+				}
+				//util.Debug("ruleText1---", ruleText1)
+				tmpArr := []string{}
+				for _, t := range strings.Split(ruleText1, "|") {
+					if ruleMap[k][t] {
+						t = "<r class='_rt'>" + t + "</r>"
+					} else {
+						t = "<r class='_rf'>" + t + "</r>"
+					}
+					//util.Debug("t---", t)
+					tmpArr = append(tmpArr, t)
+				}
+				ruleText2 := "(" + strings.Join(tmpArr, "|") + ")"
+				//util.Debug("ruleText2---", ruleText2)
+				length := len(ruleText)
+				if end+1 == length { //有数字
+					ruleText2 += string(ruleText[length-1])
+				}
+				//util.Debug("ruleText2---", ruleText2)
+				ruleResult2 += ruleText2
+			}
+			if hasRmWord && j == 0 {
+				ruleResult2 += "^"
+			}
+			ruleResult1 += ruleResult2
+		}
+		rules = append(rules, []string{ruleResult1})
+	}
+	return s_con, rules
+}

+ 11 - 0
src/classification/classification_test.go

@@ -0,0 +1,11 @@
+package classification
+
+import (
+	"log"
+	"testing"
+)
+
+func Test_analyrule(t *testing.T) {
+	//log.Println(AnalyRule(rule))
+	log.Println(Compute("一班里有警察可以多", AnalyRule("((学校+公安)|((校园|一班)+警察))^(呵|没有|(可以+(公告|多少)))"), 1))
+}

+ 376 - 0
src/config.json

@@ -0,0 +1,376 @@
+{
+    "mgoaddr": "192.168.3.207:27092",
+    "mgosize": 10,
+    "dbname": "classfication",
+    "isbidding": false,
+    "dbname_dis": "classfication",
+    "dbinfo": {
+    	"bidding":{
+			"username": "dataAnyWrite",
+   			"password": "data@dataAnyWrite"
+		}
+    }, 
+    "jkmail": {
+        "to": "maxiaoshan@topnet.net.cn,zhangjinkun@topnet.net.cn",
+        "api": "http://10.171.112.160:19281/_send/_mail"
+    },
+    "udpport": "1781",
+    "noautorun": 1,
+    "fields":"detail,projectscope,title,buyer,channel",
+    "controltaskrun": false,
+    "controllastaskid": "58be14cbedbcdc5e57727311",
+    "timetask": {
+    	"istart": false,
+		"idcollsid": "604821bc0bb7e9fcc314e65a",
+		"taskid": "5f630c38ecbae9aac0432adc",
+		"nextnodeaddr": "192.168.3.205",
+		"nextnodeport": 1182
+    },
+    "extract":{
+		"addr": "127.0.0.1",
+        "port": 1781,
+        "preNodeId": "57982b4436b82b073c000001",
+        "stype": "biaoqian"
+    },
+    "mgc":{
+    	"name": "敏感词分类",
+        "taskid": "5f2770fadc1831746ac847ba",
+        "nextNode": [
+		    {	
+				"addr": "127.0.0.1",
+				"port": 1781,
+				"stype": "yezhu",
+				"memo": "抽取"
+		    }
+		]
+    },
+    "kvtextzhaobiao":{
+        "name": "kvtext招标",
+        "taskid": "5e8fd751bb030d5bc5d0140e",
+        "nextNode": [
+		    {	
+				"addr": "127.0.0.1",
+				"port": 1781,
+				"stype": "hangye",
+				"memo": "行业分类"
+		    }
+		]
+    },
+    "newzhaobiao":{
+        "name": "新招标分类",
+        "taskid": "5b8394bee13823571599fe4a",
+        "mgoaddr": "192.168.3.207:27092",
+        "db": "mxs",
+        "coll": "test1",
+        "dbtype": ""
+    },
+    "newhangye":{
+        "name": "新行业分类",
+        "taskid": "5b88b66fe13823589679467f",
+        "mgoaddr": "192.168.3.207:27092",
+        "db": "mxs",
+        "coll": "test1",
+        "dbtype": ""
+    },
+    "newyezhu":{
+        "name": "新业主分类",
+        "taskid": "5c92f6630ef826365d805826"
+    },
+    "zhaobiao":{
+        "name":"招标分类",
+        "taskid":"57982b4436b82b073c000001"
+    },
+    "yezhu":{
+        "name":"业主",
+        "taskid":"58be14cbedbcdc5e57727311",
+        "nextNode": [
+		    {	
+				"addr": "127.0.0.1",
+				"port": 1785,
+				"stype": "",
+				"memo": "数据判重"
+		    }
+		]
+    },
+    "hangye":{
+    	"name": "行业分类",
+        "taskid": "595d8b24e138233628af3a5d",
+        "mgoaddr": "10.81.232.246:27082",
+        "db": "qfw",
+        "coll": "bidding",
+        "dbtype": "bidding",
+        "nextNode": [
+		    {	
+				"addr": "127.0.0.1",
+				"port": 1781,
+				"stype": "yezhu",
+				"memo": "业主分类"
+		    }
+		]
+    },
+    "biaoqian":{
+        "name": "标签分类",
+        "taskid": "624110979b906b76a7439654",
+        "mgoaddr": "192.168.3.166:27082",
+        "db": "majiajia",
+        "coll": "20220330_test"
+    },
+    "esconfig": {
+        "available": true,
+        "AccessID": "LTAI4G5x9aoZx8dDamQ7vfZi",
+        "AccessSecret": "Bk98FsbPYXcJe72n1bG3Ssf73acuNh",
+        "ZoneIds": [
+            {
+                "zoneid": "cn-beijing-h",
+                "LaunchTemplateId4": "lt-2ze5ir54gy4ui8okr71f",
+                "LaunchTemplateId8": "lt-2ze5fzxwgt8jcqczvmjy",
+                "vswitchid": "vsw-2ze1n1k3mo3fv2irsfdps"
+            }
+        ]
+    },
+    "datamonitor": [
+        {
+            "class": "topscopeclass",
+            "field": [
+                {
+                    "行政办公": "xzbg"
+                },
+                {
+                    "建筑工程": "jzgz"
+                },
+                {
+                    "交通工程": "jtgz"
+                },
+                {
+                    "弱电安防": "rdaf"
+                },
+                {
+                    "市政设施": "szss"
+                },
+                {
+                    "水利水电": "slsd"
+                },
+                {
+                    "信息技术": "xxjs"
+                },
+                {
+                    "医疗卫生": "ylws"
+                },
+                {
+                    "服务采购": "fwcg"
+                },
+                {
+                    "能源化工": "nyhg"
+                },
+                {
+                    "机械设备": "jxsb"
+                },
+                {
+                    "农林牧渔": "nlmy"
+                }
+            ]
+        },
+        {
+            "class": "buyerclass",
+            "field": [
+                {
+                    "检察院": "jcy"
+                },
+                {
+                    "组织": "zz"
+                },
+                {
+                    "宣传": "xc"
+                },
+                {
+                    "纪委": "jw"
+                },
+                {
+                    "统战": "tz"
+                },
+                {
+                    "党委办": "dwb"
+                },
+                {
+                    "发改": "fg"
+                },
+                {
+                    "教育": "jy"
+                },
+                {
+                    "科技": "kj"
+                },
+                {
+                    "工信": "gx"
+                },
+                {
+                    "公安": "ga"
+                },
+                {
+                    "民政": "mz"
+                },
+                {
+                    "司法": "sf"
+                },
+                {
+                    "财政": "cz"
+                },
+                {
+                    "人社": "rs"
+                },
+                {
+                    "国土": "gt"
+                },
+                {
+                    "环保": "hb"
+                },
+                {
+                    "住建": "zhujian"
+                },
+                {
+                    "交通": "jt"
+                },
+                {
+                    "水利": "sl"
+                },
+                {
+                    "农业": "ny"
+                },
+                {
+                    "商务": "shangwu"
+                },
+                {
+                    "文化": "wh"
+                },
+                {
+                    "卫生": "ws"
+                },
+                {
+                    "人行": "rh"
+                },
+                {
+                    "审计": "sj"
+                },
+                {
+                    "国资委": "gzw"
+                },
+                {
+                    "海关": "hg"
+                },
+                {
+                    "税务": "shuiwu"
+                },
+                {
+                    "工商": "gs"
+                },
+                {
+                    "质监": "zhijian"
+                },
+                {
+                    "出版广电": "cbgd"
+                },
+                {
+                    "体育": "ty"
+                },
+                {
+                    "安监": "aj"
+                },
+                {
+                    "食药": "sy"
+                },
+                {
+                    "统计": "tj"
+                },
+                {
+                    "林业": "linye"
+                },
+                {
+                    "机关事务": "jgsw"
+                },
+                {
+                    "人大": "rd"
+                },
+                {
+                    "政协": "zx"
+                },
+                {
+                    "法院": "fy"
+                },
+                {
+                    "军队": "jd"
+                },
+                {
+                    "传媒": "cm"
+                },
+                {
+                    "医疗": "yl"
+                },
+                {
+                    "学校": "xx"
+                },
+                {
+                    "地震": "dz"
+                },
+                {
+                    "气象": "qx"
+                },
+                {
+                    "银监": "yj"
+                },
+                {
+                    "证监": "zhengjian"
+                },
+                {
+                    "保监": "bj"
+                },
+                {
+                    "公共资源交易": "ggzyjy"
+                },
+                {
+                    "城管": "cg"
+                },
+                {
+                    "市政": "sz"
+                },
+                {
+                    "档案": "da"
+                },
+                {
+                    "旅游": "luyou"
+                },
+                {
+                    "社会团体": "shtt"
+                },
+                {
+                    "政务中心": "zwzx"
+                },
+                {
+                    "企业": "qy"
+                },
+                {
+                    "政府办": "zfb"
+                }
+            ]
+        },
+        {
+            "class": "toptype",
+            "field": [
+                {
+                    "拟建": "nj"
+                },
+                {
+                    "预告": "yg"
+                },
+                {
+                    "招标": "zb"
+                },
+                {
+                    "结果": "jg"
+                },
+                {
+                    "其它": "qt"
+                }
+            ]
+        }
+    ]
+}

+ 0 - 0
src/github.com/dlclark/regexp2.zip


+ 18 - 0
src/github.com/gorilla/context/.travis.yml

@@ -0,0 +1,18 @@
+language: go
+sudo: false
+
+matrix:
+  include:
+    - go: 1.3
+    - go: 1.4
+    - go: 1.5
+    - go: 1.6
+    - go: tip
+  allow_failures:
+    - go: tip
+
+script:
+  - go get -t -v ./...
+  - diff -u <(echo -n) <(gofmt -d .)
+  - go vet $(go list ./... | grep -v /vendor/)
+  - go test -v -race ./...

+ 27 - 0
src/github.com/gorilla/context/LICENSE

@@ -0,0 +1,27 @@
+Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+	 * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+	 * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+	 * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 7 - 0
src/github.com/gorilla/context/README.md

@@ -0,0 +1,7 @@
+context
+=======
+[![Build Status](https://travis-ci.org/gorilla/context.png?branch=master)](https://travis-ci.org/gorilla/context)
+
+gorilla/context is a general purpose registry for global request variables.
+
+Read the full documentation here: http://www.gorillatoolkit.org/pkg/context

+ 143 - 0
src/github.com/gorilla/context/context.go

@@ -0,0 +1,143 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package context
+
+import (
+	"net/http"
+	"sync"
+	"time"
+)
+
+var (
+	mutex sync.RWMutex
+	data  = make(map[*http.Request]map[interface{}]interface{})
+	datat = make(map[*http.Request]int64)
+)
+
+// Set stores a value for a given key in a given request.
+func Set(r *http.Request, key, val interface{}) {
+	mutex.Lock()
+	if data[r] == nil {
+		data[r] = make(map[interface{}]interface{})
+		datat[r] = time.Now().Unix()
+	}
+	data[r][key] = val
+	mutex.Unlock()
+}
+
+// Get returns a value stored for a given key in a given request.
+func Get(r *http.Request, key interface{}) interface{} {
+	mutex.RLock()
+	if ctx := data[r]; ctx != nil {
+		value := ctx[key]
+		mutex.RUnlock()
+		return value
+	}
+	mutex.RUnlock()
+	return nil
+}
+
+// GetOk returns stored value and presence state like multi-value return of map access.
+func GetOk(r *http.Request, key interface{}) (interface{}, bool) {
+	mutex.RLock()
+	if _, ok := data[r]; ok {
+		value, ok := data[r][key]
+		mutex.RUnlock()
+		return value, ok
+	}
+	mutex.RUnlock()
+	return nil, false
+}
+
+// GetAll returns all stored values for the request as a map. Nil is returned for invalid requests.
+func GetAll(r *http.Request) map[interface{}]interface{} {
+	mutex.RLock()
+	if context, ok := data[r]; ok {
+		result := make(map[interface{}]interface{}, len(context))
+		for k, v := range context {
+			result[k] = v
+		}
+		mutex.RUnlock()
+		return result
+	}
+	mutex.RUnlock()
+	return nil
+}
+
+// GetAllOk returns all stored values for the request as a map and a boolean value that indicates if
+// the request was registered.
+func GetAllOk(r *http.Request) (map[interface{}]interface{}, bool) {
+	mutex.RLock()
+	context, ok := data[r]
+	result := make(map[interface{}]interface{}, len(context))
+	for k, v := range context {
+		result[k] = v
+	}
+	mutex.RUnlock()
+	return result, ok
+}
+
+// Delete removes a value stored for a given key in a given request.
+func Delete(r *http.Request, key interface{}) {
+	mutex.Lock()
+	if data[r] != nil {
+		delete(data[r], key)
+	}
+	mutex.Unlock()
+}
+
+// Clear removes all values stored for a given request.
+//
+// This is usually called by a handler wrapper to clean up request
+// variables at the end of a request lifetime. See ClearHandler().
+func Clear(r *http.Request) {
+	mutex.Lock()
+	clear(r)
+	mutex.Unlock()
+}
+
+// clear is Clear without the lock.
+func clear(r *http.Request) {
+	delete(data, r)
+	delete(datat, r)
+}
+
+// Purge removes request data stored for longer than maxAge, in seconds.
+// It returns the amount of requests removed.
+//
+// If maxAge <= 0, all request data is removed.
+//
+// This is only used for sanity check: in case context cleaning was not
+// properly set some request data can be kept forever, consuming an increasing
+// amount of memory. In case this is detected, Purge() must be called
+// periodically until the problem is fixed.
+func Purge(maxAge int) int {
+	mutex.Lock()
+	count := 0
+	if maxAge <= 0 {
+		count = len(data)
+		data = make(map[*http.Request]map[interface{}]interface{})
+		datat = make(map[*http.Request]int64)
+	} else {
+		min := time.Now().Unix() - int64(maxAge)
+		for r := range data {
+			if datat[r] < min {
+				clear(r)
+				count++
+			}
+		}
+	}
+	mutex.Unlock()
+	return count
+}
+
+// ClearHandler wraps an http.Handler and clears request values at the end
+// of a request lifetime.
+func ClearHandler(h http.Handler) http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		defer Clear(r)
+		h.ServeHTTP(w, r)
+	})
+}

+ 161 - 0
src/github.com/gorilla/context/context_test.go

@@ -0,0 +1,161 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package context
+
+import (
+	"net/http"
+	"testing"
+)
+
+type keyType int
+
+const (
+	key1 keyType = iota
+	key2
+)
+
+func TestContext(t *testing.T) {
+	assertEqual := func(val interface{}, exp interface{}) {
+		if val != exp {
+			t.Errorf("Expected %v, got %v.", exp, val)
+		}
+	}
+
+	r, _ := http.NewRequest("GET", "http://localhost:8080/", nil)
+	emptyR, _ := http.NewRequest("GET", "http://localhost:8080/", nil)
+
+	// Get()
+	assertEqual(Get(r, key1), nil)
+
+	// Set()
+	Set(r, key1, "1")
+	assertEqual(Get(r, key1), "1")
+	assertEqual(len(data[r]), 1)
+
+	Set(r, key2, "2")
+	assertEqual(Get(r, key2), "2")
+	assertEqual(len(data[r]), 2)
+
+	//GetOk
+	value, ok := GetOk(r, key1)
+	assertEqual(value, "1")
+	assertEqual(ok, true)
+
+	value, ok = GetOk(r, "not exists")
+	assertEqual(value, nil)
+	assertEqual(ok, false)
+
+	Set(r, "nil value", nil)
+	value, ok = GetOk(r, "nil value")
+	assertEqual(value, nil)
+	assertEqual(ok, true)
+
+	// GetAll()
+	values := GetAll(r)
+	assertEqual(len(values), 3)
+
+	// GetAll() for empty request
+	values = GetAll(emptyR)
+	if values != nil {
+		t.Error("GetAll didn't return nil value for invalid request")
+	}
+
+	// GetAllOk()
+	values, ok = GetAllOk(r)
+	assertEqual(len(values), 3)
+	assertEqual(ok, true)
+
+	// GetAllOk() for empty request
+	values, ok = GetAllOk(emptyR)
+	assertEqual(len(values), 0)
+	assertEqual(ok, false)
+
+	// Delete()
+	Delete(r, key1)
+	assertEqual(Get(r, key1), nil)
+	assertEqual(len(data[r]), 2)
+
+	Delete(r, key2)
+	assertEqual(Get(r, key2), nil)
+	assertEqual(len(data[r]), 1)
+
+	// Clear()
+	Clear(r)
+	assertEqual(len(data), 0)
+}
+
+func parallelReader(r *http.Request, key string, iterations int, wait, done chan struct{}) {
+	<-wait
+	for i := 0; i < iterations; i++ {
+		Get(r, key)
+	}
+	done <- struct{}{}
+
+}
+
+func parallelWriter(r *http.Request, key, value string, iterations int, wait, done chan struct{}) {
+	<-wait
+	for i := 0; i < iterations; i++ {
+		Set(r, key, value)
+	}
+	done <- struct{}{}
+
+}
+
+func benchmarkMutex(b *testing.B, numReaders, numWriters, iterations int) {
+
+	b.StopTimer()
+	r, _ := http.NewRequest("GET", "http://localhost:8080/", nil)
+	done := make(chan struct{})
+	b.StartTimer()
+
+	for i := 0; i < b.N; i++ {
+		wait := make(chan struct{})
+
+		for i := 0; i < numReaders; i++ {
+			go parallelReader(r, "test", iterations, wait, done)
+		}
+
+		for i := 0; i < numWriters; i++ {
+			go parallelWriter(r, "test", "123", iterations, wait, done)
+		}
+
+		close(wait)
+
+		for i := 0; i < numReaders+numWriters; i++ {
+			<-done
+		}
+
+	}
+
+}
+
+func BenchmarkMutexSameReadWrite1(b *testing.B) {
+	benchmarkMutex(b, 1, 1, 32)
+}
+func BenchmarkMutexSameReadWrite2(b *testing.B) {
+	benchmarkMutex(b, 2, 2, 32)
+}
+func BenchmarkMutexSameReadWrite4(b *testing.B) {
+	benchmarkMutex(b, 4, 4, 32)
+}
+func BenchmarkMutex1(b *testing.B) {
+	benchmarkMutex(b, 2, 8, 32)
+}
+func BenchmarkMutex2(b *testing.B) {
+	benchmarkMutex(b, 16, 4, 64)
+}
+func BenchmarkMutex3(b *testing.B) {
+	benchmarkMutex(b, 1, 2, 128)
+}
+func BenchmarkMutex4(b *testing.B) {
+	benchmarkMutex(b, 128, 32, 256)
+}
+func BenchmarkMutex5(b *testing.B) {
+	benchmarkMutex(b, 1024, 2048, 64)
+}
+func BenchmarkMutex6(b *testing.B) {
+	benchmarkMutex(b, 2048, 1024, 512)
+}

+ 82 - 0
src/github.com/gorilla/context/doc.go

@@ -0,0 +1,82 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+/*
+Package context stores values shared during a request lifetime.
+
+For example, a router can set variables extracted from the URL and later
+application handlers can access those values, or it can be used to store
+sessions values to be saved at the end of a request. There are several
+others common uses.
+
+The idea was posted by Brad Fitzpatrick to the go-nuts mailing list:
+
+	http://groups.google.com/group/golang-nuts/msg/e2d679d303aa5d53
+
+Here's the basic usage: first define the keys that you will need. The key
+type is interface{} so a key can be of any type that supports equality.
+Here we define a key using a custom int type to avoid name collisions:
+
+	package foo
+
+	import (
+		"github.com/gorilla/context"
+	)
+
+	type key int
+
+	const MyKey key = 0
+
+Then set a variable. Variables are bound to an http.Request object, so you
+need a request instance to set a value:
+
+	context.Set(r, MyKey, "bar")
+
+The application can later access the variable using the same key you provided:
+
+	func MyHandler(w http.ResponseWriter, r *http.Request) {
+		// val is "bar".
+		val := context.Get(r, foo.MyKey)
+
+		// returns ("bar", true)
+		val, ok := context.GetOk(r, foo.MyKey)
+		// ...
+	}
+
+And that's all about the basic usage. We discuss some other ideas below.
+
+Any type can be stored in the context. To enforce a given type, make the key
+private and wrap Get() and Set() to accept and return values of a specific
+type:
+
+	type key int
+
+	const mykey key = 0
+
+	// GetMyKey returns a value for this package from the request values.
+	func GetMyKey(r *http.Request) SomeType {
+		if rv := context.Get(r, mykey); rv != nil {
+			return rv.(SomeType)
+		}
+		return nil
+	}
+
+	// SetMyKey sets a value for this package in the request values.
+	func SetMyKey(r *http.Request, val SomeType) {
+		context.Set(r, mykey, val)
+	}
+
+Variables must be cleared at the end of a request, to remove all values
+that were stored. This can be done in an http.Handler, after a request was
+served. Just call Clear() passing the request:
+
+	context.Clear(r)
+
+...or use ClearHandler(), which conveniently wraps an http.Handler to clear
+variables at the end of a request lifetime.
+
+The Routers from the packages gorilla/mux and gorilla/pat call Clear()
+so if you are using either of them you don't need to clear the context manually.
+*/
+package context

+ 0 - 0
src/github.com/gorilla/gorilla.zip


+ 20 - 0
src/github.com/gorilla/mux/.travis.yml

@@ -0,0 +1,20 @@
+language: go
+sudo: false
+
+matrix:
+  include:
+    - go: 1.2
+    - go: 1.3
+    - go: 1.4
+    - go: 1.5
+    - go: 1.6
+    - go: tip
+
+install:
+  - # Skip
+
+script:
+  - go get -t -v ./...
+  - diff -u <(echo -n) <(gofmt -d .)
+  - go tool vet .
+  - go test -v -race ./...

+ 27 - 0
src/github.com/gorilla/mux/LICENSE

@@ -0,0 +1,27 @@
+Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+	 * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+	 * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+	 * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 242 - 0
src/github.com/gorilla/mux/README.md

@@ -0,0 +1,242 @@
+mux
+===
+[![GoDoc](https://godoc.org/github.com/gorilla/mux?status.svg)](https://godoc.org/github.com/gorilla/mux)
+[![Build Status](https://travis-ci.org/gorilla/mux.svg?branch=master)](https://travis-ci.org/gorilla/mux)
+
+http://www.gorillatoolkit.org/pkg/mux
+
+Package `gorilla/mux` implements a request router and dispatcher.
+
+The name mux stands for "HTTP request multiplexer". Like the standard `http.ServeMux`, `mux.Router` matches incoming requests against a list of registered routes and calls a handler for the route that matches the URL or other conditions. The main features are:
+
+* Requests can be matched based on URL host, path, path prefix, schemes, header and query values, HTTP methods or using custom matchers.
+* URL hosts and paths can have variables with an optional regular expression.
+* Registered URLs can be built, or "reversed", which helps maintaining references to resources.
+* Routes can be used as subrouters: nested routes are only tested if the parent route matches. This is useful to define groups of routes that share common conditions like a host, a path prefix or other repeated attributes. As a bonus, this optimizes request matching.
+* It implements the `http.Handler` interface so it is compatible with the standard `http.ServeMux`.
+
+Let's start registering a couple of URL paths and handlers:
+
+```go
+func main() {
+	r := mux.NewRouter()
+	r.HandleFunc("/", HomeHandler)
+	r.HandleFunc("/products", ProductsHandler)
+	r.HandleFunc("/articles", ArticlesHandler)
+	http.Handle("/", r)
+}
+```
+
+Here we register three routes mapping URL paths to handlers. This is equivalent to how `http.HandleFunc()` works: if an incoming request URL matches one of the paths, the corresponding handler is called passing (`http.ResponseWriter`, `*http.Request`) as parameters.
+
+Paths can have variables. They are defined using the format `{name}` or `{name:pattern}`. If a regular expression pattern is not defined, the matched variable will be anything until the next slash. For example:
+
+```go
+r := mux.NewRouter()
+r.HandleFunc("/products/{key}", ProductHandler)
+r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
+r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
+```
+
+The names are used to create a map of route variables which can be retrieved calling `mux.Vars()`:
+
+```go
+vars := mux.Vars(request)
+category := vars["category"]
+```
+
+And this is all you need to know about the basic usage. More advanced options are explained below.
+
+Routes can also be restricted to a domain or subdomain. Just define a host pattern to be matched. They can also have variables:
+
+```go
+r := mux.NewRouter()
+// Only matches if domain is "www.example.com".
+r.Host("www.example.com")
+// Matches a dynamic subdomain.
+r.Host("{subdomain:[a-z]+}.domain.com")
+```
+
+There are several other matchers that can be added. To match path prefixes:
+
+```go
+r.PathPrefix("/products/")
+```
+
+...or HTTP methods:
+
+```go
+r.Methods("GET", "POST")
+```
+
+...or URL schemes:
+
+```go
+r.Schemes("https")
+```
+
+...or header values:
+
+```go
+r.Headers("X-Requested-With", "XMLHttpRequest")
+```
+
+...or query values:
+
+```go
+r.Queries("key", "value")
+```
+
+...or to use a custom matcher function:
+
+```go
+r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool {
+	return r.ProtoMajor == 0
+})
+```
+
+...and finally, it is possible to combine several matchers in a single route:
+
+```go
+r.HandleFunc("/products", ProductsHandler).
+  Host("www.example.com").
+  Methods("GET").
+  Schemes("http")
+```
+
+Setting the same matching conditions again and again can be boring, so we have a way to group several routes that share the same requirements. We call it "subrouting".
+
+For example, let's say we have several URLs that should only match when the host is `www.example.com`. Create a route for that host and get a "subrouter" from it:
+
+```go
+r := mux.NewRouter()
+s := r.Host("www.example.com").Subrouter()
+```
+
+Then register routes in the subrouter:
+
+```go
+s.HandleFunc("/products/", ProductsHandler)
+s.HandleFunc("/products/{key}", ProductHandler)
+s.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
+```
+
+The three URL paths we registered above will only be tested if the domain is `www.example.com`, because the subrouter is tested first. This is not only convenient, but also optimizes request matching. You can create subrouters combining any attribute matchers accepted by a route.
+
+Subrouters can be used to create domain or path "namespaces": you define subrouters in a central place and then parts of the app can register its paths relatively to a given subrouter.
+
+There's one more thing about subroutes. When a subrouter has a path prefix, the inner routes use it as base for their paths:
+
+```go
+r := mux.NewRouter()
+s := r.PathPrefix("/products").Subrouter()
+// "/products/"
+s.HandleFunc("/", ProductsHandler)
+// "/products/{key}/"
+s.HandleFunc("/{key}/", ProductHandler)
+// "/products/{key}/details"
+s.HandleFunc("/{key}/details", ProductDetailsHandler)
+```
+
+Now let's see how to build registered URLs.
+
+Routes can be named. All routes that define a name can have their URLs built, or "reversed". We define a name calling `Name()` on a route. For example:
+
+```go
+r := mux.NewRouter()
+r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
+  Name("article")
+```
+
+To build a URL, get the route and call the `URL()` method, passing a sequence of key/value pairs for the route variables. For the previous route, we would do:
+
+```go
+url, err := r.Get("article").URL("category", "technology", "id", "42")
+```
+
+...and the result will be a `url.URL` with the following path:
+
+```
+"/articles/technology/42"
+```
+
+This also works for host variables:
+
+```go
+r := mux.NewRouter()
+r.Host("{subdomain}.domain.com").
+  Path("/articles/{category}/{id:[0-9]+}").
+  HandlerFunc(ArticleHandler).
+  Name("article")
+
+// url.String() will be "http://news.domain.com/articles/technology/42"
+url, err := r.Get("article").URL("subdomain", "news",
+                                 "category", "technology",
+                                 "id", "42")
+```
+
+All variables defined in the route are required, and their values must conform to the corresponding patterns. These requirements guarantee that a generated URL will always match a registered route -- the only exception is for explicitly defined "build-only" routes which never match.
+
+Regex support also exists for matching Headers within a route. For example, we could do:
+
+```go
+r.HeadersRegexp("Content-Type", "application/(text|json)")
+```
+
+...and the route will match both requests with a Content-Type of `application/json` as well as `application/text`
+
+There's also a way to build only the URL host or path for a route: use the methods `URLHost()` or `URLPath()` instead. For the previous route, we would do:
+
+```go
+// "http://news.domain.com/"
+host, err := r.Get("article").URLHost("subdomain", "news")
+
+// "/articles/technology/42"
+path, err := r.Get("article").URLPath("category", "technology", "id", "42")
+```
+
+And if you use subrouters, host and path defined separately can be built as well:
+
+```go
+r := mux.NewRouter()
+s := r.Host("{subdomain}.domain.com").Subrouter()
+s.Path("/articles/{category}/{id:[0-9]+}").
+  HandlerFunc(ArticleHandler).
+  Name("article")
+
+// "http://news.domain.com/articles/technology/42"
+url, err := r.Get("article").URL("subdomain", "news",
+                                 "category", "technology",
+                                 "id", "42")
+```
+
+## Full Example
+
+Here's a complete, runnable example of a small `mux` based server:
+
+```go
+package main
+
+import (
+	"net/http"
+	"log"
+	"github.com/gorilla/mux"
+)
+
+func YourHandler(w http.ResponseWriter, r *http.Request) {
+	w.Write([]byte("Gorilla!\n"))
+}
+
+func main() {
+	r := mux.NewRouter()
+	// Routes consist of a path and a handler function.
+	r.HandleFunc("/", YourHandler)
+
+	// Bind to a port and pass our router in
+	log.Fatal(http.ListenAndServe(":8000", r))
+}
+```
+
+## License
+
+BSD licensed. See the LICENSE file for details.

+ 49 - 0
src/github.com/gorilla/mux/bench_test.go

@@ -0,0 +1,49 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package mux
+
+import (
+	"net/http"
+	"net/http/httptest"
+	"testing"
+)
+
+func BenchmarkMux(b *testing.B) {
+	router := new(Router)
+	handler := func(w http.ResponseWriter, r *http.Request) {}
+	router.HandleFunc("/v1/{v1}", handler)
+
+	request, _ := http.NewRequest("GET", "/v1/anything", nil)
+	for i := 0; i < b.N; i++ {
+		router.ServeHTTP(nil, request)
+	}
+}
+
+func BenchmarkMuxAlternativeInRegexp(b *testing.B) {
+	router := new(Router)
+	handler := func(w http.ResponseWriter, r *http.Request) {}
+	router.HandleFunc("/v1/{v1:(a|b)}", handler)
+
+	requestA, _ := http.NewRequest("GET", "/v1/a", nil)
+	requestB, _ := http.NewRequest("GET", "/v1/b", nil)
+	for i := 0; i < b.N; i++ {
+		router.ServeHTTP(nil, requestA)
+		router.ServeHTTP(nil, requestB)
+	}
+}
+
+func BenchmarkManyPathVariables(b *testing.B) {
+	router := new(Router)
+	handler := func(w http.ResponseWriter, r *http.Request) {}
+	router.HandleFunc("/v1/{v1}/{v2}/{v3}/{v4}/{v5}", handler)
+
+	matchingRequest, _ := http.NewRequest("GET", "/v1/1/2/3/4/5", nil)
+	notMatchingRequest, _ := http.NewRequest("GET", "/v1/1/2/3/4", nil)
+	recorder := httptest.NewRecorder()
+	for i := 0; i < b.N; i++ {
+		router.ServeHTTP(nil, matchingRequest)
+		router.ServeHTTP(recorder, notMatchingRequest)
+	}
+}

+ 26 - 0
src/github.com/gorilla/mux/context_gorilla.go

@@ -0,0 +1,26 @@
+// +build !go1.7
+
+package mux
+
+import (
+	"net/http"
+
+	"github.com/gorilla/context"
+)
+
+func contextGet(r *http.Request, key interface{}) interface{} {
+	return context.Get(r, key)
+}
+
+func contextSet(r *http.Request, key, val interface{}) *http.Request {
+	if val == nil {
+		return r
+	}
+
+	context.Set(r, key, val)
+	return r
+}
+
+func contextClear(r *http.Request) {
+	context.Clear(r)
+}

+ 40 - 0
src/github.com/gorilla/mux/context_gorilla_test.go

@@ -0,0 +1,40 @@
+// +build !go1.7
+
+package mux
+
+import (
+	"net/http"
+	"testing"
+
+	"github.com/gorilla/context"
+)
+
+// Tests that the context is cleared or not cleared properly depending on
+// the configuration of the router
+func TestKeepContext(t *testing.T) {
+	func1 := func(w http.ResponseWriter, r *http.Request) {}
+
+	r := NewRouter()
+	r.HandleFunc("/", func1).Name("func1")
+
+	req, _ := http.NewRequest("GET", "http://localhost/", nil)
+	context.Set(req, "t", 1)
+
+	res := new(http.ResponseWriter)
+	r.ServeHTTP(*res, req)
+
+	if _, ok := context.GetOk(req, "t"); ok {
+		t.Error("Context should have been cleared at end of request")
+	}
+
+	r.KeepContext = true
+
+	req, _ = http.NewRequest("GET", "http://localhost/", nil)
+	context.Set(req, "t", 1)
+
+	r.ServeHTTP(*res, req)
+	if _, ok := context.GetOk(req, "t"); !ok {
+		t.Error("Context should NOT have been cleared at end of request")
+	}
+
+}

+ 24 - 0
src/github.com/gorilla/mux/context_native.go

@@ -0,0 +1,24 @@
+// +build go1.7
+
+package mux
+
+import (
+	"context"
+	"net/http"
+)
+
+func contextGet(r *http.Request, key interface{}) interface{} {
+	return r.Context().Value(key)
+}
+
+func contextSet(r *http.Request, key, val interface{}) *http.Request {
+	if val == nil {
+		return r
+	}
+
+	return r.WithContext(context.WithValue(r.Context(), key, val))
+}
+
+func contextClear(r *http.Request) {
+	return
+}

+ 32 - 0
src/github.com/gorilla/mux/context_native_test.go

@@ -0,0 +1,32 @@
+// +build go1.7
+
+package mux
+
+import (
+	"context"
+	"net/http"
+	"testing"
+	"time"
+)
+
+func TestNativeContextMiddleware(t *testing.T) {
+	withTimeout := func(h http.Handler) http.Handler {
+		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+			ctx, cancel := context.WithTimeout(r.Context(), time.Minute)
+			defer cancel()
+			h.ServeHTTP(w, r.WithContext(ctx))
+		})
+	}
+
+	r := NewRouter()
+	r.Handle("/path/{foo}", withTimeout(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		vars := Vars(r)
+		if vars["foo"] != "bar" {
+			t.Fatal("Expected foo var to be set")
+		}
+	})))
+
+	rec := NewRecorder()
+	req := newRequest("GET", "/path/bar")
+	r.ServeHTTP(rec, req)
+}

+ 206 - 0
src/github.com/gorilla/mux/doc.go

@@ -0,0 +1,206 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+/*
+Package mux implements a request router and dispatcher.
+
+The name mux stands for "HTTP request multiplexer". Like the standard
+http.ServeMux, mux.Router matches incoming requests against a list of
+registered routes and calls a handler for the route that matches the URL
+or other conditions. The main features are:
+
+	* Requests can be matched based on URL host, path, path prefix, schemes,
+	  header and query values, HTTP methods or using custom matchers.
+	* URL hosts and paths can have variables with an optional regular
+	  expression.
+	* Registered URLs can be built, or "reversed", which helps maintaining
+	  references to resources.
+	* Routes can be used as subrouters: nested routes are only tested if the
+	  parent route matches. This is useful to define groups of routes that
+	  share common conditions like a host, a path prefix or other repeated
+	  attributes. As a bonus, this optimizes request matching.
+	* It implements the http.Handler interface so it is compatible with the
+	  standard http.ServeMux.
+
+Let's start registering a couple of URL paths and handlers:
+
+	func main() {
+		r := mux.NewRouter()
+		r.HandleFunc("/", HomeHandler)
+		r.HandleFunc("/products", ProductsHandler)
+		r.HandleFunc("/articles", ArticlesHandler)
+		http.Handle("/", r)
+	}
+
+Here we register three routes mapping URL paths to handlers. This is
+equivalent to how http.HandleFunc() works: if an incoming request URL matches
+one of the paths, the corresponding handler is called passing
+(http.ResponseWriter, *http.Request) as parameters.
+
+Paths can have variables. They are defined using the format {name} or
+{name:pattern}. If a regular expression pattern is not defined, the matched
+variable will be anything until the next slash. For example:
+
+	r := mux.NewRouter()
+	r.HandleFunc("/products/{key}", ProductHandler)
+	r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
+	r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
+
+The names are used to create a map of route variables which can be retrieved
+calling mux.Vars():
+
+	vars := mux.Vars(request)
+	category := vars["category"]
+
+And this is all you need to know about the basic usage. More advanced options
+are explained below.
+
+Routes can also be restricted to a domain or subdomain. Just define a host
+pattern to be matched. They can also have variables:
+
+	r := mux.NewRouter()
+	// Only matches if domain is "www.example.com".
+	r.Host("www.example.com")
+	// Matches a dynamic subdomain.
+	r.Host("{subdomain:[a-z]+}.domain.com")
+
+There are several other matchers that can be added. To match path prefixes:
+
+	r.PathPrefix("/products/")
+
+...or HTTP methods:
+
+	r.Methods("GET", "POST")
+
+...or URL schemes:
+
+	r.Schemes("https")
+
+...or header values:
+
+	r.Headers("X-Requested-With", "XMLHttpRequest")
+
+...or query values:
+
+	r.Queries("key", "value")
+
+...or to use a custom matcher function:
+
+	r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool {
+		return r.ProtoMajor == 0
+	})
+
+...and finally, it is possible to combine several matchers in a single route:
+
+	r.HandleFunc("/products", ProductsHandler).
+	  Host("www.example.com").
+	  Methods("GET").
+	  Schemes("http")
+
+Setting the same matching conditions again and again can be boring, so we have
+a way to group several routes that share the same requirements.
+We call it "subrouting".
+
+For example, let's say we have several URLs that should only match when the
+host is "www.example.com". Create a route for that host and get a "subrouter"
+from it:
+
+	r := mux.NewRouter()
+	s := r.Host("www.example.com").Subrouter()
+
+Then register routes in the subrouter:
+
+	s.HandleFunc("/products/", ProductsHandler)
+	s.HandleFunc("/products/{key}", ProductHandler)
+	s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)
+
+The three URL paths we registered above will only be tested if the domain is
+"www.example.com", because the subrouter is tested first. This is not
+only convenient, but also optimizes request matching. You can create
+subrouters combining any attribute matchers accepted by a route.
+
+Subrouters can be used to create domain or path "namespaces": you define
+subrouters in a central place and then parts of the app can register its
+paths relatively to a given subrouter.
+
+There's one more thing about subroutes. When a subrouter has a path prefix,
+the inner routes use it as base for their paths:
+
+	r := mux.NewRouter()
+	s := r.PathPrefix("/products").Subrouter()
+	// "/products/"
+	s.HandleFunc("/", ProductsHandler)
+	// "/products/{key}/"
+	s.HandleFunc("/{key}/", ProductHandler)
+	// "/products/{key}/details"
+	s.HandleFunc("/{key}/details", ProductDetailsHandler)
+
+Now let's see how to build registered URLs.
+
+Routes can be named. All routes that define a name can have their URLs built,
+or "reversed". We define a name calling Name() on a route. For example:
+
+	r := mux.NewRouter()
+	r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
+	  Name("article")
+
+To build a URL, get the route and call the URL() method, passing a sequence of
+key/value pairs for the route variables. For the previous route, we would do:
+
+	url, err := r.Get("article").URL("category", "technology", "id", "42")
+
+...and the result will be a url.URL with the following path:
+
+	"/articles/technology/42"
+
+This also works for host variables:
+
+	r := mux.NewRouter()
+	r.Host("{subdomain}.domain.com").
+	  Path("/articles/{category}/{id:[0-9]+}").
+	  HandlerFunc(ArticleHandler).
+	  Name("article")
+
+	// url.String() will be "http://news.domain.com/articles/technology/42"
+	url, err := r.Get("article").URL("subdomain", "news",
+	                                 "category", "technology",
+	                                 "id", "42")
+
+All variables defined in the route are required, and their values must
+conform to the corresponding patterns. These requirements guarantee that a
+generated URL will always match a registered route -- the only exception is
+for explicitly defined "build-only" routes which never match.
+
+Regex support also exists for matching Headers within a route. For example, we could do:
+
+	r.HeadersRegexp("Content-Type", "application/(text|json)")
+
+...and the route will match both requests with a Content-Type of `application/json` as well as
+`application/text`
+
+There's also a way to build only the URL host or path for a route:
+use the methods URLHost() or URLPath() instead. For the previous route,
+we would do:
+
+	// "http://news.domain.com/"
+	host, err := r.Get("article").URLHost("subdomain", "news")
+
+	// "/articles/technology/42"
+	path, err := r.Get("article").URLPath("category", "technology", "id", "42")
+
+And if you use subrouters, host and path defined separately can be built
+as well:
+
+	r := mux.NewRouter()
+	s := r.Host("{subdomain}.domain.com").Subrouter()
+	s.Path("/articles/{category}/{id:[0-9]+}").
+	  HandlerFunc(ArticleHandler).
+	  Name("article")
+
+	// "http://news.domain.com/articles/technology/42"
+	url, err := r.Get("article").URL("subdomain", "news",
+	                                 "category", "technology",
+	                                 "id", "42")
+*/
+package mux

+ 495 - 0
src/github.com/gorilla/mux/mux.go

@@ -0,0 +1,495 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package mux
+
+import (
+	"errors"
+	"fmt"
+	"net/http"
+	"path"
+	"regexp"
+)
+
+// NewRouter returns a new router instance.
+func NewRouter() *Router {
+	return &Router{namedRoutes: make(map[string]*Route), KeepContext: false}
+}
+
+// Router registers routes to be matched and dispatches a handler.
+//
+// It implements the http.Handler interface, so it can be registered to serve
+// requests:
+//
+//     var router = mux.NewRouter()
+//
+//     func main() {
+//         http.Handle("/", router)
+//     }
+//
+// Or, for Google App Engine, register it in a init() function:
+//
+//     func init() {
+//         http.Handle("/", router)
+//     }
+//
+// This will send all incoming requests to the router.
+type Router struct {
+	// Configurable Handler to be used when no route matches.
+	NotFoundHandler http.Handler
+	// Parent route, if this is a subrouter.
+	parent parentRoute
+	// Routes to be matched, in order.
+	routes []*Route
+	// Routes by name for URL building.
+	namedRoutes map[string]*Route
+	// See Router.StrictSlash(). This defines the flag for new routes.
+	strictSlash bool
+	// See Router.SkipClean(). This defines the flag for new routes.
+	skipClean bool
+	// If true, do not clear the request context after handling the request.
+	// This has no effect when go1.7+ is used, since the context is stored
+	// on the request itself.
+	KeepContext bool
+}
+
+// Match matches registered routes against the request.
+func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
+	for _, route := range r.routes {
+		if route.Match(req, match) {
+			return true
+		}
+	}
+
+	// Closest match for a router (includes sub-routers)
+	if r.NotFoundHandler != nil {
+		match.Handler = r.NotFoundHandler
+		return true
+	}
+	return false
+}
+
+// ServeHTTP dispatches the handler registered in the matched route.
+//
+// When there is a match, the route variables can be retrieved calling
+// mux.Vars(request).
+func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+	if !r.skipClean {
+		// Clean path to canonical form and redirect.
+		if p := cleanPath(req.URL.Path); p != req.URL.Path {
+
+			// Added 3 lines (Philip Schlump) - It was dropping the query string and #whatever from query.
+			// This matches with fix in go 1.2 r.c. 4 for same problem.  Go Issue:
+			// http://code.google.com/p/go/issues/detail?id=5252
+			url := *req.URL
+			url.Path = p
+			p = url.String()
+
+			w.Header().Set("Location", p)
+			w.WriteHeader(http.StatusMovedPermanently)
+			return
+		}
+	}
+	var match RouteMatch
+	var handler http.Handler
+	if r.Match(req, &match) {
+		handler = match.Handler
+		req = setVars(req, match.Vars)
+		req = setCurrentRoute(req, match.Route)
+	}
+	if handler == nil {
+		handler = http.NotFoundHandler()
+	}
+	if !r.KeepContext {
+		defer contextClear(req)
+	}
+	handler.ServeHTTP(w, req)
+}
+
+// Get returns a route registered with the given name.
+func (r *Router) Get(name string) *Route {
+	return r.getNamedRoutes()[name]
+}
+
+// GetRoute returns a route registered with the given name. This method
+// was renamed to Get() and remains here for backwards compatibility.
+func (r *Router) GetRoute(name string) *Route {
+	return r.getNamedRoutes()[name]
+}
+
+// StrictSlash defines the trailing slash behavior for new routes. The initial
+// value is false.
+//
+// When true, if the route path is "/path/", accessing "/path" will redirect
+// to the former and vice versa. In other words, your application will always
+// see the path as specified in the route.
+//
+// When false, if the route path is "/path", accessing "/path/" will not match
+// this route and vice versa.
+//
+// Special case: when a route sets a path prefix using the PathPrefix() method,
+// strict slash is ignored for that route because the redirect behavior can't
+// be determined from a prefix alone. However, any subrouters created from that
+// route inherit the original StrictSlash setting.
+func (r *Router) StrictSlash(value bool) *Router {
+	r.strictSlash = value
+	return r
+}
+
+// SkipClean defines the path cleaning behaviour for new routes. The initial
+// value is false. Users should be careful about which routes are not cleaned
+//
+// When true, if the route path is "/path//to", it will remain with the double
+// slash. This is helpful if you have a route like: /fetch/http://xkcd.com/534/
+//
+// When false, the path will be cleaned, so /fetch/http://xkcd.com/534/ will
+// become /fetch/http/xkcd.com/534
+func (r *Router) SkipClean(value bool) *Router {
+	r.skipClean = value
+	return r
+}
+
+// ----------------------------------------------------------------------------
+// parentRoute
+// ----------------------------------------------------------------------------
+
+// getNamedRoutes returns the map where named routes are registered.
+func (r *Router) getNamedRoutes() map[string]*Route {
+	if r.namedRoutes == nil {
+		if r.parent != nil {
+			r.namedRoutes = r.parent.getNamedRoutes()
+		} else {
+			r.namedRoutes = make(map[string]*Route)
+		}
+	}
+	return r.namedRoutes
+}
+
+// getRegexpGroup returns regexp definitions from the parent route, if any.
+func (r *Router) getRegexpGroup() *routeRegexpGroup {
+	if r.parent != nil {
+		return r.parent.getRegexpGroup()
+	}
+	return nil
+}
+
+func (r *Router) buildVars(m map[string]string) map[string]string {
+	if r.parent != nil {
+		m = r.parent.buildVars(m)
+	}
+	return m
+}
+
+// ----------------------------------------------------------------------------
+// Route factories
+// ----------------------------------------------------------------------------
+
+// NewRoute registers an empty route.
+func (r *Router) NewRoute() *Route {
+	route := &Route{parent: r, strictSlash: r.strictSlash, skipClean: r.skipClean}
+	r.routes = append(r.routes, route)
+	return route
+}
+
+// Handle registers a new route with a matcher for the URL path.
+// See Route.Path() and Route.Handler().
+func (r *Router) Handle(path string, handler http.Handler) *Route {
+	return r.NewRoute().Path(path).Handler(handler)
+}
+
+// HandleFunc registers a new route with a matcher for the URL path.
+// See Route.Path() and Route.HandlerFunc().
+func (r *Router) HandleFunc(path string, f func(http.ResponseWriter,
+	*http.Request)) *Route {
+	return r.NewRoute().Path(path).HandlerFunc(f)
+}
+
+// Headers registers a new route with a matcher for request header values.
+// See Route.Headers().
+func (r *Router) Headers(pairs ...string) *Route {
+	return r.NewRoute().Headers(pairs...)
+}
+
+// Host registers a new route with a matcher for the URL host.
+// See Route.Host().
+func (r *Router) Host(tpl string) *Route {
+	return r.NewRoute().Host(tpl)
+}
+
+// MatcherFunc registers a new route with a custom matcher function.
+// See Route.MatcherFunc().
+func (r *Router) MatcherFunc(f MatcherFunc) *Route {
+	return r.NewRoute().MatcherFunc(f)
+}
+
+// Methods registers a new route with a matcher for HTTP methods.
+// See Route.Methods().
+func (r *Router) Methods(methods ...string) *Route {
+	return r.NewRoute().Methods(methods...)
+}
+
+// Path registers a new route with a matcher for the URL path.
+// See Route.Path().
+func (r *Router) Path(tpl string) *Route {
+	return r.NewRoute().Path(tpl)
+}
+
+// PathPrefix registers a new route with a matcher for the URL path prefix.
+// See Route.PathPrefix().
+func (r *Router) PathPrefix(tpl string) *Route {
+	return r.NewRoute().PathPrefix(tpl)
+}
+
+// Queries registers a new route with a matcher for URL query values.
+// See Route.Queries().
+func (r *Router) Queries(pairs ...string) *Route {
+	return r.NewRoute().Queries(pairs...)
+}
+
+// Schemes registers a new route with a matcher for URL schemes.
+// See Route.Schemes().
+func (r *Router) Schemes(schemes ...string) *Route {
+	return r.NewRoute().Schemes(schemes...)
+}
+
+// BuildVarsFunc registers a new route with a custom function for modifying
+// route variables before building a URL.
+func (r *Router) BuildVarsFunc(f BuildVarsFunc) *Route {
+	return r.NewRoute().BuildVarsFunc(f)
+}
+
+// Walk walks the router and all its sub-routers, calling walkFn for each route
+// in the tree. The routes are walked in the order they were added. Sub-routers
+// are explored depth-first.
+func (r *Router) Walk(walkFn WalkFunc) error {
+	return r.walk(walkFn, []*Route{})
+}
+
+// SkipRouter is used as a return value from WalkFuncs to indicate that the
+// router that walk is about to descend down to should be skipped.
+var SkipRouter = errors.New("skip this router")
+
+// WalkFunc is the type of the function called for each route visited by Walk.
+// At every invocation, it is given the current route, and the current router,
+// and a list of ancestor routes that lead to the current route.
+type WalkFunc func(route *Route, router *Router, ancestors []*Route) error
+
+func (r *Router) walk(walkFn WalkFunc, ancestors []*Route) error {
+	for _, t := range r.routes {
+		if t.regexp == nil || t.regexp.path == nil || t.regexp.path.template == "" {
+			continue
+		}
+
+		err := walkFn(t, r, ancestors)
+		if err == SkipRouter {
+			continue
+		}
+		for _, sr := range t.matchers {
+			if h, ok := sr.(*Router); ok {
+				err := h.walk(walkFn, ancestors)
+				if err != nil {
+					return err
+				}
+			}
+		}
+		if h, ok := t.handler.(*Router); ok {
+			ancestors = append(ancestors, t)
+			err := h.walk(walkFn, ancestors)
+			if err != nil {
+				return err
+			}
+			ancestors = ancestors[:len(ancestors)-1]
+		}
+	}
+	return nil
+}
+
+// ----------------------------------------------------------------------------
+// Context
+// ----------------------------------------------------------------------------
+
+// RouteMatch stores information about a matched route.
+type RouteMatch struct {
+	Route   *Route
+	Handler http.Handler
+	Vars    map[string]string
+}
+
+type contextKey int
+
+const (
+	varsKey contextKey = iota
+	routeKey
+)
+
+// Vars returns the route variables for the current request, if any.
+func Vars(r *http.Request) map[string]string {
+	if rv := contextGet(r, varsKey); rv != nil {
+		return rv.(map[string]string)
+	}
+	return nil
+}
+
+// CurrentRoute returns the matched route for the current request, if any.
+// This only works when called inside the handler of the matched route
+// because the matched route is stored in the request context which is cleared
+// after the handler returns, unless the KeepContext option is set on the
+// Router.
+func CurrentRoute(r *http.Request) *Route {
+	if rv := contextGet(r, routeKey); rv != nil {
+		return rv.(*Route)
+	}
+	return nil
+}
+
+func setVars(r *http.Request, val interface{}) *http.Request {
+	return contextSet(r, varsKey, val)
+}
+
+func setCurrentRoute(r *http.Request, val interface{}) *http.Request {
+	return contextSet(r, routeKey, val)
+}
+
+// ----------------------------------------------------------------------------
+// Helpers
+// ----------------------------------------------------------------------------
+
+// cleanPath returns the canonical path for p, eliminating . and .. elements.
+// Borrowed from the net/http package.
+func cleanPath(p string) string {
+	if p == "" {
+		return "/"
+	}
+	if p[0] != '/' {
+		p = "/" + p
+	}
+	np := path.Clean(p)
+	// path.Clean removes trailing slash except for root;
+	// put the trailing slash back if necessary.
+	if p[len(p)-1] == '/' && np != "/" {
+		np += "/"
+	}
+
+	return np
+}
+
+// uniqueVars returns an error if two slices contain duplicated strings.
+func uniqueVars(s1, s2 []string) error {
+	for _, v1 := range s1 {
+		for _, v2 := range s2 {
+			if v1 == v2 {
+				return fmt.Errorf("mux: duplicated route variable %q", v2)
+			}
+		}
+	}
+	return nil
+}
+
+// checkPairs returns the count of strings passed in, and an error if
+// the count is not an even number.
+func checkPairs(pairs ...string) (int, error) {
+	length := len(pairs)
+	if length%2 != 0 {
+		return length, fmt.Errorf(
+			"mux: number of parameters must be multiple of 2, got %v", pairs)
+	}
+	return length, nil
+}
+
+// mapFromPairsToString converts variadic string parameters to a
+// string to string map.
+func mapFromPairsToString(pairs ...string) (map[string]string, error) {
+	length, err := checkPairs(pairs...)
+	if err != nil {
+		return nil, err
+	}
+	m := make(map[string]string, length/2)
+	for i := 0; i < length; i += 2 {
+		m[pairs[i]] = pairs[i+1]
+	}
+	return m, nil
+}
+
+// mapFromPairsToRegex converts variadic string paramers to a
+// string to regex map.
+func mapFromPairsToRegex(pairs ...string) (map[string]*regexp.Regexp, error) {
+	length, err := checkPairs(pairs...)
+	if err != nil {
+		return nil, err
+	}
+	m := make(map[string]*regexp.Regexp, length/2)
+	for i := 0; i < length; i += 2 {
+		regex, err := regexp.Compile(pairs[i+1])
+		if err != nil {
+			return nil, err
+		}
+		m[pairs[i]] = regex
+	}
+	return m, nil
+}
+
+// matchInArray returns true if the given string value is in the array.
+func matchInArray(arr []string, value string) bool {
+	for _, v := range arr {
+		if v == value {
+			return true
+		}
+	}
+	return false
+}
+
+// matchMapWithString returns true if the given key/value pairs exist in a given map.
+func matchMapWithString(toCheck map[string]string, toMatch map[string][]string, canonicalKey bool) bool {
+	for k, v := range toCheck {
+		// Check if key exists.
+		if canonicalKey {
+			k = http.CanonicalHeaderKey(k)
+		}
+		if values := toMatch[k]; values == nil {
+			return false
+		} else if v != "" {
+			// If value was defined as an empty string we only check that the
+			// key exists. Otherwise we also check for equality.
+			valueExists := false
+			for _, value := range values {
+				if v == value {
+					valueExists = true
+					break
+				}
+			}
+			if !valueExists {
+				return false
+			}
+		}
+	}
+	return true
+}
+
+// matchMapWithRegex returns true if the given key/value pairs exist in a given map compiled against
+// the given regex
+func matchMapWithRegex(toCheck map[string]*regexp.Regexp, toMatch map[string][]string, canonicalKey bool) bool {
+	for k, v := range toCheck {
+		// Check if key exists.
+		if canonicalKey {
+			k = http.CanonicalHeaderKey(k)
+		}
+		if values := toMatch[k]; values == nil {
+			return false
+		} else if v != nil {
+			// If value was defined as an empty string we only check that the
+			// key exists. Otherwise we also check for equality.
+			valueExists := false
+			for _, value := range values {
+				if v.MatchString(value) {
+					valueExists = true
+					break
+				}
+			}
+			if !valueExists {
+				return false
+			}
+		}
+	}
+	return true
+}

+ 1439 - 0
src/github.com/gorilla/mux/mux_test.go

@@ -0,0 +1,1439 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package mux
+
+import (
+	"fmt"
+	"net/http"
+	"strings"
+	"testing"
+)
+
+func (r *Route) GoString() string {
+	matchers := make([]string, len(r.matchers))
+	for i, m := range r.matchers {
+		matchers[i] = fmt.Sprintf("%#v", m)
+	}
+	return fmt.Sprintf("&Route{matchers:[]matcher{%s}}", strings.Join(matchers, ", "))
+}
+
+func (r *routeRegexp) GoString() string {
+	return fmt.Sprintf("&routeRegexp{template: %q, matchHost: %t, matchQuery: %t, strictSlash: %t, regexp: regexp.MustCompile(%q), reverse: %q, varsN: %v, varsR: %v", r.template, r.matchHost, r.matchQuery, r.strictSlash, r.regexp.String(), r.reverse, r.varsN, r.varsR)
+}
+
+type routeTest struct {
+	title          string            // title of the test
+	route          *Route            // the route being tested
+	request        *http.Request     // a request to test the route
+	vars           map[string]string // the expected vars of the match
+	host           string            // the expected host of the match
+	path           string            // the expected path of the match
+	path_template  string            // the expected path template to match
+	host_template  string            // the expected host template to match
+	shouldMatch    bool              // whether the request is expected to match the route at all
+	shouldRedirect bool              // whether the request should result in a redirect
+}
+
+func TestHost(t *testing.T) {
+	// newRequestHost a new request with a method, url, and host header
+	newRequestHost := func(method, url, host string) *http.Request {
+		req, err := http.NewRequest(method, url, nil)
+		if err != nil {
+			panic(err)
+		}
+		req.Host = host
+		return req
+	}
+
+	tests := []routeTest{
+		{
+			title:       "Host route match",
+			route:       new(Route).Host("aaa.bbb.ccc"),
+			request:     newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
+			vars:        map[string]string{},
+			host:        "aaa.bbb.ccc",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "Host route, wrong host in request URL",
+			route:       new(Route).Host("aaa.bbb.ccc"),
+			request:     newRequest("GET", "http://aaa.222.ccc/111/222/333"),
+			vars:        map[string]string{},
+			host:        "aaa.bbb.ccc",
+			path:        "",
+			shouldMatch: false,
+		},
+		{
+			title:       "Host route with port, match",
+			route:       new(Route).Host("aaa.bbb.ccc:1234"),
+			request:     newRequest("GET", "http://aaa.bbb.ccc:1234/111/222/333"),
+			vars:        map[string]string{},
+			host:        "aaa.bbb.ccc:1234",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "Host route with port, wrong port in request URL",
+			route:       new(Route).Host("aaa.bbb.ccc:1234"),
+			request:     newRequest("GET", "http://aaa.bbb.ccc:9999/111/222/333"),
+			vars:        map[string]string{},
+			host:        "aaa.bbb.ccc:1234",
+			path:        "",
+			shouldMatch: false,
+		},
+		{
+			title:       "Host route, match with host in request header",
+			route:       new(Route).Host("aaa.bbb.ccc"),
+			request:     newRequestHost("GET", "/111/222/333", "aaa.bbb.ccc"),
+			vars:        map[string]string{},
+			host:        "aaa.bbb.ccc",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "Host route, wrong host in request header",
+			route:       new(Route).Host("aaa.bbb.ccc"),
+			request:     newRequestHost("GET", "/111/222/333", "aaa.222.ccc"),
+			vars:        map[string]string{},
+			host:        "aaa.bbb.ccc",
+			path:        "",
+			shouldMatch: false,
+		},
+		// BUG {new(Route).Host("aaa.bbb.ccc:1234"), newRequestHost("GET", "/111/222/333", "aaa.bbb.ccc:1234"), map[string]string{}, "aaa.bbb.ccc:1234", "", true},
+		{
+			title:       "Host route with port, wrong host in request header",
+			route:       new(Route).Host("aaa.bbb.ccc:1234"),
+			request:     newRequestHost("GET", "/111/222/333", "aaa.bbb.ccc:9999"),
+			vars:        map[string]string{},
+			host:        "aaa.bbb.ccc:1234",
+			path:        "",
+			shouldMatch: false,
+		},
+		{
+			title:         "Host route with pattern, match",
+			route:         new(Route).Host("aaa.{v1:[a-z]{3}}.ccc"),
+			request:       newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
+			vars:          map[string]string{"v1": "bbb"},
+			host:          "aaa.bbb.ccc",
+			path:          "",
+			host_template: `aaa.{v1:[a-z]{3}}.ccc`,
+			shouldMatch:   true,
+		},
+		{
+			title:         "Host route with pattern, additional capturing group, match",
+			route:         new(Route).Host("aaa.{v1:[a-z]{2}(b|c)}.ccc"),
+			request:       newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
+			vars:          map[string]string{"v1": "bbb"},
+			host:          "aaa.bbb.ccc",
+			path:          "",
+			host_template: `aaa.{v1:[a-z]{2}(b|c)}.ccc`,
+			shouldMatch:   true,
+		},
+		{
+			title:         "Host route with pattern, wrong host in request URL",
+			route:         new(Route).Host("aaa.{v1:[a-z]{3}}.ccc"),
+			request:       newRequest("GET", "http://aaa.222.ccc/111/222/333"),
+			vars:          map[string]string{"v1": "bbb"},
+			host:          "aaa.bbb.ccc",
+			path:          "",
+			host_template: `aaa.{v1:[a-z]{3}}.ccc`,
+			shouldMatch:   false,
+		},
+		{
+			title:         "Host route with multiple patterns, match",
+			route:         new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}"),
+			request:       newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
+			vars:          map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc"},
+			host:          "aaa.bbb.ccc",
+			path:          "",
+			host_template: `{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}`,
+			shouldMatch:   true,
+		},
+		{
+			title:         "Host route with multiple patterns, wrong host in request URL",
+			route:         new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}"),
+			request:       newRequest("GET", "http://aaa.222.ccc/111/222/333"),
+			vars:          map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc"},
+			host:          "aaa.bbb.ccc",
+			path:          "",
+			host_template: `{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}`,
+			shouldMatch:   false,
+		},
+		{
+			title:         "Host route with hyphenated name and pattern, match",
+			route:         new(Route).Host("aaa.{v-1:[a-z]{3}}.ccc"),
+			request:       newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
+			vars:          map[string]string{"v-1": "bbb"},
+			host:          "aaa.bbb.ccc",
+			path:          "",
+			host_template: `aaa.{v-1:[a-z]{3}}.ccc`,
+			shouldMatch:   true,
+		},
+		{
+			title:         "Host route with hyphenated name and pattern, additional capturing group, match",
+			route:         new(Route).Host("aaa.{v-1:[a-z]{2}(b|c)}.ccc"),
+			request:       newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
+			vars:          map[string]string{"v-1": "bbb"},
+			host:          "aaa.bbb.ccc",
+			path:          "",
+			host_template: `aaa.{v-1:[a-z]{2}(b|c)}.ccc`,
+			shouldMatch:   true,
+		},
+		{
+			title:         "Host route with multiple hyphenated names and patterns, match",
+			route:         new(Route).Host("{v-1:[a-z]{3}}.{v-2:[a-z]{3}}.{v-3:[a-z]{3}}"),
+			request:       newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
+			vars:          map[string]string{"v-1": "aaa", "v-2": "bbb", "v-3": "ccc"},
+			host:          "aaa.bbb.ccc",
+			path:          "",
+			host_template: `{v-1:[a-z]{3}}.{v-2:[a-z]{3}}.{v-3:[a-z]{3}}`,
+			shouldMatch:   true,
+		},
+		{
+			title:         "Path route with single pattern with pipe, match",
+			route:         new(Route).Path("/{category:a|b/c}"),
+			request:       newRequest("GET", "http://localhost/a"),
+			vars:          map[string]string{"category": "a"},
+			host:          "",
+			path:          "/a",
+			path_template: `/{category:a|b/c}`,
+			shouldMatch:   true,
+		},
+		{
+			title:         "Path route with single pattern with pipe, match",
+			route:         new(Route).Path("/{category:a|b/c}"),
+			request:       newRequest("GET", "http://localhost/b/c"),
+			vars:          map[string]string{"category": "b/c"},
+			host:          "",
+			path:          "/b/c",
+			path_template: `/{category:a|b/c}`,
+			shouldMatch:   true,
+		},
+		{
+			title:         "Path route with multiple patterns with pipe, match",
+			route:         new(Route).Path("/{category:a|b/c}/{product}/{id:[0-9]+}"),
+			request:       newRequest("GET", "http://localhost/a/product_name/1"),
+			vars:          map[string]string{"category": "a", "product": "product_name", "id": "1"},
+			host:          "",
+			path:          "/a/product_name/1",
+			path_template: `/{category:a|b/c}/{product}/{id:[0-9]+}`,
+			shouldMatch:   true,
+		},
+		{
+			title:         "Path route with multiple patterns with pipe, match",
+			route:         new(Route).Path("/{category:a|b/c}/{product}/{id:[0-9]+}"),
+			request:       newRequest("GET", "http://localhost/b/c/product_name/1"),
+			vars:          map[string]string{"category": "b/c", "product": "product_name", "id": "1"},
+			host:          "",
+			path:          "/b/c/product_name/1",
+			path_template: `/{category:a|b/c}/{product}/{id:[0-9]+}`,
+			shouldMatch:   true,
+		},
+	}
+	for _, test := range tests {
+		testRoute(t, test)
+		testTemplate(t, test)
+	}
+}
+
+func TestPath(t *testing.T) {
+	tests := []routeTest{
+		{
+			title:       "Path route, match",
+			route:       new(Route).Path("/111/222/333"),
+			request:     newRequest("GET", "http://localhost/111/222/333"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "/111/222/333",
+			shouldMatch: true,
+		},
+		{
+			title:       "Path route, match with trailing slash in request and path",
+			route:       new(Route).Path("/111/"),
+			request:     newRequest("GET", "http://localhost/111/"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "/111/",
+			shouldMatch: true,
+		},
+		{
+			title:         "Path route, do not match with trailing slash in path",
+			route:         new(Route).Path("/111/"),
+			request:       newRequest("GET", "http://localhost/111"),
+			vars:          map[string]string{},
+			host:          "",
+			path:          "/111",
+			path_template: `/111/`,
+			shouldMatch:   false,
+		},
+		{
+			title:         "Path route, do not match with trailing slash in request",
+			route:         new(Route).Path("/111"),
+			request:       newRequest("GET", "http://localhost/111/"),
+			vars:          map[string]string{},
+			host:          "",
+			path:          "/111/",
+			path_template: `/111`,
+			shouldMatch:   false,
+		},
+		{
+			title:       "Path route, wrong path in request in request URL",
+			route:       new(Route).Path("/111/222/333"),
+			request:     newRequest("GET", "http://localhost/1/2/3"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "/111/222/333",
+			shouldMatch: false,
+		},
+		{
+			title:         "Path route with pattern, match",
+			route:         new(Route).Path("/111/{v1:[0-9]{3}}/333"),
+			request:       newRequest("GET", "http://localhost/111/222/333"),
+			vars:          map[string]string{"v1": "222"},
+			host:          "",
+			path:          "/111/222/333",
+			path_template: `/111/{v1:[0-9]{3}}/333`,
+			shouldMatch:   true,
+		},
+		{
+			title:         "Path route with pattern, URL in request does not match",
+			route:         new(Route).Path("/111/{v1:[0-9]{3}}/333"),
+			request:       newRequest("GET", "http://localhost/111/aaa/333"),
+			vars:          map[string]string{"v1": "222"},
+			host:          "",
+			path:          "/111/222/333",
+			path_template: `/111/{v1:[0-9]{3}}/333`,
+			shouldMatch:   false,
+		},
+		{
+			title:         "Path route with multiple patterns, match",
+			route:         new(Route).Path("/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}"),
+			request:       newRequest("GET", "http://localhost/111/222/333"),
+			vars:          map[string]string{"v1": "111", "v2": "222", "v3": "333"},
+			host:          "",
+			path:          "/111/222/333",
+			path_template: `/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}`,
+			shouldMatch:   true,
+		},
+		{
+			title:         "Path route with multiple patterns, URL in request does not match",
+			route:         new(Route).Path("/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}"),
+			request:       newRequest("GET", "http://localhost/111/aaa/333"),
+			vars:          map[string]string{"v1": "111", "v2": "222", "v3": "333"},
+			host:          "",
+			path:          "/111/222/333",
+			path_template: `/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}`,
+			shouldMatch:   false,
+		},
+		{
+			title:         "Path route with multiple patterns with pipe, match",
+			route:         new(Route).Path("/{category:a|(b/c)}/{product}/{id:[0-9]+}"),
+			request:       newRequest("GET", "http://localhost/a/product_name/1"),
+			vars:          map[string]string{"category": "a", "product": "product_name", "id": "1"},
+			host:          "",
+			path:          "/a/product_name/1",
+			path_template: `/{category:a|(b/c)}/{product}/{id:[0-9]+}`,
+			shouldMatch:   true,
+		},
+		{
+			title:         "Path route with hyphenated name and pattern, match",
+			route:         new(Route).Path("/111/{v-1:[0-9]{3}}/333"),
+			request:       newRequest("GET", "http://localhost/111/222/333"),
+			vars:          map[string]string{"v-1": "222"},
+			host:          "",
+			path:          "/111/222/333",
+			path_template: `/111/{v-1:[0-9]{3}}/333`,
+			shouldMatch:   true,
+		},
+		{
+			title:         "Path route with multiple hyphenated names and patterns, match",
+			route:         new(Route).Path("/{v-1:[0-9]{3}}/{v-2:[0-9]{3}}/{v-3:[0-9]{3}}"),
+			request:       newRequest("GET", "http://localhost/111/222/333"),
+			vars:          map[string]string{"v-1": "111", "v-2": "222", "v-3": "333"},
+			host:          "",
+			path:          "/111/222/333",
+			path_template: `/{v-1:[0-9]{3}}/{v-2:[0-9]{3}}/{v-3:[0-9]{3}}`,
+			shouldMatch:   true,
+		},
+		{
+			title:         "Path route with multiple hyphenated names and patterns with pipe, match",
+			route:         new(Route).Path("/{product-category:a|(b/c)}/{product-name}/{product-id:[0-9]+}"),
+			request:       newRequest("GET", "http://localhost/a/product_name/1"),
+			vars:          map[string]string{"product-category": "a", "product-name": "product_name", "product-id": "1"},
+			host:          "",
+			path:          "/a/product_name/1",
+			path_template: `/{product-category:a|(b/c)}/{product-name}/{product-id:[0-9]+}`,
+			shouldMatch:   true,
+		},
+		{
+			title:         "Path route with multiple hyphenated names and patterns with pipe and case insensitive, match",
+			route:         new(Route).Path("/{type:(?i:daily|mini|variety)}-{date:\\d{4,4}-\\d{2,2}-\\d{2,2}}"),
+			request:       newRequest("GET", "http://localhost/daily-2016-01-01"),
+			vars:          map[string]string{"type": "daily", "date": "2016-01-01"},
+			host:          "",
+			path:          "/daily-2016-01-01",
+			path_template: `/{type:(?i:daily|mini|variety)}-{date:\d{4,4}-\d{2,2}-\d{2,2}}`,
+			shouldMatch:   true,
+		},
+	}
+
+	for _, test := range tests {
+		testRoute(t, test)
+		testTemplate(t, test)
+	}
+}
+
+func TestPathPrefix(t *testing.T) {
+	tests := []routeTest{
+		{
+			title:       "PathPrefix route, match",
+			route:       new(Route).PathPrefix("/111"),
+			request:     newRequest("GET", "http://localhost/111/222/333"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "/111",
+			shouldMatch: true,
+		},
+		{
+			title:       "PathPrefix route, match substring",
+			route:       new(Route).PathPrefix("/1"),
+			request:     newRequest("GET", "http://localhost/111/222/333"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "/1",
+			shouldMatch: true,
+		},
+		{
+			title:       "PathPrefix route, URL prefix in request does not match",
+			route:       new(Route).PathPrefix("/111"),
+			request:     newRequest("GET", "http://localhost/1/2/3"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "/111",
+			shouldMatch: false,
+		},
+		{
+			title:         "PathPrefix route with pattern, match",
+			route:         new(Route).PathPrefix("/111/{v1:[0-9]{3}}"),
+			request:       newRequest("GET", "http://localhost/111/222/333"),
+			vars:          map[string]string{"v1": "222"},
+			host:          "",
+			path:          "/111/222",
+			path_template: `/111/{v1:[0-9]{3}}`,
+			shouldMatch:   true,
+		},
+		{
+			title:         "PathPrefix route with pattern, URL prefix in request does not match",
+			route:         new(Route).PathPrefix("/111/{v1:[0-9]{3}}"),
+			request:       newRequest("GET", "http://localhost/111/aaa/333"),
+			vars:          map[string]string{"v1": "222"},
+			host:          "",
+			path:          "/111/222",
+			path_template: `/111/{v1:[0-9]{3}}`,
+			shouldMatch:   false,
+		},
+		{
+			title:         "PathPrefix route with multiple patterns, match",
+			route:         new(Route).PathPrefix("/{v1:[0-9]{3}}/{v2:[0-9]{3}}"),
+			request:       newRequest("GET", "http://localhost/111/222/333"),
+			vars:          map[string]string{"v1": "111", "v2": "222"},
+			host:          "",
+			path:          "/111/222",
+			path_template: `/{v1:[0-9]{3}}/{v2:[0-9]{3}}`,
+			shouldMatch:   true,
+		},
+		{
+			title:         "PathPrefix route with multiple patterns, URL prefix in request does not match",
+			route:         new(Route).PathPrefix("/{v1:[0-9]{3}}/{v2:[0-9]{3}}"),
+			request:       newRequest("GET", "http://localhost/111/aaa/333"),
+			vars:          map[string]string{"v1": "111", "v2": "222"},
+			host:          "",
+			path:          "/111/222",
+			path_template: `/{v1:[0-9]{3}}/{v2:[0-9]{3}}`,
+			shouldMatch:   false,
+		},
+	}
+
+	for _, test := range tests {
+		testRoute(t, test)
+		testTemplate(t, test)
+	}
+}
+
+func TestHostPath(t *testing.T) {
+	tests := []routeTest{
+		{
+			title:         "Host and Path route, match",
+			route:         new(Route).Host("aaa.bbb.ccc").Path("/111/222/333"),
+			request:       newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
+			vars:          map[string]string{},
+			host:          "",
+			path:          "",
+			path_template: `/111/222/333`,
+			host_template: `aaa.bbb.ccc`,
+			shouldMatch:   true,
+		},
+		{
+			title:         "Host and Path route, wrong host in request URL",
+			route:         new(Route).Host("aaa.bbb.ccc").Path("/111/222/333"),
+			request:       newRequest("GET", "http://aaa.222.ccc/111/222/333"),
+			vars:          map[string]string{},
+			host:          "",
+			path:          "",
+			path_template: `/111/222/333`,
+			host_template: `aaa.bbb.ccc`,
+			shouldMatch:   false,
+		},
+		{
+			title:         "Host and Path route with pattern, match",
+			route:         new(Route).Host("aaa.{v1:[a-z]{3}}.ccc").Path("/111/{v2:[0-9]{3}}/333"),
+			request:       newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
+			vars:          map[string]string{"v1": "bbb", "v2": "222"},
+			host:          "aaa.bbb.ccc",
+			path:          "/111/222/333",
+			path_template: `/111/{v2:[0-9]{3}}/333`,
+			host_template: `aaa.{v1:[a-z]{3}}.ccc`,
+			shouldMatch:   true,
+		},
+		{
+			title:         "Host and Path route with pattern, URL in request does not match",
+			route:         new(Route).Host("aaa.{v1:[a-z]{3}}.ccc").Path("/111/{v2:[0-9]{3}}/333"),
+			request:       newRequest("GET", "http://aaa.222.ccc/111/222/333"),
+			vars:          map[string]string{"v1": "bbb", "v2": "222"},
+			host:          "aaa.bbb.ccc",
+			path:          "/111/222/333",
+			path_template: `/111/{v2:[0-9]{3}}/333`,
+			host_template: `aaa.{v1:[a-z]{3}}.ccc`,
+			shouldMatch:   false,
+		},
+		{
+			title:         "Host and Path route with multiple patterns, match",
+			route:         new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}").Path("/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}"),
+			request:       newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
+			vars:          map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc", "v4": "111", "v5": "222", "v6": "333"},
+			host:          "aaa.bbb.ccc",
+			path:          "/111/222/333",
+			path_template: `/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}`,
+			host_template: `{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}`,
+			shouldMatch:   true,
+		},
+		{
+			title:         "Host and Path route with multiple patterns, URL in request does not match",
+			route:         new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}").Path("/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}"),
+			request:       newRequest("GET", "http://aaa.222.ccc/111/222/333"),
+			vars:          map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc", "v4": "111", "v5": "222", "v6": "333"},
+			host:          "aaa.bbb.ccc",
+			path:          "/111/222/333",
+			path_template: `/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}`,
+			host_template: `{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}`,
+			shouldMatch:   false,
+		},
+	}
+
+	for _, test := range tests {
+		testRoute(t, test)
+		testTemplate(t, test)
+	}
+}
+
+func TestHeaders(t *testing.T) {
+	// newRequestHeaders creates a new request with a method, url, and headers
+	newRequestHeaders := func(method, url string, headers map[string]string) *http.Request {
+		req, err := http.NewRequest(method, url, nil)
+		if err != nil {
+			panic(err)
+		}
+		for k, v := range headers {
+			req.Header.Add(k, v)
+		}
+		return req
+	}
+
+	tests := []routeTest{
+		{
+			title:       "Headers route, match",
+			route:       new(Route).Headers("foo", "bar", "baz", "ding"),
+			request:     newRequestHeaders("GET", "http://localhost", map[string]string{"foo": "bar", "baz": "ding"}),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "Headers route, bad header values",
+			route:       new(Route).Headers("foo", "bar", "baz", "ding"),
+			request:     newRequestHeaders("GET", "http://localhost", map[string]string{"foo": "bar", "baz": "dong"}),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: false,
+		},
+		{
+			title:       "Headers route, regex header values to match",
+			route:       new(Route).Headers("foo", "ba[zr]"),
+			request:     newRequestHeaders("GET", "http://localhost", map[string]string{"foo": "bar"}),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: false,
+		},
+		{
+			title:       "Headers route, regex header values to match",
+			route:       new(Route).HeadersRegexp("foo", "ba[zr]"),
+			request:     newRequestHeaders("GET", "http://localhost", map[string]string{"foo": "baz"}),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: true,
+		},
+	}
+
+	for _, test := range tests {
+		testRoute(t, test)
+		testTemplate(t, test)
+	}
+
+}
+
+func TestMethods(t *testing.T) {
+	tests := []routeTest{
+		{
+			title:       "Methods route, match GET",
+			route:       new(Route).Methods("GET", "POST"),
+			request:     newRequest("GET", "http://localhost"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "Methods route, match POST",
+			route:       new(Route).Methods("GET", "POST"),
+			request:     newRequest("POST", "http://localhost"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "Methods route, bad method",
+			route:       new(Route).Methods("GET", "POST"),
+			request:     newRequest("PUT", "http://localhost"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: false,
+		},
+	}
+
+	for _, test := range tests {
+		testRoute(t, test)
+		testTemplate(t, test)
+	}
+}
+
+func TestQueries(t *testing.T) {
+	tests := []routeTest{
+		{
+			title:       "Queries route, match",
+			route:       new(Route).Queries("foo", "bar", "baz", "ding"),
+			request:     newRequest("GET", "http://localhost?foo=bar&baz=ding"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:         "Queries route, match with a query string",
+			route:         new(Route).Host("www.example.com").Path("/api").Queries("foo", "bar", "baz", "ding"),
+			request:       newRequest("GET", "http://www.example.com/api?foo=bar&baz=ding"),
+			vars:          map[string]string{},
+			host:          "",
+			path:          "",
+			path_template: `/api`,
+			host_template: `www.example.com`,
+			shouldMatch:   true,
+		},
+		{
+			title:         "Queries route, match with a query string out of order",
+			route:         new(Route).Host("www.example.com").Path("/api").Queries("foo", "bar", "baz", "ding"),
+			request:       newRequest("GET", "http://www.example.com/api?baz=ding&foo=bar"),
+			vars:          map[string]string{},
+			host:          "",
+			path:          "",
+			path_template: `/api`,
+			host_template: `www.example.com`,
+			shouldMatch:   true,
+		},
+		{
+			title:       "Queries route, bad query",
+			route:       new(Route).Queries("foo", "bar", "baz", "ding"),
+			request:     newRequest("GET", "http://localhost?foo=bar&baz=dong"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: false,
+		},
+		{
+			title:       "Queries route with pattern, match",
+			route:       new(Route).Queries("foo", "{v1}"),
+			request:     newRequest("GET", "http://localhost?foo=bar"),
+			vars:        map[string]string{"v1": "bar"},
+			host:        "",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "Queries route with multiple patterns, match",
+			route:       new(Route).Queries("foo", "{v1}", "baz", "{v2}"),
+			request:     newRequest("GET", "http://localhost?foo=bar&baz=ding"),
+			vars:        map[string]string{"v1": "bar", "v2": "ding"},
+			host:        "",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "Queries route with regexp pattern, match",
+			route:       new(Route).Queries("foo", "{v1:[0-9]+}"),
+			request:     newRequest("GET", "http://localhost?foo=10"),
+			vars:        map[string]string{"v1": "10"},
+			host:        "",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "Queries route with regexp pattern, regexp does not match",
+			route:       new(Route).Queries("foo", "{v1:[0-9]+}"),
+			request:     newRequest("GET", "http://localhost?foo=a"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: false,
+		},
+		{
+			title:       "Queries route with regexp pattern with quantifier, match",
+			route:       new(Route).Queries("foo", "{v1:[0-9]{1}}"),
+			request:     newRequest("GET", "http://localhost?foo=1"),
+			vars:        map[string]string{"v1": "1"},
+			host:        "",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "Queries route with regexp pattern with quantifier, additional variable in query string, match",
+			route:       new(Route).Queries("foo", "{v1:[0-9]{1}}"),
+			request:     newRequest("GET", "http://localhost?bar=2&foo=1"),
+			vars:        map[string]string{"v1": "1"},
+			host:        "",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "Queries route with regexp pattern with quantifier, regexp does not match",
+			route:       new(Route).Queries("foo", "{v1:[0-9]{1}}"),
+			request:     newRequest("GET", "http://localhost?foo=12"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: false,
+		},
+		{
+			title:       "Queries route with regexp pattern with quantifier, additional capturing group",
+			route:       new(Route).Queries("foo", "{v1:[0-9]{1}(a|b)}"),
+			request:     newRequest("GET", "http://localhost?foo=1a"),
+			vars:        map[string]string{"v1": "1a"},
+			host:        "",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "Queries route with regexp pattern with quantifier, additional variable in query string, regexp does not match",
+			route:       new(Route).Queries("foo", "{v1:[0-9]{1}}"),
+			request:     newRequest("GET", "http://localhost?foo=12"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: false,
+		},
+		{
+			title:       "Queries route with hyphenated name, match",
+			route:       new(Route).Queries("foo", "{v-1}"),
+			request:     newRequest("GET", "http://localhost?foo=bar"),
+			vars:        map[string]string{"v-1": "bar"},
+			host:        "",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "Queries route with multiple hyphenated names, match",
+			route:       new(Route).Queries("foo", "{v-1}", "baz", "{v-2}"),
+			request:     newRequest("GET", "http://localhost?foo=bar&baz=ding"),
+			vars:        map[string]string{"v-1": "bar", "v-2": "ding"},
+			host:        "",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "Queries route with hyphenate name and pattern, match",
+			route:       new(Route).Queries("foo", "{v-1:[0-9]+}"),
+			request:     newRequest("GET", "http://localhost?foo=10"),
+			vars:        map[string]string{"v-1": "10"},
+			host:        "",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "Queries route with hyphenated name and pattern with quantifier, additional capturing group",
+			route:       new(Route).Queries("foo", "{v-1:[0-9]{1}(a|b)}"),
+			request:     newRequest("GET", "http://localhost?foo=1a"),
+			vars:        map[string]string{"v-1": "1a"},
+			host:        "",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "Queries route with empty value, should match",
+			route:       new(Route).Queries("foo", ""),
+			request:     newRequest("GET", "http://localhost?foo=bar"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "Queries route with empty value and no parameter in request, should not match",
+			route:       new(Route).Queries("foo", ""),
+			request:     newRequest("GET", "http://localhost"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: false,
+		},
+		{
+			title:       "Queries route with empty value and empty parameter in request, should match",
+			route:       new(Route).Queries("foo", ""),
+			request:     newRequest("GET", "http://localhost?foo="),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "Queries route with overlapping value, should not match",
+			route:       new(Route).Queries("foo", "bar"),
+			request:     newRequest("GET", "http://localhost?foo=barfoo"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: false,
+		},
+		{
+			title:       "Queries route with no parameter in request, should not match",
+			route:       new(Route).Queries("foo", "{bar}"),
+			request:     newRequest("GET", "http://localhost"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: false,
+		},
+		{
+			title:       "Queries route with empty parameter in request, should match",
+			route:       new(Route).Queries("foo", "{bar}"),
+			request:     newRequest("GET", "http://localhost?foo="),
+			vars:        map[string]string{"foo": ""},
+			host:        "",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "Queries route, bad submatch",
+			route:       new(Route).Queries("foo", "bar", "baz", "ding"),
+			request:     newRequest("GET", "http://localhost?fffoo=bar&baz=dingggg"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: false,
+		},
+	}
+
+	for _, test := range tests {
+		testRoute(t, test)
+		testTemplate(t, test)
+	}
+}
+
+func TestSchemes(t *testing.T) {
+	tests := []routeTest{
+		// Schemes
+		{
+			title:       "Schemes route, match https",
+			route:       new(Route).Schemes("https", "ftp"),
+			request:     newRequest("GET", "https://localhost"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "Schemes route, match ftp",
+			route:       new(Route).Schemes("https", "ftp"),
+			request:     newRequest("GET", "ftp://localhost"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "Schemes route, bad scheme",
+			route:       new(Route).Schemes("https", "ftp"),
+			request:     newRequest("GET", "http://localhost"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: false,
+		},
+	}
+	for _, test := range tests {
+		testRoute(t, test)
+		testTemplate(t, test)
+	}
+}
+
+func TestMatcherFunc(t *testing.T) {
+	m := func(r *http.Request, m *RouteMatch) bool {
+		if r.URL.Host == "aaa.bbb.ccc" {
+			return true
+		}
+		return false
+	}
+
+	tests := []routeTest{
+		{
+			title:       "MatchFunc route, match",
+			route:       new(Route).MatcherFunc(m),
+			request:     newRequest("GET", "http://aaa.bbb.ccc"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: true,
+		},
+		{
+			title:       "MatchFunc route, non-match",
+			route:       new(Route).MatcherFunc(m),
+			request:     newRequest("GET", "http://aaa.222.ccc"),
+			vars:        map[string]string{},
+			host:        "",
+			path:        "",
+			shouldMatch: false,
+		},
+	}
+
+	for _, test := range tests {
+		testRoute(t, test)
+		testTemplate(t, test)
+	}
+}
+
+func TestBuildVarsFunc(t *testing.T) {
+	tests := []routeTest{
+		{
+			title: "BuildVarsFunc set on route",
+			route: new(Route).Path(`/111/{v1:\d}{v2:.*}`).BuildVarsFunc(func(vars map[string]string) map[string]string {
+				vars["v1"] = "3"
+				vars["v2"] = "a"
+				return vars
+			}),
+			request:       newRequest("GET", "http://localhost/111/2"),
+			path:          "/111/3a",
+			path_template: `/111/{v1:\d}{v2:.*}`,
+			shouldMatch:   true,
+		},
+		{
+			title: "BuildVarsFunc set on route and parent route",
+			route: new(Route).PathPrefix(`/{v1:\d}`).BuildVarsFunc(func(vars map[string]string) map[string]string {
+				vars["v1"] = "2"
+				return vars
+			}).Subrouter().Path(`/{v2:\w}`).BuildVarsFunc(func(vars map[string]string) map[string]string {
+				vars["v2"] = "b"
+				return vars
+			}),
+			request:       newRequest("GET", "http://localhost/1/a"),
+			path:          "/2/b",
+			path_template: `/{v1:\d}/{v2:\w}`,
+			shouldMatch:   true,
+		},
+	}
+
+	for _, test := range tests {
+		testRoute(t, test)
+		testTemplate(t, test)
+	}
+}
+
+func TestSubRouter(t *testing.T) {
+	subrouter1 := new(Route).Host("{v1:[a-z]+}.google.com").Subrouter()
+	subrouter2 := new(Route).PathPrefix("/foo/{v1}").Subrouter()
+
+	tests := []routeTest{
+		{
+			route:         subrouter1.Path("/{v2:[a-z]+}"),
+			request:       newRequest("GET", "http://aaa.google.com/bbb"),
+			vars:          map[string]string{"v1": "aaa", "v2": "bbb"},
+			host:          "aaa.google.com",
+			path:          "/bbb",
+			path_template: `/{v2:[a-z]+}`,
+			host_template: `{v1:[a-z]+}.google.com`,
+			shouldMatch:   true,
+		},
+		{
+			route:         subrouter1.Path("/{v2:[a-z]+}"),
+			request:       newRequest("GET", "http://111.google.com/111"),
+			vars:          map[string]string{"v1": "aaa", "v2": "bbb"},
+			host:          "aaa.google.com",
+			path:          "/bbb",
+			path_template: `/{v2:[a-z]+}`,
+			host_template: `{v1:[a-z]+}.google.com`,
+			shouldMatch:   false,
+		},
+		{
+			route:         subrouter2.Path("/baz/{v2}"),
+			request:       newRequest("GET", "http://localhost/foo/bar/baz/ding"),
+			vars:          map[string]string{"v1": "bar", "v2": "ding"},
+			host:          "",
+			path:          "/foo/bar/baz/ding",
+			path_template: `/foo/{v1}/baz/{v2}`,
+			shouldMatch:   true,
+		},
+		{
+			route:         subrouter2.Path("/baz/{v2}"),
+			request:       newRequest("GET", "http://localhost/foo/bar"),
+			vars:          map[string]string{"v1": "bar", "v2": "ding"},
+			host:          "",
+			path:          "/foo/bar/baz/ding",
+			path_template: `/foo/{v1}/baz/{v2}`,
+			shouldMatch:   false,
+		},
+	}
+
+	for _, test := range tests {
+		testRoute(t, test)
+		testTemplate(t, test)
+	}
+}
+
+func TestNamedRoutes(t *testing.T) {
+	r1 := NewRouter()
+	r1.NewRoute().Name("a")
+	r1.NewRoute().Name("b")
+	r1.NewRoute().Name("c")
+
+	r2 := r1.NewRoute().Subrouter()
+	r2.NewRoute().Name("d")
+	r2.NewRoute().Name("e")
+	r2.NewRoute().Name("f")
+
+	r3 := r2.NewRoute().Subrouter()
+	r3.NewRoute().Name("g")
+	r3.NewRoute().Name("h")
+	r3.NewRoute().Name("i")
+
+	if r1.namedRoutes == nil || len(r1.namedRoutes) != 9 {
+		t.Errorf("Expected 9 named routes, got %v", r1.namedRoutes)
+	} else if r1.Get("i") == nil {
+		t.Errorf("Subroute name not registered")
+	}
+}
+
+func TestStrictSlash(t *testing.T) {
+	r := NewRouter()
+	r.StrictSlash(true)
+
+	tests := []routeTest{
+		{
+			title:          "Redirect path without slash",
+			route:          r.NewRoute().Path("/111/"),
+			request:        newRequest("GET", "http://localhost/111"),
+			vars:           map[string]string{},
+			host:           "",
+			path:           "/111/",
+			shouldMatch:    true,
+			shouldRedirect: true,
+		},
+		{
+			title:          "Do not redirect path with slash",
+			route:          r.NewRoute().Path("/111/"),
+			request:        newRequest("GET", "http://localhost/111/"),
+			vars:           map[string]string{},
+			host:           "",
+			path:           "/111/",
+			shouldMatch:    true,
+			shouldRedirect: false,
+		},
+		{
+			title:          "Redirect path with slash",
+			route:          r.NewRoute().Path("/111"),
+			request:        newRequest("GET", "http://localhost/111/"),
+			vars:           map[string]string{},
+			host:           "",
+			path:           "/111",
+			shouldMatch:    true,
+			shouldRedirect: true,
+		},
+		{
+			title:          "Do not redirect path without slash",
+			route:          r.NewRoute().Path("/111"),
+			request:        newRequest("GET", "http://localhost/111"),
+			vars:           map[string]string{},
+			host:           "",
+			path:           "/111",
+			shouldMatch:    true,
+			shouldRedirect: false,
+		},
+		{
+			title:          "Propagate StrictSlash to subrouters",
+			route:          r.NewRoute().PathPrefix("/static/").Subrouter().Path("/images/"),
+			request:        newRequest("GET", "http://localhost/static/images"),
+			vars:           map[string]string{},
+			host:           "",
+			path:           "/static/images/",
+			shouldMatch:    true,
+			shouldRedirect: true,
+		},
+		{
+			title:          "Ignore StrictSlash for path prefix",
+			route:          r.NewRoute().PathPrefix("/static/"),
+			request:        newRequest("GET", "http://localhost/static/logo.png"),
+			vars:           map[string]string{},
+			host:           "",
+			path:           "/static/",
+			shouldMatch:    true,
+			shouldRedirect: false,
+		},
+	}
+
+	for _, test := range tests {
+		testRoute(t, test)
+		testTemplate(t, test)
+	}
+}
+
+func TestWalkSingleDepth(t *testing.T) {
+	r0 := NewRouter()
+	r1 := NewRouter()
+	r2 := NewRouter()
+
+	r0.Path("/g")
+	r0.Path("/o")
+	r0.Path("/d").Handler(r1)
+	r0.Path("/r").Handler(r2)
+	r0.Path("/a")
+
+	r1.Path("/z")
+	r1.Path("/i")
+	r1.Path("/l")
+	r1.Path("/l")
+
+	r2.Path("/i")
+	r2.Path("/l")
+	r2.Path("/l")
+
+	paths := []string{"g", "o", "r", "i", "l", "l", "a"}
+	depths := []int{0, 0, 0, 1, 1, 1, 0}
+	i := 0
+	err := r0.Walk(func(route *Route, router *Router, ancestors []*Route) error {
+		matcher := route.matchers[0].(*routeRegexp)
+		if matcher.template == "/d" {
+			return SkipRouter
+		}
+		if len(ancestors) != depths[i] {
+			t.Errorf(`Expected depth of %d at i = %d; got "%d"`, depths[i], i, len(ancestors))
+		}
+		if matcher.template != "/"+paths[i] {
+			t.Errorf(`Expected "/%s" at i = %d; got "%s"`, paths[i], i, matcher.template)
+		}
+		i++
+		return nil
+	})
+	if err != nil {
+		panic(err)
+	}
+	if i != len(paths) {
+		t.Errorf("Expected %d routes, found %d", len(paths), i)
+	}
+}
+
+func TestWalkNested(t *testing.T) {
+	router := NewRouter()
+
+	g := router.Path("/g").Subrouter()
+	o := g.PathPrefix("/o").Subrouter()
+	r := o.PathPrefix("/r").Subrouter()
+	i := r.PathPrefix("/i").Subrouter()
+	l1 := i.PathPrefix("/l").Subrouter()
+	l2 := l1.PathPrefix("/l").Subrouter()
+	l2.Path("/a")
+
+	paths := []string{"/g", "/g/o", "/g/o/r", "/g/o/r/i", "/g/o/r/i/l", "/g/o/r/i/l/l", "/g/o/r/i/l/l/a"}
+	idx := 0
+	err := router.Walk(func(route *Route, router *Router, ancestors []*Route) error {
+		path := paths[idx]
+		tpl := route.regexp.path.template
+		if tpl != path {
+			t.Errorf(`Expected %s got %s`, path, tpl)
+		}
+		idx++
+		return nil
+	})
+	if err != nil {
+		panic(err)
+	}
+	if idx != len(paths) {
+		t.Errorf("Expected %d routes, found %d", len(paths), idx)
+	}
+}
+
+func TestSubrouterErrorHandling(t *testing.T) {
+	superRouterCalled := false
+	subRouterCalled := false
+
+	router := NewRouter()
+	router.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		superRouterCalled = true
+	})
+	subRouter := router.PathPrefix("/bign8").Subrouter()
+	subRouter.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		subRouterCalled = true
+	})
+
+	req, _ := http.NewRequest("GET", "http://localhost/bign8/was/here", nil)
+	router.ServeHTTP(NewRecorder(), req)
+
+	if superRouterCalled {
+		t.Error("Super router 404 handler called when sub-router 404 handler is available.")
+	}
+	if !subRouterCalled {
+		t.Error("Sub-router 404 handler was not called.")
+	}
+}
+
+// ----------------------------------------------------------------------------
+// Helpers
+// ----------------------------------------------------------------------------
+
+func getRouteTemplate(route *Route) string {
+	host, err := route.GetHostTemplate()
+	if err != nil {
+		host = "none"
+	}
+	path, err := route.GetPathTemplate()
+	if err != nil {
+		path = "none"
+	}
+	return fmt.Sprintf("Host: %v, Path: %v", host, path)
+}
+
+func testRoute(t *testing.T, test routeTest) {
+	request := test.request
+	route := test.route
+	vars := test.vars
+	shouldMatch := test.shouldMatch
+	host := test.host
+	path := test.path
+	url := test.host + test.path
+	shouldRedirect := test.shouldRedirect
+
+	var match RouteMatch
+	ok := route.Match(request, &match)
+	if ok != shouldMatch {
+		msg := "Should match"
+		if !shouldMatch {
+			msg = "Should not match"
+		}
+		t.Errorf("(%v) %v:\nRoute: %#v\nRequest: %#v\nVars: %v\n", test.title, msg, route, request, vars)
+		return
+	}
+	if shouldMatch {
+		if test.vars != nil && !stringMapEqual(test.vars, match.Vars) {
+			t.Errorf("(%v) Vars not equal: expected %v, got %v", test.title, vars, match.Vars)
+			return
+		}
+		if host != "" {
+			u, _ := test.route.URLHost(mapToPairs(match.Vars)...)
+			if host != u.Host {
+				t.Errorf("(%v) URLHost not equal: expected %v, got %v -- %v", test.title, host, u.Host, getRouteTemplate(route))
+				return
+			}
+		}
+		if path != "" {
+			u, _ := route.URLPath(mapToPairs(match.Vars)...)
+			if path != u.Path {
+				t.Errorf("(%v) URLPath not equal: expected %v, got %v -- %v", test.title, path, u.Path, getRouteTemplate(route))
+				return
+			}
+		}
+		if url != "" {
+			u, _ := route.URL(mapToPairs(match.Vars)...)
+			if url != u.Host+u.Path {
+				t.Errorf("(%v) URL not equal: expected %v, got %v -- %v", test.title, url, u.Host+u.Path, getRouteTemplate(route))
+				return
+			}
+		}
+		if shouldRedirect && match.Handler == nil {
+			t.Errorf("(%v) Did not redirect", test.title)
+			return
+		}
+		if !shouldRedirect && match.Handler != nil {
+			t.Errorf("(%v) Unexpected redirect", test.title)
+			return
+		}
+	}
+}
+
+func testTemplate(t *testing.T, test routeTest) {
+	route := test.route
+	path_template := test.path_template
+	if len(path_template) == 0 {
+		path_template = test.path
+	}
+	host_template := test.host_template
+	if len(host_template) == 0 {
+		host_template = test.host
+	}
+
+	path_tmpl, path_err := route.GetPathTemplate()
+	if path_err == nil && path_tmpl != path_template {
+		t.Errorf("(%v) GetPathTemplate not equal: expected %v, got %v", test.title, path_template, path_tmpl)
+	}
+
+	host_tmpl, host_err := route.GetHostTemplate()
+	if host_err == nil && host_tmpl != host_template {
+		t.Errorf("(%v) GetHostTemplate not equal: expected %v, got %v", test.title, host_template, host_tmpl)
+	}
+}
+
+type TestA301ResponseWriter struct {
+	hh     http.Header
+	status int
+}
+
+func (ho TestA301ResponseWriter) Header() http.Header {
+	return http.Header(ho.hh)
+}
+
+func (ho TestA301ResponseWriter) Write(b []byte) (int, error) {
+	return 0, nil
+}
+
+func (ho TestA301ResponseWriter) WriteHeader(code int) {
+	ho.status = code
+}
+
+func Test301Redirect(t *testing.T) {
+	m := make(http.Header)
+
+	func1 := func(w http.ResponseWriter, r *http.Request) {}
+	func2 := func(w http.ResponseWriter, r *http.Request) {}
+
+	r := NewRouter()
+	r.HandleFunc("/api/", func2).Name("func2")
+	r.HandleFunc("/", func1).Name("func1")
+
+	req, _ := http.NewRequest("GET", "http://localhost//api/?abc=def", nil)
+
+	res := TestA301ResponseWriter{
+		hh:     m,
+		status: 0,
+	}
+	r.ServeHTTP(&res, req)
+
+	if "http://localhost/api/?abc=def" != res.hh["Location"][0] {
+		t.Errorf("Should have complete URL with query string")
+	}
+}
+
+func TestSkipClean(t *testing.T) {
+	func1 := func(w http.ResponseWriter, r *http.Request) {}
+	func2 := func(w http.ResponseWriter, r *http.Request) {}
+
+	r := NewRouter()
+	r.SkipClean(true)
+	r.HandleFunc("/api/", func2).Name("func2")
+	r.HandleFunc("/", func1).Name("func1")
+
+	req, _ := http.NewRequest("GET", "http://localhost//api/?abc=def", nil)
+	res := NewRecorder()
+	r.ServeHTTP(res, req)
+
+	if len(res.HeaderMap["Location"]) != 0 {
+		t.Errorf("Shouldn't redirect since skip clean is disabled")
+	}
+}
+
+// https://plus.google.com/101022900381697718949/posts/eWy6DjFJ6uW
+func TestSubrouterHeader(t *testing.T) {
+	expected := "func1 response"
+	func1 := func(w http.ResponseWriter, r *http.Request) {
+		fmt.Fprint(w, expected)
+	}
+	func2 := func(http.ResponseWriter, *http.Request) {}
+
+	r := NewRouter()
+	s := r.Headers("SomeSpecialHeader", "").Subrouter()
+	s.HandleFunc("/", func1).Name("func1")
+	r.HandleFunc("/", func2).Name("func2")
+
+	req, _ := http.NewRequest("GET", "http://localhost/", nil)
+	req.Header.Add("SomeSpecialHeader", "foo")
+	match := new(RouteMatch)
+	matched := r.Match(req, match)
+	if !matched {
+		t.Errorf("Should match request")
+	}
+	if match.Route.GetName() != "func1" {
+		t.Errorf("Expecting func1 handler, got %s", match.Route.GetName())
+	}
+	resp := NewRecorder()
+	match.Handler.ServeHTTP(resp, req)
+	if resp.Body.String() != expected {
+		t.Errorf("Expecting %q", expected)
+	}
+}
+
+// mapToPairs converts a string map to a slice of string pairs
+func mapToPairs(m map[string]string) []string {
+	var i int
+	p := make([]string, len(m)*2)
+	for k, v := range m {
+		p[i] = k
+		p[i+1] = v
+		i += 2
+	}
+	return p
+}
+
+// stringMapEqual checks the equality of two string maps
+func stringMapEqual(m1, m2 map[string]string) bool {
+	nil1 := m1 == nil
+	nil2 := m2 == nil
+	if nil1 != nil2 || len(m1) != len(m2) {
+		return false
+	}
+	for k, v := range m1 {
+		if v != m2[k] {
+			return false
+		}
+	}
+	return true
+}
+
+// newRequest is a helper function to create a new request with a method and url
+func newRequest(method, url string) *http.Request {
+	req, err := http.NewRequest(method, url, nil)
+	if err != nil {
+		panic(err)
+	}
+	return req
+}

+ 710 - 0
src/github.com/gorilla/mux/old_test.go

@@ -0,0 +1,710 @@
+// Old tests ported to Go1. This is a mess. Want to drop it one day.
+
+// Copyright 2011 Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package mux
+
+import (
+	"bytes"
+	"net/http"
+	"testing"
+)
+
+// ----------------------------------------------------------------------------
+// ResponseRecorder
+// ----------------------------------------------------------------------------
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// ResponseRecorder is an implementation of http.ResponseWriter that
+// records its mutations for later inspection in tests.
+type ResponseRecorder struct {
+	Code      int           // the HTTP response code from WriteHeader
+	HeaderMap http.Header   // the HTTP response headers
+	Body      *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to
+	Flushed   bool
+}
+
+// NewRecorder returns an initialized ResponseRecorder.
+func NewRecorder() *ResponseRecorder {
+	return &ResponseRecorder{
+		HeaderMap: make(http.Header),
+		Body:      new(bytes.Buffer),
+	}
+}
+
+// Header returns the response headers.
+func (rw *ResponseRecorder) Header() http.Header {
+	return rw.HeaderMap
+}
+
+// Write always succeeds and writes to rw.Body, if not nil.
+func (rw *ResponseRecorder) Write(buf []byte) (int, error) {
+	if rw.Body != nil {
+		rw.Body.Write(buf)
+	}
+	if rw.Code == 0 {
+		rw.Code = http.StatusOK
+	}
+	return len(buf), nil
+}
+
+// WriteHeader sets rw.Code.
+func (rw *ResponseRecorder) WriteHeader(code int) {
+	rw.Code = code
+}
+
+// Flush sets rw.Flushed to true.
+func (rw *ResponseRecorder) Flush() {
+	rw.Flushed = true
+}
+
+// ----------------------------------------------------------------------------
+
+func TestRouteMatchers(t *testing.T) {
+	var scheme, host, path, query, method string
+	var headers map[string]string
+	var resultVars map[bool]map[string]string
+
+	router := NewRouter()
+	router.NewRoute().Host("{var1}.google.com").
+		Path("/{var2:[a-z]+}/{var3:[0-9]+}").
+		Queries("foo", "bar").
+		Methods("GET").
+		Schemes("https").
+		Headers("x-requested-with", "XMLHttpRequest")
+	router.NewRoute().Host("www.{var4}.com").
+		PathPrefix("/foo/{var5:[a-z]+}/{var6:[0-9]+}").
+		Queries("baz", "ding").
+		Methods("POST").
+		Schemes("http").
+		Headers("Content-Type", "application/json")
+
+	reset := func() {
+		// Everything match.
+		scheme = "https"
+		host = "www.google.com"
+		path = "/product/42"
+		query = "?foo=bar"
+		method = "GET"
+		headers = map[string]string{"X-Requested-With": "XMLHttpRequest"}
+		resultVars = map[bool]map[string]string{
+			true:  {"var1": "www", "var2": "product", "var3": "42"},
+			false: {},
+		}
+	}
+
+	reset2 := func() {
+		// Everything match.
+		scheme = "http"
+		host = "www.google.com"
+		path = "/foo/product/42/path/that/is/ignored"
+		query = "?baz=ding"
+		method = "POST"
+		headers = map[string]string{"Content-Type": "application/json"}
+		resultVars = map[bool]map[string]string{
+			true:  {"var4": "google", "var5": "product", "var6": "42"},
+			false: {},
+		}
+	}
+
+	match := func(shouldMatch bool) {
+		url := scheme + "://" + host + path + query
+		request, _ := http.NewRequest(method, url, nil)
+		for key, value := range headers {
+			request.Header.Add(key, value)
+		}
+
+		var routeMatch RouteMatch
+		matched := router.Match(request, &routeMatch)
+		if matched != shouldMatch {
+			// Need better messages. :)
+			if matched {
+				t.Errorf("Should match.")
+			} else {
+				t.Errorf("Should not match.")
+			}
+		}
+
+		if matched {
+			currentRoute := routeMatch.Route
+			if currentRoute == nil {
+				t.Errorf("Expected a current route.")
+			}
+			vars := routeMatch.Vars
+			expectedVars := resultVars[shouldMatch]
+			if len(vars) != len(expectedVars) {
+				t.Errorf("Expected vars: %v Got: %v.", expectedVars, vars)
+			}
+			for name, value := range vars {
+				if expectedVars[name] != value {
+					t.Errorf("Expected vars: %v Got: %v.", expectedVars, vars)
+				}
+			}
+		}
+	}
+
+	// 1st route --------------------------------------------------------------
+
+	// Everything match.
+	reset()
+	match(true)
+
+	// Scheme doesn't match.
+	reset()
+	scheme = "http"
+	match(false)
+
+	// Host doesn't match.
+	reset()
+	host = "www.mygoogle.com"
+	match(false)
+
+	// Path doesn't match.
+	reset()
+	path = "/product/notdigits"
+	match(false)
+
+	// Query doesn't match.
+	reset()
+	query = "?foo=baz"
+	match(false)
+
+	// Method doesn't match.
+	reset()
+	method = "POST"
+	match(false)
+
+	// Header doesn't match.
+	reset()
+	headers = map[string]string{}
+	match(false)
+
+	// Everything match, again.
+	reset()
+	match(true)
+
+	// 2nd route --------------------------------------------------------------
+
+	// Everything match.
+	reset2()
+	match(true)
+
+	// Scheme doesn't match.
+	reset2()
+	scheme = "https"
+	match(false)
+
+	// Host doesn't match.
+	reset2()
+	host = "sub.google.com"
+	match(false)
+
+	// Path doesn't match.
+	reset2()
+	path = "/bar/product/42"
+	match(false)
+
+	// Query doesn't match.
+	reset2()
+	query = "?foo=baz"
+	match(false)
+
+	// Method doesn't match.
+	reset2()
+	method = "GET"
+	match(false)
+
+	// Header doesn't match.
+	reset2()
+	headers = map[string]string{}
+	match(false)
+
+	// Everything match, again.
+	reset2()
+	match(true)
+}
+
+type headerMatcherTest struct {
+	matcher headerMatcher
+	headers map[string]string
+	result  bool
+}
+
+var headerMatcherTests = []headerMatcherTest{
+	{
+		matcher: headerMatcher(map[string]string{"x-requested-with": "XMLHttpRequest"}),
+		headers: map[string]string{"X-Requested-With": "XMLHttpRequest"},
+		result:  true,
+	},
+	{
+		matcher: headerMatcher(map[string]string{"x-requested-with": ""}),
+		headers: map[string]string{"X-Requested-With": "anything"},
+		result:  true,
+	},
+	{
+		matcher: headerMatcher(map[string]string{"x-requested-with": "XMLHttpRequest"}),
+		headers: map[string]string{},
+		result:  false,
+	},
+}
+
+type hostMatcherTest struct {
+	matcher *Route
+	url     string
+	vars    map[string]string
+	result  bool
+}
+
+var hostMatcherTests = []hostMatcherTest{
+	{
+		matcher: NewRouter().NewRoute().Host("{foo:[a-z][a-z][a-z]}.{bar:[a-z][a-z][a-z]}.{baz:[a-z][a-z][a-z]}"),
+		url:     "http://abc.def.ghi/",
+		vars:    map[string]string{"foo": "abc", "bar": "def", "baz": "ghi"},
+		result:  true,
+	},
+	{
+		matcher: NewRouter().NewRoute().Host("{foo:[a-z][a-z][a-z]}.{bar:[a-z][a-z][a-z]}.{baz:[a-z][a-z][a-z]}"),
+		url:     "http://a.b.c/",
+		vars:    map[string]string{"foo": "abc", "bar": "def", "baz": "ghi"},
+		result:  false,
+	},
+}
+
+type methodMatcherTest struct {
+	matcher methodMatcher
+	method  string
+	result  bool
+}
+
+var methodMatcherTests = []methodMatcherTest{
+	{
+		matcher: methodMatcher([]string{"GET", "POST", "PUT"}),
+		method:  "GET",
+		result:  true,
+	},
+	{
+		matcher: methodMatcher([]string{"GET", "POST", "PUT"}),
+		method:  "POST",
+		result:  true,
+	},
+	{
+		matcher: methodMatcher([]string{"GET", "POST", "PUT"}),
+		method:  "PUT",
+		result:  true,
+	},
+	{
+		matcher: methodMatcher([]string{"GET", "POST", "PUT"}),
+		method:  "DELETE",
+		result:  false,
+	},
+}
+
+type pathMatcherTest struct {
+	matcher *Route
+	url     string
+	vars    map[string]string
+	result  bool
+}
+
+var pathMatcherTests = []pathMatcherTest{
+	{
+		matcher: NewRouter().NewRoute().Path("/{foo:[0-9][0-9][0-9]}/{bar:[0-9][0-9][0-9]}/{baz:[0-9][0-9][0-9]}"),
+		url:     "http://localhost:8080/123/456/789",
+		vars:    map[string]string{"foo": "123", "bar": "456", "baz": "789"},
+		result:  true,
+	},
+	{
+		matcher: NewRouter().NewRoute().Path("/{foo:[0-9][0-9][0-9]}/{bar:[0-9][0-9][0-9]}/{baz:[0-9][0-9][0-9]}"),
+		url:     "http://localhost:8080/1/2/3",
+		vars:    map[string]string{"foo": "123", "bar": "456", "baz": "789"},
+		result:  false,
+	},
+}
+
+type schemeMatcherTest struct {
+	matcher schemeMatcher
+	url     string
+	result  bool
+}
+
+var schemeMatcherTests = []schemeMatcherTest{
+	{
+		matcher: schemeMatcher([]string{"http", "https"}),
+		url:     "http://localhost:8080/",
+		result:  true,
+	},
+	{
+		matcher: schemeMatcher([]string{"http", "https"}),
+		url:     "https://localhost:8080/",
+		result:  true,
+	},
+	{
+		matcher: schemeMatcher([]string{"https"}),
+		url:     "http://localhost:8080/",
+		result:  false,
+	},
+	{
+		matcher: schemeMatcher([]string{"http"}),
+		url:     "https://localhost:8080/",
+		result:  false,
+	},
+}
+
+type urlBuildingTest struct {
+	route *Route
+	vars  []string
+	url   string
+}
+
+var urlBuildingTests = []urlBuildingTest{
+	{
+		route: new(Route).Host("foo.domain.com"),
+		vars:  []string{},
+		url:   "http://foo.domain.com",
+	},
+	{
+		route: new(Route).Host("{subdomain}.domain.com"),
+		vars:  []string{"subdomain", "bar"},
+		url:   "http://bar.domain.com",
+	},
+	{
+		route: new(Route).Host("foo.domain.com").Path("/articles"),
+		vars:  []string{},
+		url:   "http://foo.domain.com/articles",
+	},
+	{
+		route: new(Route).Path("/articles"),
+		vars:  []string{},
+		url:   "/articles",
+	},
+	{
+		route: new(Route).Path("/articles/{category}/{id:[0-9]+}"),
+		vars:  []string{"category", "technology", "id", "42"},
+		url:   "/articles/technology/42",
+	},
+	{
+		route: new(Route).Host("{subdomain}.domain.com").Path("/articles/{category}/{id:[0-9]+}"),
+		vars:  []string{"subdomain", "foo", "category", "technology", "id", "42"},
+		url:   "http://foo.domain.com/articles/technology/42",
+	},
+}
+
+func TestHeaderMatcher(t *testing.T) {
+	for _, v := range headerMatcherTests {
+		request, _ := http.NewRequest("GET", "http://localhost:8080/", nil)
+		for key, value := range v.headers {
+			request.Header.Add(key, value)
+		}
+		var routeMatch RouteMatch
+		result := v.matcher.Match(request, &routeMatch)
+		if result != v.result {
+			if v.result {
+				t.Errorf("%#v: should match %v.", v.matcher, request.Header)
+			} else {
+				t.Errorf("%#v: should not match %v.", v.matcher, request.Header)
+			}
+		}
+	}
+}
+
+func TestHostMatcher(t *testing.T) {
+	for _, v := range hostMatcherTests {
+		request, _ := http.NewRequest("GET", v.url, nil)
+		var routeMatch RouteMatch
+		result := v.matcher.Match(request, &routeMatch)
+		vars := routeMatch.Vars
+		if result != v.result {
+			if v.result {
+				t.Errorf("%#v: should match %v.", v.matcher, v.url)
+			} else {
+				t.Errorf("%#v: should not match %v.", v.matcher, v.url)
+			}
+		}
+		if result {
+			if len(vars) != len(v.vars) {
+				t.Errorf("%#v: vars length should be %v, got %v.", v.matcher, len(v.vars), len(vars))
+			}
+			for name, value := range vars {
+				if v.vars[name] != value {
+					t.Errorf("%#v: expected value %v for key %v, got %v.", v.matcher, v.vars[name], name, value)
+				}
+			}
+		} else {
+			if len(vars) != 0 {
+				t.Errorf("%#v: vars length should be 0, got %v.", v.matcher, len(vars))
+			}
+		}
+	}
+}
+
+func TestMethodMatcher(t *testing.T) {
+	for _, v := range methodMatcherTests {
+		request, _ := http.NewRequest(v.method, "http://localhost:8080/", nil)
+		var routeMatch RouteMatch
+		result := v.matcher.Match(request, &routeMatch)
+		if result != v.result {
+			if v.result {
+				t.Errorf("%#v: should match %v.", v.matcher, v.method)
+			} else {
+				t.Errorf("%#v: should not match %v.", v.matcher, v.method)
+			}
+		}
+	}
+}
+
+func TestPathMatcher(t *testing.T) {
+	for _, v := range pathMatcherTests {
+		request, _ := http.NewRequest("GET", v.url, nil)
+		var routeMatch RouteMatch
+		result := v.matcher.Match(request, &routeMatch)
+		vars := routeMatch.Vars
+		if result != v.result {
+			if v.result {
+				t.Errorf("%#v: should match %v.", v.matcher, v.url)
+			} else {
+				t.Errorf("%#v: should not match %v.", v.matcher, v.url)
+			}
+		}
+		if result {
+			if len(vars) != len(v.vars) {
+				t.Errorf("%#v: vars length should be %v, got %v.", v.matcher, len(v.vars), len(vars))
+			}
+			for name, value := range vars {
+				if v.vars[name] != value {
+					t.Errorf("%#v: expected value %v for key %v, got %v.", v.matcher, v.vars[name], name, value)
+				}
+			}
+		} else {
+			if len(vars) != 0 {
+				t.Errorf("%#v: vars length should be 0, got %v.", v.matcher, len(vars))
+			}
+		}
+	}
+}
+
+func TestSchemeMatcher(t *testing.T) {
+	for _, v := range schemeMatcherTests {
+		request, _ := http.NewRequest("GET", v.url, nil)
+		var routeMatch RouteMatch
+		result := v.matcher.Match(request, &routeMatch)
+		if result != v.result {
+			if v.result {
+				t.Errorf("%#v: should match %v.", v.matcher, v.url)
+			} else {
+				t.Errorf("%#v: should not match %v.", v.matcher, v.url)
+			}
+		}
+	}
+}
+
+func TestUrlBuilding(t *testing.T) {
+
+	for _, v := range urlBuildingTests {
+		u, _ := v.route.URL(v.vars...)
+		url := u.String()
+		if url != v.url {
+			t.Errorf("expected %v, got %v", v.url, url)
+			/*
+				reversePath := ""
+				reverseHost := ""
+				if v.route.pathTemplate != nil {
+						reversePath = v.route.pathTemplate.Reverse
+				}
+				if v.route.hostTemplate != nil {
+						reverseHost = v.route.hostTemplate.Reverse
+				}
+
+				t.Errorf("%#v:\nexpected: %q\ngot: %q\nreverse path: %q\nreverse host: %q", v.route, v.url, url, reversePath, reverseHost)
+			*/
+		}
+	}
+
+	ArticleHandler := func(w http.ResponseWriter, r *http.Request) {
+	}
+
+	router := NewRouter()
+	router.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).Name("article")
+
+	url, _ := router.Get("article").URL("category", "technology", "id", "42")
+	expected := "/articles/technology/42"
+	if url.String() != expected {
+		t.Errorf("Expected %v, got %v", expected, url.String())
+	}
+}
+
+func TestMatchedRouteName(t *testing.T) {
+	routeName := "stock"
+	router := NewRouter()
+	route := router.NewRoute().Path("/products/").Name(routeName)
+
+	url := "http://www.example.com/products/"
+	request, _ := http.NewRequest("GET", url, nil)
+	var rv RouteMatch
+	ok := router.Match(request, &rv)
+
+	if !ok || rv.Route != route {
+		t.Errorf("Expected same route, got %+v.", rv.Route)
+	}
+
+	retName := rv.Route.GetName()
+	if retName != routeName {
+		t.Errorf("Expected %q, got %q.", routeName, retName)
+	}
+}
+
+func TestSubRouting(t *testing.T) {
+	// Example from docs.
+	router := NewRouter()
+	subrouter := router.NewRoute().Host("www.example.com").Subrouter()
+	route := subrouter.NewRoute().Path("/products/").Name("products")
+
+	url := "http://www.example.com/products/"
+	request, _ := http.NewRequest("GET", url, nil)
+	var rv RouteMatch
+	ok := router.Match(request, &rv)
+
+	if !ok || rv.Route != route {
+		t.Errorf("Expected same route, got %+v.", rv.Route)
+	}
+
+	u, _ := router.Get("products").URL()
+	builtURL := u.String()
+	// Yay, subroute aware of the domain when building!
+	if builtURL != url {
+		t.Errorf("Expected %q, got %q.", url, builtURL)
+	}
+}
+
+func TestVariableNames(t *testing.T) {
+	route := new(Route).Host("{arg1}.domain.com").Path("/{arg1}/{arg2:[0-9]+}")
+	if route.err == nil {
+		t.Errorf("Expected error for duplicated variable names")
+	}
+}
+
+func TestRedirectSlash(t *testing.T) {
+	var route *Route
+	var routeMatch RouteMatch
+	r := NewRouter()
+
+	r.StrictSlash(false)
+	route = r.NewRoute()
+	if route.strictSlash != false {
+		t.Errorf("Expected false redirectSlash.")
+	}
+
+	r.StrictSlash(true)
+	route = r.NewRoute()
+	if route.strictSlash != true {
+		t.Errorf("Expected true redirectSlash.")
+	}
+
+	route = new(Route)
+	route.strictSlash = true
+	route.Path("/{arg1}/{arg2:[0-9]+}/")
+	request, _ := http.NewRequest("GET", "http://localhost/foo/123", nil)
+	routeMatch = RouteMatch{}
+	_ = route.Match(request, &routeMatch)
+	vars := routeMatch.Vars
+	if vars["arg1"] != "foo" {
+		t.Errorf("Expected foo.")
+	}
+	if vars["arg2"] != "123" {
+		t.Errorf("Expected 123.")
+	}
+	rsp := NewRecorder()
+	routeMatch.Handler.ServeHTTP(rsp, request)
+	if rsp.HeaderMap.Get("Location") != "http://localhost/foo/123/" {
+		t.Errorf("Expected redirect header.")
+	}
+
+	route = new(Route)
+	route.strictSlash = true
+	route.Path("/{arg1}/{arg2:[0-9]+}")
+	request, _ = http.NewRequest("GET", "http://localhost/foo/123/", nil)
+	routeMatch = RouteMatch{}
+	_ = route.Match(request, &routeMatch)
+	vars = routeMatch.Vars
+	if vars["arg1"] != "foo" {
+		t.Errorf("Expected foo.")
+	}
+	if vars["arg2"] != "123" {
+		t.Errorf("Expected 123.")
+	}
+	rsp = NewRecorder()
+	routeMatch.Handler.ServeHTTP(rsp, request)
+	if rsp.HeaderMap.Get("Location") != "http://localhost/foo/123" {
+		t.Errorf("Expected redirect header.")
+	}
+}
+
+// Test for the new regexp library, still not available in stable Go.
+func TestNewRegexp(t *testing.T) {
+	var p *routeRegexp
+	var matches []string
+
+	tests := map[string]map[string][]string{
+		"/{foo:a{2}}": {
+			"/a":    nil,
+			"/aa":   {"aa"},
+			"/aaa":  nil,
+			"/aaaa": nil,
+		},
+		"/{foo:a{2,}}": {
+			"/a":    nil,
+			"/aa":   {"aa"},
+			"/aaa":  {"aaa"},
+			"/aaaa": {"aaaa"},
+		},
+		"/{foo:a{2,3}}": {
+			"/a":    nil,
+			"/aa":   {"aa"},
+			"/aaa":  {"aaa"},
+			"/aaaa": nil,
+		},
+		"/{foo:[a-z]{3}}/{bar:[a-z]{2}}": {
+			"/a":       nil,
+			"/ab":      nil,
+			"/abc":     nil,
+			"/abcd":    nil,
+			"/abc/ab":  {"abc", "ab"},
+			"/abc/abc": nil,
+			"/abcd/ab": nil,
+		},
+		`/{foo:\w{3,}}/{bar:\d{2,}}`: {
+			"/a":        nil,
+			"/ab":       nil,
+			"/abc":      nil,
+			"/abc/1":    nil,
+			"/abc/12":   {"abc", "12"},
+			"/abcd/12":  {"abcd", "12"},
+			"/abcd/123": {"abcd", "123"},
+		},
+	}
+
+	for pattern, paths := range tests {
+		p, _ = newRouteRegexp(pattern, false, false, false, false)
+		for path, result := range paths {
+			matches = p.regexp.FindStringSubmatch(path)
+			if result == nil {
+				if matches != nil {
+					t.Errorf("%v should not match %v.", pattern, path)
+				}
+			} else {
+				if len(matches) != len(result)+1 {
+					t.Errorf("Expected %v matches, got %v.", len(result)+1, len(matches))
+				} else {
+					for k, v := range result {
+						if matches[k+1] != v {
+							t.Errorf("Expected %v, got %v.", v, matches[k+1])
+						}
+					}
+				}
+			}
+		}
+	}
+}

+ 312 - 0
src/github.com/gorilla/mux/regexp.go

@@ -0,0 +1,312 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package mux
+
+import (
+	"bytes"
+	"fmt"
+	"net/http"
+	"net/url"
+	"regexp"
+	"strconv"
+	"strings"
+)
+
+// newRouteRegexp parses a route template and returns a routeRegexp,
+// used to match a host, a path or a query string.
+//
+// It will extract named variables, assemble a regexp to be matched, create
+// a "reverse" template to build URLs and compile regexps to validate variable
+// values used in URL building.
+//
+// Previously we accepted only Python-like identifiers for variable
+// names ([a-zA-Z_][a-zA-Z0-9_]*), but currently the only restriction is that
+// name and pattern can't be empty, and names can't contain a colon.
+func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash bool) (*routeRegexp, error) {
+	// Check if it is well-formed.
+	idxs, errBraces := braceIndices(tpl)
+	if errBraces != nil {
+		return nil, errBraces
+	}
+	// Backup the original.
+	template := tpl
+	// Now let's parse it.
+	defaultPattern := "[^/]+"
+	if matchQuery {
+		defaultPattern = "[^?&]*"
+	} else if matchHost {
+		defaultPattern = "[^.]+"
+		matchPrefix = false
+	}
+	// Only match strict slash if not matching
+	if matchPrefix || matchHost || matchQuery {
+		strictSlash = false
+	}
+	// Set a flag for strictSlash.
+	endSlash := false
+	if strictSlash && strings.HasSuffix(tpl, "/") {
+		tpl = tpl[:len(tpl)-1]
+		endSlash = true
+	}
+	varsN := make([]string, len(idxs)/2)
+	varsR := make([]*regexp.Regexp, len(idxs)/2)
+	pattern := bytes.NewBufferString("")
+	pattern.WriteByte('^')
+	reverse := bytes.NewBufferString("")
+	var end int
+	var err error
+	for i := 0; i < len(idxs); i += 2 {
+		// Set all values we are interested in.
+		raw := tpl[end:idxs[i]]
+		end = idxs[i+1]
+		parts := strings.SplitN(tpl[idxs[i]+1:end-1], ":", 2)
+		name := parts[0]
+		patt := defaultPattern
+		if len(parts) == 2 {
+			patt = parts[1]
+		}
+		// Name or pattern can't be empty.
+		if name == "" || patt == "" {
+			return nil, fmt.Errorf("mux: missing name or pattern in %q",
+				tpl[idxs[i]:end])
+		}
+		// Build the regexp pattern.
+		fmt.Fprintf(pattern, "%s(?P<%s>%s)", regexp.QuoteMeta(raw), varGroupName(i/2), patt)
+
+		// Build the reverse template.
+		fmt.Fprintf(reverse, "%s%%s", raw)
+
+		// Append variable name and compiled pattern.
+		varsN[i/2] = name
+		varsR[i/2], err = regexp.Compile(fmt.Sprintf("^%s$", patt))
+		if err != nil {
+			return nil, err
+		}
+	}
+	// Add the remaining.
+	raw := tpl[end:]
+	pattern.WriteString(regexp.QuoteMeta(raw))
+	if strictSlash {
+		pattern.WriteString("[/]?")
+	}
+	if matchQuery {
+		// Add the default pattern if the query value is empty
+		if queryVal := strings.SplitN(template, "=", 2)[1]; queryVal == "" {
+			pattern.WriteString(defaultPattern)
+		}
+	}
+	if !matchPrefix {
+		pattern.WriteByte('$')
+	}
+	reverse.WriteString(raw)
+	if endSlash {
+		reverse.WriteByte('/')
+	}
+	// Compile full regexp.
+	reg, errCompile := regexp.Compile(pattern.String())
+	if errCompile != nil {
+		return nil, errCompile
+	}
+	// Done!
+	return &routeRegexp{
+		template:    template,
+		matchHost:   matchHost,
+		matchQuery:  matchQuery,
+		strictSlash: strictSlash,
+		regexp:      reg,
+		reverse:     reverse.String(),
+		varsN:       varsN,
+		varsR:       varsR,
+	}, nil
+}
+
+// routeRegexp stores a regexp to match a host or path and information to
+// collect and validate route variables.
+type routeRegexp struct {
+	// The unmodified template.
+	template string
+	// True for host match, false for path or query string match.
+	matchHost bool
+	// True for query string match, false for path and host match.
+	matchQuery bool
+	// The strictSlash value defined on the route, but disabled if PathPrefix was used.
+	strictSlash bool
+	// Expanded regexp.
+	regexp *regexp.Regexp
+	// Reverse template.
+	reverse string
+	// Variable names.
+	varsN []string
+	// Variable regexps (validators).
+	varsR []*regexp.Regexp
+}
+
+// Match matches the regexp against the URL host or path.
+func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool {
+	if !r.matchHost {
+		if r.matchQuery {
+			return r.matchQueryString(req)
+		}
+
+		return r.regexp.MatchString(req.URL.Path)
+	}
+
+	return r.regexp.MatchString(getHost(req))
+}
+
+// url builds a URL part using the given values.
+func (r *routeRegexp) url(values map[string]string) (string, error) {
+	urlValues := make([]interface{}, len(r.varsN))
+	for k, v := range r.varsN {
+		value, ok := values[v]
+		if !ok {
+			return "", fmt.Errorf("mux: missing route variable %q", v)
+		}
+		urlValues[k] = value
+	}
+	rv := fmt.Sprintf(r.reverse, urlValues...)
+	if !r.regexp.MatchString(rv) {
+		// The URL is checked against the full regexp, instead of checking
+		// individual variables. This is faster but to provide a good error
+		// message, we check individual regexps if the URL doesn't match.
+		for k, v := range r.varsN {
+			if !r.varsR[k].MatchString(values[v]) {
+				return "", fmt.Errorf(
+					"mux: variable %q doesn't match, expected %q", values[v],
+					r.varsR[k].String())
+			}
+		}
+	}
+	return rv, nil
+}
+
+// getURLQuery returns a single query parameter from a request URL.
+// For a URL with foo=bar&baz=ding, we return only the relevant key
+// value pair for the routeRegexp.
+func (r *routeRegexp) getURLQuery(req *http.Request) string {
+	if !r.matchQuery {
+		return ""
+	}
+	templateKey := strings.SplitN(r.template, "=", 2)[0]
+	for key, vals := range req.URL.Query() {
+		if key == templateKey && len(vals) > 0 {
+			return key + "=" + vals[0]
+		}
+	}
+	return ""
+}
+
+func (r *routeRegexp) matchQueryString(req *http.Request) bool {
+	return r.regexp.MatchString(r.getURLQuery(req))
+}
+
+// braceIndices returns the first level curly brace indices from a string.
+// It returns an error in case of unbalanced braces.
+func braceIndices(s string) ([]int, error) {
+	var level, idx int
+	var idxs []int
+	for i := 0; i < len(s); i++ {
+		switch s[i] {
+		case '{':
+			if level++; level == 1 {
+				idx = i
+			}
+		case '}':
+			if level--; level == 0 {
+				idxs = append(idxs, idx, i+1)
+			} else if level < 0 {
+				return nil, fmt.Errorf("mux: unbalanced braces in %q", s)
+			}
+		}
+	}
+	if level != 0 {
+		return nil, fmt.Errorf("mux: unbalanced braces in %q", s)
+	}
+	return idxs, nil
+}
+
+// varGroupName builds a capturing group name for the indexed variable.
+func varGroupName(idx int) string {
+	return "v" + strconv.Itoa(idx)
+}
+
+// ----------------------------------------------------------------------------
+// routeRegexpGroup
+// ----------------------------------------------------------------------------
+
+// routeRegexpGroup groups the route matchers that carry variables.
+type routeRegexpGroup struct {
+	host    *routeRegexp
+	path    *routeRegexp
+	queries []*routeRegexp
+}
+
+// setMatch extracts the variables from the URL once a route matches.
+func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) {
+	// Store host variables.
+	if v.host != nil {
+		host := getHost(req)
+		matches := v.host.regexp.FindStringSubmatchIndex(host)
+		if len(matches) > 0 {
+			extractVars(host, matches, v.host.varsN, m.Vars)
+		}
+	}
+	// Store path variables.
+	if v.path != nil {
+		matches := v.path.regexp.FindStringSubmatchIndex(req.URL.Path)
+		if len(matches) > 0 {
+			extractVars(req.URL.Path, matches, v.path.varsN, m.Vars)
+			// Check if we should redirect.
+			if v.path.strictSlash {
+				p1 := strings.HasSuffix(req.URL.Path, "/")
+				p2 := strings.HasSuffix(v.path.template, "/")
+				if p1 != p2 {
+					u, _ := url.Parse(req.URL.String())
+					if p1 {
+						u.Path = u.Path[:len(u.Path)-1]
+					} else {
+						u.Path += "/"
+					}
+					m.Handler = http.RedirectHandler(u.String(), 301)
+				}
+			}
+		}
+	}
+	// Store query string variables.
+	for _, q := range v.queries {
+		queryURL := q.getURLQuery(req)
+		matches := q.regexp.FindStringSubmatchIndex(queryURL)
+		if len(matches) > 0 {
+			extractVars(queryURL, matches, q.varsN, m.Vars)
+		}
+	}
+}
+
+// getHost tries its best to return the request host.
+func getHost(r *http.Request) string {
+	if r.URL.IsAbs() {
+		return r.URL.Host
+	}
+	host := r.Host
+	// Slice off any port information.
+	if i := strings.Index(host, ":"); i != -1 {
+		host = host[:i]
+	}
+	return host
+
+}
+
+func extractVars(input string, matches []int, names []string, output map[string]string) {
+	matchesCount := 0
+	prevEnd := -1
+	for i := 2; i < len(matches) && matchesCount < len(names); i += 2 {
+		if prevEnd < matches[i+1] {
+			value := input[matches[i]:matches[i+1]]
+			output[names[matchesCount]] = value
+			prevEnd = matches[i+1]
+			matchesCount++
+		}
+	}
+}

+ 634 - 0
src/github.com/gorilla/mux/route.go

@@ -0,0 +1,634 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package mux
+
+import (
+	"errors"
+	"fmt"
+	"net/http"
+	"net/url"
+	"regexp"
+	"strings"
+)
+
+// Route stores information to match a request and build URLs.
+type Route struct {
+	// Parent where the route was registered (a Router).
+	parent parentRoute
+	// Request handler for the route.
+	handler http.Handler
+	// List of matchers.
+	matchers []matcher
+	// Manager for the variables from host and path.
+	regexp *routeRegexpGroup
+	// If true, when the path pattern is "/path/", accessing "/path" will
+	// redirect to the former and vice versa.
+	strictSlash bool
+	// If true, when the path pattern is "/path//to", accessing "/path//to"
+	// will not redirect
+	skipClean bool
+	// If true, this route never matches: it is only used to build URLs.
+	buildOnly bool
+	// The name used to build URLs.
+	name string
+	// Error resulted from building a route.
+	err error
+
+	buildVarsFunc BuildVarsFunc
+}
+
+func (r *Route) SkipClean() bool {
+	return r.skipClean
+}
+
+// Match matches the route against the request.
+func (r *Route) Match(req *http.Request, match *RouteMatch) bool {
+	if r.buildOnly || r.err != nil {
+		return false
+	}
+	// Match everything.
+	for _, m := range r.matchers {
+		if matched := m.Match(req, match); !matched {
+			return false
+		}
+	}
+	// Yay, we have a match. Let's collect some info about it.
+	if match.Route == nil {
+		match.Route = r
+	}
+	if match.Handler == nil {
+		match.Handler = r.handler
+	}
+	if match.Vars == nil {
+		match.Vars = make(map[string]string)
+	}
+	// Set variables.
+	if r.regexp != nil {
+		r.regexp.setMatch(req, match, r)
+	}
+	return true
+}
+
+// ----------------------------------------------------------------------------
+// Route attributes
+// ----------------------------------------------------------------------------
+
+// GetError returns an error resulted from building the route, if any.
+func (r *Route) GetError() error {
+	return r.err
+}
+
+// BuildOnly sets the route to never match: it is only used to build URLs.
+func (r *Route) BuildOnly() *Route {
+	r.buildOnly = true
+	return r
+}
+
+// Handler --------------------------------------------------------------------
+
+// Handler sets a handler for the route.
+func (r *Route) Handler(handler http.Handler) *Route {
+	if r.err == nil {
+		r.handler = handler
+	}
+	return r
+}
+
+// HandlerFunc sets a handler function for the route.
+func (r *Route) HandlerFunc(f func(http.ResponseWriter, *http.Request)) *Route {
+	return r.Handler(http.HandlerFunc(f))
+}
+
+// GetHandler returns the handler for the route, if any.
+func (r *Route) GetHandler() http.Handler {
+	return r.handler
+}
+
+// Name -----------------------------------------------------------------------
+
+// Name sets the name for the route, used to build URLs.
+// If the name was registered already it will be overwritten.
+func (r *Route) Name(name string) *Route {
+	if r.name != "" {
+		r.err = fmt.Errorf("mux: route already has name %q, can't set %q",
+			r.name, name)
+	}
+	if r.err == nil {
+		r.name = name
+		r.getNamedRoutes()[name] = r
+	}
+	return r
+}
+
+// GetName returns the name for the route, if any.
+func (r *Route) GetName() string {
+	return r.name
+}
+
+// ----------------------------------------------------------------------------
+// Matchers
+// ----------------------------------------------------------------------------
+
+// matcher types try to match a request.
+type matcher interface {
+	Match(*http.Request, *RouteMatch) bool
+}
+
+// addMatcher adds a matcher to the route.
+func (r *Route) addMatcher(m matcher) *Route {
+	if r.err == nil {
+		r.matchers = append(r.matchers, m)
+	}
+	return r
+}
+
+// addRegexpMatcher adds a host or path matcher and builder to a route.
+func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix, matchQuery bool) error {
+	if r.err != nil {
+		return r.err
+	}
+	r.regexp = r.getRegexpGroup()
+	if !matchHost && !matchQuery {
+		if len(tpl) == 0 || tpl[0] != '/' {
+			return fmt.Errorf("mux: path must start with a slash, got %q", tpl)
+		}
+		if r.regexp.path != nil {
+			tpl = strings.TrimRight(r.regexp.path.template, "/") + tpl
+		}
+	}
+	rr, err := newRouteRegexp(tpl, matchHost, matchPrefix, matchQuery, r.strictSlash)
+	if err != nil {
+		return err
+	}
+	for _, q := range r.regexp.queries {
+		if err = uniqueVars(rr.varsN, q.varsN); err != nil {
+			return err
+		}
+	}
+	if matchHost {
+		if r.regexp.path != nil {
+			if err = uniqueVars(rr.varsN, r.regexp.path.varsN); err != nil {
+				return err
+			}
+		}
+		r.regexp.host = rr
+	} else {
+		if r.regexp.host != nil {
+			if err = uniqueVars(rr.varsN, r.regexp.host.varsN); err != nil {
+				return err
+			}
+		}
+		if matchQuery {
+			r.regexp.queries = append(r.regexp.queries, rr)
+		} else {
+			r.regexp.path = rr
+		}
+	}
+	r.addMatcher(rr)
+	return nil
+}
+
+// Headers --------------------------------------------------------------------
+
+// headerMatcher matches the request against header values.
+type headerMatcher map[string]string
+
+func (m headerMatcher) Match(r *http.Request, match *RouteMatch) bool {
+	return matchMapWithString(m, r.Header, true)
+}
+
+// Headers adds a matcher for request header values.
+// It accepts a sequence of key/value pairs to be matched. For example:
+//
+//     r := mux.NewRouter()
+//     r.Headers("Content-Type", "application/json",
+//               "X-Requested-With", "XMLHttpRequest")
+//
+// The above route will only match if both request header values match.
+// If the value is an empty string, it will match any value if the key is set.
+func (r *Route) Headers(pairs ...string) *Route {
+	if r.err == nil {
+		var headers map[string]string
+		headers, r.err = mapFromPairsToString(pairs...)
+		return r.addMatcher(headerMatcher(headers))
+	}
+	return r
+}
+
+// headerRegexMatcher matches the request against the route given a regex for the header
+type headerRegexMatcher map[string]*regexp.Regexp
+
+func (m headerRegexMatcher) Match(r *http.Request, match *RouteMatch) bool {
+	return matchMapWithRegex(m, r.Header, true)
+}
+
+// HeadersRegexp accepts a sequence of key/value pairs, where the value has regex
+// support. For example:
+//
+//     r := mux.NewRouter()
+//     r.HeadersRegexp("Content-Type", "application/(text|json)",
+//               "X-Requested-With", "XMLHttpRequest")
+//
+// The above route will only match if both the request header matches both regular expressions.
+// It the value is an empty string, it will match any value if the key is set.
+func (r *Route) HeadersRegexp(pairs ...string) *Route {
+	if r.err == nil {
+		var headers map[string]*regexp.Regexp
+		headers, r.err = mapFromPairsToRegex(pairs...)
+		return r.addMatcher(headerRegexMatcher(headers))
+	}
+	return r
+}
+
+// Host -----------------------------------------------------------------------
+
+// Host adds a matcher for the URL host.
+// It accepts a template with zero or more URL variables enclosed by {}.
+// Variables can define an optional regexp pattern to be matched:
+//
+// - {name} matches anything until the next dot.
+//
+// - {name:pattern} matches the given regexp pattern.
+//
+// For example:
+//
+//     r := mux.NewRouter()
+//     r.Host("www.example.com")
+//     r.Host("{subdomain}.domain.com")
+//     r.Host("{subdomain:[a-z]+}.domain.com")
+//
+// Variable names must be unique in a given route. They can be retrieved
+// calling mux.Vars(request).
+func (r *Route) Host(tpl string) *Route {
+	r.err = r.addRegexpMatcher(tpl, true, false, false)
+	return r
+}
+
+// MatcherFunc ----------------------------------------------------------------
+
+// MatcherFunc is the function signature used by custom matchers.
+type MatcherFunc func(*http.Request, *RouteMatch) bool
+
+// Match returns the match for a given request.
+func (m MatcherFunc) Match(r *http.Request, match *RouteMatch) bool {
+	return m(r, match)
+}
+
+// MatcherFunc adds a custom function to be used as request matcher.
+func (r *Route) MatcherFunc(f MatcherFunc) *Route {
+	return r.addMatcher(f)
+}
+
+// Methods --------------------------------------------------------------------
+
+// methodMatcher matches the request against HTTP methods.
+type methodMatcher []string
+
+func (m methodMatcher) Match(r *http.Request, match *RouteMatch) bool {
+	return matchInArray(m, r.Method)
+}
+
+// Methods adds a matcher for HTTP methods.
+// It accepts a sequence of one or more methods to be matched, e.g.:
+// "GET", "POST", "PUT".
+func (r *Route) Methods(methods ...string) *Route {
+	for k, v := range methods {
+		methods[k] = strings.ToUpper(v)
+	}
+	return r.addMatcher(methodMatcher(methods))
+}
+
+// Path -----------------------------------------------------------------------
+
+// Path adds a matcher for the URL path.
+// It accepts a template with zero or more URL variables enclosed by {}. The
+// template must start with a "/".
+// Variables can define an optional regexp pattern to be matched:
+//
+// - {name} matches anything until the next slash.
+//
+// - {name:pattern} matches the given regexp pattern.
+//
+// For example:
+//
+//     r := mux.NewRouter()
+//     r.Path("/products/").Handler(ProductsHandler)
+//     r.Path("/products/{key}").Handler(ProductsHandler)
+//     r.Path("/articles/{category}/{id:[0-9]+}").
+//       Handler(ArticleHandler)
+//
+// Variable names must be unique in a given route. They can be retrieved
+// calling mux.Vars(request).
+func (r *Route) Path(tpl string) *Route {
+	r.err = r.addRegexpMatcher(tpl, false, false, false)
+	return r
+}
+
+// PathPrefix -----------------------------------------------------------------
+
+// PathPrefix adds a matcher for the URL path prefix. This matches if the given
+// template is a prefix of the full URL path. See Route.Path() for details on
+// the tpl argument.
+//
+// Note that it does not treat slashes specially ("/foobar/" will be matched by
+// the prefix "/foo") so you may want to use a trailing slash here.
+//
+// Also note that the setting of Router.StrictSlash() has no effect on routes
+// with a PathPrefix matcher.
+func (r *Route) PathPrefix(tpl string) *Route {
+	r.err = r.addRegexpMatcher(tpl, false, true, false)
+	return r
+}
+
+// Query ----------------------------------------------------------------------
+
+// Queries adds a matcher for URL query values.
+// It accepts a sequence of key/value pairs. Values may define variables.
+// For example:
+//
+//     r := mux.NewRouter()
+//     r.Queries("foo", "bar", "id", "{id:[0-9]+}")
+//
+// The above route will only match if the URL contains the defined queries
+// values, e.g.: ?foo=bar&id=42.
+//
+// It the value is an empty string, it will match any value if the key is set.
+//
+// Variables can define an optional regexp pattern to be matched:
+//
+// - {name} matches anything until the next slash.
+//
+// - {name:pattern} matches the given regexp pattern.
+func (r *Route) Queries(pairs ...string) *Route {
+	length := len(pairs)
+	if length%2 != 0 {
+		r.err = fmt.Errorf(
+			"mux: number of parameters must be multiple of 2, got %v", pairs)
+		return nil
+	}
+	for i := 0; i < length; i += 2 {
+		if r.err = r.addRegexpMatcher(pairs[i]+"="+pairs[i+1], false, false, true); r.err != nil {
+			return r
+		}
+	}
+
+	return r
+}
+
+// Schemes --------------------------------------------------------------------
+
+// schemeMatcher matches the request against URL schemes.
+type schemeMatcher []string
+
+func (m schemeMatcher) Match(r *http.Request, match *RouteMatch) bool {
+	return matchInArray(m, r.URL.Scheme)
+}
+
+// Schemes adds a matcher for URL schemes.
+// It accepts a sequence of schemes to be matched, e.g.: "http", "https".
+func (r *Route) Schemes(schemes ...string) *Route {
+	for k, v := range schemes {
+		schemes[k] = strings.ToLower(v)
+	}
+	return r.addMatcher(schemeMatcher(schemes))
+}
+
+// BuildVarsFunc --------------------------------------------------------------
+
+// BuildVarsFunc is the function signature used by custom build variable
+// functions (which can modify route variables before a route's URL is built).
+type BuildVarsFunc func(map[string]string) map[string]string
+
+// BuildVarsFunc adds a custom function to be used to modify build variables
+// before a route's URL is built.
+func (r *Route) BuildVarsFunc(f BuildVarsFunc) *Route {
+	r.buildVarsFunc = f
+	return r
+}
+
+// Subrouter ------------------------------------------------------------------
+
+// Subrouter creates a subrouter for the route.
+//
+// It will test the inner routes only if the parent route matched. For example:
+//
+//     r := mux.NewRouter()
+//     s := r.Host("www.example.com").Subrouter()
+//     s.HandleFunc("/products/", ProductsHandler)
+//     s.HandleFunc("/products/{key}", ProductHandler)
+//     s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)
+//
+// Here, the routes registered in the subrouter won't be tested if the host
+// doesn't match.
+func (r *Route) Subrouter() *Router {
+	router := &Router{parent: r, strictSlash: r.strictSlash}
+	r.addMatcher(router)
+	return router
+}
+
+// ----------------------------------------------------------------------------
+// URL building
+// ----------------------------------------------------------------------------
+
+// URL builds a URL for the route.
+//
+// It accepts a sequence of key/value pairs for the route variables. For
+// example, given this route:
+//
+//     r := mux.NewRouter()
+//     r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
+//       Name("article")
+//
+// ...a URL for it can be built using:
+//
+//     url, err := r.Get("article").URL("category", "technology", "id", "42")
+//
+// ...which will return an url.URL with the following path:
+//
+//     "/articles/technology/42"
+//
+// This also works for host variables:
+//
+//     r := mux.NewRouter()
+//     r.Host("{subdomain}.domain.com").
+//       HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
+//       Name("article")
+//
+//     // url.String() will be "http://news.domain.com/articles/technology/42"
+//     url, err := r.Get("article").URL("subdomain", "news",
+//                                      "category", "technology",
+//                                      "id", "42")
+//
+// All variables defined in the route are required, and their values must
+// conform to the corresponding patterns.
+func (r *Route) URL(pairs ...string) (*url.URL, error) {
+	if r.err != nil {
+		return nil, r.err
+	}
+	if r.regexp == nil {
+		return nil, errors.New("mux: route doesn't have a host or path")
+	}
+	values, err := r.prepareVars(pairs...)
+	if err != nil {
+		return nil, err
+	}
+	var scheme, host, path string
+	if r.regexp.host != nil {
+		// Set a default scheme.
+		scheme = "http"
+		if host, err = r.regexp.host.url(values); err != nil {
+			return nil, err
+		}
+	}
+	if r.regexp.path != nil {
+		if path, err = r.regexp.path.url(values); err != nil {
+			return nil, err
+		}
+	}
+	return &url.URL{
+		Scheme: scheme,
+		Host:   host,
+		Path:   path,
+	}, nil
+}
+
+// URLHost builds the host part of the URL for a route. See Route.URL().
+//
+// The route must have a host defined.
+func (r *Route) URLHost(pairs ...string) (*url.URL, error) {
+	if r.err != nil {
+		return nil, r.err
+	}
+	if r.regexp == nil || r.regexp.host == nil {
+		return nil, errors.New("mux: route doesn't have a host")
+	}
+	values, err := r.prepareVars(pairs...)
+	if err != nil {
+		return nil, err
+	}
+	host, err := r.regexp.host.url(values)
+	if err != nil {
+		return nil, err
+	}
+	return &url.URL{
+		Scheme: "http",
+		Host:   host,
+	}, nil
+}
+
+// URLPath builds the path part of the URL for a route. See Route.URL().
+//
+// The route must have a path defined.
+func (r *Route) URLPath(pairs ...string) (*url.URL, error) {
+	if r.err != nil {
+		return nil, r.err
+	}
+	if r.regexp == nil || r.regexp.path == nil {
+		return nil, errors.New("mux: route doesn't have a path")
+	}
+	values, err := r.prepareVars(pairs...)
+	if err != nil {
+		return nil, err
+	}
+	path, err := r.regexp.path.url(values)
+	if err != nil {
+		return nil, err
+	}
+	return &url.URL{
+		Path: path,
+	}, nil
+}
+
+// GetPathTemplate returns the template used to build the
+// route match.
+// This is useful for building simple REST API documentation and for instrumentation
+// against third-party services.
+// An error will be returned if the route does not define a path.
+func (r *Route) GetPathTemplate() (string, error) {
+	if r.err != nil {
+		return "", r.err
+	}
+	if r.regexp == nil || r.regexp.path == nil {
+		return "", errors.New("mux: route doesn't have a path")
+	}
+	return r.regexp.path.template, nil
+}
+
+// GetHostTemplate returns the template used to build the
+// route match.
+// This is useful for building simple REST API documentation and for instrumentation
+// against third-party services.
+// An error will be returned if the route does not define a host.
+func (r *Route) GetHostTemplate() (string, error) {
+	if r.err != nil {
+		return "", r.err
+	}
+	if r.regexp == nil || r.regexp.host == nil {
+		return "", errors.New("mux: route doesn't have a host")
+	}
+	return r.regexp.host.template, nil
+}
+
+// prepareVars converts the route variable pairs into a map. If the route has a
+// BuildVarsFunc, it is invoked.
+func (r *Route) prepareVars(pairs ...string) (map[string]string, error) {
+	m, err := mapFromPairsToString(pairs...)
+	if err != nil {
+		return nil, err
+	}
+	return r.buildVars(m), nil
+}
+
+func (r *Route) buildVars(m map[string]string) map[string]string {
+	if r.parent != nil {
+		m = r.parent.buildVars(m)
+	}
+	if r.buildVarsFunc != nil {
+		m = r.buildVarsFunc(m)
+	}
+	return m
+}
+
+// ----------------------------------------------------------------------------
+// parentRoute
+// ----------------------------------------------------------------------------
+
+// parentRoute allows routes to know about parent host and path definitions.
+type parentRoute interface {
+	getNamedRoutes() map[string]*Route
+	getRegexpGroup() *routeRegexpGroup
+	buildVars(map[string]string) map[string]string
+}
+
+// getNamedRoutes returns the map where named routes are registered.
+func (r *Route) getNamedRoutes() map[string]*Route {
+	if r.parent == nil {
+		// During tests router is not always set.
+		r.parent = NewRouter()
+	}
+	return r.parent.getNamedRoutes()
+}
+
+// getRegexpGroup returns regexp definitions from this route.
+func (r *Route) getRegexpGroup() *routeRegexpGroup {
+	if r.regexp == nil {
+		if r.parent == nil {
+			// During tests router is not always set.
+			r.parent = NewRouter()
+		}
+		regexp := r.parent.getRegexpGroup()
+		if regexp == nil {
+			r.regexp = new(routeRegexpGroup)
+		} else {
+			// Copy.
+			r.regexp = &routeRegexpGroup{
+				host:    regexp.host,
+				path:    regexp.path,
+				queries: regexp.queries,
+			}
+		}
+	}
+	return r.regexp
+}

+ 18 - 0
src/github.com/gorilla/securecookie/.travis.yml

@@ -0,0 +1,18 @@
+language: go
+sudo: false
+
+matrix:
+  include:
+    - go: 1.3
+    - go: 1.4
+    - go: 1.5
+    - go: 1.6
+    - go: tip
+  allow_failures:
+    - go: tip
+
+script:
+  - go get -t -v ./...
+  - diff -u <(echo -n) <(gofmt -d .)
+  - go vet $(go list ./... | grep -v /vendor/)
+  - go test -v -race ./...

+ 27 - 0
src/github.com/gorilla/securecookie/LICENSE

@@ -0,0 +1,27 @@
+Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+	 * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+	 * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+	 * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 78 - 0
src/github.com/gorilla/securecookie/README.md

@@ -0,0 +1,78 @@
+securecookie
+============
+[![GoDoc](https://godoc.org/github.com/gorilla/securecookie?status.svg)](https://godoc.org/github.com/gorilla/securecookie) [![Build Status](https://travis-ci.org/gorilla/securecookie.png?branch=master)](https://travis-ci.org/gorilla/securecookie)
+
+securecookie encodes and decodes authenticated and optionally encrypted 
+cookie values.
+
+Secure cookies can't be forged, because their values are validated using HMAC.
+When encrypted, the content is also inaccessible to malicious eyes. It is still
+recommended that sensitive data not be stored in cookies, and that HTTPS be used
+to prevent cookie [replay attacks](https://en.wikipedia.org/wiki/Replay_attack).
+
+## Examples
+
+To use it, first create a new SecureCookie instance:
+
+```go
+// Hash keys should be at least 32 bytes long
+var hashKey = []byte("very-secret")
+// Block keys should be 16 bytes (AES-128) or 32 bytes (AES-256) long.
+// Shorter keys may weaken the encryption used.
+var blockKey = []byte("a-lot-secret")
+var s = securecookie.New(hashKey, blockKey)
+```
+
+The hashKey is required, used to authenticate the cookie value using HMAC.
+It is recommended to use a key with 32 or 64 bytes.
+
+The blockKey is optional, used to encrypt the cookie value -- set it to nil
+to not use encryption. If set, the length must correspond to the block size
+of the encryption algorithm. For AES, used by default, valid lengths are
+16, 24, or 32 bytes to select AES-128, AES-192, or AES-256.
+
+Strong keys can be created using the convenience function GenerateRandomKey().
+
+Once a SecureCookie instance is set, use it to encode a cookie value:
+
+```go
+func SetCookieHandler(w http.ResponseWriter, r *http.Request) {
+	value := map[string]string{
+		"foo": "bar",
+	}
+	if encoded, err := s.Encode("cookie-name", value); err == nil {
+		cookie := &http.Cookie{
+			Name:  "cookie-name",
+			Value: encoded,
+			Path:  "/",
+			Secure: true,
+			HttpOnly: true,
+		}
+		http.SetCookie(w, cookie)
+	}
+}
+```
+
+Later, use the same SecureCookie instance to decode and validate a cookie
+value:
+
+```go
+func ReadCookieHandler(w http.ResponseWriter, r *http.Request) {
+	if cookie, err := r.Cookie("cookie-name"); err == nil {
+		value := make(map[string]string)
+		if err = s2.Decode("cookie-name", cookie.Value, &value); err == nil {
+			fmt.Fprintf(w, "The value of foo is %q", value["foo"])
+		}
+	}
+}
+```
+
+We stored a map[string]string, but secure cookies can hold any value that
+can be encoded using `encoding/gob`. To store custom types, they must be
+registered first using gob.Register(). For basic types this is not needed;
+it works out of the box. An optional JSON encoder that uses `encoding/json` is
+available for types compatible with JSON.
+
+## License
+
+BSD licensed. See the LICENSE file for details.

+ 61 - 0
src/github.com/gorilla/securecookie/doc.go

@@ -0,0 +1,61 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+/*
+Package securecookie encodes and decodes authenticated and optionally
+encrypted cookie values.
+
+Secure cookies can't be forged, because their values are validated using HMAC.
+When encrypted, the content is also inaccessible to malicious eyes.
+
+To use it, first create a new SecureCookie instance:
+
+	var hashKey = []byte("very-secret")
+	var blockKey = []byte("a-lot-secret")
+	var s = securecookie.New(hashKey, blockKey)
+
+The hashKey is required, used to authenticate the cookie value using HMAC.
+It is recommended to use a key with 32 or 64 bytes.
+
+The blockKey is optional, used to encrypt the cookie value -- set it to nil
+to not use encryption. If set, the length must correspond to the block size
+of the encryption algorithm. For AES, used by default, valid lengths are
+16, 24, or 32 bytes to select AES-128, AES-192, or AES-256.
+
+Strong keys can be created using the convenience function GenerateRandomKey().
+
+Once a SecureCookie instance is set, use it to encode a cookie value:
+
+	func SetCookieHandler(w http.ResponseWriter, r *http.Request) {
+		value := map[string]string{
+			"foo": "bar",
+		}
+		if encoded, err := s.Encode("cookie-name", value); err == nil {
+			cookie := &http.Cookie{
+				Name:  "cookie-name",
+				Value: encoded,
+				Path:  "/",
+			}
+			http.SetCookie(w, cookie)
+		}
+	}
+
+Later, use the same SecureCookie instance to decode and validate a cookie
+value:
+
+	func ReadCookieHandler(w http.ResponseWriter, r *http.Request) {
+		if cookie, err := r.Cookie("cookie-name"); err == nil {
+			value := make(map[string]string)
+			if err = s2.Decode("cookie-name", cookie.Value, &value); err == nil {
+				fmt.Fprintf(w, "The value of foo is %q", value["foo"])
+			}
+		}
+	}
+
+We stored a map[string]string, but secure cookies can hold any value that
+can be encoded using encoding/gob. To store custom types, they must be
+registered first using gob.Register(). For basic types this is not needed;
+it works out of the box.
+*/
+package securecookie

+ 25 - 0
src/github.com/gorilla/securecookie/fuzz.go

@@ -0,0 +1,25 @@
+// +build gofuzz
+
+package securecookie
+
+var hashKey = []byte("very-secret12345")
+var blockKey = []byte("a-lot-secret1234")
+var s = New(hashKey, blockKey)
+
+type Cookie struct {
+	B bool
+	I int
+	S string
+}
+
+func Fuzz(data []byte) int {
+	datas := string(data)
+	var c Cookie
+	if err := s.Decode("fuzz", datas, &c); err != nil {
+		return 0
+	}
+	if _, err := s.Encode("fuzz", c); err != nil {
+		panic(err)
+	}
+	return 1
+}

+ 47 - 0
src/github.com/gorilla/securecookie/fuzz/gencorpus.go

@@ -0,0 +1,47 @@
+package main
+
+import (
+	"fmt"
+	"io"
+	"math/rand"
+	"os"
+	"reflect"
+	"testing/quick"
+
+	"github.com/gorilla/securecookie"
+)
+
+var hashKey = []byte("very-secret12345")
+var blockKey = []byte("a-lot-secret1234")
+var s = securecookie.New(hashKey, blockKey)
+
+type Cookie struct {
+	B bool
+	I int
+	S string
+}
+
+func main() {
+	var c Cookie
+	t := reflect.TypeOf(c)
+	rnd := rand.New(rand.NewSource(0))
+	for i := 0; i < 100; i++ {
+		v, ok := quick.Value(t, rnd)
+		if !ok {
+			panic("couldn't generate value")
+		}
+		encoded, err := s.Encode("fuzz", v.Interface())
+		if err != nil {
+			panic(err)
+		}
+		f, err := os.Create(fmt.Sprintf("corpus/%d.sc", i))
+		if err != nil {
+			panic(err)
+		}
+		_, err = io.WriteString(f, encoded)
+		if err != nil {
+			panic(err)
+		}
+		f.Close()
+	}
+}

+ 646 - 0
src/github.com/gorilla/securecookie/securecookie.go

@@ -0,0 +1,646 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package securecookie
+
+import (
+	"bytes"
+	"crypto/aes"
+	"crypto/cipher"
+	"crypto/hmac"
+	"crypto/rand"
+	"crypto/sha256"
+	"crypto/subtle"
+	"encoding/base64"
+	"encoding/gob"
+	"encoding/json"
+	"fmt"
+	"hash"
+	"io"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// Error is the interface of all errors returned by functions in this library.
+type Error interface {
+	error
+
+	// IsUsage returns true for errors indicating the client code probably
+	// uses this library incorrectly.  For example, the client may have
+	// failed to provide a valid hash key, or may have failed to configure
+	// the Serializer adequately for encoding value.
+	IsUsage() bool
+
+	// IsDecode returns true for errors indicating that a cookie could not
+	// be decoded and validated.  Since cookies are usually untrusted
+	// user-provided input, errors of this type should be expected.
+	// Usually, the proper action is simply to reject the request.
+	IsDecode() bool
+
+	// IsInternal returns true for unexpected errors occurring in the
+	// securecookie implementation.
+	IsInternal() bool
+
+	// Cause, if it returns a non-nil value, indicates that this error was
+	// propagated from some underlying library.  If this method returns nil,
+	// this error was raised directly by this library.
+	//
+	// Cause is provided principally for debugging/logging purposes; it is
+	// rare that application logic should perform meaningfully different
+	// logic based on Cause.  See, for example, the caveats described on
+	// (MultiError).Cause().
+	Cause() error
+}
+
+// errorType is a bitmask giving the error type(s) of an cookieError value.
+type errorType int
+
+const (
+	usageError = errorType(1 << iota)
+	decodeError
+	internalError
+)
+
+type cookieError struct {
+	typ   errorType
+	msg   string
+	cause error
+}
+
+func (e cookieError) IsUsage() bool    { return (e.typ & usageError) != 0 }
+func (e cookieError) IsDecode() bool   { return (e.typ & decodeError) != 0 }
+func (e cookieError) IsInternal() bool { return (e.typ & internalError) != 0 }
+
+func (e cookieError) Cause() error { return e.cause }
+
+func (e cookieError) Error() string {
+	parts := []string{"securecookie: "}
+	if e.msg == "" {
+		parts = append(parts, "error")
+	} else {
+		parts = append(parts, e.msg)
+	}
+	if c := e.Cause(); c != nil {
+		parts = append(parts, " - caused by: ", c.Error())
+	}
+	return strings.Join(parts, "")
+}
+
+var (
+	errGeneratingIV = cookieError{typ: internalError, msg: "failed to generate random iv"}
+
+	errNoCodecs            = cookieError{typ: usageError, msg: "no codecs provided"}
+	errHashKeyNotSet       = cookieError{typ: usageError, msg: "hash key is not set"}
+	errBlockKeyNotSet      = cookieError{typ: usageError, msg: "block key is not set"}
+	errEncodedValueTooLong = cookieError{typ: usageError, msg: "the value is too long"}
+
+	errValueToDecodeTooLong = cookieError{typ: decodeError, msg: "the value is too long"}
+	errTimestampInvalid     = cookieError{typ: decodeError, msg: "invalid timestamp"}
+	errTimestampTooNew      = cookieError{typ: decodeError, msg: "timestamp is too new"}
+	errTimestampExpired     = cookieError{typ: decodeError, msg: "expired timestamp"}
+	errDecryptionFailed     = cookieError{typ: decodeError, msg: "the value could not be decrypted"}
+	errValueNotByte         = cookieError{typ: decodeError, msg: "value not a []byte."}
+
+	// ErrMacInvalid indicates that cookie decoding failed because the HMAC
+	// could not be extracted and verified.  Direct use of this error
+	// variable is deprecated; it is public only for legacy compatibility,
+	// and may be privatized in the future, as it is rarely useful to
+	// distinguish between this error and other Error implementations.
+	ErrMacInvalid = cookieError{typ: decodeError, msg: "the value is not valid"}
+)
+
+// Codec defines an interface to encode and decode cookie values.
+type Codec interface {
+	Encode(name string, value interface{}) (string, error)
+	Decode(name, value string, dst interface{}) error
+}
+
+// New returns a new SecureCookie.
+//
+// hashKey is required, used to authenticate values using HMAC. Create it using
+// GenerateRandomKey(). It is recommended to use a key with 32 or 64 bytes.
+//
+// blockKey is optional, used to encrypt values. Create it using
+// GenerateRandomKey(). The key length must correspond to the block size
+// of the encryption algorithm. For AES, used by default, valid lengths are
+// 16, 24, or 32 bytes to select AES-128, AES-192, or AES-256.
+// The default encoder used for cookie serialization is encoding/gob.
+//
+// Note that keys created using GenerateRandomKey() are not automatically
+// persisted. New keys will be created when the application is restarted, and
+// previously issued cookies will not be able to be decoded.
+func New(hashKey, blockKey []byte) *SecureCookie {
+	s := &SecureCookie{
+		hashKey:   hashKey,
+		blockKey:  blockKey,
+		hashFunc:  sha256.New,
+		maxAge:    86400 * 30,
+		maxLength: 4096,
+		sz:        GobEncoder{},
+	}
+	if hashKey == nil {
+		s.err = errHashKeyNotSet
+	}
+	if blockKey != nil {
+		s.BlockFunc(aes.NewCipher)
+	}
+	return s
+}
+
+// SecureCookie encodes and decodes authenticated and optionally encrypted
+// cookie values.
+type SecureCookie struct {
+	hashKey   []byte
+	hashFunc  func() hash.Hash
+	blockKey  []byte
+	block     cipher.Block
+	maxLength int
+	maxAge    int64
+	minAge    int64
+	err       error
+	sz        Serializer
+	// For testing purposes, the function that returns the current timestamp.
+	// If not set, it will use time.Now().UTC().Unix().
+	timeFunc func() int64
+}
+
+// Serializer provides an interface for providing custom serializers for cookie
+// values.
+type Serializer interface {
+	Serialize(src interface{}) ([]byte, error)
+	Deserialize(src []byte, dst interface{}) error
+}
+
+// GobEncoder encodes cookie values using encoding/gob. This is the simplest
+// encoder and can handle complex types via gob.Register.
+type GobEncoder struct{}
+
+// JSONEncoder encodes cookie values using encoding/json. Users who wish to
+// encode complex types need to satisfy the json.Marshaller and
+// json.Unmarshaller interfaces.
+type JSONEncoder struct{}
+
+// NopEncoder does not encode cookie values, and instead simply accepts a []byte
+// (as an interface{}) and returns a []byte. This is particularly useful when
+// you encoding an object upstream and do not wish to re-encode it.
+type NopEncoder struct{}
+
+// MaxLength restricts the maximum length, in bytes, for the cookie value.
+//
+// Default is 4096, which is the maximum value accepted by Internet Explorer.
+func (s *SecureCookie) MaxLength(value int) *SecureCookie {
+	s.maxLength = value
+	return s
+}
+
+// MaxAge restricts the maximum age, in seconds, for the cookie value.
+//
+// Default is 86400 * 30. Set it to 0 for no restriction.
+func (s *SecureCookie) MaxAge(value int) *SecureCookie {
+	s.maxAge = int64(value)
+	return s
+}
+
+// MinAge restricts the minimum age, in seconds, for the cookie value.
+//
+// Default is 0 (no restriction).
+func (s *SecureCookie) MinAge(value int) *SecureCookie {
+	s.minAge = int64(value)
+	return s
+}
+
+// HashFunc sets the hash function used to create HMAC.
+//
+// Default is crypto/sha256.New.
+func (s *SecureCookie) HashFunc(f func() hash.Hash) *SecureCookie {
+	s.hashFunc = f
+	return s
+}
+
+// BlockFunc sets the encryption function used to create a cipher.Block.
+//
+// Default is crypto/aes.New.
+func (s *SecureCookie) BlockFunc(f func([]byte) (cipher.Block, error)) *SecureCookie {
+	if s.blockKey == nil {
+		s.err = errBlockKeyNotSet
+	} else if block, err := f(s.blockKey); err == nil {
+		s.block = block
+	} else {
+		s.err = cookieError{cause: err, typ: usageError}
+	}
+	return s
+}
+
+// Encoding sets the encoding/serialization method for cookies.
+//
+// Default is encoding/gob.  To encode special structures using encoding/gob,
+// they must be registered first using gob.Register().
+func (s *SecureCookie) SetSerializer(sz Serializer) *SecureCookie {
+	s.sz = sz
+
+	return s
+}
+
+// Encode encodes a cookie value.
+//
+// It serializes, optionally encrypts, signs with a message authentication code,
+// and finally encodes the value.
+//
+// The name argument is the cookie name. It is stored with the encoded value.
+// The value argument is the value to be encoded. It can be any value that can
+// be encoded using the currently selected serializer; see SetSerializer().
+//
+// It is the client's responsibility to ensure that value, when encoded using
+// the current serialization/encryption settings on s and then base64-encoded,
+// is shorter than the maximum permissible length.
+func (s *SecureCookie) Encode(name string, value interface{}) (string, error) {
+	if s.err != nil {
+		return "", s.err
+	}
+	if s.hashKey == nil {
+		s.err = errHashKeyNotSet
+		return "", s.err
+	}
+	var err error
+	var b []byte
+	// 1. Serialize.
+	if b, err = s.sz.Serialize(value); err != nil {
+		return "", cookieError{cause: err, typ: usageError}
+	}
+	// 2. Encrypt (optional).
+	if s.block != nil {
+		if b, err = encrypt(s.block, b); err != nil {
+			return "", cookieError{cause: err, typ: usageError}
+		}
+	}
+	b = encode(b)
+	// 3. Create MAC for "name|date|value". Extra pipe to be used later.
+	b = []byte(fmt.Sprintf("%s|%d|%s|", name, s.timestamp(), b))
+	mac := createMac(hmac.New(s.hashFunc, s.hashKey), b[:len(b)-1])
+	// Append mac, remove name.
+	b = append(b, mac...)[len(name)+1:]
+	// 4. Encode to base64.
+	b = encode(b)
+	// 5. Check length.
+	if s.maxLength != 0 && len(b) > s.maxLength {
+		return "", errEncodedValueTooLong
+	}
+	// Done.
+	return string(b), nil
+}
+
+// Decode decodes a cookie value.
+//
+// It decodes, verifies a message authentication code, optionally decrypts and
+// finally deserializes the value.
+//
+// The name argument is the cookie name. It must be the same name used when
+// it was stored. The value argument is the encoded cookie value. The dst
+// argument is where the cookie will be decoded. It must be a pointer.
+func (s *SecureCookie) Decode(name, value string, dst interface{}) error {
+	if s.err != nil {
+		return s.err
+	}
+	if s.hashKey == nil {
+		s.err = errHashKeyNotSet
+		return s.err
+	}
+	// 1. Check length.
+	if s.maxLength != 0 && len(value) > s.maxLength {
+		return errValueToDecodeTooLong
+	}
+	// 2. Decode from base64.
+	b, err := decode([]byte(value))
+	if err != nil {
+		return err
+	}
+	// 3. Verify MAC. Value is "date|value|mac".
+	parts := bytes.SplitN(b, []byte("|"), 3)
+	if len(parts) != 3 {
+		return ErrMacInvalid
+	}
+	h := hmac.New(s.hashFunc, s.hashKey)
+	b = append([]byte(name+"|"), b[:len(b)-len(parts[2])-1]...)
+	if err = verifyMac(h, b, parts[2]); err != nil {
+		return err
+	}
+	// 4. Verify date ranges.
+	var t1 int64
+	if t1, err = strconv.ParseInt(string(parts[0]), 10, 64); err != nil {
+		return errTimestampInvalid
+	}
+	t2 := s.timestamp()
+	if s.minAge != 0 && t1 > t2-s.minAge {
+		return errTimestampTooNew
+	}
+	if s.maxAge != 0 && t1 < t2-s.maxAge {
+		return errTimestampExpired
+	}
+	// 5. Decrypt (optional).
+	b, err = decode(parts[1])
+	if err != nil {
+		return err
+	}
+	if s.block != nil {
+		if b, err = decrypt(s.block, b); err != nil {
+			return err
+		}
+	}
+	// 6. Deserialize.
+	if err = s.sz.Deserialize(b, dst); err != nil {
+		return cookieError{cause: err, typ: decodeError}
+	}
+	// Done.
+	return nil
+}
+
+// timestamp returns the current timestamp, in seconds.
+//
+// For testing purposes, the function that generates the timestamp can be
+// overridden. If not set, it will return time.Now().UTC().Unix().
+func (s *SecureCookie) timestamp() int64 {
+	if s.timeFunc == nil {
+		return time.Now().UTC().Unix()
+	}
+	return s.timeFunc()
+}
+
+// Authentication -------------------------------------------------------------
+
+// createMac creates a message authentication code (MAC).
+func createMac(h hash.Hash, value []byte) []byte {
+	h.Write(value)
+	return h.Sum(nil)
+}
+
+// verifyMac verifies that a message authentication code (MAC) is valid.
+func verifyMac(h hash.Hash, value []byte, mac []byte) error {
+	mac2 := createMac(h, value)
+	// Check that both MACs are of equal length, as subtle.ConstantTimeCompare
+	// does not do this prior to Go 1.4.
+	if len(mac) == len(mac2) && subtle.ConstantTimeCompare(mac, mac2) == 1 {
+		return nil
+	}
+	return ErrMacInvalid
+}
+
+// Encryption -----------------------------------------------------------------
+
+// encrypt encrypts a value using the given block in counter mode.
+//
+// A random initialization vector (http://goo.gl/zF67k) with the length of the
+// block size is prepended to the resulting ciphertext.
+func encrypt(block cipher.Block, value []byte) ([]byte, error) {
+	iv := GenerateRandomKey(block.BlockSize())
+	if iv == nil {
+		return nil, errGeneratingIV
+	}
+	// Encrypt it.
+	stream := cipher.NewCTR(block, iv)
+	stream.XORKeyStream(value, value)
+	// Return iv + ciphertext.
+	return append(iv, value...), nil
+}
+
+// decrypt decrypts a value using the given block in counter mode.
+//
+// The value to be decrypted must be prepended by a initialization vector
+// (http://goo.gl/zF67k) with the length of the block size.
+func decrypt(block cipher.Block, value []byte) ([]byte, error) {
+	size := block.BlockSize()
+	if len(value) > size {
+		// Extract iv.
+		iv := value[:size]
+		// Extract ciphertext.
+		value = value[size:]
+		// Decrypt it.
+		stream := cipher.NewCTR(block, iv)
+		stream.XORKeyStream(value, value)
+		return value, nil
+	}
+	return nil, errDecryptionFailed
+}
+
+// Serialization --------------------------------------------------------------
+
+// Serialize encodes a value using gob.
+func (e GobEncoder) Serialize(src interface{}) ([]byte, error) {
+	buf := new(bytes.Buffer)
+	enc := gob.NewEncoder(buf)
+	if err := enc.Encode(src); err != nil {
+		return nil, cookieError{cause: err, typ: usageError}
+	}
+	return buf.Bytes(), nil
+}
+
+// Deserialize decodes a value using gob.
+func (e GobEncoder) Deserialize(src []byte, dst interface{}) error {
+	dec := gob.NewDecoder(bytes.NewBuffer(src))
+	if err := dec.Decode(dst); err != nil {
+		return cookieError{cause: err, typ: decodeError}
+	}
+	return nil
+}
+
+// Serialize encodes a value using encoding/json.
+func (e JSONEncoder) Serialize(src interface{}) ([]byte, error) {
+	buf := new(bytes.Buffer)
+	enc := json.NewEncoder(buf)
+	if err := enc.Encode(src); err != nil {
+		return nil, cookieError{cause: err, typ: usageError}
+	}
+	return buf.Bytes(), nil
+}
+
+// Deserialize decodes a value using encoding/json.
+func (e JSONEncoder) Deserialize(src []byte, dst interface{}) error {
+	dec := json.NewDecoder(bytes.NewReader(src))
+	if err := dec.Decode(dst); err != nil {
+		return cookieError{cause: err, typ: decodeError}
+	}
+	return nil
+}
+
+// Serialize passes a []byte through as-is.
+func (e NopEncoder) Serialize(src interface{}) ([]byte, error) {
+	if b, ok := src.([]byte); ok {
+		return b, nil
+	}
+
+	return nil, errValueNotByte
+}
+
+// Deserialize passes a []byte through as-is.
+func (e NopEncoder) Deserialize(src []byte, dst interface{}) error {
+	if _, ok := dst.([]byte); ok {
+		dst = src
+		return nil
+	}
+
+	return errValueNotByte
+}
+
+// Encoding -------------------------------------------------------------------
+
+// encode encodes a value using base64.
+func encode(value []byte) []byte {
+	encoded := make([]byte, base64.URLEncoding.EncodedLen(len(value)))
+	base64.URLEncoding.Encode(encoded, value)
+	return encoded
+}
+
+// decode decodes a cookie using base64.
+func decode(value []byte) ([]byte, error) {
+	decoded := make([]byte, base64.URLEncoding.DecodedLen(len(value)))
+	b, err := base64.URLEncoding.Decode(decoded, value)
+	if err != nil {
+		return nil, cookieError{cause: err, typ: decodeError, msg: "base64 decode failed"}
+	}
+	return decoded[:b], nil
+}
+
+// Helpers --------------------------------------------------------------------
+
+// GenerateRandomKey creates a random key with the given length in bytes.
+// On failure, returns nil.
+//
+// Callers should explicitly check for the possibility of a nil return, treat
+// it as a failure of the system random number generator, and not continue.
+func GenerateRandomKey(length int) []byte {
+	k := make([]byte, length)
+	if _, err := io.ReadFull(rand.Reader, k); err != nil {
+		return nil
+	}
+	return k
+}
+
+// CodecsFromPairs returns a slice of SecureCookie instances.
+//
+// It is a convenience function to create a list of codecs for key rotation. Note
+// that the generated Codecs will have the default options applied: callers
+// should iterate over each Codec and type-assert the underlying *SecureCookie to
+// change these.
+//
+// Example:
+//
+//      codecs := securecookie.CodecsFromPairs(
+//           []byte("new-hash-key"),
+//           []byte("new-block-key"),
+//           []byte("old-hash-key"),
+//           []byte("old-block-key"),
+//       )
+//
+//      // Modify each instance.
+//      for _, s := range codecs {
+//             if cookie, ok := s.(*securecookie.SecureCookie); ok {
+//                 cookie.MaxAge(86400 * 7)
+//                 cookie.SetSerializer(securecookie.JSONEncoder{})
+//                 cookie.HashFunc(sha512.New512_256)
+//             }
+//         }
+//
+func CodecsFromPairs(keyPairs ...[]byte) []Codec {
+	codecs := make([]Codec, len(keyPairs)/2+len(keyPairs)%2)
+	for i := 0; i < len(keyPairs); i += 2 {
+		var blockKey []byte
+		if i+1 < len(keyPairs) {
+			blockKey = keyPairs[i+1]
+		}
+		codecs[i/2] = New(keyPairs[i], blockKey)
+	}
+	return codecs
+}
+
+// EncodeMulti encodes a cookie value using a group of codecs.
+//
+// The codecs are tried in order. Multiple codecs are accepted to allow
+// key rotation.
+//
+// On error, may return a MultiError.
+func EncodeMulti(name string, value interface{}, codecs ...Codec) (string, error) {
+	if len(codecs) == 0 {
+		return "", errNoCodecs
+	}
+
+	var errors MultiError
+	for _, codec := range codecs {
+		encoded, err := codec.Encode(name, value)
+		if err == nil {
+			return encoded, nil
+		}
+		errors = append(errors, err)
+	}
+	return "", errors
+}
+
+// DecodeMulti decodes a cookie value using a group of codecs.
+//
+// The codecs are tried in order. Multiple codecs are accepted to allow
+// key rotation.
+//
+// On error, may return a MultiError.
+func DecodeMulti(name string, value string, dst interface{}, codecs ...Codec) error {
+	if len(codecs) == 0 {
+		return errNoCodecs
+	}
+
+	var errors MultiError
+	for _, codec := range codecs {
+		err := codec.Decode(name, value, dst)
+		if err == nil {
+			return nil
+		}
+		errors = append(errors, err)
+	}
+	return errors
+}
+
+// MultiError groups multiple errors.
+type MultiError []error
+
+func (m MultiError) IsUsage() bool    { return m.any(func(e Error) bool { return e.IsUsage() }) }
+func (m MultiError) IsDecode() bool   { return m.any(func(e Error) bool { return e.IsDecode() }) }
+func (m MultiError) IsInternal() bool { return m.any(func(e Error) bool { return e.IsInternal() }) }
+
+// Cause returns nil for MultiError; there is no unique underlying cause in the
+// general case.
+//
+// Note: we could conceivably return a non-nil Cause only when there is exactly
+// one child error with a Cause.  However, it would be brittle for client code
+// to rely on the arity of causes inside a MultiError, so we have opted not to
+// provide this functionality.  Clients which really wish to access the Causes
+// of the underlying errors are free to iterate through the errors themselves.
+func (m MultiError) Cause() error { return nil }
+
+func (m MultiError) Error() string {
+	s, n := "", 0
+	for _, e := range m {
+		if e != nil {
+			if n == 0 {
+				s = e.Error()
+			}
+			n++
+		}
+	}
+	switch n {
+	case 0:
+		return "(0 errors)"
+	case 1:
+		return s
+	case 2:
+		return s + " (and 1 other error)"
+	}
+	return fmt.Sprintf("%s (and %d other errors)", s, n-1)
+}
+
+// any returns true if any element of m is an Error for which pred returns true.
+func (m MultiError) any(pred func(Error) bool) bool {
+	for _, e := range m {
+		if ourErr, ok := e.(Error); ok && pred(ourErr) {
+			return true
+		}
+	}
+	return false
+}

+ 274 - 0
src/github.com/gorilla/securecookie/securecookie_test.go

@@ -0,0 +1,274 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package securecookie
+
+import (
+	"crypto/aes"
+	"crypto/hmac"
+	"crypto/sha256"
+	"encoding/base64"
+	"fmt"
+	"reflect"
+	"strings"
+	"testing"
+)
+
+// Asserts that cookieError and MultiError are Error implementations.
+var _ Error = cookieError{}
+var _ Error = MultiError{}
+
+var testCookies = []interface{}{
+	map[string]string{"foo": "bar"},
+	map[string]string{"baz": "ding"},
+}
+
+var testStrings = []string{"foo", "bar", "baz"}
+
+func TestSecureCookie(t *testing.T) {
+	// TODO test too old / too new timestamps
+	s1 := New([]byte("12345"), []byte("1234567890123456"))
+	s2 := New([]byte("54321"), []byte("6543210987654321"))
+	value := map[string]interface{}{
+		"foo": "bar",
+		"baz": 128,
+	}
+
+	for i := 0; i < 50; i++ {
+		// Running this multiple times to check if any special character
+		// breaks encoding/decoding.
+		encoded, err1 := s1.Encode("sid", value)
+		if err1 != nil {
+			t.Error(err1)
+			continue
+		}
+		dst := make(map[string]interface{})
+		err2 := s1.Decode("sid", encoded, &dst)
+		if err2 != nil {
+			t.Fatalf("%v: %v", err2, encoded)
+		}
+		if !reflect.DeepEqual(dst, value) {
+			t.Fatalf("Expected %v, got %v.", value, dst)
+		}
+		dst2 := make(map[string]interface{})
+		err3 := s2.Decode("sid", encoded, &dst2)
+		if err3 == nil {
+			t.Fatalf("Expected failure decoding.")
+		}
+		err4, ok := err3.(Error)
+		if !ok {
+			t.Fatalf("Expected error to implement Error, got: %#v", err3)
+		}
+		if !err4.IsDecode() {
+			t.Fatalf("Expected DecodeError, got: %#v", err4)
+		}
+
+		// Test other error type flags.
+		if err4.IsUsage() {
+			t.Fatalf("Expected IsUsage() == false, got: %#v", err4)
+		}
+		if err4.IsInternal() {
+			t.Fatalf("Expected IsInternal() == false, got: %#v", err4)
+		}
+	}
+}
+
+func TestSecureCookieNilKey(t *testing.T) {
+	s1 := New(nil, nil)
+	value := map[string]interface{}{
+		"foo": "bar",
+		"baz": 128,
+	}
+	_, err := s1.Encode("sid", value)
+	if err != errHashKeyNotSet {
+		t.Fatal("Wrong error returned:", err)
+	}
+}
+
+func TestDecodeInvalid(t *testing.T) {
+	// List of invalid cookies, which must not be accepted, base64-decoded
+	// (they will be encoded before passing to Decode).
+	invalidCookies := []string{
+		"",
+		" ",
+		"\n",
+		"||",
+		"|||",
+		"cookie",
+	}
+	s := New([]byte("12345"), nil)
+	var dst string
+	for i, v := range invalidCookies {
+		for _, enc := range []*base64.Encoding{
+			base64.StdEncoding,
+			base64.URLEncoding,
+		} {
+			err := s.Decode("name", enc.EncodeToString([]byte(v)), &dst)
+			if err == nil {
+				t.Fatalf("%d: expected failure decoding", i)
+			}
+			err2, ok := err.(Error)
+			if !ok || !err2.IsDecode() {
+				t.Fatalf("%d: Expected IsDecode(), got: %#v", i, err)
+			}
+		}
+	}
+}
+
+func TestAuthentication(t *testing.T) {
+	hash := hmac.New(sha256.New, []byte("secret-key"))
+	for _, value := range testStrings {
+		hash.Reset()
+		signed := createMac(hash, []byte(value))
+		hash.Reset()
+		err := verifyMac(hash, []byte(value), signed)
+		if err != nil {
+			t.Error(err)
+		}
+	}
+}
+
+func TestEncryption(t *testing.T) {
+	block, err := aes.NewCipher([]byte("1234567890123456"))
+	if err != nil {
+		t.Fatalf("Block could not be created")
+	}
+	var encrypted, decrypted []byte
+	for _, value := range testStrings {
+		if encrypted, err = encrypt(block, []byte(value)); err != nil {
+			t.Error(err)
+		} else {
+			if decrypted, err = decrypt(block, encrypted); err != nil {
+				t.Error(err)
+			}
+			if string(decrypted) != value {
+				t.Errorf("Expected %v, got %v.", value, string(decrypted))
+			}
+		}
+	}
+}
+
+func TestGobSerialization(t *testing.T) {
+	var (
+		sz           GobEncoder
+		serialized   []byte
+		deserialized map[string]string
+		err          error
+	)
+	for _, value := range testCookies {
+		if serialized, err = sz.Serialize(value); err != nil {
+			t.Error(err)
+		} else {
+			deserialized = make(map[string]string)
+			if err = sz.Deserialize(serialized, &deserialized); err != nil {
+				t.Error(err)
+			}
+			if fmt.Sprintf("%v", deserialized) != fmt.Sprintf("%v", value) {
+				t.Errorf("Expected %v, got %v.", value, deserialized)
+			}
+		}
+	}
+}
+
+func TestJSONSerialization(t *testing.T) {
+	var (
+		sz           JSONEncoder
+		serialized   []byte
+		deserialized map[string]string
+		err          error
+	)
+	for _, value := range testCookies {
+		if serialized, err = sz.Serialize(value); err != nil {
+			t.Error(err)
+		} else {
+			deserialized = make(map[string]string)
+			if err = sz.Deserialize(serialized, &deserialized); err != nil {
+				t.Error(err)
+			}
+			if fmt.Sprintf("%v", deserialized) != fmt.Sprintf("%v", value) {
+				t.Errorf("Expected %v, got %v.", value, deserialized)
+			}
+		}
+	}
+}
+
+func TestEncoding(t *testing.T) {
+	for _, value := range testStrings {
+		encoded := encode([]byte(value))
+		decoded, err := decode(encoded)
+		if err != nil {
+			t.Error(err)
+		} else if string(decoded) != value {
+			t.Errorf("Expected %v, got %s.", value, string(decoded))
+		}
+	}
+}
+
+func TestMultiError(t *testing.T) {
+	s1, s2 := New(nil, nil), New(nil, nil)
+	_, err := EncodeMulti("sid", "value", s1, s2)
+	if len(err.(MultiError)) != 2 {
+		t.Errorf("Expected 2 errors, got %s.", err)
+	} else {
+		if strings.Index(err.Error(), "hash key is not set") == -1 {
+			t.Errorf("Expected missing hash key error, got %s.", err.Error())
+		}
+		ourErr, ok := err.(Error)
+		if !ok || !ourErr.IsUsage() {
+			t.Fatalf("Expected error to be a usage error; got %#v", err)
+		}
+		if ourErr.IsDecode() {
+			t.Errorf("Expected error NOT to be a decode error; got %#v", ourErr)
+		}
+		if ourErr.IsInternal() {
+			t.Errorf("Expected error NOT to be an internal error; got %#v", ourErr)
+		}
+	}
+}
+
+func TestMultiNoCodecs(t *testing.T) {
+	_, err := EncodeMulti("foo", "bar")
+	if err != errNoCodecs {
+		t.Errorf("EncodeMulti: bad value for error, got: %v", err)
+	}
+
+	var dst []byte
+	err = DecodeMulti("foo", "bar", &dst)
+	if err != errNoCodecs {
+		t.Errorf("DecodeMulti: bad value for error, got: %v", err)
+	}
+}
+
+func TestMissingKey(t *testing.T) {
+	s1 := New(nil, nil)
+
+	var dst []byte
+	err := s1.Decode("sid", "value", &dst)
+	if err != errHashKeyNotSet {
+		t.Fatalf("Expected %#v, got %#v", errHashKeyNotSet, err)
+	}
+	if err2, ok := err.(Error); !ok || !err2.IsUsage() {
+		t.Errorf("Expected missing hash key to be IsUsage(); was %#v", err)
+	}
+}
+
+// ----------------------------------------------------------------------------
+
+type FooBar struct {
+	Foo int
+	Bar string
+}
+
+func TestCustomType(t *testing.T) {
+	s1 := New([]byte("12345"), []byte("1234567890123456"))
+	// Type is not registered in gob. (!!!)
+	src := &FooBar{42, "bar"}
+	encoded, _ := s1.Encode("sid", src)
+
+	dst := &FooBar{}
+	_ = s1.Decode("sid", encoded, dst)
+	if dst.Foo != 42 || dst.Bar != "bar" {
+		t.Fatalf("Expected %#v, got %#v", src, dst)
+	}
+}

+ 21 - 0
src/github.com/gorilla/sessions/.travis.yml

@@ -0,0 +1,21 @@
+language: go
+sudo: false
+
+matrix:
+  include:
+    - go: 1.3
+    - go: 1.4
+    - go: 1.5
+    - go: 1.6
+    - go: tip
+  allow_failures:
+    - go: tip
+
+install:
+  - # skip
+
+script:
+  - go get -t -v ./...
+  - diff -u <(echo -n) <(gofmt -d .)
+  - go vet $(go list ./... | grep -v /vendor/)
+  - go test -v -race ./...

+ 27 - 0
src/github.com/gorilla/sessions/LICENSE

@@ -0,0 +1,27 @@
+Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+	 * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+	 * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+	 * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 79 - 0
src/github.com/gorilla/sessions/README.md

@@ -0,0 +1,79 @@
+sessions
+========
+[![GoDoc](https://godoc.org/github.com/gorilla/sessions?status.svg)](https://godoc.org/github.com/gorilla/sessions) [![Build Status](https://travis-ci.org/gorilla/sessions.png?branch=master)](https://travis-ci.org/gorilla/sessions)
+
+gorilla/sessions provides cookie and filesystem sessions and infrastructure for
+custom session backends.
+
+The key features are:
+
+* Simple API: use it as an easy way to set signed (and optionally
+  encrypted) cookies.
+* Built-in backends to store sessions in cookies or the filesystem.
+* Flash messages: session values that last until read.
+* Convenient way to switch session persistency (aka "remember me") and set
+  other attributes.
+* Mechanism to rotate authentication and encryption keys.
+* Multiple sessions per request, even using different backends.
+* Interfaces and infrastructure for custom session backends: sessions from
+  different stores can be retrieved and batch-saved using a common API.
+
+Let's start with an example that shows the sessions API in a nutshell:
+
+```go
+	import (
+		"net/http"
+		"github.com/gorilla/sessions"
+	)
+
+	var store = sessions.NewCookieStore([]byte("something-very-secret"))
+
+	func MyHandler(w http.ResponseWriter, r *http.Request) {
+		// Get a session. We're ignoring the error resulted from decoding an
+		// existing session: Get() always returns a session, even if empty.
+		session, _ := store.Get(r, "session-name")
+		// Set some session values.
+		session.Values["foo"] = "bar"
+		session.Values[42] = 43
+		// Save it before we write to the response/return from the handler.
+		session.Save(r, w)
+	}
+```
+
+First we initialize a session store calling `NewCookieStore()` and passing a
+secret key used to authenticate the session. Inside the handler, we call
+`store.Get()` to retrieve an existing session or a new one. Then we set some
+session values in session.Values, which is a `map[interface{}]interface{}`.
+And finally we call `session.Save()` to save the session in the response.
+
+Important Note: If you aren't using gorilla/mux, you need to wrap your handlers
+with
+[`context.ClearHandler`](http://www.gorillatoolkit.org/pkg/context#ClearHandler)
+as or else you will leak memory! An easy way to do this is to wrap the top-level
+mux when calling http.ListenAndServe:
+
+More examples are available [on the Gorilla
+website](http://www.gorillatoolkit.org/pkg/sessions).
+
+## Store Implementations
+
+Other implementations of the `sessions.Store` interface:
+
+* [github.com/starJammer/gorilla-sessions-arangodb](https://github.com/starJammer/gorilla-sessions-arangodb) - ArangoDB
+* [github.com/yosssi/boltstore](https://github.com/yosssi/boltstore) - Bolt
+* [github.com/srinathgs/couchbasestore](https://github.com/srinathgs/couchbasestore) - Couchbase
+* [github.com/denizeren/dynamostore](https://github.com/denizeren/dynamostore) - Dynamodb on AWS
+* [github.com/bradleypeabody/gorilla-sessions-memcache](https://github.com/bradleypeabody/gorilla-sessions-memcache) - Memcache
+* [github.com/hnakamur/gaesessions](https://github.com/hnakamur/gaesessions) - Memcache on GAE
+* [github.com/kidstuff/mongostore](https://github.com/kidstuff/mongostore) - MongoDB
+* [github.com/srinathgs/mysqlstore](https://github.com/srinathgs/mysqlstore) - MySQL
+* [github.com/antonlindstrom/pgstore](https://github.com/antonlindstrom/pgstore) - PostgreSQL
+* [github.com/boj/redistore](https://github.com/boj/redistore) - Redis
+* [github.com/boj/rethinkstore](https://github.com/boj/rethinkstore) - RethinkDB
+* [github.com/boj/riakstore](https://github.com/boj/riakstore) - Riak
+* [github.com/michaeljs1990/sqlitestore](https://github.com/michaeljs1990/sqlitestore) - SQLite
+* [github.com/wader/gormstore](https://github.com/wader/gormstore) - GORM (MySQL, PostgreSQL, SQLite)
+
+## License
+
+BSD licensed. See the LICENSE file for details.

+ 199 - 0
src/github.com/gorilla/sessions/doc.go

@@ -0,0 +1,199 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+/*
+Package sessions provides cookie and filesystem sessions and
+infrastructure for custom session backends.
+
+The key features are:
+
+	* Simple API: use it as an easy way to set signed (and optionally
+	  encrypted) cookies.
+	* Built-in backends to store sessions in cookies or the filesystem.
+	* Flash messages: session values that last until read.
+	* Convenient way to switch session persistency (aka "remember me") and set
+	  other attributes.
+	* Mechanism to rotate authentication and encryption keys.
+	* Multiple sessions per request, even using different backends.
+	* Interfaces and infrastructure for custom session backends: sessions from
+	  different stores can be retrieved and batch-saved using a common API.
+
+Let's start with an example that shows the sessions API in a nutshell:
+
+	import (
+		"net/http"
+		"github.com/gorilla/sessions"
+	)
+
+	var store = sessions.NewCookieStore([]byte("something-very-secret"))
+
+	func MyHandler(w http.ResponseWriter, r *http.Request) {
+		// Get a session. We're ignoring the error resulted from decoding an
+		// existing session: Get() always returns a session, even if empty.
+		session, err := store.Get(r, "session-name")
+		if err != nil {
+			http.Error(w, err.Error(), http.StatusInternalServerError)
+			return
+		}
+
+		// Set some session values.
+		session.Values["foo"] = "bar"
+		session.Values[42] = 43
+		// Save it before we write to the response/return from the handler.
+		session.Save(r, w)
+	}
+
+First we initialize a session store calling NewCookieStore() and passing a
+secret key used to authenticate the session. Inside the handler, we call
+store.Get() to retrieve an existing session or a new one. Then we set some
+session values in session.Values, which is a map[interface{}]interface{}.
+And finally we call session.Save() to save the session in the response.
+
+Note that in production code, we should check for errors when calling
+session.Save(r, w), and either display an error message or otherwise handle it.
+
+Save must be called before writing to the response, otherwise the session
+cookie will not be sent to the client.
+
+Important Note: If you aren't using gorilla/mux, you need to wrap your handlers
+with context.ClearHandler as or else you will leak memory! An easy way to do this
+is to wrap the top-level mux when calling http.ListenAndServe:
+
+    http.ListenAndServe(":8080", context.ClearHandler(http.DefaultServeMux))
+
+The ClearHandler function is provided by the gorilla/context package.
+
+That's all you need to know for the basic usage. Let's take a look at other
+options, starting with flash messages.
+
+Flash messages are session values that last until read. The term appeared with
+Ruby On Rails a few years back. When we request a flash message, it is removed
+from the session. To add a flash, call session.AddFlash(), and to get all
+flashes, call session.Flashes(). Here is an example:
+
+	func MyHandler(w http.ResponseWriter, r *http.Request) {
+		// Get a session.
+		session, err := store.Get(r, "session-name")
+		if err != nil {
+			http.Error(w, err.Error(), http.StatusInternalServerError)
+			return
+		}
+
+		// Get the previously flashes, if any.
+		if flashes := session.Flashes(); len(flashes) > 0 {
+			// Use the flash values.
+		} else {
+			// Set a new flash.
+			session.AddFlash("Hello, flash messages world!")
+		}
+		session.Save(r, w)
+	}
+
+Flash messages are useful to set information to be read after a redirection,
+like after form submissions.
+
+There may also be cases where you want to store a complex datatype within a
+session, such as a struct. Sessions are serialised using the encoding/gob package,
+so it is easy to register new datatypes for storage in sessions:
+
+	import(
+		"encoding/gob"
+		"github.com/gorilla/sessions"
+	)
+
+	type Person struct {
+		FirstName	string
+		LastName 	string
+		Email		string
+		Age			int
+	}
+
+	type M map[string]interface{}
+
+	func init() {
+
+		gob.Register(&Person{})
+		gob.Register(&M{})
+	}
+
+As it's not possible to pass a raw type as a parameter to a function, gob.Register()
+relies on us passing it a value of the desired type. In the example above we've passed
+it a pointer to a struct and a pointer to a custom type representing a
+map[string]interface. (We could have passed non-pointer values if we wished.) This will
+then allow us to serialise/deserialise values of those types to and from our sessions.
+
+Note that because session values are stored in a map[string]interface{}, there's
+a need to type-assert data when retrieving it. We'll use the Person struct we registered above:
+
+	func MyHandler(w http.ResponseWriter, r *http.Request) {
+		session, err := store.Get(r, "session-name")
+		if err != nil {
+			http.Error(w, err.Error(), http.StatusInternalServerError)
+			return
+		}
+
+		// Retrieve our struct and type-assert it
+		val := session.Values["person"]
+		var person = &Person{}
+		if person, ok := val.(*Person); !ok {
+			// Handle the case that it's not an expected type
+		}
+
+		// Now we can use our person object
+	}
+
+By default, session cookies last for a month. This is probably too long for
+some cases, but it is easy to change this and other attributes during
+runtime. Sessions can be configured individually or the store can be
+configured and then all sessions saved using it will use that configuration.
+We access session.Options or store.Options to set a new configuration. The
+fields are basically a subset of http.Cookie fields. Let's change the
+maximum age of a session to one week:
+
+	session.Options = &sessions.Options{
+		Path:     "/",
+		MaxAge:   86400 * 7,
+		HttpOnly: true,
+	}
+
+Sometimes we may want to change authentication and/or encryption keys without
+breaking existing sessions. The CookieStore supports key rotation, and to use
+it you just need to set multiple authentication and encryption keys, in pairs,
+to be tested in order:
+
+	var store = sessions.NewCookieStore(
+		[]byte("new-authentication-key"),
+		[]byte("new-encryption-key"),
+		[]byte("old-authentication-key"),
+		[]byte("old-encryption-key"),
+	)
+
+New sessions will be saved using the first pair. Old sessions can still be
+read because the first pair will fail, and the second will be tested. This
+makes it easy to "rotate" secret keys and still be able to validate existing
+sessions. Note: for all pairs the encryption key is optional; set it to nil
+or omit it and and encryption won't be used.
+
+Multiple sessions can be used in the same request, even with different
+session backends. When this happens, calling Save() on each session
+individually would be cumbersome, so we have a way to save all sessions
+at once: it's sessions.Save(). Here's an example:
+
+	var store = sessions.NewCookieStore([]byte("something-very-secret"))
+
+	func MyHandler(w http.ResponseWriter, r *http.Request) {
+		// Get a session and set a value.
+		session1, _ := store.Get(r, "session-one")
+		session1.Values["foo"] = "bar"
+		// Get another session and set another value.
+		session2, _ := store.Get(r, "session-two")
+		session2.Values[42] = 43
+		// Save all sessions.
+		sessions.Save(r, w)
+	}
+
+This is possible because when we call Get() from a session store, it adds the
+session to a common registry. Save() uses it to save all registered sessions.
+*/
+package sessions

+ 102 - 0
src/github.com/gorilla/sessions/lex.go

@@ -0,0 +1,102 @@
+// This file contains code adapted from the Go standard library
+// https://github.com/golang/go/blob/39ad0fd0789872f9469167be7fe9578625ff246e/src/net/http/lex.go
+
+package sessions
+
+import "strings"
+
+var isTokenTable = [127]bool{
+	'!':  true,
+	'#':  true,
+	'$':  true,
+	'%':  true,
+	'&':  true,
+	'\'': true,
+	'*':  true,
+	'+':  true,
+	'-':  true,
+	'.':  true,
+	'0':  true,
+	'1':  true,
+	'2':  true,
+	'3':  true,
+	'4':  true,
+	'5':  true,
+	'6':  true,
+	'7':  true,
+	'8':  true,
+	'9':  true,
+	'A':  true,
+	'B':  true,
+	'C':  true,
+	'D':  true,
+	'E':  true,
+	'F':  true,
+	'G':  true,
+	'H':  true,
+	'I':  true,
+	'J':  true,
+	'K':  true,
+	'L':  true,
+	'M':  true,
+	'N':  true,
+	'O':  true,
+	'P':  true,
+	'Q':  true,
+	'R':  true,
+	'S':  true,
+	'T':  true,
+	'U':  true,
+	'W':  true,
+	'V':  true,
+	'X':  true,
+	'Y':  true,
+	'Z':  true,
+	'^':  true,
+	'_':  true,
+	'`':  true,
+	'a':  true,
+	'b':  true,
+	'c':  true,
+	'd':  true,
+	'e':  true,
+	'f':  true,
+	'g':  true,
+	'h':  true,
+	'i':  true,
+	'j':  true,
+	'k':  true,
+	'l':  true,
+	'm':  true,
+	'n':  true,
+	'o':  true,
+	'p':  true,
+	'q':  true,
+	'r':  true,
+	's':  true,
+	't':  true,
+	'u':  true,
+	'v':  true,
+	'w':  true,
+	'x':  true,
+	'y':  true,
+	'z':  true,
+	'|':  true,
+	'~':  true,
+}
+
+func isToken(r rune) bool {
+	i := int(r)
+	return i < len(isTokenTable) && isTokenTable[i]
+}
+
+func isNotToken(r rune) bool {
+	return !isToken(r)
+}
+
+func isCookieNameValid(raw string) bool {
+	if raw == "" {
+		return false
+	}
+	return strings.IndexFunc(raw, isNotToken) < 0
+}

+ 241 - 0
src/github.com/gorilla/sessions/sessions.go

@@ -0,0 +1,241 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package sessions
+
+import (
+	"encoding/gob"
+	"fmt"
+	"net/http"
+	"time"
+
+	"github.com/gorilla/context"
+)
+
+// Default flashes key.
+const flashesKey = "_flash"
+
+// Options --------------------------------------------------------------------
+
+// Options stores configuration for a session or session store.
+//
+// Fields are a subset of http.Cookie fields.
+type Options struct {
+	Path   string
+	Domain string
+	// MaxAge=0 means no 'Max-Age' attribute specified.
+	// MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'.
+	// MaxAge>0 means Max-Age attribute present and given in seconds.
+	MaxAge   int
+	Secure   bool
+	HttpOnly bool
+}
+
+// Session --------------------------------------------------------------------
+
+// NewSession is called by session stores to create a new session instance.
+func NewSession(store Store, name string) *Session {
+	return &Session{
+		Values: make(map[interface{}]interface{}),
+		store:  store,
+		name:   name,
+	}
+}
+
+// Session stores the values and optional configuration for a session.
+type Session struct {
+	// The ID of the session, generated by stores. It should not be used for
+	// user data.
+	ID string
+	// Values contains the user-data for the session.
+	Values  map[interface{}]interface{}
+	Options *Options
+	IsNew   bool
+	store   Store
+	name    string
+}
+
+// Flashes returns a slice of flash messages from the session.
+//
+// A single variadic argument is accepted, and it is optional: it defines
+// the flash key. If not defined "_flash" is used by default.
+func (s *Session) Flashes(vars ...string) []interface{} {
+	var flashes []interface{}
+	key := flashesKey
+	if len(vars) > 0 {
+		key = vars[0]
+	}
+	if v, ok := s.Values[key]; ok {
+		// Drop the flashes and return it.
+		delete(s.Values, key)
+		flashes = v.([]interface{})
+	}
+	return flashes
+}
+
+// AddFlash adds a flash message to the session.
+//
+// A single variadic argument is accepted, and it is optional: it defines
+// the flash key. If not defined "_flash" is used by default.
+func (s *Session) AddFlash(value interface{}, vars ...string) {
+	key := flashesKey
+	if len(vars) > 0 {
+		key = vars[0]
+	}
+	var flashes []interface{}
+	if v, ok := s.Values[key]; ok {
+		flashes = v.([]interface{})
+	}
+	s.Values[key] = append(flashes, value)
+}
+
+// Save is a convenience method to save this session. It is the same as calling
+// store.Save(request, response, session). You should call Save before writing to
+// the response or returning from the handler.
+func (s *Session) Save(r *http.Request, w http.ResponseWriter) error {
+	return s.store.Save(r, w, s)
+}
+
+// Name returns the name used to register the session.
+func (s *Session) Name() string {
+	return s.name
+}
+
+// Store returns the session store used to register the session.
+func (s *Session) Store() Store {
+	return s.store
+}
+
+// Registry -------------------------------------------------------------------
+
+// sessionInfo stores a session tracked by the registry.
+type sessionInfo struct {
+	s *Session
+	e error
+}
+
+// contextKey is the type used to store the registry in the context.
+type contextKey int
+
+// registryKey is the key used to store the registry in the context.
+const registryKey contextKey = 0
+
+// GetRegistry returns a registry instance for the current request.
+func GetRegistry(r *http.Request) *Registry {
+	registry := context.Get(r, registryKey)
+	if registry != nil {
+		return registry.(*Registry)
+	}
+	newRegistry := &Registry{
+		request:  r,
+		sessions: make(map[string]sessionInfo),
+	}
+	context.Set(r, registryKey, newRegistry)
+	return newRegistry
+}
+
+// Registry stores sessions used during a request.
+type Registry struct {
+	request  *http.Request
+	sessions map[string]sessionInfo
+}
+
+// Get registers and returns a session for the given name and session store.
+//
+// It returns a new session if there are no sessions registered for the name.
+func (s *Registry) Get(store Store, name string) (session *Session, err error) {
+	if !isCookieNameValid(name) {
+		return nil, fmt.Errorf("sessions: invalid character in cookie name: %s", name)
+	}
+	if info, ok := s.sessions[name]; ok {
+		session, err = info.s, info.e
+	} else {
+		session, err = store.New(s.request, name)
+		session.name = name
+		s.sessions[name] = sessionInfo{s: session, e: err}
+	}
+	session.store = store
+	return
+}
+
+// Save saves all sessions registered for the current request.
+func (s *Registry) Save(w http.ResponseWriter) error {
+	var errMulti MultiError
+	for name, info := range s.sessions {
+		session := info.s
+		if session.store == nil {
+			errMulti = append(errMulti, fmt.Errorf(
+				"sessions: missing store for session %q", name))
+		} else if err := session.store.Save(s.request, w, session); err != nil {
+			errMulti = append(errMulti, fmt.Errorf(
+				"sessions: error saving session %q -- %v", name, err))
+		}
+	}
+	if errMulti != nil {
+		return errMulti
+	}
+	return nil
+}
+
+// Helpers --------------------------------------------------------------------
+
+func init() {
+	gob.Register([]interface{}{})
+}
+
+// Save saves all sessions used during the current request.
+func Save(r *http.Request, w http.ResponseWriter) error {
+	return GetRegistry(r).Save(w)
+}
+
+// NewCookie returns an http.Cookie with the options set. It also sets
+// the Expires field calculated based on the MaxAge value, for Internet
+// Explorer compatibility.
+func NewCookie(name, value string, options *Options) *http.Cookie {
+	cookie := &http.Cookie{
+		Name:     name,
+		Value:    value,
+		Path:     options.Path,
+		Domain:   options.Domain,
+		MaxAge:   options.MaxAge,
+		Secure:   options.Secure,
+		HttpOnly: options.HttpOnly,
+	}
+	if options.MaxAge > 0 {
+		d := time.Duration(options.MaxAge) * time.Second
+		cookie.Expires = time.Now().Add(d)
+	} else if options.MaxAge < 0 {
+		// Set it to the past to expire now.
+		cookie.Expires = time.Unix(1, 0)
+	}
+	return cookie
+}
+
+// Error ----------------------------------------------------------------------
+
+// MultiError stores multiple errors.
+//
+// Borrowed from the App Engine SDK.
+type MultiError []error
+
+func (m MultiError) Error() string {
+	s, n := "", 0
+	for _, e := range m {
+		if e != nil {
+			if n == 0 {
+				s = e.Error()
+			}
+			n++
+		}
+	}
+	switch n {
+	case 0:
+		return "(0 errors)"
+	case 1:
+		return s
+	case 2:
+		return s + " (and 1 other error)"
+	}
+	return fmt.Sprintf("%s (and %d other errors)", s, n-1)
+}

+ 201 - 0
src/github.com/gorilla/sessions/sessions_test.go

@@ -0,0 +1,201 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package sessions
+
+import (
+	"bytes"
+	"encoding/gob"
+	"net/http"
+	"testing"
+)
+
+// ----------------------------------------------------------------------------
+// ResponseRecorder
+// ----------------------------------------------------------------------------
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// ResponseRecorder is an implementation of http.ResponseWriter that
+// records its mutations for later inspection in tests.
+type ResponseRecorder struct {
+	Code      int           // the HTTP response code from WriteHeader
+	HeaderMap http.Header   // the HTTP response headers
+	Body      *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to
+	Flushed   bool
+}
+
+// NewRecorder returns an initialized ResponseRecorder.
+func NewRecorder() *ResponseRecorder {
+	return &ResponseRecorder{
+		HeaderMap: make(http.Header),
+		Body:      new(bytes.Buffer),
+	}
+}
+
+// DefaultRemoteAddr is the default remote address to return in RemoteAddr if
+// an explicit DefaultRemoteAddr isn't set on ResponseRecorder.
+const DefaultRemoteAddr = "1.2.3.4"
+
+// Header returns the response headers.
+func (rw *ResponseRecorder) Header() http.Header {
+	return rw.HeaderMap
+}
+
+// Write always succeeds and writes to rw.Body, if not nil.
+func (rw *ResponseRecorder) Write(buf []byte) (int, error) {
+	if rw.Body != nil {
+		rw.Body.Write(buf)
+	}
+	if rw.Code == 0 {
+		rw.Code = http.StatusOK
+	}
+	return len(buf), nil
+}
+
+// WriteHeader sets rw.Code.
+func (rw *ResponseRecorder) WriteHeader(code int) {
+	rw.Code = code
+}
+
+// Flush sets rw.Flushed to true.
+func (rw *ResponseRecorder) Flush() {
+	rw.Flushed = true
+}
+
+// ----------------------------------------------------------------------------
+
+type FlashMessage struct {
+	Type    int
+	Message string
+}
+
+func TestFlashes(t *testing.T) {
+	var req *http.Request
+	var rsp *ResponseRecorder
+	var hdr http.Header
+	var err error
+	var ok bool
+	var cookies []string
+	var session *Session
+	var flashes []interface{}
+
+	store := NewCookieStore([]byte("secret-key"))
+
+	// Round 1 ----------------------------------------------------------------
+
+	req, _ = http.NewRequest("GET", "http://localhost:8080/", nil)
+	rsp = NewRecorder()
+	// Get a session.
+	if session, err = store.Get(req, "session-key"); err != nil {
+		t.Fatalf("Error getting session: %v", err)
+	}
+	// Get a flash.
+	flashes = session.Flashes()
+	if len(flashes) != 0 {
+		t.Errorf("Expected empty flashes; Got %v", flashes)
+	}
+	// Add some flashes.
+	session.AddFlash("foo")
+	session.AddFlash("bar")
+	// Custom key.
+	session.AddFlash("baz", "custom_key")
+	// Save.
+	if err = Save(req, rsp); err != nil {
+		t.Fatalf("Error saving session: %v", err)
+	}
+	hdr = rsp.Header()
+	cookies, ok = hdr["Set-Cookie"]
+	if !ok || len(cookies) != 1 {
+		t.Fatal("No cookies. Header:", hdr)
+	}
+
+	if _, err = store.Get(req, "session:key"); err.Error() != "sessions: invalid character in cookie name: session:key" {
+		t.Fatalf("Expected error due to invalid cookie name")
+	}
+
+	// Round 2 ----------------------------------------------------------------
+
+	req, _ = http.NewRequest("GET", "http://localhost:8080/", nil)
+	req.Header.Add("Cookie", cookies[0])
+	rsp = NewRecorder()
+	// Get a session.
+	if session, err = store.Get(req, "session-key"); err != nil {
+		t.Fatalf("Error getting session: %v", err)
+	}
+	// Check all saved values.
+	flashes = session.Flashes()
+	if len(flashes) != 2 {
+		t.Fatalf("Expected flashes; Got %v", flashes)
+	}
+	if flashes[0] != "foo" || flashes[1] != "bar" {
+		t.Errorf("Expected foo,bar; Got %v", flashes)
+	}
+	flashes = session.Flashes()
+	if len(flashes) != 0 {
+		t.Errorf("Expected dumped flashes; Got %v", flashes)
+	}
+	// Custom key.
+	flashes = session.Flashes("custom_key")
+	if len(flashes) != 1 {
+		t.Errorf("Expected flashes; Got %v", flashes)
+	} else if flashes[0] != "baz" {
+		t.Errorf("Expected baz; Got %v", flashes)
+	}
+	flashes = session.Flashes("custom_key")
+	if len(flashes) != 0 {
+		t.Errorf("Expected dumped flashes; Got %v", flashes)
+	}
+
+	// Round 3 ----------------------------------------------------------------
+	// Custom type
+
+	req, _ = http.NewRequest("GET", "http://localhost:8080/", nil)
+	rsp = NewRecorder()
+	// Get a session.
+	if session, err = store.Get(req, "session-key"); err != nil {
+		t.Fatalf("Error getting session: %v", err)
+	}
+	// Get a flash.
+	flashes = session.Flashes()
+	if len(flashes) != 0 {
+		t.Errorf("Expected empty flashes; Got %v", flashes)
+	}
+	// Add some flashes.
+	session.AddFlash(&FlashMessage{42, "foo"})
+	// Save.
+	if err = Save(req, rsp); err != nil {
+		t.Fatalf("Error saving session: %v", err)
+	}
+	hdr = rsp.Header()
+	cookies, ok = hdr["Set-Cookie"]
+	if !ok || len(cookies) != 1 {
+		t.Fatal("No cookies. Header:", hdr)
+	}
+
+	// Round 4 ----------------------------------------------------------------
+	// Custom type
+
+	req, _ = http.NewRequest("GET", "http://localhost:8080/", nil)
+	req.Header.Add("Cookie", cookies[0])
+	rsp = NewRecorder()
+	// Get a session.
+	if session, err = store.Get(req, "session-key"); err != nil {
+		t.Fatalf("Error getting session: %v", err)
+	}
+	// Check all saved values.
+	flashes = session.Flashes()
+	if len(flashes) != 1 {
+		t.Fatalf("Expected flashes; Got %v", flashes)
+	}
+	custom := flashes[0].(FlashMessage)
+	if custom.Type != 42 || custom.Message != "foo" {
+		t.Errorf("Expected %#v, got %#v", FlashMessage{42, "foo"}, custom)
+	}
+}
+
+func init() {
+	gob.Register(FlashMessage{})
+}

+ 270 - 0
src/github.com/gorilla/sessions/store.go

@@ -0,0 +1,270 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package sessions
+
+import (
+	"encoding/base32"
+	"io/ioutil"
+	"net/http"
+	"os"
+	"path/filepath"
+	"strings"
+	"sync"
+
+	"github.com/gorilla/securecookie"
+)
+
+// Store is an interface for custom session stores.
+//
+// See CookieStore and FilesystemStore for examples.
+type Store interface {
+	// Get should return a cached session.
+	Get(r *http.Request, name string) (*Session, error)
+
+	// New should create and return a new session.
+	//
+	// Note that New should never return a nil session, even in the case of
+	// an error if using the Registry infrastructure to cache the session.
+	New(r *http.Request, name string) (*Session, error)
+
+	// Save should persist session to the underlying store implementation.
+	Save(r *http.Request, w http.ResponseWriter, s *Session) error
+}
+
+// CookieStore ----------------------------------------------------------------
+
+// NewCookieStore returns a new CookieStore.
+//
+// Keys are defined in pairs to allow key rotation, but the common case is
+// to set a single authentication key and optionally an encryption key.
+//
+// The first key in a pair is used for authentication and the second for
+// encryption. The encryption key can be set to nil or omitted in the last
+// pair, but the authentication key is required in all pairs.
+//
+// It is recommended to use an authentication key with 32 or 64 bytes.
+// The encryption key, if set, must be either 16, 24, or 32 bytes to select
+// AES-128, AES-192, or AES-256 modes.
+//
+// Use the convenience function securecookie.GenerateRandomKey() to create
+// strong keys.
+func NewCookieStore(keyPairs ...[]byte) *CookieStore {
+	cs := &CookieStore{
+		Codecs: securecookie.CodecsFromPairs(keyPairs...),
+		Options: &Options{
+			Path:   "/",
+			MaxAge: 86400 * 30,
+		},
+	}
+
+	cs.MaxAge(cs.Options.MaxAge)
+	return cs
+}
+
+// CookieStore stores sessions using secure cookies.
+type CookieStore struct {
+	Codecs  []securecookie.Codec
+	Options *Options // default configuration
+}
+
+// Get returns a session for the given name after adding it to the registry.
+//
+// It returns a new session if the sessions doesn't exist. Access IsNew on
+// the session to check if it is an existing session or a new one.
+//
+// It returns a new session and an error if the session exists but could
+// not be decoded.
+func (s *CookieStore) Get(r *http.Request, name string) (*Session, error) {
+	return GetRegistry(r).Get(s, name)
+}
+
+// New returns a session for the given name without adding it to the registry.
+//
+// The difference between New() and Get() is that calling New() twice will
+// decode the session data twice, while Get() registers and reuses the same
+// decoded session after the first call.
+func (s *CookieStore) New(r *http.Request, name string) (*Session, error) {
+	session := NewSession(s, name)
+	opts := *s.Options
+	session.Options = &opts
+	session.IsNew = true
+	var err error
+	if c, errCookie := r.Cookie(name); errCookie == nil {
+		err = securecookie.DecodeMulti(name, c.Value, &session.Values,
+			s.Codecs...)
+		if err == nil {
+			session.IsNew = false
+		}
+	}
+	return session, err
+}
+
+// Save adds a single session to the response.
+func (s *CookieStore) Save(r *http.Request, w http.ResponseWriter,
+	session *Session) error {
+	encoded, err := securecookie.EncodeMulti(session.Name(), session.Values,
+		s.Codecs...)
+	if err != nil {
+		return err
+	}
+	http.SetCookie(w, NewCookie(session.Name(), encoded, session.Options))
+	return nil
+}
+
+// MaxAge sets the maximum age for the store and the underlying cookie
+// implementation. Individual sessions can be deleted by setting Options.MaxAge
+// = -1 for that session.
+func (s *CookieStore) MaxAge(age int) {
+	s.Options.MaxAge = age
+
+	// Set the maxAge for each securecookie instance.
+	for _, codec := range s.Codecs {
+		if sc, ok := codec.(*securecookie.SecureCookie); ok {
+			sc.MaxAge(age)
+		}
+	}
+}
+
+// FilesystemStore ------------------------------------------------------------
+
+var fileMutex sync.RWMutex
+
+// NewFilesystemStore returns a new FilesystemStore.
+//
+// The path argument is the directory where sessions will be saved. If empty
+// it will use os.TempDir().
+//
+// See NewCookieStore() for a description of the other parameters.
+func NewFilesystemStore(path string, keyPairs ...[]byte) *FilesystemStore {
+	if path == "" {
+		path = os.TempDir()
+	}
+	fs := &FilesystemStore{
+		Codecs: securecookie.CodecsFromPairs(keyPairs...),
+		Options: &Options{
+			Path:   "/",
+			MaxAge: 86400 * 30,
+		},
+		path: path,
+	}
+
+	fs.MaxAge(fs.Options.MaxAge)
+	return fs
+}
+
+// FilesystemStore stores sessions in the filesystem.
+//
+// It also serves as a reference for custom stores.
+//
+// This store is still experimental and not well tested. Feedback is welcome.
+type FilesystemStore struct {
+	Codecs  []securecookie.Codec
+	Options *Options // default configuration
+	path    string
+}
+
+// MaxLength restricts the maximum length of new sessions to l.
+// If l is 0 there is no limit to the size of a session, use with caution.
+// The default for a new FilesystemStore is 4096.
+func (s *FilesystemStore) MaxLength(l int) {
+	for _, c := range s.Codecs {
+		if codec, ok := c.(*securecookie.SecureCookie); ok {
+			codec.MaxLength(l)
+		}
+	}
+}
+
+// Get returns a session for the given name after adding it to the registry.
+//
+// See CookieStore.Get().
+func (s *FilesystemStore) Get(r *http.Request, name string) (*Session, error) {
+	return GetRegistry(r).Get(s, name)
+}
+
+// New returns a session for the given name without adding it to the registry.
+//
+// See CookieStore.New().
+func (s *FilesystemStore) New(r *http.Request, name string) (*Session, error) {
+	session := NewSession(s, name)
+	opts := *s.Options
+	session.Options = &opts
+	session.IsNew = true
+	var err error
+	if c, errCookie := r.Cookie(name); errCookie == nil {
+		err = securecookie.DecodeMulti(name, c.Value, &session.ID, s.Codecs...)
+		if err == nil {
+			err = s.load(session)
+			if err == nil {
+				session.IsNew = false
+			}
+		}
+	}
+	return session, err
+}
+
+// Save adds a single session to the response.
+func (s *FilesystemStore) Save(r *http.Request, w http.ResponseWriter,
+	session *Session) error {
+	if session.ID == "" {
+		// Because the ID is used in the filename, encode it to
+		// use alphanumeric characters only.
+		session.ID = strings.TrimRight(
+			base32.StdEncoding.EncodeToString(
+				securecookie.GenerateRandomKey(32)), "=")
+	}
+	if err := s.save(session); err != nil {
+		return err
+	}
+	encoded, err := securecookie.EncodeMulti(session.Name(), session.ID,
+		s.Codecs...)
+	if err != nil {
+		return err
+	}
+	http.SetCookie(w, NewCookie(session.Name(), encoded, session.Options))
+	return nil
+}
+
+// MaxAge sets the maximum age for the store and the underlying cookie
+// implementation. Individual sessions can be deleted by setting Options.MaxAge
+// = -1 for that session.
+func (s *FilesystemStore) MaxAge(age int) {
+	s.Options.MaxAge = age
+
+	// Set the maxAge for each securecookie instance.
+	for _, codec := range s.Codecs {
+		if sc, ok := codec.(*securecookie.SecureCookie); ok {
+			sc.MaxAge(age)
+		}
+	}
+}
+
+// save writes encoded session.Values to a file.
+func (s *FilesystemStore) save(session *Session) error {
+	encoded, err := securecookie.EncodeMulti(session.Name(), session.Values,
+		s.Codecs...)
+	if err != nil {
+		return err
+	}
+	filename := filepath.Join(s.path, "session_"+session.ID)
+	fileMutex.Lock()
+	defer fileMutex.Unlock()
+	return ioutil.WriteFile(filename, []byte(encoded), 0600)
+}
+
+// load reads a file and decodes its content into session.Values.
+func (s *FilesystemStore) load(session *Session) error {
+	filename := filepath.Join(s.path, "session_"+session.ID)
+	fileMutex.RLock()
+	defer fileMutex.RUnlock()
+	fdata, err := ioutil.ReadFile(filename)
+	if err != nil {
+		return err
+	}
+	if err = securecookie.DecodeMulti(session.Name(), string(fdata),
+		&session.Values, s.Codecs...); err != nil {
+		return err
+	}
+	return nil
+}

+ 73 - 0
src/github.com/gorilla/sessions/store_test.go

@@ -0,0 +1,73 @@
+package sessions
+
+import (
+	"encoding/base64"
+	"net/http"
+	"net/http/httptest"
+	"testing"
+)
+
+// Test for GH-8 for CookieStore
+func TestGH8CookieStore(t *testing.T) {
+	originalPath := "/"
+	store := NewCookieStore()
+	store.Options.Path = originalPath
+	req, err := http.NewRequest("GET", "http://www.example.com", nil)
+	if err != nil {
+		t.Fatal("failed to create request", err)
+	}
+
+	session, err := store.New(req, "hello")
+	if err != nil {
+		t.Fatal("failed to create session", err)
+	}
+
+	store.Options.Path = "/foo"
+	if session.Options.Path != originalPath {
+		t.Fatalf("bad session path: got %q, want %q", session.Options.Path, originalPath)
+	}
+}
+
+// Test for GH-8 for FilesystemStore
+func TestGH8FilesystemStore(t *testing.T) {
+	originalPath := "/"
+	store := NewFilesystemStore("")
+	store.Options.Path = originalPath
+	req, err := http.NewRequest("GET", "http://www.example.com", nil)
+	if err != nil {
+		t.Fatal("failed to create request", err)
+	}
+
+	session, err := store.New(req, "hello")
+	if err != nil {
+		t.Fatal("failed to create session", err)
+	}
+
+	store.Options.Path = "/foo"
+	if session.Options.Path != originalPath {
+		t.Fatalf("bad session path: got %q, want %q", session.Options.Path, originalPath)
+	}
+}
+
+// Test for GH-2.
+func TestGH2MaxLength(t *testing.T) {
+	store := NewFilesystemStore("", []byte("some key"))
+	req, err := http.NewRequest("GET", "http://www.example.com", nil)
+	if err != nil {
+		t.Fatal("failed to create request", err)
+	}
+	w := httptest.NewRecorder()
+
+	session, err := store.New(req, "my session")
+	session.Values["big"] = make([]byte, base64.StdEncoding.DecodedLen(4096*2))
+	err = session.Save(req, w)
+	if err == nil {
+		t.Fatal("expected an error, got nil")
+	}
+
+	store.MaxLength(4096 * 3) // A bit more than the value size to account for encoding overhead.
+	err = session.Save(req, w)
+	if err != nil {
+		t.Fatal("failed to Save:", err)
+	}
+}

+ 16 - 0
src/go.mongodb.org/mongo-driver/.errcheck-excludes

@@ -0,0 +1,16 @@
+(go.mongodb.org/mongo-driver/x/mongo/driver.Connection).Close
+(*go.mongodb.org/mongo-driver/x/network/connection.connection).Close
+(go.mongodb.org/mongo-driver/x/network/connection.Connection).Close
+(*go.mongodb.org/mongo-driver/x/mongo/driver/topology.connection).close
+(*go.mongodb.org/mongo-driver/x/mongo/driver/topology.Topology).Unsubscribe
+(*go.mongodb.org/mongo-driver/x/mongo/driver/topology.Server).Close
+(*go.mongodb.org/mongo-driver/x/network/connection.pool).closeConnection
+(*go.mongodb.org/mongo-driver/x/mongo/driver/topology.pool).close
+(go.mongodb.org/mongo-driver/x/network/wiremessage.ReadWriteCloser).Close
+(*go.mongodb.org/mongo-driver/mongo.Cursor).Close
+(*go.mongodb.org/mongo-driver/mongo.ChangeStream).Close
+(*go.mongodb.org/mongo-driver/mongo.Client).Disconnect
+(net.Conn).Close
+encoding/pem.Encode
+fmt.Fprintf
+fmt.Fprint

+ 911 - 0
src/go.mongodb.org/mongo-driver/.evergreen/config.yml

@@ -0,0 +1,911 @@
+########################################
+# Evergreen Template for MongoDB Drivers
+########################################
+
+# When a task that used to pass starts to fail
+# Go through all versions that may have been skipped to detect
+# when the task started failing
+stepback: true
+
+# Mark a failure as a system/bootstrap failure (purple box) rather then a task
+# failure by default.
+# Actual testing tasks are marked with `type: test`
+command_type: setup
+
+# Protect ourself against rogue test case, or curl gone wild, that runs forever
+# 12 minutes is the longest we'll ever run
+exec_timeout_secs: 3600 # 12 minutes is the longest we'll ever run
+
+# What to do when evergreen hits the timeout (`post:` tasks are run automatically)
+timeout:
+  - command: shell.exec
+    params:
+      script: |
+        ls -la
+functions:
+
+  fetch-source:
+    # Executes git clone and applies the submitted patch, if any
+    - command: git.get_project
+      type: system
+      params:
+        directory: src/go.mongodb.org/mongo-driver
+    # Make an evergreen expansion file with dynamic values
+    - command: shell.exec
+      params:
+        working_dir: src/go.mongodb.org/mongo-driver
+        script: |
+           if [ "Windows_NT" = "$OS" ]; then
+              export GOPATH=$(cygpath -w $(dirname $(dirname $(dirname `pwd`))))
+           else
+              export GOPATH=$(dirname $(dirname $(dirname `pwd`)))
+           fi;
+
+           # Get the current unique version of this checkout
+           if [ "${is_patch}" = "true" ]; then
+              CURRENT_VERSION=$(git describe)-patch-${version_id}
+           else
+              CURRENT_VERSION=latest
+           fi
+
+           export DRIVERS_TOOLS="$(pwd)/../drivers-tools"
+           export PROJECT_DIRECTORY="$(pwd)"
+           export GOCACHE="$(pwd)/.cache"
+
+           # Python has cygwin path problems on Windows. Detect prospective mongo-orchestration home directory
+           if [ "Windows_NT" = "$OS" ]; then # Magic variable in cygwin
+              export DRIVERS_TOOLS=$(cygpath -m $DRIVERS_TOOLS)
+              export PROJECT_DIRECTORY=$(cygpath -m $PROJECT_DIRECTORY)
+              export GOCACHE=$(cygpath -m $GOCACHE)
+           fi
+
+           export MONGO_ORCHESTRATION_HOME="$DRIVERS_TOOLS/.evergreen/orchestration"
+           export MONGODB_BINARIES="$DRIVERS_TOOLS/mongodb/bin"
+           export UPLOAD_BUCKET="${project}"
+           export PATH="${GO_DIST}/bin:${GCC_PATH}:$GOPATH/bin:$MONGODB_BINARIES:$PATH"
+           export PROJECT="${project}"
+
+           if [ "Windows_NT" = "$OS" ]; then
+              export USERPROFILE=$(cygpath -w $(dirname $(dirname $(dirname `pwd`))))
+              export HOME=$(cygpath -w $(dirname $(dirname $(dirname `pwd`))))
+           fi
+
+           go version
+           go env
+
+           if [ "Windows_NT" = "$OS" ]; then
+              mkdir -p c:/libmongocrypt/include
+              mkdir -p c:/libmongocrypt/bin
+              curl https://s3.amazonaws.com/mciuploads/libmongocrypt/windows-test/master/latest/libmongocrypt.tar.gz --output libmongocrypt.tar.gz
+              tar -xvzf libmongocrypt.tar.gz
+              cp ./bin/mongocrypt.dll c:/libmongocrypt/bin
+              cp ./include/mongocrypt/*.h c:/libmongocrypt/include
+              export PATH=$PATH:/cygdrive/c/libmongocrypt/bin
+           else
+              git clone https://github.com/mongodb/libmongocrypt
+              ./libmongocrypt/.evergreen/compile.sh
+           fi
+
+           cat <<EOT > expansion.yml
+           CURRENT_VERSION: "$CURRENT_VERSION"
+           DRIVERS_TOOLS: "$DRIVERS_TOOLS"
+           MONGO_ORCHESTRATION_HOME: "$MONGO_ORCHESTRATION_HOME"
+           MONGODB_BINARIES: "$MONGODB_BINARIES"
+           UPLOAD_BUCKET: "$UPLOAD_BUCKET"
+           PROJECT_DIRECTORY: "$PROJECT_DIRECTORY"
+           PREPARE_SHELL: |
+              set -o errexit
+              set -o xtrace
+              export GOPATH="$GOPATH"
+              export GOROOT="${GO_DIST}"
+              export GOCACHE="$GOCACHE"
+              export DRIVERS_TOOLS="$DRIVERS_TOOLS"
+              export MONGO_ORCHESTRATION_HOME="$MONGO_ORCHESTRATION_HOME"
+              export MONGODB_BINARIES="$MONGODB_BINARIES"
+              export UPLOAD_BUCKET="$UPLOAD_BUCKET"
+              export PROJECT_DIRECTORY="$PROJECT_DIRECTORY"
+              export TMPDIR="$MONGO_ORCHESTRATION_HOME/db"
+              export PATH="$PATH"
+              export PROJECT="$PROJECT"
+              export PKG_CONFIG_PATH=$(pwd)/install/libmongocrypt/lib/pkgconfig:$(pwd)/install/mongo-c-driver/lib/pkgconfig
+              export LD_LIBRARY_PATH=$(pwd)/install/libmongocrypt/lib
+           EOT
+           # See what we've done
+           cat expansion.yml
+    # Load the expansion file to make an evergreen variable with the current unique version
+    - command: expansions.update
+      params:
+        file: src/go.mongodb.org/mongo-driver/expansion.yml
+
+
+  prepare-resources:
+    - command: shell.exec
+      params:
+        script: |
+          ${PREPARE_SHELL}
+          rm -rf $DRIVERS_TOOLS
+          if [ "${project}" = "drivers-tools" ]; then
+            # If this was a patch build, doing a fresh clone would not actually test the patch
+            cp -R ${PROJECT_DIRECTORY}/ $DRIVERS_TOOLS
+          else
+            git clone git://github.com/mongodb-labs/drivers-evergreen-tools.git $DRIVERS_TOOLS
+          fi
+          echo "{ \"releases\": { \"default\": \"$MONGODB_BINARIES\" }}" > $MONGO_ORCHESTRATION_HOME/orchestration.config
+    - command: shell.exec
+      params:
+        working_dir: src/go.mongodb.org/mongo-driver
+        script: |
+           ${PREPARE_SHELL}
+           # any go tools that we need
+           go get -u golang.org/x/lint/golint
+           go get -u github.com/kisielk/errcheck
+
+           # initialize submodules
+           git submodule init
+           git submodule update
+
+           # generate any source code
+           make generate
+
+
+  upload-mo-artifacts:
+    - command: shell.exec
+      params:
+        script: |
+          ${PREPARE_SHELL}
+          find $MONGO_ORCHESTRATION_HOME -name \*.log | xargs tar czf mongodb-logs.tar.gz
+    - command: s3.put
+      params:
+        aws_key: ${aws_key}
+        aws_secret: ${aws_secret}
+        local_file: mongodb-logs.tar.gz
+        remote_file: ${UPLOAD_BUCKET}/${build_variant}/${revision}/${version_id}/${build_id}/logs/${task_id}-${execution}-mongodb-logs.tar.gz
+        bucket: mciuploads
+        permissions: public-read
+        content_type: ${content_type|application/x-gzip}
+        display_name: "mongodb-logs.tar.gz"
+
+  bootstrap-mongo-orchestration:
+    - command: shell.exec
+      params:
+        script: |
+          ${PREPARE_SHELL}
+
+          cp ${PROJECT_DIRECTORY}/data/certificates/server.pem ${DRIVERS_TOOLS}/.evergreen/x509gen/server.pem
+          cp ${PROJECT_DIRECTORY}/data/certificates/ca.pem ${DRIVERS_TOOLS}/.evergreen/x509gen/ca.pem
+          cp ${PROJECT_DIRECTORY}/data/certificates/client.pem ${DRIVERS_TOOLS}/.evergreen/x509gen/client.pem
+          cp ${PROJECT_DIRECTORY}/data/certificates/client.pem ${MONGO_ORCHESTRATION_HOME}/lib/client.pem
+
+          MONGODB_VERSION=${VERSION} AUTH=${AUTH} SSL=${SSL} sh ${DRIVERS_TOOLS}/.evergreen/run-orchestration.sh
+    - command: expansions.update
+      params:
+        file: mo-expansion.yml
+
+
+  cleanup:
+    - command: shell.exec
+      params:
+        script: |
+          ${PREPARE_SHELL}
+          cd "$MONGO_ORCHESTRATION_HOME"
+          # source the mongo-orchestration virtualenv if it exists
+          if [ -f venv/bin/activate ]; then
+            . venv/bin/activate
+          elif [ -f venv/Scripts/activate ]; then
+            . venv/Scripts/activate
+          fi
+          mongo-orchestration stop
+          cd -
+          rm -rf $DRIVERS_TOOLS || true
+
+
+  fix-absolute-paths:
+    - command: shell.exec
+      params:
+        script: |
+          ${PREPARE_SHELL}
+          for filename in $(find ${DRIVERS_TOOLS} -name \*.json); do
+            perl -p -i -e "s|ABSOLUTE_PATH_REPLACEMENT_TOKEN|${DRIVERS_TOOLS}|g" $filename
+          done
+
+
+  windows-fix:
+    - command: shell.exec
+      params:
+        script: |
+          ${PREPARE_SHELL}
+          for i in $(find ${DRIVERS_TOOLS}/.evergreen ${PROJECT_DIRECTORY} -name \*.sh); do
+            cat $i | tr -d '\r' > $i.new
+            mv $i.new $i
+          done
+
+
+  make-files-executable:
+    - command: shell.exec
+      params:
+        script: |
+          ${PREPARE_SHELL}
+          for i in $(find ${DRIVERS_TOOLS}/.evergreen ${PROJECT_DIRECTORY} -name \*.sh); do
+            chmod +x $i
+          done
+
+
+  run-make:
+    - command: shell.exec
+      type: test
+      params:
+        working_dir: src/go.mongodb.org/mongo-driver
+        script: |
+          ${PREPARE_SHELL}
+          ${BUILD_ENV|} make ${targets} BUILD_TAGS="-tags gssapi"
+
+
+  run-tests:
+    - command: shell.exec
+      type: test
+      params:
+        working_dir: src/go.mongodb.org/mongo-driver
+        script: |
+          ${PREPARE_SHELL}
+
+          if [ ${SSL} = "ssl" ]; then
+              export MONGO_GO_DRIVER_CA_FILE="$PROJECT_DIRECTORY/data/certificates/ca.pem"
+
+              if [ "Windows_NT" = "$OS" ]; then # Magic variable in cygwin
+                  export MONGO_GO_DRIVER_CA_FILE=$(cygpath -m $MONGO_GO_DRIVER_CA_FILE)
+              fi
+          fi
+
+          set +o xtrace
+          AUTH=${AUTH} \
+          SSL=${SSL} \
+          MONGODB_URI="${MONGODB_URI}" \
+          MONGO_GO_DRIVER_COMPRESSOR=${MONGO_GO_DRIVER_COMPRESSOR} \
+          BUILD_TAGS="-tags cse" \
+          AWS_ACCESS_KEY_ID="${cse_aws_access_key_id}" \
+          AWS_SECRET_ACCESS_KEY="${cse_aws_secret_access_key}" \
+          make evg-test \
+          PKG_CONFIG_PATH=$PKG_CONFIG_PATH \
+          LD_LIBRARY_PATH=$LD_LIBRARY_PATH
+
+  send-perf-data:
+   - command: json.send
+     params:
+       name: perf
+       file: src/go.mongodb.org/mongo-driver/perf.json
+
+
+  run-enterprise-auth-tests:
+    - command: shell.exec
+      type: test
+      params:
+        silent: true
+        working_dir: src/go.mongodb.org/mongo-driver
+        script: |
+          # DO NOT ECHO WITH XTRACE (which PREPARE_SHELL does)
+          if [ "Windows_NT" = "$OS" ]; then
+            export GOPATH=$(cygpath -w $(dirname $(dirname $(dirname `pwd`))))
+            export GOCACHE=$(cygpath -w "$(pwd)/.cache")
+          else
+            export GOPATH=$(dirname $(dirname $(dirname `pwd`)))
+            export GOCACHE="$(pwd)/.cache"
+          fi;
+          export GOPATH="$GOPATH"
+          export GOROOT="${GO_DIST}"
+          export GOCACHE="$GOCACHE"
+          export PATH="${GCC_PATH}:${GO_DIST}/bin:$PATH"
+          MONGODB_URI="${MONGODB_URI}" MONGO_GO_DRIVER_COMPRESSOR="${MONGO_GO_DRIVER_COMPRESSOR}" make -s evg-test-auth
+
+  run-enterprise-gssapi-auth-tests:
+    - command: shell.exec
+      type: test
+      params:
+        working_dir: src/go.mongodb.org/mongo-driver
+        script: |
+          # DO NOT ECHO WITH XTRACE (which PREPARE_SHELL does)
+          if [ "Windows_NT" = "$OS" ]; then
+            export GOPATH=$(cygpath -w $(dirname $(dirname $(dirname `pwd`))))
+            export GOCACHE=$(cygpath -w "$(pwd)/.cache")
+            export MONGODB_URI=${gssapi_auth_windows_mongodb_uri}
+          else
+            export GOPATH=$(dirname $(dirname $(dirname `pwd`)))
+            export GOCACHE="$(pwd)/.cache"
+            echo "${gssapi_auth_linux_keytab_base64}" > /tmp/drivers.keytab.base64
+            base64 --decode /tmp/drivers.keytab.base64 > ${PROJECT_DIRECTORY}/.evergreen/drivers.keytab
+            mkdir -p ~/.krb5
+            cat .evergreen/krb5.config | tee -a ~/.krb5/config
+            kinit -k -t ${PROJECT_DIRECTORY}/.evergreen/drivers.keytab -p "${gssapi_auth_username}"
+            export MONGODB_URI="${gssapi_auth_linux_mongodb_uri}"
+          fi;
+          export GOPATH="$GOPATH"
+          export GOROOT="${GO_DIST}"
+          export GOCACHE="$GOCACHE"
+          export PATH="${GCC_PATH}:${GO_DIST}/bin:$PATH"
+          MONGO_GO_DRIVER_COMPRESSOR="${MONGO_GO_DRIVER_COMPRESSOR}" make -s evg-test-auth
+
+  run-enterprise-gssapi-service-host-auth-tests:
+    - command: shell.exec
+      type: test
+      params:
+        working_dir: src/go.mongodb.org/mongo-driver
+        script: |
+          # DO NOT ECHO WITH XTRACE (which PREPARE_SHELL does)
+          if [ "Windows_NT" = "$OS" ]; then
+            export GOPATH=$(cygpath -w $(dirname $(dirname $(dirname `pwd`))))
+            export GOCACHE=$(cygpath -w "$(pwd)/.cache")
+            export MONGODB_URI="${gssapi_service_host_auth_windows_mongodb_uri}"
+          else
+            export GOPATH=$(dirname $(dirname $(dirname `pwd`)))
+            export GOCACHE="$(pwd)/.cache"
+            echo "${gssapi_auth_linux_keytab_base64}" > /tmp/drivers.keytab.base64
+            base64 --decode /tmp/drivers.keytab.base64 > ${PROJECT_DIRECTORY}/.evergreen/drivers.keytab
+            mkdir -p ~/.krb5
+            cat .evergreen/krb5.config | tee -a ~/.krb5/config
+            kinit -k -t ${PROJECT_DIRECTORY}/.evergreen/drivers.keytab -p "${gssapi_auth_username}"
+            export MONGODB_URI="${gssapi_service_host_auth_linux_mongodb_uri}"
+          fi;
+          export GOPATH="$GOPATH"
+          export GOROOT="${GO_DIST}"
+          export GOCACHE="$GOCACHE"
+          export PATH="${GCC_PATH}:${GO_DIST}/bin:$PATH"
+          MONGO_GO_DRIVER_COMPRESSOR="${MONGO_GO_DRIVER_COMPRESSOR}" make -s evg-test-auth
+
+  run-atlas-test:
+    - command: shell.exec
+      type: test
+      params:
+        working_dir: src/go.mongodb.org/mongo-driver
+        script: |
+          # DO NOT ECHO WITH XTRACE (which PREPARE_SHELL does)
+          if [ "Windows_NT" = "$OS" ]; then
+            export GOPATH=$(cygpath -w $(dirname $(dirname $(dirname `pwd`))))
+            export GOCACHE=$(cygpath -w "$(pwd)/.cache")
+          else
+            export GOPATH=$(dirname $(dirname $(dirname `pwd`)))
+            export GOCACHE="$(pwd)/.cache"
+          fi;
+          export GOPATH="$GOPATH"
+          export GOROOT="${GO_DIST}"
+          export GOCACHE="$GOCACHE"
+          export PATH="${GCC_PATH}:${GO_DIST}/bin:$PATH"
+          export ATLAS_FREE="${atlas_free_tier_uri}"
+          export ATLAS_REPLSET="${atlas_replica_set_uri}"
+          export ATLAS_SHARD="${atlas_sharded_uri}"
+          export ATLAS_TLS11="${atlas_tls_v11_uri}"
+          export ATLAS_TLS12="${atlas_tls_v12_uri}"
+          export ATLAS_FREE_SRV="${atlas_free_tier_uri_srv}"
+          export ATLAS_REPLSET_SRV="${atlas_replica_set_uri_srv}"
+          export ATLAS_SHARD_SRV="${atlas_sharded_uri_srv}"
+          export ATLAS_TLS11_SRV="${atlas_tls_v11_uri_srv}"
+          export ATLAS_TLS12_SRV="${atlas_tls_v12_uri_srv}"
+          make -s evg-test-atlas
+
+
+pre:
+  - func: fetch-source
+  - func: prepare-resources
+  - func: windows-fix
+  - func: fix-absolute-paths
+  - func: make-files-executable
+
+post:
+  - command: gotest.parse_files
+    params:
+      files:
+        - "src/go.mongodb.org/mongo-driver/*.suite"
+  - func: upload-mo-artifacts
+  - func: cleanup
+
+tasks:
+    - name: sa-fmt
+      tags: ["static-analysis"]
+      commands:
+        - func: run-make
+          vars:
+            targets: check-fmt
+
+    - name: sa-errcheck
+      tags: ["static-analysis"]
+      commands:
+        - func: run-make
+          vars:
+            targets: errcheck
+
+
+    - name: sa-lint
+      tags: ["static-analysis"]
+      commands:
+        - func: run-make
+          vars:
+            targets: lint
+
+    - name: sa-vet
+      tags: ["static-analysis"]
+      commands:
+        - func: run-make
+          vars:
+            targets: vet
+
+    - name: perf
+      tags: ["performance"]
+      exec_timeout_secs: 7200
+      commands:
+        - func: bootstrap-mongo-orchestration
+          vars:
+            AUTH: "noauth"
+            SSL: "nossl"
+        - func: run-make
+          vars:
+            targets: driver-benchmark
+        - func: send-perf-data
+
+    - name: sa-build-examples
+      tags: ["static-analysis"]
+      commands:
+        - func: run-make
+          vars:
+            targets: build-examples
+
+    - name: test-standalone-noauth-nossl
+      tags: ["test", "standalone"]
+      commands:
+        - func: bootstrap-mongo-orchestration
+          vars:
+            AUTH: "noauth"
+            SSL: "nossl"
+        - func: run-tests
+          vars:
+            AUTH: "noauth"
+            SSL: "nossl"
+
+    - name: test-standalone-noauth-nossl-snappy-compression
+      tags: ["test", "standalone", "compression", "snappy"]
+      commands:
+        - func: bootstrap-mongo-orchestration
+          vars:
+            AUTH: "noauth"
+            SSL: "nossl"
+        - func: run-tests
+          vars:
+            AUTH: "noauth"
+            SSL: "nossl"
+            MONGO_GO_DRIVER_COMPRESSOR: "snappy"
+
+    - name: test-standalone-noauth-nossl-zlib-compression
+      tags: ["test", "standalone", "compression", "zlib"]
+      commands:
+        - func: bootstrap-mongo-orchestration
+          vars:
+            AUTH: "noauth"
+            SSL: "nossl"
+        - func: run-tests
+          vars:
+            AUTH: "noauth"
+            SSL: "nossl"
+            MONGO_GO_DRIVER_COMPRESSOR: "zlib"
+
+    - name: test-standalone-noauth-nossl-zstd-compression
+      tags: ["test", "standalone", "compression", "zstd"]
+      commands:
+        - func: bootstrap-mongo-orchestration
+          vars:
+            AUTH: "noauth"
+            SSL: "nossl"
+        - func: run-tests
+          vars:
+            AUTH: "noauth"
+            SSL: "nossl"
+            MONGO_GO_DRIVER_COMPRESSOR: "zstd"
+
+    - name: test-standalone-auth-ssl
+      tags: ["test", "standalone", "authssl"]
+      commands:
+        - func: bootstrap-mongo-orchestration
+          vars:
+            AUTH: "auth"
+            SSL: "ssl"
+        - func: run-tests
+          vars:
+            AUTH: "auth"
+            SSL: "ssl"
+
+    - name: test-standalone-auth-ssl-snappy-compression
+      tags: ["test", "standalone", "authssl", "compression", "snappy"]
+      commands:
+        - func: bootstrap-mongo-orchestration
+          vars:
+            AUTH: "auth"
+            SSL: "ssl"
+        - func: run-tests
+          vars:
+            AUTH: "auth"
+            SSL: "ssl"
+            MONGO_GO_DRIVER_COMPRESSOR: "snappy"
+
+    - name: test-standalone-auth-ssl-zlib-compression
+      tags: ["test", "standalone", "authssl", "compression", "zlib"]
+      commands:
+        - func: bootstrap-mongo-orchestration
+          vars:
+            AUTH: "auth"
+            SSL: "ssl"
+        - func: run-tests
+          vars:
+            AUTH: "auth"
+            SSL: "ssl"
+            MONGO_GO_DRIVER_COMPRESSOR: "zlib"
+
+    - name: test-standalone-auth-ssl-zstd-compression
+      tags: ["test", "standalone", "authssl", "compression", "zstd"]
+      commands:
+        - func: bootstrap-mongo-orchestration
+          vars:
+            AUTH: "auth"
+            SSL: "ssl"
+        - func: run-tests
+          vars:
+            AUTH: "auth"
+            SSL: "ssl"
+            MONGO_GO_DRIVER_COMPRESSOR: "zstd"
+
+    - name: test-replicaset-noauth-nossl
+      tags: ["test", "replicaset"]
+      commands:
+        - func: bootstrap-mongo-orchestration
+          vars:
+            AUTH: "noauth"
+            SSL: "nossl"
+        - func: run-tests
+          vars:
+            AUTH: "noauth"
+            SSL: "nossl"
+
+    - name: test-replicaset-auth-ssl
+      tags: ["test", "replicaset", "authssl"]
+      commands:
+        - func: bootstrap-mongo-orchestration
+          vars:
+            AUTH: "auth"
+            SSL: "ssl"
+        - func: run-tests
+          vars:
+            AUTH: "auth"
+            SSL: "ssl"
+
+    - name: test-sharded-noauth-nossl
+      tags: ["test", "sharded"]
+      commands:
+        - func: bootstrap-mongo-orchestration
+          vars:
+            AUTH: "noauth"
+            SSL: "nossl"
+        - func: run-tests
+          vars:
+            AUTH: "noauth"
+            SSL: "nossl"
+
+    - name: test-sharded-noauth-nossl-snappy-compression
+      tags: ["test", "sharded", "compression", "snappy"]
+      commands:
+        - func: bootstrap-mongo-orchestration
+          vars:
+            AUTH: "noauth"
+            SSL: "nossl"
+        - func: run-tests
+          vars:
+            AUTH: "noauth"
+            SSL: "nossl"
+            MONGO_GO_DRIVER_COMPRESSOR: "snappy"
+
+    - name: test-sharded-noauth-nossl-zlib-compression
+      tags: ["test", "sharded", "compression", "zlib"]
+      commands:
+        - func: bootstrap-mongo-orchestration
+          vars:
+            AUTH: "noauth"
+            SSL: "nossl"
+        - func: run-tests
+          vars:
+            AUTH: "noauth"
+            SSL: "nossl"
+            MONGO_GO_DRIVER_COMPRESSOR: "zlib"
+
+    - name: test-sharded-noauth-nossl-zstd-compression
+      tags: ["test", "sharded", "compression", "zstd"]
+      commands:
+        - func: bootstrap-mongo-orchestration
+          vars:
+            AUTH: "noauth"
+            SSL: "nossl"
+        - func: run-tests
+          vars:
+            AUTH: "noauth"
+            SSL: "nossl"
+            MONGO_GO_DRIVER_COMPRESSOR: "zstd"
+
+    - name: test-sharded-auth-ssl
+      tags: ["test", "sharded", "authssl"]
+      commands:
+        - func: bootstrap-mongo-orchestration
+          vars:
+            AUTH: "auth"
+            SSL: "ssl"
+        - func: run-tests
+          vars:
+            AUTH: "auth"
+            SSL: "ssl"
+
+    - name: test-sharded-auth-ssl-snappy-compression
+      tags: ["test", "sharded", "authssl", "compression", "snappy"]
+      commands:
+        - func: bootstrap-mongo-orchestration
+          vars:
+            AUTH: "auth"
+            SSL: "ssl"
+        - func: run-tests
+          vars:
+            AUTH: "auth"
+            SSL: "ssl"
+            MONGO_GO_DRIVER_COMPRESSOR: "snappy"
+
+    - name: test-sharded-auth-ssl-zlib-compression
+      tags: ["test", "sharded", "authssl", "compression", "zlib"]
+      commands:
+        - func: bootstrap-mongo-orchestration
+          vars:
+            AUTH: "auth"
+            SSL: "ssl"
+        - func: run-tests
+          vars:
+            AUTH: "auth"
+            SSL: "ssl"
+            MONGO_GO_DRIVER_COMPRESSOR: "zlib"
+
+    - name: test-sharded-auth-ssl-zstd-compression
+      tags: ["test", "sharded", "authssl", "compression", "zstd"]
+      commands:
+        - func: bootstrap-mongo-orchestration
+          vars:
+            AUTH: "auth"
+            SSL: "ssl"
+        - func: run-tests
+          vars:
+            AUTH: "auth"
+            SSL: "ssl"
+            MONGO_GO_DRIVER_COMPRESSOR: "zstd"
+
+    - name: test-enterprise-auth-plain
+      tags: ["test", "enterprise-auth"]
+      commands:
+        - func: run-enterprise-auth-tests
+          vars:
+            MONGODB_URI: "${plain_auth_mongodb_uri}"
+
+    - name: test-enterprise-auth-gssapi
+      tags: ["test", "enterprise-auth"]
+      commands:
+        - func: run-enterprise-gssapi-auth-tests
+
+    - name: test-enterprise-auth-gssapi-service-host
+      tags: ["test", "enterprise-auth"]
+      commands:
+        - func: run-enterprise-gssapi-service-host-auth-tests
+          vars:
+            MONGO_GO_DRIVER_COMPRESSOR: "snappy"
+
+    - name: go1.9-build
+      tags: ["compile-check"]
+      commands:
+        - func: run-make
+          vars:
+            targets: "build"
+            BUILD_ENV: "PATH=/opt/golang/go1.9/bin:$PATH GOROOT=/opt/golang/go1.9"
+
+    - name: go1.9-build-nocgo
+      tags: ["compile-check"]
+      commands:
+        - func: run-make
+          vars:
+            targets: "build"
+            BUILD_ENV: "CGO_ENABLED=0 PATH=/opt/golang/go1.9/bin:$PATH GOROOT=/opt/golang/go1.9"
+
+    - name: go1.10-build
+      tags: ["compile-check"]
+      commands:
+        - func: run-make
+          vars:
+            targets: "build"
+            BUILD_ENV: "PATH=/opt/golang/go1.10/bin:$PATH GOROOT=/opt/golang/go1.10"
+
+    - name: go1.10-build-nocgo
+      tags: ["compile-check"]
+      commands:
+        - func: run-make
+          vars:
+            targets: "build"
+            BUILD_ENV: "CGO_ENABLED=0 PATH=/opt/golang/go1.10/bin:$PATH GOROOT=/opt/golang/go1.10"
+
+    - name: linux-32-bit
+      tags: ["compile-check"]
+      commands:
+        - func: run-make
+          vars:
+            targets: "build"
+            BUILD_ENV: "GOARCH=386"
+
+    - name: linux-arm64
+      tags: ["compile-check"]
+      commands:
+        - func: run-make
+          vars:
+            targets: "build"
+            BUILD_ENV: "GOARCH=arm64"
+
+    - name: linux-s390x
+      tags: ["compile-check"]
+      commands:
+        - func: run-make
+          vars:
+            targets: "build"
+            BUILD_ENV: "GOARCH=ppc64le"
+
+    - name: "atlas-test"
+      commands:
+        - func: "run-atlas-test"
+
+    - name: go1.10-build-cse
+      tags: ["compile-check"]
+      commands:
+        - func: run-make
+          vars:
+            targets: "build-cse"
+            BUILD_ENV: "PATH=/opt/golang/go1.10/bin:$PATH GOROOT=/opt/golang/go1.10"
+
+axes:
+  - id: version
+    display_name: MongoDB Version
+    values:
+      - id: "4.2"
+        display_name: "4.2"
+        variables:
+          VERSION: "4.2"
+      - id: "4.0"
+        display_name: "4.0"
+        variables:
+           VERSION: "4.0"
+      - id: "3.6"
+        display_name: "3.6"
+        variables:
+           VERSION: "3.6"
+      - id: "3.4"
+        display_name: "3.4"
+        variables:
+           VERSION: "3.4"
+      - id: "3.2"
+        display_name: "3.2"
+        variables:
+           VERSION: "3.2"
+      - id: "3.0"
+        display_name: "3.0"
+        variables:
+          VERSION: "3.0"
+      - id: "2.6"
+        display_name: "2.6"
+        variables:
+          VERSION: "2.6"
+      - id: "latest"
+        display_name: "latest"
+        variables:
+           VERSION: "latest"
+
+  # OSes that support versions of MongoDB >= 2.6 with SSL.
+  - id: os-ssl-legacy
+    display_name: OS
+    values:
+      - id: "ubuntu1404-go-1-12"
+        display_name: "Ubuntu 14.04"
+        run_on: ubuntu1404-test
+        variables:
+          GO_DIST: "/opt/golang/go1.12"
+
+  # OSes that require >= 3.2 for SSL
+  - id: os-ssl-32
+    display_name: OS
+    values:
+      - id: "windows-64-go-1-12"
+        display_name: "Windows 64-bit"
+        run_on:
+          - windows-64-vs2015-test
+        variables:
+          GCC_PATH: "/cygdrive/c/mingw-w64/x86_64-4.9.1-posix-seh-rt_v3-rev1/mingw64/bin"
+          GO_DIST: "C:\\golang\\go1.12"
+      - id: "ubuntu1604-64-go-1-12"
+        display_name: "Ubuntu 16.04"
+        run_on: ubuntu1604-build
+        variables:
+          GO_DIST: "/opt/golang/go1.12"
+      - id: "osx-go-1-12"
+        display_name: "MacOS 10.14"
+        run_on: macos-1014
+        variables:
+          GO_DIST: "/opt/golang/go1.12"
+
+
+buildvariants:
+- name: static-analysis
+  display_name: "Static Analysis"
+  run_on:
+    - ubuntu1604-build
+  expansions:
+    GO_DIST: "/opt/golang/go1.12"
+  tasks:
+    - name: ".static-analysis"
+
+- name: perf
+  display_name: "Performance"
+  run_on:
+    - ubuntu1604-build
+  expansions:
+    GO_DIST: "/opt/golang/go1.12"
+  tasks:
+    - name: ".performance"
+
+- name: build-check
+  display_name: "Compile Only Checks"
+  run_on:
+    - ubuntu1604-test
+  expansions:
+    GO_DIST: "/opt/golang/go1.12"
+  tasks:
+    - name: ".compile-check"
+
+- name: atlas-test
+  display_name: "Atlas test"
+  run_on:
+    - ubuntu1604-build
+  expansions:
+    GO_DIST: "/opt/golang/go1.12"
+  tasks:
+    - name: "atlas-test"
+
+- matrix_name: "tests-legacy-auth-ssl"
+  matrix_spec: { version: ["2.6", "3.0"], os-ssl-legacy: "*" }
+  display_name: "${version} ${os-ssl-legacy}"
+  tasks:
+    - name: ".test !.enterprise-auth !.compression"
+
+- matrix_name: "tests-legacy-noauth-nossl"
+  matrix_spec: { version: ["2.6", "3.0"], os-ssl-32: "*" }
+  display_name: "${version} ${os-ssl-32}"
+  tasks:
+    - name: ".test !.authssl !.enterprise-auth !.compression"
+
+- matrix_name: "tests-nonlegacy-servers"
+  matrix_spec: { version: "3.2", os-ssl-32: "*" }
+  display_name: "${version} ${os-ssl-32}"
+  tasks:
+    - name: ".test !.enterprise-auth !.compression"
+
+- matrix_name: "tests-nonlegacy-servers-with-snappy-support"
+  matrix_spec: { version: "3.4", os-ssl-32: "*" }
+  display_name: "${version} ${os-ssl-32}"
+  tasks:
+    - name: ".test !.enterprise-auth !.zlib !.zstd"
+
+- matrix_name: "tests-36-40-with-zlib-support"
+  matrix_spec: { version: ["3.6", "4.0"], os-ssl-32: "*" }
+  display_name: "${version} ${os-ssl-32}"
+  tasks:
+    - name: ".test !.enterprise-auth !.snappy !.zstd"
+
+- matrix_name: "tests-42-lts-zlib-zstd-support"
+  matrix_spec: { version: ["4.2", "latest"], os-ssl-32: "*" }
+  display_name: "${version} ${os-ssl-32}"
+  tasks:
+    - name: ".test !.enterprise-auth !.snappy"
+
+- matrix_name: "enterprise-auth-tests"
+  matrix_spec: { os-ssl-32: "*" }
+  display_name: "Enterprise Auth - ${os-ssl-32}"
+  tasks:
+     - name: ".test .enterprise-auth"

+ 8 - 0
src/go.mongodb.org/mongo-driver/.evergreen/krb5.config

@@ -0,0 +1,8 @@
+[realms]
+  LDAPTEST.10GEN.CC = {
+    kdc = ldaptest.10gen.cc
+    admin_server = ldaptest.10gen.cc
+  }
+
+[libdefaults]
+  rdns = false

+ 12 - 0
src/go.mongodb.org/mongo-driver/.gitignore

@@ -0,0 +1,12 @@
+.vscode
+debug
+.idea
+*.iml
+*.ipr
+*.iws
+.idea
+*.sublime-project
+*.sublime-workspace
+driver-test-data.tar.gz
+perf
+**mongocryptd.pid

+ 3 - 0
src/go.mongodb.org/mongo-driver/.gitmodules

@@ -0,0 +1,3 @@
+[submodule "specifications"]
+	path = specifications
+	url = git@github.com:mongodb/specifications.git

+ 64 - 0
src/go.mongodb.org/mongo-driver/.lint-whitelist

@@ -0,0 +1,64 @@
+bson/bson.go:103:5: error var SetZero should have name of the form ErrFoo
+bson/bson.go:187:6: type ObjectId should be ObjectID
+bson/bson.go:192:6: func ObjectIdHex should be ObjectIDHex
+bson/bson.go:202:6: func IsObjectIdHex should be IsObjectIDHex
+bson/bson.go:249:6: func NewObjectId should be NewObjectID
+bson/bson.go:273:6: func NewObjectIdWithTime should be NewObjectIDWithTime
+bson/bson.go:470:2: struct field Id should be ID
+bson/bson.go:587:21: error strings should not be capitalized or end with punctuation or a newline
+bson/bson.go:589:21: error strings should not be capitalized or end with punctuation or a newline
+bson/bson.go:613:21: error strings should not be capitalized or end with punctuation or a newline
+bson/bson.go:615:21: error strings should not be capitalized or end with punctuation or a newline
+bson/encode.go:46:2: var typeObjectId should be typeObjectID
+bson/internal/json/stream_test.go:196:3: struct field Id should be ID
+bson/internal/json/stream_test.go:221:3: struct field Id should be ID
+bson/internal/json/stream_test.go:285:22: should omit type []tokenStreamCase from declaration of var tokenStreamCases; it will be inferred from the right-hand side
+bson/internal/testutil/close_helper.go:14:1: exported function CloseReadOnlyFile should have comment or be unexported
+bson/internal/testutil/close_helper.go:8:1: exported function CloseOrError should have comment or be unexported
+bson/json.go:246:6: func jdecObjectId should be jdecObjectID
+bson/json.go:263:6: func jencObjectId should be jencObjectID
+mongo/internal/testutil/helpers/helpers.go:10:1: exported function RequireNoErrorOnClose should have comment or be unexported
+mongo/internal/testutil/helpers/helpers.go:14:1: exported function FindJSONFilesInDir should have comment or be unexported
+mongo/internal/testutil/helpers/helpers.go:45:1: exported function VerifyConnStringOptions should have comment or be unexported
+mongo/options/find_and_modify.go:19:1: exported function CopyFindOneAndReplaceOptions should have comment or be unexported
+mongo/options/find_and_modify.go:29:1: exported function CopyFindOneAndUpdateOptions should have comment or be unexported
+mongo/options/find_and_modify.go:9:1: exported function CopyFindOneAndDeleteOptions should have comment or be unexported
+mongo/options.go:157:1: exported function OplogReplay should have comment or be unexported
+mongo/options.go:177:56: exported func ReadPreference returns unexported type *mongo.optReadPreference, which can be annoying to use
+bson/internal/jsonparser/bytes.go:9:10: should omit type bool from declaration of var neg; it will be inferred from the right-hand side
+bson/internal/jsonparser/bytes.go:25:9: if block ends with a return statement, so drop this else and outdent its block
+bson/internal/jsonparser/escape.go:113:10: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary)
+bson/internal/jsonparser/escape.go:123:1: comment on exported function Unescape should be of the form "Unescape ..."
+bson/internal/jsonparser/parser.go:14:2: error var KeyPathNotFoundError should have name of the form ErrFoo
+bson/internal/jsonparser/parser.go:15:2: error var UnknownValueTypeError should have name of the form ErrFoo
+bson/internal/jsonparser/parser.go:16:2: error var MalformedJsonError should have name of the form ErrFoo
+bson/internal/jsonparser/parser.go:16:2: var MalformedJsonError should be MalformedJSONError
+bson/internal/jsonparser/parser.go:17:2: error var MalformedStringError should have name of the form ErrFoo
+bson/internal/jsonparser/parser.go:18:2: error var MalformedArrayError should have name of the form ErrFoo
+bson/internal/jsonparser/parser.go:19:2: error var MalformedObjectError should have name of the form ErrFoo
+bson/internal/jsonparser/parser.go:20:2: error var MalformedValueError should have name of the form ErrFoo
+bson/internal/jsonparser/parser.go:21:2: error var MalformedStringEscapeError should have name of the form ErrFoo
+bson/internal/jsonparser/parser.go:147:11: if block ends with a return statement, so drop this else and outdent its block
+bson/internal/jsonparser/parser.go:285:6: should replace curIdx += 1 with curIdx++
+bson/internal/jsonparser/parser.go:292:12: if block ends with a return statement, so drop this else and outdent its block
+bson/internal/jsonparser/parser.go:303:12: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary)
+bson/internal/jsonparser/parser.go:329:6: don't use underscores in Go names; range var pi_1 should be pi1
+bson/internal/jsonparser/parser.go:329:12: don't use underscores in Go names; range var p_1 should be p1
+bson/internal/jsonparser/parser.go:338:1: exported function EachKey should have comment or be unexported
+bson/internal/jsonparser/parser.go:489:6: should replace curIdx += 1 with curIdx++
+bson/internal/jsonparser/parser.go:503:12: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary)
+bson/internal/jsonparser/parser.go:517:1: comment on exported type ValueType should be of the form "ValueType ..." (with optional leading article)
+bson/internal/jsonparser/parser.go:521:2: exported const NotExist should have comment (or a comment on this block) or be unexported
+bson/internal/jsonparser/parser.go:582:1: comment on exported function Delete should be of the form "Delete ..."
+bson/internal/jsonparser/parser.go:931:10: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary)
+bson/internal/jsonparser/parser.go:971:10: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary)
+bson/internal/jsonparser/parser.go:980:11: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary)
+bson/internal/jsonparser/parser.go:1006:10: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary)
+bson/internal/jsonparser/parser.go:1021:10: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary)
+bson/internal/jsonparser/parser.go:1128:9: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary)
+bson/internal/jsonparser/parser.go:1133:1: comment on exported function ParseFloat should be of the form "ParseFloat ..."
+bson/internal/jsonparser/parser.go:1137:9: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary)
+bson/internal/jsonparser/parser.go:1146:9: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary)
+bson/internal/jsonparser/parser_test.go:1361:5: var testJson should be testJSON
+bson/internal/jsonpretty/pretty.go:7:1: comment on exported type Options should be of the form "Options ..." (with optional leading article)
+examples/documentation_examples/examples.go:10:1: don't use an underscore in package name

+ 37 - 0
src/go.mongodb.org/mongo-driver/CONTRIBUTING.md

@@ -0,0 +1,37 @@
+# Contributing to the MongoDB Go Driver
+
+Thank you for your interest in contributing to the MongoDB Go driver.
+
+We are building this software together and strongly encourage contributions from the community that are within the guidelines set forth
+below.
+
+## Bug Fixes and New Features
+
+Before starting to write code, look for existing [tickets](https://jira.mongodb.org/browse/GODRIVER) or
+[create one](https://jira.mongodb.org/secure/CreateIssue!default.jspa) for your bug, issue, or feature request. This helps the community
+avoid working on something that might not be of interest or which has already been addressed.
+
+## Pull Requests & Patches
+
+The Go Driver team is experimenting with GerritHub for contributions. GerritHub uses GitHub for authentication and uses a patch based
+workflow. Since GerritHub supports importing of Pull Requests we will also accept Pull Requests, but Code Review will be done in
+GerritHub.
+
+Patches should generally be made against the master (default) branch and include relevant tests, if applicable.
+
+Code should compile and tests should pass under all go versions which the driver currently supports.  Currently the driver
+supports a minimum version of go 1.7. Please ensure the following tools have been run on the code: gofmt, golint, errcheck,
+go test (with coverage and with the race detector), and go vet. For convenience, you can run 'make' to run all these tools.
+**By default, running the tests requires that you have a mongod server running on localhost, listening on the default port.**
+At minimum, please test against the latest release version of the MongoDB server.
+
+If any tests do not pass, or relevant tests are not included, the patch will not be considered.
+
+If you are working on a bug or feature listed in Jira, please include the ticket number prefixed with GODRIVER in the commit,
+e.g. GODRIVER-123. For the patch commit message itself, please follow the [How to Write a Git Commit Message](https://chris.beams.io/posts/git-commit/) guide.
+
+## Talk To Us
+
+If you want to work on the driver, write documentation, or have questions/complaints, please reach out to use either via
+the [mongo-go-driver Google Group](https://groups.google.com/forum/#!forum/mongodb-go-driver) or by creating a Question
+issue at (https://jira.mongodb.org/secure/CreateIssue!default.jspa).

+ 367 - 0
src/go.mongodb.org/mongo-driver/Gopkg.lock

@@ -0,0 +1,367 @@
+# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
+
+
+[[projects]]
+  digest = "1:6d8a3b164679872fa5a4c44559235f7fb109c7b5cd0f456a2159d579b76cc9ba"
+  name = "github.com/DataDog/zstd"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "809b919c325d7887bff7bd876162af73db53e878"
+  version = "v1.4.0"
+
+[[projects]]
+  digest = "1:ffe9824d294da03b391f44e1ae8281281b4afc1bdaa9588c9097785e3af10cec"
+  name = "github.com/davecgh/go-spew"
+  packages = ["spew"]
+  pruneopts = "UT"
+  revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73"
+  version = "v1.1.1"
+
+[[projects]]
+  digest = "1:586ea76dbd0374d6fb649a91d70d652b7fe0ccffb8910a77468e7702e7901f3d"
+  name = "github.com/go-stack/stack"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "2fee6af1a9795aafbe0253a0cfbdf668e1fb8a9a"
+  version = "v1.8.0"
+
+[[projects]]
+  digest = "1:d29ee5ef14a7e0253facd0bcebe6a69a7a4e02a67eb24d2aacd8ccb4a7cea6fc"
+  name = "github.com/gobuffalo/envy"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "043cb4b8af871b49563291e32c66bb84378a60ac"
+  version = "v1.7.0"
+
+[[projects]]
+  digest = "1:149bf2c3b6b332f0cfefed9ae4b8c01c527927d369d7a0f41a211c8bf588b556"
+  name = "github.com/gobuffalo/genny"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "05dc5921e98daa5d406f90c2844ff163cb1e1c4e"
+  version = "v0.1.1"
+
+[[projects]]
+  digest = "1:3812ee37547a0d26633006c910672c42ddf6f932f79dea6a4446979a561f7d60"
+  name = "github.com/gobuffalo/gogen"
+  packages = [
+    ".",
+    "goimports",
+    "gomods",
+  ]
+  pruneopts = "UT"
+  revision = "5482e6ef5d999f8cfa9b038a53953c9b4ee090af"
+  version = "v0.1.1"
+
+[[projects]]
+  branch = "master"
+  digest = "1:f391538f4166f7c0aa50943372af196304488b039fe663c134d42f064e1ed92c"
+  name = "github.com/gobuffalo/logger"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "86e12af44bc273e0063fad5b6ad588890b8cfe6b"
+
+[[projects]]
+  digest = "1:a3259d2a6c245b62e40232800e3ebb4d7d5840940638de0e4754cb13cdd37790"
+  name = "github.com/gobuffalo/mapi"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "0bb5e840be332d4280e40f2e6c50777c615bbac5"
+  version = "v1.0.2"
+
+[[projects]]
+  digest = "1:1342bed0d7aded5e5c1c958675f0662c9dbaf2d61fa0a8c5918a144737cff3e4"
+  name = "github.com/gobuffalo/packd"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "9efbb667f2024a9370cadda45a57a0657c88a6fb"
+  version = "v0.1.0"
+
+[[projects]]
+  digest = "1:0fac441be66684180b031233313b8315644c0734fd73944c3e156e3cf5fbe203"
+  name = "github.com/gobuffalo/packr"
+  packages = [
+    "v2",
+    "v2/file",
+    "v2/file/resolver",
+    "v2/file/resolver/encoding/hex",
+    "v2/jam/parser",
+    "v2/plog",
+  ]
+  pruneopts = "UT"
+  revision = "cda4ac25577350dac682c8715dc6659327232596"
+  version = "v2.2.0"
+
+[[projects]]
+  branch = "master"
+  digest = "1:b90ac64448d67ef218124185a8524f3c665fb7f51f096d5dd51ce950a1775d89"
+  name = "github.com/gobuffalo/syncx"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "33c29581e754bd354236e977dfe426e55331c45d"
+
+[[projects]]
+  branch = "master"
+  digest = "1:e4f5819333ac698d294fe04dbf640f84719658d5c7ce195b10060cc37292ce79"
+  name = "github.com/golang/snappy"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "2a8bb927dd31d8daada140a5d09578521ce5c36a"
+
+[[projects]]
+  digest = "1:d2754cafcab0d22c13541618a8029a70a8959eb3525ff201fe971637e2274cd0"
+  name = "github.com/google/go-cmp"
+  packages = [
+    "cmp",
+    "cmp/cmpopts",
+    "cmp/internal/diff",
+    "cmp/internal/function",
+    "cmp/internal/value",
+  ]
+  pruneopts = "UT"
+  revision = "3af367b6b30c263d47e8895973edcca9a49cf029"
+  version = "v0.2.0"
+
+[[projects]]
+  digest = "1:ecd9aa82687cf31d1585d4ac61d0ba180e42e8a6182b85bd785fcca8dfeefc1b"
+  name = "github.com/joho/godotenv"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "23d116af351c84513e1946b527c88823e476be13"
+  version = "v1.3.0"
+
+[[projects]]
+  digest = "1:19cce3954c778b676622745e850f7d201eefffbb62d2c42ce87673617f96e95a"
+  name = "github.com/karrick/godirwalk"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "5e617d9cfec6f870e63b168e9a2fb7c490a1c108"
+  version = "v1.10.3"
+
+[[projects]]
+  digest = "1:31e761d97c76151dde79e9d28964a812c46efc5baee4085b86f68f0c654450de"
+  name = "github.com/konsorten/go-windows-terminal-sequences"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "f55edac94c9bbba5d6182a4be46d86a2c9b5b50e"
+  version = "v1.0.2"
+
+[[projects]]
+  digest = "1:ca955a9cd5b50b0f43d2cc3aeb35c951473eeca41b34eb67507f1dbcc0542394"
+  name = "github.com/kr/pretty"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "73f6ac0b30a98e433b289500d779f50c1a6f0712"
+  version = "v0.1.0"
+
+[[projects]]
+  digest = "1:15b5cc79aad436d47019f814fde81a10221c740dc8ddf769221a65097fb6c2e9"
+  name = "github.com/kr/text"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "e2ffdb16a802fe2bb95e2e35ff34f0e53aeef34f"
+  version = "v0.1.0"
+
+[[projects]]
+  branch = "master"
+  digest = "1:6e2ed1bdbf1d14b4d0be58bcd3f1c3000c1e226964354457b8e6ca69e83a1cbb"
+  name = "github.com/markbates/oncer"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "bf2de49a0be218916e69a11d22866e6cd0a560f2"
+
+[[projects]]
+  digest = "1:28687e854cec240942c103259668b132a8450b05a6cc677eea2282b26ae29310"
+  name = "github.com/markbates/safe"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "6fea05a5732486546a4836b7a1d596c5ec687b98"
+  version = "v1.0.1"
+
+[[projects]]
+  digest = "1:246ab598a22ea9d50f46e65f655f78161a19822f6597268b02f04af998684807"
+  name = "github.com/montanaflynn/stats"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "1bf9dbcd8cbe1fdb75add3785b1d4a9a646269ab"
+  version = "0.3.0"
+
+[[projects]]
+  digest = "1:93131d8002d7025da13582877c32d1fc302486775a1b06f62241741006428c5e"
+  name = "github.com/pelletier/go-toml"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "728039f679cbcd4f6a54e080d2219a4c4928c546"
+  version = "v1.4.0"
+
+[[projects]]
+  digest = "1:cf31692c14422fa27c83a05292eb5cbe0fb2775972e8f1f8446a71549bd8980b"
+  name = "github.com/pkg/errors"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "ba968bfe8b2f7e042a574c888954fccecfa385b4"
+  version = "v0.8.1"
+
+[[projects]]
+  digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe"
+  name = "github.com/pmezard/go-difflib"
+  packages = ["difflib"]
+  pruneopts = "UT"
+  revision = "792786c7400a136282c1664665ae0a8db921c6c2"
+  version = "v1.0.0"
+
+[[projects]]
+  digest = "1:e09ada96a5a41deda4748b1659cc8953961799e798aea557257b56baee4ecaf3"
+  name = "github.com/rogpeppe/go-internal"
+  packages = [
+    "modfile",
+    "module",
+    "semver",
+  ]
+  pruneopts = "UT"
+  revision = "438578804ca6f31be148c27683afc419ce47c06e"
+  version = "v1.3.0"
+
+[[projects]]
+  digest = "1:04457f9f6f3ffc5fea48e71d62f2ca256637dee0a04d710288e27e05c8b41976"
+  name = "github.com/sirupsen/logrus"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "839c75faf7f98a33d445d181f3018b5c3409a45e"
+  version = "v1.4.2"
+
+[[projects]]
+  digest = "1:5da8ce674952566deae4dbc23d07c85caafc6cfa815b0b3e03e41979cedb8750"
+  name = "github.com/stretchr/testify"
+  packages = [
+    "assert",
+    "require",
+  ]
+  pruneopts = "UT"
+  revision = "ffdc059bfe9ce6a4e144ba849dbedead332c6053"
+  version = "v1.3.0"
+
+[[projects]]
+  branch = "master"
+  digest = "1:ddfe0a54e5f9b29536a6d7b2defa376f2cb2b6e4234d676d7ff214d5b097cb50"
+  name = "github.com/tidwall/pretty"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "1166b9ac2b65e46a43d8618d30d1554f4652d49b"
+
+[[projects]]
+  branch = "master"
+  digest = "1:40fdfd6ab85ca32b6935853bbba35935dcb1d796c8135efd85947566c76e662e"
+  name = "github.com/xdg/scram"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "7eeb5667e42c09cb51bf7b7c28aea8c56767da90"
+
+[[projects]]
+  branch = "master"
+  digest = "1:f5c1d04bc09c644c592b45b9f0bad4030521b1a7d11c7dadbb272d9439fa6e8e"
+  name = "github.com/xdg/stringprep"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "73f8eece6fdcd902c185bf651de50f3828bed5ed"
+
+[[projects]]
+  branch = "master"
+  digest = "1:35ce874cf16f78a2da908ab28b0277497e36603d289db00d39da5200bc8ace08"
+  name = "golang.org/x/crypto"
+  packages = [
+    "pbkdf2",
+    "ssh/terminal",
+  ]
+  pruneopts = "UT"
+  revision = "20be4c3c3ed52bfccdb2d59a412ee1a936d175a7"
+
+[[projects]]
+  branch = "master"
+  digest = "1:76ee51c3f468493aff39dbacc401e8831fbb765104cbf613b89bef01cf4bad70"
+  name = "golang.org/x/net"
+  packages = ["context"]
+  pruneopts = "UT"
+  revision = "f3200d17e092c607f615320ecaad13d87ad9a2b3"
+
+[[projects]]
+  branch = "master"
+  digest = "1:382bb5a7fb4034db3b6a2d19e5a4a6bcf52f4750530603c01ca18a172fa3089b"
+  name = "golang.org/x/sync"
+  packages = ["semaphore"]
+  pruneopts = "UT"
+  revision = "112230192c580c3556b8cee6403af37a4fc5f28c"
+
+[[projects]]
+  branch = "master"
+  digest = "1:a02364950e8c7394b03f886e57a3f7acfc4fba32ce03c186dd9f36b501d0484b"
+  name = "golang.org/x/sys"
+  packages = [
+    "unix",
+    "windows",
+  ]
+  pruneopts = "UT"
+  revision = "4c3a928424d249a1013e789cf5e458d480bcba16"
+
+[[projects]]
+  digest = "1:1093f2eb4b344996604f7d8b29a16c5b22ab9e1b25652140d3fede39f640d5cd"
+  name = "golang.org/x/text"
+  packages = [
+    "internal/gen",
+    "internal/triegen",
+    "internal/ucd",
+    "transform",
+    "unicode/cldr",
+    "unicode/norm",
+  ]
+  pruneopts = "UT"
+  revision = "342b2e1fbaa52c93f31447ad2c6abc048c63e475"
+  version = "v0.3.2"
+
+[[projects]]
+  branch = "master"
+  digest = "1:7c7aa31cc6bc5c85038e46d8c6541a2fb2332b1687229a5a5a04d7b91cea0efe"
+  name = "golang.org/x/tools"
+  packages = [
+    "go/ast/astutil",
+    "go/gcexportdata",
+    "go/internal/gcimporter",
+    "go/internal/packagesdriver",
+    "go/packages",
+    "go/types/typeutil",
+    "imports",
+    "internal/fastwalk",
+    "internal/gopathwalk",
+    "internal/imports",
+    "internal/module",
+    "internal/semver",
+  ]
+  pruneopts = "UT"
+  revision = "b3315ee88b7da2b23cc6c62484a006958910e841"
+
+[solve-meta]
+  analyzer-name = "dep"
+  analyzer-version = 1
+  input-imports = [
+    "github.com/DataDog/zstd",
+    "github.com/go-stack/stack",
+    "github.com/gobuffalo/packr/v2",
+    "github.com/golang/snappy",
+    "github.com/google/go-cmp/cmp",
+    "github.com/google/go-cmp/cmp/cmpopts",
+    "github.com/kr/pretty",
+    "github.com/montanaflynn/stats",
+    "github.com/pelletier/go-toml",
+    "github.com/pkg/errors",
+    "github.com/stretchr/testify/assert",
+    "github.com/stretchr/testify/require",
+    "github.com/tidwall/pretty",
+    "github.com/xdg/scram",
+    "github.com/xdg/stringprep",
+    "golang.org/x/net/context",
+    "golang.org/x/sync/semaphore",
+    "golang.org/x/tools/go/packages",
+    "golang.org/x/tools/imports",
+  ]
+  solver-name = "gps-cdcl"
+  solver-version = 1

+ 58 - 0
src/go.mongodb.org/mongo-driver/Gopkg.toml

@@ -0,0 +1,58 @@
+# Gopkg.toml example
+#
+# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
+# for detailed Gopkg.toml documentation.
+#
+# required = ["github.com/user/thing/cmd/thing"]
+# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
+#
+# [[constraint]]
+#   name = "github.com/user/project"
+#   version = "1.0.0"
+#
+# [[constraint]]
+#   name = "github.com/user/project2"
+#   branch = "dev"
+#   source = "github.com/myfork/project2"
+#
+# [[override]]
+#   name = "github.com/x/y"
+#   version = "2.4.0"
+#
+# [prune]
+#   non-go = false
+#   go-tests = true
+#   unused-packages = true
+
+
+[[constraint]]
+  name = "github.com/go-stack/stack"
+  version = "1.7.0"
+
+[[constraint]]
+  branch = "master"
+  name = "github.com/golang/snappy"
+
+[[constraint]]
+  name = "github.com/google/go-cmp"
+  version = "0.2.0"
+
+[[constraint]]
+  name = "github.com/montanaflynn/stats"
+  version = "0.3.0"
+
+[[constraint]]
+  branch = "master"
+  name = "github.com/tidwall/pretty"
+
+[[constraint]]
+  branch = "master"
+  name = "github.com/xdg/stringprep"
+
+[prune]
+  go-tests = true
+  unused-packages = true
+
+[[constraint]]
+  name = "github.com/DataDog/zstd"
+  version = "~1.4.0"

+ 201 - 0
src/go.mongodb.org/mongo-driver/LICENSE

@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.

+ 147 - 0
src/go.mongodb.org/mongo-driver/Makefile

@@ -0,0 +1,147 @@
+BSON_PKGS = $(shell etc/list_pkgs.sh ./bson)
+BSON_TEST_PKGS = $(shell etc/list_test_pkgs.sh ./bson)
+MONGO_PKGS = $(shell etc/list_pkgs.sh ./mongo)
+MONGO_TEST_PKGS = $(shell etc/list_test_pkgs.sh ./mongo)
+UNSTABLE_PKGS = $(shell etc/list_pkgs.sh ./x)
+UNSTABLE_TEST_PKGS = $(shell etc/list_test_pkgs.sh ./x)
+TAG_PKG = $(shell etc/list_pkgs.sh ./tag)
+TAG_TEST_PKG = $(shell etc/list_test_pkgs.sh ./tag)
+EXAMPLES_PKGS = $(shell etc/list_pkgs.sh ./examples)
+EXAMPLES_TEST_PKGS = $(shell etc/list_test_pkgs.sh ./examples)
+PKGS = $(BSON_PKGS) $(MONGO_PKGS) $(UNSTABLE_PKGS) $(TAG_PKG) $(EXAMPLES_PKGS)
+TEST_PKGS = $(BSON_TEST_PKGS) $(MONGO_TEST_PKGS) $(UNSTABLE_TEST_PKGS) $(TAG_PKG) $(EXAMPLES_TEST_PKGS)
+ATLAS_URIS = "$(ATLAS_FREE)" "$(ATLAS_REPLSET)" "$(ATLAS_SHARD)" "$(ATLAS_TLS11)" "$(ATLAS_TLS12)" "$(ATLAS_FREE_SRV)" "$(ATLAS_REPLSET_SRV)" "$(ATLAS_SHARD_SRV)" "$(ATLAS_TLS11_SRV)" "$(ATLAS_TLS12_SRV)"
+
+TEST_TIMEOUT = 600
+
+.PHONY: default
+default: check-env check-fmt vet build-examples lint errcheck test-cover test-race
+
+.PHONY: check-env
+check-env:
+	etc/check_env.sh
+
+.PHONY: doc
+doc:
+	godoc -http=:6060 -index
+
+.PHONY: build-examples
+build-examples:
+	go build $(BUILD_TAGS) ./examples/... ./x/mongo/driver/examples/...
+
+.PHONY: build
+build:
+	go build $(filter-out ./core/auth/internal/gssapi,$(PKGS))
+
+.PHONY: build-cse
+build-cse:
+	go build -tags cse $(filter-out ./core/auth/internal/gssapi,$(PKGS))
+
+.PHONY: check-fmt
+check-fmt:
+	etc/check_fmt.sh $(PKGS)
+
+.PHONY: fmt
+fmt:
+	gofmt -l -s -w $(PKGS)
+
+.PHONY: lint
+lint:
+	golint $(PKGS) | ./etc/lintscreen.pl .lint-whitelist
+
+.PHONY: lint-add-whitelist
+lint-add-whitelist:
+	golint $(PKGS) | ./etc/lintscreen.pl -u .lint-whitelist
+	sort .lint-whitelist -o .lint-whitelist
+
+.PHONY: errcheck
+errcheck:
+	errcheck -exclude .errcheck-excludes ./bson/... ./mongo/... ./x/...
+
+.PHONY: test
+test:
+	for TEST in $(TEST_PKGS) ; do \
+		go test $(BUILD_TAGS) -timeout $(TEST_TIMEOUT)s $$TEST ; \
+	done
+
+.PHONY: test-cover
+test-cover:
+	for TEST in $(TEST_PKGS) ; do \
+    	go test $(BUILD_TAGS) -timeout $(TEST_TIMEOUT)s -cover $(COVER_ARGS) $$TEST ; \
+    done
+
+.PHONY: test-race
+test-race:
+	for TEST in $(TEST_PKGS) ; do \
+    	go test $(BUILD_TAGS) -timeout $(TEST_TIMEOUT)s -race $(COVER_ARGS) $$TEST ; \
+    done
+
+.PHONY: test-short
+test-short:
+	for TEST in $(TEST_PKGS) ; do \
+    	go test $(BUILD_TAGS) -timeout $(TEST_TIMEOUT)s -short $(COVER_ARGS) $$TEST ; \
+    done
+
+.PHONY: update-bson-corpus-tests
+update-bson-corpus-tests:
+	etc/update-spec-tests.sh bson-corpus
+
+.PHONY: update-connection-string-tests
+update-connection-string-tests:
+	etc/update-spec-tests.sh connection-string
+
+.PHONY: update-crud-tests
+update-crud-tests:
+	etc/update-spec-tests.sh crud
+
+.PHONY: update-initial-dns-seedlist-discovery-tests
+update-initial-dns-seedlist-discovery-tests:
+	etc/update-spec-tests.sh initial-dns-seedlist-discovery
+
+.PHONY: update-max-staleness-tests
+update-max-staleness-tests:
+	etc/update-spec-tests.sh max-staleness
+
+.PHONY: update-server-discovery-and-monitoring-tests
+update-server-discovery-and-monitoring-tests:
+	etc/update-spec-tests.sh server-discovery-and-monitoring
+
+.PHONY: update-server-selection-tests
+update-server-selection-tests:
+	etc/update-spec-tests.sh server-selection
+
+.PHONY: update-notices
+update-notices:
+	etc/generate-notices.pl > THIRD-PARTY-NOTICES
+
+.PHONY: vet
+vet:
+	go vet -cgocall=false -composites=false -unusedstringmethods="Error" $(PKGS)
+
+
+# Evergreen specific targets
+.PHONY: evg-test
+evg-test:
+	for TEST in $(TEST_PKGS) ; do \
+		LD_LIBRARY_PATH=$(LD_LIBRARY_PATH) PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) go test $(BUILD_TAGS) -v -timeout $(TEST_TIMEOUT)s $$TEST >> test.suite ; \
+	done
+
+.PHONY: evg-test-auth
+evg-test-auth:
+	go run -tags gssapi ./x/mongo/driver/examples/count/main.go -uri $(MONGODB_URI)
+
+.PHONY: evg-test-atlas
+evg-test-atlas:
+	go run ./mongo/testatlas/main.go $(ATLAS_URIS)
+
+# benchmark specific targets and support
+perf:driver-test-data.tar.gz
+	tar -zxf $< $(if $(eq $(UNAME_S),Darwin),-s , --transform=s)/data/perf/
+	@touch $@
+driver-test-data.tar.gz:
+	curl --retry 5 "https://s3.amazonaws.com/boxes.10gen.com/build/driver-test-data.tar.gz" -o driver-test-data.tar.gz --silent --max-time 120
+benchmark:perf
+	go test $(BUILD_TAGS) -benchmem -bench=. ./benchmark
+driver-benchmark:perf
+	@go run cmd/godriver-benchmark/main.go | tee perf.suite
+.PHONY:benchmark driver-benchmark

+ 193 - 0
src/go.mongodb.org/mongo-driver/README.md

@@ -0,0 +1,193 @@
+<p align="center"><img src="etc/assets/mongo-gopher.png" width="250"></p>
+<p align="center">
+  <a href="https://goreportcard.com/report/go.mongodb.org/mongo-driver"><img src="https://goreportcard.com/badge/go.mongodb.org/mongo-driver"></a>
+  <a href="https://godoc.org/go.mongodb.org/mongo-driver/mongo"><img src="etc/assets/godoc-mongo-blue.svg" alt="GoDoc"></a>
+  <a href="https://godoc.org/go.mongodb.org/mongo-driver/bson"><img src="etc/assets/godoc-bson-blue.svg" alt="GoDoc"></a>
+  <a href="https://docs.mongodb.com/ecosystem/drivers/go/"><img src="etc/assets/docs-mongodb-green.svg"></a>
+</p>
+
+# MongoDB Go Driver
+
+The MongoDB supported driver for Go.
+
+-------------------------
+- [Requirements](#requirements)
+- [Installation](#installation)
+- [Usage](#usage)
+- [Bugs / Feature Reporting](#bugs--feature-reporting)
+- [Testing / Development](#testing--development)
+- [Continuous Integration](#continuous-integration)
+- [License](#license)
+
+-------------------------
+## Requirements
+
+- Go 1.10 or higher. We aim to support the latest supported versions of go.
+- MongoDB 2.6 and higher.
+
+-------------------------
+## Installation
+
+The recommended way to get started using the MongoDB Go driver is by using `dep` to install the dependency in your project.
+
+```bash
+dep ensure -add "go.mongodb.org/mongo-driver/mongo@~1.1.0"
+```
+
+-------------------------
+## Usage
+
+To get started with the driver, import the `mongo` package, create a `mongo.Client`:
+
+```go
+import (
+    "go.mongodb.org/mongo-driver/mongo"
+    "go.mongodb.org/mongo-driver/mongo/options"
+)
+
+client, err := mongo.NewClient(options.Client().ApplyURI("mongodb://localhost:27017"))
+```
+
+And connect it to your running MongoDB server:
+
+```go
+ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
+err = client.Connect(ctx)
+```
+
+To do this in a single step, you can use the `Connect` function:
+
+```go
+ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
+client, err := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://localhost:27017"))
+```
+
+Calling `Connect` does not block for server discovery. If you wish to know if a MongoDB server has been found and connected to,
+use the `Ping` method:
+
+```go
+ctx, _ = context.WithTimeout(context.Background(), 2*time.Second)
+err = client.Ping(ctx, readpref.Primary())
+```
+
+To insert a document into a collection, first retrieve a `Database` and then `Collection` instance from the `Client`:
+
+```go
+collection := client.Database("testing").Collection("numbers")
+```
+
+The `Collection` instance can then be used to insert documents:
+
+```go
+ctx, _ = context.WithTimeout(context.Background(), 5*time.Second)
+res, err := collection.InsertOne(ctx, bson.M{"name": "pi", "value": 3.14159})
+id := res.InsertedID
+```
+
+Several query methods return a cursor, which can be used like this:
+
+```go
+ctx, _ = context.WithTimeout(context.Background(), 30*time.Second)
+cur, err := collection.Find(ctx, bson.D{})
+if err != nil { log.Fatal(err) }
+defer cur.Close(ctx)
+for cur.Next(ctx) {
+   var result bson.M
+   err := cur.Decode(&result)
+   if err != nil { log.Fatal(err) }
+   // do something with result....
+}
+if err := cur.Err(); err != nil {
+  log.Fatal(err)
+}
+```
+
+For methods that return a single item, a `SingleResult` instance is returned:
+
+```go
+var result struct {
+    Value float64
+}
+filter := bson.M{"name": "pi"}
+ctx, _ = context.WithTimeout(context.Background(), 5*time.Second)
+err = collection.FindOne(ctx, filter).Decode(&result)
+if err != nil {
+    log.Fatal(err)
+}
+// Do something with result...
+```
+
+Additional examples and documentation can be found under the examples directory and [on the MongoDB Documentation website](https://docs.mongodb.com/ecosystem/drivers/go/).
+
+-------------------------
+## Bugs / Feature Reporting
+
+New Features and bugs can be reported on jira: https://jira.mongodb.org/browse/GODRIVER
+
+-------------------------
+## Testing / Development
+
+The driver tests can be run against several database configurations. The most simple configuration is a standalone mongod with no auth, no ssl, and no compression. To run these basic driver tests, make sure a standalone MongoDB server instance is running at localhost:27017. To run the tests, you can run `make` (on Windows, run `nmake`). This will run coverage, run go-lint, run go-vet, and build the examples.
+
+### Testing Different Topologies
+
+To test a **replica set** or **sharded cluster**, set `MONGODB_URI="<connection-string>"` for the `make` command.
+For example, for a local replica set named `rs1` comprised of three nodes on ports 27017, 27018, and 27019:
+
+```
+MONGODB_URI="mongodb://localhost:27017,localhost:27018,localhost:27018/?replicaSet=rs1" make
+```
+
+### Testing Auth and SSL
+
+To test authentication and SSL, first set up a MongoDB cluster with auth and SSL configured. Testing authentication requires a user with the `root` role on the `admin` database. The Go Driver repository comes with example certificates in the `data/certificates` directory. These certs can be used for testing. Here is an example command that would run a mongod with SSL correctly configured for tests:
+
+```
+mongod \
+--auth \
+--sslMode requireSSL \
+--sslPEMKeyFile $(pwd)/data/certificates/server.pem \
+--sslCAFile $(pwd)/data/certificates/ca.pem \
+--sslWeakCertificateValidation
+```
+
+To run the tests with `make`, set `MONGO_GO_DRIVER_CA_FILE` to the location of the CA file used by the database, set `MONGODB_URI` to the connection string of the server, set `AUTH=auth`, and set `SSL=ssl`. For example:
+
+```
+AUTH=auth SSL=ssl MONGO_GO_DRIVER_CA_FILE=$(pwd)/data/certificates/ca.pem  MONGODB_URI="mongodb://user:password@localhost:27017/?authSource=admin" make
+```
+
+Notes:
+- The `--sslWeakCertificateValidation` flag is required on the server for the test suite to work correctly.
+- The test suite requires the auth database to be set with `?authSource=admin`, not `/admin`.
+
+### Testing Compression
+
+The MongoDB Go Driver supports wire protocol compression using Snappy, zLib, or zstd. To run tests with wire protocol compression, set `MONGO_GO_DRIVER_COMPRESSOR` to `snappy`, `zlib`, or `zstd`.  For example:
+
+```
+MONGO_GO_DRIVER_COMPRESSOR=snappy make
+```
+
+Ensure the [`--networkMessageCompressors` flag](https://docs.mongodb.com/manual/reference/program/mongod/#cmdoption-mongod-networkmessagecompressors) on mongod or mongos includes `zlib` if testing zLib compression.
+
+-------------------------
+## Feedback
+
+The MongoDB Go Driver is not feature complete, so any help is appreciated. Check out the [project page](https://jira.mongodb.org/browse/GODRIVER)
+for tickets that need completing. See our [contribution guidelines](CONTRIBUTING.md) for details.
+
+-------------------------
+## Continuous Integration
+
+Commits to master are run automatically on [evergreen](https://evergreen.mongodb.com/waterfall/mongo-go-driver).
+
+-------------------------
+## Thanks and Acknowledgement 
+
+<a href="https://github.com/ashleymcnamara">@ashleymcnamara</a> - Mongo Gopher Artwork
+
+-------------------------
+## License
+
+The MongoDB Go Driver is licensed under the [Apache License](LICENSE).

+ 1336 - 0
src/go.mongodb.org/mongo-driver/THIRD-PARTY-NOTICES

@@ -0,0 +1,1336 @@
+---------------------------------------------------------------------
+License notice for gopkg.in/mgo.v2/bson
+---------------------------------------------------------------------
+
+BSON library for Go
+
+Copyright (c) 2010-2013 - Gustavo Niemeyer <gustavo@niemeyer.net>
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met: 
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer. 
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution. 
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+---------------------------------------------------------------------
+License notice for JSON and CSV code from github.com/golang/go
+---------------------------------------------------------------------
+
+Copyright (c) 2009 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+License notice for github.com/davecgh/go-spew
+----------------------------------------------------------------------
+
+ISC License
+
+Copyright (c) 2012-2016 Dave Collins <dave@davec.name>
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+----------------------------------------------------------------------
+License notice for github.com/gobuffalo/genny
+----------------------------------------------------------------------
+
+The MIT License (MIT)
+
+Copyright (c) 2019 Mark Bates
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+----------------------------------------------------------------------
+License notice for github.com/gobuffalo/genny/genny
+----------------------------------------------------------------------
+
+The MIT License (MIT)
+
+Copyright © 2018 Mark Bates
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+----------------------------------------------------------------------
+License notice for github.com/gobuffalo/gogen
+----------------------------------------------------------------------
+
+The MIT License (MIT)
+
+Copyright (c) 2019 Mark Bates
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+----------------------------------------------------------------------
+License notice for github.com/gobuffalo/gogen/goimports
+----------------------------------------------------------------------
+
+Copyright (c) 2009 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+License notice for github.com/gobuffalo/logger
+----------------------------------------------------------------------
+
+The MIT License (MIT)
+
+Copyright (c) 2018 Mark Bates
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+----------------------------------------------------------------------
+License notice for github.com/gobuffalo/mapi
+----------------------------------------------------------------------
+
+The MIT License (MIT)
+
+Copyright (c) 2018 Mark Bates
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+----------------------------------------------------------------------
+License notice for github.com/gobuffalo/packd
+----------------------------------------------------------------------
+
+The MIT License (MIT)
+
+Copyright (c) 2018 Mark Bates
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+----------------------------------------------------------------------
+License notice for github.com/gobuffalo/packr/v2/packr2
+----------------------------------------------------------------------
+
+The MIT License (MIT)
+
+Copyright © 2018 Mark Bates
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+----------------------------------------------------------------------
+License notice for github.com/gobuffalo/syncx
+----------------------------------------------------------------------
+
+The MIT License (MIT)
+
+Copyright (c) 2018 Mark Bates
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+----------------------------------------------------------------------
+License notice for github.com/golang/snappy
+----------------------------------------------------------------------
+
+Copyright (c) 2011 The Snappy-Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+License notice for github.com/google/go-cmp
+----------------------------------------------------------------------
+
+Copyright (c) 2017 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+License notice for github.com/karrick/godirwalk
+----------------------------------------------------------------------
+
+BSD 2-Clause License
+
+Copyright (c) 2017, Karrick McDermott
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+License notice for github.com/konsorten/go-windows-terminal-sequences
+----------------------------------------------------------------------
+
+(The MIT License)
+
+Copyright (c) 2017 marvin + konsorten GmbH (open-source@konsorten.de)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+----------------------------------------------------------------------
+License notice for github.com/kr/pretty
+----------------------------------------------------------------------
+
+The MIT License (MIT)
+
+Copyright 2012 Keith Rarick
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+----------------------------------------------------------------------
+License notice for github.com/kr/text
+----------------------------------------------------------------------
+
+Copyright 2012 Keith Rarick
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+----------------------------------------------------------------------
+License notice for github.com/markbates/oncer
+----------------------------------------------------------------------
+
+The MIT License (MIT)
+
+Copyright (c) 2018 Mark Bates
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+----------------------------------------------------------------------
+License notice for github.com/markbates/safe
+----------------------------------------------------------------------
+
+The MIT License (MIT)
+
+Copyright (c) 2018 Mark Bates
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+----------------------------------------------------------------------
+License notice for github.com/montanaflynn/stats
+----------------------------------------------------------------------
+
+The MIT License (MIT)
+
+Copyright (c) 2014-2015 Montana Flynn (https://anonfunction.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+----------------------------------------------------------------------
+License notice for github.com/pelletier/go-toml
+----------------------------------------------------------------------
+
+The MIT License (MIT)
+
+Copyright (c) 2013 - 2017 Thomas Pelletier, Eric Anderton
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+----------------------------------------------------------------------
+License notice for github.com/pkg/errors
+----------------------------------------------------------------------
+
+Copyright (c) 2015, Dave Cheney <dave@cheney.net>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+License notice for github.com/pmezard/go-difflib
+----------------------------------------------------------------------
+
+Copyright (c) 2013, Patrick Mezard
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+    Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+    The names of its contributors may not be used to endorse or promote
+products derived from this software without specific prior written
+permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+License notice for github.com/rogpeppe/go-internal
+----------------------------------------------------------------------
+
+Copyright (c) 2018 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+License notice for github.com/sirupsen/logrus
+----------------------------------------------------------------------
+
+The MIT License (MIT)
+
+Copyright (c) 2014 Simon Eskildsen
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+----------------------------------------------------------------------
+License notice for github.com/stretchr/testify
+----------------------------------------------------------------------
+
+MIT License
+
+Copyright (c) 2012-2018 Mat Ryer and Tyler Bunnell
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+----------------------------------------------------------------------
+License notice for github.com/tidwall/pretty
+----------------------------------------------------------------------
+
+The MIT License (MIT)
+
+Copyright (c) 2017 Josh Baker
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+----------------------------------------------------------------------
+License notice for github.com/xdg/scram
+----------------------------------------------------------------------
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+----------------------------------------------------------------------
+License notice for github.com/xdg/stringprep
+----------------------------------------------------------------------
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+----------------------------------------------------------------------
+License notice for golang.org/x/crypto
+----------------------------------------------------------------------
+
+Copyright (c) 2009 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+License notice for golang.org/x/net
+----------------------------------------------------------------------
+
+Copyright (c) 2009 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+License notice for golang.org/x/sync
+----------------------------------------------------------------------
+
+Copyright (c) 2009 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+License notice for golang.org/x/sys
+----------------------------------------------------------------------
+
+Copyright (c) 2009 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+License notice for golang.org/x/text
+----------------------------------------------------------------------
+
+Copyright (c) 2009 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+License notice for golang.org/x/tools
+----------------------------------------------------------------------
+
+Copyright (c) 2009 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+License notice for golang.org/x/tools/cmd/getgo
+----------------------------------------------------------------------
+
+Copyright (c) 2017 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 75 - 0
src/go.mongodb.org/mongo-driver/benchmark/bson.go

@@ -0,0 +1,75 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+package benchmark
+
+import (
+	"errors"
+	"io/ioutil"
+	"path/filepath"
+
+	"go.mongodb.org/mongo-driver/bson"
+	"go.mongodb.org/mongo-driver/x/bsonx"
+)
+
+const (
+	perfDataDir  = "perf"
+	bsonDataDir  = "extended_bson"
+	flatBSONData = "flat_bson.json"
+	deepBSONData = "deep_bson.json"
+	fullBSONData = "full_bson.json"
+)
+
+// utility functions for the bson benchmarks
+
+func loadSourceDocument(pathParts ...string) (bsonx.Doc, error) {
+	data, err := ioutil.ReadFile(filepath.Join(pathParts...))
+	if err != nil {
+		return nil, err
+	}
+	doc := bsonx.Doc{}
+	err = bson.UnmarshalExtJSON(data, true, &doc)
+	if err != nil {
+		return nil, err
+	}
+
+	if len(doc) == 0 {
+		return nil, errors.New("empty bson document")
+	}
+
+	return doc, nil
+}
+
+func loadSourceRaw(pathParts ...string) (bson.Raw, error) {
+	doc, err := loadSourceDocument(pathParts...)
+	if err != nil {
+		return nil, err
+	}
+	raw, err := doc.MarshalBSON()
+	if err != nil {
+		return nil, err
+	}
+
+	return bson.Raw(raw), nil
+}
+
+func loadSourceD(pathParts ...string) (bson.D, error) {
+	data, err := ioutil.ReadFile(filepath.Join(pathParts...))
+	if err != nil {
+		return nil, err
+	}
+	doc := bson.D{}
+	err = bson.UnmarshalExtJSON(data, true, &doc)
+	if err != nil {
+		return nil, err
+	}
+
+	if len(doc) == 0 {
+		return nil, errors.New("empty bson document")
+	}
+
+	return doc, nil
+}

+ 123 - 0
src/go.mongodb.org/mongo-driver/benchmark/bson_document.go

@@ -0,0 +1,123 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+package benchmark
+
+import (
+	"context"
+	"errors"
+
+	"go.mongodb.org/mongo-driver/x/bsonx"
+)
+
+func bsonDocumentEncoding(ctx context.Context, tm TimerManager, iters int, source string) error {
+	doc, err := loadSourceDocument(getProjectRoot(), perfDataDir, bsonDataDir, source)
+	if err != nil {
+		return err
+	}
+
+	tm.ResetTimer()
+
+	for i := 0; i < iters; i++ {
+		out, err := doc.MarshalBSON()
+		if err != nil {
+			return err
+		}
+		if len(out) == 0 {
+			return errors.New("marshaling error")
+		}
+	}
+
+	return nil
+}
+
+func bsonDocumentDecodingLazy(ctx context.Context, tm TimerManager, iters int, source string) error {
+	doc, err := loadSourceDocument(getProjectRoot(), perfDataDir, bsonDataDir, source)
+	if err != nil {
+		return err
+	}
+
+	raw, err := doc.MarshalBSON()
+	if err != nil {
+		return err
+	}
+
+	tm.ResetTimer()
+
+	for i := 0; i < iters; i++ {
+		out, err := bsonx.ReadDoc(raw)
+		if err != nil {
+			return err
+		}
+		if len(out) == 0 {
+			return errors.New("marshaling error")
+		}
+	}
+	return nil
+}
+
+func bsonDocumentDecoding(ctx context.Context, tm TimerManager, iters, numKeys int, source string) error {
+	doc, err := loadSourceDocument(getProjectRoot(), perfDataDir, bsonDataDir, source)
+	if err != nil {
+		return err
+	}
+
+	raw, err := doc.MarshalBSON()
+	if err != nil {
+		return err
+	}
+
+	tm.ResetTimer()
+
+	for i := 0; i < iters; i++ {
+		out, err := bsonx.ReadDoc(raw)
+		if err != nil {
+			return err
+		}
+
+		if len(out) != numKeys {
+			return errors.New("document parsing error")
+		}
+	}
+	return nil
+
+}
+
+func BSONFlatDocumentEncoding(ctx context.Context, tm TimerManager, iters int) error {
+	return bsonDocumentEncoding(ctx, tm, iters, flatBSONData)
+}
+
+func BSONFlatDocumentDecodingLazy(ctx context.Context, tm TimerManager, iters int) error {
+	return bsonDocumentDecodingLazy(ctx, tm, iters, flatBSONData)
+}
+
+func BSONFlatDocumentDecoding(ctx context.Context, tm TimerManager, iters int) error {
+	return bsonDocumentDecoding(ctx, tm, iters, 145, flatBSONData)
+}
+
+func BSONDeepDocumentEncoding(ctx context.Context, tm TimerManager, iters int) error {
+	return bsonDocumentEncoding(ctx, tm, iters, deepBSONData)
+}
+
+func BSONDeepDocumentDecodingLazy(ctx context.Context, tm TimerManager, iters int) error {
+	return bsonDocumentDecodingLazy(ctx, tm, iters, deepBSONData)
+}
+
+func BSONDeepDocumentDecoding(ctx context.Context, tm TimerManager, iters int) error {
+	return bsonDocumentDecoding(ctx, tm, iters, 126, deepBSONData)
+}
+
+func BSONFullDocumentEncoding(ctx context.Context, tm TimerManager, iters int) error {
+	return bsonDocumentEncoding(ctx, tm, iters, fullBSONData)
+}
+
+func BSONFullDocumentDecodingLazy(ctx context.Context, tm TimerManager, iters int) error {
+	return bsonDocumentDecodingLazy(ctx, tm, iters, fullBSONData)
+}
+
+func BSONFullDocumentDecoding(ctx context.Context, tm TimerManager, iters int) error {
+	return bsonDocumentDecoding(ctx, tm, iters, 145, fullBSONData)
+}

+ 88 - 0
src/go.mongodb.org/mongo-driver/benchmark/bson_map.go

@@ -0,0 +1,88 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+package benchmark
+
+import (
+	"context"
+	"errors"
+	"fmt"
+
+	"go.mongodb.org/mongo-driver/bson"
+)
+
+func bsonMapDecoding(ctx context.Context, tm TimerManager, iters int, dataSet string) error {
+	r, err := loadSourceRaw(getProjectRoot(), perfDataDir, bsonDataDir, dataSet)
+	if err != nil {
+		return err
+	}
+
+	tm.ResetTimer()
+
+	for i := 0; i < iters; i++ {
+		out := make(map[string]interface{})
+		err := bson.Unmarshal(r, &out)
+		if err != nil {
+			return nil
+		}
+		if len(out) == 0 {
+			return fmt.Errorf("decoding failed")
+		}
+	}
+	return nil
+}
+
+func bsonMapEncoding(ctx context.Context, tm TimerManager, iters int, dataSet string) error {
+	r, err := loadSourceRaw(getProjectRoot(), perfDataDir, bsonDataDir, dataSet)
+	if err != nil {
+		return err
+	}
+
+	doc := make(map[string]interface{})
+	err = bson.Unmarshal(r, &doc)
+	if err != nil {
+		return err
+	}
+
+	var buf []byte
+	tm.ResetTimer()
+	for i := 0; i < iters; i++ {
+		buf, err = bson.MarshalAppend(buf[:0], doc)
+		if err != nil {
+			return nil
+		}
+
+		if len(buf) == 0 {
+			return errors.New("encoding failed")
+		}
+	}
+
+	return nil
+}
+
+func BSONFlatMapDecoding(ctx context.Context, tm TimerManager, iters int) error {
+	return bsonMapDecoding(ctx, tm, iters, flatBSONData)
+}
+
+func BSONFlatMapEncoding(ctx context.Context, tm TimerManager, iters int) error {
+	return bsonMapEncoding(ctx, tm, iters, flatBSONData)
+}
+
+func BSONDeepMapDecoding(ctx context.Context, tm TimerManager, iters int) error {
+	return bsonMapDecoding(ctx, tm, iters, deepBSONData)
+}
+
+func BSONDeepMapEncoding(ctx context.Context, tm TimerManager, iters int) error {
+	return bsonMapEncoding(ctx, tm, iters, deepBSONData)
+}
+
+func BSONFullMapDecoding(ctx context.Context, tm TimerManager, iters int) error {
+	return bsonMapDecoding(ctx, tm, iters, fullBSONData)
+}
+
+func BSONFullMapEncoding(ctx context.Context, tm TimerManager, iters int) error {
+	return bsonMapEncoding(ctx, tm, iters, fullBSONData)
+}

+ 103 - 0
src/go.mongodb.org/mongo-driver/benchmark/bson_struct.go

@@ -0,0 +1,103 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+package benchmark
+
+import (
+	"context"
+	"errors"
+
+	"go.mongodb.org/mongo-driver/bson"
+)
+
+func BSONFlatStructDecoding(ctx context.Context, tm TimerManager, iters int) error {
+	r, err := loadSourceRaw(getProjectRoot(), perfDataDir, bsonDataDir, flatBSONData)
+	if err != nil {
+		return err
+	}
+
+	tm.ResetTimer()
+
+	for i := 0; i < iters; i++ {
+		out := flatBSON{}
+		err := bson.Unmarshal(r, &out)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func BSONFlatStructEncoding(ctx context.Context, tm TimerManager, iters int) error {
+	r, err := loadSourceRaw(getProjectRoot(), perfDataDir, bsonDataDir, flatBSONData)
+	if err != nil {
+		return err
+	}
+
+	doc := flatBSON{}
+	err = bson.Unmarshal(r, &doc)
+	if err != nil {
+		return err
+	}
+
+	var buf []byte
+
+	tm.ResetTimer()
+	for i := 0; i < iters; i++ {
+		buf, err = bson.Marshal(doc)
+		if err != nil {
+			return err
+		}
+		if len(buf) == 0 {
+			return errors.New("encoding failed")
+		}
+	}
+	return nil
+}
+
+func BSONFlatStructTagsEncoding(ctx context.Context, tm TimerManager, iters int) error {
+	r, err := loadSourceRaw(getProjectRoot(), perfDataDir, bsonDataDir, flatBSONData)
+	if err != nil {
+		return err
+	}
+
+	doc := flatBSONTags{}
+	err = bson.Unmarshal(r, &doc)
+	if err != nil {
+		return err
+	}
+
+	var buf []byte
+
+	tm.ResetTimer()
+	for i := 0; i < iters; i++ {
+		buf, err = bson.MarshalAppend(buf[:0], doc)
+		if err != nil {
+			return err
+		}
+		if len(buf) == 0 {
+			return errors.New("encoding failed")
+		}
+	}
+	return nil
+}
+
+func BSONFlatStructTagsDecoding(ctx context.Context, tm TimerManager, iters int) error {
+	r, err := loadSourceRaw(getProjectRoot(), perfDataDir, bsonDataDir, flatBSONData)
+	if err != nil {
+		return err
+	}
+
+	tm.ResetTimer()
+	for i := 0; i < iters; i++ {
+		out := flatBSONTags{}
+		err := bson.Unmarshal(r, &out)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}

+ 35 - 0
src/go.mongodb.org/mongo-driver/benchmark/bson_test.go

@@ -0,0 +1,35 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+package benchmark
+
+import "testing"
+
+// func BenchmarkBSONFullReaderDecoding(b *testing.B)       { WrapCase(BSONFullReaderDecoding)(b) }
+
+func BenchmarkBSONFlatDocumentEncoding(b *testing.B)     { WrapCase(BSONFlatDocumentEncoding)(b) }
+func BenchmarkBSONFlatDocumentDecodingLazy(b *testing.B) { WrapCase(BSONFlatDocumentDecodingLazy)(b) }
+func BenchmarkBSONFlatDocumentDecoding(b *testing.B)     { WrapCase(BSONFlatDocumentDecoding)(b) }
+func BenchmarkBSONDeepDocumentEncoding(b *testing.B)     { WrapCase(BSONDeepDocumentEncoding)(b) }
+func BenchmarkBSONDeepDocumentDecodingLazy(b *testing.B) { WrapCase(BSONDeepDocumentDecodingLazy)(b) }
+func BenchmarkBSONDeepDocumentDecoding(b *testing.B)     { WrapCase(BSONDeepDocumentDecoding)(b) }
+
+// func BenchmarkBSONFullDocumentEncoding(b *testing.B)     { WrapCase(BSONFullDocumentEncoding)(b) }
+// func BenchmarkBSONFullDocumentDecodingLazy(b *testing.B) { WrapCase(BSONFullDocumentDecodingLazy)(b) }
+// func BenchmarkBSONFullDocumentDecoding(b *testing.B)     { WrapCase(BSONFullDocumentDecoding)(b) }
+
+func BenchmarkBSONFlatMapDecoding(b *testing.B) { WrapCase(BSONFlatMapDecoding)(b) }
+func BenchmarkBSONFlatMapEncoding(b *testing.B) { WrapCase(BSONFlatMapEncoding)(b) }
+func BenchmarkBSONDeepMapDecoding(b *testing.B) { WrapCase(BSONDeepMapDecoding)(b) }
+func BenchmarkBSONDeepMapEncoding(b *testing.B) { WrapCase(BSONDeepMapEncoding)(b) }
+
+// func BenchmarkBSONFullMapDecoding(b *testing.B)       { WrapCase(BSONFullMapDecoding)(b) }
+// func BenchmarkBSONFullMapEncoding(b *testing.B)       { WrapCase(BSONFullMapEncoding)(b) }
+
+func BenchmarkBSONFlatStructDecoding(b *testing.B)     { WrapCase(BSONFlatStructDecoding)(b) }
+func BenchmarkBSONFlatStructTagsDecoding(b *testing.B) { WrapCase(BSONFlatStructTagsDecoding)(b) }
+func BenchmarkBSONFlatStructEncoding(b *testing.B)     { WrapCase(BSONFlatStructEncoding)(b) }
+func BenchmarkBSONFlatStructTagsEncoding(b *testing.B) { WrapCase(BSONFlatStructTagsEncoding)(b) }

+ 306 - 0
src/go.mongodb.org/mongo-driver/benchmark/bson_types.go

@@ -0,0 +1,306 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+package benchmark
+
+import "go.mongodb.org/mongo-driver/bson/primitive"
+
+type flatBSONTags struct {
+	ID primitive.ObjectID `bson:"_id"`
+
+	AA  int64  `bson:"AAgSNVyBb"`
+	AI  bool   `bson:"aicoMxZq"`
+	AM  int64  `bson:"AMQrGQmu"`
+	Ag  int    `bson:"AgYYbYPr"`
+	Ah  int64  `bson:"ahFCBmqT"`
+	At  int64  `bson:"AtWNZJXa"`
+	BB  string `bson:"BBqZInWV"`
+	BK  int64  `bson:"bkuaZWRT"`
+	Bw  int    `bson:"BwTXiovJ"`
+	CD  int    `bson:"CDIGOuIZ"`
+	CEA string `bson:"CEtYKsdd"`
+	CEB string `bson:"cepcgozk"`
+	CF  int    `bson:"CFujXoob"`
+	CV  int64  `bson:"cVjWCrlu"`
+	CX  string `bson:"cxOHMeDJ"`
+	CY  string `bson:"CYhSCkWB"`
+	Cq  string `bson:"CqCssWxW"`
+	DC  int    `bson:"dCLfYqqM"`
+	DDA int    `bson:"ddPdLgGg"`
+	DDB int    `bson:"ddVenEkK"`
+	DH  string `bson:"dHsYhRbV"`
+	DJ  int    `bson:"DJsnHZIC"`
+	DN  string `bson:"dNSuxlSU"`
+	DO  int64  `bson:"doshbrpF"`
+	DP  string `bson:"dpbwfSRb"`
+	DQ  int64  `bson:"DQBQcQFj"`
+	DT  string `bson:"dtywOLeD"`
+	DV  int    `bson:"dVkWIafN"`
+	EG  bool   `bson:"egxZaSsw"`
+	ER  string `bson:"eRTIdIJR"`
+	FD  int64  `bson:"FDYGeSiR"`
+	FE  string `bson:"fEheUtop"`
+	Fp  bool   `bson:"FpduyhQP"`
+	GE  string `bson:"gErhgZTh"`
+	GY  int    `bson:"gySFZeAE"`
+	Gi  uint   `bson:"GiAHzFII"`
+	HN  string `bson:"hnVgYIQi"`
+	HQA int    `bson:"HQeCoswW"`
+	HQB int    `bson:"HQiykral"`
+	HV  int64  `bson:"HVHyetUM"`
+	HW  int    `bson:"hwHOTmmW"`
+	Hi  bool   `bson:"HicJbMpj"`
+	Hr  int    `bson:"HrUPbFHD"`
+	IF  string `bson:"iFFGfTXc"`
+	IJ  int    `bson:"ijwXMKqI"`
+	IW  int    `bson:"iwfbMdcv"`
+	Ib  string `bson:"Ibrdrtgg"`
+	Is  bool   `bson:"IsorvnMR"`
+	JB  string `bson:"jbUymqiB"`
+	JM  string `bson:"jmglLvAS"`
+	JW  int    `bson:"jWaFvVAz"`
+	JX  int    `bson:"JXMyYkfb"`
+	Jh  bool   `bson:"JhImQOkw"`
+	Jr  string `bson:"JrJzKiIx"`
+	Jz  int    `bson:"JzgaUWVG"`
+	KF  bool   `bson:"kfvcFmKw"`
+	KM  int64  `bson:"KMKBtlov"`
+	Kn  string `bson:"KnhgtAOJ"`
+	Ky  int    `bson:"KyxOoCqS"`
+	LU  string `bson:"LUPqMOHS"`
+	LV  bool   `bson:"LVNIFCYm"`
+	Ln  int    `bson:"LngvlnTV"`
+	ML  int    `bson:"mlfZVfVT"`
+	MN  bool   `bson:"MNuWZMLP"`
+	MX  int    `bson:"MXMxLVBk"`
+	Mc  string `bson:"McpOBmaR"`
+	Me  string `bson:"MeUYSkPS"`
+	Mq  int    `bson:"MqfkBZJF"`
+	NB  int    `bson:"nBKWWUWk"`
+	NK  int    `bson:"nKhiSITP"`
+	OB  int    `bson:"obcwwqWZ"`
+	OC  string `bson:"OCsIhHxq"`
+	OM  int    `bson:"omnwvBbA"`
+	OR  string `bson:"oRWMNJTE"`
+	Of  string `bson:"OfTmCvDx"`
+	PA  int    `bson:"pacTBmxE"`
+	PF  int    `bson:"PFZSRHNN"`
+	PK  bool   `bson:"pKjOghFa"`
+	PO  int    `bson:"pOMEwSod"`
+	PP  string `bson:"pPtPsgRl"`
+	PQ  int    `bson:"pQyCJaEd"`
+	Pj  int    `bson:"PjKiuWnQ"`
+	Pv  int    `bson:"PvfnpsMV"`
+	QH  int    `bson:"qHzOMXeT"`
+	QR  bool   `bson:"qrJASGzU"`
+	Qo  string `bson:"QobifTeZ"`
+	RE  int64  `bson:"reiKnuza"`
+	RM  string `bson:"rmzUAgmk"`
+	RP  string `bson:"RPsQhgRD"`
+	Rb  uint   `bson:"Rbxpznea"`
+	ReA bool   `bson:"RemSsnnR"`
+	ReB int    `bson:"ReOZakjB"`
+	Rw  string `bson:"RwAVVKHM"`
+	SG  bool   `bson:"sGWJTAcT"`
+	SU  uint8  `bson:"SUWXijHT"`
+	SYA int64  `bson:"sYtnozSc"`
+	SYB string `bson:"SYtZkQbC"`
+	Sq  int64  `bson:"SqNvlUZF"`
+	TA  int    `bson:"taoNnQYY"`
+	TD  string `bson:"TDUzNJiH"`
+	TI  string `bson:"tIJEYSYM"`
+	TR  bool   `bson:"TRpgnInA"`
+	Tg  int    `bson:"TgSwBbgp"`
+	Tk  int64  `bson:"TkXMwZlU"`
+	Tm  int64  `bson:"TmUnYUrv"`
+	UK  int    `bson:"UKwbAKGw"`
+	UM  string `bson:"uMDWqLMf"`
+	Up  bool   `bson:"UpdMADoN"`
+	Ut  int64  `bson:"UtbwOKLt"`
+	VC  int64  `bson:"VCSKFCoE"`
+	VK  string `bson:"vkEDWgmN"`
+	VL  string `bson:"vlSZaxCV"`
+	VS  string `bson:"vSLTtfDF"`
+	VVA bool   `bson:"vvUeXASH"`
+	VVB int    `bson:"VVvwKVRG"`
+	Vc  bool   `bson:"VcCSqSmp"`
+	Vp  int16  `bson:"VplFgewF"`
+	Vt  string `bson:"VtzeOlCT"`
+	WH  bool   `bson:"WHSQVLKG"`
+	WJA bool   `bson:"wjfyueDC"`
+	WJB string `bson:"wjAWaOog"`
+	WM  int64  `bson:"wmDLUkXt"`
+	WY  string `bson:"WYJdGJLu"`
+	Wm  bool   `bson:"WmMOvgFc"`
+	Wo  string `bson:"WoFGfdvb"`
+	XE  int    `bson:"XEBqaXkB"`
+	XG  bool   `bson:"XGxlHrXf"`
+	XR  string `bson:"xrzGnsEK"`
+	XWA int64  `bson:"xWpeGNjl"`
+	XWB string `bson:"xWUlYggc"`
+	XX  int64  `bson:"XXKbyIXG"`
+	XZ  int64  `bson:"xZOksssj"`
+	Xe  uint   `bson:"XeRkAyCq"`
+	Xx  int    `bson:"XxvXmHiQ"`
+	YD  string `bson:"YDHWnEXV"`
+	YE  bool   `bson:"yeTUgNrU"`
+	YK  int    `bson:"yKfZnGKG"`
+	YX  string `bson:"yXSBbPeT"`
+	ZD  bool   `bson:"zDzSGNnW"`
+	ZE  bool   `bson:"zEgGhhZf"`
+	ZM  string `bson:"zMCFzcWY"`
+	ZSA int64  `bson:"zSYvADVf"`
+	ZSB int64  `bson:"zswQbWEI"`
+	Zm  string `bson:"ZmtEJFSO"`
+}
+
+type flatBSON struct {
+	AMQrGQmu  int64
+	AAgSNVyBb int64
+	AgYYbYPr  int
+	AtWNZJXa  int64
+	BBqZInWV  string
+	BwTXiovJ  int
+	CDIGOuIZ  int
+	CEtYKsdd  string
+	CFujXoob  int
+	CYhSCkWB  string
+	CqCssWxW  string
+	DJsnHZIC  int
+	DQBQcQFj  int64
+	FDYGeSiR  int64
+	FpduyhQP  bool
+	GiAHzFII  uint
+	HQeCoswW  int
+	HQiykral  int
+	HVHyetUM  int64
+	HicJbMpj  bool
+	HrUPbFHD  int
+	Ibrdrtgg  string
+	IsorvnMR  bool
+	JXMyYkfb  int
+	JhImQOkw  bool
+	JrJzKiIx  string
+	JzgaUWVG  int
+	KMKBtlov  int64
+	KnhgtAOJ  string
+	KyxOoCqS  int
+	LUPqMOHS  string
+	LVNIFCYm  bool
+	LngvlnTV  int
+	MNuWZMLP  bool
+	MXMxLVBk  int
+	McpOBmaR  string
+	MeUYSkPS  string
+	MqfkBZJF  int
+	OCsIhHxq  string
+	OfTmCvDx  string
+	PjKiuWnQ  int
+	PvfnpsMV  int
+	QobifTeZ  string
+	RPsQhgRD  string
+	Rbxpznea  uint
+	ReOZakjB  int
+	RemSsnnR  bool
+	RwAVVKHM  string
+	SUWXijHT  uint8
+	SYtZkQbC  string
+	SqNvlUZF  int64
+	TDUzNJiH  string
+	TRpgnInA  bool
+	TgSwBbgp  int
+	TkXMwZlU  int64
+	TmUnYUrv  int64
+	UKwbAKGw  int
+	UpdMADoN  bool
+	UtbwOKLt  int64
+	VCSKFCoE  int64
+	VVvwKVRG  int
+	VcCSqSmp  bool
+	VplFgewF  int16
+	VtzeOlCT  string
+	WHSQVLKG  bool
+	WYJdGJLu  string
+	WmMOvgFc  bool
+	WoFGfdvb  string
+	XEBqaXkB  int
+	XGxlHrXf  bool
+	XXKbyIXG  int64
+	XeRkAyCq  uint
+	XxvXmHiQ  int
+	YDHWnEXV  string
+	ZmtEJFSO  string
+	ID        primitive.ObjectID `bson:"_id"`
+	AhFCBmqT  int64
+	AicoMxZq  bool
+	BkuaZWRT  int64
+	CVjWCrlu  int64
+	Cepcgozk  string
+	CxOHMeDJ  string
+	DCLfYqqM  int
+	DHsYhRbV  string
+	DNSuxlSU  string
+	DVkWIafN  int
+	DdPdLgGg  int
+	DdVenEkK  int
+	DoshbrpF  int64
+	DpbwfSRb  string
+	DtywOLeD  string
+	ERTIdIJR  string
+	EgxZaSsw  bool
+	FEheUtop  string
+	GErhgZTh  string
+	GySFZeAE  int
+	HnVgYIQi  string
+	HwHOTmmW  int
+	IFFGfTXc  string
+	IjwXMKqI  int
+	IwfbMdcv  int
+	JWaFvVAz  int
+	JbUymqiB  string
+	JmglLvAS  string
+	KfvcFmKw  bool
+	MlfZVfVT  int
+	NBKWWUWk  int
+	NKhiSITP  int
+	ORWMNJTE  string
+	ObcwwqWZ  int
+	OmnwvBbA  int
+	PKjOghFa  bool
+	POMEwSod  int
+	PPtPsgRl  string
+	PQyCJaEd  int
+	PacTBmxE  int
+	QHzOMXeT  int
+	QrJASGzU  bool
+	ReiKnuza  int64
+	RmzUAgmk  string
+	SGWJTAcT  bool
+	SYtnozSc  int64
+	TIJEYSYM  string
+	TaoNnQYY  int
+	UMDWqLMf  string
+	VSLTtfDF  string
+	VkEDWgmN  string
+	VlSZaxCV  string
+	VvUeXASH  bool
+	WjAWaOog  string
+	WjfyueDC  bool
+	WmDLUkXt  int64
+	XWUlYggc  string
+	XWpeGNjl  int64
+	XZOksssj  int64
+	XrzGnsEK  string
+	YKfZnGKG  int
+	YXSBbPeT  string
+	YeTUgNrU  bool
+	ZDzSGNnW  bool
+	ZEgGhhZf  bool
+	ZMCFzcWY  string
+	ZSYvADVf  int64
+	ZswQbWEI  int64
+	PfZSRHnn  int
+}

+ 29 - 0
src/go.mongodb.org/mongo-driver/benchmark/canary.go

@@ -0,0 +1,29 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+package benchmark
+
+import (
+	"context"
+)
+
+func CanaryIncCase(ctx context.Context, tm TimerManager, iters int) error {
+	var canaryCount int
+	for i := 0; i < iters; i++ {
+		canaryCount++
+	}
+	return nil
+}
+
+var globalCanaryCount int
+
+func GlobalCanaryIncCase(ctx context.Context, tm TimerManager, iters int) error {
+	for i := 0; i < iters; i++ {
+		globalCanaryCount++
+	}
+
+	return nil
+}

+ 12 - 0
src/go.mongodb.org/mongo-driver/benchmark/canary_test.go

@@ -0,0 +1,12 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+package benchmark
+
+import "testing"
+
+func BenchmarkCanaryInc(b *testing.B)       { WrapCase(CanaryIncCase)(b) }
+func BenchmarkGlobalCanaryInc(b *testing.B) { WrapCase(GlobalCanaryIncCase)(b) }

+ 226 - 0
src/go.mongodb.org/mongo-driver/benchmark/harness.go

@@ -0,0 +1,226 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+package benchmark // import "go.mongodb.org/mongo-driver/benchmark"
+
+import (
+	"context"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/require"
+)
+
+const (
+	five            = 5
+	ten             = 2 * five
+	hundred         = ten * ten
+	thousand        = ten * hundred
+	tenThousand     = ten * thousand
+	hundredThousand = hundred * thousand
+	million         = hundred * hundredThousand
+	halfMillion     = five * hundredThousand
+
+	ExecutionTimeout = five * time.Minute
+	StandardRuntime  = time.Minute
+	MinimumRuntime   = five * time.Second
+	MinIterations    = hundred
+)
+
+type BenchCase func(context.Context, TimerManager, int) error
+type BenchFunction func(*testing.B)
+
+func WrapCase(bench BenchCase) BenchFunction {
+	name := getName(bench)
+	return func(b *testing.B) {
+		ctx := context.Background()
+		b.ResetTimer()
+		err := bench(ctx, b, b.N)
+		require.NoError(b, err, "case='%s'", name)
+	}
+}
+
+func getAllCases() []*CaseDefinition {
+	return []*CaseDefinition{
+		{
+			Bench:              CanaryIncCase,
+			Count:              million,
+			Size:               -1,
+			Runtime:            MinimumRuntime,
+			RequiredIterations: ten,
+		},
+		{
+			Bench:              GlobalCanaryIncCase,
+			Count:              million,
+			Size:               -1,
+			Runtime:            MinimumRuntime,
+			RequiredIterations: ten,
+		},
+		{
+			Bench:   BSONFlatDocumentEncoding,
+			Count:   tenThousand,
+			Size:    75310000,
+			Runtime: StandardRuntime,
+		},
+		{
+			Bench:   BSONFlatDocumentDecodingLazy,
+			Count:   tenThousand,
+			Size:    75310000,
+			Runtime: StandardRuntime,
+		},
+		{
+			Bench:   BSONFlatDocumentDecoding,
+			Count:   tenThousand,
+			Size:    75310000,
+			Runtime: StandardRuntime,
+		},
+		{
+			Bench:   BSONDeepDocumentEncoding,
+			Count:   tenThousand,
+			Size:    19640000,
+			Runtime: StandardRuntime,
+		},
+		{
+			Bench:   BSONDeepDocumentDecodingLazy,
+			Count:   tenThousand,
+			Size:    19640000,
+			Runtime: StandardRuntime,
+		},
+		{
+			Bench:   BSONDeepDocumentDecoding,
+			Count:   tenThousand,
+			Size:    19640000,
+			Runtime: StandardRuntime,
+		},
+		// {
+		//	Bench:   BSONFullDocumentEncoding,
+		//	Count:   tenThousand,
+		//	Size:    57340000,
+		//	Runtime: StandardRuntime,
+		// },
+		// {
+		//	Bench:   BSONFullDocumentDecodingLazy,
+		//	Count:   tenThousand,
+		//	Size:    57340000,
+		//	Runtime: StandardRuntime,
+		// },
+		// {
+		//	Bench:   BSONFullDocumentDecoding,
+		//	Count:   tenThousand,
+		//	Size:    57340000,
+		//	Runtime: StandardRuntime,
+		// },
+		// {
+		//	Bench:   BSONFullReaderDecoding,
+		//	Count:   tenThousand,
+		//	Size:    57340000,
+		//	Runtime: StandardRuntime,
+		// },
+		{
+			Bench:   BSONFlatMapDecoding,
+			Count:   tenThousand,
+			Size:    75310000,
+			Runtime: StandardRuntime,
+		},
+		{
+			Bench:   BSONFlatMapEncoding,
+			Count:   tenThousand,
+			Size:    75310000,
+			Runtime: StandardRuntime,
+		},
+		{
+			Bench:   BSONDeepMapDecoding,
+			Count:   tenThousand,
+			Size:    19640000,
+			Runtime: StandardRuntime,
+		},
+		{
+			Bench:   BSONDeepMapEncoding,
+			Count:   tenThousand,
+			Size:    19640000,
+			Runtime: StandardRuntime,
+		},
+		// {
+		//	Bench:   BSONFullMapDecoding,
+		//	Count:   tenThousand,
+		//	Size:    57340000,
+		//	Runtime: StandardRuntime,
+		// },
+		// {
+		//	Bench:   BSONFullMapEncoding,
+		//	Count:   tenThousand,
+		//	Size:    57340000,
+		//	Runtime: StandardRuntime,
+		// },
+		{
+			Bench:   BSONFlatStructDecoding,
+			Count:   tenThousand,
+			Size:    75310000,
+			Runtime: StandardRuntime,
+		},
+		{
+			Bench:   BSONFlatStructTagsDecoding,
+			Count:   tenThousand,
+			Size:    75310000,
+			Runtime: StandardRuntime,
+		},
+		{
+			Bench:   BSONFlatStructEncoding,
+			Count:   tenThousand,
+			Size:    75310000,
+			Runtime: StandardRuntime,
+		},
+		{
+			Bench:   BSONFlatStructTagsEncoding,
+			Count:   tenThousand,
+			Size:    75310000,
+			Runtime: StandardRuntime,
+		},
+		{
+			Bench:   SingleRunCommand,
+			Count:   tenThousand,
+			Size:    160000,
+			Runtime: StandardRuntime,
+		},
+		{
+			Bench:   SingleFindOneByID,
+			Count:   tenThousand,
+			Size:    16220000,
+			Runtime: StandardRuntime,
+		},
+		{
+			Bench:   SingleInsertSmallDocument,
+			Count:   tenThousand,
+			Size:    2750000,
+			Runtime: StandardRuntime,
+		},
+		{
+			Bench:   SingleInsertLargeDocument,
+			Count:   ten,
+			Size:    27310890,
+			Runtime: StandardRuntime,
+		},
+		{
+			Bench:   MultiFindMany,
+			Count:   tenThousand,
+			Size:    16220000,
+			Runtime: StandardRuntime,
+		},
+		{
+			Bench:   MultiInsertSmallDocument,
+			Count:   tenThousand,
+			Size:    2750000,
+			Runtime: StandardRuntime,
+		},
+		{
+			Bench:              MultiInsertLargeDocument,
+			Count:              ten,
+			Size:               27310890,
+			Runtime:            StandardRuntime,
+			RequiredIterations: tenThousand,
+		},
+	}
+}

+ 154 - 0
src/go.mongodb.org/mongo-driver/benchmark/harness_case.go

@@ -0,0 +1,154 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+package benchmark
+
+import (
+	"context"
+	"fmt"
+	"path/filepath"
+	"reflect"
+	"runtime"
+	"strings"
+	"time"
+)
+
+type CaseDefinition struct {
+	Bench              BenchCase
+	Count              int
+	Size               int
+	RequiredIterations int
+	Runtime            time.Duration
+
+	cumulativeRuntime time.Duration
+	elapsed           time.Duration
+	startAt           time.Time
+	isRunning         bool
+}
+
+// TimerManager is a subset of the testing.B tool, used to manage
+// setup code.
+type TimerManager interface {
+	ResetTimer()
+	StartTimer()
+	StopTimer()
+}
+
+func (c *CaseDefinition) ResetTimer() {
+	c.startAt = time.Now()
+	c.elapsed = 0
+	c.isRunning = true
+}
+
+func (c *CaseDefinition) StartTimer() {
+	c.startAt = time.Now()
+	c.isRunning = true
+}
+
+func (c *CaseDefinition) StopTimer() {
+	if !c.isRunning {
+		return
+	}
+	c.elapsed += time.Since(c.startAt)
+	c.isRunning = false
+}
+
+func (c *CaseDefinition) roundedRuntime() time.Duration {
+	return roundDurationMS(c.Runtime)
+}
+
+func (c *CaseDefinition) Run(ctx context.Context) *BenchResult {
+	out := &BenchResult{
+		Trials:     1,
+		DataSize:   c.Size,
+		Name:       c.Name(),
+		Operations: c.Count,
+	}
+	var cancel context.CancelFunc
+	ctx, cancel = context.WithTimeout(ctx, 2*ExecutionTimeout)
+	defer cancel()
+
+	fmt.Println("=== RUN", out.Name)
+	if c.RequiredIterations == 0 {
+		c.RequiredIterations = MinIterations
+	}
+
+benchRepeat:
+	for {
+		if ctx.Err() != nil {
+			break
+		}
+		if c.cumulativeRuntime >= c.Runtime {
+			if out.Trials >= c.RequiredIterations {
+				break
+			} else if c.cumulativeRuntime >= ExecutionTimeout {
+				break
+			}
+		}
+
+		res := Result{
+			Iterations: c.Count,
+		}
+
+		c.StartTimer()
+		res.Error = c.Bench(ctx, c, c.Count)
+		c.StopTimer()
+		res.Duration = c.elapsed
+		c.cumulativeRuntime += res.Duration
+
+		switch res.Error {
+		case context.DeadlineExceeded:
+			break benchRepeat
+		case context.Canceled:
+			break benchRepeat
+		case nil:
+			out.Trials++
+			c.elapsed = 0
+			out.Raw = append(out.Raw, res)
+		default:
+			continue
+		}
+
+	}
+
+	out.Duration = out.totalDuration()
+	fmt.Printf("    --- REPORT: count=%d trials=%d requiredTrials=%d runtime=%s\n",
+		c.Count, out.Trials, c.RequiredIterations, c.Runtime)
+	if out.HasErrors() {
+		fmt.Printf("    --- ERRORS: %s\n", strings.Join(out.errReport(), "\n       "))
+		fmt.Printf("--- FAIL: %s (%s)\n", out.Name, out.roundedRuntime())
+	} else {
+		fmt.Printf("--- PASS: %s (%s)\n", out.Name, out.roundedRuntime())
+	}
+
+	return out
+
+}
+
+func (c *CaseDefinition) String() string {
+	return fmt.Sprintf("name=%s, count=%d, runtime=%s timeout=%s",
+		c.Name(), c.Count, c.Runtime, ExecutionTimeout)
+}
+
+func (c *CaseDefinition) Name() string { return getName(c.Bench) }
+func getName(i interface{}) string {
+	n := runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
+	parts := strings.Split(n, ".")
+	if len(parts) > 1 {
+		return parts[len(parts)-1]
+	}
+
+	return n
+
+}
+
+func getProjectRoot() string { return filepath.Dir(getDirectoryOfFile()) }
+
+func getDirectoryOfFile() string {
+	_, file, _, _ := runtime.Caller(1)
+
+	return filepath.Dir(file)
+}

+ 69 - 0
src/go.mongodb.org/mongo-driver/benchmark/harness_main.go

@@ -0,0 +1,69 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+package benchmark
+
+import (
+	"context"
+	"encoding/json"
+	"flag"
+	"fmt"
+	"io/ioutil"
+	"os"
+)
+
+func DriverBenchmarkMain() int {
+	var hasErrors bool
+	var outputFileName string
+	flag.StringVar(&outputFileName, "output", "perf.json", "path to write the 'perf.json' file")
+	flag.Parse()
+
+	ctx := context.Background()
+	output := []interface{}{}
+	for _, res := range runDriverCases(ctx) {
+		if res.HasErrors() {
+			hasErrors = true
+		}
+
+		evg, err := res.EvergreenPerfFormat()
+		if err != nil {
+			hasErrors = true
+			continue
+		}
+
+		output = append(output, evg...)
+	}
+
+	evgOutput, err := json.MarshalIndent(map[string]interface{}{"results": output}, "", "   ")
+	if err != nil {
+		return 1
+	}
+	evgOutput = append(evgOutput, []byte("\n")...)
+
+	if outputFileName == "" {
+		fmt.Println(string(evgOutput))
+	} else if err := ioutil.WriteFile(outputFileName, evgOutput, 0644); err != nil {
+		fmt.Fprintf(os.Stderr, "problem writing file '%s': %s", outputFileName, err.Error())
+		return 1
+	}
+
+	if hasErrors {
+		return 1
+	}
+
+	return 0
+}
+
+func runDriverCases(ctx context.Context) []*BenchResult {
+	cases := getAllCases()
+
+	results := []*BenchResult{}
+	for _, bc := range cases {
+		results = append(results, bc.Run(ctx))
+	}
+
+	return results
+}

+ 140 - 0
src/go.mongodb.org/mongo-driver/benchmark/harness_results.go

@@ -0,0 +1,140 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+package benchmark
+
+import (
+	"fmt"
+	"time"
+
+	"github.com/montanaflynn/stats"
+)
+
+type BenchResult struct {
+	Name       string
+	Trials     int
+	Duration   time.Duration
+	Raw        []Result
+	DataSize   int
+	Operations int
+	hasErrors  *bool
+}
+
+func (r *BenchResult) EvergreenPerfFormat() ([]interface{}, error) {
+	timings := r.timings()
+
+	median, err := stats.Median(timings)
+	if err != nil {
+		return nil, err
+	}
+
+	min, err := stats.Min(timings)
+	if err != nil {
+		return nil, err
+	}
+
+	max, err := stats.Max(timings)
+	if err != nil {
+		return nil, err
+	}
+
+	out := []interface{}{
+		map[string]interface{}{
+			"name": r.Name + "-throughput",
+			"results": map[string]interface{}{
+				"1": map[string]interface{}{
+					"seconds":        r.Duration.Round(time.Millisecond).Seconds(),
+					"ops_per_second": r.getThroughput(median),
+					"ops_per_second_values": []float64{
+						r.getThroughput(min),
+						r.getThroughput(max),
+					},
+				},
+			},
+		},
+	}
+
+	if r.DataSize > 0 {
+		out = append(out, interface{}(map[string]interface{}{
+			"name": r.Name + "-MB-adjusted",
+			"results": map[string]interface{}{
+				"1": map[string]interface{}{
+					"seconds":        r.Duration.Round(time.Millisecond).Seconds(),
+					"ops_per_second": r.adjustResults(median),
+					"ops_per_second_values": []float64{
+						r.adjustResults(min),
+						r.adjustResults(max),
+					},
+				},
+			},
+		}))
+	}
+
+	return out, nil
+}
+
+func (r *BenchResult) timings() []float64 {
+	out := []float64{}
+	for _, r := range r.Raw {
+		out = append(out, r.Duration.Seconds())
+	}
+	return out
+}
+
+func (r *BenchResult) totalDuration() time.Duration {
+	var out time.Duration
+	for _, trial := range r.Raw {
+		out += trial.Duration
+	}
+	return out
+}
+
+func (r *BenchResult) adjustResults(data float64) float64 { return float64(r.DataSize) / data }
+func (r *BenchResult) getThroughput(data float64) float64 { return float64(r.Operations) / data }
+func (r *BenchResult) roundedRuntime() time.Duration      { return roundDurationMS(r.Duration) }
+
+func (r *BenchResult) String() string {
+	return fmt.Sprintf("name=%s, trials=%d, secs=%s", r.Name, r.Trials, r.Duration)
+}
+
+func (r *BenchResult) HasErrors() bool {
+	if r.hasErrors == nil {
+		var val bool
+		for _, res := range r.Raw {
+			if res.Error != nil {
+				val = true
+				break
+			}
+		}
+		r.hasErrors = &val
+	}
+
+	return *r.hasErrors
+}
+
+func (r *BenchResult) errReport() []string {
+	errs := []string{}
+	for _, res := range r.Raw {
+		if res.Error != nil {
+			errs = append(errs, res.Error.Error())
+		}
+	}
+	return errs
+}
+
+type Result struct {
+	Duration   time.Duration
+	Iterations int
+	Error      error
+}
+
+func roundDurationMS(d time.Duration) time.Duration {
+	rounded := d.Round(time.Millisecond)
+	if rounded == 1<<63-1 {
+		return 0
+	}
+	return rounded
+}

+ 142 - 0
src/go.mongodb.org/mongo-driver/benchmark/multi.go

@@ -0,0 +1,142 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+package benchmark
+
+import (
+	"context"
+	"errors"
+
+	"go.mongodb.org/mongo-driver/x/bsonx"
+)
+
+func MultiFindMany(ctx context.Context, tm TimerManager, iters int) error {
+	ctx, cancel := context.WithCancel(ctx)
+	defer cancel()
+
+	db, err := getClientDB(ctx)
+	if err != nil {
+		return err
+	}
+	defer db.Client().Disconnect(ctx)
+
+	db = db.Client().Database("perftest")
+	if err = db.Drop(ctx); err != nil {
+		return err
+	}
+
+	doc, err := loadSourceDocument(getProjectRoot(), perfDataDir, singleAndMultiDataDir, tweetData)
+	if err != nil {
+		return err
+	}
+
+	coll := db.Collection("corpus")
+
+	payload := make([]interface{}, iters)
+	for idx := range payload {
+		payload[idx] = doc
+	}
+
+	if _, err = coll.InsertMany(ctx, payload); err != nil {
+		return err
+	}
+
+	tm.ResetTimer()
+
+	cursor, err := coll.Find(ctx, bsonx.Doc{})
+	if err != nil {
+		return err
+	}
+	defer cursor.Close(ctx)
+
+	counter := 0
+	for cursor.Next(ctx) {
+		err = cursor.Err()
+		if err != nil {
+			return err
+		}
+		if len(cursor.Current) == 0 {
+			return errors.New("error retrieving document")
+		}
+
+		counter++
+	}
+
+	if counter != iters {
+		return errors.New("problem iterating cursors")
+
+	}
+
+	tm.StopTimer()
+
+	if err = cursor.Close(ctx); err != nil {
+		return err
+	}
+
+	if err = db.Drop(ctx); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func multiInsertCase(ctx context.Context, tm TimerManager, iters int, data string) error {
+	ctx, cancel := context.WithCancel(ctx)
+	defer cancel()
+
+	db, err := getClientDB(ctx)
+	if err != nil {
+		return err
+	}
+	defer db.Client().Disconnect(ctx)
+
+	db = db.Client().Database("perftest")
+	if err = db.Drop(ctx); err != nil {
+		return err
+	}
+
+	doc, err := loadSourceDocument(getProjectRoot(), perfDataDir, singleAndMultiDataDir, data)
+	if err != nil {
+		return err
+	}
+
+	err = db.RunCommand(ctx, bsonx.Doc{{"create", bsonx.String("corpus")}}).Err()
+	if err != nil {
+		return err
+	}
+
+	payload := make([]interface{}, iters)
+	for idx := range payload {
+		payload[idx] = doc
+	}
+
+	coll := db.Collection("corpus")
+
+	tm.ResetTimer()
+	res, err := coll.InsertMany(ctx, payload)
+	if err != nil {
+		return err
+	}
+	tm.StopTimer()
+
+	if len(res.InsertedIDs) != iters {
+		return errors.New("bulk operation did not complete")
+	}
+
+	if err = db.Drop(ctx); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func MultiInsertSmallDocument(ctx context.Context, tm TimerManager, iters int) error {
+	return multiInsertCase(ctx, tm, iters, smallData)
+}
+
+func MultiInsertLargeDocument(ctx context.Context, tm TimerManager, iters int) error {
+	return multiInsertCase(ctx, tm, iters, largeData)
+}

+ 13 - 0
src/go.mongodb.org/mongo-driver/benchmark/multi_test.go

@@ -0,0 +1,13 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+package benchmark
+
+import "testing"
+
+func BenchmarkMultiFindMany(b *testing.B)            { WrapCase(MultiFindMany)(b) }
+func BenchmarkMultiInsertSmallDocument(b *testing.B) { WrapCase(MultiInsertSmallDocument)(b) }
+func BenchmarkMultiInsertLargeDocument(b *testing.B) { WrapCase(MultiInsertLargeDocument)(b) }

+ 174 - 0
src/go.mongodb.org/mongo-driver/benchmark/single.go

@@ -0,0 +1,174 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+package benchmark
+
+import (
+	"context"
+	"errors"
+
+	"go.mongodb.org/mongo-driver/internal/testutil"
+	"go.mongodb.org/mongo-driver/mongo"
+	"go.mongodb.org/mongo-driver/mongo/options"
+	"go.mongodb.org/mongo-driver/x/bsonx"
+)
+
+const (
+	singleAndMultiDataDir = "single_and_multi_document"
+	tweetData             = "tweet.json"
+	smallData             = "small_doc.json"
+	largeData             = "large_doc.json"
+)
+
+func getClientDB(ctx context.Context) (*mongo.Database, error) {
+	cs, err := testutil.GetConnString()
+	if err != nil {
+		return nil, err
+	}
+	client, err := mongo.NewClient(options.Client().ApplyURI(cs.String()))
+	if err != nil {
+		return nil, err
+	}
+	if err = client.Connect(ctx); err != nil {
+		return nil, err
+	}
+
+	db := client.Database(testutil.GetDBName(cs))
+	return db, nil
+}
+
+func SingleRunCommand(ctx context.Context, tm TimerManager, iters int) error {
+	ctx, cancel := context.WithCancel(ctx)
+	defer cancel()
+
+	db, err := getClientDB(ctx)
+	if err != nil {
+		return err
+	}
+	defer db.Client().Disconnect(ctx)
+
+	cmd := bsonx.Doc{{"ismaster", bsonx.Boolean(true)}}
+
+	tm.ResetTimer()
+	for i := 0; i < iters; i++ {
+		var doc bsonx.Doc
+		err := db.RunCommand(ctx, cmd).Decode(&doc)
+		if err != nil {
+			return err
+		}
+		// read the document and then throw it away to prevent
+		out, err := doc.MarshalBSON()
+		if len(out) == 0 {
+			return errors.New("output of ismaster is empty")
+		}
+	}
+	tm.StopTimer()
+
+	return nil
+}
+
+func SingleFindOneByID(ctx context.Context, tm TimerManager, iters int) error {
+	ctx, cancel := context.WithCancel(ctx)
+	defer cancel()
+
+	db, err := getClientDB(ctx)
+	if err != nil {
+		return err
+	}
+
+	db = db.Client().Database("perftest")
+	if err = db.Drop(ctx); err != nil {
+		return err
+	}
+
+	doc, err := loadSourceDocument(getProjectRoot(), perfDataDir, singleAndMultiDataDir, tweetData)
+	if err != nil {
+		return err
+	}
+	coll := db.Collection("corpus")
+	for i := 0; i < iters; i++ {
+		id := int32(i)
+		res, err := coll.InsertOne(ctx, doc.Set("_id", bsonx.Int32(id)))
+		if err != nil {
+			return err
+		}
+		if res.InsertedID == nil {
+			return errors.New("insert failed")
+		}
+	}
+
+	tm.ResetTimer()
+
+	for i := 0; i < iters; i++ {
+		res := coll.FindOne(ctx, bsonx.Doc{{"_id", bsonx.Int32(int32(i))}})
+		if res == nil {
+			return errors.New("find one query produced nil result")
+		}
+	}
+
+	tm.StopTimer()
+
+	if err = db.Drop(ctx); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func singleInsertCase(ctx context.Context, tm TimerManager, iters int, data string) error {
+	ctx, cancel := context.WithCancel(ctx)
+	defer cancel()
+
+	db, err := getClientDB(ctx)
+	if err != nil {
+		return err
+	}
+	defer db.Client().Disconnect(ctx)
+
+	db = db.Client().Database("perftest")
+	if err = db.Drop(ctx); err != nil {
+		return err
+	}
+
+	doc, err := loadSourceDocument(getProjectRoot(), perfDataDir, singleAndMultiDataDir, data)
+	if err != nil {
+		return err
+	}
+
+	err = db.RunCommand(ctx, bsonx.Doc{{"create", bsonx.String("corpus")}}).Err()
+	if err != nil {
+		return err
+	}
+
+	coll := db.Collection("corpus")
+
+	tm.ResetTimer()
+
+	for i := 0; i < iters; i++ {
+		if _, err = coll.InsertOne(ctx, doc); err != nil {
+			return err
+		}
+
+		// TODO: should be remove after resolving GODRIVER-468
+		_ = doc.Delete("_id")
+	}
+
+	tm.StopTimer()
+
+	if err = db.Drop(ctx); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func SingleInsertSmallDocument(ctx context.Context, tm TimerManager, iters int) error {
+	return singleInsertCase(ctx, tm, iters, smallData)
+}
+
+func SingleInsertLargeDocument(ctx context.Context, tm TimerManager, iters int) error {
+	return singleInsertCase(ctx, tm, iters, largeData)
+}

+ 14 - 0
src/go.mongodb.org/mongo-driver/benchmark/single_test.go

@@ -0,0 +1,14 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+package benchmark
+
+import "testing"
+
+func BenchmarkSingleRunCommand(b *testing.B)          { WrapCase(SingleRunCommand)(b) }
+func BenchmarkSingleFindOneByID(b *testing.B)         { WrapCase(SingleFindOneByID)(b) }
+func BenchmarkSingleInsertSmallDocument(b *testing.B) { WrapCase(SingleInsertSmallDocument)(b) }
+func BenchmarkSingleInsertLargeDocument(b *testing.B) { WrapCase(SingleInsertLargeDocument)(b) }

+ 134 - 0
src/go.mongodb.org/mongo-driver/bson/benchmark_test.go

@@ -0,0 +1,134 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+package bson
+
+import (
+	"testing"
+)
+
+type encodetest struct {
+	Field1String  string
+	Field1Int64   int64
+	Field1Float64 float64
+	Field2String  string
+	Field2Int64   int64
+	Field2Float64 float64
+	Field3String  string
+	Field3Int64   int64
+	Field3Float64 float64
+	Field4String  string
+	Field4Int64   int64
+	Field4Float64 float64
+}
+
+type nestedtest1 struct {
+	Nested nestedtest2
+}
+
+type nestedtest2 struct {
+	Nested nestedtest3
+}
+
+type nestedtest3 struct {
+	Nested nestedtest4
+}
+
+type nestedtest4 struct {
+	Nested nestedtest5
+}
+
+type nestedtest5 struct {
+	Nested nestedtest6
+}
+
+type nestedtest6 struct {
+	Nested nestedtest7
+}
+
+type nestedtest7 struct {
+	Nested nestedtest8
+}
+
+type nestedtest8 struct {
+	Nested nestedtest9
+}
+
+type nestedtest9 struct {
+	Nested nestedtest10
+}
+
+type nestedtest10 struct {
+	Nested nestedtest11
+}
+
+type nestedtest11 struct {
+	Nested encodetest
+}
+
+var encodetestInstance = encodetest{
+	Field1String:  "foo",
+	Field1Int64:   1,
+	Field1Float64: 3.0,
+	Field2String:  "bar",
+	Field2Int64:   2,
+	Field2Float64: 3.1,
+	Field3String:  "baz",
+	Field3Int64:   3,
+	Field3Float64: 3.14,
+	Field4String:  "qux",
+	Field4Int64:   4,
+	Field4Float64: 3.141,
+}
+
+var nestedInstance = nestedtest1{
+	nestedtest2{
+		nestedtest3{
+			nestedtest4{
+				nestedtest5{
+					nestedtest6{
+						nestedtest7{
+							nestedtest8{
+								nestedtest9{
+									nestedtest10{
+										nestedtest11{
+											encodetest{
+												Field1String:  "foo",
+												Field1Int64:   1,
+												Field1Float64: 3.0,
+												Field2String:  "bar",
+												Field2Int64:   2,
+												Field2Float64: 3.1,
+												Field3String:  "baz",
+												Field3Int64:   3,
+												Field3Float64: 3.14,
+												Field4String:  "qux",
+												Field4Int64:   4,
+												Field4Float64: 3.141,
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	},
+}
+
+func BenchmarkEncoding(b *testing.B) {
+	for i := 0; i < b.N; i++ {
+		_, _ = Marshal(encodetestInstance)
+	}
+}
+
+func BenchmarkEncodingNested(b *testing.B) {
+	for i := 0; i < b.N; i++ {
+		_, _ = Marshal(nestedInstance)
+	}
+}

+ 60 - 0
src/go.mongodb.org/mongo-driver/bson/bson.go

@@ -0,0 +1,60 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+//
+// Based on gopkg.in/mgo.v2/bson by Gustavo Niemeyer
+// See THIRD-PARTY-NOTICES for original license terms.
+
+// +build go1.9
+
+package bson // import "go.mongodb.org/mongo-driver/bson"
+
+import (
+	"go.mongodb.org/mongo-driver/bson/primitive"
+)
+
+// Zeroer allows custom struct types to implement a report of zero
+// state. All struct types that don't implement Zeroer or where IsZero
+// returns false are considered to be not zero.
+type Zeroer interface {
+	IsZero() bool
+}
+
+// D represents a BSON Document. This type can be used to represent BSON in a concise and readable
+// manner. It should generally be used when serializing to BSON. For deserializing, the Raw or
+// Document types should be used.
+//
+// Example usage:
+//
+// 		bson.D{{"foo", "bar"}, {"hello", "world"}, {"pi", 3.14159}}
+//
+// This type should be used in situations where order matters, such as MongoDB commands. If the
+// order is not important, a map is more comfortable and concise.
+type D = primitive.D
+
+// E represents a BSON element for a D. It is usually used inside a D.
+type E = primitive.E
+
+// M is an unordered, concise representation of a BSON Document. It should generally be used to
+// serialize BSON when the order of the elements of a BSON document do not matter. If the element
+// order matters, use a D instead.
+//
+// Example usage:
+//
+// 		bson.M{"foo": "bar", "hello": "world", "pi": 3.14159}
+//
+// This type is handled in the encoders as a regular map[string]interface{}. The elements will be
+// serialized in an undefined, random order, and the order will be different each time.
+type M = primitive.M
+
+// An A represents a BSON array. This type can be used to represent a BSON array in a concise and
+// readable manner. It should generally be used when serializing to BSON. For deserializing, the
+// RawArray or Array types should be used.
+//
+// Example usage:
+//
+// 		bson.A{"bar", "world", 3.14159, bson.D{{"qux", 12345}}}
+//
+type A = primitive.A

+ 91 - 0
src/go.mongodb.org/mongo-driver/bson/bson_1_8.go

@@ -0,0 +1,91 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+// +build !go1.9
+
+package bson // import "go.mongodb.org/mongo-driver/bson"
+
+import (
+	"math"
+	"strconv"
+	"strings"
+)
+
+// Zeroer allows custom struct types to implement a report of zero
+// state. All struct types that don't implement Zeroer or where IsZero
+// returns false are considered to be not zero.
+type Zeroer interface {
+	IsZero() bool
+}
+
+// D represents a BSON Document. This type can be used to represent BSON in a concise and readable
+// manner. It should generally be used when serializing to BSON. For deserializing, the Raw or
+// Document types should be used.
+//
+// Example usage:
+//
+// 		primitive.D{{"foo", "bar"}, {"hello", "world"}, {"pi", 3.14159}}
+//
+// This type should be used in situations where order matters, such as MongoDB commands. If the
+// order is not important, a map is more comfortable and concise.
+type D []E
+
+// Map creates a map from the elements of the D.
+func (d D) Map() M {
+	m := make(M, len(d))
+	for _, e := range d {
+		m[e.Key] = e.Value
+	}
+	return m
+}
+
+// E represents a BSON element for a D. It is usually used inside a D.
+type E struct {
+	Key   string
+	Value interface{}
+}
+
+// M is an unordered, concise representation of a BSON Document. It should generally be used to
+// serialize BSON when the order of the elements of a BSON document do not matter. If the element
+// order matters, use a D instead.
+//
+// Example usage:
+//
+// 		primitive.M{"foo": "bar", "hello": "world", "pi": 3.14159}
+//
+// This type is handled in the encoders as a regular map[string]interface{}. The elements will be
+// serialized in an undefined, random order, and the order will be different each time.
+type M map[string]interface{}
+
+// An A represents a BSON array. This type can be used to represent a BSON array in a concise and
+// readable manner. It should generally be used when serializing to BSON. For deserializing, the
+// RawArray or Array types should be used.
+//
+// Example usage:
+//
+// 		primitive.A{"bar", "world", 3.14159, primitive.D{{"qux", 12345}}}
+//
+type A []interface{}
+
+func formatDouble(f float64) string {
+	var s string
+	if math.IsInf(f, 1) {
+		s = "Infinity"
+	} else if math.IsInf(f, -1) {
+		s = "-Infinity"
+	} else if math.IsNaN(f) {
+		s = "NaN"
+	} else {
+		// Print exactly one decimalType place for integers; otherwise, print as many are necessary to
+		// perfectly represent it.
+		s = strconv.FormatFloat(f, 'G', -1, 64)
+		if !strings.ContainsRune(s, '.') {
+			s += ".0"
+		}
+	}
+
+	return s
+}

+ 371 - 0
src/go.mongodb.org/mongo-driver/bson/bson_corpus_spec_test.go

@@ -0,0 +1,371 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+package bson
+
+import (
+	"encoding/hex"
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"math"
+	"path"
+	"strconv"
+	"strings"
+	"testing"
+	"unicode"
+	"unicode/utf8"
+
+	"github.com/google/go-cmp/cmp"
+	"github.com/stretchr/testify/require"
+	"github.com/tidwall/pretty"
+	"go.mongodb.org/mongo-driver/bson/bsoncodec"
+	"go.mongodb.org/mongo-driver/bson/bsonrw"
+)
+
+type testCase struct {
+	Description  string                `json:"description"`
+	BsonType     string                `json:"bson_type"`
+	TestKey      *string               `json:"test_key"`
+	Valid        []validityTestCase    `json:"valid"`
+	DecodeErrors []decodeErrorTestCase `json:"decodeErrors"`
+	ParseErrors  []parseErrorTestCase  `json:"parseErrors"`
+	Deprecated   *bool                 `json:"deprecated"`
+}
+
+type validityTestCase struct {
+	Description       string  `json:"description"`
+	CanonicalBson     string  `json:"canonical_bson"`
+	CanonicalExtJSON  string  `json:"canonical_extjson"`
+	RelaxedExtJSON    *string `json:"relaxed_extjson"`
+	DegenerateBSON    *string `json:"degenerate_bson"`
+	DegenerateExtJSON *string `json:"degenerate_extjson"`
+	ConvertedBSON     *string `json:"converted_bson"`
+	ConvertedExtJSON  *string `json:"converted_extjson"`
+	Lossy             *bool   `json:"lossy"`
+}
+
+type decodeErrorTestCase struct {
+	Description string `json:"description"`
+	Bson        string `json:"bson"`
+}
+
+type parseErrorTestCase struct {
+	Description string `json:"description"`
+	String      string `json:"string"`
+}
+
+const dataDir = "../data"
+
+var dvd bsoncodec.DefaultValueDecoders
+var dve bsoncodec.DefaultValueEncoders
+
+var dc = bsoncodec.DecodeContext{Registry: NewRegistryBuilder().Build()}
+var ec = bsoncodec.EncodeContext{Registry: NewRegistryBuilder().Build()}
+
+func findJSONFilesInDir(t *testing.T, dir string) []string {
+	files := make([]string, 0)
+
+	entries, err := ioutil.ReadDir(dir)
+	require.NoError(t, err)
+
+	for _, entry := range entries {
+		if entry.IsDir() || path.Ext(entry.Name()) != ".json" {
+			continue
+		}
+
+		files = append(files, entry.Name())
+	}
+
+	return files
+}
+
+func needsEscapedUnicode(bsonType string) bool {
+	return bsonType == "0x02" || bsonType == "0x0D" || bsonType == "0x0E" || bsonType == "0x0F"
+}
+
+func unescapeUnicode(s, bsonType string) string {
+	if !needsEscapedUnicode(bsonType) {
+		return s
+	}
+
+	newS := ""
+
+	for i := 0; i < len(s); i++ {
+		c := s[i]
+		switch c {
+		case '\\':
+			switch s[i+1] {
+			case 'u':
+				us := s[i : i+6]
+				u, err := strconv.Unquote(strings.Replace(strconv.Quote(us), `\\u`, `\u`, 1))
+				if err != nil {
+					return ""
+				}
+				for _, r := range u {
+					if r < ' ' {
+						newS += fmt.Sprintf(`\u%04x`, r)
+					} else {
+						newS += string(r)
+					}
+				}
+				i += 5
+			default:
+				newS += string(c)
+			}
+		default:
+			if c > unicode.MaxASCII {
+				r, size := utf8.DecodeRune([]byte(s[i:]))
+				newS += string(r)
+				i += size - 1
+			} else {
+				newS += string(c)
+			}
+		}
+	}
+
+	return newS
+}
+
+func formatDouble(f float64) string {
+	var s string
+	if math.IsInf(f, 1) {
+		s = "Infinity"
+	} else if math.IsInf(f, -1) {
+		s = "-Infinity"
+	} else if math.IsNaN(f) {
+		s = "NaN"
+	} else {
+		// Print exactly one decimalType place for integers; otherwise, print as many are necessary to
+		// perfectly represent it.
+		s = strconv.FormatFloat(f, 'G', -1, 64)
+		if !strings.ContainsRune(s, 'E') && !strings.ContainsRune(s, '.') {
+			s += ".0"
+		}
+	}
+
+	return s
+}
+
+func normalizeCanonicalDouble(t *testing.T, key string, cEJ string) string {
+	// Unmarshal string into map
+	cEJMap := make(map[string]map[string]string)
+	err := json.Unmarshal([]byte(cEJ), &cEJMap)
+	require.NoError(t, err)
+
+	// Parse the float contained by the map.
+	expectedString := cEJMap[key]["$numberDouble"]
+	expectedFloat, err := strconv.ParseFloat(expectedString, 64)
+
+	// Normalize the string
+	return fmt.Sprintf(`{"%s":{"$numberDouble":"%s"}}`, key, formatDouble(expectedFloat))
+}
+
+func normalizeRelaxedDouble(t *testing.T, key string, rEJ string) string {
+	// Unmarshal string into map
+	rEJMap := make(map[string]float64)
+	err := json.Unmarshal([]byte(rEJ), &rEJMap)
+	if err != nil {
+		return normalizeCanonicalDouble(t, key, rEJ)
+	}
+
+	// Parse the float contained by the map.
+	expectedFloat := rEJMap[key]
+
+	// Normalize the string
+	return fmt.Sprintf(`{"%s":%s}`, key, formatDouble(expectedFloat))
+}
+
+// bsonToNative decodes the BSON bytes (b) into a native Document
+func bsonToNative(t *testing.T, b []byte, bType, testDesc string) D {
+	var doc D
+	err := Unmarshal(b, &doc)
+	expectNoError(t, err, fmt.Sprintf("%s: decoding %s BSON", testDesc, bType))
+	return doc
+}
+
+// nativeToBSON encodes the native Document (doc) into canonical BSON and compares it to the expected
+// canonical BSON (cB)
+func nativeToBSON(t *testing.T, cB []byte, doc D, testDesc, bType, docSrcDesc string) {
+	actual, err := Marshal(doc)
+	expectNoError(t, err, fmt.Sprintf("%s: encoding %s BSON", testDesc, bType))
+
+	if diff := cmp.Diff(cB, actual); diff != "" {
+		t.Errorf("%s: 'native_to_bson(%s) = cB' failed (-want, +got):\n-%v\n+%v\n",
+			testDesc, docSrcDesc, cB, actual)
+		t.FailNow()
+	}
+}
+
+// jsonToNative decodes the extended JSON string (ej) into a native Document
+func jsonToNative(t *testing.T, ej, ejType, testDesc string) D {
+	var doc D
+	err := UnmarshalExtJSON([]byte(ej), ejType != "relaxed", &doc)
+	expectNoError(t, err, fmt.Sprintf("%s: decoding %s extended JSON", testDesc, ejType))
+	return doc
+}
+
+// nativeToJSON encodes the native Document (doc) into an extended JSON string
+func nativeToJSON(t *testing.T, ej string, doc D, testDesc, ejType, ejShortName, docSrcDesc string) {
+	actualEJ, err := MarshalExtJSON(doc, ejType != "relaxed", true)
+	expectNoError(t, err, fmt.Sprintf("%s: encoding %s extended JSON", testDesc, ejType))
+
+	if diff := cmp.Diff(ej, string(actualEJ)); diff != "" {
+		t.Errorf("%s: 'native_to_%s_extended_json(%s) = %s' failed (-want, +got):\n%s\n",
+			testDesc, ejType, docSrcDesc, ejShortName, diff)
+		t.FailNow()
+	}
+}
+
+func runTest(t *testing.T, file string) {
+	filepath := path.Join(dataDir, file)
+	content, err := ioutil.ReadFile(filepath)
+	require.NoError(t, err)
+
+	// Remove ".json" from filename.
+	file = file[:len(file)-5]
+	testName := "bson_corpus--" + file
+
+	t.Run(testName, func(t *testing.T) {
+		var test testCase
+		require.NoError(t, json.Unmarshal(content, &test))
+
+		for _, v := range test.Valid {
+			// get canonical BSON
+			cB, err := hex.DecodeString(v.CanonicalBson)
+			expectNoError(t, err, fmt.Sprintf("%s: reading canonical BSON", v.Description))
+
+			// get canonical extended JSON
+			cEJ := unescapeUnicode(string(pretty.Ugly([]byte(v.CanonicalExtJSON))), test.BsonType)
+			if test.BsonType == "0x01" {
+				cEJ = normalizeCanonicalDouble(t, *test.TestKey, cEJ)
+			}
+
+			/*** canonical BSON round-trip tests ***/
+			doc := bsonToNative(t, cB, "canonical", v.Description)
+
+			// native_to_bson(bson_to_native(cB)) = cB
+			nativeToBSON(t, cB, doc, v.Description, "canonical", "bson_to_native(cB)")
+
+			// native_to_canonical_extended_json(bson_to_native(cB)) = cEJ
+			nativeToJSON(t, cEJ, doc, v.Description, "canonical", "cEJ", "bson_to_native(cB)")
+
+			// native_to_relaxed_extended_json(bson_to_native(cB)) = rEJ (if rEJ exists)
+			if v.RelaxedExtJSON != nil {
+				rEJ := unescapeUnicode(string(pretty.Ugly([]byte(*v.RelaxedExtJSON))), test.BsonType)
+				if test.BsonType == "0x01" {
+					rEJ = normalizeRelaxedDouble(t, *test.TestKey, rEJ)
+				}
+
+				nativeToJSON(t, rEJ, doc, v.Description, "relaxed", "rEJ", "bson_to_native(cB)")
+
+				/*** relaxed extended JSON round-trip tests (if exists) ***/
+				doc = jsonToNative(t, rEJ, "relaxed", v.Description)
+
+				// native_to_relaxed_extended_json(json_to_native(rEJ)) = rEJ
+				nativeToJSON(t, rEJ, doc, v.Description, "relaxed", "eJR", "json_to_native(rEJ)")
+			}
+
+			/*** canonical extended JSON round-trip tests ***/
+			doc = jsonToNative(t, cEJ, "canonical", v.Description)
+
+			// native_to_canonical_extended_json(json_to_native(cEJ)) = cEJ
+			nativeToJSON(t, cEJ, doc, v.Description, "canonical", "cEJ", "json_to_native(cEJ)")
+
+			// native_to_bson(json_to_native(cEJ)) = cb (unless lossy)
+			if v.Lossy == nil || !*v.Lossy {
+				nativeToBSON(t, cB, doc, v.Description, "canonical", "json_to_native(cEJ)")
+			}
+
+			/*** degenerate BSON round-trip tests (if exists) ***/
+			if v.DegenerateBSON != nil {
+				dB, err := hex.DecodeString(*v.DegenerateBSON)
+				expectNoError(t, err, fmt.Sprintf("%s: reading degenerate BSON", v.Description))
+
+				doc = bsonToNative(t, dB, "degenerate", v.Description)
+
+				// native_to_bson(bson_to_native(dB)) = cB
+				nativeToBSON(t, cB, doc, v.Description, "degenerate", "bson_to_native(dB)")
+			}
+
+			/*** degenerate JSON round-trip tests (if exists) ***/
+			if v.DegenerateExtJSON != nil {
+				dEJ := unescapeUnicode(string(pretty.Ugly([]byte(*v.DegenerateExtJSON))), test.BsonType)
+				if test.BsonType == "0x01" {
+					dEJ = normalizeCanonicalDouble(t, *test.TestKey, dEJ)
+				}
+
+				doc = jsonToNative(t, dEJ, "degenerate canonical", v.Description)
+
+				// native_to_canonical_extended_json(json_to_native(dEJ)) = cEJ
+				nativeToJSON(t, cEJ, doc, v.Description, "degenerate canonical", "cEJ", "json_to_native(dEJ)")
+
+				// native_to_bson(json_to_native(dEJ)) = cB (unless lossy)
+				if v.Lossy == nil || !*v.Lossy {
+					nativeToBSON(t, cB, doc, v.Description, "canonical", "json_to_native(dEJ)")
+				}
+			}
+		}
+
+		for _, d := range test.DecodeErrors {
+			b, err := hex.DecodeString(d.Bson)
+			expectNoError(t, err, d.Description)
+
+			var doc D
+			err = Unmarshal(b, &doc)
+			expectError(t, err, fmt.Sprintf("%s: expected decode error", d.Description))
+		}
+
+		for _, p := range test.ParseErrors {
+			// skip DBRef tests
+			if strings.Contains(p.Description, "Bad DBRef") {
+				continue
+			}
+
+			s := unescapeUnicode(p.String, test.BsonType)
+			if test.BsonType == "0x13" {
+				s = fmt.Sprintf(`{"$numberDecimal": "%s"}`, s)
+			}
+
+			switch test.BsonType {
+			case "0x00":
+				var doc D
+				err := UnmarshalExtJSON([]byte(s), true, &doc)
+				expectError(t, err, fmt.Sprintf("%s: expected parse error", p.Description))
+			case "0x13":
+				ejvr, err := bsonrw.NewExtJSONValueReader(strings.NewReader(s), true)
+				expectNoError(t, err, fmt.Sprintf("error creating value reader: %s", err))
+				_, err = ejvr.ReadDecimal128()
+				expectError(t, err, fmt.Sprintf("%s: expected parse error", p.Description))
+			default:
+				t.Errorf("Update test to check for parse errors for type %s", test.BsonType)
+				t.Fail()
+			}
+		}
+	})
+}
+
+func Test_BsonCorpus(t *testing.T) {
+	for _, file := range findJSONFilesInDir(t, dataDir) {
+		runTest(t, file)
+	}
+}
+
+func expectNoError(t *testing.T, err error, desc string) {
+	if err != nil {
+		t.Helper()
+		t.Errorf("%s: Unepexted error: %v", desc, err)
+		t.FailNow()
+	}
+}
+
+func expectError(t *testing.T, err error, desc string) {
+	if err == nil {
+		t.Helper()
+		t.Errorf("%s: Expected error", desc)
+		t.FailNow()
+	}
+}

+ 113 - 0
src/go.mongodb.org/mongo-driver/bson/bson_test.go

@@ -0,0 +1,113 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+package bson
+
+import (
+	"bytes"
+	"testing"
+	"time"
+
+	"github.com/google/go-cmp/cmp"
+	"github.com/stretchr/testify/require"
+	"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
+)
+
+func noerr(t *testing.T, err error) {
+	if err != nil {
+		t.Helper()
+		t.Errorf("Unexpected error: (%T)%v", err, err)
+		t.FailNow()
+	}
+}
+
+func requireErrEqual(t *testing.T, err1 error, err2 error) { require.True(t, compareErrors(err1, err2)) }
+
+func TestTimeRoundTrip(t *testing.T) {
+	val := struct {
+		Value time.Time
+		ID    string
+	}{
+		ID: "time-rt-test",
+	}
+
+	if !val.Value.IsZero() {
+		t.Errorf("Did not get zero time as expected.")
+	}
+
+	bsonOut, err := Marshal(val)
+	noerr(t, err)
+	rtval := struct {
+		Value time.Time
+		ID    string
+	}{}
+
+	err = Unmarshal(bsonOut, &rtval)
+	noerr(t, err)
+	if !cmp.Equal(val, rtval) {
+		t.Errorf("Did not round trip properly. got %v; want %v", val, rtval)
+	}
+	if !rtval.Value.IsZero() {
+		t.Errorf("Did not get zero time as expected.")
+	}
+}
+
+func TestNonNullTimeRoundTrip(t *testing.T) {
+	now := time.Now()
+	now = time.Unix(now.Unix(), 0)
+	val := struct {
+		Value time.Time
+		ID    string
+	}{
+		ID:    "time-rt-test",
+		Value: now,
+	}
+
+	bsonOut, err := Marshal(val)
+	noerr(t, err)
+	rtval := struct {
+		Value time.Time
+		ID    string
+	}{}
+
+	err = Unmarshal(bsonOut, &rtval)
+	noerr(t, err)
+	if !cmp.Equal(val, rtval) {
+		t.Errorf("Did not round trip properly. got %v; want %v", val, rtval)
+	}
+}
+
+func TestD(t *testing.T) {
+	t.Run("can marshal", func(t *testing.T) {
+		d := D{{"foo", "bar"}, {"hello", "world"}, {"pi", 3.14159}}
+		idx, want := bsoncore.AppendDocumentStart(nil)
+		want = bsoncore.AppendStringElement(want, "foo", "bar")
+		want = bsoncore.AppendStringElement(want, "hello", "world")
+		want = bsoncore.AppendDoubleElement(want, "pi", 3.14159)
+		want, err := bsoncore.AppendDocumentEnd(want, idx)
+		noerr(t, err)
+		got, err := Marshal(d)
+		noerr(t, err)
+		if !bytes.Equal(got, want) {
+			t.Errorf("Marshaled documents do not match. got %v; want %v", Raw(got), Raw(want))
+		}
+	})
+	t.Run("can unmarshal", func(t *testing.T) {
+		want := D{{"foo", "bar"}, {"hello", "world"}, {"pi", 3.14159}}
+		idx, doc := bsoncore.AppendDocumentStart(nil)
+		doc = bsoncore.AppendStringElement(doc, "foo", "bar")
+		doc = bsoncore.AppendStringElement(doc, "hello", "world")
+		doc = bsoncore.AppendDoubleElement(doc, "pi", 3.14159)
+		doc, err := bsoncore.AppendDocumentEnd(doc, idx)
+		noerr(t, err)
+		var got D
+		err = Unmarshal(doc, &got)
+		noerr(t, err)
+		if !cmp.Equal(got, want) {
+			t.Errorf("Unmarshaled documents do not match. got %v; want %v", got, want)
+		}
+	})
+}

+ 163 - 0
src/go.mongodb.org/mongo-driver/bson/bsoncodec/bsoncodec.go

@@ -0,0 +1,163 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+package bsoncodec // import "go.mongodb.org/mongo-driver/bson/bsoncodec"
+
+import (
+	"fmt"
+	"reflect"
+	"strings"
+
+	"go.mongodb.org/mongo-driver/bson/bsonrw"
+	"go.mongodb.org/mongo-driver/bson/bsontype"
+)
+
+// Marshaler is an interface implemented by types that can marshal themselves
+// into a BSON document represented as bytes. The bytes returned must be a valid
+// BSON document if the error is nil.
+type Marshaler interface {
+	MarshalBSON() ([]byte, error)
+}
+
+// ValueMarshaler is an interface implemented by types that can marshal
+// themselves into a BSON value as bytes. The type must be the valid type for
+// the bytes returned. The bytes and byte type together must be valid if the
+// error is nil.
+type ValueMarshaler interface {
+	MarshalBSONValue() (bsontype.Type, []byte, error)
+}
+
+// Unmarshaler is an interface implemented by types that can unmarshal a BSON
+// document representation of themselves. The BSON bytes can be assumed to be
+// valid. UnmarshalBSON must copy the BSON bytes if it wishes to retain the data
+// after returning.
+type Unmarshaler interface {
+	UnmarshalBSON([]byte) error
+}
+
+// ValueUnmarshaler is an interface implemented by types that can unmarshal a
+// BSON value representaiton of themselves. The BSON bytes and type can be
+// assumed to be valid. UnmarshalBSONValue must copy the BSON value bytes if it
+// wishes to retain the data after returning.
+type ValueUnmarshaler interface {
+	UnmarshalBSONValue(bsontype.Type, []byte) error
+}
+
+// ValueEncoderError is an error returned from a ValueEncoder when the provided value can't be
+// encoded by the ValueEncoder.
+type ValueEncoderError struct {
+	Name     string
+	Types    []reflect.Type
+	Kinds    []reflect.Kind
+	Received reflect.Value
+}
+
+func (vee ValueEncoderError) Error() string {
+	typeKinds := make([]string, 0, len(vee.Types)+len(vee.Kinds))
+	for _, t := range vee.Types {
+		typeKinds = append(typeKinds, t.String())
+	}
+	for _, k := range vee.Kinds {
+		if k == reflect.Map {
+			typeKinds = append(typeKinds, "map[string]*")
+			continue
+		}
+		typeKinds = append(typeKinds, k.String())
+	}
+	received := vee.Received.Kind().String()
+	if vee.Received.IsValid() {
+		received = vee.Received.Type().String()
+	}
+	return fmt.Sprintf("%s can only encode valid %s, but got %s", vee.Name, strings.Join(typeKinds, ", "), received)
+}
+
+// ValueDecoderError is an error returned from a ValueDecoder when the provided value can't be
+// decoded by the ValueDecoder.
+type ValueDecoderError struct {
+	Name     string
+	Types    []reflect.Type
+	Kinds    []reflect.Kind
+	Received reflect.Value
+}
+
+func (vde ValueDecoderError) Error() string {
+	typeKinds := make([]string, 0, len(vde.Types)+len(vde.Kinds))
+	for _, t := range vde.Types {
+		typeKinds = append(typeKinds, t.String())
+	}
+	for _, k := range vde.Kinds {
+		if k == reflect.Map {
+			typeKinds = append(typeKinds, "map[string]*")
+			continue
+		}
+		typeKinds = append(typeKinds, k.String())
+	}
+	received := vde.Received.Kind().String()
+	if vde.Received.IsValid() {
+		received = vde.Received.Type().String()
+	}
+	return fmt.Sprintf("%s can only decode valid and settable %s, but got %s", vde.Name, strings.Join(typeKinds, ", "), received)
+}
+
+// EncodeContext is the contextual information required for a Codec to encode a
+// value.
+type EncodeContext struct {
+	*Registry
+	MinSize bool
+}
+
+// DecodeContext is the contextual information required for a Codec to decode a
+// value.
+type DecodeContext struct {
+	*Registry
+	Truncate bool
+	// Ancestor is the type of a containing document. This is mainly used to determine what type
+	// should be used when decoding an embedded document into an empty interface. For example, if
+	// Ancestor is a bson.M, BSON embedded document values being decoded into an empty interface
+	// will be decoded into a bson.M.
+	Ancestor reflect.Type
+}
+
+// ValueCodec is the interface that groups the methods to encode and decode
+// values.
+type ValueCodec interface {
+	ValueEncoder
+	ValueDecoder
+}
+
+// ValueEncoder is the interface implemented by types that can handle the encoding of a value.
+type ValueEncoder interface {
+	EncodeValue(EncodeContext, bsonrw.ValueWriter, reflect.Value) error
+}
+
+// ValueEncoderFunc is an adapter function that allows a function with the correct signature to be
+// used as a ValueEncoder.
+type ValueEncoderFunc func(EncodeContext, bsonrw.ValueWriter, reflect.Value) error
+
+// EncodeValue implements the ValueEncoder interface.
+func (fn ValueEncoderFunc) EncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
+	return fn(ec, vw, val)
+}
+
+// ValueDecoder is the interface implemented by types that can handle the decoding of a value.
+type ValueDecoder interface {
+	DecodeValue(DecodeContext, bsonrw.ValueReader, reflect.Value) error
+}
+
+// ValueDecoderFunc is an adapter function that allows a function with the correct signature to be
+// used as a ValueDecoder.
+type ValueDecoderFunc func(DecodeContext, bsonrw.ValueReader, reflect.Value) error
+
+// DecodeValue implements the ValueDecoder interface.
+func (fn ValueDecoderFunc) DecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	return fn(dc, vr, val)
+}
+
+// CodecZeroer is the interface implemented by Codecs that can also determine if
+// a value of the type that would be encoded is zero.
+type CodecZeroer interface {
+	IsTypeZero(interface{}) bool
+}

+ 145 - 0
src/go.mongodb.org/mongo-driver/bson/bsoncodec/bsoncodec_test.go

@@ -0,0 +1,145 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+package bsoncodec
+
+import (
+	"fmt"
+	"reflect"
+	"testing"
+	"time"
+
+	"go.mongodb.org/mongo-driver/bson/bsonrw"
+	"go.mongodb.org/mongo-driver/bson/bsontype"
+	"go.mongodb.org/mongo-driver/bson/primitive"
+)
+
+func ExampleValueEncoder() {
+	var _ ValueEncoderFunc = func(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
+		if val.Kind() != reflect.String {
+			return ValueEncoderError{Name: "StringEncodeValue", Kinds: []reflect.Kind{reflect.String}, Received: val}
+		}
+
+		return vw.WriteString(val.String())
+	}
+}
+
+func ExampleValueDecoder() {
+	var _ ValueDecoderFunc = func(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+		if !val.CanSet() || val.Kind() != reflect.String {
+			return ValueDecoderError{Name: "StringDecodeValue", Kinds: []reflect.Kind{reflect.String}, Received: val}
+		}
+
+		if vr.Type() != bsontype.String {
+			return fmt.Errorf("cannot decode %v into a string type", vr.Type())
+		}
+
+		str, err := vr.ReadString()
+		if err != nil {
+			return err
+		}
+		val.SetString(str)
+		return nil
+	}
+}
+
+func noerr(t *testing.T, err error) {
+	if err != nil {
+		t.Helper()
+		t.Errorf("Unexpected error: (%T)%v", err, err)
+		t.FailNow()
+	}
+}
+
+func compareTime(t1, t2 time.Time) bool {
+	if t1.Location() != t2.Location() {
+		return false
+	}
+	return t1.Equal(t2)
+}
+
+func compareErrors(err1, err2 error) bool {
+	if err1 == nil && err2 == nil {
+		return true
+	}
+
+	if err1 == nil || err2 == nil {
+		return false
+	}
+
+	if err1.Error() != err2.Error() {
+		return false
+	}
+
+	return true
+}
+
+func compareDecimal128(d1, d2 primitive.Decimal128) bool {
+	d1H, d1L := d1.GetBytes()
+	d2H, d2L := d2.GetBytes()
+
+	if d1H != d2H {
+		return false
+	}
+
+	if d1L != d2L {
+		return false
+	}
+
+	return true
+}
+
+func compareStrings(s1, s2 string) bool { return s1 == s2 }
+
+type noPrivateFields struct {
+	a string
+}
+
+func compareNoPrivateFields(npf1, npf2 noPrivateFields) bool {
+	return npf1.a != npf2.a // We don't want these to be equal
+}
+
+type zeroTest struct {
+	reportZero bool
+}
+
+func (z zeroTest) IsZero() bool { return z.reportZero }
+
+func compareZeroTest(_, _ zeroTest) bool { return true }
+
+type nonZeroer struct {
+	value bool
+}
+
+type llCodec struct {
+	t         *testing.T
+	decodeval interface{}
+	encodeval interface{}
+	err       error
+}
+
+func (llc *llCodec) EncodeValue(_ EncodeContext, _ bsonrw.ValueWriter, i interface{}) error {
+	if llc.err != nil {
+		return llc.err
+	}
+
+	llc.encodeval = i
+	return nil
+}
+
+func (llc *llCodec) DecodeValue(_ DecodeContext, _ bsonrw.ValueReader, val reflect.Value) error {
+	if llc.err != nil {
+		return llc.err
+	}
+
+	if !reflect.TypeOf(llc.decodeval).AssignableTo(val.Type()) {
+		llc.t.Errorf("decodeval must be assignable to val provided to DecodeValue, but is not. decodeval %T; val %T", llc.decodeval, val)
+		return nil
+	}
+
+	val.Set(reflect.ValueOf(llc.decodeval))
+	return nil
+}

+ 1014 - 0
src/go.mongodb.org/mongo-driver/bson/bsoncodec/default_value_decoders.go

@@ -0,0 +1,1014 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+package bsoncodec
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"math"
+	"net/url"
+	"reflect"
+	"strconv"
+	"time"
+
+	"go.mongodb.org/mongo-driver/bson/bsonrw"
+	"go.mongodb.org/mongo-driver/bson/bsontype"
+	"go.mongodb.org/mongo-driver/bson/primitive"
+	"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
+)
+
+var defaultValueDecoders DefaultValueDecoders
+
+// DefaultValueDecoders is a namespace type for the default ValueDecoders used
+// when creating a registry.
+type DefaultValueDecoders struct{}
+
+// RegisterDefaultDecoders will register the decoder methods attached to DefaultValueDecoders with
+// the provided RegistryBuilder.
+//
+// There is no support for decoding map[string]interface{} becuase there is no decoder for
+// interface{}, so users must either register this decoder themselves or use the
+// EmptyInterfaceDecoder avaialble in the bson package.
+func (dvd DefaultValueDecoders) RegisterDefaultDecoders(rb *RegistryBuilder) {
+	if rb == nil {
+		panic(errors.New("argument to RegisterDefaultDecoders must not be nil"))
+	}
+
+	rb.
+		RegisterDecoder(tBinary, ValueDecoderFunc(dvd.BinaryDecodeValue)).
+		RegisterDecoder(tUndefined, ValueDecoderFunc(dvd.UndefinedDecodeValue)).
+		RegisterDecoder(tDateTime, ValueDecoderFunc(dvd.DateTimeDecodeValue)).
+		RegisterDecoder(tNull, ValueDecoderFunc(dvd.NullDecodeValue)).
+		RegisterDecoder(tRegex, ValueDecoderFunc(dvd.RegexDecodeValue)).
+		RegisterDecoder(tDBPointer, ValueDecoderFunc(dvd.DBPointerDecodeValue)).
+		RegisterDecoder(tTimestamp, ValueDecoderFunc(dvd.TimestampDecodeValue)).
+		RegisterDecoder(tMinKey, ValueDecoderFunc(dvd.MinKeyDecodeValue)).
+		RegisterDecoder(tMaxKey, ValueDecoderFunc(dvd.MaxKeyDecodeValue)).
+		RegisterDecoder(tJavaScript, ValueDecoderFunc(dvd.JavaScriptDecodeValue)).
+		RegisterDecoder(tSymbol, ValueDecoderFunc(dvd.SymbolDecodeValue)).
+		RegisterDecoder(tByteSlice, ValueDecoderFunc(dvd.ByteSliceDecodeValue)).
+		RegisterDecoder(tTime, ValueDecoderFunc(dvd.TimeDecodeValue)).
+		RegisterDecoder(tEmpty, ValueDecoderFunc(dvd.EmptyInterfaceDecodeValue)).
+		RegisterDecoder(tOID, ValueDecoderFunc(dvd.ObjectIDDecodeValue)).
+		RegisterDecoder(tDecimal, ValueDecoderFunc(dvd.Decimal128DecodeValue)).
+		RegisterDecoder(tJSONNumber, ValueDecoderFunc(dvd.JSONNumberDecodeValue)).
+		RegisterDecoder(tURL, ValueDecoderFunc(dvd.URLDecodeValue)).
+		RegisterDecoder(tValueUnmarshaler, ValueDecoderFunc(dvd.ValueUnmarshalerDecodeValue)).
+		RegisterDecoder(tUnmarshaler, ValueDecoderFunc(dvd.UnmarshalerDecodeValue)).
+		RegisterDecoder(tCoreDocument, ValueDecoderFunc(dvd.CoreDocumentDecodeValue)).
+		RegisterDecoder(tCodeWithScope, ValueDecoderFunc(dvd.CodeWithScopeDecodeValue)).
+		RegisterDefaultDecoder(reflect.Bool, ValueDecoderFunc(dvd.BooleanDecodeValue)).
+		RegisterDefaultDecoder(reflect.Int, ValueDecoderFunc(dvd.IntDecodeValue)).
+		RegisterDefaultDecoder(reflect.Int8, ValueDecoderFunc(dvd.IntDecodeValue)).
+		RegisterDefaultDecoder(reflect.Int16, ValueDecoderFunc(dvd.IntDecodeValue)).
+		RegisterDefaultDecoder(reflect.Int32, ValueDecoderFunc(dvd.IntDecodeValue)).
+		RegisterDefaultDecoder(reflect.Int64, ValueDecoderFunc(dvd.IntDecodeValue)).
+		RegisterDefaultDecoder(reflect.Uint, ValueDecoderFunc(dvd.UintDecodeValue)).
+		RegisterDefaultDecoder(reflect.Uint8, ValueDecoderFunc(dvd.UintDecodeValue)).
+		RegisterDefaultDecoder(reflect.Uint16, ValueDecoderFunc(dvd.UintDecodeValue)).
+		RegisterDefaultDecoder(reflect.Uint32, ValueDecoderFunc(dvd.UintDecodeValue)).
+		RegisterDefaultDecoder(reflect.Uint64, ValueDecoderFunc(dvd.UintDecodeValue)).
+		RegisterDefaultDecoder(reflect.Float32, ValueDecoderFunc(dvd.FloatDecodeValue)).
+		RegisterDefaultDecoder(reflect.Float64, ValueDecoderFunc(dvd.FloatDecodeValue)).
+		RegisterDefaultDecoder(reflect.Array, ValueDecoderFunc(dvd.ArrayDecodeValue)).
+		RegisterDefaultDecoder(reflect.Map, ValueDecoderFunc(dvd.MapDecodeValue)).
+		RegisterDefaultDecoder(reflect.Slice, ValueDecoderFunc(dvd.SliceDecodeValue)).
+		RegisterDefaultDecoder(reflect.String, ValueDecoderFunc(dvd.StringDecodeValue)).
+		RegisterDefaultDecoder(reflect.Struct, &StructCodec{cache: make(map[reflect.Type]*structDescription), parser: DefaultStructTagParser}).
+		RegisterDefaultDecoder(reflect.Ptr, NewPointerCodec()).
+		RegisterTypeMapEntry(bsontype.Double, tFloat64).
+		RegisterTypeMapEntry(bsontype.String, tString).
+		RegisterTypeMapEntry(bsontype.Array, tA).
+		RegisterTypeMapEntry(bsontype.Binary, tBinary).
+		RegisterTypeMapEntry(bsontype.Undefined, tUndefined).
+		RegisterTypeMapEntry(bsontype.ObjectID, tOID).
+		RegisterTypeMapEntry(bsontype.Boolean, tBool).
+		RegisterTypeMapEntry(bsontype.DateTime, tDateTime).
+		RegisterTypeMapEntry(bsontype.Regex, tRegex).
+		RegisterTypeMapEntry(bsontype.DBPointer, tDBPointer).
+		RegisterTypeMapEntry(bsontype.JavaScript, tJavaScript).
+		RegisterTypeMapEntry(bsontype.Symbol, tSymbol).
+		RegisterTypeMapEntry(bsontype.CodeWithScope, tCodeWithScope).
+		RegisterTypeMapEntry(bsontype.Int32, tInt32).
+		RegisterTypeMapEntry(bsontype.Int64, tInt64).
+		RegisterTypeMapEntry(bsontype.Timestamp, tTimestamp).
+		RegisterTypeMapEntry(bsontype.Decimal128, tDecimal).
+		RegisterTypeMapEntry(bsontype.MinKey, tMinKey).
+		RegisterTypeMapEntry(bsontype.MaxKey, tMaxKey).
+		RegisterTypeMapEntry(bsontype.Type(0), tD)
+}
+
+// BooleanDecodeValue is the ValueDecoderFunc for bool types.
+func (dvd DefaultValueDecoders) BooleanDecodeValue(dctx DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if vr.Type() != bsontype.Boolean {
+		return fmt.Errorf("cannot decode %v into a boolean", vr.Type())
+	}
+	if !val.IsValid() || !val.CanSet() || val.Kind() != reflect.Bool {
+		return ValueDecoderError{Name: "BooleanDecodeValue", Kinds: []reflect.Kind{reflect.Bool}, Received: val}
+	}
+
+	b, err := vr.ReadBoolean()
+	val.SetBool(b)
+	return err
+}
+
+// IntDecodeValue is the ValueDecoderFunc for bool types.
+func (dvd DefaultValueDecoders) IntDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	var i64 int64
+	var err error
+	switch vr.Type() {
+	case bsontype.Int32:
+		i32, err := vr.ReadInt32()
+		if err != nil {
+			return err
+		}
+		i64 = int64(i32)
+	case bsontype.Int64:
+		i64, err = vr.ReadInt64()
+		if err != nil {
+			return err
+		}
+	case bsontype.Double:
+		f64, err := vr.ReadDouble()
+		if err != nil {
+			return err
+		}
+		if !dc.Truncate && math.Floor(f64) != f64 {
+			return errors.New("IntDecodeValue can only truncate float64 to an integer type when truncation is enabled")
+		}
+		if f64 > float64(math.MaxInt64) {
+			return fmt.Errorf("%g overflows int64", f64)
+		}
+		i64 = int64(f64)
+	default:
+		return fmt.Errorf("cannot decode %v into an integer type", vr.Type())
+	}
+
+	if !val.CanSet() {
+		return ValueDecoderError{
+			Name:     "IntDecodeValue",
+			Kinds:    []reflect.Kind{reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int},
+			Received: val,
+		}
+	}
+
+	switch val.Kind() {
+	case reflect.Int8:
+		if i64 < math.MinInt8 || i64 > math.MaxInt8 {
+			return fmt.Errorf("%d overflows int8", i64)
+		}
+	case reflect.Int16:
+		if i64 < math.MinInt16 || i64 > math.MaxInt16 {
+			return fmt.Errorf("%d overflows int16", i64)
+		}
+	case reflect.Int32:
+		if i64 < math.MinInt32 || i64 > math.MaxInt32 {
+			return fmt.Errorf("%d overflows int32", i64)
+		}
+	case reflect.Int64:
+	case reflect.Int:
+		if int64(int(i64)) != i64 { // Can we fit this inside of an int
+			return fmt.Errorf("%d overflows int", i64)
+		}
+	default:
+		return ValueDecoderError{
+			Name:     "IntDecodeValue",
+			Kinds:    []reflect.Kind{reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int},
+			Received: val,
+		}
+	}
+
+	val.SetInt(i64)
+	return nil
+}
+
+// UintDecodeValue is the ValueDecoderFunc for uint types.
+func (dvd DefaultValueDecoders) UintDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	var i64 int64
+	var err error
+	switch vr.Type() {
+	case bsontype.Int32:
+		i32, err := vr.ReadInt32()
+		if err != nil {
+			return err
+		}
+		i64 = int64(i32)
+	case bsontype.Int64:
+		i64, err = vr.ReadInt64()
+		if err != nil {
+			return err
+		}
+	case bsontype.Double:
+		f64, err := vr.ReadDouble()
+		if err != nil {
+			return err
+		}
+		if !dc.Truncate && math.Floor(f64) != f64 {
+			return errors.New("UintDecodeValue can only truncate float64 to an integer type when truncation is enabled")
+		}
+		if f64 > float64(math.MaxInt64) {
+			return fmt.Errorf("%g overflows int64", f64)
+		}
+		i64 = int64(f64)
+	default:
+		return fmt.Errorf("cannot decode %v into an integer type", vr.Type())
+	}
+
+	if !val.CanSet() {
+		return ValueDecoderError{
+			Name:     "UintDecodeValue",
+			Kinds:    []reflect.Kind{reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint},
+			Received: val,
+		}
+	}
+
+	switch val.Kind() {
+	case reflect.Uint8:
+		if i64 < 0 || i64 > math.MaxUint8 {
+			return fmt.Errorf("%d overflows uint8", i64)
+		}
+	case reflect.Uint16:
+		if i64 < 0 || i64 > math.MaxUint16 {
+			return fmt.Errorf("%d overflows uint16", i64)
+		}
+	case reflect.Uint32:
+		if i64 < 0 || i64 > math.MaxUint32 {
+			return fmt.Errorf("%d overflows uint32", i64)
+		}
+	case reflect.Uint64:
+		if i64 < 0 {
+			return fmt.Errorf("%d overflows uint64", i64)
+		}
+	case reflect.Uint:
+		if i64 < 0 || int64(uint(i64)) != i64 { // Can we fit this inside of an uint
+			return fmt.Errorf("%d overflows uint", i64)
+		}
+	default:
+		return ValueDecoderError{
+			Name:     "UintDecodeValue",
+			Kinds:    []reflect.Kind{reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint},
+			Received: val,
+		}
+	}
+
+	val.SetUint(uint64(i64))
+	return nil
+}
+
+// FloatDecodeValue is the ValueDecoderFunc for float types.
+func (dvd DefaultValueDecoders) FloatDecodeValue(ec DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	var f float64
+	var err error
+	switch vr.Type() {
+	case bsontype.Int32:
+		i32, err := vr.ReadInt32()
+		if err != nil {
+			return err
+		}
+		f = float64(i32)
+	case bsontype.Int64:
+		i64, err := vr.ReadInt64()
+		if err != nil {
+			return err
+		}
+		f = float64(i64)
+	case bsontype.Double:
+		f, err = vr.ReadDouble()
+		if err != nil {
+			return err
+		}
+	default:
+		return fmt.Errorf("cannot decode %v into a float32 or float64 type", vr.Type())
+	}
+
+	if !val.CanSet() {
+		return ValueDecoderError{Name: "FloatDecodeValue", Kinds: []reflect.Kind{reflect.Float32, reflect.Float64}, Received: val}
+	}
+
+	switch val.Kind() {
+	case reflect.Float32:
+		if !ec.Truncate && float64(float32(f)) != f {
+			return errors.New("FloatDecodeValue can only convert float64 to float32 when truncation is allowed")
+		}
+	case reflect.Float64:
+	default:
+		return ValueDecoderError{Name: "FloatDecodeValue", Kinds: []reflect.Kind{reflect.Float32, reflect.Float64}, Received: val}
+	}
+
+	val.SetFloat(f)
+	return nil
+}
+
+// StringDecodeValue is the ValueDecoderFunc for string types.
+func (dvd DefaultValueDecoders) StringDecodeValue(dctx DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	var str string
+	var err error
+	switch vr.Type() {
+	// TODO(GODRIVER-577): Handle JavaScript and Symbol BSON types when allowed.
+	case bsontype.String:
+		str, err = vr.ReadString()
+		if err != nil {
+			return err
+		}
+	default:
+		return fmt.Errorf("cannot decode %v into a string type", vr.Type())
+	}
+	if !val.CanSet() || val.Kind() != reflect.String {
+		return ValueDecoderError{Name: "StringDecodeValue", Kinds: []reflect.Kind{reflect.String}, Received: val}
+	}
+
+	val.SetString(str)
+	return nil
+}
+
+// JavaScriptDecodeValue is the ValueDecoderFunc for the primitive.JavaScript type.
+func (DefaultValueDecoders) JavaScriptDecodeValue(dctx DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if !val.CanSet() || val.Type() != tJavaScript {
+		return ValueDecoderError{Name: "BinaryDecodeValue", Types: []reflect.Type{tJavaScript}, Received: val}
+	}
+
+	if vr.Type() != bsontype.JavaScript {
+		return fmt.Errorf("cannot decode %v into a primitive.JavaScript", vr.Type())
+	}
+
+	js, err := vr.ReadJavascript()
+	if err != nil {
+		return err
+	}
+
+	val.SetString(js)
+	return nil
+}
+
+// SymbolDecodeValue is the ValueDecoderFunc for the primitive.Symbol type.
+func (DefaultValueDecoders) SymbolDecodeValue(dctx DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if !val.CanSet() || val.Type() != tSymbol {
+		return ValueDecoderError{Name: "BinaryDecodeValue", Types: []reflect.Type{tSymbol}, Received: val}
+	}
+
+	if vr.Type() != bsontype.Symbol {
+		return fmt.Errorf("cannot decode %v into a primitive.Symbol", vr.Type())
+	}
+
+	symbol, err := vr.ReadSymbol()
+	if err != nil {
+		return err
+	}
+
+	val.SetString(symbol)
+	return nil
+}
+
+// BinaryDecodeValue is the ValueDecoderFunc for Binary.
+func (DefaultValueDecoders) BinaryDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if !val.CanSet() || val.Type() != tBinary {
+		return ValueDecoderError{Name: "BinaryDecodeValue", Types: []reflect.Type{tBinary}, Received: val}
+	}
+
+	if vr.Type() != bsontype.Binary {
+		return fmt.Errorf("cannot decode %v into a Binary", vr.Type())
+	}
+
+	data, subtype, err := vr.ReadBinary()
+	if err != nil {
+		return err
+	}
+
+	val.Set(reflect.ValueOf(primitive.Binary{Subtype: subtype, Data: data}))
+	return nil
+}
+
+// UndefinedDecodeValue is the ValueDecoderFunc for Undefined.
+func (DefaultValueDecoders) UndefinedDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if !val.CanSet() || val.Type() != tUndefined {
+		return ValueDecoderError{Name: "UndefinedDecodeValue", Types: []reflect.Type{tUndefined}, Received: val}
+	}
+
+	if vr.Type() != bsontype.Undefined {
+		return fmt.Errorf("cannot decode %v into an Undefined", vr.Type())
+	}
+
+	val.Set(reflect.ValueOf(primitive.Undefined{}))
+	return vr.ReadUndefined()
+}
+
+// ObjectIDDecodeValue is the ValueDecoderFunc for primitive.ObjectID.
+func (dvd DefaultValueDecoders) ObjectIDDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if !val.CanSet() || val.Type() != tOID {
+		return ValueDecoderError{Name: "ObjectIDDecodeValue", Types: []reflect.Type{tOID}, Received: val}
+	}
+
+	if vr.Type() != bsontype.ObjectID {
+		return fmt.Errorf("cannot decode %v into an ObjectID", vr.Type())
+	}
+	oid, err := vr.ReadObjectID()
+	val.Set(reflect.ValueOf(oid))
+	return err
+}
+
+// DateTimeDecodeValue is the ValueDecoderFunc for DateTime.
+func (DefaultValueDecoders) DateTimeDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if !val.CanSet() || val.Type() != tDateTime {
+		return ValueDecoderError{Name: "DateTimeDecodeValue", Types: []reflect.Type{tDateTime}, Received: val}
+	}
+
+	if vr.Type() != bsontype.DateTime {
+		return fmt.Errorf("cannot decode %v into a DateTime", vr.Type())
+	}
+
+	dt, err := vr.ReadDateTime()
+	if err != nil {
+		return err
+	}
+
+	val.Set(reflect.ValueOf(primitive.DateTime(dt)))
+	return nil
+}
+
+// NullDecodeValue is the ValueDecoderFunc for Null.
+func (DefaultValueDecoders) NullDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if !val.CanSet() || val.Type() != tNull {
+		return ValueDecoderError{Name: "NullDecodeValue", Types: []reflect.Type{tNull}, Received: val}
+	}
+
+	if vr.Type() != bsontype.Null {
+		return fmt.Errorf("cannot decode %v into a Null", vr.Type())
+	}
+
+	val.Set(reflect.ValueOf(primitive.Null{}))
+	return vr.ReadNull()
+}
+
+// RegexDecodeValue is the ValueDecoderFunc for Regex.
+func (DefaultValueDecoders) RegexDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if !val.CanSet() || val.Type() != tRegex {
+		return ValueDecoderError{Name: "RegexDecodeValue", Types: []reflect.Type{tRegex}, Received: val}
+	}
+
+	if vr.Type() != bsontype.Regex {
+		return fmt.Errorf("cannot decode %v into a Regex", vr.Type())
+	}
+
+	pattern, options, err := vr.ReadRegex()
+	if err != nil {
+		return err
+	}
+
+	val.Set(reflect.ValueOf(primitive.Regex{Pattern: pattern, Options: options}))
+	return nil
+}
+
+// DBPointerDecodeValue is the ValueDecoderFunc for DBPointer.
+func (DefaultValueDecoders) DBPointerDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if !val.CanSet() || val.Type() != tDBPointer {
+		return ValueDecoderError{Name: "DBPointerDecodeValue", Types: []reflect.Type{tDBPointer}, Received: val}
+	}
+
+	if vr.Type() != bsontype.DBPointer {
+		return fmt.Errorf("cannot decode %v into a DBPointer", vr.Type())
+	}
+
+	ns, pointer, err := vr.ReadDBPointer()
+	if err != nil {
+		return err
+	}
+
+	val.Set(reflect.ValueOf(primitive.DBPointer{DB: ns, Pointer: pointer}))
+	return nil
+}
+
+// TimestampDecodeValue is the ValueDecoderFunc for Timestamp.
+func (DefaultValueDecoders) TimestampDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if !val.CanSet() || val.Type() != tTimestamp {
+		return ValueDecoderError{Name: "TimestampDecodeValue", Types: []reflect.Type{tTimestamp}, Received: val}
+	}
+
+	if vr.Type() != bsontype.Timestamp {
+		return fmt.Errorf("cannot decode %v into a Timestamp", vr.Type())
+	}
+
+	t, incr, err := vr.ReadTimestamp()
+	if err != nil {
+		return err
+	}
+
+	val.Set(reflect.ValueOf(primitive.Timestamp{T: t, I: incr}))
+	return nil
+}
+
+// MinKeyDecodeValue is the ValueDecoderFunc for MinKey.
+func (DefaultValueDecoders) MinKeyDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if !val.CanSet() || val.Type() != tMinKey {
+		return ValueDecoderError{Name: "MinKeyDecodeValue", Types: []reflect.Type{tMinKey}, Received: val}
+	}
+
+	if vr.Type() != bsontype.MinKey {
+		return fmt.Errorf("cannot decode %v into a MinKey", vr.Type())
+	}
+
+	val.Set(reflect.ValueOf(primitive.MinKey{}))
+	return vr.ReadMinKey()
+}
+
+// MaxKeyDecodeValue is the ValueDecoderFunc for MaxKey.
+func (DefaultValueDecoders) MaxKeyDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if !val.CanSet() || val.Type() != tMaxKey {
+		return ValueDecoderError{Name: "MaxKeyDecodeValue", Types: []reflect.Type{tMaxKey}, Received: val}
+	}
+
+	if vr.Type() != bsontype.MaxKey {
+		return fmt.Errorf("cannot decode %v into a MaxKey", vr.Type())
+	}
+
+	val.Set(reflect.ValueOf(primitive.MaxKey{}))
+	return vr.ReadMaxKey()
+}
+
+// Decimal128DecodeValue is the ValueDecoderFunc for primitive.Decimal128.
+func (dvd DefaultValueDecoders) Decimal128DecodeValue(dctx DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if vr.Type() != bsontype.Decimal128 {
+		return fmt.Errorf("cannot decode %v into a primitive.Decimal128", vr.Type())
+	}
+
+	if !val.CanSet() || val.Type() != tDecimal {
+		return ValueDecoderError{Name: "Decimal128DecodeValue", Types: []reflect.Type{tDecimal}, Received: val}
+	}
+	d128, err := vr.ReadDecimal128()
+	val.Set(reflect.ValueOf(d128))
+	return err
+}
+
+// JSONNumberDecodeValue is the ValueDecoderFunc for json.Number.
+func (dvd DefaultValueDecoders) JSONNumberDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if !val.CanSet() || val.Type() != tJSONNumber {
+		return ValueDecoderError{Name: "JSONNumberDecodeValue", Types: []reflect.Type{tJSONNumber}, Received: val}
+	}
+
+	switch vr.Type() {
+	case bsontype.Double:
+		f64, err := vr.ReadDouble()
+		if err != nil {
+			return err
+		}
+		val.Set(reflect.ValueOf(json.Number(strconv.FormatFloat(f64, 'g', -1, 64))))
+	case bsontype.Int32:
+		i32, err := vr.ReadInt32()
+		if err != nil {
+			return err
+		}
+		val.Set(reflect.ValueOf(json.Number(strconv.FormatInt(int64(i32), 10))))
+	case bsontype.Int64:
+		i64, err := vr.ReadInt64()
+		if err != nil {
+			return err
+		}
+		val.Set(reflect.ValueOf(json.Number(strconv.FormatInt(i64, 10))))
+	default:
+		return fmt.Errorf("cannot decode %v into a json.Number", vr.Type())
+	}
+
+	return nil
+}
+
+// URLDecodeValue is the ValueDecoderFunc for url.URL.
+func (dvd DefaultValueDecoders) URLDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if vr.Type() != bsontype.String {
+		return fmt.Errorf("cannot decode %v into a *url.URL", vr.Type())
+	}
+
+	str, err := vr.ReadString()
+	if err != nil {
+		return err
+	}
+
+	u, err := url.Parse(str)
+	if err != nil {
+		return err
+	}
+
+	if !val.CanSet() || val.Type() != tURL {
+		return ValueDecoderError{Name: "URLDecodeValue", Types: []reflect.Type{tURL}, Received: val}
+	}
+
+	val.Set(reflect.ValueOf(u).Elem())
+	return nil
+}
+
+// TimeDecodeValue is the ValueDecoderFunc for time.Time.
+func (dvd DefaultValueDecoders) TimeDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if vr.Type() != bsontype.DateTime {
+		return fmt.Errorf("cannot decode %v into a time.Time", vr.Type())
+	}
+
+	dt, err := vr.ReadDateTime()
+	if err != nil {
+		return err
+	}
+
+	if !val.CanSet() || val.Type() != tTime {
+		return ValueDecoderError{Name: "TimeDecodeValue", Types: []reflect.Type{tTime}, Received: val}
+	}
+
+	val.Set(reflect.ValueOf(time.Unix(dt/1000, dt%1000*1000000).UTC()))
+	return nil
+}
+
+// ByteSliceDecodeValue is the ValueDecoderFunc for []byte.
+func (dvd DefaultValueDecoders) ByteSliceDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if vr.Type() != bsontype.Binary && vr.Type() != bsontype.Null {
+		return fmt.Errorf("cannot decode %v into a []byte", vr.Type())
+	}
+
+	if !val.CanSet() || val.Type() != tByteSlice {
+		return ValueDecoderError{Name: "ByteSliceDecodeValue", Types: []reflect.Type{tByteSlice}, Received: val}
+	}
+
+	if vr.Type() == bsontype.Null {
+		val.Set(reflect.Zero(val.Type()))
+		return vr.ReadNull()
+	}
+
+	data, subtype, err := vr.ReadBinary()
+	if err != nil {
+		return err
+	}
+	if subtype != 0x00 {
+		return fmt.Errorf("ByteSliceDecodeValue can only be used to decode subtype 0x00 for %s, got %v", bsontype.Binary, subtype)
+	}
+
+	val.Set(reflect.ValueOf(data))
+	return nil
+}
+
+// MapDecodeValue is the ValueDecoderFunc for map[string]* types.
+func (dvd DefaultValueDecoders) MapDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if !val.CanSet() || val.Kind() != reflect.Map || val.Type().Key().Kind() != reflect.String {
+		return ValueDecoderError{Name: "MapDecodeValue", Kinds: []reflect.Kind{reflect.Map}, Received: val}
+	}
+
+	switch vr.Type() {
+	case bsontype.Type(0), bsontype.EmbeddedDocument:
+	case bsontype.Null:
+		val.Set(reflect.Zero(val.Type()))
+		return vr.ReadNull()
+	default:
+		return fmt.Errorf("cannot decode %v into a %s", vr.Type(), val.Type())
+	}
+
+	dr, err := vr.ReadDocument()
+	if err != nil {
+		return err
+	}
+
+	if val.IsNil() {
+		val.Set(reflect.MakeMap(val.Type()))
+	}
+
+	eType := val.Type().Elem()
+	decoder, err := dc.LookupDecoder(eType)
+	if err != nil {
+		return err
+	}
+
+	if eType == tEmpty {
+		dc.Ancestor = val.Type()
+	}
+
+	keyType := val.Type().Key()
+	for {
+		key, vr, err := dr.ReadElement()
+		if err == bsonrw.ErrEOD {
+			break
+		}
+		if err != nil {
+			return err
+		}
+
+		elem := reflect.New(eType).Elem()
+
+		err = decoder.DecodeValue(dc, vr, elem)
+		if err != nil {
+			return err
+		}
+
+		val.SetMapIndex(reflect.ValueOf(key).Convert(keyType), elem)
+	}
+	return nil
+}
+
+// ArrayDecodeValue is the ValueDecoderFunc for array types.
+func (dvd DefaultValueDecoders) ArrayDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if !val.IsValid() || val.Kind() != reflect.Array {
+		return ValueDecoderError{Name: "ArrayDecodeValue", Kinds: []reflect.Kind{reflect.Array}, Received: val}
+	}
+
+	switch vr.Type() {
+	case bsontype.Array:
+	case bsontype.Type(0), bsontype.EmbeddedDocument:
+		if val.Type().Elem() != tE {
+			return fmt.Errorf("cannot decode document into %s", val.Type())
+		}
+	default:
+		return fmt.Errorf("cannot decode %v into an array", vr.Type())
+	}
+
+	var elemsFunc func(DecodeContext, bsonrw.ValueReader, reflect.Value) ([]reflect.Value, error)
+	switch val.Type().Elem() {
+	case tE:
+		elemsFunc = dvd.decodeD
+	default:
+		elemsFunc = dvd.decodeDefault
+	}
+
+	elems, err := elemsFunc(dc, vr, val)
+	if err != nil {
+		return err
+	}
+
+	if len(elems) > val.Len() {
+		return fmt.Errorf("more elements returned in array than can fit inside %s", val.Type())
+	}
+
+	for idx, elem := range elems {
+		val.Index(idx).Set(elem)
+	}
+
+	return nil
+}
+
+// SliceDecodeValue is the ValueDecoderFunc for slice types.
+func (dvd DefaultValueDecoders) SliceDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if !val.CanSet() || val.Kind() != reflect.Slice {
+		return ValueDecoderError{Name: "SliceDecodeValue", Kinds: []reflect.Kind{reflect.Slice}, Received: val}
+	}
+
+	switch vr.Type() {
+	case bsontype.Array:
+	case bsontype.Null:
+		val.Set(reflect.Zero(val.Type()))
+		return vr.ReadNull()
+	case bsontype.Type(0), bsontype.EmbeddedDocument:
+		if val.Type().Elem() != tE {
+			return fmt.Errorf("cannot decode document into %s", val.Type())
+		}
+	default:
+		return fmt.Errorf("cannot decode %v into a slice", vr.Type())
+	}
+
+	var elemsFunc func(DecodeContext, bsonrw.ValueReader, reflect.Value) ([]reflect.Value, error)
+	switch val.Type().Elem() {
+	case tE:
+		dc.Ancestor = val.Type()
+		elemsFunc = dvd.decodeD
+	default:
+		elemsFunc = dvd.decodeDefault
+	}
+
+	elems, err := elemsFunc(dc, vr, val)
+	if err != nil {
+		return err
+	}
+
+	if val.IsNil() {
+		val.Set(reflect.MakeSlice(val.Type(), 0, len(elems)))
+	}
+
+	val.SetLen(0)
+	val.Set(reflect.Append(val, elems...))
+
+	return nil
+}
+
+// ValueUnmarshalerDecodeValue is the ValueDecoderFunc for ValueUnmarshaler implementations.
+func (dvd DefaultValueDecoders) ValueUnmarshalerDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if !val.IsValid() || (!val.Type().Implements(tValueUnmarshaler) && !reflect.PtrTo(val.Type()).Implements(tValueUnmarshaler)) {
+		return ValueDecoderError{Name: "ValueUnmarshalerDecodeValue", Types: []reflect.Type{tValueUnmarshaler}, Received: val}
+	}
+
+	if val.Kind() == reflect.Ptr && val.IsNil() {
+		if !val.CanSet() {
+			return ValueDecoderError{Name: "ValueUnmarshalerDecodeValue", Types: []reflect.Type{tValueUnmarshaler}, Received: val}
+		}
+		val.Set(reflect.New(val.Type().Elem()))
+	}
+
+	if !val.Type().Implements(tValueUnmarshaler) {
+		if !val.CanAddr() {
+			return ValueDecoderError{Name: "ValueUnmarshalerDecodeValue", Types: []reflect.Type{tValueUnmarshaler}, Received: val}
+		}
+		val = val.Addr() // If they type doesn't implement the interface, a pointer to it must.
+	}
+
+	t, src, err := bsonrw.Copier{}.CopyValueToBytes(vr)
+	if err != nil {
+		return err
+	}
+
+	fn := val.Convert(tValueUnmarshaler).MethodByName("UnmarshalBSONValue")
+	errVal := fn.Call([]reflect.Value{reflect.ValueOf(t), reflect.ValueOf(src)})[0]
+	if !errVal.IsNil() {
+		return errVal.Interface().(error)
+	}
+	return nil
+}
+
+// UnmarshalerDecodeValue is the ValueDecoderFunc for Unmarshaler implementations.
+func (dvd DefaultValueDecoders) UnmarshalerDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if !val.IsValid() || (!val.Type().Implements(tUnmarshaler) && !reflect.PtrTo(val.Type()).Implements(tUnmarshaler)) {
+		return ValueDecoderError{Name: "UnmarshalerDecodeValue", Types: []reflect.Type{tUnmarshaler}, Received: val}
+	}
+
+	if val.Kind() == reflect.Ptr && val.IsNil() {
+		if !val.CanSet() {
+			return ValueDecoderError{Name: "UnmarshalerDecodeValue", Types: []reflect.Type{tUnmarshaler}, Received: val}
+		}
+		val.Set(reflect.New(val.Type().Elem()))
+	}
+
+	if !val.Type().Implements(tUnmarshaler) {
+		if !val.CanAddr() {
+			return ValueDecoderError{Name: "UnmarshalerDecodeValue", Types: []reflect.Type{tUnmarshaler}, Received: val}
+		}
+		val = val.Addr() // If they type doesn't implement the interface, a pointer to it must.
+	}
+
+	_, src, err := bsonrw.Copier{}.CopyValueToBytes(vr)
+	if err != nil {
+		return err
+	}
+
+	fn := val.Convert(tUnmarshaler).MethodByName("UnmarshalBSON")
+	errVal := fn.Call([]reflect.Value{reflect.ValueOf(src)})[0]
+	if !errVal.IsNil() {
+		return errVal.Interface().(error)
+	}
+	return nil
+}
+
+// EmptyInterfaceDecodeValue is the ValueDecoderFunc for interface{}.
+func (dvd DefaultValueDecoders) EmptyInterfaceDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if !val.CanSet() || val.Type() != tEmpty {
+		return ValueDecoderError{Name: "EmptyInterfaceDecodeValue", Types: []reflect.Type{tEmpty}, Received: val}
+	}
+
+	rtype, err := dc.LookupTypeMapEntry(vr.Type())
+	if err != nil {
+		switch vr.Type() {
+		case bsontype.EmbeddedDocument:
+			if dc.Ancestor != nil {
+				rtype = dc.Ancestor
+				break
+			}
+			rtype = tD
+		case bsontype.Null:
+			val.Set(reflect.Zero(val.Type()))
+			return vr.ReadNull()
+		default:
+			return err
+		}
+	}
+
+	decoder, err := dc.LookupDecoder(rtype)
+	if err != nil {
+		return err
+	}
+
+	elem := reflect.New(rtype).Elem()
+	err = decoder.DecodeValue(dc, vr, elem)
+	if err != nil {
+		return err
+	}
+
+	val.Set(elem)
+	return nil
+}
+
+// CoreDocumentDecodeValue is the ValueDecoderFunc for bsoncore.Document.
+func (DefaultValueDecoders) CoreDocumentDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if !val.CanSet() || val.Type() != tCoreDocument {
+		return ValueDecoderError{Name: "CoreDocumentDecodeValue", Types: []reflect.Type{tCoreDocument}, Received: val}
+	}
+
+	if val.IsNil() {
+		val.Set(reflect.MakeSlice(val.Type(), 0, 0))
+	}
+
+	val.SetLen(0)
+
+	cdoc, err := bsonrw.Copier{}.AppendDocumentBytes(val.Interface().(bsoncore.Document), vr)
+	val.Set(reflect.ValueOf(cdoc))
+	return err
+}
+
+func (dvd DefaultValueDecoders) decodeDefault(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) ([]reflect.Value, error) {
+	elems := make([]reflect.Value, 0)
+
+	ar, err := vr.ReadArray()
+	if err != nil {
+		return nil, err
+	}
+
+	eType := val.Type().Elem()
+
+	decoder, err := dc.LookupDecoder(eType)
+	if err != nil {
+		return nil, err
+	}
+
+	for {
+		vr, err := ar.ReadValue()
+		if err == bsonrw.ErrEOA {
+			break
+		}
+		if err != nil {
+			return nil, err
+		}
+
+		elem := reflect.New(eType).Elem()
+
+		err = decoder.DecodeValue(dc, vr, elem)
+		if err != nil {
+			return nil, err
+		}
+		elems = append(elems, elem)
+	}
+
+	return elems, nil
+}
+
+// CodeWithScopeDecodeValue is the ValueDecoderFunc for CodeWithScope.
+func (dvd DefaultValueDecoders) CodeWithScopeDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if !val.CanSet() || val.Type() != tCodeWithScope {
+		return ValueDecoderError{Name: "CodeWithScopeDecodeValue", Types: []reflect.Type{tCodeWithScope}, Received: val}
+	}
+
+	if vr.Type() != bsontype.CodeWithScope {
+		return fmt.Errorf("cannot decode %v into a primitive.CodeWithScope", vr.Type())
+	}
+
+	code, dr, err := vr.ReadCodeWithScope()
+	if err != nil {
+		return err
+	}
+
+	scope := reflect.New(tD).Elem()
+
+	elems, err := dvd.decodeElemsFromDocumentReader(dc, dr)
+	if err != nil {
+		return err
+	}
+
+	scope.Set(reflect.MakeSlice(tD, 0, len(elems)))
+	scope.Set(reflect.Append(scope, elems...))
+
+	val.Set(reflect.ValueOf(primitive.CodeWithScope{Code: primitive.JavaScript(code), Scope: scope.Interface().(primitive.D)}))
+	return nil
+}
+
+func (dvd DefaultValueDecoders) decodeD(dc DecodeContext, vr bsonrw.ValueReader, _ reflect.Value) ([]reflect.Value, error) {
+	switch vr.Type() {
+	case bsontype.Type(0), bsontype.EmbeddedDocument:
+	default:
+		return nil, fmt.Errorf("cannot decode %v into a D", vr.Type())
+	}
+
+	dr, err := vr.ReadDocument()
+	if err != nil {
+		return nil, err
+	}
+
+	return dvd.decodeElemsFromDocumentReader(dc, dr)
+}
+
+func (DefaultValueDecoders) decodeElemsFromDocumentReader(dc DecodeContext, dr bsonrw.DocumentReader) ([]reflect.Value, error) {
+	decoder, err := dc.LookupDecoder(tEmpty)
+	if err != nil {
+		return nil, err
+	}
+
+	elems := make([]reflect.Value, 0)
+	for {
+		key, vr, err := dr.ReadElement()
+		if err == bsonrw.ErrEOD {
+			break
+		}
+		if err != nil {
+			return nil, err
+		}
+
+		val := reflect.New(tEmpty).Elem()
+		err = decoder.DecodeValue(dc, vr, val)
+		if err != nil {
+			return nil, err
+		}
+
+		elems = append(elems, reflect.ValueOf(primitive.E{Key: key, Value: val.Interface()}))
+	}
+
+	return elems, nil
+}

+ 2870 - 0
src/go.mongodb.org/mongo-driver/bson/bsoncodec/default_value_decoders_test.go

@@ -0,0 +1,2870 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+package bsoncodec
+
+import (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"math"
+	"net/url"
+	"reflect"
+	"testing"
+	"time"
+
+	"github.com/google/go-cmp/cmp"
+	"go.mongodb.org/mongo-driver/bson/bsonrw"
+	"go.mongodb.org/mongo-driver/bson/bsonrw/bsonrwtest"
+	"go.mongodb.org/mongo-driver/bson/bsontype"
+	"go.mongodb.org/mongo-driver/bson/primitive"
+	"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
+)
+
+func TestDefaultValueDecoders(t *testing.T) {
+	var dvd DefaultValueDecoders
+	var wrong = func(string, string) string { return "wrong" }
+
+	type mybool bool
+	type myint8 int8
+	type myint16 int16
+	type myint32 int32
+	type myint64 int64
+	type myint int
+	type myuint8 uint8
+	type myuint16 uint16
+	type myuint32 uint32
+	type myuint64 uint64
+	type myuint uint
+	type myfloat32 float32
+	type myfloat64 float64
+	type mystring string
+
+	const cansetreflectiontest = "cansetreflectiontest"
+	const cansettest = "cansettest"
+
+	now := time.Now().Truncate(time.Millisecond)
+	d128 := primitive.NewDecimal128(12345, 67890)
+	var pbool = func(b bool) *bool { return &b }
+	var pi32 = func(i32 int32) *int32 { return &i32 }
+	var pi64 = func(i64 int64) *int64 { return &i64 }
+
+	type subtest struct {
+		name   string
+		val    interface{}
+		dctx   *DecodeContext
+		llvrw  *bsonrwtest.ValueReaderWriter
+		invoke bsonrwtest.Invoked
+		err    error
+	}
+
+	testCases := []struct {
+		name     string
+		vd       ValueDecoder
+		subtests []subtest
+	}{
+		{
+			"BooleanDecodeValue",
+			ValueDecoderFunc(dvd.BooleanDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Boolean},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "BooleanDecodeValue", Kinds: []reflect.Kind{reflect.Bool}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"type not boolean",
+					bool(false),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					fmt.Errorf("cannot decode %v into a boolean", bsontype.String),
+				},
+				{
+					"fast path",
+					bool(true),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Boolean, Return: bool(true)},
+					bsonrwtest.ReadBoolean,
+					nil,
+				},
+				{
+					"reflection path",
+					mybool(true),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Boolean, Return: bool(true)},
+					bsonrwtest.ReadBoolean,
+					nil,
+				},
+				{
+					"reflection path error",
+					mybool(true),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Boolean, Return: bool(true), Err: errors.New("ReadBoolean Error"), ErrAfter: bsonrwtest.ReadBoolean},
+					bsonrwtest.ReadBoolean, errors.New("ReadBoolean Error"),
+				},
+				{
+					"can set false",
+					cansettest,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Boolean},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "BooleanDecodeValue", Kinds: []reflect.Kind{reflect.Bool}},
+				},
+			},
+		},
+		{
+			"IntDecodeValue",
+			ValueDecoderFunc(dvd.IntDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(0)},
+					bsonrwtest.ReadInt32,
+					ValueDecoderError{
+						Name:     "IntDecodeValue",
+						Kinds:    []reflect.Kind{reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int},
+						Received: reflect.ValueOf(wrong),
+					},
+				},
+				{
+					"type not int32/int64",
+					0,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					fmt.Errorf("cannot decode %v into an integer type", bsontype.String),
+				},
+				{
+					"ReadInt32 error",
+					0,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(0), Err: errors.New("ReadInt32 error"), ErrAfter: bsonrwtest.ReadInt32},
+					bsonrwtest.ReadInt32,
+					errors.New("ReadInt32 error"),
+				},
+				{
+					"ReadInt64 error",
+					0,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int64, Return: int64(0), Err: errors.New("ReadInt64 error"), ErrAfter: bsonrwtest.ReadInt64},
+					bsonrwtest.ReadInt64,
+					errors.New("ReadInt64 error"),
+				},
+				{
+					"ReadDouble error",
+					0,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(0), Err: errors.New("ReadDouble error"), ErrAfter: bsonrwtest.ReadDouble},
+					bsonrwtest.ReadDouble,
+					errors.New("ReadDouble error"),
+				},
+				{
+					"ReadDouble", int64(3), &DecodeContext{},
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(3.00)}, bsonrwtest.ReadDouble,
+					nil,
+				},
+				{
+					"ReadDouble (truncate)", int64(3), &DecodeContext{Truncate: true},
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(3.14)}, bsonrwtest.ReadDouble,
+					nil,
+				},
+				{
+					"ReadDouble (no truncate)", int64(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(3.14)}, bsonrwtest.ReadDouble,
+					errors.New("IntDecodeValue can only truncate float64 to an integer type when truncation is enabled"),
+				},
+				{
+					"ReadDouble overflows int64", int64(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: math.MaxFloat64}, bsonrwtest.ReadDouble,
+					fmt.Errorf("%g overflows int64", math.MaxFloat64),
+				},
+				{"int8/fast path", int8(127), nil, &bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(127)}, bsonrwtest.ReadInt32, nil},
+				{"int16/fast path", int16(32676), nil, &bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(32676)}, bsonrwtest.ReadInt32, nil},
+				{"int32/fast path", int32(1234), nil, &bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(1234)}, bsonrwtest.ReadInt32, nil},
+				{"int64/fast path", int64(1234), nil, &bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int64, Return: int64(1234)}, bsonrwtest.ReadInt64, nil},
+				{"int/fast path", int(1234), nil, &bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int64, Return: int64(1234)}, bsonrwtest.ReadInt64, nil},
+				{
+					"int8/fast path - nil", (*int8)(nil), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(0)}, bsonrwtest.ReadInt32,
+					ValueDecoderError{
+						Name:     "IntDecodeValue",
+						Kinds:    []reflect.Kind{reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int},
+						Received: reflect.ValueOf((*int8)(nil)),
+					},
+				},
+				{
+					"int16/fast path - nil", (*int16)(nil), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(0)}, bsonrwtest.ReadInt32,
+					ValueDecoderError{
+						Name:     "IntDecodeValue",
+						Kinds:    []reflect.Kind{reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int},
+						Received: reflect.ValueOf((*int16)(nil)),
+					},
+				},
+				{
+					"int32/fast path - nil", (*int32)(nil), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(0)}, bsonrwtest.ReadInt32,
+					ValueDecoderError{
+						Name:     "IntDecodeValue",
+						Kinds:    []reflect.Kind{reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int},
+						Received: reflect.ValueOf((*int32)(nil)),
+					},
+				},
+				{
+					"int64/fast path - nil", (*int64)(nil), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(0)}, bsonrwtest.ReadInt32,
+					ValueDecoderError{
+						Name:     "IntDecodeValue",
+						Kinds:    []reflect.Kind{reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int},
+						Received: reflect.ValueOf((*int64)(nil)),
+					},
+				},
+				{
+					"int/fast path - nil", (*int)(nil), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(0)}, bsonrwtest.ReadInt32,
+					ValueDecoderError{
+						Name:     "IntDecodeValue",
+						Kinds:    []reflect.Kind{reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int},
+						Received: reflect.ValueOf((*int)(nil)),
+					},
+				},
+				{
+					"int8/fast path - overflow", int8(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(129)}, bsonrwtest.ReadInt32,
+					fmt.Errorf("%d overflows int8", 129),
+				},
+				{
+					"int16/fast path - overflow", int16(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(32768)}, bsonrwtest.ReadInt32,
+					fmt.Errorf("%d overflows int16", 32768),
+				},
+				{
+					"int32/fast path - overflow", int32(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int64, Return: int64(2147483648)}, bsonrwtest.ReadInt64,
+					fmt.Errorf("%d overflows int32", 2147483648),
+				},
+				{
+					"int8/fast path - overflow (negative)", int8(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(-129)}, bsonrwtest.ReadInt32,
+					fmt.Errorf("%d overflows int8", -129),
+				},
+				{
+					"int16/fast path - overflow (negative)", int16(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(-32769)}, bsonrwtest.ReadInt32,
+					fmt.Errorf("%d overflows int16", -32769),
+				},
+				{
+					"int32/fast path - overflow (negative)", int32(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int64, Return: int64(-2147483649)}, bsonrwtest.ReadInt64,
+					fmt.Errorf("%d overflows int32", -2147483649),
+				},
+				{
+					"int8/reflection path", myint8(127), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(127)}, bsonrwtest.ReadInt32,
+					nil,
+				},
+				{
+					"int16/reflection path", myint16(255), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(255)}, bsonrwtest.ReadInt32,
+					nil,
+				},
+				{
+					"int32/reflection path", myint32(511), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(511)}, bsonrwtest.ReadInt32,
+					nil,
+				},
+				{
+					"int64/reflection path", myint64(1023), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(1023)}, bsonrwtest.ReadInt32,
+					nil,
+				},
+				{
+					"int/reflection path", myint(2047), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(2047)}, bsonrwtest.ReadInt32,
+					nil,
+				},
+				{
+					"int8/reflection path - overflow", myint8(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(129)}, bsonrwtest.ReadInt32,
+					fmt.Errorf("%d overflows int8", 129),
+				},
+				{
+					"int16/reflection path - overflow", myint16(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(32768)}, bsonrwtest.ReadInt32,
+					fmt.Errorf("%d overflows int16", 32768),
+				},
+				{
+					"int32/reflection path - overflow", myint32(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int64, Return: int64(2147483648)}, bsonrwtest.ReadInt64,
+					fmt.Errorf("%d overflows int32", 2147483648),
+				},
+				{
+					"int8/reflection path - overflow (negative)", myint8(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(-129)}, bsonrwtest.ReadInt32,
+					fmt.Errorf("%d overflows int8", -129),
+				},
+				{
+					"int16/reflection path - overflow (negative)", myint16(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(-32769)}, bsonrwtest.ReadInt32,
+					fmt.Errorf("%d overflows int16", -32769),
+				},
+				{
+					"int32/reflection path - overflow (negative)", myint32(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int64, Return: int64(-2147483649)}, bsonrwtest.ReadInt64,
+					fmt.Errorf("%d overflows int32", -2147483649),
+				},
+				{
+					"can set false",
+					cansettest,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(0)},
+					bsonrwtest.Nothing,
+					ValueDecoderError{
+						Name:  "IntDecodeValue",
+						Kinds: []reflect.Kind{reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int},
+					},
+				},
+			},
+		},
+		{
+			"UintDecodeValue",
+			ValueDecoderFunc(dvd.UintDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(0)},
+					bsonrwtest.ReadInt32,
+					ValueDecoderError{
+						Name:     "UintDecodeValue",
+						Kinds:    []reflect.Kind{reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint},
+						Received: reflect.ValueOf(wrong),
+					},
+				},
+				{
+					"type not int32/int64",
+					0,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					fmt.Errorf("cannot decode %v into an integer type", bsontype.String),
+				},
+				{
+					"ReadInt32 error",
+					uint(0),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(0), Err: errors.New("ReadInt32 error"), ErrAfter: bsonrwtest.ReadInt32},
+					bsonrwtest.ReadInt32,
+					errors.New("ReadInt32 error"),
+				},
+				{
+					"ReadInt64 error",
+					uint(0),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int64, Return: int64(0), Err: errors.New("ReadInt64 error"), ErrAfter: bsonrwtest.ReadInt64},
+					bsonrwtest.ReadInt64,
+					errors.New("ReadInt64 error"),
+				},
+				{
+					"ReadDouble error",
+					0,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(0), Err: errors.New("ReadDouble error"), ErrAfter: bsonrwtest.ReadDouble},
+					bsonrwtest.ReadDouble,
+					errors.New("ReadDouble error"),
+				},
+				{
+					"ReadDouble", uint64(3), &DecodeContext{},
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(3.00)}, bsonrwtest.ReadDouble,
+					nil,
+				},
+				{
+					"ReadDouble (truncate)", uint64(3), &DecodeContext{Truncate: true},
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(3.14)}, bsonrwtest.ReadDouble,
+					nil,
+				},
+				{
+					"ReadDouble (no truncate)", uint64(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(3.14)}, bsonrwtest.ReadDouble,
+					errors.New("UintDecodeValue can only truncate float64 to an integer type when truncation is enabled"),
+				},
+				{
+					"ReadDouble overflows int64", uint64(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: math.MaxFloat64}, bsonrwtest.ReadDouble,
+					fmt.Errorf("%g overflows int64", math.MaxFloat64),
+				},
+				{"uint8/fast path", uint8(127), nil, &bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(127)}, bsonrwtest.ReadInt32, nil},
+				{"uint16/fast path", uint16(255), nil, &bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(255)}, bsonrwtest.ReadInt32, nil},
+				{"uint32/fast path", uint32(1234), nil, &bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(1234)}, bsonrwtest.ReadInt32, nil},
+				{"uint64/fast path", uint64(1234), nil, &bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int64, Return: int64(1234)}, bsonrwtest.ReadInt64, nil},
+				{"uint/fast path", uint(1234), nil, &bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int64, Return: int64(1234)}, bsonrwtest.ReadInt64, nil},
+				{
+					"uint8/fast path - nil", (*uint8)(nil), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(0)}, bsonrwtest.ReadInt32,
+					ValueDecoderError{
+						Name:     "UintDecodeValue",
+						Kinds:    []reflect.Kind{reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint},
+						Received: reflect.ValueOf((*uint8)(nil)),
+					},
+				},
+				{
+					"uint16/fast path - nil", (*uint16)(nil), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(0)}, bsonrwtest.ReadInt32,
+					ValueDecoderError{
+						Name:     "UintDecodeValue",
+						Kinds:    []reflect.Kind{reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint},
+						Received: reflect.ValueOf((*uint16)(nil)),
+					},
+				},
+				{
+					"uint32/fast path - nil", (*uint32)(nil), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(0)}, bsonrwtest.ReadInt32,
+					ValueDecoderError{
+						Name:     "UintDecodeValue",
+						Kinds:    []reflect.Kind{reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint},
+						Received: reflect.ValueOf((*uint32)(nil)),
+					},
+				},
+				{
+					"uint64/fast path - nil", (*uint64)(nil), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(0)}, bsonrwtest.ReadInt32,
+					ValueDecoderError{
+						Name:     "UintDecodeValue",
+						Kinds:    []reflect.Kind{reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint},
+						Received: reflect.ValueOf((*uint64)(nil)),
+					},
+				},
+				{
+					"uint/fast path - nil", (*uint)(nil), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(0)}, bsonrwtest.ReadInt32,
+					ValueDecoderError{
+						Name:     "UintDecodeValue",
+						Kinds:    []reflect.Kind{reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint},
+						Received: reflect.ValueOf((*uint)(nil)),
+					},
+				},
+				{
+					"uint8/fast path - overflow", uint8(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(1 << 8)}, bsonrwtest.ReadInt32,
+					fmt.Errorf("%d overflows uint8", 1<<8),
+				},
+				{
+					"uint16/fast path - overflow", uint16(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(1 << 16)}, bsonrwtest.ReadInt32,
+					fmt.Errorf("%d overflows uint16", 1<<16),
+				},
+				{
+					"uint32/fast path - overflow", uint32(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int64, Return: int64(1 << 32)}, bsonrwtest.ReadInt64,
+					fmt.Errorf("%d overflows uint32", 1<<32),
+				},
+				{
+					"uint8/fast path - overflow (negative)", uint8(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(-1)}, bsonrwtest.ReadInt32,
+					fmt.Errorf("%d overflows uint8", -1),
+				},
+				{
+					"uint16/fast path - overflow (negative)", uint16(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(-1)}, bsonrwtest.ReadInt32,
+					fmt.Errorf("%d overflows uint16", -1),
+				},
+				{
+					"uint32/fast path - overflow (negative)", uint32(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int64, Return: int64(-1)}, bsonrwtest.ReadInt64,
+					fmt.Errorf("%d overflows uint32", -1),
+				},
+				{
+					"uint64/fast path - overflow (negative)", uint64(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int64, Return: int64(-1)}, bsonrwtest.ReadInt64,
+					fmt.Errorf("%d overflows uint64", -1),
+				},
+				{
+					"uint/fast path - overflow (negative)", uint(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int64, Return: int64(-1)}, bsonrwtest.ReadInt64,
+					fmt.Errorf("%d overflows uint", -1),
+				},
+				{
+					"uint8/reflection path", myuint8(127), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(127)}, bsonrwtest.ReadInt32,
+					nil,
+				},
+				{
+					"uint16/reflection path", myuint16(255), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(255)}, bsonrwtest.ReadInt32,
+					nil,
+				},
+				{
+					"uint32/reflection path", myuint32(511), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(511)}, bsonrwtest.ReadInt32,
+					nil,
+				},
+				{
+					"uint64/reflection path", myuint64(1023), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(1023)}, bsonrwtest.ReadInt32,
+					nil,
+				},
+				{
+					"uint/reflection path", myuint(2047), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(2047)}, bsonrwtest.ReadInt32,
+					nil,
+				},
+				{
+					"uint8/reflection path - overflow", myuint8(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(1 << 8)}, bsonrwtest.ReadInt32,
+					fmt.Errorf("%d overflows uint8", 1<<8),
+				},
+				{
+					"uint16/reflection path - overflow", myuint16(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(1 << 16)}, bsonrwtest.ReadInt32,
+					fmt.Errorf("%d overflows uint16", 1<<16),
+				},
+				{
+					"uint32/reflection path - overflow", myuint32(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int64, Return: int64(1 << 32)}, bsonrwtest.ReadInt64,
+					fmt.Errorf("%d overflows uint32", 1<<32),
+				},
+				{
+					"uint8/reflection path - overflow (negative)", myuint8(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(-1)}, bsonrwtest.ReadInt32,
+					fmt.Errorf("%d overflows uint8", -1),
+				},
+				{
+					"uint16/reflection path - overflow (negative)", myuint16(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(-1)}, bsonrwtest.ReadInt32,
+					fmt.Errorf("%d overflows uint16", -1),
+				},
+				{
+					"uint32/reflection path - overflow (negative)", myuint32(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int64, Return: int64(-1)}, bsonrwtest.ReadInt64,
+					fmt.Errorf("%d overflows uint32", -1),
+				},
+				{
+					"uint64/reflection path - overflow (negative)", myuint64(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int64, Return: int64(-1)}, bsonrwtest.ReadInt64,
+					fmt.Errorf("%d overflows uint64", -1),
+				},
+				{
+					"uint/reflection path - overflow (negative)", myuint(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int64, Return: int64(-1)}, bsonrwtest.ReadInt64,
+					fmt.Errorf("%d overflows uint", -1),
+				},
+				{
+					"can set false",
+					cansettest,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(0)},
+					bsonrwtest.Nothing,
+					ValueDecoderError{
+						Name:  "UintDecodeValue",
+						Kinds: []reflect.Kind{reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint},
+					},
+				},
+			},
+		},
+		{
+			"FloatDecodeValue",
+			ValueDecoderFunc(dvd.FloatDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(0)},
+					bsonrwtest.ReadDouble,
+					ValueDecoderError{
+						Name:     "FloatDecodeValue",
+						Kinds:    []reflect.Kind{reflect.Float32, reflect.Float64},
+						Received: reflect.ValueOf(wrong),
+					},
+				},
+				{
+					"type not double",
+					0,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					fmt.Errorf("cannot decode %v into a float32 or float64 type", bsontype.String),
+				},
+				{
+					"ReadDouble error",
+					float64(0),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(0), Err: errors.New("ReadDouble error"), ErrAfter: bsonrwtest.ReadDouble},
+					bsonrwtest.ReadDouble,
+					errors.New("ReadDouble error"),
+				},
+				{
+					"ReadInt32 error",
+					float64(0),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(0), Err: errors.New("ReadInt32 error"), ErrAfter: bsonrwtest.ReadInt32},
+					bsonrwtest.ReadInt32,
+					errors.New("ReadInt32 error"),
+				},
+				{
+					"ReadInt64 error",
+					float64(0),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int64, Return: int64(0), Err: errors.New("ReadInt64 error"), ErrAfter: bsonrwtest.ReadInt64},
+					bsonrwtest.ReadInt64,
+					errors.New("ReadInt64 error"),
+				},
+				{
+					"float64/int32", float32(32.0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(32)}, bsonrwtest.ReadInt32,
+					nil,
+				},
+				{
+					"float64/int64", float32(64.0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int64, Return: int64(64)}, bsonrwtest.ReadInt64,
+					nil,
+				},
+				{
+					"float32/fast path (equal)", float32(3.0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(3.0)}, bsonrwtest.ReadDouble,
+					nil,
+				},
+				{
+					"float64/fast path", float64(3.14159), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(3.14159)}, bsonrwtest.ReadDouble,
+					nil,
+				},
+				{
+					"float32/fast path (truncate)", float32(3.14), &DecodeContext{Truncate: true},
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(3.14)}, bsonrwtest.ReadDouble,
+					nil,
+				},
+				{
+					"float32/fast path (no truncate)", float32(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(3.14)}, bsonrwtest.ReadDouble,
+					errors.New("FloatDecodeValue can only convert float64 to float32 when truncation is allowed"),
+				},
+				{
+					"float32/fast path - nil", (*float32)(nil), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(0)}, bsonrwtest.ReadDouble,
+					ValueDecoderError{
+						Name:     "FloatDecodeValue",
+						Kinds:    []reflect.Kind{reflect.Float32, reflect.Float64},
+						Received: reflect.ValueOf((*float32)(nil)),
+					},
+				},
+				{
+					"float64/fast path - nil", (*float64)(nil), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(0)}, bsonrwtest.ReadDouble,
+					ValueDecoderError{
+						Name:     "FloatDecodeValue",
+						Kinds:    []reflect.Kind{reflect.Float32, reflect.Float64},
+						Received: reflect.ValueOf((*float64)(nil)),
+					},
+				},
+				{
+					"float32/reflection path (equal)", myfloat32(3.0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(3.0)}, bsonrwtest.ReadDouble,
+					nil,
+				},
+				{
+					"float64/reflection path", myfloat64(3.14159), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(3.14159)}, bsonrwtest.ReadDouble,
+					nil,
+				},
+				{
+					"float32/reflection path (truncate)", myfloat32(3.14), &DecodeContext{Truncate: true},
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(3.14)}, bsonrwtest.ReadDouble,
+					nil,
+				},
+				{
+					"float32/reflection path (no truncate)", myfloat32(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(3.14)}, bsonrwtest.ReadDouble,
+					errors.New("FloatDecodeValue can only convert float64 to float32 when truncation is allowed"),
+				},
+				{
+					"can set false",
+					cansettest,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(0)},
+					bsonrwtest.Nothing,
+					ValueDecoderError{
+						Name:  "FloatDecodeValue",
+						Kinds: []reflect.Kind{reflect.Float32, reflect.Float64},
+					},
+				},
+			},
+		},
+		{
+			"TimeDecodeValue",
+			ValueDecoderFunc(dvd.TimeDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.DateTime, Return: int64(0)},
+					bsonrwtest.ReadDateTime,
+					ValueDecoderError{Name: "TimeDecodeValue", Types: []reflect.Type{tTime}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"ReadDateTime error",
+					time.Time{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.DateTime, Return: int64(0), Err: errors.New("ReadDateTime error"), ErrAfter: bsonrwtest.ReadDateTime},
+					bsonrwtest.ReadDateTime,
+					errors.New("ReadDateTime error"),
+				},
+				{
+					"time.Time",
+					now,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.DateTime, Return: int64(now.UnixNano() / int64(time.Millisecond))},
+					bsonrwtest.ReadDateTime,
+					nil,
+				},
+				{
+					"can set false",
+					cansettest,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.DateTime, Return: int64(0)},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "TimeDecodeValue", Types: []reflect.Type{tTime}},
+				},
+			},
+		},
+		{
+			"MapDecodeValue",
+			ValueDecoderFunc(dvd.MapDecodeValue),
+			[]subtest{
+				{
+					"wrong kind",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "MapDecodeValue", Kinds: []reflect.Kind{reflect.Map}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"wrong kind (non-string key)",
+					map[int]interface{}{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{},
+					bsonrwtest.Nothing,
+					ValueDecoderError{
+						Name:     "MapDecodeValue",
+						Kinds:    []reflect.Kind{reflect.Map},
+						Received: reflect.ValueOf(map[int]interface{}{}),
+					},
+				},
+				{
+					"ReadDocument Error",
+					make(map[string]interface{}),
+					nil,
+					&bsonrwtest.ValueReaderWriter{Err: errors.New("rd error"), ErrAfter: bsonrwtest.ReadDocument},
+					bsonrwtest.ReadDocument,
+					errors.New("rd error"),
+				},
+				{
+					"Lookup Error",
+					map[string]string{},
+					&DecodeContext{Registry: NewRegistryBuilder().Build()},
+					&bsonrwtest.ValueReaderWriter{},
+					bsonrwtest.ReadDocument,
+					ErrNoDecoder{Type: reflect.TypeOf(string(""))},
+				},
+				{
+					"ReadElement Error",
+					make(map[string]interface{}),
+					&DecodeContext{Registry: buildDefaultRegistry()},
+					&bsonrwtest.ValueReaderWriter{Err: errors.New("re error"), ErrAfter: bsonrwtest.ReadElement},
+					bsonrwtest.ReadElement,
+					errors.New("re error"),
+				},
+				{
+					"can set false",
+					cansettest,
+					nil,
+					&bsonrwtest.ValueReaderWriter{},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "MapDecodeValue", Kinds: []reflect.Kind{reflect.Map}},
+				},
+				{
+					"wrong BSON type",
+					map[string]interface{}{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					errors.New("cannot decode string into a map[string]interface {}"),
+				},
+			},
+		},
+		{
+			"ArrayDecodeValue",
+			ValueDecoderFunc(dvd.ArrayDecodeValue),
+			[]subtest{
+				{
+					"wrong kind",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "ArrayDecodeValue", Kinds: []reflect.Kind{reflect.Array}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"can set false",
+					cansettest,
+					nil,
+					&bsonrwtest.ValueReaderWriter{},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "ArrayDecodeValue", Kinds: []reflect.Kind{reflect.Array}},
+				},
+				{
+					"Not Type Array",
+					[1]interface{}{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					errors.New("cannot decode string into an array"),
+				},
+				{
+					"ReadArray Error",
+					[1]interface{}{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{Err: errors.New("ra error"), ErrAfter: bsonrwtest.ReadArray, BSONType: bsontype.Array},
+					bsonrwtest.ReadArray,
+					errors.New("ra error"),
+				},
+				{
+					"Lookup Error",
+					[1]string{},
+					&DecodeContext{Registry: NewRegistryBuilder().Build()},
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Array},
+					bsonrwtest.ReadArray,
+					ErrNoDecoder{Type: reflect.TypeOf(string(""))},
+				},
+				{
+					"ReadValue Error",
+					[1]string{},
+					&DecodeContext{Registry: buildDefaultRegistry()},
+					&bsonrwtest.ValueReaderWriter{Err: errors.New("rv error"), ErrAfter: bsonrwtest.ReadValue, BSONType: bsontype.Array},
+					bsonrwtest.ReadValue,
+					errors.New("rv error"),
+				},
+				{
+					"DecodeValue Error",
+					[1]string{},
+					&DecodeContext{Registry: buildDefaultRegistry()},
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Array},
+					bsonrwtest.ReadValue,
+					errors.New("cannot decode array into a string type"),
+				},
+				{
+					"Document but not D",
+					[1]string{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Type(0)},
+					bsonrwtest.Nothing,
+					errors.New("cannot decode document into [1]string"),
+				},
+				{
+					"EmbeddedDocument but not D",
+					[1]string{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.EmbeddedDocument},
+					bsonrwtest.Nothing,
+					errors.New("cannot decode document into [1]string"),
+				},
+			},
+		},
+		{
+			"SliceDecodeValue",
+			ValueDecoderFunc(dvd.SliceDecodeValue),
+			[]subtest{
+				{
+					"wrong kind",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "SliceDecodeValue", Kinds: []reflect.Kind{reflect.Slice}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"can set false",
+					cansettest,
+					nil,
+					&bsonrwtest.ValueReaderWriter{},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "SliceDecodeValue", Kinds: []reflect.Kind{reflect.Slice}},
+				},
+				{
+					"Not Type Array",
+					[]interface{}{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					errors.New("cannot decode string into a slice"),
+				},
+				{
+					"ReadArray Error",
+					[]interface{}{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{Err: errors.New("ra error"), ErrAfter: bsonrwtest.ReadArray, BSONType: bsontype.Array},
+					bsonrwtest.ReadArray,
+					errors.New("ra error"),
+				},
+				{
+					"Lookup Error",
+					[]string{},
+					&DecodeContext{Registry: NewRegistryBuilder().Build()},
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Array},
+					bsonrwtest.ReadArray,
+					ErrNoDecoder{Type: reflect.TypeOf(string(""))},
+				},
+				{
+					"ReadValue Error",
+					[]string{},
+					&DecodeContext{Registry: buildDefaultRegistry()},
+					&bsonrwtest.ValueReaderWriter{Err: errors.New("rv error"), ErrAfter: bsonrwtest.ReadValue, BSONType: bsontype.Array},
+					bsonrwtest.ReadValue,
+					errors.New("rv error"),
+				},
+				{
+					"DecodeValue Error",
+					[]string{},
+					&DecodeContext{Registry: buildDefaultRegistry()},
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Array},
+					bsonrwtest.ReadValue,
+					errors.New("cannot decode array into a string type"),
+				},
+				{
+					"Document but not D",
+					[]string{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Type(0)},
+					bsonrwtest.Nothing,
+					errors.New("cannot decode document into []string"),
+				},
+				{
+					"EmbeddedDocument but not D",
+					[]string{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.EmbeddedDocument},
+					bsonrwtest.Nothing,
+					errors.New("cannot decode document into []string"),
+				},
+			},
+		},
+		{
+			"ObjectIDDecodeValue",
+			ValueDecoderFunc(dvd.ObjectIDDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.ObjectID},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "ObjectIDDecodeValue", Types: []reflect.Type{tOID}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"type not objectID",
+					primitive.ObjectID{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					fmt.Errorf("cannot decode %v into an ObjectID", bsontype.String),
+				},
+				{
+					"ReadObjectID Error",
+					primitive.ObjectID{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.ObjectID, Err: errors.New("roid error"), ErrAfter: bsonrwtest.ReadObjectID},
+					bsonrwtest.ReadObjectID,
+					errors.New("roid error"),
+				},
+				{
+					"can set false",
+					cansettest,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.ObjectID, Return: primitive.ObjectID{}},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "ObjectIDDecodeValue", Types: []reflect.Type{tOID}},
+				},
+				{
+					"success",
+					primitive.ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C},
+					nil,
+					&bsonrwtest.ValueReaderWriter{
+						BSONType: bsontype.ObjectID,
+						Return:   primitive.ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C},
+					},
+					bsonrwtest.ReadObjectID,
+					nil,
+				},
+			},
+		},
+		{
+			"Decimal128DecodeValue",
+			ValueDecoderFunc(dvd.Decimal128DecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Decimal128},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "Decimal128DecodeValue", Types: []reflect.Type{tDecimal}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"type not decimal128",
+					primitive.Decimal128{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					fmt.Errorf("cannot decode %v into a primitive.Decimal128", bsontype.String),
+				},
+				{
+					"ReadDecimal128 Error",
+					primitive.Decimal128{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Decimal128, Err: errors.New("rd128 error"), ErrAfter: bsonrwtest.ReadDecimal128},
+					bsonrwtest.ReadDecimal128,
+					errors.New("rd128 error"),
+				},
+				{
+					"can set false",
+					cansettest,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Decimal128, Return: d128},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "Decimal128DecodeValue", Types: []reflect.Type{tDecimal}},
+				},
+				{
+					"success",
+					d128,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Decimal128, Return: d128},
+					bsonrwtest.ReadDecimal128,
+					nil,
+				},
+			},
+		},
+		{
+			"JSONNumberDecodeValue",
+			ValueDecoderFunc(dvd.JSONNumberDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.ObjectID},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "JSONNumberDecodeValue", Types: []reflect.Type{tJSONNumber}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"type not double/int32/int64",
+					json.Number(""),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					fmt.Errorf("cannot decode %v into a json.Number", bsontype.String),
+				},
+				{
+					"ReadDouble Error",
+					json.Number(""),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Err: errors.New("rd error"), ErrAfter: bsonrwtest.ReadDouble},
+					bsonrwtest.ReadDouble,
+					errors.New("rd error"),
+				},
+				{
+					"ReadInt32 Error",
+					json.Number(""),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Err: errors.New("ri32 error"), ErrAfter: bsonrwtest.ReadInt32},
+					bsonrwtest.ReadInt32,
+					errors.New("ri32 error"),
+				},
+				{
+					"ReadInt64 Error",
+					json.Number(""),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int64, Err: errors.New("ri64 error"), ErrAfter: bsonrwtest.ReadInt64},
+					bsonrwtest.ReadInt64,
+					errors.New("ri64 error"),
+				},
+				{
+					"can set false",
+					cansettest,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.ObjectID, Return: primitive.ObjectID{}},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "JSONNumberDecodeValue", Types: []reflect.Type{tJSONNumber}},
+				},
+				{
+					"success/double",
+					json.Number("3.14159"),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(3.14159)},
+					bsonrwtest.ReadDouble,
+					nil,
+				},
+				{
+					"success/int32",
+					json.Number("12345"),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(12345)},
+					bsonrwtest.ReadInt32,
+					nil,
+				},
+				{
+					"success/int64",
+					json.Number("1234567890"),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int64, Return: int64(1234567890)},
+					bsonrwtest.ReadInt64,
+					nil,
+				},
+			},
+		},
+		{
+			"URLDecodeValue",
+			ValueDecoderFunc(dvd.URLDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32},
+					bsonrwtest.Nothing,
+					fmt.Errorf("cannot decode %v into a *url.URL", bsontype.Int32),
+				},
+				{
+					"type not *url.URL",
+					int64(0),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String, Return: string("http://example.com")},
+					bsonrwtest.ReadString,
+					ValueDecoderError{Name: "URLDecodeValue", Types: []reflect.Type{tURL}, Received: reflect.ValueOf(int64(0))},
+				},
+				{
+					"ReadString error",
+					url.URL{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String, Err: errors.New("rs error"), ErrAfter: bsonrwtest.ReadString},
+					bsonrwtest.ReadString,
+					errors.New("rs error"),
+				},
+				{
+					"url.Parse error",
+					url.URL{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String, Return: string("not-valid-%%%%://")},
+					bsonrwtest.ReadString,
+					errors.New("parse not-valid-%%%%://: first path segment in URL cannot contain colon"),
+				},
+				{
+					"can set false",
+					cansettest,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String, Return: string("http://example.com")},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "URLDecodeValue", Types: []reflect.Type{tURL}},
+				},
+				{
+					"url.URL",
+					url.URL{Scheme: "http", Host: "example.com"},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String, Return: string("http://example.com")},
+					bsonrwtest.ReadString,
+					nil,
+				},
+			},
+		},
+		{
+			"ByteSliceDecodeValue",
+			ValueDecoderFunc(dvd.ByteSliceDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32},
+					bsonrwtest.Nothing,
+					fmt.Errorf("cannot decode %v into a []byte", bsontype.Int32),
+				},
+				{
+					"type not []byte",
+					int64(0),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Binary, Return: bsoncore.Value{Type: bsontype.Binary}},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "ByteSliceDecodeValue", Types: []reflect.Type{tByteSlice}, Received: reflect.ValueOf(int64(0))},
+				},
+				{
+					"ReadBinary error",
+					[]byte{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Binary, Err: errors.New("rb error"), ErrAfter: bsonrwtest.ReadBinary},
+					bsonrwtest.ReadBinary,
+					errors.New("rb error"),
+				},
+				{
+					"incorrect subtype",
+					[]byte{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{
+						BSONType: bsontype.Binary,
+						Return: bsoncore.Value{
+							Type: bsontype.Binary,
+							Data: bsoncore.AppendBinary(nil, 0xFF, []byte{0x01, 0x02, 0x03}),
+						},
+					},
+					bsonrwtest.ReadBinary,
+					fmt.Errorf("ByteSliceDecodeValue can only be used to decode subtype 0x00 for %s, got %v", bsontype.Binary, byte(0xFF)),
+				},
+				{
+					"can set false",
+					cansettest,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Binary, Return: bsoncore.AppendBinary(nil, 0x00, []byte{0x01, 0x02, 0x03})},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "ByteSliceDecodeValue", Types: []reflect.Type{tByteSlice}},
+				},
+			},
+		},
+		{
+			"ValueUnmarshalerDecodeValue",
+			ValueDecoderFunc(dvd.ValueUnmarshalerDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueDecoderError{
+						Name:     "ValueUnmarshalerDecodeValue",
+						Types:    []reflect.Type{tValueUnmarshaler},
+						Received: reflect.ValueOf(wrong),
+					},
+				},
+				{
+					"copy error",
+					&testValueUnmarshaler{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String, Err: errors.New("copy error"), ErrAfter: bsonrwtest.ReadString},
+					bsonrwtest.ReadString,
+					errors.New("copy error"),
+				},
+				{
+					"ValueUnmarshaler",
+					&testValueUnmarshaler{t: bsontype.String, val: bsoncore.AppendString(nil, "hello, world")},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String, Return: string("hello, world")},
+					bsonrwtest.ReadString,
+					nil,
+				},
+			},
+		},
+		{
+			"UnmarshalerDecodeValue",
+			ValueDecoderFunc(dvd.UnmarshalerDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "UnmarshalerDecodeValue", Types: []reflect.Type{tUnmarshaler}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"copy error",
+					&testUnmarshaler{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String, Err: errors.New("copy error"), ErrAfter: bsonrwtest.ReadString},
+					bsonrwtest.ReadString,
+					errors.New("copy error"),
+				},
+				{
+					"Unmarshaler",
+					testUnmarshaler{Val: bsoncore.AppendDouble(nil, 3.14159)},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(3.14159)},
+					bsonrwtest.ReadDouble,
+					nil,
+				},
+			},
+		},
+		{
+			"PointerCodec.DecodeValue",
+			NewPointerCodec(),
+			[]subtest{
+				{
+					"not valid", nil, nil, nil, bsonrwtest.Nothing,
+					ValueDecoderError{Name: "PointerCodec.DecodeValue", Kinds: []reflect.Kind{reflect.Ptr}, Received: reflect.Value{}},
+				},
+				{
+					"can set", cansettest, nil, nil, bsonrwtest.Nothing,
+					ValueDecoderError{Name: "PointerCodec.DecodeValue", Kinds: []reflect.Kind{reflect.Ptr}},
+				},
+				{
+					"No Decoder", &wrong, &DecodeContext{Registry: buildDefaultRegistry()}, nil, bsonrwtest.Nothing,
+					ErrNoDecoder{Type: reflect.TypeOf(wrong)},
+				},
+			},
+		},
+		{
+			"BinaryDecodeValue",
+			ValueDecoderFunc(dvd.BinaryDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "BinaryDecodeValue", Types: []reflect.Type{tBinary}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"type not binary",
+					primitive.Binary{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					fmt.Errorf("cannot decode %v into a Binary", bsontype.String),
+				},
+				{
+					"ReadBinary Error",
+					primitive.Binary{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Binary, Err: errors.New("rb error"), ErrAfter: bsonrwtest.ReadBinary},
+					bsonrwtest.ReadBinary,
+					errors.New("rb error"),
+				},
+				{
+					"Binary/success",
+					primitive.Binary{Data: []byte{0x01, 0x02, 0x03}, Subtype: 0xFF},
+					nil,
+					&bsonrwtest.ValueReaderWriter{
+						BSONType: bsontype.Binary,
+						Return: bsoncore.Value{
+							Type: bsontype.Binary,
+							Data: bsoncore.AppendBinary(nil, 0xFF, []byte{0x01, 0x02, 0x03}),
+						},
+					},
+					bsonrwtest.ReadBinary,
+					nil,
+				},
+			},
+		},
+		{
+			"UndefinedDecodeValue",
+			ValueDecoderFunc(dvd.UndefinedDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Undefined},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "UndefinedDecodeValue", Types: []reflect.Type{tUndefined}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"type not undefined",
+					primitive.Undefined{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					fmt.Errorf("cannot decode %v into an Undefined", bsontype.String),
+				},
+				{
+					"ReadUndefined Error",
+					primitive.Undefined{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Undefined, Err: errors.New("ru error"), ErrAfter: bsonrwtest.ReadUndefined},
+					bsonrwtest.ReadUndefined,
+					errors.New("ru error"),
+				},
+				{
+					"ReadUndefined/success",
+					primitive.Undefined{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Undefined},
+					bsonrwtest.ReadUndefined,
+					nil,
+				},
+			},
+		},
+		{
+			"DateTimeDecodeValue",
+			ValueDecoderFunc(dvd.DateTimeDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.DateTime},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "DateTimeDecodeValue", Types: []reflect.Type{tDateTime}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"type not datetime",
+					primitive.DateTime(0),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					fmt.Errorf("cannot decode %v into a DateTime", bsontype.String),
+				},
+				{
+					"ReadDateTime Error",
+					primitive.DateTime(0),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.DateTime, Err: errors.New("rdt error"), ErrAfter: bsonrwtest.ReadDateTime},
+					bsonrwtest.ReadDateTime,
+					errors.New("rdt error"),
+				},
+				{
+					"success",
+					primitive.DateTime(1234567890),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.DateTime, Return: int64(1234567890)},
+					bsonrwtest.ReadDateTime,
+					nil,
+				},
+			},
+		},
+		{
+			"NullDecodeValue",
+			ValueDecoderFunc(dvd.NullDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Null},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "NullDecodeValue", Types: []reflect.Type{tNull}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"type not null",
+					primitive.Null{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					fmt.Errorf("cannot decode %v into a Null", bsontype.String),
+				},
+				{
+					"ReadNull Error",
+					primitive.Null{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Null, Err: errors.New("rn error"), ErrAfter: bsonrwtest.ReadNull},
+					bsonrwtest.ReadNull,
+					errors.New("rn error"),
+				},
+				{
+					"success",
+					primitive.Null{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Null},
+					bsonrwtest.ReadNull,
+					nil,
+				},
+			},
+		},
+		{
+			"RegexDecodeValue",
+			ValueDecoderFunc(dvd.RegexDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Regex},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "RegexDecodeValue", Types: []reflect.Type{tRegex}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"type not regex",
+					primitive.Regex{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					fmt.Errorf("cannot decode %v into a Regex", bsontype.String),
+				},
+				{
+					"ReadRegex Error",
+					primitive.Regex{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Regex, Err: errors.New("rr error"), ErrAfter: bsonrwtest.ReadRegex},
+					bsonrwtest.ReadRegex,
+					errors.New("rr error"),
+				},
+				{
+					"success",
+					primitive.Regex{Pattern: "foo", Options: "bar"},
+					nil,
+					&bsonrwtest.ValueReaderWriter{
+						BSONType: bsontype.Regex,
+						Return: bsoncore.Value{
+							Type: bsontype.Regex,
+							Data: bsoncore.AppendRegex(nil, "foo", "bar"),
+						},
+					},
+					bsonrwtest.ReadRegex,
+					nil,
+				},
+			},
+		},
+		{
+			"DBPointerDecodeValue",
+			ValueDecoderFunc(dvd.DBPointerDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.DBPointer},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "DBPointerDecodeValue", Types: []reflect.Type{tDBPointer}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"type not dbpointer",
+					primitive.DBPointer{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					fmt.Errorf("cannot decode %v into a DBPointer", bsontype.String),
+				},
+				{
+					"ReadDBPointer Error",
+					primitive.DBPointer{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.DBPointer, Err: errors.New("rdbp error"), ErrAfter: bsonrwtest.ReadDBPointer},
+					bsonrwtest.ReadDBPointer,
+					errors.New("rdbp error"),
+				},
+				{
+					"success",
+					primitive.DBPointer{
+						DB:      "foobar",
+						Pointer: primitive.ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C},
+					},
+					nil,
+					&bsonrwtest.ValueReaderWriter{
+						BSONType: bsontype.DBPointer,
+						Return: bsoncore.Value{
+							Type: bsontype.DBPointer,
+							Data: bsoncore.AppendDBPointer(
+								nil, "foobar", primitive.ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C},
+							),
+						},
+					},
+					bsonrwtest.ReadDBPointer,
+					nil,
+				},
+			},
+		},
+		{
+			"TimestampDecodeValue",
+			ValueDecoderFunc(dvd.TimestampDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Timestamp},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "TimestampDecodeValue", Types: []reflect.Type{tTimestamp}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"type not timestamp",
+					primitive.Timestamp{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					fmt.Errorf("cannot decode %v into a Timestamp", bsontype.String),
+				},
+				{
+					"ReadTimestamp Error",
+					primitive.Timestamp{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Timestamp, Err: errors.New("rt error"), ErrAfter: bsonrwtest.ReadTimestamp},
+					bsonrwtest.ReadTimestamp,
+					errors.New("rt error"),
+				},
+				{
+					"success",
+					primitive.Timestamp{T: 12345, I: 67890},
+					nil,
+					&bsonrwtest.ValueReaderWriter{
+						BSONType: bsontype.Timestamp,
+						Return: bsoncore.Value{
+							Type: bsontype.Timestamp,
+							Data: bsoncore.AppendTimestamp(nil, 12345, 67890),
+						},
+					},
+					bsonrwtest.ReadTimestamp,
+					nil,
+				},
+			},
+		},
+		{
+			"MinKeyDecodeValue",
+			ValueDecoderFunc(dvd.MinKeyDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.MinKey},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "MinKeyDecodeValue", Types: []reflect.Type{tMinKey}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"type not null",
+					primitive.MinKey{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					fmt.Errorf("cannot decode %v into a MinKey", bsontype.String),
+				},
+				{
+					"ReadMinKey Error",
+					primitive.MinKey{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.MinKey, Err: errors.New("rn error"), ErrAfter: bsonrwtest.ReadMinKey},
+					bsonrwtest.ReadMinKey,
+					errors.New("rn error"),
+				},
+				{
+					"success",
+					primitive.MinKey{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.MinKey},
+					bsonrwtest.ReadMinKey,
+					nil,
+				},
+			},
+		},
+		{
+			"MaxKeyDecodeValue",
+			ValueDecoderFunc(dvd.MaxKeyDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.MaxKey},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "MaxKeyDecodeValue", Types: []reflect.Type{tMaxKey}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"type not null",
+					primitive.MaxKey{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					fmt.Errorf("cannot decode %v into a MaxKey", bsontype.String),
+				},
+				{
+					"ReadMaxKey Error",
+					primitive.MaxKey{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.MaxKey, Err: errors.New("rn error"), ErrAfter: bsonrwtest.ReadMaxKey},
+					bsonrwtest.ReadMaxKey,
+					errors.New("rn error"),
+				},
+				{
+					"success",
+					primitive.MaxKey{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.MaxKey},
+					bsonrwtest.ReadMaxKey,
+					nil,
+				},
+			},
+		},
+		{
+			"JavaScriptDecodeValue",
+			ValueDecoderFunc(dvd.JavaScriptDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.JavaScript, Return: ""},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "BinaryDecodeValue", Types: []reflect.Type{tJavaScript}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"type not Javascript",
+					primitive.JavaScript(""),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					fmt.Errorf("cannot decode %v into a primitive.JavaScript", bsontype.String),
+				},
+				{
+					"ReadJavascript Error",
+					primitive.JavaScript(""),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.JavaScript, Err: errors.New("rjs error"), ErrAfter: bsonrwtest.ReadJavascript},
+					bsonrwtest.ReadJavascript,
+					errors.New("rjs error"),
+				},
+				{
+					"JavaScript/success",
+					primitive.JavaScript("var hello = 'world';"),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.JavaScript, Return: "var hello = 'world';"},
+					bsonrwtest.ReadJavascript,
+					nil,
+				},
+			},
+		},
+		{
+			"SymbolDecodeValue",
+			ValueDecoderFunc(dvd.SymbolDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Symbol, Return: ""},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "BinaryDecodeValue", Types: []reflect.Type{tSymbol}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"type not Symbol",
+					primitive.Symbol(""),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					fmt.Errorf("cannot decode %v into a primitive.Symbol", bsontype.String),
+				},
+				{
+					"ReadSymbol Error",
+					primitive.Symbol(""),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Symbol, Err: errors.New("rjs error"), ErrAfter: bsonrwtest.ReadSymbol},
+					bsonrwtest.ReadSymbol,
+					errors.New("rjs error"),
+				},
+				{
+					"Symbol/success",
+					primitive.Symbol("var hello = 'world';"),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Symbol, Return: "var hello = 'world';"},
+					bsonrwtest.ReadSymbol,
+					nil,
+				},
+			},
+		},
+		{
+			"CoreDocumentDecodeValue",
+			ValueDecoderFunc(dvd.CoreDocumentDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{},
+					bsonrwtest.Nothing,
+					ValueDecoderError{
+						Name:     "CoreDocumentDecodeValue",
+						Types:    []reflect.Type{tCoreDocument},
+						Received: reflect.ValueOf(wrong),
+					},
+				},
+				{
+					"*bsoncore.Document is nil",
+					(*bsoncore.Document)(nil),
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueDecoderError{
+						Name:     "CoreDocumentDecodeValue",
+						Types:    []reflect.Type{tCoreDocument},
+						Received: reflect.ValueOf((*bsoncore.Document)(nil)),
+					},
+				},
+				{
+					"Copy error",
+					bsoncore.Document{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{Err: errors.New("copy error"), ErrAfter: bsonrwtest.ReadDocument},
+					bsonrwtest.ReadDocument,
+					errors.New("copy error"),
+				},
+			},
+		},
+		{
+			"StructCodec.DecodeValue",
+			defaultStructCodec,
+			[]subtest{
+				{
+					"Not struct",
+					reflect.New(reflect.TypeOf(struct{ Foo string }{})).Elem().Interface(),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					errors.New("cannot decode string into a struct { Foo string }"),
+				},
+			},
+		},
+		{
+			"CodeWithScopeDecodeValue",
+			ValueDecoderFunc(dvd.CodeWithScopeDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.CodeWithScope},
+					bsonrwtest.Nothing,
+					ValueDecoderError{
+						Name:     "CodeWithScopeDecodeValue",
+						Types:    []reflect.Type{tCodeWithScope},
+						Received: reflect.ValueOf(wrong),
+					},
+				},
+				{
+					"type not codewithscope",
+					primitive.CodeWithScope{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					fmt.Errorf("cannot decode %v into a primitive.CodeWithScope", bsontype.String),
+				},
+				{
+					"ReadCodeWithScope Error",
+					primitive.CodeWithScope{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.CodeWithScope, Err: errors.New("rcws error"), ErrAfter: bsonrwtest.ReadCodeWithScope},
+					bsonrwtest.ReadCodeWithScope,
+					errors.New("rcws error"),
+				},
+				{
+					"decodeDocument Error",
+					primitive.CodeWithScope{
+						Code:  "var hello = 'world';",
+						Scope: primitive.D{{"foo", nil}},
+					},
+					&DecodeContext{Registry: buildDefaultRegistry()},
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.CodeWithScope, Err: errors.New("dd error"), ErrAfter: bsonrwtest.ReadElement},
+					bsonrwtest.ReadElement,
+					errors.New("dd error"),
+				},
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			for _, rc := range tc.subtests {
+				t.Run(rc.name, func(t *testing.T) {
+					var dc DecodeContext
+					if rc.dctx != nil {
+						dc = *rc.dctx
+					}
+					llvrw := new(bsonrwtest.ValueReaderWriter)
+					if rc.llvrw != nil {
+						llvrw = rc.llvrw
+					}
+					llvrw.T = t
+					// var got interface{}
+					if rc.val == cansetreflectiontest { // We're doing a CanSet reflection test
+						err := tc.vd.DecodeValue(dc, llvrw, reflect.Value{})
+						if !compareErrors(err, rc.err) {
+							t.Errorf("Errors do not match. got %v; want %v", err, rc.err)
+						}
+
+						val := reflect.New(reflect.TypeOf(rc.val)).Elem()
+						err = tc.vd.DecodeValue(dc, llvrw, val)
+						if !compareErrors(err, rc.err) {
+							t.Errorf("Errors do not match. got %v; want %v", err, rc.err)
+						}
+						return
+					}
+					if rc.val == cansettest { // We're doing an IsValid and CanSet test
+						wanterr, ok := rc.err.(ValueDecoderError)
+						if !ok {
+							t.Fatalf("Error must be a DecodeValueError, but got a %T", rc.err)
+						}
+
+						err := tc.vd.DecodeValue(dc, llvrw, reflect.Value{})
+						wanterr.Received = reflect.ValueOf(nil)
+						if !compareErrors(err, wanterr) {
+							t.Errorf("Errors do not match. got %v; want %v", err, wanterr)
+						}
+
+						err = tc.vd.DecodeValue(dc, llvrw, reflect.ValueOf(int(12345)))
+						wanterr.Received = reflect.ValueOf(int(12345))
+						if !compareErrors(err, wanterr) {
+							t.Errorf("Errors do not match. got %v; want %v", err, wanterr)
+						}
+						return
+					}
+					var val reflect.Value
+					if rtype := reflect.TypeOf(rc.val); rtype != nil {
+						val = reflect.New(rtype).Elem()
+					}
+					want := rc.val
+					defer func() {
+						if err := recover(); err != nil {
+							fmt.Println(t.Name())
+							panic(err)
+						}
+					}()
+					err := tc.vd.DecodeValue(dc, llvrw, val)
+					if !compareErrors(err, rc.err) {
+						t.Errorf("Errors do not match. got %v; want %v", err, rc.err)
+					}
+					invoked := llvrw.Invoked
+					if !cmp.Equal(invoked, rc.invoke) {
+						t.Errorf("Incorrect method invoked. got %v; want %v", invoked, rc.invoke)
+					}
+					var got interface{}
+					if val.IsValid() && val.CanInterface() {
+						got = val.Interface()
+					}
+					if rc.err == nil && !cmp.Equal(got, want, cmp.Comparer(compareDecimal128)) {
+						t.Errorf("Values do not match. got (%T)%v; want (%T)%v", got, got, want, want)
+					}
+				})
+			}
+		})
+	}
+
+	t.Run("CodeWithScopeCodec/DecodeValue/success", func(t *testing.T) {
+		dc := DecodeContext{Registry: buildDefaultRegistry()}
+		b := bsoncore.BuildDocument(nil,
+			bsoncore.AppendCodeWithScopeElement(
+				nil, "foo", "var hello = 'world';",
+				buildDocument(bsoncore.AppendNullElement(nil, "bar")),
+			),
+		)
+		dvr := bsonrw.NewBSONDocumentReader(b)
+		dr, err := dvr.ReadDocument()
+		noerr(t, err)
+		_, vr, err := dr.ReadElement()
+		noerr(t, err)
+
+		want := primitive.CodeWithScope{
+			Code:  "var hello = 'world';",
+			Scope: primitive.D{{"bar", nil}},
+		}
+		val := reflect.New(tCodeWithScope).Elem()
+		err = dvd.CodeWithScopeDecodeValue(dc, vr, val)
+		noerr(t, err)
+
+		got := val.Interface().(primitive.CodeWithScope)
+		if got.Code != want.Code && !cmp.Equal(got.Scope, want.Scope) {
+			t.Errorf("CodeWithScopes do not match. got %v; want %v", got, want)
+		}
+	})
+	t.Run("ValueUnmarshalerDecodeValue/UnmarshalBSONValue error", func(t *testing.T) {
+		var dc DecodeContext
+		llvrw := &bsonrwtest.ValueReaderWriter{BSONType: bsontype.String, Return: string("hello, world!")}
+		llvrw.T = t
+
+		want := errors.New("ubsonv error")
+		valUnmarshaler := &testValueUnmarshaler{err: want}
+		got := dvd.ValueUnmarshalerDecodeValue(dc, llvrw, reflect.ValueOf(valUnmarshaler))
+		if !compareErrors(got, want) {
+			t.Errorf("Errors do not match. got %v; want %v", got, want)
+		}
+	})
+	t.Run("ValueUnmarshalerDecodeValue/Unaddressable value", func(t *testing.T) {
+		var dc DecodeContext
+		llvrw := &bsonrwtest.ValueReaderWriter{BSONType: bsontype.String, Return: string("hello, world!")}
+		llvrw.T = t
+
+		val := reflect.ValueOf(testValueUnmarshaler{})
+		want := ValueDecoderError{Name: "ValueUnmarshalerDecodeValue", Types: []reflect.Type{tValueUnmarshaler}, Received: val}
+		got := dvd.ValueUnmarshalerDecodeValue(dc, llvrw, val)
+		if !compareErrors(got, want) {
+			t.Errorf("Errors do not match. got %v; want %v", got, want)
+		}
+	})
+
+	t.Run("SliceCodec/DecodeValue/can't set slice", func(t *testing.T) {
+		var val []string
+		want := ValueDecoderError{Name: "SliceDecodeValue", Kinds: []reflect.Kind{reflect.Slice}, Received: reflect.ValueOf(val)}
+		got := dvd.SliceDecodeValue(DecodeContext{}, nil, reflect.ValueOf(val))
+		if !compareErrors(got, want) {
+			t.Errorf("Errors do not match. got %v; want %v", got, want)
+		}
+	})
+	t.Run("SliceCodec/DecodeValue/too many elements", func(t *testing.T) {
+		idx, doc := bsoncore.AppendDocumentStart(nil)
+		aidx, doc := bsoncore.AppendArrayElementStart(doc, "foo")
+		doc = bsoncore.AppendStringElement(doc, "0", "foo")
+		doc = bsoncore.AppendStringElement(doc, "1", "bar")
+		doc, err := bsoncore.AppendArrayEnd(doc, aidx)
+		noerr(t, err)
+		doc, err = bsoncore.AppendDocumentEnd(doc, idx)
+		noerr(t, err)
+		dvr := bsonrw.NewBSONDocumentReader(doc)
+		noerr(t, err)
+		dr, err := dvr.ReadDocument()
+		noerr(t, err)
+		_, vr, err := dr.ReadElement()
+		noerr(t, err)
+		var val [1]string
+		want := fmt.Errorf("more elements returned in array than can fit inside %T", val)
+
+		dc := DecodeContext{Registry: buildDefaultRegistry()}
+		got := dvd.ArrayDecodeValue(dc, vr, reflect.ValueOf(val))
+		if !compareErrors(got, want) {
+			t.Errorf("Errors do not match. got %v; want %v", got, want)
+		}
+	})
+
+	t.Run("success path", func(t *testing.T) {
+		oid := primitive.NewObjectID()
+		oids := []primitive.ObjectID{primitive.NewObjectID(), primitive.NewObjectID(), primitive.NewObjectID()}
+		var str = new(string)
+		*str = "bar"
+		now := time.Now().Truncate(time.Millisecond).UTC()
+		murl, err := url.Parse("https://mongodb.com/random-url?hello=world")
+		if err != nil {
+			t.Errorf("Error parsing URL: %v", err)
+			t.FailNow()
+		}
+		decimal128, err := primitive.ParseDecimal128("1.5e10")
+		if err != nil {
+			t.Errorf("Error parsing decimal128: %v", err)
+			t.FailNow()
+		}
+
+		testCases := []struct {
+			name  string
+			value interface{}
+			b     []byte
+			err   error
+		}{
+			{
+				"map[string]int",
+				map[string]int32{"foo": 1},
+				[]byte{
+					0x0E, 0x00, 0x00, 0x00,
+					0x10, 'f', 'o', 'o', 0x00,
+					0x01, 0x00, 0x00, 0x00,
+					0x00,
+				},
+				nil,
+			},
+			{
+				"map[string]primitive.ObjectID",
+				map[string]primitive.ObjectID{"foo": oid},
+				func() []byte {
+					idx, doc := bsoncore.AppendDocumentStart(nil)
+					doc = bsoncore.AppendObjectIDElement(doc, "foo", oid)
+					doc, _ = bsoncore.AppendDocumentEnd(doc, idx)
+					return doc
+				}(),
+				nil,
+			},
+			{
+				"map[string][]int32",
+				map[string][]int32{"Z": {1, 2, 3}},
+				buildDocumentArray(func(doc []byte) []byte {
+					doc = bsoncore.AppendInt32Element(doc, "0", 1)
+					doc = bsoncore.AppendInt32Element(doc, "1", 2)
+					return bsoncore.AppendInt32Element(doc, "2", 3)
+				}),
+				nil,
+			},
+			{
+				"map[string][]primitive.ObjectID",
+				map[string][]primitive.ObjectID{"Z": oids},
+				buildDocumentArray(func(doc []byte) []byte {
+					doc = bsoncore.AppendObjectIDElement(doc, "0", oids[0])
+					doc = bsoncore.AppendObjectIDElement(doc, "1", oids[1])
+					return bsoncore.AppendObjectIDElement(doc, "2", oids[2])
+				}),
+				nil,
+			},
+			{
+				"map[string][]json.Number(int64)",
+				map[string][]json.Number{"Z": {json.Number("5"), json.Number("10")}},
+				buildDocumentArray(func(doc []byte) []byte {
+					doc = bsoncore.AppendInt64Element(doc, "0", 5)
+					return bsoncore.AppendInt64Element(doc, "1", 10)
+				}),
+				nil,
+			},
+			{
+				"map[string][]json.Number(float64)",
+				map[string][]json.Number{"Z": {json.Number("5"), json.Number("10.1")}},
+				buildDocumentArray(func(doc []byte) []byte {
+					doc = bsoncore.AppendInt64Element(doc, "0", 5)
+					return bsoncore.AppendDoubleElement(doc, "1", 10.1)
+				}),
+				nil,
+			},
+			{
+				"map[string][]*url.URL",
+				map[string][]*url.URL{"Z": {murl}},
+				buildDocumentArray(func(doc []byte) []byte {
+					return bsoncore.AppendStringElement(doc, "0", murl.String())
+				}),
+				nil,
+			},
+			{
+				"map[string][]primitive.Decimal128",
+				map[string][]primitive.Decimal128{"Z": {decimal128}},
+				buildDocumentArray(func(doc []byte) []byte {
+					return bsoncore.AppendDecimal128Element(doc, "0", decimal128)
+				}),
+				nil,
+			},
+			{
+				"map[mystring]interface{}",
+				map[mystring]interface{}{"pi": 3.14159},
+				buildDocument(bsoncore.AppendDoubleElement(nil, "pi", 3.14159)),
+				nil,
+			},
+			{
+				"-",
+				struct {
+					A string `bson:"-"`
+				}{
+					A: "",
+				},
+				[]byte{0x05, 0x00, 0x00, 0x00, 0x00},
+				nil,
+			},
+			{
+				"omitempty",
+				struct {
+					A string `bson:",omitempty"`
+				}{
+					A: "",
+				},
+				[]byte{0x05, 0x00, 0x00, 0x00, 0x00},
+				nil,
+			},
+			{
+				"omitempty, empty time",
+				struct {
+					A time.Time `bson:",omitempty"`
+				}{
+					A: time.Time{},
+				},
+				[]byte{0x05, 0x00, 0x00, 0x00, 0x00},
+				nil,
+			},
+			{
+				"no private fields",
+				noPrivateFields{a: "should be empty"},
+				[]byte{0x05, 0x00, 0x00, 0x00, 0x00},
+				nil,
+			},
+			{
+				"minsize",
+				struct {
+					A int64 `bson:",minsize"`
+				}{
+					A: 12345,
+				},
+				buildDocument(bsoncore.AppendInt32Element(nil, "a", 12345)),
+				nil,
+			},
+			{
+				"inline",
+				struct {
+					Foo struct {
+						A int64 `bson:",minsize"`
+					} `bson:",inline"`
+				}{
+					Foo: struct {
+						A int64 `bson:",minsize"`
+					}{
+						A: 12345,
+					},
+				},
+				buildDocument(bsoncore.AppendInt32Element(nil, "a", 12345)),
+				nil,
+			},
+			{
+				"inline map",
+				struct {
+					Foo map[string]string `bson:",inline"`
+				}{
+					Foo: map[string]string{"foo": "bar"},
+				},
+				buildDocument(bsoncore.AppendStringElement(nil, "foo", "bar")),
+				nil,
+			},
+			{
+				"alternate name bson:name",
+				struct {
+					A string `bson:"foo"`
+				}{
+					A: "bar",
+				},
+				buildDocument(bsoncore.AppendStringElement(nil, "foo", "bar")),
+				nil,
+			},
+			{
+				"alternate name",
+				struct {
+					A string `bson:"foo"`
+				}{
+					A: "bar",
+				},
+				buildDocument(bsoncore.AppendStringElement(nil, "foo", "bar")),
+				nil,
+			},
+			{
+				"inline, omitempty",
+				struct {
+					A   string
+					Foo zeroTest `bson:"omitempty,inline"`
+				}{
+					A:   "bar",
+					Foo: zeroTest{true},
+				},
+				buildDocument(bsoncore.AppendStringElement(nil, "a", "bar")),
+				nil,
+			},
+			{
+				"struct{}",
+				struct {
+					A bool
+					B int32
+					C int64
+					D uint16
+					E uint64
+					F float64
+					G string
+					H map[string]string
+					I []byte
+					K [2]string
+					L struct {
+						M string
+					}
+					Q  primitive.ObjectID
+					T  []struct{}
+					Y  json.Number
+					Z  time.Time
+					AA json.Number
+					AB *url.URL
+					AC primitive.Decimal128
+					AD *time.Time
+					AE *testValueUnmarshaler
+					AF *bool
+					AG *bool
+					AH *int32
+					AI *int64
+					AJ *primitive.ObjectID
+					AK *primitive.ObjectID
+					AL testValueUnmarshaler
+					AM interface{}
+					AN interface{}
+					AO interface{}
+					AP primitive.D
+					AQ primitive.A
+					AR [2]primitive.E
+					AS []byte
+					AT map[string]interface{}
+					AU primitive.CodeWithScope
+					AV primitive.M
+					AW primitive.D
+					AX map[string]interface{}
+					AY []primitive.E
+					AZ interface{}
+				}{
+					A: true,
+					B: 123,
+					C: 456,
+					D: 789,
+					E: 101112,
+					F: 3.14159,
+					G: "Hello, world",
+					H: map[string]string{"foo": "bar"},
+					I: []byte{0x01, 0x02, 0x03},
+					K: [2]string{"baz", "qux"},
+					L: struct {
+						M string
+					}{
+						M: "foobar",
+					},
+					Q:  oid,
+					T:  nil,
+					Y:  json.Number("5"),
+					Z:  now,
+					AA: json.Number("10.1"),
+					AB: murl,
+					AC: decimal128,
+					AD: &now,
+					AE: &testValueUnmarshaler{t: bsontype.String, val: bsoncore.AppendString(nil, "hello, world!")},
+					AF: func(b bool) *bool { return &b }(true),
+					AG: nil,
+					AH: func(i32 int32) *int32 { return &i32 }(12345),
+					AI: func(i64 int64) *int64 { return &i64 }(1234567890),
+					AJ: &oid,
+					AK: nil,
+					AL: testValueUnmarshaler{t: bsontype.String, val: bsoncore.AppendString(nil, "hello, world!")},
+					AM: "hello, world",
+					AN: int32(12345),
+					AO: oid,
+					AP: primitive.D{{"foo", "bar"}},
+					AQ: primitive.A{"foo", "bar"},
+					AR: [2]primitive.E{{"hello", "world"}, {"pi", 3.14159}},
+					AS: nil,
+					AT: nil,
+					AU: primitive.CodeWithScope{Code: "var hello = 'world';", Scope: primitive.D{{"pi", 3.14159}}},
+					AV: primitive.M{"foo": primitive.M{"bar": "baz"}},
+					AW: primitive.D{{"foo", primitive.D{{"bar", "baz"}}}},
+					AX: map[string]interface{}{"foo": map[string]interface{}{"bar": "baz"}},
+					AY: []primitive.E{{"foo", []primitive.E{{"bar", "baz"}}}},
+					AZ: primitive.D{{"foo", primitive.D{{"bar", "baz"}}}},
+				},
+				buildDocument(func(doc []byte) []byte {
+					doc = bsoncore.AppendBooleanElement(doc, "a", true)
+					doc = bsoncore.AppendInt32Element(doc, "b", 123)
+					doc = bsoncore.AppendInt64Element(doc, "c", 456)
+					doc = bsoncore.AppendInt32Element(doc, "d", 789)
+					doc = bsoncore.AppendInt64Element(doc, "e", 101112)
+					doc = bsoncore.AppendDoubleElement(doc, "f", 3.14159)
+					doc = bsoncore.AppendStringElement(doc, "g", "Hello, world")
+					doc = bsoncore.AppendDocumentElement(doc, "h", buildDocument(bsoncore.AppendStringElement(nil, "foo", "bar")))
+					doc = bsoncore.AppendBinaryElement(doc, "i", 0x00, []byte{0x01, 0x02, 0x03})
+					doc = bsoncore.AppendArrayElement(doc, "k",
+						buildArray(bsoncore.AppendStringElement(bsoncore.AppendStringElement(nil, "0", "baz"), "1", "qux")),
+					)
+					doc = bsoncore.AppendDocumentElement(doc, "l", buildDocument(bsoncore.AppendStringElement(nil, "m", "foobar")))
+					doc = bsoncore.AppendObjectIDElement(doc, "q", oid)
+					doc = bsoncore.AppendNullElement(doc, "t")
+					doc = bsoncore.AppendInt64Element(doc, "y", 5)
+					doc = bsoncore.AppendDateTimeElement(doc, "z", now.UnixNano()/int64(time.Millisecond))
+					doc = bsoncore.AppendDoubleElement(doc, "aa", 10.1)
+					doc = bsoncore.AppendStringElement(doc, "ab", murl.String())
+					doc = bsoncore.AppendDecimal128Element(doc, "ac", decimal128)
+					doc = bsoncore.AppendDateTimeElement(doc, "ad", now.UnixNano()/int64(time.Millisecond))
+					doc = bsoncore.AppendStringElement(doc, "ae", "hello, world!")
+					doc = bsoncore.AppendBooleanElement(doc, "af", true)
+					doc = bsoncore.AppendNullElement(doc, "ag")
+					doc = bsoncore.AppendInt32Element(doc, "ah", 12345)
+					doc = bsoncore.AppendInt32Element(doc, "ai", 1234567890)
+					doc = bsoncore.AppendObjectIDElement(doc, "aj", oid)
+					doc = bsoncore.AppendNullElement(doc, "ak")
+					doc = bsoncore.AppendStringElement(doc, "al", "hello, world!")
+					doc = bsoncore.AppendStringElement(doc, "am", "hello, world")
+					doc = bsoncore.AppendInt32Element(doc, "an", 12345)
+					doc = bsoncore.AppendObjectIDElement(doc, "ao", oid)
+					doc = bsoncore.AppendDocumentElement(doc, "ap", buildDocument(bsoncore.AppendStringElement(nil, "foo", "bar")))
+					doc = bsoncore.AppendArrayElement(doc, "aq",
+						buildArray(bsoncore.AppendStringElement(bsoncore.AppendStringElement(nil, "0", "foo"), "1", "bar")),
+					)
+					doc = bsoncore.AppendDocumentElement(doc, "ar",
+						buildDocument(bsoncore.AppendDoubleElement(bsoncore.AppendStringElement(nil, "hello", "world"), "pi", 3.14159)),
+					)
+					doc = bsoncore.AppendNullElement(doc, "as")
+					doc = bsoncore.AppendNullElement(doc, "at")
+					doc = bsoncore.AppendCodeWithScopeElement(doc, "au",
+						"var hello = 'world';", buildDocument(bsoncore.AppendDoubleElement(nil, "pi", 3.14159)),
+					)
+					for _, name := range [5]string{"av", "aw", "ax", "ay", "az"} {
+						doc = bsoncore.AppendDocumentElement(doc, name, buildDocument(
+							bsoncore.AppendDocumentElement(nil, "foo", buildDocument(
+								bsoncore.AppendStringElement(nil, "bar", "baz"),
+							)),
+						))
+					}
+					return doc
+				}(nil)),
+				nil,
+			},
+			{
+				"struct{[]interface{}}",
+				struct {
+					A []bool
+					B []int32
+					C []int64
+					D []uint16
+					E []uint64
+					F []float64
+					G []string
+					H []map[string]string
+					I [][]byte
+					K [1][2]string
+					L []struct {
+						M string
+					}
+					N  [][]string
+					R  []primitive.ObjectID
+					T  []struct{}
+					W  []map[string]struct{}
+					X  []map[string]struct{}
+					Y  []map[string]struct{}
+					Z  []time.Time
+					AA []json.Number
+					AB []*url.URL
+					AC []primitive.Decimal128
+					AD []*time.Time
+					AE []*testValueUnmarshaler
+					AF []*bool
+					AG []*int32
+					AH []*int64
+					AI []*primitive.ObjectID
+					AJ []primitive.D
+					AK []primitive.A
+					AL [][2]primitive.E
+				}{
+					A: []bool{true},
+					B: []int32{123},
+					C: []int64{456},
+					D: []uint16{789},
+					E: []uint64{101112},
+					F: []float64{3.14159},
+					G: []string{"Hello, world"},
+					H: []map[string]string{{"foo": "bar"}},
+					I: [][]byte{{0x01, 0x02, 0x03}},
+					K: [1][2]string{{"baz", "qux"}},
+					L: []struct {
+						M string
+					}{
+						{
+							M: "foobar",
+						},
+					},
+					N:  [][]string{{"foo", "bar"}},
+					R:  oids,
+					T:  nil,
+					W:  nil,
+					X:  []map[string]struct{}{},   // Should be empty BSON Array
+					Y:  []map[string]struct{}{{}}, // Should be BSON array with one element, an empty BSON SubDocument
+					Z:  []time.Time{now, now},
+					AA: []json.Number{json.Number("5"), json.Number("10.1")},
+					AB: []*url.URL{murl},
+					AC: []primitive.Decimal128{decimal128},
+					AD: []*time.Time{&now, &now},
+					AE: []*testValueUnmarshaler{
+						{t: bsontype.String, val: bsoncore.AppendString(nil, "hello")},
+						{t: bsontype.String, val: bsoncore.AppendString(nil, "world")},
+					},
+					AF: []*bool{pbool(true), nil},
+					AG: []*int32{pi32(12345), nil},
+					AH: []*int64{pi64(1234567890), nil, pi64(9012345678)},
+					AI: []*primitive.ObjectID{&oid, nil},
+					AJ: []primitive.D{{{"foo", "bar"}}, nil},
+					AK: []primitive.A{{"foo", "bar"}, nil},
+					AL: [][2]primitive.E{{{"hello", "world"}, {"pi", 3.14159}}},
+				},
+				buildDocument(func(doc []byte) []byte {
+					doc = appendArrayElement(doc, "a", bsoncore.AppendBooleanElement(nil, "0", true))
+					doc = appendArrayElement(doc, "b", bsoncore.AppendInt32Element(nil, "0", 123))
+					doc = appendArrayElement(doc, "c", bsoncore.AppendInt64Element(nil, "0", 456))
+					doc = appendArrayElement(doc, "d", bsoncore.AppendInt32Element(nil, "0", 789))
+					doc = appendArrayElement(doc, "e", bsoncore.AppendInt64Element(nil, "0", 101112))
+					doc = appendArrayElement(doc, "f", bsoncore.AppendDoubleElement(nil, "0", 3.14159))
+					doc = appendArrayElement(doc, "g", bsoncore.AppendStringElement(nil, "0", "Hello, world"))
+					doc = appendArrayElement(doc, "h", buildDocumentElement("0", bsoncore.AppendStringElement(nil, "foo", "bar")))
+					doc = appendArrayElement(doc, "i", bsoncore.AppendBinaryElement(nil, "0", 0x00, []byte{0x01, 0x02, 0x03}))
+					doc = appendArrayElement(doc, "k",
+						buildArrayElement("0",
+							bsoncore.AppendStringElement(bsoncore.AppendStringElement(nil, "0", "baz"), "1", "qux")),
+					)
+					doc = appendArrayElement(doc, "l", buildDocumentElement("0", bsoncore.AppendStringElement(nil, "m", "foobar")))
+					doc = appendArrayElement(doc, "n",
+						buildArrayElement("0",
+							bsoncore.AppendStringElement(bsoncore.AppendStringElement(nil, "0", "foo"), "1", "bar")),
+					)
+					doc = appendArrayElement(doc, "r",
+						bsoncore.AppendObjectIDElement(
+							bsoncore.AppendObjectIDElement(
+								bsoncore.AppendObjectIDElement(nil,
+									"0", oids[0]),
+								"1", oids[1]),
+							"2", oids[2]),
+					)
+					doc = bsoncore.AppendNullElement(doc, "t")
+					doc = bsoncore.AppendNullElement(doc, "w")
+					doc = appendArrayElement(doc, "x", nil)
+					doc = appendArrayElement(doc, "y", buildDocumentElement("0", nil))
+					doc = appendArrayElement(doc, "z",
+						bsoncore.AppendDateTimeElement(
+							bsoncore.AppendDateTimeElement(
+								nil, "0", now.UnixNano()/int64(time.Millisecond)),
+							"1", now.UnixNano()/int64(time.Millisecond)),
+					)
+					doc = appendArrayElement(doc, "aa", bsoncore.AppendDoubleElement(bsoncore.AppendInt64Element(nil, "0", 5), "1", 10.10))
+					doc = appendArrayElement(doc, "ab", bsoncore.AppendStringElement(nil, "0", murl.String()))
+					doc = appendArrayElement(doc, "ac", bsoncore.AppendDecimal128Element(nil, "0", decimal128))
+					doc = appendArrayElement(doc, "ad",
+						bsoncore.AppendDateTimeElement(
+							bsoncore.AppendDateTimeElement(nil, "0", now.UnixNano()/int64(time.Millisecond)),
+							"1", now.UnixNano()/int64(time.Millisecond)),
+					)
+					doc = appendArrayElement(doc, "ae",
+						bsoncore.AppendStringElement(bsoncore.AppendStringElement(nil, "0", "hello"), "1", "world"),
+					)
+					doc = appendArrayElement(doc, "af",
+						bsoncore.AppendNullElement(bsoncore.AppendBooleanElement(nil, "0", true), "1"),
+					)
+					doc = appendArrayElement(doc, "ag",
+						bsoncore.AppendNullElement(bsoncore.AppendInt32Element(nil, "0", 12345), "1"),
+					)
+					doc = appendArrayElement(doc, "ah",
+						bsoncore.AppendInt64Element(
+							bsoncore.AppendNullElement(bsoncore.AppendInt64Element(nil, "0", 1234567890), "1"),
+							"2", 9012345678,
+						),
+					)
+					doc = appendArrayElement(doc, "ai",
+						bsoncore.AppendNullElement(bsoncore.AppendObjectIDElement(nil, "0", oid), "1"),
+					)
+					doc = appendArrayElement(doc, "aj",
+						bsoncore.AppendNullElement(
+							bsoncore.AppendDocumentElement(nil, "0", buildDocument(bsoncore.AppendStringElement(nil, "foo", "bar"))),
+							"1",
+						),
+					)
+					doc = appendArrayElement(doc, "ak",
+						bsoncore.AppendNullElement(
+							buildArrayElement("0",
+								bsoncore.AppendStringElement(bsoncore.AppendStringElement(nil, "0", "foo"), "1", "bar"),
+							),
+							"1",
+						),
+					)
+					doc = appendArrayElement(doc, "al",
+						buildDocumentElement(
+							"0",
+							bsoncore.AppendDoubleElement(bsoncore.AppendStringElement(nil, "hello", "world"), "pi", 3.14159),
+						),
+					)
+					return doc
+				}(nil)),
+				nil,
+			},
+		}
+
+		t.Run("Decode", func(t *testing.T) {
+			for _, tc := range testCases {
+				t.Run(tc.name, func(t *testing.T) {
+					vr := bsonrw.NewBSONDocumentReader(tc.b)
+					reg := buildDefaultRegistry()
+					vtype := reflect.TypeOf(tc.value)
+					dec, err := reg.LookupDecoder(vtype)
+					noerr(t, err)
+
+					gotVal := reflect.New(reflect.TypeOf(tc.value)).Elem()
+					err = dec.DecodeValue(DecodeContext{Registry: reg}, vr, gotVal)
+					noerr(t, err)
+
+					got := gotVal.Interface()
+					want := tc.value
+					if diff := cmp.Diff(
+						got, want,
+						cmp.Comparer(compareDecimal128),
+						cmp.Comparer(compareNoPrivateFields),
+						cmp.Comparer(compareZeroTest),
+						cmp.Comparer(compareTime),
+					); diff != "" {
+						t.Errorf("difference:\n%s", diff)
+						t.Errorf("Values are not equal.\ngot: %#v\nwant:%#v", got, want)
+					}
+				})
+			}
+		})
+	})
+
+	t.Run("EmptyInterfaceDecodeValue", func(t *testing.T) {
+		t.Run("DecodeValue", func(t *testing.T) {
+			testCases := []struct {
+				name     string
+				val      interface{}
+				bsontype bsontype.Type
+			}{
+				{
+					"Double - float64",
+					float64(3.14159),
+					bsontype.Double,
+				},
+				{
+					"String - string",
+					string("foo bar baz"),
+					bsontype.String,
+				},
+				{
+					"Array - primitive.A",
+					primitive.A{3.14159},
+					bsontype.Array,
+				},
+				{
+					"Binary - Binary",
+					primitive.Binary{Subtype: 0xFF, Data: []byte{0x01, 0x02, 0x03}},
+					bsontype.Binary,
+				},
+				{
+					"Undefined - Undefined",
+					primitive.Undefined{},
+					bsontype.Undefined,
+				},
+				{
+					"ObjectID - primitive.ObjectID",
+					primitive.ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C},
+					bsontype.ObjectID,
+				},
+				{
+					"Boolean - bool",
+					bool(true),
+					bsontype.Boolean,
+				},
+				{
+					"DateTime - DateTime",
+					primitive.DateTime(1234567890),
+					bsontype.DateTime,
+				},
+				{
+					"Null - Null",
+					nil,
+					bsontype.Null,
+				},
+				{
+					"Regex - Regex",
+					primitive.Regex{Pattern: "foo", Options: "bar"},
+					bsontype.Regex,
+				},
+				{
+					"DBPointer - DBPointer",
+					primitive.DBPointer{
+						DB:      "foobar",
+						Pointer: primitive.ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C},
+					},
+					bsontype.DBPointer,
+				},
+				{
+					"JavaScript - JavaScript",
+					primitive.JavaScript("var foo = 'bar';"),
+					bsontype.JavaScript,
+				},
+				{
+					"Symbol - Symbol",
+					primitive.Symbol("foobarbazlolz"),
+					bsontype.Symbol,
+				},
+				{
+					"Int32 - int32",
+					int32(123456),
+					bsontype.Int32,
+				},
+				{
+					"Int64 - int64",
+					int64(1234567890),
+					bsontype.Int64,
+				},
+				{
+					"Timestamp - Timestamp",
+					primitive.Timestamp{T: 12345, I: 67890},
+					bsontype.Timestamp,
+				},
+				{
+					"Decimal128 - decimal.Decimal128",
+					primitive.NewDecimal128(12345, 67890),
+					bsontype.Decimal128,
+				},
+				{
+					"MinKey - MinKey",
+					primitive.MinKey{},
+					bsontype.MinKey,
+				},
+				{
+					"MaxKey - MaxKey",
+					primitive.MaxKey{},
+					bsontype.MaxKey,
+				},
+			}
+			for _, tc := range testCases {
+				t.Run(tc.name, func(t *testing.T) {
+					llvr := &bsonrwtest.ValueReaderWriter{BSONType: tc.bsontype}
+
+					t.Run("Type Map failure", func(t *testing.T) {
+						if tc.bsontype == bsontype.Null {
+							t.Skip()
+						}
+						val := reflect.New(tEmpty).Elem()
+						dc := DecodeContext{Registry: NewRegistryBuilder().Build()}
+						want := ErrNoTypeMapEntry{Type: tc.bsontype}
+						got := dvd.EmptyInterfaceDecodeValue(dc, llvr, val)
+						if !compareErrors(got, want) {
+							t.Errorf("Errors are not equal. got %v; want %v", got, want)
+						}
+					})
+
+					t.Run("Lookup failure", func(t *testing.T) {
+						if tc.bsontype == bsontype.Null {
+							t.Skip()
+						}
+						val := reflect.New(tEmpty).Elem()
+						dc := DecodeContext{
+							Registry: NewRegistryBuilder().
+								RegisterTypeMapEntry(tc.bsontype, reflect.TypeOf(tc.val)).
+								Build(),
+						}
+						want := ErrNoDecoder{Type: reflect.TypeOf(tc.val)}
+						got := dvd.EmptyInterfaceDecodeValue(dc, llvr, val)
+						if !compareErrors(got, want) {
+							t.Errorf("Errors are not equal. got %v; want %v", got, want)
+						}
+					})
+
+					t.Run("DecodeValue failure", func(t *testing.T) {
+						if tc.bsontype == bsontype.Null {
+							t.Skip()
+						}
+						want := errors.New("DecodeValue failure error")
+						llc := &llCodec{t: t, err: want}
+						dc := DecodeContext{
+							Registry: NewRegistryBuilder().
+								RegisterDecoder(reflect.TypeOf(tc.val), llc).
+								RegisterTypeMapEntry(tc.bsontype, reflect.TypeOf(tc.val)).
+								Build(),
+						}
+						got := dvd.EmptyInterfaceDecodeValue(dc, llvr, reflect.New(tEmpty).Elem())
+						if !compareErrors(got, want) {
+							t.Errorf("Errors are not equal. got %v; want %v", got, want)
+						}
+					})
+
+					t.Run("Success", func(t *testing.T) {
+						want := tc.val
+						llc := &llCodec{t: t, decodeval: tc.val}
+						dc := DecodeContext{
+							Registry: NewRegistryBuilder().
+								RegisterDecoder(reflect.TypeOf(tc.val), llc).
+								RegisterTypeMapEntry(tc.bsontype, reflect.TypeOf(tc.val)).
+								Build(),
+						}
+						got := reflect.New(tEmpty).Elem()
+						err := dvd.EmptyInterfaceDecodeValue(dc, llvr, got)
+						noerr(t, err)
+						if !cmp.Equal(got.Interface(), want, cmp.Comparer(compareDecimal128)) {
+							t.Errorf("Did not receive expected value. got %v; want %v", got.Interface(), want)
+						}
+					})
+				})
+			}
+		})
+
+		t.Run("non-interface{}", func(t *testing.T) {
+			val := uint64(1234567890)
+			want := ValueDecoderError{Name: "EmptyInterfaceDecodeValue", Types: []reflect.Type{tEmpty}, Received: reflect.ValueOf(val)}
+			got := dvd.EmptyInterfaceDecodeValue(DecodeContext{}, nil, reflect.ValueOf(val))
+			if !compareErrors(got, want) {
+				t.Errorf("Errors are not equal. got %v; want %v", got, want)
+			}
+		})
+
+		t.Run("nil *interface{}", func(t *testing.T) {
+			var val interface{}
+			want := ValueDecoderError{Name: "EmptyInterfaceDecodeValue", Types: []reflect.Type{tEmpty}, Received: reflect.ValueOf(val)}
+			got := dvd.EmptyInterfaceDecodeValue(DecodeContext{}, nil, reflect.ValueOf(val))
+			if !compareErrors(got, want) {
+				t.Errorf("Errors are not equal. got %v; want %v", got, want)
+			}
+		})
+
+		t.Run("no type registered", func(t *testing.T) {
+			llvr := &bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double}
+			want := ErrNoTypeMapEntry{Type: bsontype.Double}
+			val := reflect.New(tEmpty).Elem()
+			got := dvd.EmptyInterfaceDecodeValue(DecodeContext{Registry: NewRegistryBuilder().Build()}, llvr, val)
+			if !compareErrors(got, want) {
+				t.Errorf("Errors are not equal. got %v; want %v", got, want)
+			}
+		})
+		t.Run("top level document", func(t *testing.T) {
+			data := bsoncore.BuildDocument(nil, bsoncore.AppendDoubleElement(nil, "pi", 3.14159))
+			vr := bsonrw.NewBSONDocumentReader(data)
+			want := primitive.D{{"pi", 3.14159}}
+			var got interface{}
+			val := reflect.ValueOf(&got).Elem()
+			err := dvd.EmptyInterfaceDecodeValue(DecodeContext{Registry: buildDefaultRegistry()}, vr, val)
+			noerr(t, err)
+			if !cmp.Equal(got, want) {
+				t.Errorf("Did not get correct result. got %v; want %v", got, want)
+			}
+		})
+	})
+}
+
+type testValueUnmarshaler struct {
+	t   bsontype.Type
+	val []byte
+	err error
+}
+
+func (tvu *testValueUnmarshaler) UnmarshalBSONValue(t bsontype.Type, val []byte) error {
+	tvu.t, tvu.val = t, val
+	return tvu.err
+}
+
+type testUnmarshaler struct {
+	Val []byte
+	Err error
+}
+
+func (tvu *testUnmarshaler) UnmarshalBSON(val []byte) error {
+	tvu.Val = val
+	return tvu.Err
+}
+
+func (tvu testValueUnmarshaler) Equal(tvu2 testValueUnmarshaler) bool {
+	return tvu.t == tvu2.t && bytes.Equal(tvu.val, tvu2.val)
+}
+
+// buildDocumentArray inserts vals inside of an array inside of a document.
+func buildDocumentArray(fn func([]byte) []byte) []byte {
+	aix, doc := bsoncore.AppendArrayElementStart(nil, "Z")
+	doc = fn(doc)
+	doc, _ = bsoncore.AppendArrayEnd(doc, aix)
+	return buildDocument(doc)
+}
+
+func buildArray(vals []byte) []byte {
+	aix, doc := bsoncore.AppendArrayStart(nil)
+	doc = append(doc, vals...)
+	doc, _ = bsoncore.AppendArrayEnd(doc, aix)
+	return doc
+}
+
+func buildArrayElement(key string, vals []byte) []byte {
+	return appendArrayElement(nil, key, vals)
+}
+
+func appendArrayElement(dst []byte, key string, vals []byte) []byte {
+	aix, doc := bsoncore.AppendArrayElementStart(dst, key)
+	doc = append(doc, vals...)
+	doc, _ = bsoncore.AppendArrayEnd(doc, aix)
+	return doc
+}
+
+// buildDocument inserts elems inside of a document.
+func buildDocument(elems []byte) []byte {
+	idx, doc := bsoncore.AppendDocumentStart(nil)
+	doc = append(doc, elems...)
+	doc, _ = bsoncore.AppendDocumentEnd(doc, idx)
+	return doc
+}
+
+func buildDocumentElement(key string, elems []byte) []byte {
+	idx, doc := bsoncore.AppendDocumentElementStart(nil, key)
+	doc = append(doc, elems...)
+	doc, _ = bsoncore.AppendDocumentEnd(doc, idx)
+	return doc
+}

+ 648 - 0
src/go.mongodb.org/mongo-driver/bson/bsoncodec/default_value_encoders.go

@@ -0,0 +1,648 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+package bsoncodec
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"math"
+	"net/url"
+	"reflect"
+	"sync"
+	"time"
+
+	"go.mongodb.org/mongo-driver/bson/bsonrw"
+	"go.mongodb.org/mongo-driver/bson/bsontype"
+	"go.mongodb.org/mongo-driver/bson/primitive"
+	"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
+)
+
+var defaultValueEncoders DefaultValueEncoders
+
+var bvwPool = bsonrw.NewBSONValueWriterPool()
+
+var sliceWriterPool = sync.Pool{
+	New: func() interface{} {
+		sw := make(bsonrw.SliceWriter, 0, 0)
+		return &sw
+	},
+}
+
+func encodeElement(ec EncodeContext, dw bsonrw.DocumentWriter, e primitive.E) error {
+	vw, err := dw.WriteDocumentElement(e.Key)
+	if err != nil {
+		return err
+	}
+
+	if e.Value == nil {
+		return vw.WriteNull()
+	}
+	encoder, err := ec.LookupEncoder(reflect.TypeOf(e.Value))
+	if err != nil {
+		return err
+	}
+
+	err = encoder.EncodeValue(ec, vw, reflect.ValueOf(e.Value))
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+// DefaultValueEncoders is a namespace type for the default ValueEncoders used
+// when creating a registry.
+type DefaultValueEncoders struct{}
+
+// RegisterDefaultEncoders will register the encoder methods attached to DefaultValueEncoders with
+// the provided RegistryBuilder.
+func (dve DefaultValueEncoders) RegisterDefaultEncoders(rb *RegistryBuilder) {
+	if rb == nil {
+		panic(errors.New("argument to RegisterDefaultEncoders must not be nil"))
+	}
+	rb.
+		RegisterEncoder(tByteSlice, ValueEncoderFunc(dve.ByteSliceEncodeValue)).
+		RegisterEncoder(tTime, ValueEncoderFunc(dve.TimeEncodeValue)).
+		RegisterEncoder(tEmpty, ValueEncoderFunc(dve.EmptyInterfaceEncodeValue)).
+		RegisterEncoder(tOID, ValueEncoderFunc(dve.ObjectIDEncodeValue)).
+		RegisterEncoder(tDecimal, ValueEncoderFunc(dve.Decimal128EncodeValue)).
+		RegisterEncoder(tJSONNumber, ValueEncoderFunc(dve.JSONNumberEncodeValue)).
+		RegisterEncoder(tURL, ValueEncoderFunc(dve.URLEncodeValue)).
+		RegisterEncoder(tValueMarshaler, ValueEncoderFunc(dve.ValueMarshalerEncodeValue)).
+		RegisterEncoder(tMarshaler, ValueEncoderFunc(dve.MarshalerEncodeValue)).
+		RegisterEncoder(tProxy, ValueEncoderFunc(dve.ProxyEncodeValue)).
+		RegisterEncoder(tJavaScript, ValueEncoderFunc(dve.JavaScriptEncodeValue)).
+		RegisterEncoder(tSymbol, ValueEncoderFunc(dve.SymbolEncodeValue)).
+		RegisterEncoder(tBinary, ValueEncoderFunc(dve.BinaryEncodeValue)).
+		RegisterEncoder(tUndefined, ValueEncoderFunc(dve.UndefinedEncodeValue)).
+		RegisterEncoder(tDateTime, ValueEncoderFunc(dve.DateTimeEncodeValue)).
+		RegisterEncoder(tNull, ValueEncoderFunc(dve.NullEncodeValue)).
+		RegisterEncoder(tRegex, ValueEncoderFunc(dve.RegexEncodeValue)).
+		RegisterEncoder(tDBPointer, ValueEncoderFunc(dve.DBPointerEncodeValue)).
+		RegisterEncoder(tTimestamp, ValueEncoderFunc(dve.TimestampEncodeValue)).
+		RegisterEncoder(tMinKey, ValueEncoderFunc(dve.MinKeyEncodeValue)).
+		RegisterEncoder(tMaxKey, ValueEncoderFunc(dve.MaxKeyEncodeValue)).
+		RegisterEncoder(tCoreDocument, ValueEncoderFunc(dve.CoreDocumentEncodeValue)).
+		RegisterEncoder(tCodeWithScope, ValueEncoderFunc(dve.CodeWithScopeEncodeValue)).
+		RegisterDefaultEncoder(reflect.Bool, ValueEncoderFunc(dve.BooleanEncodeValue)).
+		RegisterDefaultEncoder(reflect.Int, ValueEncoderFunc(dve.IntEncodeValue)).
+		RegisterDefaultEncoder(reflect.Int8, ValueEncoderFunc(dve.IntEncodeValue)).
+		RegisterDefaultEncoder(reflect.Int16, ValueEncoderFunc(dve.IntEncodeValue)).
+		RegisterDefaultEncoder(reflect.Int32, ValueEncoderFunc(dve.IntEncodeValue)).
+		RegisterDefaultEncoder(reflect.Int64, ValueEncoderFunc(dve.IntEncodeValue)).
+		RegisterDefaultEncoder(reflect.Uint, ValueEncoderFunc(dve.UintEncodeValue)).
+		RegisterDefaultEncoder(reflect.Uint8, ValueEncoderFunc(dve.UintEncodeValue)).
+		RegisterDefaultEncoder(reflect.Uint16, ValueEncoderFunc(dve.UintEncodeValue)).
+		RegisterDefaultEncoder(reflect.Uint32, ValueEncoderFunc(dve.UintEncodeValue)).
+		RegisterDefaultEncoder(reflect.Uint64, ValueEncoderFunc(dve.UintEncodeValue)).
+		RegisterDefaultEncoder(reflect.Float32, ValueEncoderFunc(dve.FloatEncodeValue)).
+		RegisterDefaultEncoder(reflect.Float64, ValueEncoderFunc(dve.FloatEncodeValue)).
+		RegisterDefaultEncoder(reflect.Array, ValueEncoderFunc(dve.ArrayEncodeValue)).
+		RegisterDefaultEncoder(reflect.Map, ValueEncoderFunc(dve.MapEncodeValue)).
+		RegisterDefaultEncoder(reflect.Slice, ValueEncoderFunc(dve.SliceEncodeValue)).
+		RegisterDefaultEncoder(reflect.String, ValueEncoderFunc(dve.StringEncodeValue)).
+		RegisterDefaultEncoder(reflect.Struct, &StructCodec{cache: make(map[reflect.Type]*structDescription), parser: DefaultStructTagParser}).
+		RegisterDefaultEncoder(reflect.Ptr, NewPointerCodec())
+}
+
+// BooleanEncodeValue is the ValueEncoderFunc for bool types.
+func (dve DefaultValueEncoders) BooleanEncodeValue(ectx EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
+	if !val.IsValid() || val.Kind() != reflect.Bool {
+		return ValueEncoderError{Name: "BooleanEncodeValue", Kinds: []reflect.Kind{reflect.Bool}, Received: val}
+	}
+	return vw.WriteBoolean(val.Bool())
+}
+
+func fitsIn32Bits(i int64) bool {
+	return math.MinInt32 <= i && i <= math.MaxInt32
+}
+
+// IntEncodeValue is the ValueEncoderFunc for int types.
+func (dve DefaultValueEncoders) IntEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
+	switch val.Kind() {
+	case reflect.Int8, reflect.Int16, reflect.Int32:
+		return vw.WriteInt32(int32(val.Int()))
+	case reflect.Int:
+		i64 := val.Int()
+		if fitsIn32Bits(i64) {
+			return vw.WriteInt32(int32(i64))
+		}
+		return vw.WriteInt64(i64)
+	case reflect.Int64:
+		i64 := val.Int()
+		if ec.MinSize && fitsIn32Bits(i64) {
+			return vw.WriteInt32(int32(i64))
+		}
+		return vw.WriteInt64(i64)
+	}
+
+	return ValueEncoderError{
+		Name:     "IntEncodeValue",
+		Kinds:    []reflect.Kind{reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int},
+		Received: val,
+	}
+}
+
+// UintEncodeValue is the ValueEncoderFunc for uint types.
+func (dve DefaultValueEncoders) UintEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
+	switch val.Kind() {
+	case reflect.Uint8, reflect.Uint16:
+		return vw.WriteInt32(int32(val.Uint()))
+	case reflect.Uint, reflect.Uint32, reflect.Uint64:
+		u64 := val.Uint()
+		if ec.MinSize && u64 <= math.MaxInt32 {
+			return vw.WriteInt32(int32(u64))
+		}
+		if u64 > math.MaxInt64 {
+			return fmt.Errorf("%d overflows int64", u64)
+		}
+		return vw.WriteInt64(int64(u64))
+	}
+
+	return ValueEncoderError{
+		Name:     "UintEncodeValue",
+		Kinds:    []reflect.Kind{reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint},
+		Received: val,
+	}
+}
+
+// FloatEncodeValue is the ValueEncoderFunc for float types.
+func (dve DefaultValueEncoders) FloatEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
+	switch val.Kind() {
+	case reflect.Float32, reflect.Float64:
+		return vw.WriteDouble(val.Float())
+	}
+
+	return ValueEncoderError{Name: "FloatEncodeValue", Kinds: []reflect.Kind{reflect.Float32, reflect.Float64}, Received: val}
+}
+
+// StringEncodeValue is the ValueEncoderFunc for string types.
+func (dve DefaultValueEncoders) StringEncodeValue(ectx EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
+	if val.Kind() != reflect.String {
+		return ValueEncoderError{
+			Name:     "StringEncodeValue",
+			Kinds:    []reflect.Kind{reflect.String},
+			Received: val,
+		}
+	}
+
+	return vw.WriteString(val.String())
+}
+
+// ObjectIDEncodeValue is the ValueEncoderFunc for primitive.ObjectID.
+func (dve DefaultValueEncoders) ObjectIDEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
+	if !val.IsValid() || val.Type() != tOID {
+		return ValueEncoderError{Name: "ObjectIDEncodeValue", Types: []reflect.Type{tOID}, Received: val}
+	}
+	return vw.WriteObjectID(val.Interface().(primitive.ObjectID))
+}
+
+// Decimal128EncodeValue is the ValueEncoderFunc for primitive.Decimal128.
+func (dve DefaultValueEncoders) Decimal128EncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
+	if !val.IsValid() || val.Type() != tDecimal {
+		return ValueEncoderError{Name: "Decimal128EncodeValue", Types: []reflect.Type{tDecimal}, Received: val}
+	}
+	return vw.WriteDecimal128(val.Interface().(primitive.Decimal128))
+}
+
+// JSONNumberEncodeValue is the ValueEncoderFunc for json.Number.
+func (dve DefaultValueEncoders) JSONNumberEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
+	if !val.IsValid() || val.Type() != tJSONNumber {
+		return ValueEncoderError{Name: "JSONNumberEncodeValue", Types: []reflect.Type{tJSONNumber}, Received: val}
+	}
+	jsnum := val.Interface().(json.Number)
+
+	// Attempt int first, then float64
+	if i64, err := jsnum.Int64(); err == nil {
+		return dve.IntEncodeValue(ec, vw, reflect.ValueOf(i64))
+	}
+
+	f64, err := jsnum.Float64()
+	if err != nil {
+		return err
+	}
+
+	return dve.FloatEncodeValue(ec, vw, reflect.ValueOf(f64))
+}
+
+// URLEncodeValue is the ValueEncoderFunc for url.URL.
+func (dve DefaultValueEncoders) URLEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
+	if !val.IsValid() || val.Type() != tURL {
+		return ValueEncoderError{Name: "URLEncodeValue", Types: []reflect.Type{tURL}, Received: val}
+	}
+	u := val.Interface().(url.URL)
+	return vw.WriteString(u.String())
+}
+
+// TimeEncodeValue is the ValueEncoderFunc for time.TIme.
+func (dve DefaultValueEncoders) TimeEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
+	if !val.IsValid() || val.Type() != tTime {
+		return ValueEncoderError{Name: "TimeEncodeValue", Types: []reflect.Type{tTime}, Received: val}
+	}
+	tt := val.Interface().(time.Time)
+	return vw.WriteDateTime(tt.Unix()*1000 + int64(tt.Nanosecond()/1e6))
+}
+
+// ByteSliceEncodeValue is the ValueEncoderFunc for []byte.
+func (dve DefaultValueEncoders) ByteSliceEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
+	if !val.IsValid() || val.Type() != tByteSlice {
+		return ValueEncoderError{Name: "ByteSliceEncodeValue", Types: []reflect.Type{tByteSlice}, Received: val}
+	}
+	if val.IsNil() {
+		return vw.WriteNull()
+	}
+	return vw.WriteBinary(val.Interface().([]byte))
+}
+
+// MapEncodeValue is the ValueEncoderFunc for map[string]* types.
+func (dve DefaultValueEncoders) MapEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
+	if !val.IsValid() || val.Kind() != reflect.Map || val.Type().Key().Kind() != reflect.String {
+		return ValueEncoderError{Name: "MapEncodeValue", Kinds: []reflect.Kind{reflect.Map}, Received: val}
+	}
+
+	if val.IsNil() {
+		// If we have a nill map but we can't WriteNull, that means we're probably trying to encode
+		// to a TopLevel document. We can't currently tell if this is what actually happened, but if
+		// there's a deeper underlying problem, the error will also be returned from WriteDocument,
+		// so just continue. The operations on a map reflection value are valid, so we can call
+		// MapKeys within mapEncodeValue without a problem.
+		err := vw.WriteNull()
+		if err == nil {
+			return nil
+		}
+	}
+
+	dw, err := vw.WriteDocument()
+	if err != nil {
+		return err
+	}
+
+	return dve.mapEncodeValue(ec, dw, val, nil)
+}
+
+// mapEncodeValue handles encoding of the values of a map. The collisionFn returns
+// true if the provided key exists, this is mainly used for inline maps in the
+// struct codec.
+func (dve DefaultValueEncoders) mapEncodeValue(ec EncodeContext, dw bsonrw.DocumentWriter, val reflect.Value, collisionFn func(string) bool) error {
+
+	encoder, err := ec.LookupEncoder(val.Type().Elem())
+	if err != nil {
+		return err
+	}
+
+	keys := val.MapKeys()
+	for _, key := range keys {
+		if collisionFn != nil && collisionFn(key.String()) {
+			return fmt.Errorf("Key %s of inlined map conflicts with a struct field name", key)
+		}
+		vw, err := dw.WriteDocumentElement(key.String())
+		if err != nil {
+			return err
+		}
+
+		if enc, ok := encoder.(ValueEncoder); ok {
+			err = enc.EncodeValue(ec, vw, val.MapIndex(key))
+			if err != nil {
+				return err
+			}
+			continue
+		}
+		err = encoder.EncodeValue(ec, vw, val.MapIndex(key))
+		if err != nil {
+			return err
+		}
+	}
+
+	return dw.WriteDocumentEnd()
+}
+
+// ArrayEncodeValue is the ValueEncoderFunc for array types.
+func (dve DefaultValueEncoders) ArrayEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
+	if !val.IsValid() || val.Kind() != reflect.Array {
+		return ValueEncoderError{Name: "ArrayEncodeValue", Kinds: []reflect.Kind{reflect.Array}, Received: val}
+	}
+
+	// If we have a []primitive.E we want to treat it as a document instead of as an array.
+	if val.Type().Elem() == tE {
+		dw, err := vw.WriteDocument()
+		if err != nil {
+			return err
+		}
+
+		for idx := 0; idx < val.Len(); idx++ {
+			e := val.Index(idx).Interface().(primitive.E)
+			err = encodeElement(ec, dw, e)
+			if err != nil {
+				return err
+			}
+		}
+
+		return dw.WriteDocumentEnd()
+	}
+
+	aw, err := vw.WriteArray()
+	if err != nil {
+		return err
+	}
+
+	encoder, err := ec.LookupEncoder(val.Type().Elem())
+	if err != nil {
+		return err
+	}
+
+	for idx := 0; idx < val.Len(); idx++ {
+		vw, err := aw.WriteArrayElement()
+		if err != nil {
+			return err
+		}
+
+		err = encoder.EncodeValue(ec, vw, val.Index(idx))
+		if err != nil {
+			return err
+		}
+	}
+	return aw.WriteArrayEnd()
+}
+
+// SliceEncodeValue is the ValueEncoderFunc for slice types.
+func (dve DefaultValueEncoders) SliceEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
+	if !val.IsValid() || val.Kind() != reflect.Slice {
+		return ValueEncoderError{Name: "SliceEncodeValue", Kinds: []reflect.Kind{reflect.Slice}, Received: val}
+	}
+
+	if val.IsNil() {
+		return vw.WriteNull()
+	}
+
+	// If we have a []primitive.E we want to treat it as a document instead of as an array.
+	if val.Type().ConvertibleTo(tD) {
+		d := val.Convert(tD).Interface().(primitive.D)
+
+		dw, err := vw.WriteDocument()
+		if err != nil {
+			return err
+		}
+
+		for _, e := range d {
+			err = encodeElement(ec, dw, e)
+			if err != nil {
+				return err
+			}
+		}
+
+		return dw.WriteDocumentEnd()
+	}
+
+	aw, err := vw.WriteArray()
+	if err != nil {
+		return err
+	}
+
+	encoder, err := ec.LookupEncoder(val.Type().Elem())
+	if err != nil {
+		return err
+	}
+
+	for idx := 0; idx < val.Len(); idx++ {
+		vw, err := aw.WriteArrayElement()
+		if err != nil {
+			return err
+		}
+
+		err = encoder.EncodeValue(ec, vw, val.Index(idx))
+		if err != nil {
+			return err
+		}
+	}
+	return aw.WriteArrayEnd()
+}
+
+// EmptyInterfaceEncodeValue is the ValueEncoderFunc for interface{}.
+func (dve DefaultValueEncoders) EmptyInterfaceEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
+	if !val.IsValid() || val.Type() != tEmpty {
+		return ValueEncoderError{Name: "EmptyInterfaceEncodeValue", Types: []reflect.Type{tEmpty}, Received: val}
+	}
+
+	if val.IsNil() {
+		return vw.WriteNull()
+	}
+	encoder, err := ec.LookupEncoder(val.Elem().Type())
+	if err != nil {
+		return err
+	}
+
+	return encoder.EncodeValue(ec, vw, val.Elem())
+}
+
+// ValueMarshalerEncodeValue is the ValueEncoderFunc for ValueMarshaler implementations.
+func (dve DefaultValueEncoders) ValueMarshalerEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
+	if !val.IsValid() || !val.Type().Implements(tValueMarshaler) {
+		return ValueEncoderError{Name: "ValueMarshalerEncodeValue", Types: []reflect.Type{tValueMarshaler}, Received: val}
+	}
+
+	fn := val.Convert(tValueMarshaler).MethodByName("MarshalBSONValue")
+	returns := fn.Call(nil)
+	if !returns[2].IsNil() {
+		return returns[2].Interface().(error)
+	}
+	t, data := returns[0].Interface().(bsontype.Type), returns[1].Interface().([]byte)
+	return bsonrw.Copier{}.CopyValueFromBytes(vw, t, data)
+}
+
+// MarshalerEncodeValue is the ValueEncoderFunc for Marshaler implementations.
+func (dve DefaultValueEncoders) MarshalerEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
+	if !val.IsValid() || !val.Type().Implements(tMarshaler) {
+		return ValueEncoderError{Name: "MarshalerEncodeValue", Types: []reflect.Type{tMarshaler}, Received: val}
+	}
+
+	fn := val.Convert(tMarshaler).MethodByName("MarshalBSON")
+	returns := fn.Call(nil)
+	if !returns[1].IsNil() {
+		return returns[1].Interface().(error)
+	}
+	data := returns[0].Interface().([]byte)
+	return bsonrw.Copier{}.CopyValueFromBytes(vw, bsontype.EmbeddedDocument, data)
+}
+
+// ProxyEncodeValue is the ValueEncoderFunc for Proxy implementations.
+func (dve DefaultValueEncoders) ProxyEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
+	if !val.IsValid() || !val.Type().Implements(tProxy) {
+		return ValueEncoderError{Name: "ProxyEncodeValue", Types: []reflect.Type{tProxy}, Received: val}
+	}
+
+	fn := val.Convert(tProxy).MethodByName("ProxyBSON")
+	returns := fn.Call(nil)
+	if !returns[1].IsNil() {
+		return returns[1].Interface().(error)
+	}
+	data := returns[0]
+	var encoder ValueEncoder
+	var err error
+	if data.Elem().IsValid() {
+		encoder, err = ec.LookupEncoder(data.Elem().Type())
+	} else {
+		encoder, err = ec.LookupEncoder(nil)
+	}
+	if err != nil {
+		return err
+	}
+	return encoder.EncodeValue(ec, vw, data.Elem())
+}
+
+// JavaScriptEncodeValue is the ValueEncoderFunc for the primitive.JavaScript type.
+func (DefaultValueEncoders) JavaScriptEncodeValue(ectx EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
+	if !val.IsValid() || val.Type() != tJavaScript {
+		return ValueEncoderError{Name: "JavaScriptEncodeValue", Types: []reflect.Type{tJavaScript}, Received: val}
+	}
+
+	return vw.WriteJavascript(val.String())
+}
+
+// SymbolEncodeValue is the ValueEncoderFunc for the primitive.Symbol type.
+func (DefaultValueEncoders) SymbolEncodeValue(ectx EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
+	if !val.IsValid() || val.Type() != tSymbol {
+		return ValueEncoderError{Name: "SymbolEncodeValue", Types: []reflect.Type{tSymbol}, Received: val}
+	}
+
+	return vw.WriteSymbol(val.String())
+}
+
+// BinaryEncodeValue is the ValueEncoderFunc for Binary.
+func (DefaultValueEncoders) BinaryEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
+	if !val.IsValid() || val.Type() != tBinary {
+		return ValueEncoderError{Name: "BinaryEncodeValue", Types: []reflect.Type{tBinary}, Received: val}
+	}
+	b := val.Interface().(primitive.Binary)
+
+	return vw.WriteBinaryWithSubtype(b.Data, b.Subtype)
+}
+
+// UndefinedEncodeValue is the ValueEncoderFunc for Undefined.
+func (DefaultValueEncoders) UndefinedEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
+	if !val.IsValid() || val.Type() != tUndefined {
+		return ValueEncoderError{Name: "UndefinedEncodeValue", Types: []reflect.Type{tUndefined}, Received: val}
+	}
+
+	return vw.WriteUndefined()
+}
+
+// DateTimeEncodeValue is the ValueEncoderFunc for DateTime.
+func (DefaultValueEncoders) DateTimeEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
+	if !val.IsValid() || val.Type() != tDateTime {
+		return ValueEncoderError{Name: "DateTimeEncodeValue", Types: []reflect.Type{tDateTime}, Received: val}
+	}
+
+	return vw.WriteDateTime(val.Int())
+}
+
+// NullEncodeValue is the ValueEncoderFunc for Null.
+func (DefaultValueEncoders) NullEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
+	if !val.IsValid() || val.Type() != tNull {
+		return ValueEncoderError{Name: "NullEncodeValue", Types: []reflect.Type{tNull}, Received: val}
+	}
+
+	return vw.WriteNull()
+}
+
+// RegexEncodeValue is the ValueEncoderFunc for Regex.
+func (DefaultValueEncoders) RegexEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
+	if !val.IsValid() || val.Type() != tRegex {
+		return ValueEncoderError{Name: "RegexEncodeValue", Types: []reflect.Type{tRegex}, Received: val}
+	}
+
+	regex := val.Interface().(primitive.Regex)
+
+	return vw.WriteRegex(regex.Pattern, regex.Options)
+}
+
+// DBPointerEncodeValue is the ValueEncoderFunc for DBPointer.
+func (DefaultValueEncoders) DBPointerEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
+	if !val.IsValid() || val.Type() != tDBPointer {
+		return ValueEncoderError{Name: "DBPointerEncodeValue", Types: []reflect.Type{tDBPointer}, Received: val}
+	}
+
+	dbp := val.Interface().(primitive.DBPointer)
+
+	return vw.WriteDBPointer(dbp.DB, dbp.Pointer)
+}
+
+// TimestampEncodeValue is the ValueEncoderFunc for Timestamp.
+func (DefaultValueEncoders) TimestampEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
+	if !val.IsValid() || val.Type() != tTimestamp {
+		return ValueEncoderError{Name: "TimestampEncodeValue", Types: []reflect.Type{tTimestamp}, Received: val}
+	}
+
+	ts := val.Interface().(primitive.Timestamp)
+
+	return vw.WriteTimestamp(ts.T, ts.I)
+}
+
+// MinKeyEncodeValue is the ValueEncoderFunc for MinKey.
+func (DefaultValueEncoders) MinKeyEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
+	if !val.IsValid() || val.Type() != tMinKey {
+		return ValueEncoderError{Name: "MinKeyEncodeValue", Types: []reflect.Type{tMinKey}, Received: val}
+	}
+
+	return vw.WriteMinKey()
+}
+
+// MaxKeyEncodeValue is the ValueEncoderFunc for MaxKey.
+func (DefaultValueEncoders) MaxKeyEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
+	if !val.IsValid() || val.Type() != tMaxKey {
+		return ValueEncoderError{Name: "MaxKeyEncodeValue", Types: []reflect.Type{tMaxKey}, Received: val}
+	}
+
+	return vw.WriteMaxKey()
+}
+
+// CoreDocumentEncodeValue is the ValueEncoderFunc for bsoncore.Document.
+func (DefaultValueEncoders) CoreDocumentEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
+	if !val.IsValid() || val.Type() != tCoreDocument {
+		return ValueEncoderError{Name: "CoreDocumentEncodeValue", Types: []reflect.Type{tCoreDocument}, Received: val}
+	}
+
+	cdoc := val.Interface().(bsoncore.Document)
+
+	return bsonrw.Copier{}.CopyDocumentFromBytes(vw, cdoc)
+}
+
+// CodeWithScopeEncodeValue is the ValueEncoderFunc for CodeWithScope.
+func (dve DefaultValueEncoders) CodeWithScopeEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
+	if !val.IsValid() || val.Type() != tCodeWithScope {
+		return ValueEncoderError{Name: "CodeWithScopeEncodeValue", Types: []reflect.Type{tCodeWithScope}, Received: val}
+	}
+
+	cws := val.Interface().(primitive.CodeWithScope)
+
+	dw, err := vw.WriteCodeWithScope(string(cws.Code))
+	if err != nil {
+		return err
+	}
+
+	sw := sliceWriterPool.Get().(*bsonrw.SliceWriter)
+	defer sliceWriterPool.Put(sw)
+	*sw = (*sw)[:0]
+
+	scopeVW := bvwPool.Get(sw)
+	defer bvwPool.Put(scopeVW)
+
+	encoder, err := ec.LookupEncoder(reflect.TypeOf(cws.Scope))
+	if err != nil {
+		return err
+	}
+
+	err = encoder.EncodeValue(ec, scopeVW, reflect.ValueOf(cws.Scope))
+	if err != nil {
+		return err
+	}
+
+	err = bsonrw.Copier{}.CopyBytesToDocumentWriter(dw, *sw)
+	if err != nil {
+		return err
+	}
+	return dw.WriteDocumentEnd()
+}

+ 1436 - 0
src/go.mongodb.org/mongo-driver/bson/bsoncodec/default_value_encoders_test.go

@@ -0,0 +1,1436 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+package bsoncodec
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"net/url"
+	"reflect"
+	"testing"
+	"time"
+
+	"github.com/google/go-cmp/cmp"
+	"go.mongodb.org/mongo-driver/bson/bsonrw"
+	"go.mongodb.org/mongo-driver/bson/bsonrw/bsonrwtest"
+	"go.mongodb.org/mongo-driver/bson/bsontype"
+	"go.mongodb.org/mongo-driver/bson/primitive"
+	"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
+	"math"
+)
+
+func TestDefaultValueEncoders(t *testing.T) {
+	var dve DefaultValueEncoders
+	var wrong = func(string, string) string { return "wrong" }
+
+	type mybool bool
+	type myint8 int8
+	type myint16 int16
+	type myint32 int32
+	type myint64 int64
+	type myint int
+	type myuint8 uint8
+	type myuint16 uint16
+	type myuint32 uint32
+	type myuint64 uint64
+	type myuint uint
+	type myfloat32 float32
+	type myfloat64 float64
+	type mystring string
+
+	now := time.Now().Truncate(time.Millisecond)
+	pjsnum := new(json.Number)
+	*pjsnum = json.Number("3.14159")
+	d128 := primitive.NewDecimal128(12345, 67890)
+
+	type subtest struct {
+		name   string
+		val    interface{}
+		ectx   *EncodeContext
+		llvrw  *bsonrwtest.ValueReaderWriter
+		invoke bsonrwtest.Invoked
+		err    error
+	}
+
+	testCases := []struct {
+		name     string
+		ve       ValueEncoder
+		subtests []subtest
+	}{
+		{
+			"BooleanEncodeValue",
+			ValueEncoderFunc(dve.BooleanEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "BooleanEncodeValue", Kinds: []reflect.Kind{reflect.Bool}, Received: reflect.ValueOf(wrong)},
+				},
+				{"fast path", bool(true), nil, nil, bsonrwtest.WriteBoolean, nil},
+				{"reflection path", mybool(true), nil, nil, bsonrwtest.WriteBoolean, nil},
+			},
+		},
+		{
+			"IntEncodeValue",
+			ValueEncoderFunc(dve.IntEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{
+						Name:     "IntEncodeValue",
+						Kinds:    []reflect.Kind{reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int},
+						Received: reflect.ValueOf(wrong),
+					},
+				},
+				{"int8/fast path", int8(127), nil, nil, bsonrwtest.WriteInt32, nil},
+				{"int16/fast path", int16(32767), nil, nil, bsonrwtest.WriteInt32, nil},
+				{"int32/fast path", int32(2147483647), nil, nil, bsonrwtest.WriteInt32, nil},
+				{"int64/fast path", int64(1234567890987), nil, nil, bsonrwtest.WriteInt64, nil},
+				{"int64/fast path - minsize", int64(math.MaxInt32), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt32, nil},
+				{"int64/fast path - minsize too large", int64(math.MaxInt32 + 1), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt64, nil},
+				{"int64/fast path - minsize too small", int64(math.MinInt32 - 1), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt64, nil},
+				{"int/fast path - positive int32", int(math.MaxInt32 - 1), nil, nil, bsonrwtest.WriteInt32, nil},
+				{"int/fast path - negative int32", int(math.MinInt32 + 1), nil, nil, bsonrwtest.WriteInt32, nil},
+				{"int/fast path - MaxInt32", int(math.MaxInt32), nil, nil, bsonrwtest.WriteInt32, nil},
+				{"int/fast path - MinInt32", int(math.MinInt32), nil, nil, bsonrwtest.WriteInt32, nil},
+				{"int/fast path - larger than MaxInt32", int(math.MaxInt32 + 1), nil, nil, bsonrwtest.WriteInt64, nil},
+				{"int/fast path - smaller than MinInt32", int(math.MinInt32 - 1), nil, nil, bsonrwtest.WriteInt64, nil},
+				{"int8/reflection path", myint8(127), nil, nil, bsonrwtest.WriteInt32, nil},
+				{"int16/reflection path", myint16(32767), nil, nil, bsonrwtest.WriteInt32, nil},
+				{"int32/reflection path", myint32(2147483647), nil, nil, bsonrwtest.WriteInt32, nil},
+				{"int64/reflection path", myint64(1234567890987), nil, nil, bsonrwtest.WriteInt64, nil},
+				{"int64/reflection path - minsize", myint64(math.MaxInt32), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt32, nil},
+				{"int64/reflection path - minsize too large", myint64(math.MaxInt32 + 1), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt64, nil},
+				{"int64/reflection path - minsize too small", myint64(math.MinInt32 - 1), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt64, nil},
+				{"int/reflection path - positive int32", myint(math.MaxInt32 - 1), nil, nil, bsonrwtest.WriteInt32, nil},
+				{"int/reflection path - negative int32", myint(math.MinInt32 + 1), nil, nil, bsonrwtest.WriteInt32, nil},
+				{"int/reflection path - MaxInt32", myint(math.MaxInt32), nil, nil, bsonrwtest.WriteInt32, nil},
+				{"int/reflection path - MinInt32", myint(math.MinInt32), nil, nil, bsonrwtest.WriteInt32, nil},
+				{"int/reflection path - larger than MaxInt32", myint(math.MaxInt32 + 1), nil, nil, bsonrwtest.WriteInt64, nil},
+				{"int/reflection path - smaller than MinInt32", myint(math.MinInt32 - 1), nil, nil, bsonrwtest.WriteInt64, nil},
+			},
+		},
+		{
+			"UintEncodeValue",
+			ValueEncoderFunc(dve.UintEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{
+						Name:     "UintEncodeValue",
+						Kinds:    []reflect.Kind{reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint},
+						Received: reflect.ValueOf(wrong),
+					},
+				},
+				{"uint8/fast path", uint8(127), nil, nil, bsonrwtest.WriteInt32, nil},
+				{"uint16/fast path", uint16(32767), nil, nil, bsonrwtest.WriteInt32, nil},
+				{"uint32/fast path", uint32(2147483647), nil, nil, bsonrwtest.WriteInt64, nil},
+				{"uint64/fast path", uint64(1234567890987), nil, nil, bsonrwtest.WriteInt64, nil},
+				{"uint/fast path", uint(1234567), nil, nil, bsonrwtest.WriteInt64, nil},
+				{"uint32/fast path - minsize", uint32(2147483647), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt32, nil},
+				{"uint64/fast path - minsize", uint64(2147483647), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt32, nil},
+				{"uint/fast path - minsize", uint(2147483647), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt32, nil},
+				{"uint32/fast path - minsize too large", uint32(2147483648), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt64, nil},
+				{"uint64/fast path - minsize too large", uint64(2147483648), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt64, nil},
+				{"uint/fast path - minsize too large", uint(2147483648), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt64, nil},
+				{"uint64/fast path - overflow", uint64(1 << 63), nil, nil, bsonrwtest.Nothing, fmt.Errorf("%d overflows int64", uint(1<<63))},
+				{"uint/fast path - overflow", uint(1 << 63), nil, nil, bsonrwtest.Nothing, fmt.Errorf("%d overflows int64", uint(1<<63))},
+				{"uint8/reflection path", myuint8(127), nil, nil, bsonrwtest.WriteInt32, nil},
+				{"uint16/reflection path", myuint16(32767), nil, nil, bsonrwtest.WriteInt32, nil},
+				{"uint32/reflection path", myuint32(2147483647), nil, nil, bsonrwtest.WriteInt64, nil},
+				{"uint64/reflection path", myuint64(1234567890987), nil, nil, bsonrwtest.WriteInt64, nil},
+				{"uint/reflection path", myuint(1234567890987), nil, nil, bsonrwtest.WriteInt64, nil},
+				{"uint32/reflection path - minsize", myuint32(2147483647), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt32, nil},
+				{"uint64/reflection path - minsize", myuint64(2147483647), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt32, nil},
+				{"uint/reflection path - minsize", myuint(2147483647), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt32, nil},
+				{"uint32/reflection path - minsize too large", myuint(1 << 31), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt64, nil},
+				{"uint64/reflection path - minsize too large", myuint64(1 << 31), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt64, nil},
+				{"uint/reflection path - minsize too large", myuint(2147483648), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt64, nil},
+				{"uint64/reflection path - overflow", myuint64(1 << 63), nil, nil, bsonrwtest.Nothing, fmt.Errorf("%d overflows int64", uint(1<<63))},
+				{"uint/reflection path - overflow", myuint(1 << 63), nil, nil, bsonrwtest.Nothing, fmt.Errorf("%d overflows int64", uint(1<<63))},
+			},
+		},
+		{
+			"FloatEncodeValue",
+			ValueEncoderFunc(dve.FloatEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{
+						Name:     "FloatEncodeValue",
+						Kinds:    []reflect.Kind{reflect.Float32, reflect.Float64},
+						Received: reflect.ValueOf(wrong),
+					},
+				},
+				{"float32/fast path", float32(3.14159), nil, nil, bsonrwtest.WriteDouble, nil},
+				{"float64/fast path", float64(3.14159), nil, nil, bsonrwtest.WriteDouble, nil},
+				{"float32/reflection path", myfloat32(3.14159), nil, nil, bsonrwtest.WriteDouble, nil},
+				{"float64/reflection path", myfloat64(3.14159), nil, nil, bsonrwtest.WriteDouble, nil},
+			},
+		},
+		{
+			"TimeEncodeValue",
+			ValueEncoderFunc(dve.TimeEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "TimeEncodeValue", Types: []reflect.Type{tTime}, Received: reflect.ValueOf(wrong)},
+				},
+				{"time.Time", now, nil, nil, bsonrwtest.WriteDateTime, nil},
+			},
+		},
+		{
+			"MapEncodeValue",
+			ValueEncoderFunc(dve.MapEncodeValue),
+			[]subtest{
+				{
+					"wrong kind",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "MapEncodeValue", Kinds: []reflect.Kind{reflect.Map}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"wrong kind (non-string key)",
+					map[int]interface{}{},
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{
+						Name:     "MapEncodeValue",
+						Kinds:    []reflect.Kind{reflect.Map},
+						Received: reflect.ValueOf(map[int]interface{}{}),
+					},
+				},
+				{
+					"WriteDocument Error",
+					map[string]interface{}{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{Err: errors.New("wd error"), ErrAfter: bsonrwtest.WriteDocument},
+					bsonrwtest.WriteDocument,
+					errors.New("wd error"),
+				},
+				{
+					"Lookup Error",
+					map[string]interface{}{},
+					&EncodeContext{Registry: NewRegistryBuilder().Build()},
+					&bsonrwtest.ValueReaderWriter{},
+					bsonrwtest.WriteDocument,
+					ErrNoEncoder{Type: reflect.TypeOf((*interface{})(nil)).Elem()},
+				},
+				{
+					"WriteDocumentElement Error",
+					map[string]interface{}{"foo": "bar"},
+					&EncodeContext{Registry: buildDefaultRegistry()},
+					&bsonrwtest.ValueReaderWriter{Err: errors.New("wde error"), ErrAfter: bsonrwtest.WriteDocumentElement},
+					bsonrwtest.WriteDocumentElement,
+					errors.New("wde error"),
+				},
+				{
+					"EncodeValue Error",
+					map[string]interface{}{"foo": "bar"},
+					&EncodeContext{Registry: buildDefaultRegistry()},
+					&bsonrwtest.ValueReaderWriter{Err: errors.New("ev error"), ErrAfter: bsonrwtest.WriteString},
+					bsonrwtest.WriteString,
+					errors.New("ev error"),
+				},
+			},
+		},
+		{
+			"ArrayEncodeValue",
+			ValueEncoderFunc(dve.ArrayEncodeValue),
+			[]subtest{
+				{
+					"wrong kind",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "ArrayEncodeValue", Kinds: []reflect.Kind{reflect.Array}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"WriteArray Error",
+					[1]string{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{Err: errors.New("wa error"), ErrAfter: bsonrwtest.WriteArray},
+					bsonrwtest.WriteArray,
+					errors.New("wa error"),
+				},
+				{
+					"Lookup Error",
+					[1]interface{}{},
+					&EncodeContext{Registry: NewRegistryBuilder().Build()},
+					&bsonrwtest.ValueReaderWriter{},
+					bsonrwtest.WriteArray,
+					ErrNoEncoder{Type: reflect.TypeOf((*interface{})(nil)).Elem()},
+				},
+				{
+					"WriteArrayElement Error",
+					[1]string{"foo"},
+					&EncodeContext{Registry: buildDefaultRegistry()},
+					&bsonrwtest.ValueReaderWriter{Err: errors.New("wae error"), ErrAfter: bsonrwtest.WriteArrayElement},
+					bsonrwtest.WriteArrayElement,
+					errors.New("wae error"),
+				},
+				{
+					"EncodeValue Error",
+					[1]string{"foo"},
+					&EncodeContext{Registry: buildDefaultRegistry()},
+					&bsonrwtest.ValueReaderWriter{Err: errors.New("ev error"), ErrAfter: bsonrwtest.WriteString},
+					bsonrwtest.WriteString,
+					errors.New("ev error"),
+				},
+				{
+					"[1]primitive.E/success",
+					[1]primitive.E{{"hello", "world"}},
+					&EncodeContext{Registry: buildDefaultRegistry()},
+					nil,
+					bsonrwtest.WriteDocumentEnd,
+					nil,
+				},
+				{
+					"[1]primitive.E/success",
+					[1]primitive.E{{"hello", nil}},
+					&EncodeContext{Registry: buildDefaultRegistry()},
+					nil,
+					bsonrwtest.WriteDocumentEnd,
+					nil,
+				},
+			},
+		},
+		{
+			"SliceEncodeValue",
+			ValueEncoderFunc(dve.SliceEncodeValue),
+			[]subtest{
+				{
+					"wrong kind",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "SliceEncodeValue", Kinds: []reflect.Kind{reflect.Slice}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"WriteArray Error",
+					[]string{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{Err: errors.New("wa error"), ErrAfter: bsonrwtest.WriteArray},
+					bsonrwtest.WriteArray,
+					errors.New("wa error"),
+				},
+				{
+					"Lookup Error",
+					[]interface{}{},
+					&EncodeContext{Registry: NewRegistryBuilder().Build()},
+					&bsonrwtest.ValueReaderWriter{},
+					bsonrwtest.WriteArray,
+					ErrNoEncoder{Type: reflect.TypeOf((*interface{})(nil)).Elem()},
+				},
+				{
+					"WriteArrayElement Error",
+					[]string{"foo"},
+					&EncodeContext{Registry: buildDefaultRegistry()},
+					&bsonrwtest.ValueReaderWriter{Err: errors.New("wae error"), ErrAfter: bsonrwtest.WriteArrayElement},
+					bsonrwtest.WriteArrayElement,
+					errors.New("wae error"),
+				},
+				{
+					"EncodeValue Error",
+					[]string{"foo"},
+					&EncodeContext{Registry: buildDefaultRegistry()},
+					&bsonrwtest.ValueReaderWriter{Err: errors.New("ev error"), ErrAfter: bsonrwtest.WriteString},
+					bsonrwtest.WriteString,
+					errors.New("ev error"),
+				},
+				{
+					"D/success",
+					primitive.D{{"hello", "world"}},
+					&EncodeContext{Registry: buildDefaultRegistry()},
+					nil,
+					bsonrwtest.WriteDocumentEnd,
+					nil,
+				},
+				{
+					"D/success",
+					primitive.D{{"hello", nil}},
+					&EncodeContext{Registry: buildDefaultRegistry()},
+					nil,
+					bsonrwtest.WriteDocumentEnd,
+					nil,
+				},
+			},
+		},
+		{
+			"ObjectIDEncodeValue",
+			ValueEncoderFunc(dve.ObjectIDEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "ObjectIDEncodeValue", Types: []reflect.Type{tOID}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"primitive.ObjectID/success",
+					primitive.ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C},
+					nil, nil, bsonrwtest.WriteObjectID, nil,
+				},
+			},
+		},
+		{
+			"Decimal128EncodeValue",
+			ValueEncoderFunc(dve.Decimal128EncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "Decimal128EncodeValue", Types: []reflect.Type{tDecimal}, Received: reflect.ValueOf(wrong)},
+				},
+				{"Decimal128/success", d128, nil, nil, bsonrwtest.WriteDecimal128, nil},
+			},
+		},
+		{
+			"JSONNumberEncodeValue",
+			ValueEncoderFunc(dve.JSONNumberEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "JSONNumberEncodeValue", Types: []reflect.Type{tJSONNumber}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"json.Number/invalid",
+					json.Number("hello world"),
+					nil, nil, bsonrwtest.Nothing, errors.New(`strconv.ParseFloat: parsing "hello world": invalid syntax`),
+				},
+				{
+					"json.Number/int64/success",
+					json.Number("1234567890"),
+					nil, nil, bsonrwtest.WriteInt64, nil,
+				},
+				{
+					"json.Number/float64/success",
+					json.Number("3.14159"),
+					nil, nil, bsonrwtest.WriteDouble, nil,
+				},
+			},
+		},
+		{
+			"URLEncodeValue",
+			ValueEncoderFunc(dve.URLEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "URLEncodeValue", Types: []reflect.Type{tURL}, Received: reflect.ValueOf(wrong)},
+				},
+				{"url.URL", url.URL{Scheme: "http", Host: "example.com"}, nil, nil, bsonrwtest.WriteString, nil},
+			},
+		},
+		{
+			"ByteSliceEncodeValue",
+			ValueEncoderFunc(dve.ByteSliceEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "ByteSliceEncodeValue", Types: []reflect.Type{tByteSlice}, Received: reflect.ValueOf(wrong)},
+				},
+				{"[]byte", []byte{0x01, 0x02, 0x03}, nil, nil, bsonrwtest.WriteBinary, nil},
+				{"[]byte/nil", []byte(nil), nil, nil, bsonrwtest.WriteNull, nil},
+			},
+		},
+		{
+			"EmptyInterfaceEncodeValue",
+			ValueEncoderFunc(dve.EmptyInterfaceEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "EmptyInterfaceEncodeValue", Types: []reflect.Type{tEmpty}, Received: reflect.ValueOf(wrong)},
+				},
+			},
+		},
+		{
+			"ValueMarshalerEncodeValue",
+			ValueEncoderFunc(dve.ValueMarshalerEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{
+						Name:     "ValueMarshalerEncodeValue",
+						Types:    []reflect.Type{tValueMarshaler},
+						Received: reflect.ValueOf(wrong),
+					},
+				},
+				{
+					"MarshalBSONValue error",
+					testValueMarshaler{err: errors.New("mbsonv error")},
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					errors.New("mbsonv error"),
+				},
+				{
+					"Copy error",
+					testValueMarshaler{},
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					fmt.Errorf("Cannot copy unknown BSON type %s", bsontype.Type(0)),
+				},
+				{
+					"success",
+					testValueMarshaler{t: bsontype.String, buf: []byte{0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00}},
+					nil,
+					nil,
+					bsonrwtest.WriteString,
+					nil,
+				},
+			},
+		},
+		{
+			"MarshalerEncodeValue",
+			ValueEncoderFunc(dve.MarshalerEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "MarshalerEncodeValue", Types: []reflect.Type{tMarshaler}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"MarshalBSON error",
+					testMarshaler{err: errors.New("mbson error")},
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					errors.New("mbson error"),
+				},
+				{
+					"success",
+					testMarshaler{buf: bsoncore.BuildDocument(nil, bsoncore.AppendDoubleElement(nil, "pi", 3.14159))},
+					nil,
+					nil,
+					bsonrwtest.WriteDocumentEnd,
+					nil,
+				},
+			},
+		},
+		{
+			"ProxyEncodeValue",
+			ValueEncoderFunc(dve.ProxyEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "ProxyEncodeValue", Types: []reflect.Type{tProxy}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"Proxy error",
+					testProxy{err: errors.New("proxy error")},
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					errors.New("proxy error"),
+				},
+				{
+					"Lookup error",
+					testProxy{ret: nil},
+					&EncodeContext{Registry: buildDefaultRegistry()},
+					nil,
+					bsonrwtest.Nothing,
+					ErrNoEncoder{Type: nil},
+				},
+				{
+					"success",
+					testProxy{ret: int64(1234567890)},
+					&EncodeContext{Registry: buildDefaultRegistry()},
+					nil,
+					bsonrwtest.WriteInt64,
+					nil,
+				},
+			},
+		},
+		{
+			"PointerCodec.EncodeValue",
+			NewPointerCodec(),
+			[]subtest{
+				{
+					"nil",
+					nil,
+					nil,
+					nil,
+					bsonrwtest.WriteNull,
+					nil,
+				},
+				{
+					"not pointer",
+					int32(123456),
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "PointerCodec.EncodeValue", Kinds: []reflect.Kind{reflect.Ptr}, Received: reflect.ValueOf(int32(123456))},
+				},
+				{
+					"typed nil",
+					(*int32)(nil),
+					nil,
+					nil,
+					bsonrwtest.WriteNull,
+					nil,
+				},
+				{
+					"no encoder",
+					&wrong,
+					&EncodeContext{Registry: buildDefaultRegistry()},
+					nil,
+					bsonrwtest.Nothing,
+					ErrNoEncoder{Type: reflect.TypeOf(wrong)},
+				},
+			},
+		},
+		{
+			"JavaScriptEncodeValue",
+			ValueEncoderFunc(dve.JavaScriptEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "JavaScriptEncodeValue", Types: []reflect.Type{tJavaScript}, Received: reflect.ValueOf(wrong)},
+				},
+				{"JavaScript", primitive.JavaScript("foobar"), nil, nil, bsonrwtest.WriteJavascript, nil},
+			},
+		},
+		{
+			"SymbolEncodeValue",
+			ValueEncoderFunc(dve.SymbolEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "SymbolEncodeValue", Types: []reflect.Type{tSymbol}, Received: reflect.ValueOf(wrong)},
+				},
+				{"Symbol", primitive.Symbol("foobar"), nil, nil, bsonrwtest.WriteSymbol, nil},
+			},
+		},
+		{
+			"BinaryEncodeValue",
+			ValueEncoderFunc(dve.BinaryEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "BinaryEncodeValue", Types: []reflect.Type{tBinary}, Received: reflect.ValueOf(wrong)},
+				},
+				{"Binary/success", primitive.Binary{Data: []byte{0x01, 0x02}, Subtype: 0xFF}, nil, nil, bsonrwtest.WriteBinaryWithSubtype, nil},
+			},
+		},
+		{
+			"UndefinedEncodeValue",
+			ValueEncoderFunc(dve.UndefinedEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "UndefinedEncodeValue", Types: []reflect.Type{tUndefined}, Received: reflect.ValueOf(wrong)},
+				},
+				{"Undefined/success", primitive.Undefined{}, nil, nil, bsonrwtest.WriteUndefined, nil},
+			},
+		},
+		{
+			"DateTimeEncodeValue",
+			ValueEncoderFunc(dve.DateTimeEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "DateTimeEncodeValue", Types: []reflect.Type{tDateTime}, Received: reflect.ValueOf(wrong)},
+				},
+				{"DateTime/success", primitive.DateTime(1234567890), nil, nil, bsonrwtest.WriteDateTime, nil},
+			},
+		},
+		{
+			"NullEncodeValue",
+			ValueEncoderFunc(dve.NullEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "NullEncodeValue", Types: []reflect.Type{tNull}, Received: reflect.ValueOf(wrong)},
+				},
+				{"Null/success", primitive.Null{}, nil, nil, bsonrwtest.WriteNull, nil},
+			},
+		},
+		{
+			"RegexEncodeValue",
+			ValueEncoderFunc(dve.RegexEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "RegexEncodeValue", Types: []reflect.Type{tRegex}, Received: reflect.ValueOf(wrong)},
+				},
+				{"Regex/success", primitive.Regex{Pattern: "foo", Options: "bar"}, nil, nil, bsonrwtest.WriteRegex, nil},
+			},
+		},
+		{
+			"DBPointerEncodeValue",
+			ValueEncoderFunc(dve.DBPointerEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "DBPointerEncodeValue", Types: []reflect.Type{tDBPointer}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"DBPointer/success",
+					primitive.DBPointer{
+						DB:      "foobar",
+						Pointer: primitive.ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C},
+					},
+					nil, nil, bsonrwtest.WriteDBPointer, nil,
+				},
+			},
+		},
+		{
+			"TimestampEncodeValue",
+			ValueEncoderFunc(dve.TimestampEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "TimestampEncodeValue", Types: []reflect.Type{tTimestamp}, Received: reflect.ValueOf(wrong)},
+				},
+				{"Timestamp/success", primitive.Timestamp{T: 12345, I: 67890}, nil, nil, bsonrwtest.WriteTimestamp, nil},
+			},
+		},
+		{
+			"MinKeyEncodeValue",
+			ValueEncoderFunc(dve.MinKeyEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "MinKeyEncodeValue", Types: []reflect.Type{tMinKey}, Received: reflect.ValueOf(wrong)},
+				},
+				{"MinKey/success", primitive.MinKey{}, nil, nil, bsonrwtest.WriteMinKey, nil},
+			},
+		},
+		{
+			"MaxKeyEncodeValue",
+			ValueEncoderFunc(dve.MaxKeyEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "MaxKeyEncodeValue", Types: []reflect.Type{tMaxKey}, Received: reflect.ValueOf(wrong)},
+				},
+				{"MaxKey/success", primitive.MaxKey{}, nil, nil, bsonrwtest.WriteMaxKey, nil},
+			},
+		},
+		{
+			"CoreDocumentEncodeValue",
+			ValueEncoderFunc(dve.CoreDocumentEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{
+						Name:     "CoreDocumentEncodeValue",
+						Types:    []reflect.Type{tCoreDocument},
+						Received: reflect.ValueOf(wrong),
+					},
+				},
+				{
+					"WriteDocument Error",
+					bsoncore.Document{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{Err: errors.New("wd error"), ErrAfter: bsonrwtest.WriteDocument},
+					bsonrwtest.WriteDocument,
+					errors.New("wd error"),
+				},
+				{
+					"bsoncore.Document.Elements Error",
+					bsoncore.Document{0xFF, 0x00, 0x00, 0x00, 0x00},
+					nil,
+					&bsonrwtest.ValueReaderWriter{},
+					bsonrwtest.WriteDocument,
+					errors.New("length read exceeds number of bytes available. length=5 bytes=255"),
+				},
+				{
+					"WriteDocumentElement Error",
+					bsoncore.Document(buildDocument(bsoncore.AppendNullElement(nil, "foo"))),
+					nil,
+					&bsonrwtest.ValueReaderWriter{Err: errors.New("wde error"), ErrAfter: bsonrwtest.WriteDocumentElement},
+					bsonrwtest.WriteDocumentElement,
+					errors.New("wde error"),
+				},
+				{
+					"encodeValue error",
+					bsoncore.Document(buildDocument(bsoncore.AppendNullElement(nil, "foo"))),
+					nil,
+					&bsonrwtest.ValueReaderWriter{Err: errors.New("ev error"), ErrAfter: bsonrwtest.WriteNull},
+					bsonrwtest.WriteNull,
+					errors.New("ev error"),
+				},
+				{
+					"iterator error",
+					bsoncore.Document{0x0C, 0x00, 0x00, 0x00, 0x01, 'f', 'o', 'o', 0x00, 0x01, 0x02, 0x03},
+					nil,
+					&bsonrwtest.ValueReaderWriter{},
+					bsonrwtest.WriteDocumentElement,
+					errors.New("not enough bytes available to read type. bytes=3 type=double"),
+				},
+			},
+		},
+		{
+			"CodeWithScopeEncodeValue",
+			ValueEncoderFunc(dve.CodeWithScopeEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{
+						Name:     "CodeWithScopeEncodeValue",
+						Types:    []reflect.Type{tCodeWithScope},
+						Received: reflect.ValueOf(wrong),
+					},
+				},
+				{
+					"WriteCodeWithScope error",
+					primitive.CodeWithScope{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{Err: errors.New("wcws error"), ErrAfter: bsonrwtest.WriteCodeWithScope},
+					bsonrwtest.WriteCodeWithScope,
+					errors.New("wcws error"),
+				},
+				{
+					"CodeWithScope/success",
+					primitive.CodeWithScope{
+						Code:  "var hello = 'world';",
+						Scope: primitive.D{},
+					},
+					&EncodeContext{Registry: buildDefaultRegistry()},
+					nil, bsonrwtest.WriteDocumentEnd, nil,
+				},
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			for _, subtest := range tc.subtests {
+				t.Run(subtest.name, func(t *testing.T) {
+					var ec EncodeContext
+					if subtest.ectx != nil {
+						ec = *subtest.ectx
+					}
+					llvrw := new(bsonrwtest.ValueReaderWriter)
+					if subtest.llvrw != nil {
+						llvrw = subtest.llvrw
+					}
+					llvrw.T = t
+					err := tc.ve.EncodeValue(ec, llvrw, reflect.ValueOf(subtest.val))
+					if !compareErrors(err, subtest.err) {
+						t.Errorf("Errors do not match. got %v; want %v", err, subtest.err)
+					}
+					invoked := llvrw.Invoked
+					if !cmp.Equal(invoked, subtest.invoke) {
+						t.Errorf("Incorrect method invoked. got %v; want %v", invoked, subtest.invoke)
+					}
+				})
+			}
+		})
+	}
+
+	t.Run("success path", func(t *testing.T) {
+		oid := primitive.NewObjectID()
+		oids := []primitive.ObjectID{primitive.NewObjectID(), primitive.NewObjectID(), primitive.NewObjectID()}
+		var str = new(string)
+		*str = "bar"
+		now := time.Now().Truncate(time.Millisecond)
+		murl, err := url.Parse("https://mongodb.com/random-url?hello=world")
+		if err != nil {
+			t.Errorf("Error parsing URL: %v", err)
+			t.FailNow()
+		}
+		decimal128, err := primitive.ParseDecimal128("1.5e10")
+		if err != nil {
+			t.Errorf("Error parsing decimal128: %v", err)
+			t.FailNow()
+		}
+
+		testCases := []struct {
+			name  string
+			value interface{}
+			b     []byte
+			err   error
+		}{
+			{
+				"map[string]int",
+				map[string]int32{"foo": 1},
+				[]byte{
+					0x0E, 0x00, 0x00, 0x00,
+					0x10, 'f', 'o', 'o', 0x00,
+					0x01, 0x00, 0x00, 0x00,
+					0x00,
+				},
+				nil,
+			},
+			{
+				"map[string]primitive.ObjectID",
+				map[string]primitive.ObjectID{"foo": oid},
+				buildDocument(bsoncore.AppendObjectIDElement(nil, "foo", oid)),
+				nil,
+			},
+			{
+				"map[string][]int32",
+				map[string][]int32{"Z": {1, 2, 3}},
+				buildDocumentArray(func(doc []byte) []byte {
+					doc = bsoncore.AppendInt32Element(doc, "0", 1)
+					doc = bsoncore.AppendInt32Element(doc, "1", 2)
+					return bsoncore.AppendInt32Element(doc, "2", 3)
+				}),
+				nil,
+			},
+			{
+				"map[string][]primitive.ObjectID",
+				map[string][]primitive.ObjectID{"Z": oids},
+				buildDocumentArray(func(doc []byte) []byte {
+					doc = bsoncore.AppendObjectIDElement(doc, "0", oids[0])
+					doc = bsoncore.AppendObjectIDElement(doc, "1", oids[1])
+					return bsoncore.AppendObjectIDElement(doc, "2", oids[2])
+				}),
+				nil,
+			},
+			{
+				"map[string][]json.Number(int64)",
+				map[string][]json.Number{"Z": {json.Number("5"), json.Number("10")}},
+				buildDocumentArray(func(doc []byte) []byte {
+					doc = bsoncore.AppendInt64Element(doc, "0", 5)
+					return bsoncore.AppendInt64Element(doc, "1", 10)
+				}),
+				nil,
+			},
+			{
+				"map[string][]json.Number(float64)",
+				map[string][]json.Number{"Z": {json.Number("5"), json.Number("10.1")}},
+				buildDocumentArray(func(doc []byte) []byte {
+					doc = bsoncore.AppendInt64Element(doc, "0", 5)
+					return bsoncore.AppendDoubleElement(doc, "1", 10.1)
+				}),
+				nil,
+			},
+			{
+				"map[string][]*url.URL",
+				map[string][]*url.URL{"Z": {murl}},
+				buildDocumentArray(func(doc []byte) []byte {
+					return bsoncore.AppendStringElement(doc, "0", murl.String())
+				}),
+				nil,
+			},
+			{
+				"map[string][]primitive.Decimal128",
+				map[string][]primitive.Decimal128{"Z": {decimal128}},
+				buildDocumentArray(func(doc []byte) []byte {
+					return bsoncore.AppendDecimal128Element(doc, "0", decimal128)
+				}),
+				nil,
+			},
+			{
+				"-",
+				struct {
+					A string `bson:"-"`
+				}{
+					A: "",
+				},
+				[]byte{0x05, 0x00, 0x00, 0x00, 0x00},
+				nil,
+			},
+			{
+				"omitempty",
+				struct {
+					A string `bson:",omitempty"`
+				}{
+					A: "",
+				},
+				[]byte{0x05, 0x00, 0x00, 0x00, 0x00},
+				nil,
+			},
+			{
+				"omitempty, empty time",
+				struct {
+					A time.Time `bson:",omitempty"`
+				}{
+					A: time.Time{},
+				},
+				[]byte{0x05, 0x00, 0x00, 0x00, 0x00},
+				nil,
+			},
+			{
+				"no private fields",
+				noPrivateFields{a: "should be empty"},
+				[]byte{0x05, 0x00, 0x00, 0x00, 0x00},
+				nil,
+			},
+			{
+				"minsize",
+				struct {
+					A int64 `bson:",minsize"`
+				}{
+					A: 12345,
+				},
+				buildDocument(bsoncore.AppendInt32Element(nil, "a", 12345)),
+				nil,
+			},
+			{
+				"inline",
+				struct {
+					Foo struct {
+						A int64 `bson:",minsize"`
+					} `bson:",inline"`
+				}{
+					Foo: struct {
+						A int64 `bson:",minsize"`
+					}{
+						A: 12345,
+					},
+				},
+				buildDocument(bsoncore.AppendInt32Element(nil, "a", 12345)),
+				nil,
+			},
+			{
+				"inline map",
+				struct {
+					Foo map[string]string `bson:",inline"`
+				}{
+					Foo: map[string]string{"foo": "bar"},
+				},
+				buildDocument(bsoncore.AppendStringElement(nil, "foo", "bar")),
+				nil,
+			},
+			{
+				"alternate name bson:name",
+				struct {
+					A string `bson:"foo"`
+				}{
+					A: "bar",
+				},
+				buildDocument(bsoncore.AppendStringElement(nil, "foo", "bar")),
+				nil,
+			},
+			{
+				"alternate name",
+				struct {
+					A string `bson:"foo"`
+				}{
+					A: "bar",
+				},
+				buildDocument(bsoncore.AppendStringElement(nil, "foo", "bar")),
+				nil,
+			},
+			{
+				"inline, omitempty",
+				struct {
+					A   string
+					Foo zeroTest `bson:"omitempty,inline"`
+				}{
+					A:   "bar",
+					Foo: zeroTest{true},
+				},
+				buildDocument(bsoncore.AppendStringElement(nil, "a", "bar")),
+				nil,
+			},
+			{
+				"struct{}",
+				struct {
+					A bool
+					B int32
+					C int64
+					D uint16
+					E uint64
+					F float64
+					G string
+					H map[string]string
+					I []byte
+					K [2]string
+					L struct {
+						M string
+					}
+					Q  primitive.ObjectID
+					T  []struct{}
+					Y  json.Number
+					Z  time.Time
+					AA json.Number
+					AB *url.URL
+					AC primitive.Decimal128
+					AD *time.Time
+					AE testValueMarshaler
+					AF Proxy
+					AG testProxy
+					AH map[string]interface{}
+					AI primitive.CodeWithScope
+				}{
+					A: true,
+					B: 123,
+					C: 456,
+					D: 789,
+					E: 101112,
+					F: 3.14159,
+					G: "Hello, world",
+					H: map[string]string{"foo": "bar"},
+					I: []byte{0x01, 0x02, 0x03},
+					K: [2]string{"baz", "qux"},
+					L: struct {
+						M string
+					}{
+						M: "foobar",
+					},
+					Q:  oid,
+					T:  nil,
+					Y:  json.Number("5"),
+					Z:  now,
+					AA: json.Number("10.1"),
+					AB: murl,
+					AC: decimal128,
+					AD: &now,
+					AE: testValueMarshaler{t: bsontype.String, buf: bsoncore.AppendString(nil, "hello, world")},
+					AF: testProxy{ret: struct{ Hello string }{Hello: "world!"}},
+					AG: testProxy{ret: struct{ Pi float64 }{Pi: 3.14159}},
+					AH: nil,
+					AI: primitive.CodeWithScope{Code: "var hello = 'world';", Scope: primitive.D{{"pi", 3.14159}}},
+				},
+				buildDocument(func(doc []byte) []byte {
+					doc = bsoncore.AppendBooleanElement(doc, "a", true)
+					doc = bsoncore.AppendInt32Element(doc, "b", 123)
+					doc = bsoncore.AppendInt64Element(doc, "c", 456)
+					doc = bsoncore.AppendInt32Element(doc, "d", 789)
+					doc = bsoncore.AppendInt64Element(doc, "e", 101112)
+					doc = bsoncore.AppendDoubleElement(doc, "f", 3.14159)
+					doc = bsoncore.AppendStringElement(doc, "g", "Hello, world")
+					doc = bsoncore.AppendDocumentElement(doc, "h", buildDocument(bsoncore.AppendStringElement(nil, "foo", "bar")))
+					doc = bsoncore.AppendBinaryElement(doc, "i", 0x00, []byte{0x01, 0x02, 0x03})
+					doc = bsoncore.AppendArrayElement(doc, "k",
+						buildArray(bsoncore.AppendStringElement(bsoncore.AppendStringElement(nil, "0", "baz"), "1", "qux")),
+					)
+					doc = bsoncore.AppendDocumentElement(doc, "l", buildDocument(bsoncore.AppendStringElement(nil, "m", "foobar")))
+					doc = bsoncore.AppendObjectIDElement(doc, "q", oid)
+					doc = bsoncore.AppendNullElement(doc, "t")
+					doc = bsoncore.AppendInt64Element(doc, "y", 5)
+					doc = bsoncore.AppendDateTimeElement(doc, "z", now.UnixNano()/int64(time.Millisecond))
+					doc = bsoncore.AppendDoubleElement(doc, "aa", 10.1)
+					doc = bsoncore.AppendStringElement(doc, "ab", murl.String())
+					doc = bsoncore.AppendDecimal128Element(doc, "ac", decimal128)
+					doc = bsoncore.AppendDateTimeElement(doc, "ad", now.UnixNano()/int64(time.Millisecond))
+					doc = bsoncore.AppendStringElement(doc, "ae", "hello, world")
+					doc = bsoncore.AppendDocumentElement(doc, "af", buildDocument(bsoncore.AppendStringElement(nil, "hello", "world!")))
+					doc = bsoncore.AppendDocumentElement(doc, "ag", buildDocument(bsoncore.AppendDoubleElement(nil, "pi", 3.14159)))
+					doc = bsoncore.AppendNullElement(doc, "ah")
+					doc = bsoncore.AppendCodeWithScopeElement(doc, "ai",
+						"var hello = 'world';", buildDocument(bsoncore.AppendDoubleElement(nil, "pi", 3.14159)),
+					)
+					return doc
+				}(nil)),
+				nil,
+			},
+			{
+				"struct{[]interface{}}",
+				struct {
+					A []bool
+					B []int32
+					C []int64
+					D []uint16
+					E []uint64
+					F []float64
+					G []string
+					H []map[string]string
+					I [][]byte
+					K [1][2]string
+					L []struct {
+						M string
+					}
+					N  [][]string
+					R  []primitive.ObjectID
+					T  []struct{}
+					W  []map[string]struct{}
+					X  []map[string]struct{}
+					Y  []map[string]struct{}
+					Z  []time.Time
+					AA []json.Number
+					AB []*url.URL
+					AC []primitive.Decimal128
+					AD []*time.Time
+					AE []testValueMarshaler
+					AF []Proxy
+					AG []testProxy
+				}{
+					A: []bool{true},
+					B: []int32{123},
+					C: []int64{456},
+					D: []uint16{789},
+					E: []uint64{101112},
+					F: []float64{3.14159},
+					G: []string{"Hello, world"},
+					H: []map[string]string{{"foo": "bar"}},
+					I: [][]byte{{0x01, 0x02, 0x03}},
+					K: [1][2]string{{"baz", "qux"}},
+					L: []struct {
+						M string
+					}{
+						{
+							M: "foobar",
+						},
+					},
+					N:  [][]string{{"foo", "bar"}},
+					R:  oids,
+					T:  nil,
+					W:  nil,
+					X:  []map[string]struct{}{},   // Should be empty BSON Array
+					Y:  []map[string]struct{}{{}}, // Should be BSON array with one element, an empty BSON SubDocument
+					Z:  []time.Time{now, now},
+					AA: []json.Number{json.Number("5"), json.Number("10.1")},
+					AB: []*url.URL{murl},
+					AC: []primitive.Decimal128{decimal128},
+					AD: []*time.Time{&now, &now},
+					AE: []testValueMarshaler{
+						{t: bsontype.String, buf: bsoncore.AppendString(nil, "hello")},
+						{t: bsontype.String, buf: bsoncore.AppendString(nil, "world")},
+					},
+					AF: []Proxy{
+						testProxy{ret: struct{ Hello string }{Hello: "world!"}},
+						testProxy{ret: struct{ Foo string }{Foo: "bar"}},
+					},
+					AG: []testProxy{
+						{ret: struct{ One int64 }{One: 1234567890}},
+						{ret: struct{ Pi float64 }{Pi: 3.14159}},
+					},
+				},
+				buildDocument(func(doc []byte) []byte {
+					doc = appendArrayElement(doc, "a", bsoncore.AppendBooleanElement(nil, "0", true))
+					doc = appendArrayElement(doc, "b", bsoncore.AppendInt32Element(nil, "0", 123))
+					doc = appendArrayElement(doc, "c", bsoncore.AppendInt64Element(nil, "0", 456))
+					doc = appendArrayElement(doc, "d", bsoncore.AppendInt32Element(nil, "0", 789))
+					doc = appendArrayElement(doc, "e", bsoncore.AppendInt64Element(nil, "0", 101112))
+					doc = appendArrayElement(doc, "f", bsoncore.AppendDoubleElement(nil, "0", 3.14159))
+					doc = appendArrayElement(doc, "g", bsoncore.AppendStringElement(nil, "0", "Hello, world"))
+					doc = appendArrayElement(doc, "h", buildDocumentElement("0", bsoncore.AppendStringElement(nil, "foo", "bar")))
+					doc = appendArrayElement(doc, "i", bsoncore.AppendBinaryElement(nil, "0", 0x00, []byte{0x01, 0x02, 0x03}))
+					doc = appendArrayElement(doc, "k",
+						buildArrayElement("0",
+							bsoncore.AppendStringElement(bsoncore.AppendStringElement(nil, "0", "baz"), "1", "qux")),
+					)
+					doc = appendArrayElement(doc, "l", buildDocumentElement("0", bsoncore.AppendStringElement(nil, "m", "foobar")))
+					doc = appendArrayElement(doc, "n",
+						buildArrayElement("0",
+							bsoncore.AppendStringElement(bsoncore.AppendStringElement(nil, "0", "foo"), "1", "bar")),
+					)
+					doc = appendArrayElement(doc, "r",
+						bsoncore.AppendObjectIDElement(
+							bsoncore.AppendObjectIDElement(
+								bsoncore.AppendObjectIDElement(nil,
+									"0", oids[0]),
+								"1", oids[1]),
+							"2", oids[2]),
+					)
+					doc = bsoncore.AppendNullElement(doc, "t")
+					doc = bsoncore.AppendNullElement(doc, "w")
+					doc = appendArrayElement(doc, "x", nil)
+					doc = appendArrayElement(doc, "y", buildDocumentElement("0", nil))
+					doc = appendArrayElement(doc, "z",
+						bsoncore.AppendDateTimeElement(
+							bsoncore.AppendDateTimeElement(
+								nil, "0", now.UnixNano()/int64(time.Millisecond)),
+							"1", now.UnixNano()/int64(time.Millisecond)),
+					)
+					doc = appendArrayElement(doc, "aa", bsoncore.AppendDoubleElement(bsoncore.AppendInt64Element(nil, "0", 5), "1", 10.10))
+					doc = appendArrayElement(doc, "ab", bsoncore.AppendStringElement(nil, "0", murl.String()))
+					doc = appendArrayElement(doc, "ac", bsoncore.AppendDecimal128Element(nil, "0", decimal128))
+					doc = appendArrayElement(doc, "ad",
+						bsoncore.AppendDateTimeElement(
+							bsoncore.AppendDateTimeElement(nil, "0", now.UnixNano()/int64(time.Millisecond)),
+							"1", now.UnixNano()/int64(time.Millisecond)),
+					)
+					doc = appendArrayElement(doc, "ae",
+						bsoncore.AppendStringElement(bsoncore.AppendStringElement(nil, "0", "hello"), "1", "world"),
+					)
+					doc = appendArrayElement(doc, "af",
+						bsoncore.AppendDocumentElement(
+							bsoncore.AppendDocumentElement(nil, "0",
+								bsoncore.BuildDocument(nil, bsoncore.AppendStringElement(nil, "hello", "world!")),
+							), "1",
+							bsoncore.BuildDocument(nil, bsoncore.AppendStringElement(nil, "foo", "bar")),
+						),
+					)
+					doc = appendArrayElement(doc, "ag",
+						bsoncore.AppendDocumentElement(
+							bsoncore.AppendDocumentElement(nil, "0",
+								bsoncore.BuildDocument(nil, bsoncore.AppendInt64Element(nil, "one", 1234567890)),
+							), "1",
+							bsoncore.BuildDocument(nil, bsoncore.AppendDoubleElement(nil, "pi", 3.14159)),
+						),
+					)
+					return doc
+				}(nil)),
+				nil,
+			},
+		}
+
+		for _, tc := range testCases {
+			t.Run(tc.name, func(t *testing.T) {
+				b := make(bsonrw.SliceWriter, 0, 512)
+				vw, err := bsonrw.NewBSONValueWriter(&b)
+				noerr(t, err)
+				reg := buildDefaultRegistry()
+				enc, err := reg.LookupEncoder(reflect.TypeOf(tc.value))
+				noerr(t, err)
+				err = enc.EncodeValue(EncodeContext{Registry: reg}, vw, reflect.ValueOf(tc.value))
+				if err != tc.err {
+					t.Errorf("Did not receive expected error. got %v; want %v", err, tc.err)
+				}
+				if diff := cmp.Diff([]byte(b), tc.b); diff != "" {
+					t.Errorf("Bytes written differ: (-got +want)\n%s", diff)
+					t.Errorf("Bytes\ngot: %v\nwant:%v\n", b, tc.b)
+					t.Errorf("Readers\ngot: %v\nwant:%v\n", bsoncore.Document(b), bsoncore.Document(tc.b))
+				}
+			})
+		}
+	})
+
+	t.Run("EmptyInterfaceEncodeValue/nil", func(t *testing.T) {
+		val := reflect.New(tEmpty).Elem()
+		llvrw := new(bsonrwtest.ValueReaderWriter)
+		err := dve.EmptyInterfaceEncodeValue(EncodeContext{Registry: NewRegistryBuilder().Build()}, llvrw, val)
+		noerr(t, err)
+		if llvrw.Invoked != bsonrwtest.WriteNull {
+			t.Errorf("Incorrect method called. got %v; want %v", llvrw.Invoked, bsonrwtest.WriteNull)
+		}
+	})
+
+	t.Run("EmptyInterfaceEncodeValue/LookupEncoder error", func(t *testing.T) {
+		val := reflect.New(tEmpty).Elem()
+		val.Set(reflect.ValueOf(int64(1234567890)))
+		llvrw := new(bsonrwtest.ValueReaderWriter)
+		got := dve.EmptyInterfaceEncodeValue(EncodeContext{Registry: NewRegistryBuilder().Build()}, llvrw, val)
+		want := ErrNoEncoder{Type: tInt64}
+		if !compareErrors(got, want) {
+			t.Errorf("Did not recieve expected error. got %v; want %v", got, want)
+		}
+	})
+}
+
+type testValueMarshaler struct {
+	t   bsontype.Type
+	buf []byte
+	err error
+}
+
+func (tvm testValueMarshaler) MarshalBSONValue() (bsontype.Type, []byte, error) {
+	return tvm.t, tvm.buf, tvm.err
+}
+
+type testMarshaler struct {
+	buf []byte
+	err error
+}
+
+func (tvm testMarshaler) MarshalBSON() ([]byte, error) {
+	return tvm.buf, tvm.err
+}
+
+type testProxy struct {
+	ret interface{}
+	err error
+}
+
+func (tp testProxy) ProxyBSON() (interface{}, error) { return tp.ret, tp.err }

+ 61 - 0
src/go.mongodb.org/mongo-driver/bson/bsoncodec/doc.go

@@ -0,0 +1,61 @@
+// Package bsoncodec provides a system for encoding values to BSON representations and decoding
+// values from BSON representations. This package considers both binary BSON and ExtendedJSON as
+// BSON representations. The types in this package enable a flexible system for handling this
+// encoding and decoding.
+//
+// The codec system is composed of two parts:
+//
+// 1) ValueEncoders and ValueDecoders that handle encoding and decoding Go values to and from BSON
+// representations.
+//
+// 2) A Registry that holds these ValueEncoders and ValueDecoders and provides methods for
+// retrieving them.
+//
+// ValueEncoders and ValueDecoders
+//
+// The ValueEncoder interface is implemented by types that can encode a provided Go type to BSON.
+// The value to encode is provided as a reflect.Value and a bsonrw.ValueWriter is used within the
+// EncodeValue method to actually create the BSON representation. For convenience, ValueEncoderFunc
+// is provided to allow use of a function with the correct signature as a ValueEncoder. An
+// EncodeContext instance is provided to allow implementations to lookup further ValueEncoders and
+// to provide configuration information.
+//
+// The ValueDecoder interface is the inverse of the ValueEncoder. Implementations should ensure that
+// the value they receive is settable. Similar to ValueEncoderFunc, ValueDecoderFunc is provided to
+// allow the use of a function with the correct signature as a ValueDecoder. A DecodeContext
+// instance is provided and serves similar functionality to the EncodeContext.
+//
+// Registry and RegistryBuilder
+//
+// A Registry is an immutable store for ValueEncoders, ValueDecoders, and a type map. For looking up
+// ValueEncoders and Decoders the Registry first attempts to find a ValueEncoder or ValueDecoder for
+// the type provided; if one cannot be found it then checks to see if a registered ValueEncoder or
+// ValueDecoder exists for an interface the type implements. Finally, the reflect.Kind of the type
+// is used to lookup a default ValueEncoder or ValueDecoder for that kind. If no ValueEncoder or
+// ValueDecoder can be found, an error is returned.
+//
+// The Registry also holds a type map. This allows users to retrieve the Go type that should be used
+// when decoding a BSON value into an empty interface. This is primarily only used for the empty
+// interface ValueDecoder.
+//
+// A RegistryBuilder is used to construct a Registry. The Register methods are used to associate
+// either a reflect.Type or a reflect.Kind with a ValueEncoder or ValueDecoder. A RegistryBuilder
+// returned from NewRegistryBuilder contains no registered ValueEncoders nor ValueDecoders and
+// contains an empty type map.
+//
+// The RegisterTypeMapEntry method handles associating a BSON type with a Go type. For example, if
+// you want to decode BSON int64 and int32 values into Go int instances, you would do the following:
+//
+//  var regbuilder *RegistryBuilder = ... intType := reflect.TypeOf(int(0))
+//  regbuilder.RegisterTypeMapEntry(bsontype.Int64, intType).RegisterTypeMapEntry(bsontype.Int32,
+//  intType)
+//
+// DefaultValueEncoders and DefaultValueDecoders
+//
+// The DefaultValueEncoders and DefaultValueDecoders types provide a full set of ValueEncoders and
+// ValueDecoders for handling a wide range of Go types, including all of the types within the
+// primitive package. To make registering these codecs easier, a helper method on each type is
+// provided. For the DefaultValueEncoders type the method is called RegisterDefaultEncoders and for
+// the DefaultValueDecoders type the method is called RegisterDefaultDecoders, this method also
+// handles registering type map entries for each BSON type.
+package bsoncodec

+ 65 - 0
src/go.mongodb.org/mongo-driver/bson/bsoncodec/mode.go

@@ -0,0 +1,65 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+package bsoncodec
+
+import "fmt"
+
+type mode int
+
+const (
+	_ mode = iota
+	mTopLevel
+	mDocument
+	mArray
+	mValue
+	mElement
+	mCodeWithScope
+	mSpacer
+)
+
+func (m mode) String() string {
+	var str string
+
+	switch m {
+	case mTopLevel:
+		str = "TopLevel"
+	case mDocument:
+		str = "DocumentMode"
+	case mArray:
+		str = "ArrayMode"
+	case mValue:
+		str = "ValueMode"
+	case mElement:
+		str = "ElementMode"
+	case mCodeWithScope:
+		str = "CodeWithScopeMode"
+	case mSpacer:
+		str = "CodeWithScopeSpacerFrame"
+	default:
+		str = "UnknownMode"
+	}
+
+	return str
+}
+
+// TransitionError is an error returned when an invalid progressing a
+// ValueReader or ValueWriter state machine occurs.
+type TransitionError struct {
+	parent      mode
+	current     mode
+	destination mode
+}
+
+func (te TransitionError) Error() string {
+	if te.destination == mode(0) {
+		return fmt.Sprintf("invalid state transition: cannot read/write value while in %s", te.current)
+	}
+	if te.parent == mode(0) {
+		return fmt.Sprintf("invalid state transition: %s -> %s", te.current, te.destination)
+	}
+	return fmt.Sprintf("invalid state transition: %s -> %s; parent %s", te.current, te.destination, te.parent)
+}

+ 110 - 0
src/go.mongodb.org/mongo-driver/bson/bsoncodec/pointer_codec.go

@@ -0,0 +1,110 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+package bsoncodec
+
+import (
+	"reflect"
+	"sync"
+
+	"go.mongodb.org/mongo-driver/bson/bsonrw"
+	"go.mongodb.org/mongo-driver/bson/bsontype"
+)
+
+var defaultPointerCodec = &PointerCodec{
+	ecache: make(map[reflect.Type]ValueEncoder),
+	dcache: make(map[reflect.Type]ValueDecoder),
+}
+
+var _ ValueEncoder = &PointerCodec{}
+var _ ValueDecoder = &PointerCodec{}
+
+// PointerCodec is the Codec used for pointers.
+type PointerCodec struct {
+	ecache map[reflect.Type]ValueEncoder
+	dcache map[reflect.Type]ValueDecoder
+	l      sync.RWMutex
+}
+
+// NewPointerCodec returns a PointerCodec that has been initialized.
+func NewPointerCodec() *PointerCodec {
+	return &PointerCodec{
+		ecache: make(map[reflect.Type]ValueEncoder),
+		dcache: make(map[reflect.Type]ValueDecoder),
+	}
+}
+
+// EncodeValue handles encoding a pointer by either encoding it to BSON Null if the pointer is nil
+// or looking up an encoder for the type of value the pointer points to.
+func (pc *PointerCodec) EncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
+	if val.Kind() != reflect.Ptr {
+		if !val.IsValid() {
+			return vw.WriteNull()
+		}
+		return ValueEncoderError{Name: "PointerCodec.EncodeValue", Kinds: []reflect.Kind{reflect.Ptr}, Received: val}
+	}
+
+	if val.IsNil() {
+		return vw.WriteNull()
+	}
+
+	pc.l.RLock()
+	enc, ok := pc.ecache[val.Type()]
+	pc.l.RUnlock()
+	if ok {
+		if enc == nil {
+			return ErrNoEncoder{Type: val.Type()}
+		}
+		return enc.EncodeValue(ec, vw, val.Elem())
+	}
+
+	enc, err := ec.LookupEncoder(val.Type().Elem())
+	pc.l.Lock()
+	pc.ecache[val.Type()] = enc
+	pc.l.Unlock()
+	if err != nil {
+		return err
+	}
+
+	return enc.EncodeValue(ec, vw, val.Elem())
+}
+
+// DecodeValue handles decoding a pointer by looking up a decoder for the type it points to and
+// using that to decode. If the BSON value is Null, this method will set the pointer to nil.
+func (pc *PointerCodec) DecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if !val.CanSet() || val.Kind() != reflect.Ptr {
+		return ValueDecoderError{Name: "PointerCodec.DecodeValue", Kinds: []reflect.Kind{reflect.Ptr}, Received: val}
+	}
+
+	if vr.Type() == bsontype.Null {
+		val.Set(reflect.Zero(val.Type()))
+		return vr.ReadNull()
+	}
+
+	if val.IsNil() {
+		val.Set(reflect.New(val.Type().Elem()))
+	}
+
+	pc.l.RLock()
+	dec, ok := pc.dcache[val.Type()]
+	pc.l.RUnlock()
+	if ok {
+		if dec == nil {
+			return ErrNoDecoder{Type: val.Type()}
+		}
+		return dec.DecodeValue(dc, vr, val.Elem())
+	}
+
+	dec, err := dc.LookupDecoder(val.Type().Elem())
+	pc.l.Lock()
+	pc.dcache[val.Type()] = dec
+	pc.l.Unlock()
+	if err != nil {
+		return err
+	}
+
+	return dec.DecodeValue(dc, vr, val.Elem())
+}

+ 14 - 0
src/go.mongodb.org/mongo-driver/bson/bsoncodec/proxy.go

@@ -0,0 +1,14 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+package bsoncodec
+
+// Proxy is an interface implemented by types that cannot themselves be directly encoded. Types
+// that implement this interface with have ProxyBSON called during the encoding process and that
+// value will be encoded in place for the implementer.
+type Proxy interface {
+	ProxyBSON() (interface{}, error)
+}

+ 384 - 0
src/go.mongodb.org/mongo-driver/bson/bsoncodec/registry.go

@@ -0,0 +1,384 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+package bsoncodec
+
+import (
+	"errors"
+	"reflect"
+	"sync"
+
+	"go.mongodb.org/mongo-driver/bson/bsontype"
+)
+
+// ErrNilType is returned when nil is passed to either LookupEncoder or LookupDecoder.
+var ErrNilType = errors.New("cannot perform a decoder lookup on <nil>")
+
+// ErrNotPointer is returned when a non-pointer type is provided to LookupDecoder.
+var ErrNotPointer = errors.New("non-pointer provided to LookupDecoder")
+
+// ErrNoEncoder is returned when there wasn't an encoder available for a type.
+type ErrNoEncoder struct {
+	Type reflect.Type
+}
+
+func (ene ErrNoEncoder) Error() string {
+	if ene.Type == nil {
+		return "no encoder found for <nil>"
+	}
+	return "no encoder found for " + ene.Type.String()
+}
+
+// ErrNoDecoder is returned when there wasn't a decoder available for a type.
+type ErrNoDecoder struct {
+	Type reflect.Type
+}
+
+func (end ErrNoDecoder) Error() string {
+	return "no decoder found for " + end.Type.String()
+}
+
+// ErrNoTypeMapEntry is returned when there wasn't a type available for the provided BSON type.
+type ErrNoTypeMapEntry struct {
+	Type bsontype.Type
+}
+
+func (entme ErrNoTypeMapEntry) Error() string {
+	return "no type map entry found for " + entme.Type.String()
+}
+
+// ErrNotInterface is returned when the provided type is not an interface.
+var ErrNotInterface = errors.New("The provided type is not an interface")
+
+var defaultRegistry *Registry
+
+func init() {
+	defaultRegistry = buildDefaultRegistry()
+}
+
+// A RegistryBuilder is used to build a Registry. This type is not goroutine
+// safe.
+type RegistryBuilder struct {
+	typeEncoders      map[reflect.Type]ValueEncoder
+	interfaceEncoders []interfaceValueEncoder
+	kindEncoders      map[reflect.Kind]ValueEncoder
+
+	typeDecoders      map[reflect.Type]ValueDecoder
+	interfaceDecoders []interfaceValueDecoder
+	kindDecoders      map[reflect.Kind]ValueDecoder
+
+	typeMap map[bsontype.Type]reflect.Type
+}
+
+// A Registry is used to store and retrieve codecs for types and interfaces. This type is the main
+// typed passed around and Encoders and Decoders are constructed from it.
+type Registry struct {
+	typeEncoders map[reflect.Type]ValueEncoder
+	typeDecoders map[reflect.Type]ValueDecoder
+
+	interfaceEncoders []interfaceValueEncoder
+	interfaceDecoders []interfaceValueDecoder
+
+	kindEncoders map[reflect.Kind]ValueEncoder
+	kindDecoders map[reflect.Kind]ValueDecoder
+
+	typeMap map[bsontype.Type]reflect.Type
+
+	mu sync.RWMutex
+}
+
+// NewRegistryBuilder creates a new empty RegistryBuilder.
+func NewRegistryBuilder() *RegistryBuilder {
+	return &RegistryBuilder{
+		typeEncoders: make(map[reflect.Type]ValueEncoder),
+		typeDecoders: make(map[reflect.Type]ValueDecoder),
+
+		interfaceEncoders: make([]interfaceValueEncoder, 0),
+		interfaceDecoders: make([]interfaceValueDecoder, 0),
+
+		kindEncoders: make(map[reflect.Kind]ValueEncoder),
+		kindDecoders: make(map[reflect.Kind]ValueDecoder),
+
+		typeMap: make(map[bsontype.Type]reflect.Type),
+	}
+}
+
+func buildDefaultRegistry() *Registry {
+	rb := NewRegistryBuilder()
+	defaultValueEncoders.RegisterDefaultEncoders(rb)
+	defaultValueDecoders.RegisterDefaultDecoders(rb)
+	return rb.Build()
+}
+
+// RegisterCodec will register the provided ValueCodec for the provided type.
+func (rb *RegistryBuilder) RegisterCodec(t reflect.Type, codec ValueCodec) *RegistryBuilder {
+	rb.RegisterEncoder(t, codec)
+	rb.RegisterDecoder(t, codec)
+	return rb
+}
+
+// RegisterEncoder will register the provided ValueEncoder to the provided type.
+//
+// The type registered will be used directly, so an encoder can be registered for a type and a
+// different encoder can be registered for a pointer to that type.
+func (rb *RegistryBuilder) RegisterEncoder(t reflect.Type, enc ValueEncoder) *RegistryBuilder {
+	if t == tEmpty {
+		rb.typeEncoders[t] = enc
+		return rb
+	}
+	switch t.Kind() {
+	case reflect.Interface:
+		for idx, ir := range rb.interfaceEncoders {
+			if ir.i == t {
+				rb.interfaceEncoders[idx].ve = enc
+				return rb
+			}
+		}
+
+		rb.interfaceEncoders = append(rb.interfaceEncoders, interfaceValueEncoder{i: t, ve: enc})
+	default:
+		rb.typeEncoders[t] = enc
+	}
+	return rb
+}
+
+// RegisterDecoder will register the provided ValueDecoder to the provided type.
+//
+// The type registered will be used directly, so a decoder can be registered for a type and a
+// different decoder can be registered for a pointer to that type.
+func (rb *RegistryBuilder) RegisterDecoder(t reflect.Type, dec ValueDecoder) *RegistryBuilder {
+	if t == nil {
+		rb.typeDecoders[nil] = dec
+		return rb
+	}
+	if t == tEmpty {
+		rb.typeDecoders[t] = dec
+		return rb
+	}
+	switch t.Kind() {
+	case reflect.Interface:
+		for idx, ir := range rb.interfaceDecoders {
+			if ir.i == t {
+				rb.interfaceDecoders[idx].vd = dec
+				return rb
+			}
+		}
+
+		rb.interfaceDecoders = append(rb.interfaceDecoders, interfaceValueDecoder{i: t, vd: dec})
+	default:
+		rb.typeDecoders[t] = dec
+	}
+	return rb
+}
+
+// RegisterDefaultEncoder will registr the provided ValueEncoder to the provided
+// kind.
+func (rb *RegistryBuilder) RegisterDefaultEncoder(kind reflect.Kind, enc ValueEncoder) *RegistryBuilder {
+	rb.kindEncoders[kind] = enc
+	return rb
+}
+
+// RegisterDefaultDecoder will register the provided ValueDecoder to the
+// provided kind.
+func (rb *RegistryBuilder) RegisterDefaultDecoder(kind reflect.Kind, dec ValueDecoder) *RegistryBuilder {
+	rb.kindDecoders[kind] = dec
+	return rb
+}
+
+// RegisterTypeMapEntry will register the provided type to the BSON type. The primary usage for this
+// mapping is decoding situations where an empty interface is used and a default type needs to be
+// created and decoded into.
+//
+// NOTE: It is unlikely that registering a type for BSON Embedded Document is actually desired. By
+// registering a type map entry for BSON Embedded Document the type registered will be used in any
+// case where a BSON Embedded Document will be decoded into an empty interface. For example, if you
+// register primitive.M, the EmptyInterface decoder will always use primitive.M, even if an ancestor
+// was a primitive.D.
+func (rb *RegistryBuilder) RegisterTypeMapEntry(bt bsontype.Type, rt reflect.Type) *RegistryBuilder {
+	rb.typeMap[bt] = rt
+	return rb
+}
+
+// Build creates a Registry from the current state of this RegistryBuilder.
+func (rb *RegistryBuilder) Build() *Registry {
+	registry := new(Registry)
+
+	registry.typeEncoders = make(map[reflect.Type]ValueEncoder)
+	for t, enc := range rb.typeEncoders {
+		registry.typeEncoders[t] = enc
+	}
+
+	registry.typeDecoders = make(map[reflect.Type]ValueDecoder)
+	for t, dec := range rb.typeDecoders {
+		registry.typeDecoders[t] = dec
+	}
+
+	registry.interfaceEncoders = make([]interfaceValueEncoder, len(rb.interfaceEncoders))
+	copy(registry.interfaceEncoders, rb.interfaceEncoders)
+
+	registry.interfaceDecoders = make([]interfaceValueDecoder, len(rb.interfaceDecoders))
+	copy(registry.interfaceDecoders, rb.interfaceDecoders)
+
+	registry.kindEncoders = make(map[reflect.Kind]ValueEncoder)
+	for kind, enc := range rb.kindEncoders {
+		registry.kindEncoders[kind] = enc
+	}
+
+	registry.kindDecoders = make(map[reflect.Kind]ValueDecoder)
+	for kind, dec := range rb.kindDecoders {
+		registry.kindDecoders[kind] = dec
+	}
+
+	registry.typeMap = make(map[bsontype.Type]reflect.Type)
+	for bt, rt := range rb.typeMap {
+		registry.typeMap[bt] = rt
+	}
+
+	return registry
+}
+
+// LookupEncoder will inspect the registry for an encoder that satisfies the
+// type provided. An encoder registered for a specific type will take
+// precedence over an encoder registered for an interface the type satisfies,
+// which takes precedence over an encoder for the reflect.Kind of the value. If
+// no encoder can be found, an error is returned.
+func (r *Registry) LookupEncoder(t reflect.Type) (ValueEncoder, error) {
+	encodererr := ErrNoEncoder{Type: t}
+	r.mu.RLock()
+	enc, found := r.lookupTypeEncoder(t)
+	r.mu.RUnlock()
+	if found {
+		if enc == nil {
+			return nil, ErrNoEncoder{Type: t}
+		}
+		return enc, nil
+	}
+
+	enc, found = r.lookupInterfaceEncoder(t)
+	if found {
+		r.mu.Lock()
+		r.typeEncoders[t] = enc
+		r.mu.Unlock()
+		return enc, nil
+	}
+
+	if t == nil {
+		r.mu.Lock()
+		r.typeEncoders[t] = nil
+		r.mu.Unlock()
+		return nil, encodererr
+	}
+
+	enc, found = r.kindEncoders[t.Kind()]
+	if !found {
+		r.mu.Lock()
+		r.typeEncoders[t] = nil
+		r.mu.Unlock()
+		return nil, encodererr
+	}
+
+	r.mu.Lock()
+	r.typeEncoders[t] = enc
+	r.mu.Unlock()
+	return enc, nil
+}
+
+func (r *Registry) lookupTypeEncoder(t reflect.Type) (ValueEncoder, bool) {
+	enc, found := r.typeEncoders[t]
+	return enc, found
+}
+
+func (r *Registry) lookupInterfaceEncoder(t reflect.Type) (ValueEncoder, bool) {
+	if t == nil {
+		return nil, false
+	}
+	for _, ienc := range r.interfaceEncoders {
+		if !t.Implements(ienc.i) {
+			continue
+		}
+
+		return ienc.ve, true
+	}
+	return nil, false
+}
+
+// LookupDecoder will inspect the registry for a decoder that satisfies the
+// type provided. A decoder registered for a specific type will take
+// precedence over a decoder registered for an interface the type satisfies,
+// which takes precedence over a decoder for the reflect.Kind of the value. If
+// no decoder can be found, an error is returned.
+func (r *Registry) LookupDecoder(t reflect.Type) (ValueDecoder, error) {
+	if t == nil {
+		return nil, ErrNilType
+	}
+	decodererr := ErrNoDecoder{Type: t}
+	r.mu.RLock()
+	dec, found := r.lookupTypeDecoder(t)
+	r.mu.RUnlock()
+	if found {
+		if dec == nil {
+			return nil, ErrNoDecoder{Type: t}
+		}
+		return dec, nil
+	}
+
+	dec, found = r.lookupInterfaceDecoder(t)
+	if found {
+		r.mu.Lock()
+		r.typeDecoders[t] = dec
+		r.mu.Unlock()
+		return dec, nil
+	}
+
+	dec, found = r.kindDecoders[t.Kind()]
+	if !found {
+		r.mu.Lock()
+		r.typeDecoders[t] = nil
+		r.mu.Unlock()
+		return nil, decodererr
+	}
+
+	r.mu.Lock()
+	r.typeDecoders[t] = dec
+	r.mu.Unlock()
+	return dec, nil
+}
+
+func (r *Registry) lookupTypeDecoder(t reflect.Type) (ValueDecoder, bool) {
+	dec, found := r.typeDecoders[t]
+	return dec, found
+}
+
+func (r *Registry) lookupInterfaceDecoder(t reflect.Type) (ValueDecoder, bool) {
+	for _, idec := range r.interfaceDecoders {
+		if !t.Implements(idec.i) && !reflect.PtrTo(t).Implements(idec.i) {
+			continue
+		}
+
+		return idec.vd, true
+	}
+	return nil, false
+}
+
+// LookupTypeMapEntry inspects the registry's type map for a Go type for the corresponding BSON
+// type. If no type is found, ErrNoTypeMapEntry is returned.
+func (r *Registry) LookupTypeMapEntry(bt bsontype.Type) (reflect.Type, error) {
+	t, ok := r.typeMap[bt]
+	if !ok || t == nil {
+		return nil, ErrNoTypeMapEntry{Type: bt}
+	}
+	return t, nil
+}
+
+type interfaceValueEncoder struct {
+	i  reflect.Type
+	ve ValueEncoder
+}
+
+type interfaceValueDecoder struct {
+	i  reflect.Type
+	vd ValueDecoder
+}

+ 359 - 0
src/go.mongodb.org/mongo-driver/bson/bsoncodec/registry_test.go

@@ -0,0 +1,359 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+package bsoncodec
+
+import (
+	"reflect"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+	"go.mongodb.org/mongo-driver/bson/bsonrw"
+	"go.mongodb.org/mongo-driver/bson/bsontype"
+)
+
+func TestRegistry(t *testing.T) {
+	t.Run("Register", func(t *testing.T) {
+		fc1, fc2, fc3, fc4 := new(fakeCodec), new(fakeCodec), new(fakeCodec), new(fakeCodec)
+		t.Run("interface", func(t *testing.T) {
+			var t1f *testInterface1
+			var t2f *testInterface2
+			var t4f *testInterface4
+			ips := []interfaceValueEncoder{
+				{i: reflect.TypeOf(t1f).Elem(), ve: fc1},
+				{i: reflect.TypeOf(t2f).Elem(), ve: fc2},
+				{i: reflect.TypeOf(t1f).Elem(), ve: fc3},
+				{i: reflect.TypeOf(t4f).Elem(), ve: fc4},
+			}
+			want := []interfaceValueEncoder{
+				{i: reflect.TypeOf(t1f).Elem(), ve: fc3},
+				{i: reflect.TypeOf(t2f).Elem(), ve: fc2},
+				{i: reflect.TypeOf(t4f).Elem(), ve: fc4},
+			}
+			rb := NewRegistryBuilder()
+			for _, ip := range ips {
+				rb.RegisterEncoder(ip.i, ip.ve)
+			}
+			got := rb.interfaceEncoders
+			if !cmp.Equal(got, want, cmp.AllowUnexported(interfaceValueEncoder{}, fakeCodec{}), cmp.Comparer(typeComparer)) {
+				t.Errorf("The registered interfaces are not correct. got %v; want %v", got, want)
+			}
+		})
+		t.Run("type", func(t *testing.T) {
+			ft1, ft2, ft4 := fakeType1{}, fakeType2{}, fakeType4{}
+			rb := NewRegistryBuilder().
+				RegisterEncoder(reflect.TypeOf(ft1), fc1).
+				RegisterEncoder(reflect.TypeOf(ft2), fc2).
+				RegisterEncoder(reflect.TypeOf(ft1), fc3).
+				RegisterEncoder(reflect.TypeOf(ft4), fc4)
+			want := []struct {
+				t reflect.Type
+				c ValueEncoder
+			}{
+				{reflect.TypeOf(ft1), fc3},
+				{reflect.TypeOf(ft2), fc2},
+				{reflect.TypeOf(ft4), fc4},
+			}
+			got := rb.typeEncoders
+			for _, s := range want {
+				wantT, wantC := s.t, s.c
+				gotC, exists := got[wantT]
+				if !exists {
+					t.Errorf("Did not find type in the type registry: %v", wantT)
+				}
+				if !cmp.Equal(gotC, wantC, cmp.AllowUnexported(fakeCodec{})) {
+					t.Errorf("Codecs did not match. got %#v; want %#v", gotC, wantC)
+				}
+			}
+		})
+		t.Run("kind", func(t *testing.T) {
+			k1, k2, k4 := reflect.Struct, reflect.Slice, reflect.Map
+			rb := NewRegistryBuilder().
+				RegisterDefaultEncoder(k1, fc1).
+				RegisterDefaultEncoder(k2, fc2).
+				RegisterDefaultEncoder(k1, fc3).
+				RegisterDefaultEncoder(k4, fc4)
+			want := []struct {
+				k reflect.Kind
+				c ValueEncoder
+			}{
+				{k1, fc3},
+				{k2, fc2},
+				{k4, fc4},
+			}
+			got := rb.kindEncoders
+			for _, s := range want {
+				wantK, wantC := s.k, s.c
+				gotC, exists := got[wantK]
+				if !exists {
+					t.Errorf("Did not find kind in the kind registry: %v", wantK)
+				}
+				if !cmp.Equal(gotC, wantC, cmp.AllowUnexported(fakeCodec{})) {
+					t.Errorf("Codecs did not match. got %#v; want %#v", gotC, wantC)
+				}
+			}
+		})
+		t.Run("RegisterDefault", func(t *testing.T) {
+			t.Run("MapCodec", func(t *testing.T) {
+				codec := fakeCodec{num: 1}
+				codec2 := fakeCodec{num: 2}
+				rb := NewRegistryBuilder()
+				rb.RegisterDefaultEncoder(reflect.Map, codec)
+				if rb.kindEncoders[reflect.Map] != codec {
+					t.Errorf("Did not properly set the map codec. got %v; want %v", rb.kindEncoders[reflect.Map], codec)
+				}
+				rb.RegisterDefaultEncoder(reflect.Map, codec2)
+				if rb.kindEncoders[reflect.Map] != codec2 {
+					t.Errorf("Did not properly set the map codec. got %v; want %v", rb.kindEncoders[reflect.Map], codec2)
+				}
+			})
+			t.Run("StructCodec", func(t *testing.T) {
+				codec := fakeCodec{num: 1}
+				codec2 := fakeCodec{num: 2}
+				rb := NewRegistryBuilder()
+				rb.RegisterDefaultEncoder(reflect.Struct, codec)
+				if rb.kindEncoders[reflect.Struct] != codec {
+					t.Errorf("Did not properly set the struct codec. got %v; want %v", rb.kindEncoders[reflect.Struct], codec)
+				}
+				rb.RegisterDefaultEncoder(reflect.Struct, codec2)
+				if rb.kindEncoders[reflect.Struct] != codec2 {
+					t.Errorf("Did not properly set the struct codec. got %v; want %v", rb.kindEncoders[reflect.Struct], codec2)
+				}
+			})
+			t.Run("SliceCodec", func(t *testing.T) {
+				codec := fakeCodec{num: 1}
+				codec2 := fakeCodec{num: 2}
+				rb := NewRegistryBuilder()
+				rb.RegisterDefaultEncoder(reflect.Slice, codec)
+				if rb.kindEncoders[reflect.Slice] != codec {
+					t.Errorf("Did not properly set the slice codec. got %v; want %v", rb.kindEncoders[reflect.Slice], codec)
+				}
+				rb.RegisterDefaultEncoder(reflect.Slice, codec2)
+				if rb.kindEncoders[reflect.Slice] != codec2 {
+					t.Errorf("Did not properly set the slice codec. got %v; want %v", rb.kindEncoders[reflect.Slice], codec2)
+				}
+			})
+			t.Run("ArrayCodec", func(t *testing.T) {
+				codec := fakeCodec{num: 1}
+				codec2 := fakeCodec{num: 2}
+				rb := NewRegistryBuilder()
+				rb.RegisterDefaultEncoder(reflect.Array, codec)
+				if rb.kindEncoders[reflect.Array] != codec {
+					t.Errorf("Did not properly set the slice codec. got %v; want %v", rb.kindEncoders[reflect.Array], codec)
+				}
+				rb.RegisterDefaultEncoder(reflect.Array, codec2)
+				if rb.kindEncoders[reflect.Array] != codec2 {
+					t.Errorf("Did not properly set the slice codec. got %v; want %v", rb.kindEncoders[reflect.Array], codec2)
+				}
+			})
+		})
+		t.Run("Lookup", func(t *testing.T) {
+			type Codec interface {
+				ValueEncoder
+				ValueDecoder
+			}
+
+			var arrinstance [12]int
+			arr := reflect.TypeOf(arrinstance)
+			slc := reflect.TypeOf(make([]int, 12))
+			m := reflect.TypeOf(make(map[string]int))
+			strct := reflect.TypeOf(struct{ Foo string }{})
+			ft1 := reflect.PtrTo(reflect.TypeOf(fakeType1{}))
+			ft2 := reflect.TypeOf(fakeType2{})
+			ft3 := reflect.TypeOf(fakeType5(func(string, string) string { return "fakeType5" }))
+			ti2 := reflect.TypeOf((*testInterface2)(nil)).Elem()
+			fc1, fc2, fc4 := fakeCodec{num: 1}, fakeCodec{num: 2}, fakeCodec{num: 4}
+			fsc, fslcc, fmc := new(fakeStructCodec), new(fakeSliceCodec), new(fakeMapCodec)
+			pc := NewPointerCodec()
+			reg := NewRegistryBuilder().
+				RegisterEncoder(ft1, fc1).
+				RegisterEncoder(ft2, fc2).
+				RegisterEncoder(ti2, fc4).
+				RegisterDefaultEncoder(reflect.Struct, fsc).
+				RegisterDefaultEncoder(reflect.Slice, fslcc).
+				RegisterDefaultEncoder(reflect.Array, fslcc).
+				RegisterDefaultEncoder(reflect.Map, fmc).
+				RegisterDefaultEncoder(reflect.Ptr, pc).
+				RegisterDecoder(ft1, fc1).
+				RegisterDecoder(ft2, fc2).
+				RegisterDecoder(ti2, fc4).
+				RegisterDefaultDecoder(reflect.Struct, fsc).
+				RegisterDefaultDecoder(reflect.Slice, fslcc).
+				RegisterDefaultDecoder(reflect.Array, fslcc).
+				RegisterDefaultDecoder(reflect.Map, fmc).
+				RegisterDefaultDecoder(reflect.Ptr, pc).
+				Build()
+
+			testCases := []struct {
+				name      string
+				t         reflect.Type
+				wantcodec Codec
+				wanterr   error
+				testcache bool
+			}{
+				{
+					"type registry (pointer)",
+					ft1,
+					fc1,
+					nil,
+					false,
+				},
+				{
+					"type registry (non-pointer)",
+					ft2,
+					fc2,
+					nil,
+					false,
+				},
+				{
+					"interface registry",
+					ti2,
+					fc4,
+					nil,
+					true,
+				},
+				{
+					"default struct codec (pointer)",
+					reflect.PtrTo(strct),
+					pc,
+					nil,
+					false,
+				},
+				{
+					"default struct codec (non-pointer)",
+					strct,
+					fsc,
+					nil,
+					false,
+				},
+				{
+					"default array codec",
+					arr,
+					fslcc,
+					nil,
+					false,
+				},
+				{
+					"default slice codec",
+					slc,
+					fslcc,
+					nil,
+					false,
+				},
+				{
+					"default map",
+					m,
+					fmc,
+					nil,
+					false,
+				},
+				{
+					"map non-string key",
+					reflect.TypeOf(map[int]int{}),
+					fmc,
+					nil,
+					false,
+				},
+				{
+					"No Codec Registered",
+					ft3,
+					nil,
+					ErrNoEncoder{Type: ft3},
+					false,
+				},
+			}
+
+			allowunexported := cmp.AllowUnexported(fakeCodec{}, fakeStructCodec{}, fakeSliceCodec{}, fakeMapCodec{})
+			comparepc := func(pc1, pc2 *PointerCodec) bool { return true }
+			for _, tc := range testCases {
+				t.Run(tc.name, func(t *testing.T) {
+					t.Run("Encoder", func(t *testing.T) {
+						gotcodec, goterr := reg.LookupEncoder(tc.t)
+						if !cmp.Equal(goterr, tc.wanterr, cmp.Comparer(compareErrors)) {
+							t.Errorf("Errors did not match. got %v; want %v", goterr, tc.wanterr)
+						}
+						if !cmp.Equal(gotcodec, tc.wantcodec, allowunexported, cmp.Comparer(comparepc)) {
+							t.Errorf("Codecs did not match. got %v; want %v", gotcodec, tc.wantcodec)
+						}
+					})
+					t.Run("Decoder", func(t *testing.T) {
+						var wanterr error
+						if ene, ok := tc.wanterr.(ErrNoEncoder); ok {
+							wanterr = ErrNoDecoder{Type: ene.Type}
+						} else {
+							wanterr = tc.wanterr
+						}
+						gotcodec, goterr := reg.LookupDecoder(tc.t)
+						if !cmp.Equal(goterr, wanterr, cmp.Comparer(compareErrors)) {
+							t.Errorf("Errors did not match. got %v; want %v", goterr, wanterr)
+						}
+						if !cmp.Equal(gotcodec, tc.wantcodec, allowunexported, cmp.Comparer(comparepc)) {
+							t.Errorf("Codecs did not match. got %v; want %v", gotcodec, tc.wantcodec)
+							t.Errorf("Codecs did not match. got %T; want %T", gotcodec, tc.wantcodec)
+						}
+					})
+				})
+			}
+		})
+	})
+	t.Run("Type Map", func(t *testing.T) {
+		reg := NewRegistryBuilder().
+			RegisterTypeMapEntry(bsontype.String, reflect.TypeOf(string(""))).
+			RegisterTypeMapEntry(bsontype.Int32, reflect.TypeOf(int(0))).
+			Build()
+
+		var got, want reflect.Type
+
+		want = reflect.TypeOf(string(""))
+		got, err := reg.LookupTypeMapEntry(bsontype.String)
+		noerr(t, err)
+		if got != want {
+			t.Errorf("Did not get expected type. got %v; want %v", got, want)
+		}
+
+		want = reflect.TypeOf(int(0))
+		got, err = reg.LookupTypeMapEntry(bsontype.Int32)
+		noerr(t, err)
+		if got != want {
+			t.Errorf("Did not get expected type. got %v; want %v", got, want)
+		}
+
+		want = nil
+		wanterr := ErrNoTypeMapEntry{Type: bsontype.ObjectID}
+		got, err = reg.LookupTypeMapEntry(bsontype.ObjectID)
+		if err != wanterr {
+			t.Errorf("Did not get expected error. got %v; want %v", err, wanterr)
+		}
+		if got != want {
+			t.Errorf("Did not get expected type. got %v; want %v", got, want)
+		}
+	})
+}
+
+type fakeType1 struct{ b bool }
+type fakeType2 struct{ b bool }
+type fakeType3 struct{ b bool }
+type fakeType4 struct{ b bool }
+type fakeType5 func(string, string) string
+type fakeStructCodec struct{ fakeCodec }
+type fakeSliceCodec struct{ fakeCodec }
+type fakeMapCodec struct{ fakeCodec }
+
+type fakeCodec struct{ num int }
+
+func (fc fakeCodec) EncodeValue(EncodeContext, bsonrw.ValueWriter, reflect.Value) error {
+	return nil
+}
+func (fc fakeCodec) DecodeValue(DecodeContext, bsonrw.ValueReader, reflect.Value) error {
+	return nil
+}
+
+type testInterface1 interface{ test1() }
+type testInterface2 interface{ test2() }
+type testInterface3 interface{ test3() }
+type testInterface4 interface{ test4() }
+
+func typeComparer(i1, i2 reflect.Type) bool { return i1 == i2 }

+ 367 - 0
src/go.mongodb.org/mongo-driver/bson/bsoncodec/struct_codec.go

@@ -0,0 +1,367 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+package bsoncodec
+
+import (
+	"errors"
+	"fmt"
+	"reflect"
+	"strings"
+	"sync"
+
+	"go.mongodb.org/mongo-driver/bson/bsonrw"
+	"go.mongodb.org/mongo-driver/bson/bsontype"
+)
+
+var defaultStructCodec = &StructCodec{
+	cache:  make(map[reflect.Type]*structDescription),
+	parser: DefaultStructTagParser,
+}
+
+// Zeroer allows custom struct types to implement a report of zero
+// state. All struct types that don't implement Zeroer or where IsZero
+// returns false are considered to be not zero.
+type Zeroer interface {
+	IsZero() bool
+}
+
+// StructCodec is the Codec used for struct values.
+type StructCodec struct {
+	cache  map[reflect.Type]*structDescription
+	l      sync.RWMutex
+	parser StructTagParser
+}
+
+var _ ValueEncoder = &StructCodec{}
+var _ ValueDecoder = &StructCodec{}
+
+// NewStructCodec returns a StructCodec that uses p for struct tag parsing.
+func NewStructCodec(p StructTagParser) (*StructCodec, error) {
+	if p == nil {
+		return nil, errors.New("a StructTagParser must be provided to NewStructCodec")
+	}
+
+	return &StructCodec{
+		cache:  make(map[reflect.Type]*structDescription),
+		parser: p,
+	}, nil
+}
+
+// EncodeValue handles encoding generic struct types.
+func (sc *StructCodec) EncodeValue(r EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
+	if !val.IsValid() || val.Kind() != reflect.Struct {
+		return ValueEncoderError{Name: "StructCodec.EncodeValue", Kinds: []reflect.Kind{reflect.Struct}, Received: val}
+	}
+
+	sd, err := sc.describeStruct(r.Registry, val.Type())
+	if err != nil {
+		return err
+	}
+
+	dw, err := vw.WriteDocument()
+	if err != nil {
+		return err
+	}
+	var rv reflect.Value
+	for _, desc := range sd.fl {
+		if desc.inline == nil {
+			rv = val.Field(desc.idx)
+		} else {
+			rv = val.FieldByIndex(desc.inline)
+		}
+
+		if desc.encoder == nil {
+			return ErrNoEncoder{Type: rv.Type()}
+		}
+
+		encoder := desc.encoder
+
+		iszero := sc.isZero
+		if iz, ok := encoder.(CodecZeroer); ok {
+			iszero = iz.IsTypeZero
+		}
+
+		if desc.omitEmpty && iszero(rv.Interface()) {
+			continue
+		}
+
+		vw2, err := dw.WriteDocumentElement(desc.name)
+		if err != nil {
+			return err
+		}
+
+		ectx := EncodeContext{Registry: r.Registry, MinSize: desc.minSize}
+		err = encoder.EncodeValue(ectx, vw2, rv)
+		if err != nil {
+			return err
+		}
+	}
+
+	if sd.inlineMap >= 0 {
+		rv := val.Field(sd.inlineMap)
+		collisionFn := func(key string) bool {
+			_, exists := sd.fm[key]
+			return exists
+		}
+
+		return defaultValueEncoders.mapEncodeValue(r, dw, rv, collisionFn)
+	}
+
+	return dw.WriteDocumentEnd()
+}
+
+// DecodeValue implements the Codec interface.
+// By default, map types in val will not be cleared. If a map has existing key/value pairs, it will be extended with the new ones from vr.
+// For slices, the decoder will set the length of the slice to zero and append all elements. The underlying array will not be cleared.
+func (sc *StructCodec) DecodeValue(r DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if !val.CanSet() || val.Kind() != reflect.Struct {
+		return ValueDecoderError{Name: "StructCodec.DecodeValue", Kinds: []reflect.Kind{reflect.Struct}, Received: val}
+	}
+
+	switch vr.Type() {
+	case bsontype.Type(0), bsontype.EmbeddedDocument:
+	default:
+		return fmt.Errorf("cannot decode %v into a %s", vr.Type(), val.Type())
+	}
+
+	sd, err := sc.describeStruct(r.Registry, val.Type())
+	if err != nil {
+		return err
+	}
+
+	var decoder ValueDecoder
+	var inlineMap reflect.Value
+	if sd.inlineMap >= 0 {
+		inlineMap = val.Field(sd.inlineMap)
+		if inlineMap.IsNil() {
+			inlineMap.Set(reflect.MakeMap(inlineMap.Type()))
+		}
+		decoder, err = r.LookupDecoder(inlineMap.Type().Elem())
+		if err != nil {
+			return err
+		}
+	}
+
+	dr, err := vr.ReadDocument()
+	if err != nil {
+		return err
+	}
+
+	for {
+		name, vr, err := dr.ReadElement()
+		if err == bsonrw.ErrEOD {
+			break
+		}
+		if err != nil {
+			return err
+		}
+
+		fd, exists := sd.fm[name]
+		if !exists {
+			// if the original name isn't found in the struct description, try again with the name in lowercase
+			// this could match if a BSON tag isn't specified because by default, describeStruct lowercases all field
+			// names
+			fd, exists = sd.fm[strings.ToLower(name)]
+		}
+
+		if !exists {
+			if sd.inlineMap < 0 {
+				// The encoding/json package requires a flag to return on error for non-existent fields.
+				// This functionality seems appropriate for the struct codec.
+				err = vr.Skip()
+				if err != nil {
+					return err
+				}
+				continue
+			}
+
+			elem := reflect.New(inlineMap.Type().Elem()).Elem()
+			err = decoder.DecodeValue(r, vr, elem)
+			if err != nil {
+				return err
+			}
+			inlineMap.SetMapIndex(reflect.ValueOf(name), elem)
+			continue
+		}
+
+		var field reflect.Value
+		if fd.inline == nil {
+			field = val.Field(fd.idx)
+		} else {
+			field = val.FieldByIndex(fd.inline)
+		}
+
+		if !field.CanSet() { // Being settable is a super set of being addressable.
+			return fmt.Errorf("cannot decode element '%s' into field %v; it is not settable", name, field)
+		}
+		if field.Kind() == reflect.Ptr && field.IsNil() {
+			field.Set(reflect.New(field.Type().Elem()))
+		}
+		field = field.Addr()
+
+		dctx := DecodeContext{Registry: r.Registry, Truncate: fd.truncate || r.Truncate}
+		if fd.decoder == nil {
+			return ErrNoDecoder{Type: field.Elem().Type()}
+		}
+
+		if decoder, ok := fd.decoder.(ValueDecoder); ok {
+			err = decoder.DecodeValue(dctx, vr, field.Elem())
+			if err != nil {
+				return err
+			}
+			continue
+		}
+		err = fd.decoder.DecodeValue(dctx, vr, field)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func (sc *StructCodec) isZero(i interface{}) bool {
+	v := reflect.ValueOf(i)
+
+	// check the value validity
+	if !v.IsValid() {
+		return true
+	}
+
+	if z, ok := v.Interface().(Zeroer); ok && (v.Kind() != reflect.Ptr || !v.IsNil()) {
+		return z.IsZero()
+	}
+
+	switch v.Kind() {
+	case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
+		return v.Len() == 0
+	case reflect.Bool:
+		return !v.Bool()
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		return v.Int() == 0
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+		return v.Uint() == 0
+	case reflect.Float32, reflect.Float64:
+		return v.Float() == 0
+	case reflect.Interface, reflect.Ptr:
+		return v.IsNil()
+	}
+
+	return false
+}
+
+type structDescription struct {
+	fm        map[string]fieldDescription
+	fl        []fieldDescription
+	inlineMap int
+}
+
+type fieldDescription struct {
+	name      string
+	idx       int
+	omitEmpty bool
+	minSize   bool
+	truncate  bool
+	inline    []int
+	encoder   ValueEncoder
+	decoder   ValueDecoder
+}
+
+func (sc *StructCodec) describeStruct(r *Registry, t reflect.Type) (*structDescription, error) {
+	// We need to analyze the struct, including getting the tags, collecting
+	// information about inlining, and create a map of the field name to the field.
+	sc.l.RLock()
+	ds, exists := sc.cache[t]
+	sc.l.RUnlock()
+	if exists {
+		return ds, nil
+	}
+
+	numFields := t.NumField()
+	sd := &structDescription{
+		fm:        make(map[string]fieldDescription, numFields),
+		fl:        make([]fieldDescription, 0, numFields),
+		inlineMap: -1,
+	}
+
+	for i := 0; i < numFields; i++ {
+		sf := t.Field(i)
+		if sf.PkgPath != "" {
+			// unexported, ignore
+			continue
+		}
+
+		encoder, err := r.LookupEncoder(sf.Type)
+		if err != nil {
+			encoder = nil
+		}
+		decoder, err := r.LookupDecoder(sf.Type)
+		if err != nil {
+			decoder = nil
+		}
+
+		description := fieldDescription{idx: i, encoder: encoder, decoder: decoder}
+
+		stags, err := sc.parser.ParseStructTags(sf)
+		if err != nil {
+			return nil, err
+		}
+		if stags.Skip {
+			continue
+		}
+		description.name = stags.Name
+		description.omitEmpty = stags.OmitEmpty
+		description.minSize = stags.MinSize
+		description.truncate = stags.Truncate
+
+		if stags.Inline {
+			switch sf.Type.Kind() {
+			case reflect.Map:
+				if sd.inlineMap >= 0 {
+					return nil, errors.New("(struct " + t.String() + ") multiple inline maps")
+				}
+				if sf.Type.Key() != tString {
+					return nil, errors.New("(struct " + t.String() + ") inline map must have a string keys")
+				}
+				sd.inlineMap = description.idx
+			case reflect.Struct:
+				inlinesf, err := sc.describeStruct(r, sf.Type)
+				if err != nil {
+					return nil, err
+				}
+				for _, fd := range inlinesf.fl {
+					if _, exists := sd.fm[fd.name]; exists {
+						return nil, fmt.Errorf("(struct %s) duplicated key %s", t.String(), fd.name)
+					}
+					if fd.inline == nil {
+						fd.inline = []int{i, fd.idx}
+					} else {
+						fd.inline = append([]int{i}, fd.inline...)
+					}
+					sd.fm[fd.name] = fd
+					sd.fl = append(sd.fl, fd)
+				}
+			default:
+				return nil, fmt.Errorf("(struct %s) inline fields must be either a struct or a map", t.String())
+			}
+			continue
+		}
+
+		if _, exists := sd.fm[description.name]; exists {
+			return nil, fmt.Errorf("struct %s) duplicated key %s", t.String(), description.name)
+		}
+
+		sd.fm[description.name] = description
+		sd.fl = append(sd.fl, description)
+	}
+
+	sc.l.Lock()
+	sc.cache[t] = sd
+	sc.l.Unlock()
+
+	return sd, nil
+}

+ 47 - 0
src/go.mongodb.org/mongo-driver/bson/bsoncodec/struct_codec_test.go

@@ -0,0 +1,47 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+package bsoncodec
+
+import (
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestZeoerInterfaceUsedByDecoder(t *testing.T) {
+	enc := &StructCodec{}
+
+	// cases that are zero, because they are known types or pointers
+	var st *nonZeroer
+	assert.True(t, enc.isZero(st))
+	assert.True(t, enc.isZero(0))
+	assert.True(t, enc.isZero(false))
+
+	// cases that shouldn't be zero
+	st = &nonZeroer{value: false}
+	assert.False(t, enc.isZero(struct{ val bool }{val: true}))
+	assert.False(t, enc.isZero(struct{ val bool }{val: false}))
+	assert.False(t, enc.isZero(st))
+	st.value = true
+	assert.False(t, enc.isZero(st))
+
+	// a test to see if the interface impacts the outcome
+	z := zeroTest{}
+	assert.False(t, enc.isZero(z))
+
+	z.reportZero = true
+	assert.True(t, enc.isZero(z))
+
+	// *time.Time with nil should be zero
+	var tp *time.Time
+	assert.True(t, enc.isZero(tp))
+
+	// actually all zeroer if nil should also be zero
+	var zp *zeroTest
+	assert.True(t, enc.isZero(zp))
+}

+ 119 - 0
src/go.mongodb.org/mongo-driver/bson/bsoncodec/struct_tag_parser.go

@@ -0,0 +1,119 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+package bsoncodec
+
+import (
+	"reflect"
+	"strings"
+)
+
+// StructTagParser returns the struct tags for a given struct field.
+type StructTagParser interface {
+	ParseStructTags(reflect.StructField) (StructTags, error)
+}
+
+// StructTagParserFunc is an adapter that allows a generic function to be used
+// as a StructTagParser.
+type StructTagParserFunc func(reflect.StructField) (StructTags, error)
+
+// ParseStructTags implements the StructTagParser interface.
+func (stpf StructTagParserFunc) ParseStructTags(sf reflect.StructField) (StructTags, error) {
+	return stpf(sf)
+}
+
+// StructTags represents the struct tag fields that the StructCodec uses during
+// the encoding and decoding process.
+//
+// In the case of a struct, the lowercased field name is used as the key for each exported
+// field but this behavior may be changed using a struct tag. The tag may also contain flags to
+// adjust the marshalling behavior for the field.
+//
+// The properties are defined below:
+//
+//     OmitEmpty  Only include the field if it's not set to the zero value for the type or to
+//                empty slices or maps.
+//
+//     MinSize    Marshal an integer of a type larger than 32 bits value as an int32, if that's
+//                feasible while preserving the numeric value.
+//
+//     Truncate   When unmarshaling a BSON double, it is permitted to lose precision to fit within
+//                a float32.
+//
+//     Inline     Inline the field, which must be a struct or a map, causing all of its fields
+//                or keys to be processed as if they were part of the outer struct. For maps,
+//                keys must not conflict with the bson keys of other struct fields.
+//
+//     Skip       This struct field should be skipped. This is usually denoted by parsing a "-"
+//                for the name.
+//
+// TODO(skriptble): Add tags for undefined as nil and for null as nil.
+type StructTags struct {
+	Name      string
+	OmitEmpty bool
+	MinSize   bool
+	Truncate  bool
+	Inline    bool
+	Skip      bool
+}
+
+// DefaultStructTagParser is the StructTagParser used by the StructCodec by default.
+// It will handle the bson struct tag. See the documentation for StructTags to see
+// what each of the returned fields means.
+//
+// If there is no name in the struct tag fields, the struct field name is lowercased.
+// The tag formats accepted are:
+//
+//     "[<key>][,<flag1>[,<flag2>]]"
+//
+//     `(...) bson:"[<key>][,<flag1>[,<flag2>]]" (...)`
+//
+// An example:
+//
+//     type T struct {
+//         A bool
+//         B int    "myb"
+//         C string "myc,omitempty"
+//         D string `bson:",omitempty" json:"jsonkey"`
+//         E int64  ",minsize"
+//         F int64  "myf,omitempty,minsize"
+//     }
+//
+// A struct tag either consisting entirely of '-' or with a bson key with a
+// value consisting entirely of '-' will return a StructTags with Skip true and
+// the remaining fields will be their default values.
+var DefaultStructTagParser StructTagParserFunc = func(sf reflect.StructField) (StructTags, error) {
+	key := strings.ToLower(sf.Name)
+	tag, ok := sf.Tag.Lookup("bson")
+	if !ok && !strings.Contains(string(sf.Tag), ":") && len(sf.Tag) > 0 {
+		tag = string(sf.Tag)
+	}
+	var st StructTags
+	if tag == "-" {
+		st.Skip = true
+		return st, nil
+	}
+
+	for idx, str := range strings.Split(tag, ",") {
+		if idx == 0 && str != "" {
+			key = str
+		}
+		switch str {
+		case "omitempty":
+			st.OmitEmpty = true
+		case "minsize":
+			st.MinSize = true
+		case "truncate":
+			st.Truncate = true
+		case "inline":
+			st.Inline = true
+		}
+	}
+
+	st.Name = key
+
+	return st, nil
+}

+ 73 - 0
src/go.mongodb.org/mongo-driver/bson/bsoncodec/struct_tag_parser_test.go

@@ -0,0 +1,73 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+package bsoncodec
+
+import (
+	"reflect"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+)
+
+func TestDefaultStructTagParser(t *testing.T) {
+	testCases := []struct {
+		name string
+		sf   reflect.StructField
+		want StructTags
+	}{
+		{
+			"no bson tag",
+			reflect.StructField{Name: "foo", Tag: reflect.StructTag("bar")},
+			StructTags{Name: "bar"},
+		},
+		{
+			"empty",
+			reflect.StructField{Name: "foo", Tag: reflect.StructTag("")},
+			StructTags{Name: "foo"},
+		},
+		{
+			"tag only dash",
+			reflect.StructField{Name: "foo", Tag: reflect.StructTag("-")},
+			StructTags{Skip: true},
+		},
+		{
+			"bson tag only dash",
+			reflect.StructField{Name: "foo", Tag: reflect.StructTag(`bson:"-"`)},
+			StructTags{Skip: true},
+		},
+		{
+			"all options",
+			reflect.StructField{Name: "foo", Tag: reflect.StructTag(`bar,omitempty,minsize,truncate,inline`)},
+			StructTags{Name: "bar", OmitEmpty: true, MinSize: true, Truncate: true, Inline: true},
+		},
+		{
+			"all options default name",
+			reflect.StructField{Name: "foo", Tag: reflect.StructTag(`,omitempty,minsize,truncate,inline`)},
+			StructTags{Name: "foo", OmitEmpty: true, MinSize: true, Truncate: true, Inline: true},
+		},
+		{
+			"bson tag all options",
+			reflect.StructField{Name: "foo", Tag: reflect.StructTag(`bson:"bar,omitempty,minsize,truncate,inline"`)},
+			StructTags{Name: "bar", OmitEmpty: true, MinSize: true, Truncate: true, Inline: true},
+		},
+		{
+			"bson tag all options default name",
+			reflect.StructField{Name: "foo", Tag: reflect.StructTag(`bson:",omitempty,minsize,truncate,inline"`)},
+			StructTags{Name: "foo", OmitEmpty: true, MinSize: true, Truncate: true, Inline: true},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			got, err := DefaultStructTagParser(tc.sf)
+			noerr(t, err)
+			if !cmp.Equal(got, tc.want) {
+				t.Errorf("Returned struct tags do not match. got %#v; want %#v", got, tc.want)
+			}
+		})
+	}
+}

+ 80 - 0
src/go.mongodb.org/mongo-driver/bson/bsoncodec/types.go

@@ -0,0 +1,80 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+package bsoncodec
+
+import (
+	"encoding/json"
+	"net/url"
+	"reflect"
+	"time"
+
+	"go.mongodb.org/mongo-driver/bson/primitive"
+	"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
+)
+
+var ptBool = reflect.TypeOf((*bool)(nil))
+var ptInt8 = reflect.TypeOf((*int8)(nil))
+var ptInt16 = reflect.TypeOf((*int16)(nil))
+var ptInt32 = reflect.TypeOf((*int32)(nil))
+var ptInt64 = reflect.TypeOf((*int64)(nil))
+var ptInt = reflect.TypeOf((*int)(nil))
+var ptUint8 = reflect.TypeOf((*uint8)(nil))
+var ptUint16 = reflect.TypeOf((*uint16)(nil))
+var ptUint32 = reflect.TypeOf((*uint32)(nil))
+var ptUint64 = reflect.TypeOf((*uint64)(nil))
+var ptUint = reflect.TypeOf((*uint)(nil))
+var ptFloat32 = reflect.TypeOf((*float32)(nil))
+var ptFloat64 = reflect.TypeOf((*float64)(nil))
+var ptString = reflect.TypeOf((*string)(nil))
+
+var tBool = reflect.TypeOf(false)
+var tFloat32 = reflect.TypeOf(float32(0))
+var tFloat64 = reflect.TypeOf(float64(0))
+var tInt = reflect.TypeOf(int(0))
+var tInt8 = reflect.TypeOf(int8(0))
+var tInt16 = reflect.TypeOf(int16(0))
+var tInt32 = reflect.TypeOf(int32(0))
+var tInt64 = reflect.TypeOf(int64(0))
+var tString = reflect.TypeOf("")
+var tTime = reflect.TypeOf(time.Time{})
+var tUint = reflect.TypeOf(uint(0))
+var tUint8 = reflect.TypeOf(uint8(0))
+var tUint16 = reflect.TypeOf(uint16(0))
+var tUint32 = reflect.TypeOf(uint32(0))
+var tUint64 = reflect.TypeOf(uint64(0))
+
+var tEmpty = reflect.TypeOf((*interface{})(nil)).Elem()
+var tByteSlice = reflect.TypeOf([]byte(nil))
+var tByte = reflect.TypeOf(byte(0x00))
+var tURL = reflect.TypeOf(url.URL{})
+var tJSONNumber = reflect.TypeOf(json.Number(""))
+
+var tValueMarshaler = reflect.TypeOf((*ValueMarshaler)(nil)).Elem()
+var tValueUnmarshaler = reflect.TypeOf((*ValueUnmarshaler)(nil)).Elem()
+var tMarshaler = reflect.TypeOf((*Marshaler)(nil)).Elem()
+var tUnmarshaler = reflect.TypeOf((*Unmarshaler)(nil)).Elem()
+var tProxy = reflect.TypeOf((*Proxy)(nil)).Elem()
+
+var tBinary = reflect.TypeOf(primitive.Binary{})
+var tUndefined = reflect.TypeOf(primitive.Undefined{})
+var tOID = reflect.TypeOf(primitive.ObjectID{})
+var tDateTime = reflect.TypeOf(primitive.DateTime(0))
+var tNull = reflect.TypeOf(primitive.Null{})
+var tRegex = reflect.TypeOf(primitive.Regex{})
+var tCodeWithScope = reflect.TypeOf(primitive.CodeWithScope{})
+var tDBPointer = reflect.TypeOf(primitive.DBPointer{})
+var tJavaScript = reflect.TypeOf(primitive.JavaScript(""))
+var tSymbol = reflect.TypeOf(primitive.Symbol(""))
+var tTimestamp = reflect.TypeOf(primitive.Timestamp{})
+var tDecimal = reflect.TypeOf(primitive.Decimal128{})
+var tMinKey = reflect.TypeOf(primitive.MinKey{})
+var tMaxKey = reflect.TypeOf(primitive.MaxKey{})
+var tD = reflect.TypeOf(primitive.D{})
+var tA = reflect.TypeOf(primitive.A{})
+var tE = reflect.TypeOf(primitive.E{})
+
+var tCoreDocument = reflect.TypeOf(bsoncore.Document{})

+ 33 - 0
src/go.mongodb.org/mongo-driver/bson/bsonrw/bsonrw_test.go

@@ -0,0 +1,33 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+package bsonrw
+
+import "testing"
+
+func compareErrors(err1, err2 error) bool {
+	if err1 == nil && err2 == nil {
+		return true
+	}
+
+	if err1 == nil || err2 == nil {
+		return false
+	}
+
+	if err1.Error() != err2.Error() {
+		return false
+	}
+
+	return true
+}
+
+func noerr(t *testing.T, err error) {
+	if err != nil {
+		t.Helper()
+		t.Errorf("Unexpected error: (%T)%v", err, err)
+		t.FailNow()
+	}
+}

+ 847 - 0
src/go.mongodb.org/mongo-driver/bson/bsonrw/bsonrwtest/bsonrwtest.go

@@ -0,0 +1,847 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+package bsonrwtest // import "go.mongodb.org/mongo-driver/bson/bsonrw/bsonrwtest"
+
+import (
+	"testing"
+
+	"go.mongodb.org/mongo-driver/bson/bsonrw"
+	"go.mongodb.org/mongo-driver/bson/bsontype"
+	"go.mongodb.org/mongo-driver/bson/primitive"
+	"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
+)
+
+var _ bsonrw.ValueReader = (*ValueReaderWriter)(nil)
+var _ bsonrw.ValueWriter = (*ValueReaderWriter)(nil)
+
+// Invoked is a type used to indicate what method was called last.
+type Invoked byte
+
+// These are the different methods that can be invoked.
+const (
+	Nothing Invoked = iota
+	ReadArray
+	ReadBinary
+	ReadBoolean
+	ReadDocument
+	ReadCodeWithScope
+	ReadDBPointer
+	ReadDateTime
+	ReadDecimal128
+	ReadDouble
+	ReadInt32
+	ReadInt64
+	ReadJavascript
+	ReadMaxKey
+	ReadMinKey
+	ReadNull
+	ReadObjectID
+	ReadRegex
+	ReadString
+	ReadSymbol
+	ReadTimestamp
+	ReadUndefined
+	ReadElement
+	ReadValue
+	WriteArray
+	WriteBinary
+	WriteBinaryWithSubtype
+	WriteBoolean
+	WriteCodeWithScope
+	WriteDBPointer
+	WriteDateTime
+	WriteDecimal128
+	WriteDouble
+	WriteInt32
+	WriteInt64
+	WriteJavascript
+	WriteMaxKey
+	WriteMinKey
+	WriteNull
+	WriteObjectID
+	WriteRegex
+	WriteString
+	WriteDocument
+	WriteSymbol
+	WriteTimestamp
+	WriteUndefined
+	WriteDocumentElement
+	WriteDocumentEnd
+	WriteArrayElement
+	WriteArrayEnd
+	Skip
+)
+
+func (i Invoked) String() string {
+	switch i {
+	case Nothing:
+		return "Nothing"
+	case ReadArray:
+		return "ReadArray"
+	case ReadBinary:
+		return "ReadBinary"
+	case ReadBoolean:
+		return "ReadBoolean"
+	case ReadDocument:
+		return "ReadDocument"
+	case ReadCodeWithScope:
+		return "ReadCodeWithScope"
+	case ReadDBPointer:
+		return "ReadDBPointer"
+	case ReadDateTime:
+		return "ReadDateTime"
+	case ReadDecimal128:
+		return "ReadDecimal128"
+	case ReadDouble:
+		return "ReadDouble"
+	case ReadInt32:
+		return "ReadInt32"
+	case ReadInt64:
+		return "ReadInt64"
+	case ReadJavascript:
+		return "ReadJavascript"
+	case ReadMaxKey:
+		return "ReadMaxKey"
+	case ReadMinKey:
+		return "ReadMinKey"
+	case ReadNull:
+		return "ReadNull"
+	case ReadObjectID:
+		return "ReadObjectID"
+	case ReadRegex:
+		return "ReadRegex"
+	case ReadString:
+		return "ReadString"
+	case ReadSymbol:
+		return "ReadSymbol"
+	case ReadTimestamp:
+		return "ReadTimestamp"
+	case ReadUndefined:
+		return "ReadUndefined"
+	case ReadElement:
+		return "ReadElement"
+	case ReadValue:
+		return "ReadValue"
+	case WriteArray:
+		return "WriteArray"
+	case WriteBinary:
+		return "WriteBinary"
+	case WriteBinaryWithSubtype:
+		return "WriteBinaryWithSubtype"
+	case WriteBoolean:
+		return "WriteBoolean"
+	case WriteCodeWithScope:
+		return "WriteCodeWithScope"
+	case WriteDBPointer:
+		return "WriteDBPointer"
+	case WriteDateTime:
+		return "WriteDateTime"
+	case WriteDecimal128:
+		return "WriteDecimal128"
+	case WriteDouble:
+		return "WriteDouble"
+	case WriteInt32:
+		return "WriteInt32"
+	case WriteInt64:
+		return "WriteInt64"
+	case WriteJavascript:
+		return "WriteJavascript"
+	case WriteMaxKey:
+		return "WriteMaxKey"
+	case WriteMinKey:
+		return "WriteMinKey"
+	case WriteNull:
+		return "WriteNull"
+	case WriteObjectID:
+		return "WriteObjectID"
+	case WriteRegex:
+		return "WriteRegex"
+	case WriteString:
+		return "WriteString"
+	case WriteDocument:
+		return "WriteDocument"
+	case WriteSymbol:
+		return "WriteSymbol"
+	case WriteTimestamp:
+		return "WriteTimestamp"
+	case WriteUndefined:
+		return "WriteUndefined"
+	case WriteDocumentElement:
+		return "WriteDocumentElement"
+	case WriteDocumentEnd:
+		return "WriteDocumentEnd"
+	case WriteArrayElement:
+		return "WriteArrayElement"
+	case WriteArrayEnd:
+		return "WriteArrayEnd"
+	default:
+		return "<unknown>"
+	}
+}
+
+// ValueReaderWriter is a test implementation of a bsonrw.ValueReader and bsonrw.ValueWriter
+type ValueReaderWriter struct {
+	T        *testing.T
+	Invoked  Invoked
+	Return   interface{} // Can be a primitive or a bsoncore.Value
+	BSONType bsontype.Type
+	Err      error
+	ErrAfter Invoked // error after this method is called
+	depth    uint64
+}
+
+// prevent infinite recursion.
+func (llvrw *ValueReaderWriter) checkdepth() {
+	llvrw.depth++
+	if llvrw.depth > 1000 {
+		panic("max depth exceeded")
+	}
+}
+
+// Type implements the bsonrw.ValueReader interface.
+func (llvrw *ValueReaderWriter) Type() bsontype.Type {
+	llvrw.checkdepth()
+	return llvrw.BSONType
+}
+
+// Skip implements the bsonrw.ValueReader interface.
+func (llvrw *ValueReaderWriter) Skip() error {
+	llvrw.checkdepth()
+	llvrw.Invoked = Skip
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return llvrw.Err
+	}
+	return nil
+}
+
+// ReadArray implements the bsonrw.ValueReader interface.
+func (llvrw *ValueReaderWriter) ReadArray() (bsonrw.ArrayReader, error) {
+	llvrw.checkdepth()
+	llvrw.Invoked = ReadArray
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return nil, llvrw.Err
+	}
+
+	return llvrw, nil
+}
+
+// ReadBinary implements the bsonrw.ValueReader interface.
+func (llvrw *ValueReaderWriter) ReadBinary() (b []byte, btype byte, err error) {
+	llvrw.checkdepth()
+	llvrw.Invoked = ReadBinary
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return nil, 0x00, llvrw.Err
+	}
+
+	switch tt := llvrw.Return.(type) {
+	case bsoncore.Value:
+		subtype, data, _, ok := bsoncore.ReadBinary(tt.Data)
+		if !ok {
+			llvrw.T.Error("Invalid Value provided for return value of ReadBinary.")
+			return nil, 0x00, nil
+		}
+		return data, subtype, nil
+	default:
+		llvrw.T.Errorf("Incorrect type provided for return value of ReadBinary: %T", llvrw.Return)
+		return nil, 0x00, nil
+	}
+}
+
+// ReadBoolean implements the bsonrw.ValueReader interface.
+func (llvrw *ValueReaderWriter) ReadBoolean() (bool, error) {
+	llvrw.checkdepth()
+	llvrw.Invoked = ReadBoolean
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return false, llvrw.Err
+	}
+
+	switch tt := llvrw.Return.(type) {
+	case bool:
+		return tt, nil
+	case bsoncore.Value:
+		b, _, ok := bsoncore.ReadBoolean(tt.Data)
+		if !ok {
+			llvrw.T.Error("Invalid Value provided for return value of ReadBoolean.")
+			return false, nil
+		}
+		return b, nil
+	default:
+		llvrw.T.Errorf("Incorrect type provided for return value of ReadBoolean: %T", llvrw.Return)
+		return false, nil
+	}
+}
+
+// ReadDocument implements the bsonrw.ValueReader interface.
+func (llvrw *ValueReaderWriter) ReadDocument() (bsonrw.DocumentReader, error) {
+	llvrw.checkdepth()
+	llvrw.Invoked = ReadDocument
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return nil, llvrw.Err
+	}
+
+	return llvrw, nil
+}
+
+// ReadCodeWithScope implements the bsonrw.ValueReader interface.
+func (llvrw *ValueReaderWriter) ReadCodeWithScope() (code string, dr bsonrw.DocumentReader, err error) {
+	llvrw.checkdepth()
+	llvrw.Invoked = ReadCodeWithScope
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return "", nil, llvrw.Err
+	}
+
+	return "", llvrw, nil
+}
+
+// ReadDBPointer implements the bsonrw.ValueReader interface.
+func (llvrw *ValueReaderWriter) ReadDBPointer() (ns string, oid primitive.ObjectID, err error) {
+	llvrw.checkdepth()
+	llvrw.Invoked = ReadDBPointer
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return "", primitive.ObjectID{}, llvrw.Err
+	}
+
+	switch tt := llvrw.Return.(type) {
+	case bsoncore.Value:
+		ns, oid, _, ok := bsoncore.ReadDBPointer(tt.Data)
+		if !ok {
+			llvrw.T.Error("Invalid Value instance provided for return value of ReadDBPointer")
+			return "", primitive.ObjectID{}, nil
+		}
+		return ns, oid, nil
+	default:
+		llvrw.T.Errorf("Incorrect type provided for return value of ReadDBPointer: %T", llvrw.Return)
+		return "", primitive.ObjectID{}, nil
+	}
+}
+
+// ReadDateTime implements the bsonrw.ValueReader interface.
+func (llvrw *ValueReaderWriter) ReadDateTime() (int64, error) {
+	llvrw.checkdepth()
+	llvrw.Invoked = ReadDateTime
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return 0, llvrw.Err
+	}
+
+	dt, ok := llvrw.Return.(int64)
+	if !ok {
+		llvrw.T.Errorf("Incorrect type provided for return value of ReadDateTime: %T", llvrw.Return)
+		return 0, nil
+	}
+
+	return dt, nil
+}
+
+// ReadDecimal128 implements the bsonrw.ValueReader interface.
+func (llvrw *ValueReaderWriter) ReadDecimal128() (primitive.Decimal128, error) {
+	llvrw.checkdepth()
+	llvrw.Invoked = ReadDecimal128
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return primitive.Decimal128{}, llvrw.Err
+	}
+
+	d128, ok := llvrw.Return.(primitive.Decimal128)
+	if !ok {
+		llvrw.T.Errorf("Incorrect type provided for return value of ReadDecimal128: %T", llvrw.Return)
+		return primitive.Decimal128{}, nil
+	}
+
+	return d128, nil
+}
+
+// ReadDouble implements the bsonrw.ValueReader interface.
+func (llvrw *ValueReaderWriter) ReadDouble() (float64, error) {
+	llvrw.checkdepth()
+	llvrw.Invoked = ReadDouble
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return 0, llvrw.Err
+	}
+
+	f64, ok := llvrw.Return.(float64)
+	if !ok {
+		llvrw.T.Errorf("Incorrect type provided for return value of ReadDouble: %T", llvrw.Return)
+		return 0, nil
+	}
+
+	return f64, nil
+}
+
+// ReadInt32 implements the bsonrw.ValueReader interface.
+func (llvrw *ValueReaderWriter) ReadInt32() (int32, error) {
+	llvrw.checkdepth()
+	llvrw.Invoked = ReadInt32
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return 0, llvrw.Err
+	}
+
+	i32, ok := llvrw.Return.(int32)
+	if !ok {
+		llvrw.T.Errorf("Incorrect type provided for return value of ReadInt32: %T", llvrw.Return)
+		return 0, nil
+	}
+
+	return i32, nil
+}
+
+// ReadInt64 implements the bsonrw.ValueReader interface.
+func (llvrw *ValueReaderWriter) ReadInt64() (int64, error) {
+	llvrw.checkdepth()
+	llvrw.Invoked = ReadInt64
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return 0, llvrw.Err
+	}
+	i64, ok := llvrw.Return.(int64)
+	if !ok {
+		llvrw.T.Errorf("Incorrect type provided for return value of ReadInt64: %T", llvrw.Return)
+		return 0, nil
+	}
+
+	return i64, nil
+}
+
+// ReadJavascript implements the bsonrw.ValueReader interface.
+func (llvrw *ValueReaderWriter) ReadJavascript() (code string, err error) {
+	llvrw.checkdepth()
+	llvrw.Invoked = ReadJavascript
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return "", llvrw.Err
+	}
+	js, ok := llvrw.Return.(string)
+	if !ok {
+		llvrw.T.Errorf("Incorrect type provided for return value of ReadJavascript: %T", llvrw.Return)
+		return "", nil
+	}
+
+	return js, nil
+}
+
+// ReadMaxKey implements the bsonrw.ValueReader interface.
+func (llvrw *ValueReaderWriter) ReadMaxKey() error {
+	llvrw.checkdepth()
+	llvrw.Invoked = ReadMaxKey
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return llvrw.Err
+	}
+
+	return nil
+}
+
+// ReadMinKey implements the bsonrw.ValueReader interface.
+func (llvrw *ValueReaderWriter) ReadMinKey() error {
+	llvrw.checkdepth()
+	llvrw.Invoked = ReadMinKey
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return llvrw.Err
+	}
+
+	return nil
+}
+
+// ReadNull implements the bsonrw.ValueReader interface.
+func (llvrw *ValueReaderWriter) ReadNull() error {
+	llvrw.checkdepth()
+	llvrw.Invoked = ReadNull
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return llvrw.Err
+	}
+
+	return nil
+}
+
+// ReadObjectID implements the bsonrw.ValueReader interface.
+func (llvrw *ValueReaderWriter) ReadObjectID() (primitive.ObjectID, error) {
+	llvrw.checkdepth()
+	llvrw.Invoked = ReadObjectID
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return primitive.ObjectID{}, llvrw.Err
+	}
+	oid, ok := llvrw.Return.(primitive.ObjectID)
+	if !ok {
+		llvrw.T.Errorf("Incorrect type provided for return value of ReadObjectID: %T", llvrw.Return)
+		return primitive.ObjectID{}, nil
+	}
+
+	return oid, nil
+}
+
+// ReadRegex implements the bsonrw.ValueReader interface.
+func (llvrw *ValueReaderWriter) ReadRegex() (pattern string, options string, err error) {
+	llvrw.checkdepth()
+	llvrw.Invoked = ReadRegex
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return "", "", llvrw.Err
+	}
+	switch tt := llvrw.Return.(type) {
+	case bsoncore.Value:
+		pattern, options, _, ok := bsoncore.ReadRegex(tt.Data)
+		if !ok {
+			llvrw.T.Error("Invalid Value instance provided for ReadRegex")
+			return "", "", nil
+		}
+		return pattern, options, nil
+	default:
+		llvrw.T.Errorf("Incorrect type provided for return value of ReadRegex: %T", llvrw.Return)
+		return "", "", nil
+	}
+}
+
+// ReadString implements the bsonrw.ValueReader interface.
+func (llvrw *ValueReaderWriter) ReadString() (string, error) {
+	llvrw.checkdepth()
+	llvrw.Invoked = ReadString
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return "", llvrw.Err
+	}
+	str, ok := llvrw.Return.(string)
+	if !ok {
+		llvrw.T.Errorf("Incorrect type provided for return value of ReadString: %T", llvrw.Return)
+		return "", nil
+	}
+
+	return str, nil
+}
+
+// ReadSymbol implements the bsonrw.ValueReader interface.
+func (llvrw *ValueReaderWriter) ReadSymbol() (symbol string, err error) {
+	llvrw.checkdepth()
+	llvrw.Invoked = ReadSymbol
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return "", llvrw.Err
+	}
+	switch tt := llvrw.Return.(type) {
+	case string:
+		return tt, nil
+	case bsoncore.Value:
+		symbol, _, ok := bsoncore.ReadSymbol(tt.Data)
+		if !ok {
+			llvrw.T.Error("Invalid Value instance provided for ReadSymbol")
+			return "", nil
+		}
+		return symbol, nil
+	default:
+		llvrw.T.Errorf("Incorrect type provided for return value of ReadSymbol: %T", llvrw.Return)
+		return "", nil
+	}
+}
+
+// ReadTimestamp implements the bsonrw.ValueReader interface.
+func (llvrw *ValueReaderWriter) ReadTimestamp() (t uint32, i uint32, err error) {
+	llvrw.checkdepth()
+	llvrw.Invoked = ReadTimestamp
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return 0, 0, llvrw.Err
+	}
+	switch tt := llvrw.Return.(type) {
+	case bsoncore.Value:
+		t, i, _, ok := bsoncore.ReadTimestamp(tt.Data)
+		if !ok {
+			llvrw.T.Errorf("Invalid Value instance provided for return value of ReadTimestamp")
+			return 0, 0, nil
+		}
+		return t, i, nil
+	default:
+		llvrw.T.Errorf("Incorrect type provided for return value of ReadTimestamp: %T", llvrw.Return)
+		return 0, 0, nil
+	}
+}
+
+// ReadUndefined implements the bsonrw.ValueReader interface.
+func (llvrw *ValueReaderWriter) ReadUndefined() error {
+	llvrw.checkdepth()
+	llvrw.Invoked = ReadUndefined
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return llvrw.Err
+	}
+
+	return nil
+}
+
+// WriteArray implements the bsonrw.ValueWriter interface.
+func (llvrw *ValueReaderWriter) WriteArray() (bsonrw.ArrayWriter, error) {
+	llvrw.checkdepth()
+	llvrw.Invoked = WriteArray
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return nil, llvrw.Err
+	}
+	return llvrw, nil
+}
+
+// WriteBinary implements the bsonrw.ValueWriter interface.
+func (llvrw *ValueReaderWriter) WriteBinary(b []byte) error {
+	llvrw.checkdepth()
+	llvrw.Invoked = WriteBinary
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return llvrw.Err
+	}
+	return nil
+}
+
+// WriteBinaryWithSubtype implements the bsonrw.ValueWriter interface.
+func (llvrw *ValueReaderWriter) WriteBinaryWithSubtype(b []byte, btype byte) error {
+	llvrw.checkdepth()
+	llvrw.Invoked = WriteBinaryWithSubtype
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return llvrw.Err
+	}
+	return nil
+}
+
+// WriteBoolean implements the bsonrw.ValueWriter interface.
+func (llvrw *ValueReaderWriter) WriteBoolean(bool) error {
+	llvrw.checkdepth()
+	llvrw.Invoked = WriteBoolean
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return llvrw.Err
+	}
+	return nil
+}
+
+// WriteCodeWithScope implements the bsonrw.ValueWriter interface.
+func (llvrw *ValueReaderWriter) WriteCodeWithScope(code string) (bsonrw.DocumentWriter, error) {
+	llvrw.checkdepth()
+	llvrw.Invoked = WriteCodeWithScope
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return nil, llvrw.Err
+	}
+	return llvrw, nil
+}
+
+// WriteDBPointer implements the bsonrw.ValueWriter interface.
+func (llvrw *ValueReaderWriter) WriteDBPointer(ns string, oid primitive.ObjectID) error {
+	llvrw.checkdepth()
+	llvrw.Invoked = WriteDBPointer
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return llvrw.Err
+	}
+	return nil
+}
+
+// WriteDateTime implements the bsonrw.ValueWriter interface.
+func (llvrw *ValueReaderWriter) WriteDateTime(dt int64) error {
+	llvrw.checkdepth()
+	llvrw.Invoked = WriteDateTime
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return llvrw.Err
+	}
+	return nil
+}
+
+// WriteDecimal128 implements the bsonrw.ValueWriter interface.
+func (llvrw *ValueReaderWriter) WriteDecimal128(primitive.Decimal128) error {
+	llvrw.checkdepth()
+	llvrw.Invoked = WriteDecimal128
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return llvrw.Err
+	}
+	return nil
+}
+
+// WriteDouble implements the bsonrw.ValueWriter interface.
+func (llvrw *ValueReaderWriter) WriteDouble(float64) error {
+	llvrw.checkdepth()
+	llvrw.Invoked = WriteDouble
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return llvrw.Err
+	}
+	return nil
+}
+
+// WriteInt32 implements the bsonrw.ValueWriter interface.
+func (llvrw *ValueReaderWriter) WriteInt32(int32) error {
+	llvrw.checkdepth()
+	llvrw.Invoked = WriteInt32
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return llvrw.Err
+	}
+	return nil
+}
+
+// WriteInt64 implements the bsonrw.ValueWriter interface.
+func (llvrw *ValueReaderWriter) WriteInt64(int64) error {
+	llvrw.checkdepth()
+	llvrw.Invoked = WriteInt64
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return llvrw.Err
+	}
+	return nil
+}
+
+// WriteJavascript implements the bsonrw.ValueWriter interface.
+func (llvrw *ValueReaderWriter) WriteJavascript(code string) error {
+	llvrw.checkdepth()
+	llvrw.Invoked = WriteJavascript
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return llvrw.Err
+	}
+	return nil
+}
+
+// WriteMaxKey implements the bsonrw.ValueWriter interface.
+func (llvrw *ValueReaderWriter) WriteMaxKey() error {
+	llvrw.checkdepth()
+	llvrw.Invoked = WriteMaxKey
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return llvrw.Err
+	}
+	return nil
+}
+
+// WriteMinKey implements the bsonrw.ValueWriter interface.
+func (llvrw *ValueReaderWriter) WriteMinKey() error {
+	llvrw.checkdepth()
+	llvrw.Invoked = WriteMinKey
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return llvrw.Err
+	}
+	return nil
+}
+
+// WriteNull implements the bsonrw.ValueWriter interface.
+func (llvrw *ValueReaderWriter) WriteNull() error {
+	llvrw.checkdepth()
+	llvrw.Invoked = WriteNull
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return llvrw.Err
+	}
+	return nil
+}
+
+// WriteObjectID implements the bsonrw.ValueWriter interface.
+func (llvrw *ValueReaderWriter) WriteObjectID(primitive.ObjectID) error {
+	llvrw.checkdepth()
+	llvrw.Invoked = WriteObjectID
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return llvrw.Err
+	}
+	return nil
+}
+
+// WriteRegex implements the bsonrw.ValueWriter interface.
+func (llvrw *ValueReaderWriter) WriteRegex(pattern string, options string) error {
+	llvrw.checkdepth()
+	llvrw.Invoked = WriteRegex
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return llvrw.Err
+	}
+	return nil
+}
+
+// WriteString implements the bsonrw.ValueWriter interface.
+func (llvrw *ValueReaderWriter) WriteString(string) error {
+	llvrw.checkdepth()
+	llvrw.Invoked = WriteString
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return llvrw.Err
+	}
+	return nil
+}
+
+// WriteDocument implements the bsonrw.ValueWriter interface.
+func (llvrw *ValueReaderWriter) WriteDocument() (bsonrw.DocumentWriter, error) {
+	llvrw.checkdepth()
+	llvrw.Invoked = WriteDocument
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return nil, llvrw.Err
+	}
+	return llvrw, nil
+}
+
+// WriteSymbol implements the bsonrw.ValueWriter interface.
+func (llvrw *ValueReaderWriter) WriteSymbol(symbol string) error {
+	llvrw.checkdepth()
+	llvrw.Invoked = WriteSymbol
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return llvrw.Err
+	}
+	return nil
+}
+
+// WriteTimestamp implements the bsonrw.ValueWriter interface.
+func (llvrw *ValueReaderWriter) WriteTimestamp(t uint32, i uint32) error {
+	llvrw.checkdepth()
+	llvrw.Invoked = WriteTimestamp
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return llvrw.Err
+	}
+	return nil
+}
+
+// WriteUndefined implements the bsonrw.ValueWriter interface.
+func (llvrw *ValueReaderWriter) WriteUndefined() error {
+	llvrw.checkdepth()
+	llvrw.Invoked = WriteUndefined
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return llvrw.Err
+	}
+	return nil
+}
+
+// ReadElement implements the bsonrw.DocumentReader interface.
+func (llvrw *ValueReaderWriter) ReadElement() (string, bsonrw.ValueReader, error) {
+	llvrw.checkdepth()
+	llvrw.Invoked = ReadElement
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return "", nil, llvrw.Err
+	}
+
+	return "", llvrw, nil
+}
+
+// WriteDocumentElement implements the bsonrw.DocumentWriter interface.
+func (llvrw *ValueReaderWriter) WriteDocumentElement(string) (bsonrw.ValueWriter, error) {
+	llvrw.checkdepth()
+	llvrw.Invoked = WriteDocumentElement
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return nil, llvrw.Err
+	}
+
+	return llvrw, nil
+}
+
+// WriteDocumentEnd implements the bsonrw.DocumentWriter interface.
+func (llvrw *ValueReaderWriter) WriteDocumentEnd() error {
+	llvrw.checkdepth()
+	llvrw.Invoked = WriteDocumentEnd
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return llvrw.Err
+	}
+
+	return nil
+}
+
+// ReadValue implements the bsonrw.ArrayReader interface.
+func (llvrw *ValueReaderWriter) ReadValue() (bsonrw.ValueReader, error) {
+	llvrw.checkdepth()
+	llvrw.Invoked = ReadValue
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return nil, llvrw.Err
+	}
+
+	return llvrw, nil
+}
+
+// WriteArrayElement implements the bsonrw.ArrayWriter interface.
+func (llvrw *ValueReaderWriter) WriteArrayElement() (bsonrw.ValueWriter, error) {
+	llvrw.checkdepth()
+	llvrw.Invoked = WriteArrayElement
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return nil, llvrw.Err
+	}
+
+	return llvrw, nil
+}
+
+// WriteArrayEnd implements the bsonrw.ArrayWriter interface.
+func (llvrw *ValueReaderWriter) WriteArrayEnd() error {
+	llvrw.checkdepth()
+	llvrw.Invoked = WriteArrayEnd
+	if llvrw.ErrAfter == llvrw.Invoked {
+		return llvrw.Err
+	}
+
+	return nil
+}

+ 389 - 0
src/go.mongodb.org/mongo-driver/bson/bsonrw/copier.go

@@ -0,0 +1,389 @@
+// Copyright (C) MongoDB, Inc. 2017-present.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+package bsonrw
+
+import (
+	"fmt"
+	"io"
+
+	"go.mongodb.org/mongo-driver/bson/bsontype"
+	"go.mongodb.org/mongo-driver/bson/primitive"
+	"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
+)
+
+// Copier is a type that allows copying between ValueReaders, ValueWriters, and
+// []byte values.
+type Copier struct{}
+
+// NewCopier creates a new copier with the given registry. If a nil registry is provided
+// a default registry is used.
+func NewCopier() Copier {
+	return Copier{}
+}
+
+// CopyDocument handles copying a document from src to dst.
+func CopyDocument(dst ValueWriter, src ValueReader) error {
+	return Copier{}.CopyDocument(dst, src)
+}
+
+// CopyDocument handles copying one document from the src to the dst.
+func (c Copier) CopyDocument(dst ValueWriter, src ValueReader) error {
+	dr, err := src.ReadDocument()
+	if err != nil {
+		return err
+	}
+
+	dw, err := dst.WriteDocument()
+	if err != nil {
+		return err
+	}
+
+	return c.copyDocumentCore(dw, dr)
+}
+
+// CopyDocumentFromBytes copies the values from a BSON document represented as a
+// []byte to a ValueWriter.
+func (c Copier) CopyDocumentFromBytes(dst ValueWriter, src []byte) error {
+	dw, err := dst.WriteDocument()
+	if err != nil {
+		return err
+	}
+
+	err = c.CopyBytesToDocumentWriter(dw, src)
+	if err != nil {
+		return err
+	}
+
+	return dw.WriteDocumentEnd()
+}
+
+// CopyBytesToDocumentWriter copies the values from a BSON document represented as a []byte to a
+// DocumentWriter.
+func (c Copier) CopyBytesToDocumentWriter(dst DocumentWriter, src []byte) error {
+	// TODO(skriptble): Create errors types here. Anything thats a tag should be a property.
+	length, rem, ok := bsoncore.ReadLength(src)
+	if !ok {
+		return fmt.Errorf("couldn't read length from src, not enough bytes. length=%d", len(src))
+	}
+	if len(src) < int(length) {
+		return fmt.Errorf("length read exceeds number of bytes available. length=%d bytes=%d", len(src), length)
+	}
+	rem = rem[:length-4]
+
+	var t bsontype.Type
+	var key string
+	var val bsoncore.Value
+	for {
+		t, rem, ok = bsoncore.ReadType(rem)
+		if !ok {
+			return io.EOF
+		}
+		if t == bsontype.Type(0) {
+			if len(rem) != 0 {
+				return fmt.Errorf("document end byte found before end of document. remaining bytes=%v", rem)
+			}
+			break
+		}
+
+		key, rem, ok = bsoncore.ReadKey(rem)
+		if !ok {
+			return fmt.Errorf("invalid key found. remaining bytes=%v", rem)
+		}
+		dvw, err := dst.WriteDocumentElement(key)
+		if err != nil {
+			return err
+		}
+		val, rem, ok = bsoncore.ReadValue(rem, t)
+		if !ok {
+			return fmt.Errorf("not enough bytes available to read type. bytes=%d type=%s", len(rem), t)
+		}
+		err = c.CopyValueFromBytes(dvw, t, val.Data)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// CopyDocumentToBytes copies an entire document from the ValueReader and
+// returns it as bytes.
+func (c Copier) CopyDocumentToBytes(src ValueReader) ([]byte, error) {
+	return c.AppendDocumentBytes(nil, src)
+}
+
+// AppendDocumentBytes functions the same as CopyDocumentToBytes, but will
+// append the result to dst.
+func (c Copier) AppendDocumentBytes(dst []byte, src ValueReader) ([]byte, error) {
+	if br, ok := src.(BytesReader); ok {
+		_, dst, err := br.ReadValueBytes(dst)
+		return dst, err
+	}
+
+	vw := vwPool.Get().(*valueWriter)
+	defer vwPool.Put(vw)
+
+	vw.reset(dst)
+
+	err := c.CopyDocument(vw, src)
+	dst = vw.buf
+	return dst, err
+}
+
+// CopyValueFromBytes will write the value represtend by t and src to dst.
+func (c Copier) CopyValueFromBytes(dst ValueWriter, t bsontype.Type, src []byte) error {
+	if wvb, ok := dst.(BytesWriter); ok {
+		return wvb.WriteValueBytes(t, src)
+	}
+
+	vr := vrPool.Get().(*valueReader)
+	defer vrPool.Put(vr)
+
+	vr.reset(src)
+	vr.pushElement(t)
+
+	return c.CopyValue(dst, vr)
+}
+
+// CopyValueToBytes copies a value from src and returns it as a bsontype.Type and a
+// []byte.
+func (c Copier) CopyValueToBytes(src ValueReader) (bsontype.Type, []byte, error) {
+	return c.AppendValueBytes(nil, src)
+}
+
+// AppendValueBytes functions the same as CopyValueToBytes, but will append the
+// result to dst.
+func (c Copier) AppendValueBytes(dst []byte, src ValueReader) (bsontype.Type, []byte, error) {
+	if br, ok := src.(BytesReader); ok {
+		return br.ReadValueBytes(dst)
+	}
+
+	vw := vwPool.Get().(*valueWriter)
+	defer vwPool.Put(vw)
+
+	start := len(dst)
+
+	vw.reset(dst)
+	vw.push(mElement)
+
+	err := c.CopyValue(vw, src)
+	if err != nil {
+		return 0, dst, err
+	}
+
+	return bsontype.Type(vw.buf[start]), vw.buf[start+2:], nil
+}
+
+// CopyValue will copy a single value from src to dst.
+func (c Copier) CopyValue(dst ValueWriter, src ValueReader) error {
+	var err error
+	switch src.Type() {
+	case bsontype.Double:
+		var f64 float64
+		f64, err = src.ReadDouble()
+		if err != nil {
+			break
+		}
+		err = dst.WriteDouble(f64)
+	case bsontype.String:
+		var str string
+		str, err = src.ReadString()
+		if err != nil {
+			return err
+		}
+		err = dst.WriteString(str)
+	case bsontype.EmbeddedDocument:
+		err = c.CopyDocument(dst, src)
+	case bsontype.Array:
+		err = c.copyArray(dst, src)
+	case bsontype.Binary:
+		var data []byte
+		var subtype byte
+		data, subtype, err = src.ReadBinary()
+		if err != nil {
+			break
+		}
+		err = dst.WriteBinaryWithSubtype(data, subtype)
+	case bsontype.Undefined:
+		err = src.ReadUndefined()
+		if err != nil {
+			break
+		}
+		err = dst.WriteUndefined()
+	case bsontype.ObjectID:
+		var oid primitive.ObjectID
+		oid, err = src.ReadObjectID()
+		if err != nil {
+			break
+		}
+		err = dst.WriteObjectID(oid)
+	case bsontype.Boolean:
+		var b bool
+		b, err = src.ReadBoolean()
+		if err != nil {
+			break
+		}
+		err = dst.WriteBoolean(b)
+	case bsontype.DateTime:
+		var dt int64
+		dt, err = src.ReadDateTime()
+		if err != nil {
+			break
+		}
+		err = dst.WriteDateTime(dt)
+	case bsontype.Null:
+		err = src.ReadNull()
+		if err != nil {
+			break
+		}
+		err = dst.WriteNull()
+	case bsontype.Regex:
+		var pattern, options string
+		pattern, options, err = src.ReadRegex()
+		if err != nil {
+			break
+		}
+		err = dst.WriteRegex(pattern, options)
+	case bsontype.DBPointer:
+		var ns string
+		var pointer primitive.ObjectID
+		ns, pointer, err = src.ReadDBPointer()
+		if err != nil {
+			break
+		}
+		err = dst.WriteDBPointer(ns, pointer)
+	case bsontype.JavaScript:
+		var js string
+		js, err = src.ReadJavascript()
+		if err != nil {
+			break
+		}
+		err = dst.WriteJavascript(js)
+	case bsontype.Symbol:
+		var symbol string
+		symbol, err = src.ReadSymbol()
+		if err != nil {
+			break
+		}
+		err = dst.WriteSymbol(symbol)
+	case bsontype.CodeWithScope:
+		var code string
+		var srcScope DocumentReader
+		code, srcScope, err = src.ReadCodeWithScope()
+		if err != nil {
+			break
+		}
+
+		var dstScope DocumentWriter
+		dstScope, err = dst.WriteCodeWithScope(code)
+		if err != nil {
+			break
+		}
+		err = c.copyDocumentCore(dstScope, srcScope)
+	case bsontype.Int32:
+		var i32 int32
+		i32, err = src.ReadInt32()
+		if err != nil {
+			break
+		}
+		err = dst.WriteInt32(i32)
+	case bsontype.Timestamp:
+		var t, i uint32
+		t, i, err = src.ReadTimestamp()
+		if err != nil {
+			break
+		}
+		err = dst.WriteTimestamp(t, i)
+	case bsontype.Int64:
+		var i64 int64
+		i64, err = src.ReadInt64()
+		if err != nil {
+			break
+		}
+		err = dst.WriteInt64(i64)
+	case bsontype.Decimal128:
+		var d128 primitive.Decimal128
+		d128, err = src.ReadDecimal128()
+		if err != nil {
+			break
+		}
+		err = dst.WriteDecimal128(d128)
+	case bsontype.MinKey:
+		err = src.ReadMinKey()
+		if err != nil {
+			break
+		}
+		err = dst.WriteMinKey()
+	case bsontype.MaxKey:
+		err = src.ReadMaxKey()
+		if err != nil {
+			break
+		}
+		err = dst.WriteMaxKey()
+	default:
+		err = fmt.Errorf("Cannot copy unknown BSON type %s", src.Type())
+	}
+
+	return err
+}
+
+func (c Copier) copyArray(dst ValueWriter, src ValueReader) error {
+	ar, err := src.ReadArray()
+	if err != nil {
+		return err
+	}
+
+	aw, err := dst.WriteArray()
+	if err != nil {
+		return err
+	}
+
+	for {
+		vr, err := ar.ReadValue()
+		if err == ErrEOA {
+			break
+		}
+		if err != nil {
+			return err
+		}
+
+		vw, err := aw.WriteArrayElement()
+		if err != nil {
+			return err
+		}
+
+		err = c.CopyValue(vw, vr)
+		if err != nil {
+			return err
+		}
+	}
+
+	return aw.WriteArrayEnd()
+}
+
+func (c Copier) copyDocumentCore(dw DocumentWriter, dr DocumentReader) error {
+	for {
+		key, vr, err := dr.ReadElement()
+		if err == ErrEOD {
+			break
+		}
+		if err != nil {
+			return err
+		}
+
+		vw, err := dw.WriteDocumentElement(key)
+		if err != nil {
+			return err
+		}
+
+		err = c.CopyValue(vw, vr)
+		if err != nil {
+			return err
+		}
+	}
+
+	return dw.WriteDocumentEnd()
+}

Неке датотеке нису приказане због велике количине промена