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 }