From 4259171e786080429571c4e6e75879c92187a39d Mon Sep 17 00:00:00 2001 From: Matt Fellows Date: Tue, 5 Dec 2017 12:44:44 +1100 Subject: [PATCH] feat(matching): add a number of common matchers to DSL Added the following matchers: * `boolean` - Match a boolean value (using equality) * `integer` - Will match all numbers that are integers (both ints and longs) * `decimal` - Will match all real numbers (floating point and decimal) * `hexadecimal` - Will match all hexadecimal encoded strings * `iso8601Date` - Will match string containing basic ISO8601 dates (e.g. 2016-01-01) * `iso8601DateTime` - Will match string containing ISO 8601 formatted dates (e.g. 2015-08-06T16:53:10+01:00) * `iso8601DateTimeWithMillis` - Will match string containing ISO 8601 formatted dates, enforcing millisecond precision (e.g. 2015-08-06T16:53:10.123+01:00) * `rfc3339Timestamp` - Will match a string containing an RFC3339 formatted timestamp (e.g. Mon, 31 Oct 2016 15:21:41 -0400) * `iso8601Time` - Will match string containing times (e.g. T22:44:30.652Z) * `ipv4Address` - Will match string containing IP4 formatted address * `ipv6Address` - Will match string containing IP6 formatted address * `uuid` - Will match strings containing UUIDs See #120. --- README.md | 95 ++++++++++------- src/dsl/matchers.spec.ts | 225 +++++++++++++++++++++++++++++++++++++-- src/dsl/matchers.ts | 80 +++++++++----- 3 files changed, 328 insertions(+), 72 deletions(-) diff --git a/README.md b/README.md index 3620abcfb..241f042c2 100644 --- a/README.md +++ b/README.md @@ -40,10 +40,11 @@ how to get going. - [API with Authorization](#api-with-authorization) - [Publishing Verification Results to a Pact Broker](#publishing-verification-results-to-a-pact-broker) - [Publishing Pacts to a Broker](#publishing-pacts-to-a-broker) - - [Flexible Matching](#flexible-matching) - - [Match by regular expression](#match-by-regular-expression) + - [Matching](#matching) + - [Match common formats](#match-common-formats) - [Match based on type](#match-based-on-type) - [Match based on arrays](#match-based-on-arrays) + - [Match by regular expression](#match-by-regular-expression) - [Tutorial (60 minutes)](#tutorial-60-minutes) - [Examples](#examples) - [Using Pact in non-Node environments](#using-pact-in-non-node-environments) @@ -314,49 +315,34 @@ pact.publishPacts(opts)).then(function () { }); ``` -### Flexible Matching +### Matching + +Matching makes your tests more expressive making your tests less brittle. -Flexible matching makes your tests more expressive making your tests less brittle. Rather than use hard-coded values which must then be present on the Provider side, you can use regular expressions and type matches on objects and arrays to validate the structure of your APIs. -Read more about using regular expressions and type based matching [here][https://github.com/realestate-com-au/pact/wiki/Regular-expressions-and-type-matching-with-Pact] before continuing. - _NOTE: Make sure to start the mock service via the `Pact` declaration with the option `specification: 2` to get access to these features._ -For simplicity, we alias the main matches to make our code more readable: - -#### Match by regular expression - -The underlying mock service is written in Ruby, so the regular expression must be in a Ruby format, not a Javascript format. - -```javascript -const { term } = pact - -provider.addInteraction({ - state: 'Has some animals', - uponReceiving: 'a request for an animal', - withRequest: { - method: 'GET', - path: '/animals/1' - }, - willRespondWith: { - status: 200, - headers: { - 'Content-Type': 'application/json; charset=utf-8' - }, - body: { - id: 100, - name: "billy", - 'gender': term({ - matcher: 'F|M', - generate: 'F' - }), - } - } -}) -``` +#### Match common formats + +Often times, you find yourself having to re-write regular expressions for common formats. We've created a number of them for you to save you the time: + +| method | description | +|--------|-------------| +| `boolean` | Match a boolean value (using equality) | +| `integer` | Will match all numbers that are integers (both ints and longs)| +| `decimal` | Will match all real numbers (floating point and decimal)| +| `hexadecimal` | Will match all hexadecimal encoded strings | +| `iso8601Date` | Will match string containing basic ISO8601 dates (e.g. 2016-01-01)| +| `iso8601DateTime` | Will match string containing ISO 8601 formatted dates (e.g. 2015-08-06T16:53:10+01:00)| +| `iso8601DateTimeWithMillis` | Will match string containing ISO 8601 formatted dates, enforcing millisecond precision (e.g. 2015-08-06T16:53:10.123+01:00)| +| `rfc3339Timestamp` | Will match a string containing an RFC3339 formatted timestapm (e.g. Mon, 31 Oct 2016 15:21:41 -0400)| +| `iso8601Time` | Will match string containing times (e.g. T22:44:30.652Z)| +| `ipv4Address` | Will match string containing IP4 formatted address | +| `ipv6Address` | Will match string containing IP6 formatted address | +| `uuid` | Will match strings containing UUIDs | #### Match based on type @@ -452,6 +438,39 @@ provider.addInteraction({ }) ``` +#### Match by regular expression + +If none of the above matchers or formats work, you can write your own regex matcher. + +The underlying mock service is written in Ruby, so the regular expression must be in a Ruby format, not a Javascript format. + +```javascript +const { term } = pact + +provider.addInteraction({ + state: 'Has some animals', + uponReceiving: 'a request for an animal', + withRequest: { + method: 'GET', + path: '/animals/1' + }, + willRespondWith: { + status: 200, + headers: { + 'Content-Type': 'application/json; charset=utf-8' + }, + body: { + id: 100, + name: "billy", + 'gender': term({ + matcher: 'F|M', + generate: 'F' + }), + } + } +}) +``` + ## Tutorial (60 minutes) Learn everything in Pact JS in 60 minutes: https://github.com/DiUS/pact-workshop-js diff --git a/src/dsl/matchers.spec.ts b/src/dsl/matchers.spec.ts index 38e5ba1f8..e1d9f7ec4 100644 --- a/src/dsl/matchers.spec.ts +++ b/src/dsl/matchers.spec.ts @@ -1,12 +1,39 @@ import * as chai from 'chai'; const expect = require('chai').expect; import { Interaction } from './interaction'; -import { term, somethingLike, eachLike } from './matchers'; +import { + term, somethingLike, eachLike, validateExample, + ISO8601_DATE_FORMAT, uuid, ipv4Address, ipv6Address, hexadecimal, + boolean, integer, decimal, rfc3339Timestamp, iso8601Date, iso8601DateTime, iso8601Time, + iso8601DateTimeWithMillis +} from './matchers'; describe('Matcher', () => { + describe('#validateExample', () => { + describe('when given a valid regex', () => { + describe('and a matching example', () => { + it('should return true', () => { + expect(validateExample('2010-01-01', ISO8601_DATE_FORMAT)).to.eql(true); + }) + }); + describe('and a failing example', () => { + it('should return false', () => { + expect(validateExample('not a date', ISO8601_DATE_FORMAT)).to.eql(false); + }) + }); + }); + describe('when given an invalid regex', () => { + it('should return an error', () => { + expect(() => { + validateExample('', 'abc(') + }).to.throw(Error); + }) + }); + }); + describe('#term', () => { - describe('when provided a term', () => { + describe('when given a valid regular expression and example', () => { it('should return a serialized Ruby object', () => { const expected = { 'json_class': 'Pact::Term', @@ -29,7 +56,7 @@ describe('Matcher', () => { }); }); - describe('when not provided with a valid term', () => { + describe('when not provided with a valid expression', () => { const createTheTerm = function (badArg: any) { return () => { term(badArg); @@ -37,8 +64,8 @@ describe('Matcher', () => { }; describe('when no term is provided', () => { - it.skip('should throw an Error', () => { - // expect(createTheTerm()).to.throw(Error) + it('should throw an Error', () => { + expect(createTheTerm.call({})).to.throw(Error) }); }); @@ -51,6 +78,17 @@ describe('Matcher', () => { }); }); }); + + describe('when given an example that doesn\'t match the regular expression', () => { + it('should fail with an error', () => { + expect(() => { + term({ + generate: 'abc', + matcher: ISO8601_DATE_FORMAT + }) + }).to.throw(Error); + }); + }); }); describe('#somethingLike', () => { @@ -74,8 +112,8 @@ describe('Matcher', () => { }; describe('when no value is provided', () => { - it.skip('`should throw an Error', () => { - // expect(createTheValue()).to.throw(Error) + it('`should throw an Error', () => { + expect(createTheValue.call({})).to.throw(Error) }); }); @@ -300,4 +338,177 @@ describe('Matcher', () => { }); }); }); + + describe('#uuid', () => { + describe('when given a valid UUID', () => { + it('should not fail', () => { + expect(uuid('ce118b6e-d8e1-11e7-9296-cec278b6b50a')).to.be.an('object') + expect(uuid()).to.be.an('object') + }) + }); + describe('when given an invalid UUID', () => { + it('should return an error', () => { + expect(() => { + uuid('abc'); + }).to.throw(Error); + }); + }); + }); + + describe('#ipv4Address', () => { + describe('when given a valid ipv4Address', () => { + it('should not fail', () => { + expect(ipv4Address('127.0.0.1')).to.be.an('object') + expect(ipv4Address()).to.be.an('object') + }) + }); + describe('when given an invalid ipv4Address', () => { + it('should return an error', () => { + expect(() => { + ipv4Address('abc'); + }).to.throw(Error); + }); + }); + }); + + describe('#ipv6Address', () => { + describe('when given a valid ipv6Address', () => { + it('should not fail', () => { + expect(ipv6Address('::1')).to.be.an('object') + expect(ipv6Address('2001:0db8:85a3:0000:0000:8a2e:0370:7334')).to.be.an('object') + expect(ipv6Address()).to.be.an('object') + }) + }); + describe('when given an invalid ipv6Address', () => { + it('should return an error', () => { + expect(() => { + ipv6Address('abc'); + }).to.throw(Error); + }); + }); + }); + + describe('#hexadecimal', () => { + describe('when given a valid hexadecimal', () => { + it('should not fail', () => { + expect(hexadecimal('6F')).to.be.an('object') + expect(hexadecimal()).to.be.an('object') + }) + }); + describe('when given an invalid hexadecimal', () => { + it('should return an error', () => { + expect(() => { + hexadecimal('x1'); + }).to.throw(Error); + }); + }); + }); + + describe('#boolean', () => { + describe('when used it should create a JSON object', () => { + it('should not fail', () => { + expect(boolean()).to.be.an('object') + }) + }); + }); + + describe('#decimal', () => { + describe('when given a valid decimal', () => { + it('should not fail', () => { + expect(decimal(10.1)).to.be.an('object') + expect(decimal()).to.be.an('object') + }) + }); + }); + + describe('#integer', () => { + describe('when given a valid integer', () => { + it('should not fail', () => { + expect(integer(10)).to.be.an('object') + expect(integer()).to.be.an('object') + }) + }); + }); + + describe('Date Matchers', () => { + describe('#rfc3339Timestamp', () => { + describe('when given a valid rfc3339Timestamp', () => { + it('should not fail', () => { + expect(rfc3339Timestamp('Mon, 31 Oct 2016 15:21:41 -0400')).to.be.an('object') + expect(rfc3339Timestamp()).to.be.an('object') + }) + }); + describe('when given an invalid rfc3339Timestamp', () => { + it('should return an error', () => { + expect(() => { + rfc3339Timestamp('abc'); + }).to.throw(Error); + }); + }); + }); + + describe('#iso8601Time', () => { + describe('when given a valid iso8601Time', () => { + it('should not fail', () => { + expect(iso8601Time('T22:44:30.652Z')).to.be.an('object') + expect(iso8601Time()).to.be.an('object') + }) + }); + describe('when given an invalid iso8601Time', () => { + it('should return an error', () => { + expect(() => { + iso8601Time('abc'); + }).to.throw(Error); + }); + }); + }); + + describe('#iso8601Date', () => { + describe('when given a valid iso8601Date', () => { + it('should not fail', () => { + expect(iso8601Date('2017-12-05')).to.be.an('object') + expect(iso8601Date()).to.be.an('object') + }) + }); + describe('when given an invalid iso8601Date', () => { + it('should return an error', () => { + expect(() => { + iso8601Date('abc'); + }).to.throw(Error); + }); + }); + }); + + describe('#iso8601DateTime', () => { + describe('when given a valid iso8601DateTime', () => { + it('should not fail', () => { + expect(iso8601DateTime('2015-08-06T16:53:10+01:00')).to.be.an('object') + expect(iso8601DateTime()).to.be.an('object') + }) + }); + describe('when given an invalid iso8601DateTime', () => { + it('should return an error', () => { + expect(() => { + iso8601DateTime('abc'); + }).to.throw(Error); + }); + }); + }); + + describe('#iso8601DateTimeWithMillis', () => { + describe('when given a valid iso8601DateTimeWithMillis', () => { + it('should not fail', () => { + expect(iso8601DateTimeWithMillis('2015-08-06T16:53:10.123+01:00')).to.be.an('object') + expect(iso8601DateTimeWithMillis()).to.be.an('object') + }) + }); + describe('when given an invalid iso8601DateTimeWithMillis', () => { + it('should return an error', () => { + expect(() => { + iso8601DateTimeWithMillis('abc'); + }).to.throw(Error); + }); + }); + }); + }); }); diff --git a/src/dsl/matchers.ts b/src/dsl/matchers.ts index 27a856707..bf98c9cb7 100644 --- a/src/dsl/matchers.ts +++ b/src/dsl/matchers.ts @@ -6,8 +6,30 @@ import { isNil, isFunction, isUndefined } from 'lodash'; +// Note: The following regexes are Ruby formatted, so attempting to parse as JS without modification is probably not going to work as intended! +export const ISO8601_DATE_FORMAT = '^([\\+-]?\\d{4}(?!\\d{2}\\b))((-?)((0[1-9]|1[0-2])(\\3([12]\\d|0[1-9]|3[01]))?|W([0-4]\\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\\d|[12]\\d{2}|3([0-5]\\d|6[1-6])))?)$'; +export const ISO8601_DATETIME_FORMAT = '^\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d([+-][0-2]\\d:[0-5]\\d|Z)$'; +export const ISO8601_DATETIME_WITH_MILLIS_FORMAT = '^\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d\\.\\d{3}([+-][0-2]\\d:[0-5]\\d|Z)$'; +export const ISO8601_TIME_FORMAT = '^(T\\d\\d:\\d\\d(:\\d\\d)?(\\.\\d+)?(([+-]\\d\\d:\\d\\d)|Z)?)?$'; +export const RFC3339_TIMESTAMP_FORMAT = '^(Mon|Tue|Wed|Thu|Fri|Sat|Sun),\\s\\d{2}\\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\\s\\d{4}\\s\\d{2}:\\d{2}:\\d{2}\\s(\\+|-)\\d{4}$'; +export const UUID_V4_FORMAT = '^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$'; +export const IPV4_FORMAT = '^(\\d{1,3}\\.)+\\d{1,3}$'; +export const IPV6_FORMAT = '^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$'; +export const HEX_FORMAT = '^[0-9a-fA-F]+$'; + /** - * The term matcher. + * Validates the given example against the regex. + * + * @param example Example to use in the matcher. + * @param matcher Regular expression to check. + */ +export function validateExample(example: string, matcher: string): boolean { + // Note we escape the double \\ as these get sent over the wire as JSON + return new RegExp(matcher.replace('\\\\', '\\')).test(example); +} + +/** + * The term matcher. Also aliased to 'regex' for discoverability. * @param {Object} opts * @param {string} opts.generate - a value to represent the matched String * @param {string} opts.matcher - a Regex representing the value @@ -20,6 +42,10 @@ export function term(opts: { generate: string, matcher: string }) { throw new Error('Error creating a Pact Term. Please provide an object containing "generate" and "matcher" properties'); } + if (!validateExample(generate, matcher)) { + throw new Error(`Example '${generate}' does not match provided regular expression '${matcher}'`); + } + return { 'json_class': 'Pact::Term', 'data': { @@ -34,13 +60,13 @@ export function term(opts: { generate: string, matcher: string }) { } /** - * UUIDv4 matcher. + * UUID v4 matcher. * @param {string} uuuid - a UUID to use as an example. */ -export function uuidV4(uuid?: string) { +export function uuid(uuid?: string) { return term({ generate: uuid || 'ce118b6e-d8e1-11e7-9296-cec278b6b50a', - matcher: '/^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$/' + matcher: UUID_V4_FORMAT }); } @@ -48,10 +74,10 @@ export function uuidV4(uuid?: string) { * IPv4 matcher. * @param {string} ip - an IPv4 address to use as an example. Defaults to `127.0.0.13` */ -export function ipv4(ip?: string) { +export function ipv4Address(ip?: string) { return term({ generate: ip || '127.0.0.13', - matcher: '(\\d{1,3}\\.)+\\d{1,3}' + matcher: IPV4_FORMAT }); } @@ -59,47 +85,47 @@ export function ipv4(ip?: string) { * IPv6 matcher. * @param {string} ip - an IPv6 address to use as an example. Defaults to '::ffff:192.0.2.128' */ -export function ipv6(ip?: string) { +export function ipv6Address(ip?: string) { return term({ generate: ip || '::ffff:192.0.2.128', - matcher: '(\\A([0-9a-f]{1,4}:){1,1}(:[0-9a-f]{1,4}){1,6}\\Z)|(\\A([0-9a-f]{1,4}:){1,2}(:[0-9a-f]{1,4}){1,5}\\Z)|(\\A([0-9a-f]{1,4}:){1,3}(:[0-9a-f]{1,4}){1,4}\\Z)|(\\A([0-9a-f]{1,4}:){1,4}(:[0-9a-f]{1,4}){1,3}\\Z)|(\\A([0-9a-f]{1,4}:){1,5}(:[0-9a-f]{1,4}){1,2}\\Z)|(\\A([0-9a-f]{1,4}:){1,6}(:[0-9a-f]{1,4}){1,1}\\Z)|(\\A(([0-9a-f]{1,4}:){1,7}|:):\\Z)|(\\A:(:[0-9a-f]{1,4}){1,7}\\Z)|(\\A((([0-9a-f]{1,4}:){6})(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3})\\Z)|(\\A(([0-9a-f]{1,4}:){5}[0-9a-f]{1,4}:(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3})\\Z)|(\\A([0-9a-f]{1,4}:){5}:[0-9a-f]{1,4}:(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\Z)|(\\A([0-9a-f]{1,4}:){1,1}(:[0-9a-f]{1,4}){1,4}:(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\Z)|(\\A([0-9a-f]{1,4}:){1,2}(:[0-9a-f]{1,4}){1,3}:(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\Z)|(\\A([0-9a-f]{1,4}:){1,3}(:[0-9a-f]{1,4}){1,2}:(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\Z)|(\\A([0-9a-f]{1,4}:){1,4}(:[0-9a-f]{1,4}){1,1}:(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\Z)|(\\A(([0-9a-f]{1,4}:){1,5}|:):(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\Z)|(\\A:(:[0-9a-f]{1,4}){1,5}:(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\Z)' + matcher: IPV6_FORMAT }); } /** - * ISO8601 DateTime matcher - * @param {string} date - an ISO8601 Date and Time string, e.g. 2013-02-04T22:44:30.652Z + * ISO8601 DateTime matcher. + * @param {string} date - an ISO8601 Date and Time string + * e.g. 2015-08-06T16:53:10+01:00 are valid */ export function iso8601DateTime(date?: string) { return term({ - generate: date || '2013-02-01T22:44:30.652Z', - matcher: '^([\\+-]?\\d{4}(?!\\d{2}\\b))((-?)((0[1-9]|1[0-2])(\\3([12]\\d|0[1-9]|3[01]))?|W([0-4]\\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\\d|[12]\\d{2}|3([0-5]\\d|6[1-6])))([T\\s]((([01]\\d|2[0-3])((:?)[0-5]\\d)?|24\\:?00)([\\.,]\\d+(?!:))?)?(\\17[0-5]\\d([\\.,]\\d+)?)?([zZ]|([\\+-])([01]\\d|2[0-3]):?([0-5]\\d)?)?)?)?$' + generate: date || '2015-08-06T16:53:10+01:00', + matcher: ISO8601_DATETIME_FORMAT }); } /** - * ISO8601 Date matcher. - * @param {string} date - a basic yyyy-MM-dd date string e.g. 2000-09-31 + * ISO8601 DateTime matcher with required millisecond precision + * @param {string} date - an ISO8601 Date and Time string, e.g. 2015-08-06T16:53:10.123+01:00 */ -export function iso8601Date(date?: string) { +export function iso8601DateTimeWithMillis(date?: string) { return term({ - generate: date || '2013-02-01', - matcher: '^([\\+-]?\\d{4}(?!\\d{2}\\b))((-?)((0[1-9]|1[0-2])(\\3([12]\\d|0[1-9]|3[01]))?|W([0-4]\\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\\d|[12]\\d{2}|3([0-5]\\d|6[1-6])))?)' + generate: date || '2015-08-06T16:53:10.123+01:00', + matcher: ISO8601_DATETIME_WITH_MILLIS_FORMAT }); } /** - * RFC822 Date matcher. + * ISO8601 Date matcher. * @param {string} date - a basic yyyy-MM-dd date string e.g. 2000-09-31 */ export function iso8601Date(date?: string) { return term({ generate: date || '2013-02-01', - matcher: '(?x)(Mon|Tue|Wed|Thu|Fri|Sat|Sun),\\s\\d{2}\\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\\s\\d{4}\\s\\d{2}:\\d{2}:\\d{2}\\s(\\+|-)\\d{4}' + matcher: ISO8601_DATE_FORMAT }); } - /** * ISO8601 Time Matcher, matches a pattern of the format "'T'HH:mm:ss". * @param {string} date - a ISO8601 formatted time string e.g. T22:44:30.652Z @@ -107,18 +133,18 @@ export function iso8601Date(date?: string) { export function iso8601Time(time?: string) { return term({ generate: time || 'T22:44:30.652Z', - matcher: '^(T\\d\\d:\\d\\d(:\\d\\d)?(\\.\\d+)?(([+-]\\d\\d:\\d\\d)|Z)?)?$' + matcher: ISO8601_TIME_FORMAT }); } /** * RFC3339 Timestamp matcher, a subset of ISO8609 - * @param {string} date - an RFC3339 Date and Time string, e.g. 2013-02-04T22:44:30.652Z + * @param {string} date - an RFC3339 Date and Time string, e.g. Mon, 31 Oct 2016 15:21:41 -0400 */ export function rfc3339Timestamp(timestamp?: string) { return term({ - generate: date || '2013-02-01T22:44:30.652Z', - matcher: '^([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\\.[0-9]+)?(([Zz])|([\+|\-]([01][0-9]|2[0-3]):[0-5][0-9]))$' + generate: timestamp || 'Mon, 31 Oct 2016 15:21:41 -0400', + matcher: RFC3339_TIMESTAMP_FORMAT }); } @@ -126,16 +152,16 @@ export function rfc3339Timestamp(timestamp?: string) { * Hexadecimal Matcher. * @param {string} hex - a hex value. */ -export function Hex(hex?: string) { +export function hexadecimal(hex?: string) { return term({ generate: hex || '3F', - matcher: '[0-9a-fA-F]+' + matcher: HEX_FORMAT }); } /** * Decimal Matcher. - * @param {float} hex - a hex value. + * @param {float} float - a decimal value. */ export function decimal(float?: number) { return somethingLike(float || 13.01);