in with the new

This commit is contained in:
Ron
2017-03-17 21:17:53 -07:00
parent bd8344c808
commit f22a628934
157 changed files with 12952 additions and 0 deletions

View File

@@ -0,0 +1,43 @@
import serializeError from "serialize-error"
export const NEW_THROWN_ERR = "err_new_thrown_err"
export const NEW_THROWN_ERR_BATCH = "err_new_thrown_err_batch"
export const NEW_SPEC_ERR = "err_new_spec_err"
export const NEW_AUTH_ERR = "err_new_auth_err"
export const CLEAR = "err_clear"
export function newThrownErr(err, action) {
return {
type: NEW_THROWN_ERR,
payload: { action, error: serializeError(err) }
}
}
export function newThrownErrBatch(errors) {
return {
type: NEW_THROWN_ERR_BATCH,
payload: errors
}
}
export function newSpecErr(err, action) {
return {
type: NEW_SPEC_ERR,
payload: err
}
}
export function newAuthErr(err, action) {
return {
type: NEW_AUTH_ERR,
payload: err
}
}
export function clear(filter = {}) {
// filter looks like: {type: 'spec'}, {source: 'parser'}
return {
type: CLEAR,
payload: filter
}
}

View File

@@ -0,0 +1,31 @@
# Error transformers
Error transformers provide a standard interface for making generated error messages more useful to end users.
### Inputs & outputs
Each transformer's `transform` function is given an Immutable List of Immutable Maps as its first argument.
It is expected that each `transform` function returns a List of similarly-formed Maps.
These errors originate from the Redux error actions that add errors to state. Errors are transformed before being passed into the reducer.
It's important that all the keys present in each error (specifically, `line`, `level`, `message`, `source`, and `type`) are present when the transformer is finished.
##### Deleting an error
If you want to delete an error completely, you can overwrite it with `null`. The null value will be filtered out of the transformed error array before the errors are returned.
å
### Example transformer
This transformer will increase all your line numbers by 10.
```
export function transform(errors) {
return errors.map(err => {
err.line += 10
return err
})
}
```

View File

@@ -0,0 +1,57 @@
import concat from "lodash/concat"
import reduce from "lodash/reduce"
let request = require.context("./transformers/", true, /\.js$/)
let errorTransformers = []
request.keys().forEach( function( key ){
if( key === "./hook.js" ) {
return
}
if( !key.match(/js$/) ) {
return
}
if( key.slice(2).indexOf("/") > -1) {
// skip files in subdirs
return
}
errorTransformers.push({
name: toTitleCase(key).replace(".js", "").replace("./", ""),
transform: request(key).transform
})
})
export default function transformErrors (errors, system) {
let inputs = {
jsSpec: system.specSelectors.specJson().toJS()
}
let transformedErrors = reduce(errorTransformers, (result, transformer) => {
try {
let newlyTransformedErrors = transformer.transform(result, inputs)
return newlyTransformedErrors.filter(err => !!err) // filter removed errors
} catch(e) {
console.error("Transformer error:", e)
return result
}
}, errors)
return transformedErrors
.filter(err => !!err) // filter removed errors
.map(err => {
if(!err.get("line") && err.get("path")) {
// TODO: re-resolve line number if we've transformed it away
}
return err
})
}
function toTitleCase(str) {
return str
.split("-")
.map(substr => substr[0].toUpperCase() + substr.slice(1))
.join("")
}

View File

@@ -0,0 +1,29 @@
export function transform(errors) {
// JSONSchema refers to the current object being validated
// as 'instance'. This isn't helpful to users, so we remove it.
return errors
.map(err => {
let seekStr = "is not of a type(s)"
let i = err.get("message").indexOf(seekStr)
if(i > -1) {
let types = err.get("message").slice(i + seekStr.length).split(",")
return err.set("message", err.get("message").slice(0, i) + makeNewMessage(types))
} else {
return err
}
})
}
function makeNewMessage(types) {
return types.reduce((p, c, i, arr) => {
if(i === arr.length - 1 && arr.length > 1) {
return p + "or " + c
} else if(arr[i+1] && arr.length > 2) {
return p + c + ", "
} else if(arr[i+1]) {
return p + c + " "
} else {
return p + c
}
}, "should be a")
}

View File

