Skip to content

Commit

Permalink
Merge pull request #25 from chilland/24-add-require
Browse files Browse the repository at this point in the history
Add path validation
  • Loading branch information
chilland authored Feb 23, 2017
2 parents 3213fcc + 2e21d13 commit c12da03
Show file tree
Hide file tree
Showing 3 changed files with 222 additions and 35 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ The jsonpath implementation supports a few special cases:
- *Array wildcarding*: indexing an array with `[*]` will return every matching element in an array
- *Top-level object capture*: Mapping `$` into a field will nest the entire original object under the requested key

The shift transform also supports a `"require"` field. When set to `true`,
Kazaam will throw an error if *any* of the paths in the source JSON are not
present.

### Concat
The concat transform allows to combine fields and literal strings into a single string value.
```javascript
Expand Down Expand Up @@ -111,6 +115,10 @@ Notes:
- if this an existing path, the result will replace current value.
- *delim*: Optional delimiter

The concat transform also supports a `"require"` field. When set to `true`,
Kazaam will throw an error if *any* of the paths in the source JSON are not
present.

### Coalesce
A coalesce transform provides the ability to check multiple possible keys to find a desired value. The first matching key found of those provided is returned.
```javascript
Expand Down
107 changes: 74 additions & 33 deletions kazaam.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package kazaam

