From 9ddea1945d8796c6ea2b28b337c3980db9777c23 Mon Sep 17 00:00:00 2001 From: Ryan Leary Date: Fri, 25 Aug 2017 14:29:02 -0400 Subject: [PATCH] Fix UUID bugs and refactor. Closes #54. * Update UUID namespace handling * Fix bugs in UUID implementation * Fix tests --- README.md | 2 +- transform/util.go | 15 +++-- transform/uuid.go | 121 +++++++++++++++++++++-------------------- transform/uuid_test.go | 80 +++++++++++---------------- 4 files changed, 105 insertions(+), 113 deletions(-) diff --git a/README.md b/README.md index 2b53bd5..2aa27d2 100644 --- a/README.md +++ b/README.md @@ -296,7 +296,7 @@ For UUIDv3 & UUIDV5 are a bit more complex. These require a Name Space which is "spec":{ "doc.uuid":{ "version":5, - "nameSpace":"DNS", + "namespace":"DNS", "names":[ {"path":"doc.author_name", "default":"some string"}, {"path":"doc.type", "default":"another string"}, diff --git a/transform/util.go b/transform/util.go index ee14f47..a89eda8 100644 --- a/transform/util.go +++ b/transform/util.go @@ -40,7 +40,10 @@ type Config struct { InPlace bool `json:"inplace,omitempty"` } -var jsonPathRe = regexp.MustCompile("([^\\[\\]]+)\\[(.*?)\\]") +var ( + NonExistentPath = RequireError("Path does not exist") + jsonPathRe = regexp.MustCompile("([^\\[\\]]+)\\[(.*?)\\]") +) // Given a json byte slice `data` and a kazaam `path` string, return the object at the path in data if it exists. func getJSONRaw(data []byte, path string, pathRequired bool) ([]byte, error) { @@ -70,7 +73,7 @@ func getJSONRaw(data []byte, path string, pathRequired bool) ([]byte, error) { }, beforePath...) if err == jsonparser.KeyPathNotFoundError { if pathRequired { - return nil, RequireError("Path does not exist") + return nil, NonExistentPath } } else if err != nil { return nil, err @@ -80,7 +83,11 @@ func getJSONRaw(data []byte, path string, pathRequired bool) ([]byte, error) { if newPath != "" { for i, value := range results { intermediate, err := getJSONRaw(value, newPath, pathRequired) - if err != nil { + if err == jsonparser.KeyPathNotFoundError { + if pathRequired { + return nil, NonExistentPath + } + } else if err != nil { return nil, err } results[i] = intermediate @@ -121,7 +128,7 @@ func getJSONRaw(data []byte, path string, pathRequired bool) ([]byte, error) { } if err == jsonparser.KeyPathNotFoundError { if pathRequired { - return nil, RequireError("Path does not exist") + return nil, NonExistentPath } } else if err != nil { return nil, err diff --git a/transform/uuid.go b/transform/uuid.go index 6cd2b1f..d8b6474 100644 --- a/transform/uuid.go +++ b/transform/uuid.go @@ -18,16 +18,13 @@ func UUID(spec *Config, data []byte) ([]byte, error) { for k, v := range *spec.Spec { outPath := strings.Split(k, ".") - // convert to corrct type + // convert spec to correct type uuidSpec, ok := v.(map[string]interface{}) if !ok { return nil, SpecError("Invalid Spec for UUID") } - - //grab version - version, ok := uuidSpec["version"] - - if !ok { + version := getUUIDVersion(uuidSpec) + if version < 3 || version > 5 { return nil, versionError } @@ -35,91 +32,95 @@ func UUID(spec *Config, data []byte) ([]byte, error) { var err error switch version { - case 4.0: + case 4: u = uuid.NewV4() - case 3.0, 5.0: + case 3, 5: + // choose the correct UUID function + var NewUUID func(uuid.UUID, string) uuid.UUID + NewUUID = uuid.NewV3 + if version == 5 { + NewUUID = uuid.NewV5 + } + // pull required configuration from spec and do validation names, ok := uuidSpec["names"] if !ok { return nil, SpecError("Must provide names field") } - - nameSpace, ok := uuidSpec["nameSpace"].(string) + namespaceString, ok := uuidSpec["namespace"].(string) if !ok { - return nil, SpecError("Must provide namesapce, Must be a string") - } - - var nameSpaceUUID uuid.UUID - - // swtich on the namespace - switch nameSpace { - case "DNS": - nameSpaceUUID = uuid.NamespaceDNS - case "URL": - nameSpaceUUID = uuid.NamespaceURL - case "OID": - nameSpaceUUID = uuid.NamespaceOID - case "X500": - nameSpaceUUID = uuid.NamespaceX500 - default: - nameSpaceUUID, err = uuid.FromString(nameSpace) - if err != nil { - return nil, SpecError("nameSpace is not a valid UUID or is not DNS, URL, OID, X500") - } + return nil, SpecError("Must provide `namespace` as a string") } - nameFields, ok := names.([]interface{}) if !ok { - return nil, SpecError("Spec is invalid") + return nil, SpecError("Spec is invalid. `Names` field must be an array.") + } + + // generate the required namespace + u, err = namespaceFromString(namespaceString) + if err != nil { + return nil, SpecError("namespace is not a valid UUID or is not DNS, URL, OID, X500") } // loop over the names field for _, field := range nameFields { p, _ := field.(map[string]interface{})["path"].(string) - name, err := getJSONRaw(data, p, false) - if err == jsonparser.KeyPathNotFoundError { - - d, ok := field.(map[string]interface{})["default"].(string) + name, pathErr := getJSONRaw(data, p, true) + // if a string, remove the heading and trailing quote + nameString := strings.TrimPrefix(strings.TrimSuffix(string(name), "\""), "\"") + if pathErr == NonExistentPath { + nameString, ok = field.(map[string]interface{})["default"].(string) if !ok { - return nil, SpecError("Spec is invalid") - + return nil, SpecError("Spec is invalid. Unable to get path or default") } - name = []byte(d) - - } - - // check if there is an empty uuid & version 3 - if u.String() == "00000000-0000-0000-0000-000000000000" && version == 3.0 { - - u = uuid.NewV3(nameSpaceUUID, string(name)) - - // same as above except version 5 - } else if u.String() == "00000000-0000-0000-0000-000000000000" && version == 5.0 { - - u = uuid.NewV5(nameSpaceUUID, string(name)) - - } else if version == 3.0 { - - u = uuid.NewV3(u, string(name)) - - } else if version == 5.0 { - - u = uuid.NewV3(u, string(name)) } + u = NewUUID(u, nameString) } default: return nil, versionError } + // set the uuid in the appropraite place d, err := jsonparser.Set(data, bookend([]byte(u.String()), '"', '"'), outPath...) if err != nil { return nil, err } - return d, nil } return nil, SpecError("Spec invalid for UUID") } + +func namespaceFromString(namespace string) (uuid.UUID, error) { + var u uuid.UUID + var err error + switch namespace { + case "DNS": + u = uuid.NamespaceDNS + case "URL": + u = uuid.NamespaceURL + case "OID": + u = uuid.NamespaceOID + case "X500": + u = uuid.NamespaceX500 + default: + u, err = uuid.FromString(namespace) + } + return u, err +} + +func getUUIDVersion(uuidSpec map[string]interface{}) int { + var version int + versionInterface, ok := uuidSpec["version"] + if !ok { + return -1 + } + versionFloat, ok := versionInterface.(float64) + version = int(versionFloat) + if !ok || version < 3 || version > 5 { + return -2 + } + return version +} diff --git a/transform/uuid_test.go b/transform/uuid_test.go index 69473f9..2e70bfb 100644 --- a/transform/uuid_test.go +++ b/transform/uuid_test.go @@ -68,9 +68,7 @@ func TestUUIDInValidSpec(t *testing.T) { } func TestUUIDV3WithoutNamespace(t *testing.T) { - spec := `{"a.uuid": {"version": 3, "names": [{"path": "a.id", "default": "test"}, {"path": "a.author", "default": "test"}]}}` - jsonIn := `{"a":{"id":2323223}}` cfg := getConfig(spec, false) @@ -84,8 +82,7 @@ func TestUUIDV3WithoutNamespace(t *testing.T) { } func TestUUIDV3InvalidNamespace(t *testing.T) { - - spec := `{"a.uuid": {"version": 3, "nameSpace": "test", "names": [{"path": "a.id", "default": "test"}, + spec := `{"a.uuid": {"version": 3, "namespace": "test", "names": [{"path": "a.id", "default": "test"}, {"path": "a.author", "default": "test"}]}}` jsonIn := `{"a":{"id":2323223}}` @@ -97,15 +94,13 @@ func TestUUIDV3InvalidNamespace(t *testing.T) { t.Error("Should fail on missing namespace") t.FailNow() } - } func TestUUIDV3URLNameSpace(t *testing.T) { - - spec := `{"a.uuid": {"version": 3, "nameSpace": "URL", "names": [{"path": "a.id", "default": "test"}, + spec := `{"a.uuid": {"version": 3, "namespace": "URL", "names": [{"path": "a.id", "default": "test"}, {"path": "a.author", "default": "test"}]}}` jsonIn := `{"a":{"author":"jason","id":2323223}}` - jsonOut := `{"a":{"author":"jason","id":2323223,"uuid":"cad3ae9e-7a89-3b0b-8ef4-9c22ead9c6eb"}}` + jsonOut := `{"a":{"author":"jason","id":2323223,"uuid":"06af7528-f22c-3716-86e0-579192ed244a"}}` cfg := getConfig(spec, false) kazaamOut, err := getTransformTestWrapper(UUID, cfg, jsonIn) @@ -119,7 +114,7 @@ func TestUUIDV3URLNameSpace(t *testing.T) { if !areEqual { t.Error("Transformed data does not match expectation.") t.Log("Expected: ", jsonOut) - t.Log("Actual: ", kazaamOut) + t.Log("Actual: ", string(kazaamOut)) t.FailNow() } @@ -134,11 +129,10 @@ func TestUUIDV3URLNameSpace(t *testing.T) { } func TestUUIDV3DNSNameSpace(t *testing.T) { - - spec := `{"a.uuid": {"version": 3, "nameSpace": "DNS", "names": [{"path": "a.id", "default": "test"}, + spec := `{"a.uuid": {"version": 3, "namespace": "DNS", "names": [{"path": "a.id", "default": "test"}, {"path": "a.author", "default": "test"}]}}` jsonIn := `{"a":{"author":"jason","id":2323223}}` - jsonOut := `{"a":{"author":"jason","id":2323223,"uuid":"9a77a459-b1a3-32bc-b758-e5569a667a61"}}` + jsonOut := `{"a":{"author":"jason","id":2323223,"uuid":"83e9b77c-641d-331f-961d-bac57a61e534"}}` cfg := getConfig(spec, false) kazaamOut, err := getTransformTestWrapper(UUID, cfg, jsonIn) @@ -152,7 +146,7 @@ func TestUUIDV3DNSNameSpace(t *testing.T) { if !areEqual { t.Error("Transformed data does not match expectation.") t.Log("Expected: ", jsonOut) - t.Log("Actual: ", kazaamOut) + t.Log("Actual: ", string(kazaamOut)) t.FailNow() } @@ -167,11 +161,10 @@ func TestUUIDV3DNSNameSpace(t *testing.T) { } func TestUUIDV3OIDNameSpace(t *testing.T) { - - spec := `{"a.uuid": {"version": 3, "nameSpace": "OID", "names": [{"path": "a.id", "default": "test"}, + spec := `{"a.uuid": {"version": 3, "namespace": "OID", "names": [{"path": "a.id", "default": "test"}, {"path": "a.author", "default": "test"}]}}` jsonIn := `{"a":{"author":"jason","id":2323223}}` - jsonOut := `{"a":{"author":"jason","id":2323223,"uuid":"c01bef62-619d-3524-a36c-4bdcf263e0cb"}}` + jsonOut := `{"a":{"author":"jason","id":2323223,"uuid":"d26cc082-0ba8-38a7-a738-983f3830f0cf"}}` cfg := getConfig(spec, false) kazaamOut, err := getTransformTestWrapper(UUID, cfg, jsonIn) @@ -185,7 +178,7 @@ func TestUUIDV3OIDNameSpace(t *testing.T) { if !areEqual { t.Error("Transformed data does not match expectation.") t.Log("Expected: ", jsonOut) - t.Log("Actual: ", kazaamOut) + t.Log("Actual: ", string(kazaamOut)) t.FailNow() } @@ -200,11 +193,10 @@ func TestUUIDV3OIDNameSpace(t *testing.T) { } func TestUUIDV3X500NameSpace(t *testing.T) { - - spec := `{"a.uuid": {"version": 3, "nameSpace": "X500", "names": [{"path": "a.id", "default": "test"}, + spec := `{"a.uuid": {"version": 3, "namespace": "X500", "names": [{"path": "a.id", "default": "test"}, {"path": "a.author", "default": "test"}]}}` jsonIn := `{"a":{"author":"jason","id":2323223}}` - jsonOut := `{"a":{"author":"jason","id":2323223,"uuid":"b7b0fc51-085c-35a3-9b1b-e1b5dcef128b"}}` + jsonOut := `{"a":{"author":"jason","id":2323223,"uuid":"438297ee-7562-336d-a913-7e7745455e80"}}` cfg := getConfig(spec, false) kazaamOut, err := getTransformTestWrapper(UUID, cfg, jsonIn) @@ -218,7 +210,7 @@ func TestUUIDV3X500NameSpace(t *testing.T) { if !areEqual { t.Error("Transformed data does not match expectation.") t.Log("Expected: ", jsonOut) - t.Log("Actual: ", kazaamOut) + t.Log("Actual: ", string(kazaamOut)) t.FailNow() } @@ -232,12 +224,11 @@ func TestUUIDV3X500NameSpace(t *testing.T) { } } -func TestUUIDWithCustomeNameSpace(t *testing.T) { - - spec := `{"a.uuid": {"version": 3, "nameSpace": "04536ac7-c030-4f10-811b-451bcc4c8ef5", "names": [{"path": "a.id", "default": "test"}, +func TestUUIDWithCustomNameSpace(t *testing.T) { + spec := `{"a.uuid": {"version": 3, "namespace": "04536ac7-c030-4f10-811b-451bcc4c8ef5", "names": [{"path": "a.id", "default": "test"}, {"path": "a.author", "default": "test"}]}}` jsonIn := `{"a":{"author":"jason","id":2323223}}` - jsonOut := `{"a":{"author":"jason","id":2323223,"uuid":"49121a9c-2d58-30aa-8eed-02eb1b61a0e1"}}` + jsonOut := `{"a":{"author":"jason","id":2323223,"uuid":"82966ef3-a31d-379a-a266-d8f323901397"}}` cfg := getConfig(spec, false) kazaamOut, err := getTransformTestWrapper(UUID, cfg, jsonIn) @@ -251,7 +242,7 @@ func TestUUIDWithCustomeNameSpace(t *testing.T) { if !areEqual { t.Error("Transformed data does not match expectation.") t.Log("Expected: ", jsonOut) - t.Log("Actual: ", kazaamOut) + t.Log("Actual: ", string(kazaamOut)) t.FailNow() } @@ -266,11 +257,10 @@ func TestUUIDWithCustomeNameSpace(t *testing.T) { } func TestUUIDV3UsingDefaultField(t *testing.T) { - - spec := `{"a.uuid":{"version": 3, "nameSpace": "URL", "names": [{"path": "a.id", "default": "test"}, + spec := `{"a.uuid":{"version": 3, "namespace": "URL", "names": [{"path": "a.id", "default": "test"}, {"path": "a.author", "default": "test"}]}}` jsonIn := `{"a":{"id":2323223}}` - jsonOut := `{"a":{"id":2323223,"uuid":"9a4d8062-cefd-35d5-907e-b2da04873d95"}}` + jsonOut := `{"a":{"id":2323223,"uuid":"a6cb7732-ccc1-3725-a9dc-040e73e0889b"}}` cfg := getConfig(spec, false) kazaamOut, err := getTransformTestWrapper(UUID, cfg, jsonIn) @@ -284,7 +274,7 @@ func TestUUIDV3UsingDefaultField(t *testing.T) { if !areEqual { t.Error("Transformed data does not match expectation.") t.Log("Expected: ", jsonOut) - t.Log("Actual: ", kazaamOut) + t.Log("Actual: ", string(kazaamOut)) t.FailNow() } @@ -299,8 +289,7 @@ func TestUUIDV3UsingDefaultField(t *testing.T) { } func TestUUIDBadNameSpace(t *testing.T) { - - spec := `{"a.uuid": {"version": 3, "nameSpace": "not a uuid", "names": [{"path": "a.id", "default": "test"}, + spec := `{"a.uuid": {"version": 3, "namespace": "not a uuid", "names": [{"path": "a.id", "default": "test"}, {"path": "a.author", "default": "test"}]}}` jsonIn := `{"a":{"id":2323223}}` @@ -316,11 +305,10 @@ func TestUUIDBadNameSpace(t *testing.T) { } func TestUUIDV5(t *testing.T) { - - spec := `{"a.uuid": {"version": 5, "nameSpace": "URL", "names": [{"path": "a.id", "default": "test"}, + spec := `{"a.uuid": {"version": 5, "namespace": "URL", "names": [{"path": "a.id", "default": "test"}, {"path": "a.author", "default": "test"}]}}` jsonIn := `{"a":{"author":"jason","id":2323223}}` - jsonOut := `{"a":{"author":"jason","id":2323223,"uuid":"7e7c9ede-828f-39f7-9cc0-55c4a259d8f4"}}` + jsonOut := `{"a":{"author":"jason","id":2323223,"uuid":"388607bf-b5c1-5f55-b10a-6afbbb91e18e"}}` cfg := getConfig(spec, false) kazaamOut, err := getTransformTestWrapper(UUID, cfg, jsonIn) @@ -334,7 +322,7 @@ func TestUUIDV5(t *testing.T) { if !areEqual { t.Error("Transformed data does not match expectation.") t.Log("Expected: ", jsonOut) - t.Log("Actual: ", kazaamOut) + t.Log("Actual: ", string(kazaamOut)) t.FailNow() } @@ -349,11 +337,10 @@ func TestUUIDV5(t *testing.T) { } func TestUUIDV5UsingDefaultField(t *testing.T) { - - spec := `{"a.uuid": {"version": 5, "nameSpace": "URL", "names": [{"path": "a.id", "default": "test"}, + spec := `{"a.uuid": {"version": 5, "namespace": "URL", "names": [{"path": "a.id", "default": "test"}, {"path": "a.author", "default": "go lang rules!"}]}}` jsonIn := `{"a":{"id":2323223}}` - jsonOut := `{"a":{"id":2323223,"uuid":"015747dc-eef7-36ab-b22f-1c851ef3118e"}}` + jsonOut := `{"a":{"id":2323223,"uuid":"fb26c0d3-3cd2-514f-aced-2ecd901a1196"}}` cfg := getConfig(spec, false) kazaamOut, err := getTransformTestWrapper(UUID, cfg, jsonIn) @@ -367,7 +354,7 @@ func TestUUIDV5UsingDefaultField(t *testing.T) { if !areEqual { t.Error("Transformed data does not match expectation.") t.Log("Expected: ", jsonOut) - t.Log("Actual: ", kazaamOut) + t.Log("Actual: ", string(kazaamOut)) t.FailNow() } @@ -382,8 +369,7 @@ func TestUUIDV5UsingDefaultField(t *testing.T) { } func TestUUIDV5NoNames(t *testing.T) { - - spec := `{"a.uuid": {"version": 5, "nameSpace": "URL", "names": []}, + spec := `{"a.uuid": {"version": 5, "namespace": "URL", "names": []}, {"path": "a.author", "default": "go lang rules!"}]}}` jsonIn := `{"a":{"id":2323223}}` @@ -398,8 +384,7 @@ func TestUUIDV5NoNames(t *testing.T) { } func TestUUIDV5NoName(t *testing.T) { - - spec := `{"a.uuid": {"version": 5, "nameSpace": "URL"}, + spec := `{"a.uuid": {"version": 5, "namespace": "URL"}, {"path": "a.author", "default": "go lang rules!"}]}}` jsonIn := `{"a":{"id":2323223}}` @@ -414,10 +399,10 @@ func TestUUIDV5NoName(t *testing.T) { } func TestUUIDV5ArrayIndex(t *testing.T) { - spec := `{"a.uuid": {"version": 5, "nameSpace": "URL", "names": [{"path": "a.tags[0]", "default": "test"}, + spec := `{"a.uuid": {"version": 5, "namespace": "URL", "names": [{"path": "a.tags[0]", "default": "test"}, {"path": "a.author", "default": "go lang rules!"}]}}` jsonIn := `{"a":{"id":2323223, "tags": ["tag1", "tag2"]}}` - jsonOut := `{"a":{"id":2323223, "tags": ["tag1", "tag2"],"uuid":"49ad2943-37a6-3baa-96ca-980861b80191"}}` + jsonOut := `{"a":{"id":2323223, "tags": ["tag1", "tag2"],"uuid":"5ae718bd-0add-5bab-a155-340433391b8c"}}` cfg := getConfig(spec, false) kazaamOut, err := getTransformTestWrapper(UUID, cfg, jsonIn) @@ -431,7 +416,7 @@ func TestUUIDV5ArrayIndex(t *testing.T) { if !areEqual { t.Error("Transformed data does not match expectation.") t.Log("Expected: ", jsonOut) - t.Log("Actual: ", kazaamOut) + t.Log("Actual: ", string(kazaamOut)) t.FailNow() } @@ -443,5 +428,4 @@ func TestUUIDV5ArrayIndex(t *testing.T) { t.Error(err) t.FailNow() } - }