summaryrefslogtreecommitdiff
path: root/vendor/go.mongodb.org/mongo-driver/bson/bsonrw/extjson_writer.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/go.mongodb.org/mongo-driver/bson/bsonrw/extjson_writer.go')
-rw-r--r--vendor/go.mongodb.org/mongo-driver/bson/bsonrw/extjson_writer.go732
1 files changed, 732 insertions, 0 deletions
diff --git a/vendor/go.mongodb.org/mongo-driver/bson/bsonrw/extjson_writer.go b/vendor/go.mongodb.org/mongo-driver/bson/bsonrw/extjson_writer.go
new file mode 100644
index 000000000..99ed524b7
--- /dev/null
+++ b/vendor/go.mongodb.org/mongo-driver/bson/bsonrw/extjson_writer.go
@@ -0,0 +1,732 @@
+// 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 (
+ "bytes"
+ "encoding/base64"
+ "fmt"
+ "io"
+ "math"
+ "sort"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+ "unicode/utf8"
+
+ "go.mongodb.org/mongo-driver/bson/primitive"
+)
+
+// ExtJSONValueWriterPool is a pool for ExtJSON ValueWriters.
+type ExtJSONValueWriterPool struct {
+ pool sync.Pool
+}
+
+// NewExtJSONValueWriterPool creates a new pool for ValueWriter instances that write to ExtJSON.
+func NewExtJSONValueWriterPool() *ExtJSONValueWriterPool {
+ return &ExtJSONValueWriterPool{
+ pool: sync.Pool{
+ New: func() interface{} {
+ return new(extJSONValueWriter)
+ },
+ },
+ }
+}
+
+// Get retrieves a ExtJSON ValueWriter from the pool and resets it to use w as the destination.
+func (bvwp *ExtJSONValueWriterPool) Get(w io.Writer, canonical, escapeHTML bool) ValueWriter {
+ vw := bvwp.pool.Get().(*extJSONValueWriter)
+ if writer, ok := w.(*SliceWriter); ok {
+ vw.reset(*writer, canonical, escapeHTML)
+ vw.w = writer
+ return vw
+ }
+ vw.buf = vw.buf[:0]
+ vw.w = w
+ return vw
+}
+
+// Put inserts a ValueWriter into the pool. If the ValueWriter is not a ExtJSON ValueWriter, nothing
+// happens and ok will be false.
+func (bvwp *ExtJSONValueWriterPool) Put(vw ValueWriter) (ok bool) {
+ bvw, ok := vw.(*extJSONValueWriter)
+ if !ok {
+ return false
+ }
+
+ if _, ok := bvw.w.(*SliceWriter); ok {
+ bvw.buf = nil
+ }
+ bvw.w = nil
+
+ bvwp.pool.Put(bvw)
+ return true
+}
+
+type ejvwState struct {
+ mode mode
+}
+
+type extJSONValueWriter struct {
+ w io.Writer
+ buf []byte
+
+ stack []ejvwState
+ frame int64
+ canonical bool
+ escapeHTML bool
+}
+
+// NewExtJSONValueWriter creates a ValueWriter that writes Extended JSON to w.
+func NewExtJSONValueWriter(w io.Writer, canonical, escapeHTML bool) (ValueWriter, error) {
+ if w == nil {
+ return nil, errNilWriter
+ }
+
+ return newExtJSONWriter(w, canonical, escapeHTML), nil
+}
+
+func newExtJSONWriter(w io.Writer, canonical, escapeHTML bool) *extJSONValueWriter {
+ stack := make([]ejvwState, 1, 5)
+ stack[0] = ejvwState{mode: mTopLevel}
+
+ return &extJSONValueWriter{
+ w: w,
+ buf: []byte{},
+ stack: stack,
+ canonical: canonical,
+ escapeHTML: escapeHTML,
+ }
+}
+
+func newExtJSONWriterFromSlice(buf []byte, canonical, escapeHTML bool) *extJSONValueWriter {
+ stack := make([]ejvwState, 1, 5)
+ stack[0] = ejvwState{mode: mTopLevel}
+
+ return &extJSONValueWriter{
+ buf: buf,
+ stack: stack,
+ canonical: canonical,
+ escapeHTML: escapeHTML,
+ }
+}
+
+func (ejvw *extJSONValueWriter) reset(buf []byte, canonical, escapeHTML bool) {
+ if ejvw.stack == nil {
+ ejvw.stack = make([]ejvwState, 1, 5)
+ }
+
+ ejvw.stack = ejvw.stack[:1]
+ ejvw.stack[0] = ejvwState{mode: mTopLevel}
+ ejvw.canonical = canonical
+ ejvw.escapeHTML = escapeHTML
+ ejvw.frame = 0
+ ejvw.buf = buf
+ ejvw.w = nil
+}
+
+func (ejvw *extJSONValueWriter) advanceFrame() {
+ if ejvw.frame+1 >= int64(len(ejvw.stack)) { // We need to grow the stack
+ length := len(ejvw.stack)
+ if length+1 >= cap(ejvw.stack) {
+ // double it
+ buf := make([]ejvwState, 2*cap(ejvw.stack)+1)
+ copy(buf, ejvw.stack)
+ ejvw.stack = buf
+ }
+ ejvw.stack = ejvw.stack[:length+1]
+ }
+ ejvw.frame++
+}
+
+func (ejvw *extJSONValueWriter) push(m mode) {
+ ejvw.advanceFrame()
+
+ ejvw.stack[ejvw.frame].mode = m
+}
+
+func (ejvw *extJSONValueWriter) pop() {
+ switch ejvw.stack[ejvw.frame].mode {
+ case mElement, mValue:
+ ejvw.frame--
+ case mDocument, mArray, mCodeWithScope:
+ ejvw.frame -= 2 // we pop twice to jump over the mElement: mDocument -> mElement -> mDocument/mTopLevel/etc...
+ }
+}
+
+func (ejvw *extJSONValueWriter) invalidTransitionErr(destination mode, name string, modes []mode) error {
+ te := TransitionError{
+ name: name,
+ current: ejvw.stack[ejvw.frame].mode,
+ destination: destination,
+ modes: modes,
+ action: "write",
+ }
+ if ejvw.frame != 0 {
+ te.parent = ejvw.stack[ejvw.frame-1].mode
+ }
+ return te
+}
+
+func (ejvw *extJSONValueWriter) ensureElementValue(destination mode, callerName string, addmodes ...mode) error {
+ switch ejvw.stack[ejvw.frame].mode {
+ case mElement, mValue:
+ default:
+ modes := []mode{mElement, mValue}
+ if addmodes != nil {
+ modes = append(modes, addmodes...)
+ }
+ return ejvw.invalidTransitionErr(destination, callerName, modes)
+ }
+
+ return nil
+}
+
+func (ejvw *extJSONValueWriter) writeExtendedSingleValue(key string, value string, quotes bool) {
+ var s string
+ if quotes {
+ s = fmt.Sprintf(`{"$%s":"%s"}`, key, value)
+ } else {
+ s = fmt.Sprintf(`{"$%s":%s}`, key, value)
+ }
+
+ ejvw.buf = append(ejvw.buf, []byte(s)...)
+}
+
+func (ejvw *extJSONValueWriter) WriteArray() (ArrayWriter, error) {
+ if err := ejvw.ensureElementValue(mArray, "WriteArray"); err != nil {
+ return nil, err
+ }
+
+ ejvw.buf = append(ejvw.buf, '[')
+
+ ejvw.push(mArray)
+ return ejvw, nil
+}
+
+func (ejvw *extJSONValueWriter) WriteBinary(b []byte) error {
+ return ejvw.WriteBinaryWithSubtype(b, 0x00)
+}
+
+func (ejvw *extJSONValueWriter) WriteBinaryWithSubtype(b []byte, btype byte) error {
+ if err := ejvw.ensureElementValue(mode(0), "WriteBinaryWithSubtype"); err != nil {
+ return err
+ }
+
+ var buf bytes.Buffer
+ buf.WriteString(`{"$binary":{"base64":"`)
+ buf.WriteString(base64.StdEncoding.EncodeToString(b))
+ buf.WriteString(fmt.Sprintf(`","subType":"%02x"}},`, btype))
+
+ ejvw.buf = append(ejvw.buf, buf.Bytes()...)
+
+ ejvw.pop()
+ return nil
+}
+
+func (ejvw *extJSONValueWriter) WriteBoolean(b bool) error {
+ if err := ejvw.ensureElementValue(mode(0), "WriteBoolean"); err != nil {
+ return err
+ }
+
+ ejvw.buf = append(ejvw.buf, []byte(strconv.FormatBool(b))...)
+ ejvw.buf = append(ejvw.buf, ',')
+
+ ejvw.pop()
+ return nil
+}
+
+func (ejvw *extJSONValueWriter) WriteCodeWithScope(code string) (DocumentWriter, error) {
+ if err := ejvw.ensureElementValue(mCodeWithScope, "WriteCodeWithScope"); err != nil {
+ return nil, err
+ }
+
+ var buf bytes.Buffer
+ buf.WriteString(`{"$code":`)
+ writeStringWithEscapes(code, &buf, ejvw.escapeHTML)
+ buf.WriteString(`,"$scope":{`)
+
+ ejvw.buf = append(ejvw.buf, buf.Bytes()...)
+
+ ejvw.push(mCodeWithScope)
+ return ejvw, nil
+}
+
+func (ejvw *extJSONValueWriter) WriteDBPointer(ns string, oid primitive.ObjectID) error {
+ if err := ejvw.ensureElementValue(mode(0), "WriteDBPointer"); err != nil {
+ return err
+ }
+
+ var buf bytes.Buffer
+ buf.WriteString(`{"$dbPointer":{"$ref":"`)
+ buf.WriteString(ns)
+ buf.WriteString(`","$id":{"$oid":"`)
+ buf.WriteString(oid.Hex())
+ buf.WriteString(`"}}},`)
+
+ ejvw.buf = append(ejvw.buf, buf.Bytes()...)
+
+ ejvw.pop()
+ return nil
+}
+
+func (ejvw *extJSONValueWriter) WriteDateTime(dt int64) error {
+ if err := ejvw.ensureElementValue(mode(0), "WriteDateTime"); err != nil {
+ return err
+ }
+
+ t := time.Unix(dt/1e3, dt%1e3*1e6).UTC()
+
+ if ejvw.canonical || t.Year() < 1970 || t.Year() > 9999 {
+ s := fmt.Sprintf(`{"$numberLong":"%d"}`, dt)
+ ejvw.writeExtendedSingleValue("date", s, false)
+ } else {
+ ejvw.writeExtendedSingleValue("date", t.Format(rfc3339Milli), true)
+ }
+
+ ejvw.buf = append(ejvw.buf, ',')
+
+ ejvw.pop()
+ return nil
+}
+
+func (ejvw *extJSONValueWriter) WriteDecimal128(d primitive.Decimal128) error {
+ if err := ejvw.ensureElementValue(mode(0), "WriteDecimal128"); err != nil {
+ return err
+ }
+
+ ejvw.writeExtendedSingleValue("numberDecimal", d.String(), true)
+ ejvw.buf = append(ejvw.buf, ',')
+
+ ejvw.pop()
+ return nil
+}
+
+func (ejvw *extJSONValueWriter) WriteDocument() (DocumentWriter, error) {
+ if ejvw.stack[ejvw.frame].mode == mTopLevel {
+ ejvw.buf = append(ejvw.buf, '{')
+ return ejvw, nil
+ }
+
+ if err := ejvw.ensureElementValue(mDocument, "WriteDocument", mTopLevel); err != nil {
+ return nil, err
+ }
+
+ ejvw.buf = append(ejvw.buf, '{')
+ ejvw.push(mDocument)
+ return ejvw, nil
+}
+
+func (ejvw *extJSONValueWriter) WriteDouble(f float64) error {
+ if err := ejvw.ensureElementValue(mode(0), "WriteDouble"); err != nil {
+ return err
+ }
+
+ s := formatDouble(f)
+
+ if ejvw.canonical {
+ ejvw.writeExtendedSingleValue("numberDouble", s, true)
+ } else {
+ switch s {
+ case "Infinity":
+ fallthrough
+ case "-Infinity":
+ fallthrough
+ case "NaN":
+ s = fmt.Sprintf(`{"$numberDouble":"%s"}`, s)
+ }
+ ejvw.buf = append(ejvw.buf, []byte(s)...)
+ }
+
+ ejvw.buf = append(ejvw.buf, ',')
+
+ ejvw.pop()
+ return nil
+}
+
+func (ejvw *extJSONValueWriter) WriteInt32(i int32) error {
+ if err := ejvw.ensureElementValue(mode(0), "WriteInt32"); err != nil {
+ return err
+ }
+
+ s := strconv.FormatInt(int64(i), 10)
+
+ if ejvw.canonical {
+ ejvw.writeExtendedSingleValue("numberInt", s, true)
+ } else {
+ ejvw.buf = append(ejvw.buf, []byte(s)...)
+ }
+
+ ejvw.buf = append(ejvw.buf, ',')
+
+ ejvw.pop()
+ return nil
+}
+
+func (ejvw *extJSONValueWriter) WriteInt64(i int64) error {
+ if err := ejvw.ensureElementValue(mode(0), "WriteInt64"); err != nil {
+ return err
+ }
+
+ s := strconv.FormatInt(i, 10)
+
+ if ejvw.canonical {
+ ejvw.writeExtendedSingleValue("numberLong", s, true)
+ } else {
+ ejvw.buf = append(ejvw.buf, []byte(s)...)
+ }
+
+ ejvw.buf = append(ejvw.buf, ',')
+
+ ejvw.pop()
+ return nil
+}
+
+func (ejvw *extJSONValueWriter) WriteJavascript(code string) error {
+ if err := ejvw.ensureElementValue(mode(0), "WriteJavascript"); err != nil {
+ return err
+ }
+
+ var buf bytes.Buffer
+ writeStringWithEscapes(code, &buf, ejvw.escapeHTML)
+
+ ejvw.writeExtendedSingleValue("code", buf.String(), false)
+ ejvw.buf = append(ejvw.buf, ',')
+
+ ejvw.pop()
+ return nil
+}
+
+func (ejvw *extJSONValueWriter) WriteMaxKey() error {
+ if err := ejvw.ensureElementValue(mode(0), "WriteMaxKey"); err != nil {
+ return err
+ }
+
+ ejvw.writeExtendedSingleValue("maxKey", "1", false)
+ ejvw.buf = append(ejvw.buf, ',')
+
+ ejvw.pop()
+ return nil
+}
+
+func (ejvw *extJSONValueWriter) WriteMinKey() error {
+ if err := ejvw.ensureElementValue(mode(0), "WriteMinKey"); err != nil {
+ return err
+ }
+
+ ejvw.writeExtendedSingleValue("minKey", "1", false)
+ ejvw.buf = append(ejvw.buf, ',')
+
+ ejvw.pop()
+ return nil
+}
+
+func (ejvw *extJSONValueWriter) WriteNull() error {
+ if err := ejvw.ensureElementValue(mode(0), "WriteNull"); err != nil {
+ return err
+ }
+
+ ejvw.buf = append(ejvw.buf, []byte("null")...)
+ ejvw.buf = append(ejvw.buf, ',')
+
+ ejvw.pop()
+ return nil
+}
+
+func (ejvw *extJSONValueWriter) WriteObjectID(oid primitive.ObjectID) error {
+ if err := ejvw.ensureElementValue(mode(0), "WriteObjectID"); err != nil {
+ return err
+ }
+
+ ejvw.writeExtendedSingleValue("oid", oid.Hex(), true)
+ ejvw.buf = append(ejvw.buf, ',')
+
+ ejvw.pop()
+ return nil
+}
+
+func (ejvw *extJSONValueWriter) WriteRegex(pattern string, options string) error {
+ if err := ejvw.ensureElementValue(mode(0), "WriteRegex"); err != nil {
+ return err
+ }
+
+ var buf bytes.Buffer
+ buf.WriteString(`{"$regularExpression":{"pattern":`)
+ writeStringWithEscapes(pattern, &buf, ejvw.escapeHTML)
+ buf.WriteString(`,"options":"`)
+ buf.WriteString(sortStringAlphebeticAscending(options))
+ buf.WriteString(`"}},`)
+
+ ejvw.buf = append(ejvw.buf, buf.Bytes()...)
+
+ ejvw.pop()
+ return nil
+}
+
+func (ejvw *extJSONValueWriter) WriteString(s string) error {
+ if err := ejvw.ensureElementValue(mode(0), "WriteString"); err != nil {
+ return err
+ }
+
+ var buf bytes.Buffer
+ writeStringWithEscapes(s, &buf, ejvw.escapeHTML)
+
+ ejvw.buf = append(ejvw.buf, buf.Bytes()...)
+ ejvw.buf = append(ejvw.buf, ',')
+
+ ejvw.pop()
+ return nil
+}
+
+func (ejvw *extJSONValueWriter) WriteSymbol(symbol string) error {
+ if err := ejvw.ensureElementValue(mode(0), "WriteSymbol"); err != nil {
+ return err
+ }
+
+ var buf bytes.Buffer
+ writeStringWithEscapes(symbol, &buf, ejvw.escapeHTML)
+
+ ejvw.writeExtendedSingleValue("symbol", buf.String(), false)
+ ejvw.buf = append(ejvw.buf, ',')
+
+ ejvw.pop()
+ return nil
+}
+
+func (ejvw *extJSONValueWriter) WriteTimestamp(t uint32, i uint32) error {
+ if err := ejvw.ensureElementValue(mode(0), "WriteTimestamp"); err != nil {
+ return err
+ }
+
+ var buf bytes.Buffer
+ buf.WriteString(`{"$timestamp":{"t":`)
+ buf.WriteString(strconv.FormatUint(uint64(t), 10))
+ buf.WriteString(`,"i":`)
+ buf.WriteString(strconv.FormatUint(uint64(i), 10))
+ buf.WriteString(`}},`)
+
+ ejvw.buf = append(ejvw.buf, buf.Bytes()...)
+
+ ejvw.pop()
+ return nil
+}
+
+func (ejvw *extJSONValueWriter) WriteUndefined() error {
+ if err := ejvw.ensureElementValue(mode(0), "WriteUndefined"); err != nil {
+ return err
+ }
+
+ ejvw.writeExtendedSingleValue("undefined", "true", false)
+ ejvw.buf = append(ejvw.buf, ',')
+
+ ejvw.pop()
+ return nil
+}
+
+func (ejvw *extJSONValueWriter) WriteDocumentElement(key string) (ValueWriter, error) {
+ switch ejvw.stack[ejvw.frame].mode {
+ case mDocument, mTopLevel, mCodeWithScope:
+ var buf bytes.Buffer
+ writeStringWithEscapes(key, &buf, ejvw.escapeHTML)
+
+ ejvw.buf = append(ejvw.buf, []byte(fmt.Sprintf(`%s:`, buf.String()))...)
+ ejvw.push(mElement)
+ default:
+ return nil, ejvw.invalidTransitionErr(mElement, "WriteDocumentElement", []mode{mDocument, mTopLevel, mCodeWithScope})
+ }
+
+ return ejvw, nil
+}
+
+func (ejvw *extJSONValueWriter) WriteDocumentEnd() error {
+ switch ejvw.stack[ejvw.frame].mode {
+ case mDocument, mTopLevel, mCodeWithScope:
+ default:
+ return fmt.Errorf("incorrect mode to end document: %s", ejvw.stack[ejvw.frame].mode)
+ }
+
+ // close the document
+ if ejvw.buf[len(ejvw.buf)-1] == ',' {
+ ejvw.buf[len(ejvw.buf)-1] = '}'
+ } else {
+ ejvw.buf = append(ejvw.buf, '}')
+ }
+
+ switch ejvw.stack[ejvw.frame].mode {
+ case mCodeWithScope:
+ ejvw.buf = append(ejvw.buf, '}')
+ fallthrough
+ case mDocument:
+ ejvw.buf = append(ejvw.buf, ',')
+ case mTopLevel:
+ if ejvw.w != nil {
+ if _, err := ejvw.w.Write(ejvw.buf); err != nil {
+ return err
+ }
+ ejvw.buf = ejvw.buf[:0]
+ }
+ }
+
+ ejvw.pop()
+ return nil
+}
+
+func (ejvw *extJSONValueWriter) WriteArrayElement() (ValueWriter, error) {
+ switch ejvw.stack[ejvw.frame].mode {
+ case mArray:
+ ejvw.push(mValue)
+ default:
+ return nil, ejvw.invalidTransitionErr(mValue, "WriteArrayElement", []mode{mArray})
+ }
+
+ return ejvw, nil
+}
+
+func (ejvw *extJSONValueWriter) WriteArrayEnd() error {
+ switch ejvw.stack[ejvw.frame].mode {
+ case mArray:
+ // close the array
+ if ejvw.buf[len(ejvw.buf)-1] == ',' {
+ ejvw.buf[len(ejvw.buf)-1] = ']'
+ } else {
+ ejvw.buf = append(ejvw.buf, ']')
+ }
+
+ ejvw.buf = append(ejvw.buf, ',')
+
+ ejvw.pop()
+ default:
+ return fmt.Errorf("incorrect mode to end array: %s", ejvw.stack[ejvw.frame].mode)
+ }
+
+ return nil
+}
+
+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
+}
+
+var hexChars = "0123456789abcdef"
+
+func writeStringWithEscapes(s string, buf *bytes.Buffer, escapeHTML bool) {
+ buf.WriteByte('"')
+ start := 0
+ for i := 0; i < len(s); {
+ if b := s[i]; b < utf8.RuneSelf {
+ if htmlSafeSet[b] || (!escapeHTML && safeSet[b]) {
+ i++
+ continue
+ }
+ if start < i {
+ buf.WriteString(s[start:i])
+ }
+ switch b {
+ case '\\', '"':
+ buf.WriteByte('\\')
+ buf.WriteByte(b)
+ case '\n':
+ buf.WriteByte('\\')
+ buf.WriteByte('n')
+ case '\r':
+ buf.WriteByte('\\')
+ buf.WriteByte('r')
+ case '\t':
+ buf.WriteByte('\\')
+ buf.WriteByte('t')
+ case '\b':
+ buf.WriteByte('\\')
+ buf.WriteByte('b')
+ case '\f':
+ buf.WriteByte('\\')
+ buf.WriteByte('f')
+ default:
+ // This encodes bytes < 0x20 except for \t, \n and \r.
+ // If escapeHTML is set, it also escapes <, >, and &
+ // because they can lead to security holes when
+ // user-controlled strings are rendered into JSON
+ // and served to some browsers.
+ buf.WriteString(`\u00`)
+ buf.WriteByte(hexChars[b>>4])
+ buf.WriteByte(hexChars[b&0xF])
+ }
+ i++
+ start = i
+ continue
+ }
+ c, size := utf8.DecodeRuneInString(s[i:])
+ if c == utf8.RuneError && size == 1 {
+ if start < i {
+ buf.WriteString(s[start:i])
+ }
+ buf.WriteString(`\ufffd`)
+ i += size
+ start = i
+ continue
+ }
+ // U+2028 is LINE SEPARATOR.
+ // U+2029 is PARAGRAPH SEPARATOR.
+ // They are both technically valid characters in JSON strings,
+ // but don't work in JSONP, which has to be evaluated as JavaScript,
+ // and can lead to security holes there. It is valid JSON to
+ // escape them, so we do so unconditionally.
+ // See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion.
+ if c == '\u2028' || c == '\u2029' {
+ if start < i {
+ buf.WriteString(s[start:i])
+ }
+ buf.WriteString(`\u202`)
+ buf.WriteByte(hexChars[c&0xF])
+ i += size
+ start = i
+ continue
+ }
+ i += size
+ }
+ if start < len(s) {
+ buf.WriteString(s[start:])
+ }
+ buf.WriteByte('"')
+}
+
+type sortableString []rune
+
+func (ss sortableString) Len() int {
+ return len(ss)
+}
+
+func (ss sortableString) Less(i, j int) bool {
+ return ss[i] < ss[j]
+}
+
+func (ss sortableString) Swap(i, j int) {
+ oldI := ss[i]
+ ss[i] = ss[j]
+ ss[j] = oldI
+}
+
+func sortStringAlphebeticAscending(s string) string {
+ ss := sortableString([]rune(s))
+ sort.Sort(ss)
+ return string([]rune(ss))
+}