import (
"encoding/json"
"errors"
"fmt"
"reflect"
"regexp"
Expand All @@ -19,12 +18,12 @@ var validSpecTypes map[string]transformFunc

func init() {
validSpecTypes = map[string]transformFunc{
"pass": transformPass,
"shift": transformShift,
"extract": transformExtract,
"default": transformDefault,
"concat": transformConcat,
"coalesce": transformCoalesce,
"pass": transformPass,
"shift": transformShift,
"extract": transformExtract,
"default": transformDefault,
"concat": transformConcat,
"coalesce": transformCoalesce,
}
}

Expand All @@ -33,6 +32,29 @@ type spec struct {
Operation *string `json:"operation"`
Spec *map[string]interface{} `json:"spec"`
Over *string `json:"over,omitempty"`
Require bool `json:"require,omitempty"`
}

type KazaamError struct {
ErrMsg string
ErrType int
}

const (
ParseError int = iota // There was an error parsing the data
RequireError int = iota // The JSON path does not exist and is required
SpecError int = iota // The kazaam specification is not properly formatted
)

func (e *KazaamError) Error() string {
switch e.ErrType {
case ParseError:
return fmt.Sprintf("ParseError - %s", e.ErrMsg)
case RequireError:
return fmt.Sprintf("RequiredError - %s", e.ErrMsg)
default:
return fmt.Sprintf("SpecError - %s", e.ErrMsg)
}
}

type specInt spec
Expand All @@ -44,14 +66,14 @@ func (s *spec) UnmarshalJSON(b []byte) (err error) {
if err = json.Unmarshal(b, &j); err == nil {
*s = spec(j)
if s.Operation == nil {
err = errors.New("Spec must contain an \"operation\" field")
err = &KazaamError{ErrMsg: "Spec must contain an \"operation\" field", ErrType: SpecError}
return
}
if _, ok := validSpecTypes[*s.Operation]; ok == false {
err = errors.New("Invalid spec operation specified")
err = &KazaamError{ErrMsg: "Invalid spec operation specified", ErrType: SpecError}
}
if s.Spec != nil && len(*s.Spec) < 1 {
err = errors.New("Spec must contain at least one element")
err = &KazaamError{ErrMsg: "Spec must contain at least one element", ErrType: SpecError}
return
}
return
Expand Down Expand Up @@ -183,9 +205,9 @@ func transformDefault(spec *spec, data *simplejson.Json) (*simplejson.Json, erro
func transformExtract(spec *spec, data *simplejson.Json) (*simplejson.Json, error) {
outPath, ok := (*spec.Spec)["path"]
if !ok {
return nil, fmt.Errorf("Unable to get path")
return nil, &KazaamError{ErrMsg: fmt.Sprintf("Unable to get path"), ErrType: SpecError}
}
outData, err := getJSONPath(data, outPath.(string))
outData, err := getJSONPath(data, outPath.(string), spec.Require)
return outData, err
}

Expand All @@ -206,12 +228,12 @@ func transformShift(spec *spec, data *simplejson.Json) (*simplejson.Json, error)
for _, vItem := range v.([]interface{}) {
vItemStr, found := vItem.(string)
if !found {
return nil, fmt.Errorf("Warn: Unable to coerce element to json string: %v", vItem)
return nil, &KazaamError{ErrMsg: fmt.Sprintf("Warn: Unable to coerce element to json string: %v", vItem), ErrType: ParseError}
}
keyList = append(keyList, vItemStr)
}
default:
return nil, fmt.Errorf("Warn: Unknown type in message for key: %s", k)
return nil, &KazaamError{ErrMsg: fmt.Sprintf("Warn: Unknown type in message for key: %s", k), ErrType: ParseError}
}

// iterate over keys to evaluate
Expand All @@ -223,7 +245,7 @@ func transformShift(spec *spec, data *simplejson.Json) (*simplejson.Json, error)
if v == "$" {
dataForV = data
} else {
dataForV, err = getJSONPath(data, v)
dataForV, err = getJSONPath(data, v, spec.Require)
if err != nil {
return nil, err
}
Expand All @@ -243,16 +265,16 @@ func transformShift(spec *spec, data *simplejson.Json) (*simplejson.Json, error)
}

func transformConcat(spec *spec, data *simplejson.Json) (*simplejson.Json, error) {
sourceList, ok := (*spec.Spec)["sources"]
if !ok {
return nil, fmt.Errorf("Unable to get sources")
sourceList, sourceOk := (*spec.Spec)["sources"]
if !sourceOk {
return nil, &KazaamError{ErrMsg: fmt.Sprintf("Unable to get sources"), ErrType: SpecError}
}
targetPath, ok := (*spec.Spec)["targetPath"]
if !ok {
return nil, fmt.Errorf("Unable to get targetPath")
targetPath, targetOk := (*spec.Spec)["targetPath"]
if !targetOk {
return nil, &KazaamError{ErrMsg: fmt.Sprintf("Unable to get targetPath"), ErrType: SpecError}
}
delimiter, ok := (*spec.Spec)["delim"]
if !ok {
delimiter, delimOk := (*spec.Spec)["delim"]
if !delimOk {
// missing delimiter. default to blank
delimiter = ""
}
Expand All @@ -267,10 +289,13 @@ func transformConcat(spec *spec, data *simplejson.Json) (*simplejson.Json, error
if !ok {
path, ok := vItem.(map[string]interface{})["path"]
if ok {
valueNodePtr, err := getJSONPath(data, path.(string))
if err != nil {
valueNodePtr, err := getJSONPath(data, path.(string), spec.Require)
switch {
case err != nil && spec.Require == true:
return nil, &KazaamError{ErrMsg: fmt.Sprintf("Path does not exist"), ErrType: RequireError}
case err != nil:
value = ""
} else {
default:
zed := (*valueNodePtr).Interface()
switch zed.(type) {
case []interface{}:
Expand All @@ -286,7 +311,7 @@ func transformConcat(spec *spec, data *simplejson.Json) (*simplejson.Json, error
}
}
} else {
return nil, fmt.Errorf("Error processing %v: must have either value or path specified", vItem)
return nil, &KazaamError{ErrMsg: fmt.Sprintf("Error processing %v: must have either value or path specified", vItem), ErrType: SpecError}
}
}
outString += value.(string)
Expand All @@ -300,6 +325,9 @@ func transformConcat(spec *spec, data *simplejson.Json) (*simplejson.Json, error
}

func transformCoalesce(spec *spec, data *simplejson.Json) (*simplejson.Json, error) {
if spec.Require == true {
return nil, &KazaamError{ErrMsg: fmt.Sprintf("Invalid spec. Coalesce does not support \"require\""), ErrType: SpecError}
}
for k, v := range *spec.Spec {
outPath := strings.Split(k, ".")

Expand All @@ -311,12 +339,12 @@ func transformCoalesce(spec *spec, data *simplejson.Json) (*simplejson.Json, err
for _, vItem := range v.([]interface{}) {
vItemStr, found := vItem.(string)
if !found {
return nil, fmt.Errorf("Warn: Unable to coerce element to json string: %v", vItem)
return nil, &KazaamError{ErrMsg: fmt.Sprintf("Warn: Unable to coerce element to json string: %v", vItem), ErrType: ParseError}
}
keyList = append(keyList, vItemStr)
}
default:
return nil, fmt.Errorf("Warn: Expected list in message for key: %s", k)
return nil, &KazaamError{ErrMsg: fmt.Sprintf("Warn: Expected list in message for key: %s", k), ErrType: ParseError}
}

// iterate over keys to evaluate
Expand All @@ -325,7 +353,7 @@ func transformCoalesce(spec *spec, data *simplejson.Json) (*simplejson.Json, err
var err error

// grab the data
dataForV, err = getJSONPath(data, v)
dataForV, err = getJSONPath(data, v, false)
if err != nil {
return nil, err
}
Expand All @@ -340,7 +368,7 @@ func transformCoalesce(spec *spec, data *simplejson.Json) (*simplejson.Json, err

var jsonPathRe = regexp.MustCompile("([^\\[\\]]+)\\[([0-9\\*]+)\\]")

func getJSONPath(j *simplejson.Json, path string) (*simplejson.Json, error) {
func getJSONPath(j *simplejson.Json, path string, pathRequired bool) (*simplejson.Json, error) {
jin := j
objectKeys := strings.Split(path, ".")
// iterate over each subsequent object key
Expand All @@ -353,7 +381,14 @@ func getJSONPath(j *simplejson.Json, path string) (*simplejson.Json, error) {
// if there's a wildcard array reference
if arrayKeyStr == "*" {
// get the array
jin = jin.Get(objKey)
if pathRequired == true {
jin, exists := jin.CheckGet(objKey)
if exists != true {
return jin, &KazaamError{ErrMsg: fmt.Sprintf("Path does not exist"), ErrType: RequireError}
}
} else {
jin = jin.Get(objKey)
}
arrayLength := len(jin.MustArray())
// construct the remainder of the jsonPath
newPath := strings.Join(objectKeys[element+1:], ".")
Expand All @@ -364,7 +399,7 @@ func getJSONPath(j *simplejson.Json, path string) (*simplejson.Json, error) {
if newPath == "" {
results = append(results, jin.GetIndex(i).Interface())
} else {
intermediate, err := getJSONPath(jin.GetIndex(i), newPath)
intermediate, err := getJSONPath(jin.GetIndex(i), newPath, pathRequired)
if err != nil {
return nil, err
}
Expand All @@ -382,6 +417,12 @@ func getJSONPath(j *simplejson.Json, path string) (*simplejson.Json, error) {
}
jin = jin.Get(objKey).GetIndex(arrayKey)
} else {
if pathRequired == true {
_, exists := jin.CheckGet(k)
if exists != true {
return nil, &KazaamError{ErrMsg: fmt.Sprintf("Path does not exist"), ErrType: RequireError}
}
}
jin = jin.Get(k)
}
}
Expand Down
Loading

0 comments on commit c12da03

Please sign in to comment.