Skip to content

Commit

Permalink
Add uuid to ulid converter (#52)
Browse files Browse the repository at this point in the history
  • Loading branch information
ahelmy committed Feb 1, 2024
1 parent 696b5a5 commit 4323ef6
Show file tree
Hide file tree
Showing 11 changed files with 321 additions and 26 deletions.
22 changes: 21 additions & 1 deletion api/uuid.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,27 @@ const (

func uuidAPI(app *fiber.App) {
app.Get(APIPrefix+UUIDPath, func(c fiber.Ctx) error {
uuidResponse := Response{Success: true, Data: map[string]interface{}{"uuid": internal.GenerateGUID()}}
uuidResponse := Response{Success: true, Data: map[string]interface{}{"uuid": internal.GenerateUUID()}}
return c.JSON(uuidResponse)
})

app.Get(APIPrefix+UUIDPath+"/convert", func(c fiber.Ctx) error {
uuid := c.Query("uuid", "")
if uuid == "" {
return c.JSON(Response{Success: false, Message: "uuid is required"})
}

convertTo := c.Query("to", "ulid")
var convertedString string
if convertTo == "ulid" {
ulid, err := internal.UUIDtoULID(uuid)
if err != nil {
return c.JSON(Response{Success: false, Message: err.Error()})
}
convertedString = ulid
} else {
return c.JSON(Response{Success: false, Message: "invalid conversion type"})
}
return c.JSON(Response{Success: true, Data: map[string]interface{}{"result": convertedString}})
})
}
87 changes: 87 additions & 0 deletions api/uuid_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package api

import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
Expand All @@ -25,4 +28,88 @@ func TestUUIDAPI(t *testing.T) {
assert.True(t, ulidResponse.Success)
assert.NotNil(t, ulidResponse.Data.(map[string]interface{})["uuid"])
})

t.Run("Convert UUID to ULID", func(t *testing.T) {
uuid := "860d3040-1e23-416f-a1fd-f4ccc773833d"
expectedULID := "461MR407H385QT3ZFMSK3Q70SX"

req, _ := http.NewRequest("GET", fmt.Sprintf("/api/uuid/convert?uuid=%s&to=ulid", uuid), nil)
resp, _ := app.Test(req)

if resp.StatusCode != http.StatusOK {
t.Errorf("Expected status code %d, but got %d", http.StatusOK, resp.StatusCode)
}

body, _ := ioutil.ReadAll(resp.Body)
var result Response
json.Unmarshal(body, &result)

if !result.Success {
t.Errorf("Expected success to be true, but got false")
}

if _, ok := result.Data.(map[string]interface{})["result"]; !ok {
t.Errorf("Expected 'result' field in response data, but not found")
}

ulid, ok := result.Data.(map[string]interface{})["result"].(string)
if !ok {
t.Errorf("Expected 'result' field to be a string, but got %T", result.Data.(map[string]interface{})["result"])
}

if ulid != expectedULID {
t.Errorf("Expected ULID to be %s, but got %s", expectedULID, ulid)
}
})

t.Run("Invalid UUID", func(t *testing.T) {
req, _ := http.NewRequest("GET", "/api/uuid/convert?uuid=860d3040-1e23-416f-a1fd-f4ccc773833", nil)
resp, _ := app.Test(req)

if resp.StatusCode != http.StatusOK {
t.Errorf("Expected status code %d, but got %d", http.StatusOK, resp.StatusCode)
}

body, _ := io.ReadAll(resp.Body)
var result Response
json.Unmarshal(body, &result)

if result.Success {
t.Errorf("Expected success to be false, but got true")
}

if result.Message != "invalid UUID length: 35" {
t.Errorf("Expected message to be 'invalid UUID length: 35', but got '%s'", result.Message)
}
})

t.Run("Missing UUID", func(t *testing.T) {
req, _ := http.NewRequest("GET", "/api/uuid/convert?to=ulid", nil)
resp, _ := app.Test(req)
if resp.StatusCode != http.StatusOK {
t.Errorf("Expected status code %d, but got %d", http.StatusOK, resp.StatusCode)
}

})

t.Run("Invalid conversion type", func(t *testing.T) {
req, _ := http.NewRequest("GET", "/api/uuid/convert?uuid=860d3040-1e23-416f-a1fd-f4ccc773833d&to=invalid", nil)
resp, _ := app.Test(req)

if resp.StatusCode != http.StatusOK {
t.Errorf("Expected status code %d, but got %d", http.StatusOK, resp.StatusCode)
}

body, _ := io.ReadAll(resp.Body)
var result Response
json.Unmarshal(body, &result)

if result.Success {
t.Errorf("Expected success to be false, but got true")
}

if result.Message != "invalid conversion type" {
t.Errorf("Expected message to be 'invalid conversion type', but got '%s'", result.Message)
}
})
}
31 changes: 31 additions & 0 deletions api/yaml_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package api

import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"strings"
Expand Down Expand Up @@ -179,4 +180,34 @@ func TestYamlAPI(t *testing.T) {

assert.Equal(t, false, response.Success)
})

