diff --git a/.gitignore b/.gitignore index 123ae94..05880b6 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,5 @@ build/Release # Dependency directory # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git node_modules + +target/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..4b643c2 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,2 @@ +--- +language: node_js diff --git a/LICENSE b/LICENSE index 8f71f43..099ac17 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright {yyyy} {name of copyright owner} + Copyright 2015 Wave Software Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index deab844..a238485 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,20 @@ # javascript-eid-exceptions EID Runtime Exceptions and Utilities for JavaScript + +## Getting started + +You'll need [node / npm](https://nodejs.org/) and [tsd](http://definitelytyped.org/tsd/) installed globally. To get up and running just enter: + +``` +npm install +tsd install +npm run test +``` + +This will: + +1. Download the npm packages you need +2. Download the typings from DefinitelyTyped that you need. +3. Compile the code and serve it up at [http://localhost:8080](http://localhost:8080) + +Now you need dev tools. There's a world of choice out there; there's [Atom](https://atom.io/), there's [VS Code](https://www.visualstudio.com/en-us/products/code-vs.aspx), there's [Sublime](http://www.sublimetext.com/). There's even something called [Visual Studio](http://www.visualstudio.com). It's all your choice really. diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..928ded0 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,63 @@ +/* eslint-disable no-var, strict, prefer-arrow-callback */ +'use strict'; + +var gulp = require('gulp'); +var gutil = require('gulp-util'); +var connect = require('gulp-connect'); +var eslint = require('gulp-eslint'); +var webpack = require('./src/gulp/webpack'); +var tests = require('./src/gulp/tests'); +var staticFiles = require('./src/gulp/staticFiles'); +var inject = require('./src/gulp/inject'); +var clean = require('./src/gulp/clean'); +var karma = require('karma'); + +var lintSrcs = ['./src/gulp/**/*.js']; + +gulp.task('clean-webpack', function (done) { + clean.runOnWebPack(done); +}); + +gulp.task('clean', ['clean-webpack'], function (done) { + clean.run(done); +}); + +gulp.task('build-process.env.NODE_ENV', function () { + process.env.NODE_ENV = 'production'; +}); + +gulp.task('build-js', ['clean-webpack', 'build-process.env.NODE_ENV'], function(done) { + webpack.build().then(function() { done(); }); +}); + +gulp.task('build-other', ['build-process.env.NODE_ENV'], function() { + staticFiles.build(); +}); + +gulp.task('build', ['build-js', 'build-other', 'lint'], function () { + inject.build(); +}); + +gulp.task('lint', function () { + return gulp.src(lintSrcs) + .pipe(eslint()) + .pipe(eslint.format()); +}); + +gulp.task('test', [], function (done) { + new karma.Server({ + configFile: __dirname + '/karma.conf.js', + singleRun: true + }, done).start(); +}); + +gulp.task('watch', ['build'], function() {}); + +gulp.task('serve', ['watch'], function() { + connect.server({ + root: './target', + port: 8080 + }); +}); + +gulp.task('default', ['build', 'test']); diff --git a/karma.conf.js b/karma.conf.js new file mode 100644 index 0000000..2364e83 --- /dev/null +++ b/karma.conf.js @@ -0,0 +1,70 @@ +/* eslint-disable no-var, strict */ +'use strict'; + +var webpackConfig = require('./webpack.config.js'); + +module.exports = function(config) { + // Documentation: https://karma-runner.github.io/0.13/config/configuration-file.html + config.set({ + browsers: [ 'PhantomJS' ], + + files: [ + 'src/test/ts/import-babel-polyfill.js', // This ensures we have the es6 shims in place from babel + 'src/test/ts/**/*.ts', + 'src/test/ts/**/*.tsx' + ], + + port: 9876, + + frameworks: [ 'jasmine', 'phantomjs-shim' ], + + logLevel: config.LOG_INFO, //config.LOG_DEBUG + + preprocessors: { + 'src/test/ts/import-babel-polyfill.js': [ 'webpack', 'sourcemap' ], + 'src/main/ts/**/*.{ts,tsx}': [ 'webpack', 'sourcemap' ], + 'src/test/ts/**/*.{ts,tsx}': [ 'webpack', 'sourcemap' ] + }, + + reporters: [ 'mocha', 'junit'/*, 'coverage'*/ ], + + webpack: { + devtool: 'inline-source-map', //'inline-source-map', + debug: true, + module: webpackConfig.module, + resolve: webpackConfig.resolve + }, + + webpackMiddleware: { + quiet: true, + stats: { + colors: true + } + }, + + // reporter options + mochaReporter: { + colors: { + success: 'green', + info: 'grey', + warning: 'yellow', + error: 'red' + } + }, + + // the default configuration + junitReporter: { + outputDir: 'target/junit-results', // results will be saved as $outputDir/$browserName.xml + outputFile: undefined, // if included, results will be saved as $outputDir/$browserName/$outputFile + suite: '' + }, + + coverageReporter: { + reporters:[ + {type: 'html', dir:'target/coverage'}, // https://github.com/karma-runner/karma-coverage/issues/123 + {type: 'text'}, + {type: 'text-summary'} + ], + } + }); +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..e65fc19 --- /dev/null +++ b/package.json @@ -0,0 +1,68 @@ +{ + "name": "eid-exceptions", + "version": "0.1.0", + "description": "EID Runtime Exceptions and Utilities for JavaScript", + "babel": {}, + "main": "eid.js", + "scripts": { + "test": "node_modules/.bin/gulp build test", + "serve": "node_modules/.bin/gulp serve", + "watch": "node_modules/.bin/gulp watch", + "build": "node_modules/.bin/gulp build" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/wavesoftware/javascript-eid-exceptions.git" + }, + "devDependencies": { + "babel": "^6.0.0", + "babel-core": "^6.0.0", + "babel-loader": "^6.0.0", + "babel-polyfill": "^6.0.0", + "babel-preset-es2015": "^6.0.0", + "del": "^2.0.2", + "eslint": "^1.6.0", + "glob": "^5.0.15", + "gulp": "^3.9.0", + "gulp-autoprefixer": "^3.1.0", + "gulp-cached": "^1.1.0", + "gulp-connect": "^2.2.0", + "gulp-cssmin": "^0.1.7", + "gulp-debug": "^2.1.2", + "gulp-eslint": "^1.0.0", + "gulp-if": "^2.0.0", + "gulp-inject": "^3.0.0", + "gulp-notify": "^2.2.0", + "gulp-sourcemaps": "^1.5.2", + "gulp-streamify": "1.0.0", + "gulp-uglify": "^1.2.0", + "gulp-util": "^3.0.6", + "jasmine-core": "^2.3.4", + "karma": "^0.13.10", + "karma-coverage": "^0.5.2", + "karma-jasmine": "^0.3.6", + "karma-junit-reporter": "^0.3.7", + "karma-mocha-reporter": "^1.1.1", + "karma-phantomjs-launcher": "^0.2.1", + "karma-phantomjs-shim": "^1.1.1", + "karma-sourcemap-loader": "^0.3.6", + "karma-webpack": "^1.7.0", + "phantomjs": "^1.9.17", + "ts-loader": "^0.7.1", + "typescript": "^1.7.5", + "webpack": "^1.12.2", + "webpack-notifier": "^1.2.1" + }, + "keywords": [ + "exception", + "errorcodes", + "error", + "handling" + ], + "author": "Krzysztof Suszyński", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/wavesoftware/javascript-eid-exceptions/issues" + }, + "homepage": "https://github.com/wavesoftware/javascript-eid-exceptions" +} diff --git a/src/gulp/.eslintrc b/src/gulp/.eslintrc new file mode 100644 index 0000000..0d32281 --- /dev/null +++ b/src/gulp/.eslintrc @@ -0,0 +1,59 @@ +{ + "root": true, + "env": { + "commonjs": true, + }, + "rules": { + "no-alert": 2, + "no-array-constructor": 2, + "no-caller": 2, + "no-catch-shadow": 2, + "no-empty-label": 2, + "no-eval": 2, + "no-extend-native": 2, + "no-extra-bind": 2, + "no-implied-eval": 2, + "no-iterator": 2, + "no-label-var": 2, + "no-labels": 2, + "no-lone-blocks": 2, + "no-loop-func": 2, + "no-multi-str": 2, + "no-native-reassign": 2, + "no-new": 2, + "no-new-func": 2, + "no-new-object": 2, + "no-new-wrappers": 2, + "no-octal-escape": 2, + "no-proto": 2, + "no-return-assign": 2, + "no-script-url": 2, + "no-sequences": 2, + "no-shadow": 2, + "no-shadow-restricted-names": 2, + "no-spaced-func": 2, + "no-trailing-spaces": 2, + "no-undef-init": 2, + "no-unused-expressions": 2, + "no-use-before-define": [2, "nofunc"], + "no-with": 2, + "camelcase": 2, + "comma-spacing": 1, + "consistent-return": 2, + "curly": [2, "all"], + "dot-notation": [2, { "allowKeywords": true }], + "eol-last": 2, + "no-extra-parens": [2, "functions"], + "eqeqeq": 2, + "new-cap": 2, + "new-parens": 2, + "quotes": [1, "single"], + "semi": 2, + "semi-spacing": [2, {"before": false, "after": true}], + "space-infix-ops": 2, + "space-return-throw-case": 2, + "space-unary-ops": [2, { "words": true, "nonwords": false }], + "strict": [2, "global"], + "yoda": [2, "never"] + } +} diff --git a/src/gulp/clean.js b/src/gulp/clean.js new file mode 100644 index 0000000..b7b3c37 --- /dev/null +++ b/src/gulp/clean.js @@ -0,0 +1,30 @@ +'use strict'; + +var del = require('del'); +var gutil = require('gulp-util'); +var fs = require('fs'); + +function run(directory, done) { + fs.stat(directory, function(err){ + if (err) { + // Never existed + done(); + } + else { + del([directory], { force: true }) + .then(function(paths) { + gutil.log('Deleted files/folders:\n', paths.join('\n')); + done(); + }) + .catch(function(error) { + gutil.log('Problem deleting:\n', error); + done(); + }); + } + }); +} + +module.exports = { + run: function(done) { return run('./target', done); }, + runOnWebPack: function(done) { return run('./target/webpack', done); } +}; diff --git a/src/gulp/inject.js b/src/gulp/inject.js new file mode 100644 index 0000000..59ba566 --- /dev/null +++ b/src/gulp/inject.js @@ -0,0 +1,36 @@ +'use strict'; + +var gulp = require('gulp'); +var inject = require('gulp-inject'); +var glob = require('glob'); + +function injectIndex(options) { + function run() { + var target = gulp.src('./src/test/html/index.html'); + var sources = gulp.src([ + './target/webpack/vendor*.js', + './target/webpack/main*.js' + ], { read: false }); + var injectOptions = { ignorePath: '/target/', addRootSlash: false }; + return target + .pipe(inject(sources, injectOptions)) + .pipe(gulp.dest('./target')); + } + + var jsCssGlob = './target/**/*.{js,css}'; + + if (options.shouldWatch) { + gulp.watch(jsCssGlob, function(evt) { + if (evt.path && evt.type === 'changed') { + run(evt.path); + } + }); + } else { + run('initial build'); + } +} + +module.exports = { + build: function() { return injectIndex({ shouldWatch: false }); }, + watch: function() { return injectIndex({ shouldWatch: true }); } +}; diff --git a/src/gulp/staticFiles.js b/src/gulp/staticFiles.js new file mode 100644 index 0000000..846a5f1 --- /dev/null +++ b/src/gulp/staticFiles.js @@ -0,0 +1,31 @@ +'use strict'; + +var gulp = require('gulp'); +var cache = require('gulp-cached'); + +var targets = [ + { description: 'INDEX', src: './src/test/html/index.html', dest: './target' } +]; + +function copy(options) { + function run(target) { + gulp.src(target.src) + .pipe(cache(target.description)) + .pipe(gulp.dest(target.dest)); + } + + function watch(target) { + gulp.watch(target.src, function() { run(target); }); + } + + targets.forEach(run); + + if (options.shouldWatch) { + targets.forEach(watch); + } +} + +module.exports = { + build: function() { return copy({ shouldWatch: false }); }, + watch: function() { return copy({ shouldWatch: true }); } +}; diff --git a/src/gulp/tests.js b/src/gulp/tests.js new file mode 100644 index 0000000..1b0b47a --- /dev/null +++ b/src/gulp/tests.js @@ -0,0 +1,35 @@ +'use strict'; + +var Server = require('karma').Server; +var path = require('path'); +var gutil = require('gulp-util'); + +module.exports = { + watch: function() { + // Documentation: https://karma-runner.github.io/0.13/dev/public-api.html + var karmaConfig = { + configFile: path.join(__dirname, '../../karma.conf.js'), + singleRun: false, + + // Fancy runner + plugins: [ + 'karma-webpack', + 'karma-jasmine', + 'karma-mocha-reporter', + 'karma-junit-reporter', + 'karma-coverage', + 'karma-sourcemap-loader', + 'karma-phantomjs-launcher', + 'karma-phantomjs-shim' + ], // karma-phantomjs-shim only in place until PhantomJS hits 2.0 and has function.bind + reporters: ['mocha'] + }; + + new Server(karmaConfig, karmaCompleted).start(); + + function karmaCompleted(exitCode) { + gutil.log('Karma has exited with:', exitCode); + process.exit(exitCode); + } + } +}; diff --git a/src/gulp/webpack.js b/src/gulp/webpack.js new file mode 100644 index 0000000..1c5bb5f --- /dev/null +++ b/src/gulp/webpack.js @@ -0,0 +1,91 @@ +'use strict'; + +var gulp = require('gulp'); +var gutil = require('gulp-util'); +var webpack = require('webpack'); +var WebpackNotifierPlugin = require('webpack-notifier'); +var webpackConfig = require('../../webpack.config.js'); + +function buildProduction(done) { + // modify some webpack config options + var myProdConfig = Object.create(webpackConfig); + myProdConfig.output.filename = '[name].[hash].js'; + + myProdConfig.plugins = myProdConfig.plugins.concat( + new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', filename: 'vendor.[hash].js' }), + new webpack.optimize.DedupePlugin(), + new webpack.optimize.UglifyJsPlugin() + ); + + // run webpack + webpack(myProdConfig, function(err, stats) { + if(err) { throw new gutil.PluginError('webpack:build', err); } + gutil.log('[webpack:build]', stats.toString({ + colors: true + })); + + if (done) { done(); } + }); +} + +function createDevCompiler() { + // modify some webpack config options + var myDevConfig = Object.create(webpackConfig); + myDevConfig.devtool = 'inline-source-map'; + myDevConfig.debug = true; + + myDevConfig.plugins = myDevConfig.plugins.concat( + new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', filename: 'vendor.js' }), + new WebpackNotifierPlugin({ title: 'Webpack build', excludeWarnings: true }) + ); + + // create a single instance of the compiler to allow caching + return webpack(myDevConfig); +} + +function buildDevelopment(done, devCompiler) { + // run webpack + devCompiler.run(function(err, stats) { + if(err) { throw new gutil.PluginError('webpack:build-dev', err); } + gutil.log('[webpack:build-dev]', stats.toString({ + chunks: false, + colors: true + })); + + if (done) { done(); } + }); +} + + +function bundle(options) { + var devCompiler; + + function build(done) { + if (options.shouldWatch) { + buildDevelopment(done, devCompiler); + } else { + buildProduction(done); + } + } + + if (options.shouldWatch) { + devCompiler = createDevCompiler(); + + gulp.watch('src/main/ts/**/*', function() { build(); }); + } + + return new Promise(function(resolve, reject) { + build(function (err) { + if (err) { + reject(err); + } else { + resolve('webpack built'); + } + }); + }); +} + +module.exports = { + build: function() { return bundle({ shouldWatch: false }); }, + watch: function() { return bundle({ shouldWatch: true }); } +}; diff --git a/src/main/ts/eid.ts b/src/main/ts/eid.ts new file mode 100644 index 0000000..242af26 --- /dev/null +++ b/src/main/ts/eid.ts @@ -0,0 +1,4 @@ +import 'babel-polyfill'; +import Eid from './eid/exceptions/Eid'; + +export default Eid; diff --git a/src/main/ts/eid/exceptions/Eid.ts b/src/main/ts/eid/exceptions/Eid.ts new file mode 100644 index 0000000..bf46590 --- /dev/null +++ b/src/main/ts/eid/exceptions/Eid.ts @@ -0,0 +1,208 @@ + +import JFormatter from './internal/JFormatter'; +import MathRandom from './internal/MathRandom'; + +export module Eid { + /** + * It is used to generate unique ID for each EID object. It's mustn't be secure because it just indicate EID object while + * logging. + */ + export interface UniqIdGenerator { + /** + * Generates a unique string ID + * + * @return a generated unique ID + */ + generateUniqId(): string; + } +} + +class StdUniqIdGenerator implements Eid.UniqIdGenerator { + private static BASE36 = 36; + private static INTEGER_MAX_VALUE = Math.pow(2, 31); + private static generator : MathRandom = new MathRandom(); + generateUniqId(): string { + var first : number = Math.abs(StdUniqIdGenerator.generator.nextLong() + 1); + var second : number = Math.abs(StdUniqIdGenerator.generator.nextInt(StdUniqIdGenerator.INTEGER_MAX_VALUE)); + var calc = first + second; + return (Math.abs(calc) % StdUniqIdGenerator.INTEGER_MAX_VALUE).toString(StdUniqIdGenerator.BASE36); + } +} +/** + * This class shouldn't be used in any public API or library. It is designed to be used for in-house development + * of end user applications which will report Bugs in standardized error pages or post them to issue tracker. + *

