Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Customize Error Paths #102

Closed
davidchase opened this issue Apr 26, 2018 · 12 comments
Closed

Customize Error Paths #102

davidchase opened this issue Apr 26, 2018 · 12 comments
Labels
feature ♥ help please Issues that we'd love help solving

Comments

@davidchase
Copy link
Contributor

Hi 👋 really digging this library for validating objects

My only question is about customizing paths when creating types via superstruct

const struct = superstruct({
  types: {
    location: value => {
      const empty = Object.keys(value).reduce((acc, key) => {
        return value[key].state === null && value[key].county === null
          ? acc.concat(key)
          : acc;
      }, []);
      const str = empty.join(", ");
      return str === ""
        ? true
        : `Either state or county for ${str} is required`;
    },
    duplicate: val => {
      const mmap = Object.keys(val).reduce((map, key) => {
        CHECK_LIST.forEach(item => {
          if (val[key][item] !== "" && val[key][item] != null) {
            map.set(`${item}.${val[key][item]}`, `${key}.${item}`);
          }
        });
        return map;
      }, new MultiMap());
      
      const gatherDuplicates = mmap
        .values()
        .reduce((acc, x) => (x.length >= 2 ? acc.concat(x) : acc), []);
        
      const error = `The following keys are duplicates: ${gatherDuplicates.join(", ")}`

      return gatherDuplicates.length > 0 ? error : true;
    }
  }
});

const Location = struct('location&duplicate')

const data = {
  123: {
    state: null,
    county: null
  },
  456: {
    state: null,
    county: null
  }
}

Location.validate(data)

Currently the above code checks if the dynamic keys have either state or county filled in and then if either state or county is a duplicate

Returning custom messages works great but for whatever reason the paths array is always empty which brings me to my question of customizing paths
because i have all of the information i need to create a path but no obvious way of doing, unless I am missing something ?

@davidchase
Copy link
Contributor Author

Would you be opposed to returning an object from a custom type something like {message: '', path: []} ?

@ianstormtaylor
Copy link
Owner

Returning an object is interesting. I’d be down for that if you wanted to make a PR.

I’d generally try to avoid these kinds of setups, but if two values are co-dependent then there’s no other way really. Good solution!

@davidchase
Copy link
Contributor Author

@ianstormtaylor thanks for the quick response

Returning an object is interesting. I’d be down for that if you wanted to make a PR.

Yes i can definitely do a PR

I’d generally try to avoid these kinds of setups, but if two values are co-dependent then there’s no other way really.

I'm open to other ideas, originally i was thinking about returning an array like ['message', ['paths']] but then developers have to remember
which goes first message or array of paths. Or if you think their might be a better solution altogether im up for discussion.

@ianstormtaylor
Copy link
Owner

@davidchase what's the data shape you're trying to assert? It's a bit hard to tell from the code sample above what "correct" data can look like.

@davidchase
Copy link
Contributor Author

so for example we have

const data = {
  123: {
    state: 'PA',
    county: null
  },
  456: {
    state: 'CA',
    county: null
  },
  789: {
    state: 'PA',
    county: null
  }
}

Updated way of having dynamic keys with either state or county filled in
note path works fine here

const State = st.object({ state: 'string', county: 'string|null' })
const County = st.object({ state: 'string|null', county: 'string' })
const StateOrCounty = st.union([State, County])

const EitherStateOrCounty = st.dict(['string', StateOrCounty])

Now i need to validate duplicates based on the original code i had in the OP so in the above case i would want to return and object withpath = [["123", "state"], ["789", "state"]] and some message = these {paths} are duplicates

does this help? finding duplicates still yields a an empty array path which is why i purpose an object with {message,path} for custom types

@ianstormtaylor
Copy link
Owner

Interesting!

So what if you structured it as such:

const State = struct.object({ 
  state: 'string', 
  county: 'string|null' 
})

const County = struct.object({ 
  state: 'string|null', 
  county: 'string' 
})

const StateOrCounty = struct.union([
  State, 
  County
])

const StateOrCountyDict = stuct.dict([
  'string', 
  StateOrCounty
])

const StateOrCountyDictWithoutDuplicates = struct.union([
  StateOrCountyDictionary, 
  'unique_array'
])

Where unique_array is a custom type that ensures uniqueness. It seems like you'd still need to have the ability to pass { message, path } back.

I'm down for that!

@ianstormtaylor ianstormtaylor added feature ♥ help please Issues that we'd love help solving labels Apr 27, 2018
@ianstormtaylor
Copy link
Owner

Although, also consider that you may consider the entire array invalid, instead of considering individual paths invalid. Although I'm down for the PR either way.

@davidchase
Copy link
Contributor Author

const StateOrCountyDictWithoutDuplicates = struct.union([
StateOrCountyDictionary,
'unique_array'
])

Can you explain why its a union? Essentially i need to first validate to see if my object with dynamic keys has either state or county filled in... both fields are allowed. Followed by checking to see if any dynamic key object has duplicate values. In the above scenario 123.state and 789.state are duplicates. So unique_array wouldn't quite work because its all objects.

So i would have thought that would be an & (intersection) vs an | (union), no?

I also like that technically i can just compose like const validate = compose(Duplicates, StateOrCounty) and then validate(data)

ability to pass { message, path } back.

It seems like if i build custom types in superstruct my error.paths will always be empty, no?

Although I'm down for the PR either way.

Would you want to keep string and boolean and now have a return object? or maybe just get rid of string and have the message and path be optional in the object?

@davidchase
Copy link
Contributor Author

started a PR to continue the discussion :)

@ianstormtaylor
Copy link
Owner

@davidchase ah you're right, I meant intersection in that case.

@ianstormtaylor
Copy link
Owner

Done in 0.5.3, thanks @davidchase!

@davidchase
Copy link
Contributor Author

Sure thanks for adding to your library

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature ♥ help please Issues that we'd love help solving
Projects
None yet
Development

No branches or pull requests

2 participants