t.Run("Test YAML to Properties conversion - empty yaml", func(t *testing.T) {
requestBody := `{"yaml": ""}`

// Create a new request
req := httptest.NewRequest(http.MethodPost, "/api/yaml?action=to_properties", strings.NewReader(requestBody))
req.Header.Set("Content-Type", "application/json")

// Perform the request
resp, err := app.Test(req)
if err != nil {
t.Fatalf("Failed to perform request: %v", err)
}
defer resp.Body.Close()

// Check the response status code
if resp.StatusCode != http.StatusOK {
t.Errorf("Expected status code %d, but got %d", http.StatusOK, resp.StatusCode)
}

// Parse the response body
var response Response
err = json.NewDecoder(resp.Body).Decode(&response)
if err != nil {
t.Fatalf("Failed to parse response body: %v", err)
}
fmt.Println(response)

assert.Equal(t, true, response.Success)
})
}
19 changes: 17 additions & 2 deletions cmd/uuid.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,28 @@ var uuidCmd = &cobra.Command{
Short: "Generate a uuid string",
Long: `Generate a uuid string.`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println(internal.GenerateGUID())
if len(args) == 0 {
fmt.Println(internal.GenerateUUID())
} else {
to := cmd.Flag("to").Value.String()
switch to {
case "ulid":
ulid, err := internal.UUIDtoULID(args[0])
if err != nil {
fmt.Println(err)
} else {
fmt.Println(ulid)
}
default:
fmt.Println("Unknown conversion")
}
}
},
}

func init() {
rootCmd.AddCommand(uuidCmd)

uuidCmd.Flags().StringP("to", "t", "ulid", "Convert from uuid to (ulid)")
// Here you will define your flags and configuration settings.

// Cobra supports Persistent Flags which will work for this command
Expand Down
7 changes: 0 additions & 7 deletions internal/guid.go

This file was deleted.

12 changes: 0 additions & 12 deletions internal/guid_test.go

This file was deleted.

5 changes: 1 addition & 4 deletions internal/properties.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,7 @@ func Properties2Yaml(propertiesData string) (string, error) {
}

// Convert map to YAML
yamlData, err := yaml.Marshal(dataMap)
if err != nil {
return "", err
}
yamlData, _ := yaml.Marshal(dataMap)

return string(yamlData), nil
}
Expand Down
67 changes: 67 additions & 0 deletions internal/uuid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package internal

import (
"bytes"
"encoding/hex"
"strings"

"github.com/google/uuid"
)

const B32_CHARACTERS = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"

func GenerateUUID() string {
return uuid.NewString()
}

func UUIDtoULID(uuidStr string) (string, error) {
_, err := uuid.Parse(uuidStr)
if err != nil {
return "", err
}
uuidStr = strings.ReplaceAll(uuidStr, "-", "")
uuidBytes, _ := hex.DecodeString(uuidStr)

return crockfordEncode(uuidBytes), nil
}

func crockfordEncode(input []byte) string {
var output []int
bitsRead := 0
buffer := 0

// Work from the end of the buffer
reversedInput := reverseBuffer(input)

for _, byteVal := range reversedInput {
// Add current byte to start of buffer
buffer |= int(byteVal) << bitsRead
bitsRead += 8

for bitsRead >= 5 {
output = append([]int{buffer & 0x1f}, output...)
buffer >>= 5
bitsRead -= 5
}
}

if bitsRead > 0 {
output = append([]int{buffer & 0x1f}, output...)
}

var resultBuffer bytes.Buffer
for _, byteVal := range output {
resultBuffer.WriteString(string(B32_CHARACTERS[byteVal]))
}

return resultBuffer.String()
}

func reverseBuffer(input []byte) []byte {
length := len(input)
reversed := make([]byte, length)
for i := 0; i < length; i++ {
reversed[i] = input[length-i-1]
}
return reversed
}
33 changes: 33 additions & 0 deletions internal/uuid_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package internal

import (
"testing"
)

func TestGenerateUUID(t *testing.T) {
guid := GenerateUUID()
if len(guid) != 36 {
t.Errorf("Expected GUID length to be 36, but got %d", len(guid))
}
}

func TestUUIDtoULID(t *testing.T) {
t.Run("Valid UUID", func(t *testing.T) {
uuid := "860d3040-1e23-416f-a1fd-f4ccc773833d"
expectedULID := "461MR407H385QT3ZFMSK3Q70SX"
ulid, err := UUIDtoULID(uuid)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if ulid != expectedULID {
t.Errorf("Expected ULID to be %s, but got %s", expectedULID, ulid)
}
})
t.Run("Invalid UUID", func(t *testing.T) {
uuid := "860d3040-1e23-416f-a1fd-f4ccc773833"
_, err := UUIDtoULID(uuid)
if err == nil {
t.Errorf("Expected error, but got nil")
}
})
}
3 changes: 3 additions & 0 deletions internal/yaml.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ func Yaml2Json(yamlString string) (string, error) {
}

func Yaml2Properties(yamlData string) (string, error) {
if yamlData == "" {
return "", nil
}
// Parse YAML into a Node
var yamlNode yaml.Node
err := yaml.Unmarshal([]byte(yamlData), &yamlNode)
Expand Down
Loading

0 comments on commit 4323ef6

Please sign in to comment.