@@ -0,0 +1,60 @@
import get from "lodash/get"
import last from "lodash/get"
import { fromJS, List } from "immutable"
export function transform(errors, { jsSpec }) {
// LOOK HERE THIS TRANSFORMER IS CURRENTLY DISABLED 😃
// TODO: finish implementing, fix flattening problem
/* eslint-disable no-unreachable */
return errors
// JSONSchema gives us very little to go on
let searchStr = "is not exactly one from <#/definitions/parameter>,<#/definitions/jsonReference>"
return errors
.map(err => {
let message = err.get("message")
let isParameterOneOfError = message.indexOf(searchStr) > -1
if(isParameterOneOfError) {
// try to find what's wrong
return createTailoredParameterError(err, jsSpec)
} else {
return err
}
})
.flatten(true) // shallow Immutable flatten
}
const VALID_IN_VALUES = ["path", "query", "header", "body", "formData"]
const VALID_COLLECTIONFORMAT_VALUES = ["csv", "ssv", "tsv", "pipes", "multi"]
function createTailoredParameterError(err, jsSpec) {
let newErrs = []
let parameter = get(jsSpec, err.get("path"))
// find addressable cases
if(parameter.in && VALID_IN_VALUES.indexOf(parameter.in) === -1) {
let message = `Wrong value for the "in" keyword. Expected one of: ${VALID_IN_VALUES.join(", ")}.`
newErrs.push({
message,
path: err.get("path") + ".in",
type: "spec",
source: "schema",
level: "error"
})
}
if(parameter.collectionFormat && VALID_COLLECTIONFORMAT_VALUES.indexOf(parameter.collectionFormat) === -1) {
let message = `Wrong value for the "collectionFormat" keyword. Expected one of: ${VALID_COLLECTIONFORMAT_VALUES.join(", ")}.`
newErrs.push({
message,
path: err.get("path") + ".collectionFormat",
type: "spec",
source: "schema",
level: "error"
})
}
return newErrs.length ? fromJS(newErrs) : err // fall back to making no changes
}

View File

@@ -0,0 +1,10 @@
export function transform(errors) {
return errors
.map(err => {
return err.set("message", removeSubstring(err.get("message"), "instance."))
})
}
function removeSubstring(str, substr) {
return str.replace(new RegExp(substr, "g"), "")
}

View File

@@ -0,0 +1,15 @@
import makeReducers from "./reducers"
import * as actions from "./actions"
import * as selectors from "./selectors"
export default function(system) {
return {
statePlugins: {
err: {
reducers: makeReducers(system),
actions,
selectors
}
}
}
}

View File

@@ -0,0 +1,68 @@
import {
NEW_THROWN_ERR,
NEW_THROWN_ERR_BATCH,
NEW_SPEC_ERR,
NEW_AUTH_ERR,
CLEAR
} from "./actions"
import reject from "lodash/reject"
import Im, { fromJS, List } from "immutable"
import transformErrors from "./error-transformers/hook"
let DEFAULT_ERROR_STRUCTURE = {
// defaults
line: 0,
level: "error",
message: "Unknown error"
}
export default function(system) {
return {
[NEW_THROWN_ERR]: (state, { payload }) => {
let error = Object.assign(DEFAULT_ERROR_STRUCTURE, payload, {type: "thrown"})
return state
.update("errors", errors => (errors || List()).push( fromJS( error )) )
.update("errors", errors => transformErrors(errors, system.getSystem()))
},
[NEW_THROWN_ERR_BATCH]: (state, { payload }) => {
payload = payload.map(err => {
return fromJS(Object.assign(DEFAULT_ERROR_STRUCTURE, err, { type: "thrown" }))
})
return state
.update("errors", errors => (errors || List()).concat( fromJS( payload )) )
.update("errors", errors => transformErrors(errors, system.getSystem()))
},
[NEW_SPEC_ERR]: (state, { payload }) => {
let error = fromJS(payload)
error = error.set("type", "spec")
return state
.update("errors", errors => (errors || List()).push( fromJS(error)).sortBy(err => err.get("line")) )
.update("errors", errors => transformErrors(errors, system.getSystem()))
},
[NEW_AUTH_ERR]: (state, { payload }) => {
let error = fromJS(Object.assign({}, payload))
error = error.set("type", "auth")
return state
.update("errors", errors => (errors || List()).push( fromJS(error)) )
.update("errors", errors => transformErrors(errors, system.getSystem()))
},
[CLEAR]: (state, { payload }) => {
if(!payload) {
return
}
// TODO: Rework, to use immutable only, no need for lodash
let newErrors = Im.fromJS(reject((state.get("errors") || List()).toJS(), payload))
return state.merge({
errors: newErrors
})
}
}
}

View File

@@ -0,0 +1,15 @@
import { List } from "immutable"
import { createSelector } from "reselect"
const state = state => state
export const allErrors = createSelector(
state,
err => err.get("errors", List())
)
export const lastError = createSelector(
allErrors,
all => all.last()
)