github.com/blend/go-sdk@v1.20220411.3/uuid/parse.go (about)

     1  /*
     2  
     3  Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved
     4  Use of this source code is governed by a MIT license that can be found in the LICENSE file.
     5  
     6  */
     7  
     8  package uuid
     9  
    10  import "github.com/blend/go-sdk/ex"
    11  
    12  // Error Classes
    13  const (
    14  	ErrParseInvalidUUIDInput = ex.Class("parse uuid: existing uuid is invalid")
    15  	// ErrParseEmpty            = ex.Class("parse uuid: input is empty")
    16  	ErrParseInvalidLength    = ex.Class("parse uuid: input is an invalid length")
    17  	ErrParseIllegalCharacter = ex.Class("parse uuid: illegal character")
    18  )
    19  
    20  // MustParse parses a uuid and will panic if there is an error.
    21  func MustParse(corpus string) UUID {
    22  	var uuid UUID = make([]byte, 16)
    23  	if err := ParseExisting(&uuid, corpus); err != nil {
    24  		panic(err)
    25  	}
    26  	return uuid
    27  }
    28  
    29  // Parse parses a uuidv4 from a given string.
    30  // valid forms are:
    31  // - {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
    32  // - xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
    33  // - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    34  func Parse(corpus string) (UUID, error) {
    35  	var uuid UUID = make([]byte, 16)
    36  	if err := ParseExisting(&uuid, corpus); err != nil {
    37  		return nil, err
    38  	}
    39  	return uuid, nil
    40  }
    41  
    42  // ParseExisting parses into an existing UUID.
    43  func ParseExisting(uuid *UUID, corpus string) error {
    44  	if len(corpus) == 0 {
    45  		return nil // ex.New(ErrParseEmpty)
    46  	}
    47  	if len(corpus)%2 == 1 {
    48  		return ex.New(ErrParseInvalidLength)
    49  	}
    50  	if len(*uuid) != 16 {
    51  		return ex.New(ErrParseInvalidUUIDInput)
    52  	}
    53  	var data = []byte(corpus)
    54  	var c byte
    55  	hex := [2]byte{}
    56  	var hexChar byte
    57  	var isHexChar bool
    58  	var hexIndex, uuidIndex, di int
    59  
    60  	for i := 0; i < len(data); i++ {
    61  		c = data[i]
    62  		if c == '{' && i == 0 {
    63  			continue
    64  		}
    65  		if c == '{' {
    66  			return ex.New(ErrParseIllegalCharacter, ex.OptMessagef("at %d: %v", i, string(c)))
    67  		}
    68  		if c == '}' && i != len(data)-1 {
    69  			return ex.New(ErrParseIllegalCharacter, ex.OptMessagef("at %d: %v", i, string(c)))
    70  		}
    71  		if c == '}' {
    72  			continue
    73  		}
    74  
    75  		if c == '-' && !(di == 8 || di == 12 || di == 16 || di == 20) {
    76  			return ex.New(ErrParseIllegalCharacter, ex.OptMessagef("at %d: %v", i, string(c)))
    77  		}
    78  		if c == '-' {
    79  			continue
    80  		}
    81  
    82  		hexChar, isHexChar = fromHexChar(c)
    83  		if !isHexChar {
    84  			return ex.New(ErrParseIllegalCharacter, ex.OptMessagef("at %d: %v", i, string(c)))
    85  		}
    86  
    87  		hex[hexIndex] = hexChar
    88  		if hexIndex == 1 {
    89  			(*uuid)[uuidIndex] = hex[0]<<4 | hex[1]
    90  			uuidIndex++
    91  			hexIndex = 0
    92  		} else {
    93  			hexIndex++
    94  		}
    95  		di++
    96  	}
    97  	if uuidIndex != 16 {
    98  		return ex.New(ErrParseInvalidLength)
    99  	}
   100  	return nil
   101  }
   102  
   103  func fromHexChar(c byte) (byte, bool) {
   104  	switch {
   105  	case '0' <= c && c <= '9':
   106  		return c - '0', true
   107  	case 'a' <= c && c <= 'f':
   108  		return c - 'a' + 10, true
   109  	case 'A' <= c && c <= 'F':
   110  		return c - 'A' + 10, true
   111  	}
   112  
   113  	return 0, false
   114  }