...

Source file src/github.com/go-kivik/kivik/v4/x/server/uuid.go

Documentation: github.com/go-kivik/kivik/v4/x/server

     1  // Licensed under the Apache License, Version 2.0 (the "License"); you may not
     2  // use this file except in compliance with the License. You may obtain a copy of
     3  // the License at
     4  //
     5  //  http://www.apache.org/licenses/LICENSE-2.0
     6  //
     7  // Unless required by applicable law or agreed to in writing, software
     8  // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
     9  // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    10  // License for the specific language governing permissions and limitations under
    11  // the License.
    12  
    13  //go:build !js
    14  
    15  package server
    16  
    17  import (
    18  	"context"
    19  	"crypto/rand"
    20  	"encoding/hex"
    21  	"fmt"
    22  	"net/http"
    23  	"strconv"
    24  	"sync/atomic"
    25  	"time"
    26  
    27  	"gitlab.com/flimzy/httpe"
    28  
    29  	internal "github.com/go-kivik/kivik/v4/int/errors"
    30  )
    31  
    32  type uuidAlgorithm string
    33  
    34  const (
    35  	// Supported UUID algorithms
    36  	uuidAlgorithmRandom     uuidAlgorithm = "random"
    37  	uuidAlgorithmSequential uuidAlgorithm = "sequential"
    38  	uuidAlgorithmUTCRandom  uuidAlgorithm = "utc_random"
    39  	uuidAlgorithmUTCID      uuidAlgorithm = "utc_id"
    40  	uuidAlgorithmDefault                  = uuidAlgorithmSequential
    41  
    42  	uuidDefaultMaxCount = 1000
    43  )
    44  
    45  var supportedUUIDAlgorithms = []uuidAlgorithm{uuidAlgorithmRandom, uuidAlgorithmSequential, uuidAlgorithmUTCRandom, uuidAlgorithmUTCID}
    46  
    47  // confUUIDAlgorithm returns the UUID algorithm used by the server.
    48  func (s *Server) confUUIDAlgorithm(ctx context.Context) uuidAlgorithm {
    49  	value, _ := s.config.Key(ctx, "uuids", "algorithm")
    50  	algo := uuidAlgorithm(value)
    51  	for _, a := range supportedUUIDAlgorithms {
    52  		if algo == a {
    53  			return algo
    54  		}
    55  	}
    56  
    57  	return uuidAlgorithmDefault
    58  }
    59  
    60  func (s *Server) confUUIDMaxCount(ctx context.Context) int {
    61  	var count int
    62  	_ = s.conf(ctx, "uuids", "max_count", &count)
    63  	if count < 1 {
    64  		return uuidDefaultMaxCount
    65  	}
    66  	return count
    67  }
    68  
    69  func (s *Server) uuids() httpe.HandlerWithError {
    70  	return httpe.HandlerWithErrorFunc(func(w http.ResponseWriter, r *http.Request) error {
    71  		var count int
    72  		if param := r.URL.Query().Get("count"); param != "" {
    73  			var err error
    74  			count, err = strconv.Atoi(param)
    75  			if err != nil {
    76  				return &internal.Error{Status: http.StatusBadRequest, Message: "count must be a positive integer"}
    77  			}
    78  		}
    79  		if count == 0 {
    80  			count = 1
    81  		}
    82  		maxCount := s.confUUIDMaxCount(r.Context())
    83  		if count > maxCount {
    84  			return &internal.Error{Status: http.StatusBadRequest, Message: fmt.Sprintf("count must not exceed %d", maxCount)}
    85  		}
    86  		var uuids []string
    87  		var err error
    88  		switch algo := s.confUUIDAlgorithm(r.Context()); algo {
    89  		case uuidAlgorithmRandom:
    90  			uuids, err = s.uuidsRandom(count)
    91  		case uuidAlgorithmSequential:
    92  			uuids, err = s.uuidsSequential(count)
    93  		case uuidAlgorithmUTCRandom:
    94  			uuids, err = s.uuidsUTCRandom(count)
    95  		case uuidAlgorithmUTCID:
    96  			uuids = s.uuidsUTCID(r.Context(), count)
    97  		}
    98  		if err != nil {
    99  			return err
   100  		}
   101  		return serveJSON(w, http.StatusOK, map[string][]string{"uuids": uuids})
   102  	})
   103  }
   104  
   105  func (s *Server) uuidsRandom(count int) ([]string, error) {
   106  	const randomUUIDLength = 32
   107  	uuids := make([]string, count)
   108  	for i := range uuids {
   109  		uuid, err := randomHexString(randomUUIDLength)
   110  		if err != nil {
   111  			return nil, err
   112  		}
   113  		uuids[i] = uuid
   114  	}
   115  	return uuids, nil
   116  }
   117  
   118  func randomHexString(length int) (string, error) {
   119  	const charsPerByte = 2
   120  	var hexByteLength int
   121  	if length%4 == 0 {
   122  		hexByteLength = length / charsPerByte
   123  	} else {
   124  		hexByteLength = length/charsPerByte + 1
   125  	}
   126  	randomBytes := make([]byte, hexByteLength)
   127  	if _, err := rand.Read(randomBytes); err != nil {
   128  		return "", err
   129  	}
   130  	return hex.EncodeToString(randomBytes)[:length], nil
   131  }
   132  
   133  // randomIncrement returns a random number between 1 and 256.
   134  func randomIncrement() (int32, error) {
   135  	buf := make([]byte, 1)
   136  	if _, err := rand.Read(buf); err != nil {
   137  		return 0, err
   138  	}
   139  	return int32(buf[0]) + 1, nil
   140  }
   141  
   142  func (s *Server) uuidsSequential(count int) ([]string, error) {
   143  	prefix, err := s.uuidSequentialPrefix()
   144  	if err != nil {
   145  		return nil, err
   146  	}
   147  
   148  	uuids := make([]string, count)
   149  	for i := range uuids {
   150  		incr, err := randomIncrement()
   151  		if err != nil {
   152  			return nil, err
   153  		}
   154  		monotonic := atomic.AddInt32(&s.sequentialUUIDMonotonicID, incr)
   155  		uuids[i] = prefix + fmt.Sprintf("%06x", monotonic)
   156  	}
   157  	return uuids, nil
   158  }
   159  
   160  func (s *Server) uuidSequentialPrefix() (string, error) {
   161  	s.sequentialMU.Lock()
   162  	defer s.sequentialMU.Unlock()
   163  	if s.sequentialUUIDPrefix != "" {
   164  		return s.sequentialUUIDPrefix, nil
   165  	}
   166  
   167  	const sequentialUUIDPrefixLength = 26
   168  	var err error
   169  	s.sequentialUUIDPrefix, err = randomHexString(sequentialUUIDPrefixLength)
   170  	return s.sequentialUUIDPrefix, err
   171  }
   172  
   173  func (s *Server) uuidsUTCRandom(count int) ([]string, error) {
   174  	const randomChars = 18
   175  	uuids := make([]string, count)
   176  	for i := range uuids {
   177  		r, err := randomHexString(randomChars)
   178  		if err != nil {
   179  			return nil, err
   180  		}
   181  		uuids[i] = fmt.Sprintf("%014x%s", time.Now().UnixMicro(), r)
   182  	}
   183  	return uuids, nil
   184  }
   185  
   186  func (s *Server) uuidsUTCID(ctx context.Context, count int) []string {
   187  	suffix, _ := s.config.Key(ctx, "uuids", "utc_id_suffix")
   188  	uuids := make([]string, count)
   189  	for i := range uuids {
   190  		uuids[i] = fmt.Sprintf("%014x%s", time.Now().UnixMicro(), suffix)
   191  	}
   192  	return uuids
   193  }
   194  

View as plain text