Repository: TechnologyAdvice/obey
Pull request #19 (2016-03-01 19:47:07)

Adding more built-in types


Patch:
diff --git a/src/types/README.md b/src/types/README.md
index 13a43dc..88f9265 100644
--- a/src/types/README.md
+++ b/src/types/README.md
@@ -30,9 +30,19 @@ Checks for native type `number`
 
 Checks for native type `object`
 
+## Phone
+
+Checks for valid phone numbers:
+
+* `phone`: Default, valid with or without separators
+* `phone:numeric`: Check value for numeric phone number, 7-10 digits
+
 ## String
 
-Checks for native type `string`
+Checks for valid string types:
+
+* `string`: Default, `typeof` should be `string`
+* `string:alphanumeric`: Checks value contains only alpha-numeric characters
 
 ## URL
 
@@ -40,4 +50,12 @@ Checks for valid URL
 
 ## UUID
 
-Checks for valid URL
\ No newline at end of file
+Checks for valid v4 UUID
+
+## Zip
+
+Checks for valid zip/postal codes:
+
+* `zip`: Default, checks generic postal code
+* `zip:us`: Checks US zip code format
+* `zip:ca`: Checks Canadian zip code format
\ No newline at end of file
diff --git a/src/types/email.js b/src/types/email.js
index 0696e5a..5744696 100644
--- a/src/types/email.js
+++ b/src/types/email.js
@@ -1,7 +1,9 @@
 const email = {
+  _regex: {
+    default: /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/
+  },
   default: context => {
-    const emailRegEx = /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/
-    if (!emailRegEx.test(context.value)) {
+    if (!context.value.match(email._regex.default)) {
       context.fail('Value must be a valid email')
     }
   }
diff --git a/src/types/ip.js b/src/types/ip.js
index a687fef..05cfcb2 100644
--- a/src/types/ip.js
+++ b/src/types/ip.js
@@ -1,13 +1,15 @@
 const ip = {
+  _regex: {
+    v4: /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,
+    v6: /^((?:[0-9A-Fa-f]{1,4}))((?::[0-9A-Fa-f]{1,4}))*::((?:[0-9A-Fa-f]{1,4}))((?::[0-9A-Fa-f]{1,4}))*|((?:[0-9A-Fa-f]{1,4}))((?::[0-9A-Fa-f]{1,4})){7}$/
+  },
   v4: context => {
-    const ipv4RegEx = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
-    if (!ipv4RegEx.test(context.value)) {
+    if (!context.value.match(ip._regex.v4)) {
       context.fail('Value must be a valid IPv4 address')
     }
   },
   v6: context => {
-    const ipv6RegEx = /^((?:[0-9A-Fa-f]{1,4}))((?::[0-9A-Fa-f]{1,4}))*::((?:[0-9A-Fa-f]{1,4}))((?::[0-9A-Fa-f]{1,4}))*|((?:[0-9A-Fa-f]{1,4}))((?::[0-9A-Fa-f]{1,4})){7}$/
-    if (!ipv6RegEx.test(context.value)) {
+    if (!context.value.match(ip._regex.v6)) {
       context.fail('Value must be a valid IPv6 address')
     }
   },
diff --git a/src/types/phone.js b/src/types/phone.js
new file mode 100644
index 0000000..c7b6429
--- /dev/null
+++ b/src/types/phone.js
@@ -0,0 +1,18 @@
+const phone = {
+  _regex: {
+    default: /^\(?\d{3}\)?([-. ]?)\d{3}\1\d{4}$/,
+    numeric: /\d{7,10}/
+  },
+  default: context => {
+    if (!context.value.match(phone._regex.default)) {
+      context.fail('Value must be a valid phone number')
+    }
+  },
+  numeric: context => {
+    if (!context.value.match(phone._regex.numeric)) {
+      context.fail('Value must be a numeric phone number')
+    }
+  }
+}
+
+export default phone
diff --git a/src/types/string.js b/src/types/string.js
index e2f6036..f0fb5d9 100644
--- a/src/types/string.js
+++ b/src/types/string.js
@@ -1,8 +1,16 @@
 const string = {
+  _regex: {
+    alphanumeric: /^[a-zA-Z0-9]*$/
+  },
   default: context => {
     if (typeof context.value !== 'string') {
       context.fail('Value must be a string')
     }
+  },
+  alphanumeric: context => {
+    if (!context.value.match(string._regex.alphanumeric)) {
+      context.fail('Value must contain only letters and/or numbers')
+    }
   }
 }
 
diff --git a/src/types/url.js b/src/types/url.js
index b60992b..320f9f6 100644
--- a/src/types/url.js
+++ b/src/types/url.js
@@ -1,7 +1,9 @@
 const url = {
+  _regex: {
+    default: /^((http|https|ftp):\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/
+  },
   default: context => {
-    const urlRegEx = /^((http|https|ftp):\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/
-    if (!urlRegEx.test(context.value)) {
+    if (!context.value.match(url._regex.default)) {
       context.fail('Value must be a valid URL')
     }
   }
diff --git a/src/types/uuid.js b/src/types/uuid.js
index 3a590b6..55b4ac4 100644
--- a/src/types/uuid.js
+++ b/src/types/uuid.js
@@ -1,7 +1,9 @@
 const uuid = {
+  _regex: {
+    default: /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/
+  },
   default: context => {
-    const uuidRegEx = /[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/
-    if (!uuidRegEx.test(context.value)) {
+    if (!context.value.match(uuid._regex.default)) {
       context.fail('Value must be a valid UUID')
     }
   }
diff --git a/src/types/zip.js b/src/types/zip.js
new file mode 100644
index 0000000..150e26a
--- /dev/null
+++ b/src/types/zip.js
@@ -0,0 +1,24 @@
+const zip = {
+  _regex: {
+    default: /^(?:[A-Z0-9]+([- ]?[A-Z0-9]+)*)?$/,
+    us: /^([0-9]{5})(?:[-\s]*([0-9]{4}))?$/,
+    ca: /^([A-Z][0-9][A-Z])\s*([0-9][A-Z][0-9])$/
+  },
+  default: context => {
+    if (!context.value.match(zip._regex.default)) {
+      context.fail('Value must be a valid zip code')
+    }
+  },
+  us: context => {
+    if (!context.value.match(zip._regex.us)) {
+      context.fail('Value must be a valid US zip code')
+    }
+  },
+  ca: context => {
+    if (!context.value.match(zip._regex.ca)) {
+      context.fail('Value must be a valid Canadian zip code')
+    }
+  }
+}
+
+export default zip


Test patch:
diff --git a/test/src/types/phone.spec.js b/test/src/types/phone.spec.js
new file mode 100644
index 0000000..b5da29a
--- /dev/null
+++ b/test/src/types/phone.spec.js
@@ -0,0 +1,37 @@
+/* global describe, it, expect, sinon */
+import phone from 'src/types/phone'
+
+describe('type:phone', () => {
+  it('calls context.fail if value is not a valid phone number', () => {
+    const context = {
+      value: 'foo',
+      fail: sinon.spy()
+    }
+    phone.default(context)
+    expect(context.fail).to.be.calledWith('Value must be a valid phone number')
+  })
+  it('does not call context fail if value is a valid phone number', () => {
+    const context = {
+      value: '(555)-123-4567',
+      fail: sinon.spy()
+    }
+    phone.default(context)
+    expect(context.fail).to.not.be.called
+  })
+  it('calls context.fail if value is not a valid numeric phone number', () => {
+    const context = {
+      value: 'foo',
+      fail: sinon.spy()
+    }
+    phone.numeric(context)
+    expect(context.fail).to.be.calledWith('Value must be a numeric phone number')
+  })
+  it('does not call context fail if value is a valid phone number', () => {
+    const context = {
+      value: '5551234567',
+      fail: sinon.spy()
+    }
+    phone.numeric(context)
+    expect(context.fail).to.not.be.called
+  })
+})
diff --git a/test/src/types/string.spec.js b/test/src/types/string.spec.js
index a908bb9..b67278f 100644
--- a/test/src/types/string.spec.js
+++ b/test/src/types/string.spec.js
@@ -18,4 +18,20 @@ describe('type:string', () => {
     string.default(context)
     expect(context.fail).to.not.be.called
   })
+  it('calls context.fail if type is not an alphanumberic string', () => {
+    const context = {
+      value: 'abc$#',
+      fail: sinon.spy()
+    }
+    string.alphanumeric(context)
+    expect(context.fail).to.be.calledWith('Value must contain only letters and/or numbers')
+  })
+  it('does not call context.fail if type is an alphanumeric string', () => {
+    const context = {
+      value: 'abc123',
+      fail: sinon.spy()
+    }
+    string.alphanumeric(context)
+    expect(context.fail).to.not.be.called
+  })
 })
diff --git a/test/src/types/zip.spec.js b/test/src/types/zip.spec.js
new file mode 100644
index 0000000..573e726
--- /dev/null
+++ b/test/src/types/zip.spec.js
@@ -0,0 +1,53 @@
+/* global describe, it, expect, sinon */
+import zip from 'src/types/zip'
+
+describe('type:zip', () => {
+  it('calls context.fail if value is not a valid zip code', () => {
+    const context = {
+      value: 'foo',
+      fail: sinon.spy()
+    }
+    zip.default(context)
+    expect(context.fail).to.be.calledWith('Value must be a valid zip code')
+  })
+  it('does not call context fail if value is a valid zip code', () => {
+    const context = {
+      value: '61029',
+      fail: sinon.spy()
+    }
+    zip.default(context)
+    expect(context.fail).to.not.be.called
+  })
+  it('calls context.fail if value is not a valid US zip code', () => {
+    const context = {
+      value: 'foo',
+      fail: sinon.spy()
+    }
+    zip.us(context)
+    expect(context.fail).to.be.calledWith('Value must be a valid US zip code')
+  })
+  it('does not call context fail if value is a valid US zip code', () => {
+    const context = {
+      value: '61029-1234',
+      fail: sinon.spy()
+    }
+    zip.us(context)
+    expect(context.fail).to.not.be.called
+  })
+  it('calls context.fail if value is not a valid CA zip code', () => {
+    const context = {
+      value: 'foo',
+      fail: sinon.spy()
+    }
+    zip.ca(context)
+    expect(context.fail).to.be.calledWith('Value must be a valid Canadian zip code')
+  })
+  it('does not call context fail if value is a valid US zip code', () => {
+    const context = {
+      value: 'A1A 1A1',
+      fail: sinon.spy()
+    }
+    zip.ca(context)
+    expect(context.fail).to.not.be.called
+  })
+})