+ * Exception identifier for all Eid Runtime Exceptions. + * + * @author Krzysztof Suszyński + */ +export default class Eid { + public static get DEFAULT_FORMAT() : string { return "[%s]<%s>" }; + public static get DEFAULT_REF_FORMAT() : string { return "[%s|%s]<%s>" }; + public static get DEFAULT_MESSAGE_FORMAT() : string { return "%s => %s" }; + public static get DEFAULT_UNIQ_ID_GENERATOR() : Eid.UniqIdGenerator { return new StdUniqIdGenerator() }; + static FORMAT_NUM_SPEC : number = 2; + static REF_FORMAT_NUM_SPEC : number = 3; + private static MESSAGE_FORMAT_NUM_SPEC : number = 2; + private static messageFormat : string = Eid.DEFAULT_MESSAGE_FORMAT; + private static uniqIdGenerator : Eid.UniqIdGenerator = Eid.DEFAULT_UNIQ_ID_GENERATOR; + private static format : string = Eid.DEFAULT_FORMAT; + private static refFormat : string = Eid.DEFAULT_REF_FORMAT; + private id : string; + private ref : string; + private uniq : string; + + /** + * Constructor + * + * @param id the exception id, must be unique developer inserted string, from date + * @param ref an optional reference + */ + constructor(id : string, ref : string = null) { + this.uniq = Eid.uniqIdGenerator.generateUniqId(); + this.id = id; + this.ref = (ref == null ? '' : ref); + } + + /** + * Sets the actual unique ID generator that will be used to generate IDs for all Eid objects. It will return previously used + * generator. + * + * @param uniqIdGenerator new instance of unique ID generator + * @return a previously used unique ID generator + * @throws TypeError if given generator was null + */ + public static setUniqIdGenerator(uniqIdGenerator : Eid.UniqIdGenerator) : Eid.UniqIdGenerator { + if (uniqIdGenerator == null) { + throw new TypeError("Unique ID generator can't be null, but given one"); + } + var previous : Eid.UniqIdGenerator = Eid.uniqIdGenerator; + Eid.uniqIdGenerator = uniqIdGenerator; + return previous; + } + + /** + * Sets a format that will be used for all Eid exceptions when printing a detail message. + *

+ * Format must be non-null and contain two format specifiers "%s" + * + * @param format a format that will be used, must be non-null and contain two format specifiers "%s" + * @return previously used format + * @throws IllegalArgumentException if given format hasn't got two format specifiers "%s", or if given format was + * null + */ + public static setMessageFormat(format : string) : string { + Eid.validateFormat(format, Eid.MESSAGE_FORMAT_NUM_SPEC); + var oldFormat : string = Eid.messageFormat; + Eid.messageFormat = format; + return oldFormat; + } + + /** + * Gets actually set message format + * + * @return actually set message format + */ + public static getMessageFormat() : string { + return Eid.messageFormat; + } + + /** + * Sets the actual format that will be used in {@link #toString()} method. It will return previously used format. + * + * @param format a format compliant with {@link JFormatter#format(String, Object...)} with 2 object arguments + * @return a previously used format + * @throws TypeError if given format hasn't got two format specifiers "%s", or if given format was + * null + */ + public static setFormat(format : string) : string { + Eid.validateFormat(format, Eid.FORMAT_NUM_SPEC); + var previously:string = Eid.format; + Eid.format = format; + return previously; + } + + /** + * Sets the actual format that will be used in {@link #toString()} method + * + * @param refFormat a format compliant with {@link JFormatter#format(String, Object...)} with 3 object arguments + * @return a previously used format + * @throws TypeError if given format hasn't got tree format specifiers "%s", or if given format was + * null + */ + public static setRefFormat(refFormat : string) : string { + Eid.validateFormat(refFormat, Eid.REF_FORMAT_NUM_SPEC); + var previously : string = Eid.refFormat; + Eid.refFormat = refFormat; + return previously; + } + + /** + * Makes a log message from this EID object + *

+ *

This method is for convenience of usage of EID in logging. You can use it like this: + *

+ *

+   * log.debug(new Eid("20151025:202129").makeLogMessage("A request: %s", request));
+   * 
+ * @param logMessageFormat a log message format as accepted by {@link JFormatter#format(String, Object...)} + * @param parameters a parameters for logMessageFormat to by passed to {@link JFormatter#format(String, Object...)} + * @return a formatted message + */ + public makeLogMessage(logMessageFormat : string, ...parameters : any[]) { + var message : string = JFormatter.format(logMessageFormat, parameters); + return JFormatter.format(Eid.getMessageFormat(), this.toString(), message); + } + + public toString() : string { + if ("" === this.ref || null === this.ref) { + return JFormatter.format(Eid.format, this.id, this.uniq); + } + return JFormatter.format(Eid.refFormat, this.id, this.ref, this.uniq); + } + + /** + * Getter for constant Exception ID + * + * @return ID of exception + */ + public getId() : string { + return this.id; + } + + /** + * Get custom ref passed to Exception ID + * + * @return ID of exception + */ + public getRef() : string { + return this.ref; + } + + /** + * Gets unique generated string for this instance of Eid + * + * @return a unique string + */ + public getUniq() : string { + return this.uniq; + } + + private static validateFormat(format : string, numSpecifiers : number) : void { + if (format == null) { + throw new TypeError("Format can't be null, but just received one"); + } + var specifiers:string[] = []; + for (var i:number = 0; i < numSpecifiers; i++) { + specifiers.push(i + "-test-id"); + } + var formatted : string = JFormatter.format(format, specifiers); + for (var specifier in specifiers) { + if (!formatted.includes(specifier)) { + throw new TypeError("Given format contains to little format specifiers, " + + "expected " + numSpecifiers + " but given \"" + format + "\""); + } + } + } + +} diff --git a/src/main/ts/eid/exceptions/internal/JFormatter.ts b/src/main/ts/eid/exceptions/internal/JFormatter.ts new file mode 100644 index 0000000..1df7beb --- /dev/null +++ b/src/main/ts/eid/exceptions/internal/JFormatter.ts @@ -0,0 +1,12 @@ + +/** + * Internal class, do not use it. + * @internal + */ +export default class JFormatter { + static format(format : string, ...args : any[]) : string { + var regex = /%s/; + var _r = function(p,c) { return p.replace(regex, c); } + return args.reduce(_r, format); + } +} diff --git a/src/main/ts/eid/exceptions/internal/MathRandom.ts b/src/main/ts/eid/exceptions/internal/MathRandom.ts new file mode 100644 index 0000000..336f8e7 --- /dev/null +++ b/src/main/ts/eid/exceptions/internal/MathRandom.ts @@ -0,0 +1,17 @@ +/** + * This is internal package, do not use it externally + */ + +export default class MathRandom { + private static get LONG_MAX(): number { return Math.pow(2, 53) - 1 }; + private static get LONG_MIN(): number { return -1 * Math.pow(2, 53) }; + nextLong() : number { + return this.random(MathRandom.LONG_MIN, MathRandom.LONG_MAX); + }; + nextInt(max : number) : number { + return this.random(0, max); + }; + private random(min : number, max: number) : number { + return Math.floor(Math.random() * (max - min + 1)) + min + }; +} diff --git a/src/main/ts/eid/utils/EidPreconditions.ts b/src/main/ts/eid/utils/EidPreconditions.ts new file mode 100644 index 0000000..6449673 --- /dev/null +++ b/src/main/ts/eid/utils/EidPreconditions.ts @@ -0,0 +1,89 @@ + + +/** + * This class shouldn't be used in any public API or library. It is designed to be used for in-house development + * of end user applications which will report Bugs in standardized error pages or post them to issue tracker. + *

+ * Static convenience methods that help a method or constructor check whether it was invoked correctly (whether its + * preconditions + * have been met). These methods generally accept a {@code boolean} expression which is expected to be {@code true} (or in the + * case of {@code + * checkNotNull}, an object reference which is expected to be non-null). When {@code false} (or {@code null}) is passed instead, + * the {@code EidPreconditions} method throws an unchecked exception, which helps the calling method communicate to its + * caller that + * that caller has made a mistake. + *

+ * Each method accepts a EID String or {@link Eid} object, which is designed to ease of use and provide strict ID for given + * Exception usage. This approach speed up development of large application and helps support teams to by giving the both static + * and random ID for each possible unpredicted bug. + *

+ * This is best to use with tools and plugins like + * EidGenerator for Netbeans IDE + *

+ * Example: + *

   {@code
+ *
+ *   /**
+ *    * Returns the positive square root of the given value.
+ *    *
+ *    * @throws EidIllegalArgumentException if the value is negative
+ *    *}{@code /
+ *   public static double sqrt(double value) {
+ *     EidPreconditions.checkArgument(value >= 0.0, "20150718:012333");
+ *     // calculate the square root
+ *   }
+ *
+ *   void exampleBadCaller() {
+ *     double d = sqrt(-1.0);
+ *   }
+ * }
+ *

+ * In this example, {@code checkArgument} throws an {@code EidIllegalArgumentException} to indicate that {@code exampleBadCaller} + * made an error in its call to {@code sqrt}. Exception, when it will be printed will contain user given Eid and also + * Randomly generated ID. Those fields can be displayed to user on error page on posted directly to issue tracker. + *

+ * Example: + *

+ *

+ *
+ * {@code
+ *   // Main application class for ex.: http servlet
+ *    try {
+ *        performRequest(request, response);
+ *    } catch (EidRuntimeException ex) {
+ *        issuesTracker.put(ex);
+ *        throw ex;
+ *    }
+ * }
+ *

+ *

+ *

Functional try to execute blocks

+ *

+ *

+ * Using functional blocks to handle operations, that are intended to operate properly, simplify the code and makes it more + * readable. It's also good way to deal with untested, uncovered {@code catch} blocks. It's easy and gives developers nice way of + * dealing with countless operations that suppose to work as intended. + *

+ *

+ * Example: + *


+ *
+ *     InputStream is = EidPreconditions.tryToExecute({@code new UnsafeSupplier}() {
+ *        {@literal @}Override
+ *         public InputStream get() throws IOException {
+ *             return this.getClass().getClassLoader()
+ *                 .getResourceAsStream("project.properties");
+ *         }
+ *     }, "20150718:121521");
+ * 
+ * + * @author Krzysztof Suszyński + * @since 0.1.0 (idea imported from Guava Library and COI code) + */ +class EidPreconditions { + static checkArgument(expression : boolean, eid: String) { + + } +} + +export default EidPreconditions; diff --git a/src/main/ts/tsconfig.json b/src/main/ts/tsconfig.json new file mode 100644 index 0000000..44250f3 --- /dev/null +++ b/src/main/ts/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "es6", + "noImplicitAny": false, + "removeComments": false, + "preserveConstEnums": true, + "outDir": "../../../target/js" + }, + "filesGlob": [ + "../typings/**/*.ts", + "**/*.ts" + ], + "files": [], + "atom": { + "rewriteTsconfig": false + } +} diff --git a/src/main/typings/jasmine/jasmine.d.ts b/src/main/typings/jasmine/jasmine.d.ts new file mode 100644 index 0000000..ed85914 --- /dev/null +++ b/src/main/typings/jasmine/jasmine.d.ts @@ -0,0 +1,496 @@ +// Type definitions for Jasmine 2.2 +// Project: http://jasmine.github.io/ +// Definitions by: Boris Yankov , Theodore Brown , David Pärsson +// Definitions: https://github.com/borisyankov/DefinitelyTyped + + +// For ddescribe / iit use : https://github.com/borisyankov/DefinitelyTyped/blob/master/karma-jasmine/karma-jasmine.d.ts + +declare function describe(description: string, specDefinitions: () => void): void; +declare function fdescribe(description: string, specDefinitions: () => void): void; +declare function xdescribe(description: string, specDefinitions: () => void): void; + +declare function it(expectation: string, assertion?: () => void, timeout?: number): void; +declare function it(expectation: string, assertion?: (done: () => void) => void, timeout?: number): void; +declare function fit(expectation: string, assertion?: () => void, timeout?: number): void; +declare function fit(expectation: string, assertion?: (done: () => void) => void, timeout?: number): void; +declare function xit(expectation: string, assertion?: () => void, timeout?: number): void; +declare function xit(expectation: string, assertion?: (done: () => void) => void, timeout?: number): void; + +/** If you call the function pending anywhere in the spec body, no matter the expectations, the spec will be marked pending. */ +declare function pending(reason?: string): void; + +declare function beforeEach(action: () => void, timeout?: number): void; +declare function beforeEach(action: (done: () => void) => void, timeout?: number): void; +declare function afterEach(action: () => void, timeout?: number): void; +declare function afterEach(action: (done: () => void) => void, timeout?: number): void; + +declare function beforeAll(action: () => void, timeout?: number): void; +declare function beforeAll(action: (done: () => void) => void, timeout?: number): void; +declare function afterAll(action: () => void, timeout?: number): void; +declare function afterAll(action: (done: () => void) => void, timeout?: number): void; + +declare function expect(spy: Function): jasmine.Matchers; +declare function expect(actual: any): jasmine.Matchers; + +declare function fail(e?: any): void; + +declare function spyOn(object: any, method: string): jasmine.Spy; + +declare function runs(asyncMethod: Function): void; +declare function waitsFor(latchMethod: () => boolean, failureMessage?: string, timeout?: number): void; +declare function waits(timeout?: number): void; + +declare module jasmine { + + var clock: () => Clock; + + function any(aclass: any): Any; + function anything(): Any; + function arrayContaining(sample: any[]): ArrayContaining; + function objectContaining(sample: any): ObjectContaining; + function createSpy(name: string, originalFn?: Function): Spy; + function createSpyObj(baseName: string, methodNames: any[]): any; + function createSpyObj(baseName: string, methodNames: any[]): T; + function pp(value: any): string; + function getEnv(): Env; + function addCustomEqualityTester(equalityTester: CustomEqualityTester): void; + function addMatchers(matchers: CustomMatcherFactories): void; + function stringMatching(str: string): Any; + function stringMatching(str: RegExp): Any; + + interface Any { + + new (expectedClass: any): any; + + jasmineMatches(other: any): boolean; + jasmineToString(): string; + } + + // taken from TypeScript lib.core.es6.d.ts, applicable to CustomMatchers.contains() + interface ArrayLike { + length: number; + [n: number]: T; + } + + interface ArrayContaining { + new (sample: any[]): any; + + asymmetricMatch(other: any): boolean; + jasmineToString(): string; + } + + interface ObjectContaining { + new (sample: any): any; + + jasmineMatches(other: any, mismatchKeys: any[], mismatchValues: any[]): boolean; + jasmineToString(): string; + } + + interface Block { + + new (env: Env, func: SpecFunction, spec: Spec): any; + + execute(onComplete: () => void): void; + } + + interface WaitsBlock extends Block { + new (env: Env, timeout: number, spec: Spec): any; + } + + interface WaitsForBlock extends Block { + new (env: Env, timeout: number, latchFunction: SpecFunction, message: string, spec: Spec): any; + } + + interface Clock { + install(): void; + uninstall(): void; + /** Calls to any registered callback are triggered when the clock is ticked forward via the jasmine.clock().tick function, which takes a number of milliseconds. */ + tick(ms: number): void; + mockDate(date?: Date): void; + } + + interface CustomEqualityTester { + (first: any, second: any): boolean; + } + + interface CustomMatcher { + compare(actual: T, expected: T): CustomMatcherResult; + compare(actual: any, expected: any): CustomMatcherResult; + } + + interface CustomMatcherFactory { + (util: MatchersUtil, customEqualityTesters: Array): CustomMatcher; + } + + interface CustomMatcherFactories { + [index: string]: CustomMatcherFactory; + } + + interface CustomMatcherResult { + pass: boolean; + message?: string; + } + + interface MatchersUtil { + equals(a: any, b: any, customTesters?: Array): boolean; + contains(haystack: ArrayLike | string, needle: any, customTesters?: Array): boolean; + buildFailureMessage(matcherName: string, isNot: boolean, actual: any, ...expected: Array): string; + } + + interface Env { + setTimeout: any; + clearTimeout: void; + setInterval: any; + clearInterval: void; + updateInterval: number; + + currentSpec: Spec; + + matchersClass: Matchers; + + version(): any; + versionString(): string; + nextSpecId(): number; + addReporter(reporter: Reporter): void; + execute(): void; + describe(description: string, specDefinitions: () => void): Suite; + // ddescribe(description: string, specDefinitions: () => void): Suite; Not a part of jasmine. Angular team adds these + beforeEach(beforeEachFunction: () => void): void; + beforeAll(beforeAllFunction: () => void): void; + currentRunner(): Runner; + afterEach(afterEachFunction: () => void): void; + afterAll(afterAllFunction: () => void): void; + xdescribe(desc: string, specDefinitions: () => void): XSuite; + it(description: string, func: () => void): Spec; + // iit(description: string, func: () => void): Spec; Not a part of jasmine. Angular team adds these + xit(desc: string, func: () => void): XSpec; + compareRegExps_(a: RegExp, b: RegExp, mismatchKeys: string[], mismatchValues: string[]): boolean; + compareObjects_(a: any, b: any, mismatchKeys: string[], mismatchValues: string[]): boolean; + equals_(a: any, b: any, mismatchKeys: string[], mismatchValues: string[]): boolean; + contains_(haystack: any, needle: any): boolean; + addCustomEqualityTester(equalityTester: CustomEqualityTester): void; + addMatchers(matchers: CustomMatcherFactories): void; + specFilter(spec: Spec): boolean; + } + + interface FakeTimer { + + new (): any; + + reset(): void; + tick(millis: number): void; + runFunctionsWithinRange(oldMillis: number, nowMillis: number): void; + scheduleFunction(timeoutKey: any, funcToCall: () => void, millis: number, recurring: boolean): void; + } + + interface HtmlReporter { + new (): any; + } + + interface HtmlSpecFilter { + new (): any; + } + + interface Result { + type: string; + } + + interface NestedResults extends Result { + description: string; + + totalCount: number; + passedCount: number; + failedCount: number; + + skipped: boolean; + + rollupCounts(result: NestedResults): void; + log(values: any): void; + getItems(): Result[]; + addResult(result: Result): void; + passed(): boolean; + } + + interface MessageResult extends Result { + values: any; + trace: Trace; + } + + interface ExpectationResult extends Result { + matcherName: string; + passed(): boolean; + expected: any; + actual: any; + message: string; + trace: Trace; + } + + interface Trace { + name: string; + message: string; + stack: any; + } + + interface PrettyPrinter { + + new (): any; + + format(value: any): void; + iterateObject(obj: any, fn: (property: string, isGetter: boolean) => void): void; + emitScalar(value: any): void; + emitString(value: string): void; + emitArray(array: any[]): void; + emitObject(obj: any): void; + append(value: any): void; + } + + interface StringPrettyPrinter extends PrettyPrinter { + } + + interface Queue { + + new (env: any): any; + + env: Env; + ensured: boolean[]; + blocks: Block[]; + running: boolean; + index: number; + offset: number; + abort: boolean; + + addBefore(block: Block, ensure?: boolean): void; + add(block: any, ensure?: boolean): void; + insertNext(block: any, ensure?: boolean): void; + start(onComplete?: () => void): void; + isRunning(): boolean; + next_(): void; + results(): NestedResults; + } + + interface Matchers { + + new (env: Env, actual: any, spec: Env, isNot?: boolean): any; + + env: Env; + actual: any; + spec: Env; + isNot?: boolean; + message(): any; + + toBe(expected: any, expectationFailOutput?: any): boolean; + toEqual(expected: any, expectationFailOutput?: any): boolean; + toMatch(expected: any, expectationFailOutput?: any): boolean; + toBeDefined(expectationFailOutput?: any): boolean; + toBeUndefined(expectationFailOutput?: any): boolean; + toBeNull(expectationFailOutput?: any): boolean; + toBeNaN(): boolean; + toBeTruthy(expectationFailOutput?: any): boolean; + toBeFalsy(expectationFailOutput?: any): boolean; + toHaveBeenCalled(): boolean; + toHaveBeenCalledWith(...params: any[]): boolean; + toContain(expected: any, expectationFailOutput?: any): boolean; + toBeLessThan(expected: any, expectationFailOutput?: any): boolean; + toBeGreaterThan(expected: any, expectationFailOutput?: any): boolean; + toBeCloseTo(expected: any, precision: any, expectationFailOutput?: any): boolean; + toContainHtml(expected: string): boolean; + toContainText(expected: string): boolean; + toThrow(expected?: any): boolean; + toThrowError(expected?: any, message?: string): boolean; + not: Matchers; + + Any: Any; + } + + interface Reporter { + reportRunnerStarting(runner: Runner): void; + reportRunnerResults(runner: Runner): void; + reportSuiteResults(suite: Suite): void; + reportSpecStarting(spec: Spec): void; + reportSpecResults(spec: Spec): void; + log(str: string): void; + } + + interface MultiReporter extends Reporter { + addReporter(reporter: Reporter): void; + } + + interface Runner { + + new (env: Env): any; + + execute(): void; + beforeEach(beforeEachFunction: SpecFunction): void; + afterEach(afterEachFunction: SpecFunction): void; + beforeAll(beforeAllFunction: SpecFunction): void; + afterAll(afterAllFunction: SpecFunction): void; + finishCallback(): void; + addSuite(suite: Suite): void; + add(block: Block): void; + specs(): Spec[]; + suites(): Suite[]; + topLevelSuites(): Suite[]; + results(): NestedResults; + } + + interface SpecFunction { + (spec?: Spec): void; + } + + interface SuiteOrSpec { + id: number; + env: Env; + description: string; + queue: Queue; + } + + interface Spec extends SuiteOrSpec { + + new (env: Env, suite: Suite, description: string): any; + + suite: Suite; + + afterCallbacks: SpecFunction[]; + spies_: Spy[]; + + results_: NestedResults; + matchersClass: Matchers; + + getFullName(): string; + results(): NestedResults; + log(arguments: any): any; + runs(func: SpecFunction): Spec; + addToQueue(block: Block): void; + addMatcherResult(result: Result): void; + expect(actual: any): any; + waits(timeout: number): Spec; + waitsFor(latchFunction: SpecFunction, timeoutMessage?: string, timeout?: number): Spec; + fail(e?: any): void; + getMatchersClass_(): Matchers; + addMatchers(matchersPrototype: CustomMatcherFactories): void; + finishCallback(): void; + finish(onComplete?: () => void): void; + after(doAfter: SpecFunction): void; + execute(onComplete?: () => void): any; + addBeforesAndAftersToQueue(): void; + explodes(): void; + spyOn(obj: any, methodName: string, ignoreMethodDoesntExist: boolean): Spy; + removeAllSpies(): void; + } + + interface XSpec { + id: number; + runs(): void; + } + + interface Suite extends SuiteOrSpec { + + new (env: Env, description: string, specDefinitions: () => void, parentSuite: Suite): any; + + parentSuite: Suite; + + getFullName(): string; + finish(onComplete?: () => void): void; + beforeEach(beforeEachFunction: SpecFunction): void; + afterEach(afterEachFunction: SpecFunction): void; + beforeAll(beforeAllFunction: SpecFunction): void; + afterAll(afterAllFunction: SpecFunction): void; + results(): NestedResults; + add(suiteOrSpec: SuiteOrSpec): void; + specs(): Spec[]; + suites(): Suite[]; + children(): any[]; + execute(onComplete?: () => void): void; + } + + interface XSuite { + execute(): void; + } + + interface Spy { + (...params: any[]): any; + + identity: string; + and: SpyAnd; + calls: Calls; + mostRecentCall: { args: any[]; }; + argsForCall: any[]; + wasCalled: boolean; + } + + interface SpyAnd { + /** By chaining the spy with and.callThrough, the spy will still track all calls to it but in addition it will delegate to the actual implementation. */ + callThrough(): Spy; + /** By chaining the spy with and.returnValue, all calls to the function will return a specific value. */ + returnValue(val: any): void; + /** By chaining the spy with and.callFake, all calls to the spy will delegate to the supplied function. */ + callFake(fn: Function): Spy; + /** By chaining the spy with and.throwError, all calls to the spy will throw the specified value. */ + throwError(msg: string): void; + /** When a calling strategy is used for a spy, the original stubbing behavior can be returned at any time with and.stub. */ + stub(): Spy; + } + + interface Calls { + /** By chaining the spy with calls.any(), will return false if the spy has not been called at all, and then true once at least one call happens. **/ + any(): boolean; + /** By chaining the spy with calls.count(), will return the number of times the spy was called **/ + count(): number; + /** By chaining the spy with calls.argsFor(), will return the arguments passed to call number index **/ + argsFor(index: number): any[]; + /** By chaining the spy with calls.allArgs(), will return the arguments to all calls **/ + allArgs(): any[]; + /** By chaining the spy with calls.all(), will return the context (the this) and arguments passed all calls **/ + all(): CallInfo[]; + /** By chaining the spy with calls.mostRecent(), will return the context (the this) and arguments for the most recent call **/ + mostRecent(): CallInfo; + /** By chaining the spy with calls.first(), will return the context (the this) and arguments for the first call **/ + first(): CallInfo; + /** By chaining the spy with calls.reset(), will clears all tracking for a spy **/ + reset(): void; + } + + interface CallInfo { + /** The context (the this) for the call */ + object: any; + /** All arguments passed to the call */ + args: any[]; + } + + interface Util { + inherit(childClass: Function, parentClass: Function): any; + formatException(e: any): any; + htmlEscape(str: string): string; + argsToArray(args: any): any; + extend(destination: any, source: any): any; + } + + interface JsApiReporter extends Reporter { + + started: boolean; + finished: boolean; + result: any; + messages: any; + + new (): any; + + suites(): Suite[]; + summarize_(suiteOrSpec: SuiteOrSpec): any; + results(): any; + resultsForSpec(specId: any): any; + log(str: any): any; + resultsForSpecs(specIds: any): any; + summarizeResult_(result: any): any; + } + + interface Jasmine { + Spec: Spec; + clock: Clock; + util: Util; + } + + export var HtmlReporter: HtmlReporter; + export var HtmlSpecFilter: HtmlSpecFilter; + export var DEFAULT_TIMEOUT_INTERVAL: number; +} diff --git a/src/main/typings/tsd.d.ts b/src/main/typings/tsd.d.ts new file mode 100644 index 0000000..3917153 --- /dev/null +++ b/src/main/typings/tsd.d.ts @@ -0,0 +1 @@ +/// diff --git a/src/test/html/index.html b/src/test/html/index.html new file mode 100644 index 0000000..5523d29 --- /dev/null +++ b/src/test/html/index.html @@ -0,0 +1,19 @@ + + + + + + + + EID Runtime Exceptions and Utilities for JavaScript (Test file) + + + + + + +
+ + + + diff --git a/src/test/ts/eid/exceptions/Eid.tests.ts b/src/test/ts/eid/exceptions/Eid.tests.ts new file mode 100644 index 0000000..7891a9e --- /dev/null +++ b/src/test/ts/eid/exceptions/Eid.tests.ts @@ -0,0 +1,30 @@ +import Eid from '../../../../main/ts/eid/exceptions/Eid'; + +describe('eid.exceptions.Eid', () => { + describe('#setUniqIdGenerator', () => { + afterEach(() => { + Eid.setUniqIdGenerator(Eid.DEFAULT_UNIQ_ID_GENERATOR); + }); + it('throws TypeError when given null', () => { + expect(() => { Eid.setUniqIdGenerator(null) }).toThrow(new TypeError('Unique ID generator can\'t be null, but given one')); + }); + it('sets properly new UniqIdGenerator impl, and return previous', () => { + // given + var generator = { + generateUniqId() { + // fail("Generator should not be executed while validating"); + return "constant"; + } + }; + // when + var oldGenerator = Eid.setUniqIdGenerator(generator); + // then + expect(oldGenerator).toEqual(Eid.DEFAULT_UNIQ_ID_GENERATOR); + // when + var replaced = Eid.setUniqIdGenerator(Eid.DEFAULT_UNIQ_ID_GENERATOR); + // then + expect(replaced).toBe(generator); + expect(replaced.generateUniqId()).toEqual("constant"); + }); + }); +}); diff --git a/src/test/ts/import-babel-polyfill.js b/src/test/ts/import-babel-polyfill.js new file mode 100644 index 0000000..b012711 --- /dev/null +++ b/src/test/ts/import-babel-polyfill.js @@ -0,0 +1 @@ +import 'babel-polyfill'; diff --git a/src/test/ts/tsconfig.json b/src/test/ts/tsconfig.json new file mode 100644 index 0000000..6d06a30 --- /dev/null +++ b/src/test/ts/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "es6", + "noImplicitAny": false, + "removeComments": false, + "preserveConstEnums": true, + "outDir": "../../../target/js" + }, + "filesGlob": [ + "../../main/ts/**/*.{ts,tsx}", + "../../main/typings/**/*.*.ts", + "**/*.{ts,tsx}" + ], + "exclude": [], + "compileOnSave": true, + "atom": { + "rewriteTsconfig": false + }, + "files": [] +} diff --git a/tsd.json b/tsd.json new file mode 100644 index 0000000..8f67717 --- /dev/null +++ b/tsd.json @@ -0,0 +1,12 @@ +{ + "version": "v4", + "repo": "borisyankov/DefinitelyTyped", + "ref": "master", + "path": "src/main/typings", + "bundle": "src/main/typings/tsd.d.ts", + "installed": { + "jasmine/jasmine.d.ts": { + "commit": "4c41545030eba44b27ce7209112dbf862f6b1d78" + } + } +} diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..cd7f264 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,29 @@ +/* eslint-disable no-var, strict, prefer-arrow-callback */ +'use strict'; + +var path = require('path'); + +module.exports = { + cache: true, + entry: { + main: './src/main/ts/eid.ts', + vendor: [ 'babel-polyfill' ] + }, + devtool: 'source-map', + output: { + path: path.resolve(__dirname, './target/webpack'), + filename: '[name].js', + chunkFilename: '[chunkhash].js' + }, + module: { + loaders: [ + { test: /\.ts(x?)$/, exclude: /node_modules/, loader: 'babel-loader?presets[]=es2015!ts-loader' }, + { test: /\.js$/, exclude: /node_modules/, loader: 'babel', query: { presets: ['es2015'] } } + ] + }, + plugins: [], + resolve: { + // Add `.ts` and `.tsx` as a resolvable extension. + extensions: ['', '.webpack.js', '.web.js', '.ts', '.tsx', '.js'] + }, +};