devtin / duckfficer Goto Github PK
View Code? Open in Web Editor NEWZero-dependencies light-weight library for modeling, validating and sanitizing data 🦆 🐵 👁
Home Page: https://devtin.github.io/duckfficer
License: MIT License
Zero-dependencies light-weight library for modeling, validating and sanitizing data 🦆 🐵 👁
Home Page: https://devtin.github.io/duckfficer
License: MIT License
Would be nice to have some validation sugar for:
autoCast then would:
const NewNumber = new Schema({
type: Number,
min: 0, // just positive numbers
max: 100,
decimalPlaces: 2
})
const err1 = t.throws(() => NewNumber.parse(-0.1))
t.is(err1.message, 'minimum accepted value is 0')
const err2 = t.throws(() => NewNumber.parse(100.1))
t.is(err2.message, 'maximum accepted value is 100')
t.is(NewNumber.parse(1.111), 1.11)
t.is(NewNumber.parse(1.114), 1.11)
t.is(NewNumber.parse(1.115), 1.12)
NewNumber._settings.autoCast = true
t.is(NewNumber.parse('1.114'), 1.11)
Describe the bug
Transformers are not being consistent with the errors they are throwing: seems like only the String
transformer throws a ValidationError
and all other just a regular Error
. This makes inconsistence the entire experience.
To Reproduce
const numberType = new Schema({
type: Number
})
try {
numberType.parse('no')
}
catch (err) {
console.log(err instanceof ValidationError) // => false
}
Expected behavior
All transformers should throw a ValidationError.
const numberType = new Schema({
type: Number
})
try {
numberType.parse('no')
}
catch (err) {
console.log(err instanceof ValidationError) // => true
}
Is your feature request related to a problem? Please describe.
The type auto-cast is a nice feature...
const BooleanSchema = new Schema({
type: Boolean
})
BooleanSchema.parse(5) // => true
const DateSchema = new Schema({
type: Date
})
console.log(BooleanSchema.parse('6/11/1983') instanceof Date) // => true
But sometimes it is necessary to prevent value type's auto-casting, making the validation strict.
Describe the solution you'd like
Maybe introducing a configuration value that all transformers must check prior proceeding with auto-casting and just throw an error when the given value is not what expected.
Describe alternatives you've considered
const BooleanSchema = new Schema({
type: Boolean,
autoCast: false
})
try {
BooleanSchema.parse(5)
} catch (err) {
console.log(err.message) // => Invalid boolean
}
const DateSchema = new Schema({
type: Date,
autoCast: false
})
DateSchema.parse(new Date('6/11/1983 23:11 GMT-0400')) // => Sat Jun 11 1983 23:11:00 GMT-0400 (Eastern Daylight Time)
try {
DateSchema.parse('6/11/1983')
} catch (err) {
console.log(err.message) // => Invalid date
}
As mentioned in #19, it is a nice feature to have!
Originally posted by @devtin in #19 (comment)
@next
branch was never implemented ):
Reduce README scroll by using <details><summary></summary></details>
In order to avoid inconsistent releases, add more tests per type.
Each transformer should have a test covering all possible scenarios.
I know I've been back and forth with this. But feels like there are scenarios where having a sync validation is useful too...
const { SchemaSync } = require('duckfficer')
const SomeSchema = new Schema({
// my schema
name: String
},
{
settings: {
// my initial settings
required: false
}
})
t.notThrows(() => SomeSchema.parse(undefined))
feels like if we are setting a schema with a virtual, we are actually demanding that prop on the final payload...
Transformers.Custom = {
loaders: [{
type: String,
allowEmpty: false,
emptyError: 'Allow empty not valid',
}],
validate (v) {
// I'll still receive an empty value here which is not what I'm expecting
}
}
Is your feature request related to a problem? Please describe.
Sometimes it is useful to allow a property being any of some given types.
Describe the solution you'd like
const UserSchema = new Schema({
name: String,
profilePicture: {
type: [Function, Promise]
}
})
Describe alternatives you've considered
Creating custom transformers for each scenario, but that is pretty nasty I think.
const { Schema } = require('./')
const s = new Schema({
name: String,
address: {
line1: String,
zip: Number
}
})
console.log(s.parse({
name: 'Martin',
papo: null,
address: {
line1: '2451',
zip: 33129,
}
}))
throws:
TypeError: Cannot convert undefined or null to object
Implementation suggested in #6 may be cleaner, hence better to address if the transformers had two methods: one for auto-casting and another for validating.
Transformers = {
Date: {
parse(value) {
// validates given value is of the expected type
},
cast (value) {
// instructions to cast given value (if possible) to expected one
}
}
}
Transformers.Custom = {
loaders: [{
type: String,
allowEmpty: false,
emptyError: 'Allow empty not valid',
}]
parse (v) {
return v.split('').reverse().join('')
}
}
const schema = new Schema('Custom')
schema.parse(undefined) // => Could not resolve given value type in property password. Allowed types are and String
Describe the solution you'd like
Transformers.Promise = {
settings: {
typeError: `Invalid Promise`
},
validate (v) {
// see: https://stackoverflow.com/a/27746324/1064165
if (!(typeof v === 'object' && typeof v.then === 'function')) {
this.throwError(Transformers.Promise.settings.typeError, { value })
}
}
}
In order to improve reach and collaborations.
it would be nice to implement virtuals:
const Order = new Schema({
paymentStatus: {
type: String,
enum: ['processing', 'hold', 'approved', 'rejected', 'refunded'],
default: 'processing'
},
shippingStatus: {
type: String,
enum: ['preparing', 'shipped', 'delivered', 'returned-issued', 'returned']
allowNull: true,
default: null
}
}, {
virtuals: {
get status () {
if (this.paymentStatus === 'approved') {
return this.shippingStatus
}
return this.paymentStatus
}
}
})
const order = Order.parse({})
t.is(order.status, 'processing')
order.paymentStatus = 'approved'
order.status = 'preparing'
new Schema({
name: String
}, {
defaultValues ({ state }) {
return state.thisCondition ? thisDefaultVals : thisOthers
}
})
Issue
const objectParser = new Schema({
type: Object
})
try {
objectParser.parse({ hi: 'hello' })
}
catch (err) {
console.log(err.message) // => v is not defined
}
Suggestion
Not only adding more tests as suggested in #16 will help catch this kind of issues in the future prior performing a release, but errors of this kind can also be addressed by only performing a linting in the development flow.
Describe the bug
The cast option allows to transform a payload prior parsing it.
I started building this library using a synchronous philosophy on purposely. At the beginning I felt by staying away from asynchronous tasks I was making the library more simple and elegant, but soon I was proof wrong.
What I feel right now is that we live in an asynchronous world and there is nothing we can do about it, so we better deal with it. Thinking so synchronously is a wing blocker.
How about:
Transformers.BucketFile = {
loaders: [
{
type: String,
async cast (url) {
if (isFile(url)) {
// upload the file to a bucker and retrieve the url
return (await Bucket.upload(url)).url
}
return url
}
}
],
validate (url) {
if (typeof url !== 'string' || !/^https?:\/\//.test(url)) {
this.throwError(`${ url } is not a valid url
}
}
}
const Product = new Schema({
name: String,
image: 'BucketFile'
})
const productA = await Product.parse({
name: 'Some product',
image: new Buffer({...})
})
console.log(/^https:\/\//.test(productA.image)) // => true
Describe the solution you'd like
const mySchema = new Schema({
topping: {
type: String,
enum: ['cheese', 'ham', 'tomatoes']
}
})
const error = t.throws(() => mySchema.parse({ topping: 'potatoes' }))
t.is(error.errors[0].message, 'Unknown enum option potatoes')
Is your feature request related to a problem? Please describe.
It would be nice that given an array type in the schema, one could also provide the type of value accepted by the array, initializing them in case it can be guessed.
Describe the solution you'd like
const Log = new Schema({
user: String,
lastAccess: {
type: Array,
items: {
type: Date,
autoCast: true
}
}
})
const tinLog = Log.parse({
user: 'tin',
lastAccess: ['6/11/2019', 'Sat Jan 11 2020 17:06:31 GMT-0500 (Eastern Standard Time)']
})
console.log(Array.isArray(tinLog.lastAccess)) // => true
console.log(tinLog.lastAccess.length) // => 2
console.log(tinLog.lastAccess[0] instanceof Date) // => true
console.log(tinLog.lastAccess[1] instanceof Date) // => true
try {
Log.parse({
user: 'tin',
lastAccess: ['6/11/1983', 'What is love?']
})
}
catch (err) {
console.log(err.message) // => Data is not valid
console.log(err.errors[0].message) // => Invalid date
console.log(err.errors[0].field.path) // => lastAccess
console.log(err.errors[0].index) // => 1
}
It would be nice to be able to extend a schema...
const UserSchema = new Schema({
name: String,
email: String,
password: String
})
const ITUserSchema = UserSchema.extend({
phoneNumber: Number,
publicKey: String
})
Just like the property allowNull
allows any property to be set with a null value, there are times when you need a property to be set with any of a given specific list of values.
Maybe introducing a property called allow
where we can allow: [null, '', false]
would be nice!
It would be nice to create a @next
branch in a CD workflow following semantic-versioning in order to automatically manage version releases, as suggested here.
The option should default to true in order not to break the current behavior.
It should validate that a string is not an empty string:
// should validate like
function isEmpty (v) {
return /^[\s]*$/.test(v)
}
The .pop here is causing the types to be lost as the instance fails
Sometimes the default values of a schema are given in an object, making the current way of using it little longer.
const defaultValues = {
name: 'Martin',
address: {
state: 'Florida',
zip: 33129
},
subscribe: true
}
const SomeSchema = new Schema({
name: {
type: String,
default: defaultValues.name
},
address: {
state: {
type: String,
default: defaultValues.address.state
},
zip: {
type: Number,
default: defaultValues.address.zip
},
street: String
},
phoneNumber: Number,
subscribe: {
type: Boolean,
default: defaultValues.subscribe
}
})
I think something like this should be better:
const defaultValues = {
name: 'Martin',
address: {
state: 'Florida',
zip: 33129
},
subscribe: true
}
const SomeSchema = new Schema({
name: String,
address: {
state: String,
zip: Number,
street: String
},
phoneNumber: Number,
subscribe: Boolean
}, { defaultValues })
While performing validation it would be useful to provide a payload to alternatively use in custom validation phases:
const UserSchema = new Schema({
id: BigInt,
name: String,
email: String,
status: {
type: String,
enum: ['user', 'admin'],
default: 'user',
validate(v, payload) {
if (v === 'admin' && (!payload || !payload.user || payload.user.status !== 'admin')) {
this.throwError(`Only admin users can set status to admin`)
}
}
}
})
const payload = {
user: {
id: 11,
name: 'Martin',
email: '[email protected]',
status: 'admin'
}
}
UserSchema.parse({
name: 'Olivia',
email: '[email protected]',
status: 'admin'
}, payload)
Describe the bug
Given an invalid date, the schema validator does not throw an error.
To Reproduce
const dateValidator = new Schema({
type: Date
})
console.log(dateValidator.parse('Some invalid date')) // => Invalid Date
Expected behavior
const dateValidator = new Schema({
type: Date
})
try {
dateValidator.parse('Some invalid date')
} catch (err) {
console.log(err.message) // => Invalid date
}
After using the library for a while, I think the documentation can be improved by organizing it in different categories:
As suggested in #17 we should enforce code linting in the development flow.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.