Skip to content

Commit

Permalink
Move propPathOr to getPathOr (evilsoft#395)
Browse files Browse the repository at this point in the history
* move from propPathOr -> getPathOr

* update docs for getPathOr

* address PR feedback
  • Loading branch information
evilsoft authored and dalefrancis88 committed May 10, 2019
1 parent f6d1ea3 commit 40fccb0
Show file tree
Hide file tree
Showing 8 changed files with 236 additions and 100 deletions.
108 changes: 54 additions & 54 deletions docs/src/pages/docs/functions/helpers.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: "Helpers"
description: "Helper functions"
layout: "notopic"
functions: ["assign", "assoc", "binary", "compose", "composek", "composep", "composes", "curry", "defaultprops", "defaultto", "dissoc", "frompairs", "getpropor", "lifta2", "lifta3", "liftn", "mapprops", "mapreduce", "mconcat", "mconcatmap", "mreduce", "mreducemap", "nary", "objof", "omit", "once", "partial", "pick", "pipe", "pipek", "pipep", "pipes", "propor", "proppathor", "setpath", "setprop", "tap", "unary", "unit", "unsetpath", "unsetprop"]
functions: ["assign", "assoc", "binary", "compose", "composek", "composep", "composes", "curry", "defaultprops", "defaultto", "dissoc", "frompairs", "getpathor", "getpropor", "lifta2", "lifta3", "liftn", "mapprops", "mapreduce", "mconcat", "mconcatmap", "mreduce", "mreducemap", "nary", "objof", "omit", "once", "partial", "pick", "pipe", "pipek", "pipep", "pipes", "propor", "proppathor", "setpath", "setprop", "tap", "unary", "unit", "unsetpath", "unsetprop"]
weight: 20
---

Expand Down Expand Up @@ -340,6 +340,59 @@ you provide an `undefined` values for the second, that `Pair` will not be
represented in the resulting `Object`. Also, when if multiple keys share the
same name, that last value will be moved over.

#### getPathOr

`crocks/helpers/getPathOr`

```haskell
getPathOr :: a -> [ (String | Integer) ] -> b -> c
```

While [`getPropOr`](#getpropor) is good for simple, single-level
structures, there may come a time when you have to work with nested POJOs or
Arrays. When you run into this situation, just pull in `getPathOr`, which was
previously called `propPathOr`, and pass it a left-to-right traversal path of
keys, indices or a combination of both (gross...but possible). This will kick
you back a function that behaves just like [`getPropOr`](#getpropor). You pass
it some data, and it will attempt to resolve your provided path. If the path is
valid, it will return the value. But if at any point that path "breaks" it will
give you back the default value.

```javascript
import getPathOr from 'crocks/helpers/getPathOr'

const data = {
foo: {
bar: 'bar',
null: null,
nan: NaN,
undef: undefined
},
arr: [ 1, 2 ]
}

// def :: [ String | Integer ] -> a -> b
const def =
getPathOr('default')

def([ 'foo', 'bar' ], data)
//=> "bar"

def([ 'baz', 'tommy' ], data)
//=> "default"

def([ 'foo', 'null' ], data)
//=> null

def([ 'foo', 'nan' ], data)
//=> NaN

def([ 'foo', 'undef' ], data)
//=> "default"

def([ 'arr', 'length' ], data)
//=> 2
```

#### getPropOr

Expand Down Expand Up @@ -963,59 +1016,6 @@ flow('string', 100)
// => Nothing
```

#### propPathOr

`crocks/helpers/propPathOr`

```haskell
propPathOr :: Foldable f => a -> f (String | Integer) -> b -> c
```

While [`getPropOr`](#getpropor) is good for simple, single-level
structures, there may come a time when you have to work with nested POJOs or
Arrays. When you run into this situation, just pull in `propPathOr` and pass it
a left-to-right traversal path of keys, indices or a combination of both
(gross...but possible). This will kick you back a function that behaves just
like [`getPropOr`](#getpropor). You pass it some data, and it will attempt to
resolve your provided path. If the path is valid, it will return the value. But
if at any point that path "breaks" it will give you back the default value.

```javascript
import propPathOr from 'crocks/helpers/propPathOr'

const data = {
foo: {
bar: 'bar',
null: null,
nan: NaN,
undef: undefined
},
arr: [ 1, 2 ]
}

// def :: [ String | Integer ] -> a -> b
const def =
propPathOr('default')

def([ 'foo', 'bar' ], data)
//=> "bar"

def([ 'baz', 'tommy' ], data)
//=> "default"

def([ 'foo', 'null' ], data)
//=> null

def([ 'foo', 'nan' ], data)
//=> NaN

def([ 'foo', 'undef' ], data)
//=> "default"

def([ 'arr', 'length' ], data)
//=> 2
```

#### setPath

`crocks/helpers/setPath`
Expand Down
5 changes: 3 additions & 2 deletions docs/src/pages/docs/functions/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ need to account for for the rest of your flow.
| [`fanout`][fanout] | `(a -> b) -> (a -> c) -> (a -> Pair b c)` | `crocks/Pair/fanout` |
| [`find`][find] | <code>Foldable f => ((a -> Boolean) &#124; Pred) -> f a -> Maybe a</code> | `crocks/Maybe/find` |
| [`fromPairs`][frompairs] | `Foldable f => f (Pair String a) -> Object` | `crocks/helpers/fromPairs` |
| [`getPathOr`][getpathor] | <code>a -> f [ (String &#124; Integer) ] -> b -> a</code> | `crocks/helpers/getPathOr` |
| [`getPropOr`][getpropor] | <code>a -> (String &#124; Integer) -> b -> c</code> | `crocks/helpers/getPropOr` |
| [`liftA2`][lifta2] | `Applicative m => (a -> b -> c) -> m a -> m b -> m c` | `crocks/helpers/liftA2` |
| [`liftA3`][lifta3] | `Applicative m => (a -> b -> c -> d) -> m a -> m b -> m c -> m d` | `crocks/helpers/liftA3` |
Expand All @@ -93,7 +94,7 @@ need to account for for the rest of your flow.
| [`prop`][prop] | <code>(String &#124; Integer) -> a -> Maybe b</code> | `crocks/Maybe/prop` |
| [`propOr`][getpropor]<br /><i>(deprecated)</i> | <code>a -> (String &#124; Integer) -> b -> c</code> | `crocks/helpers/propOr` |
| [`propPath`][proppath] | <code>Foldable f => f (String &#124; Integer) -> a -> Maybe b</code> | `crocks/Maybe/propPath` |
| [`propPathOr`][proppathor] | <code>Foldable f => a -> f (String &#124; Integer) -> b -> c</code> | `crocks/helpers/propPathOr` |
| [`propPathOr`][getpathor]<br /><i>(deprecated)</i> | <code>a -> [ (String &#124; Integer) ] -> b -> a</code> | `crocks/helpers/propPathOr` |
| [`safe`][safe] | <code>((a -> Boolean) &#124; Pred) -> a -> Maybe a</code> | `crocks/Maybe/safe` |
| [`safeAfter`][safeafter] | <code>safeAfter :: ((b -> Boolean) &#124; Pred) -> (a -> b) -> a -> Maybe b</code> | `crocks/Maybe/safeAfter` |
| [`safeLift`][safelift] | <code>((a -> Boolean) &#124; Pred) -> (a -> b) -> a -> Maybe b</code> | `crocks/Maybe/safeLift` |
Expand Down Expand Up @@ -150,6 +151,7 @@ type: `Pred a` and vice-versa
[fanout]: helpers.html#fanout
[find]: ../crocks/Maybe.html#find
[frompairs]: helpers.html#frompairs
[getpathor]: helpers.html#getpathor
[getpropor]: helpers.html#getpropor
[lifta2]: helpers.html#lifta2
[lifta3]: helpers.html#lifta3
Expand All @@ -172,7 +174,6 @@ type: `Pred a` and vice-versa
[pipes]: helpers.html#pipes
[prop]: ../crocks/Maybe.html#prop
[proppath]: ../crocks/Maybe.html#proppath
[proppathor]: helpers.html#proppathor
[safe]: ../crocks/Maybe.html#safe
[safeafter]: ../crocks/Maybe.html#safeafter
[safelift]: ../crocks/Maybe.html#safelift
Expand Down
58 changes: 58 additions & 0 deletions src/helpers/getPathOr.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/** @license ISC License (c) copyright 2019 original and current authors */
/** @author Ian Hofmann-Hicks */

const curry = require('../core/curry')
const isArray = require('../core/isArray')
const isDefined = require('../core/isDefined')
const isEmpty = require('../core/isEmpty')
const isInteger = require('../core/isInteger')
const isNil = require('../core/isNil')
const isString = require('../core/isString')

const errFn = name =>
`${name}: Array of Non-empty Strings or Integers required for second argument`

function fn(name) {
function getPathOr(def, keys, target) {
if(!isArray(keys)) {
throw new TypeError(errFn(name))
}

if(isNil(target)) {
return def
}

let value = target
for(let i = 0; i < keys.length; i++) {
const key = keys[i]

if(!(isString(key) && !isEmpty(key) || isInteger(key))) {
throw new TypeError(errFn(name))
}

if(isNil(value)) {
return def
}

value = value[key]

if(!isDefined(value)) {
return def
}
}

return value
}

return curry(getPathOr)
}

// getPathOr :: a -> [ String | Integer ] -> b -> c
const getPathOr =
fn('getPathOr')

getPathOr.origFn =
fn

module.exports =
getPathOr
113 changes: 113 additions & 0 deletions src/helpers/getPathOr.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
const test = require('tape')
const helpers = require('../test/helpers')

const bindFunc = helpers.bindFunc

const equals = require('../core/equals')
const isFunction = require('../core/isFunction')
const unit = require('../core/_unit')

const getPathOr = require('./getPathOr')

test('getPathOr function', t => {
const def = 'default value'
const fn = getPathOr(def, [ 'key' ])
const empty = getPathOr(def, [])

t.ok(isFunction(getPathOr), 'is a function')

t.equals(fn(undefined), def, 'returns the default value when data is undefined')
t.equals(fn(null), def, 'returns the default value when data is null')
t.equals(fn(NaN), def, 'returns the default value when data is NaN')

t.equals(empty(undefined), def, 'returns default with empty path and undefined')
t.equals(empty(null), def, 'returns default with empty path and null')
t.equals(empty(NaN), def, 'returns default with empty path and NaN')

t.end()
})

test('getPathOr errors', t => {
const def = 'default value'
const fn = bindFunc(x => getPathOr(def, x, {}))

const err = /getPathOr: Array of Non-empty Strings or Integers required for second argument/
t.throws(fn(def, undefined, {}), err, 'throws with undefined in second argument')
t.throws(fn(def, null, {}), err, 'throws with null in second argument')
t.throws(fn(def, 0, {}), err, 'throws with falsey number in second argument')
t.throws(fn(def, 1, {}), err, 'throws with truthy number in second argument')
t.throws(fn(def, '', {}), err, 'throws with falsey string in second argument')
t.throws(fn(def, 'string', {}), err, 'throws with truthy string in second argument')
t.throws(fn(def, false, {}), err, 'throws with false in second argument')
t.throws(fn(def, true, {}), err, 'throws with true in second argument')
t.throws(fn(def, {}, {}), err, 'throws with an object in second argument')
t.throws(fn(def, unit, {}), err, 'throws with an function in second argument')

t.throws(fn([ undefined ]), err, 'throws with an array of undefined in second argument')
t.throws(fn([ null ]), err, 'throws with array of null in second argument')
t.throws(fn([ false ]), err, 'throws with an array of false in second argument')
t.throws(fn([ true ]), err, 'throws with an array of true in second argument')
t.throws(fn([ '' ]), err, 'throws with an array of empty string in second argument')
t.throws(fn([ 1.543 ]), err, 'throws with an array of float in second argument')
t.throws(fn([ {} ]), err, 'throws with an array of object in second argument')
t.throws(fn([ unit ]), err, 'throws with an array of function in second argument')
t.throws(fn([ [ 'key' ] ], {}), err, 'throws with a nested array in second argument')

t.end()
})

test('getPathOr object traversal', t => {
const def = { b: 1 }

const fn = getPathOr(def, [ 'a', 'b' ])
const empty = getPathOr(def, [])

t.same(fn({ a: { b: { c: 32 } } }), { c: 32 }, 'returns the value when keypath is found')
t.equals(fn({ a: { b: null } }), null, 'returns null when keypath is found and value is null')
t.ok(equals(fn({ a: { b: NaN } }), NaN), 'returns NaN when keypath is found and value is NaN')

t.equals(fn({ a: { c: true } }), def, 'returns the default value when keypath is not found')
t.equals(fn({ a: { b: undefined } }), def, 'returns the default value when keypath is found and value is undefined')

t.equals(fn({ a: undefined }), def, 'returns the default value when keypath contains an undefined')
t.equals(fn({ a: null }), def, 'returns the default value when keypath contains a null')
t.equals(fn({ a: NaN }), def, 'returns the default value when keypath contains a NaN')

t.same(empty({ a: 32 }), { a: 32 }, 'returns the original object when an empty array is provided as path')

t.same(fn({ c: { b: 2 } }), def, 'returns the default value when default is an object that has the same property as a nested property in target')

t.end()
})

test('getPathOr array traversal', t => {
const def = 99

const fn = getPathOr(def, [ 0, '1' ])
const empty = getPathOr(def, [])

t.equals(fn([ [ '', 13 ] ]), 13, 'returns the value when index is found')
t.equals(fn([ [ false, null ] ]), null , 'returns null when index is found and value is null')
t.ok(equals(fn([ [ false, NaN ] ]), NaN), 'returns NaN when index is found and value is NaN')

t.equals(fn([ true ]), def, 'returns the default value when index is not found')
t.equals(fn([ [ 0, undefined ] ]), def, 'returns the default value when index is found and value is undefined')

t.equals(fn([ undefined ]), def, 'returns the default value when key path contains a null')
t.equals(fn([ null ]), def, 'returns the default value when key path contains a null')
t.equals(fn([ NaN ]), def, 'returns the default value when key path contains a NaN')

t.same(empty([ 1, { a: 23 } ]), [ 1, { a: 23 } ], 'returns the original array when an empty array is provided as path')

t.end()
})

test('getPathOr mixed traversal', t => {
const def = 'default value'
const fn = getPathOr(def, [ 'a', 1 ])

t.equals(fn({ a: [ 0, false ] }), false, 'returns value when found with mixed path')
t.equals(fn({ c: [ true ] }), def, 'returns default when not found with mixed path')

t.end()
})
1 change: 1 addition & 0 deletions src/helpers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ module.exports = {
defaultTo: require('./defaultTo'),
dissoc: require('./dissoc'),
fromPairs: require('./fromPairs'),
getPathOr: require('./getPathOr'),
liftA2: require('./liftA2'),
liftA3: require('./liftA3'),
liftN: require('./liftN'),
Expand Down
2 changes: 2 additions & 0 deletions src/helpers/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const defaultProps = require('./defaultProps')
const defaultTo = require('./defaultTo')
const dissoc = require('./dissoc')
const fromPairs = require('./fromPairs')
const getPathOr = require('./getPathOr')
const getPropOr = require('./getPropOr')
const liftA2 = require('./liftA2')
const liftA3 = require('./liftA3')
Expand Down Expand Up @@ -56,6 +57,7 @@ test('helpers entry', t => {
t.equal(index.defaultTo, defaultTo, 'provides the defaultTo helper')
t.equal(index.dissoc, dissoc, 'provides the dissoc helper')
t.equal(index.fromPairs, fromPairs, 'provides the fromPairs helper')
t.equal(index.getPathOr, getPathOr, 'provides the getPathOr helper')
t.equal(index.getPropOr, getPropOr, 'provides the getPropOr helper')
t.equal(index.liftA2, liftA2, 'provides the liftA2 helper')
t.equal(index.liftA3, liftA3, 'provides the liftA3 helper')
Expand Down
Loading

0 comments on commit 40fccb0

Please sign in to comment.