diff --git a/package-lock.json b/package-lock.json index a85179b..3ee1c99 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "vulcain-corejs", - "version": "2.0.0-beta939", + "version": "2.0.0-beta952", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -58,9 +58,9 @@ } }, "@types/mocha": { - "version": "2.2.48", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-2.2.48.tgz", - "integrity": "sha512-nlK/iyETgafGli8Zh9zJVCTicvU3iajSkRwOh3Hhiva598CMqNJ4NcVCGMTGKpGpTYj/9R8RLzS9NAykSSCqGw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.0.0.tgz", + "integrity": "sha512-ZS0vBV7Jn5Z/Q4T3VXauEKMDCV8nWOtJJg90OsDylkYJiQwcWtKuLzohWzrthBkerUF7DLMmJcwOPEP0i/AOXw==", "dev": true }, "@types/mongodb": { @@ -272,11 +272,6 @@ "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-0.0.2.tgz", "integrity": "sha1-JrOIXRD6E9t/wBquOquHAZngEkw=" }, - "buffer-shims": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", - "integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E=" - }, "bufrw": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/bufrw/-/bufrw-1.2.1.tgz", @@ -459,11 +454,6 @@ "xtend": "4.0.1" } }, - "es6-promise": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.2.1.tgz", - "integrity": "sha1-7FYjOGgDKQkgcXDDlEjiREndH8Q=" - }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -959,48 +949,17 @@ "integrity": "sha512-TCZ36BjURTeFTM/CwRcViQlfkMvL1/vFISuNLO5GkcVm1+QHfbSiNqZuWeMFjj1/3+uAjXswgRk30j1kkLYJBQ==" }, "mongodb": { - "version": "2.2.35", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-2.2.35.tgz", - "integrity": "sha512-3HGLucDg/8EeYMin3k+nFWChTA85hcYDCw1lPsWR6yV9A6RgKb24BkLiZ9ySZR+S0nfBjWoIUS7cyV6ceGx5Gg==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.0.5.tgz", + "integrity": "sha512-8ioTyyc8tkNwZCTDa1FPWvmpJFfvE484DnugC8KpVrw4AKAE03OOAlORl2yYTNtz3TX4Ab7FRo00vzgexB/67A==", "requires": { - "es6-promise": "3.2.1", - "mongodb-core": "2.1.19", - "readable-stream": "2.2.7" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "readable-stream": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.7.tgz", - "integrity": "sha1-BwV6y+JGeyIELTb5jFrVBwVOlbE=", - "requires": { - "buffer-shims": "1.0.0", - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "string_decoder": "1.0.3", - "util-deprecate": "1.0.2" - } - }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "requires": { - "safe-buffer": "5.1.1" - } - } + "mongodb-core": "3.0.5" } }, "mongodb-core": { - "version": "2.1.19", - "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-2.1.19.tgz", - "integrity": "sha512-Jt4AtWUkpuW03kRdYGxga4O65O1UHlFfvvInslEfLlGi+zDMxbBe3J2NVmN9qPJ957Mn6Iz0UpMtV80cmxCVxw==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-3.0.5.tgz", + "integrity": "sha512-4A1nx/xAU5d/NPICjiyzVxzNrIdJQQsYRe3xQkV1O638t+fHHfAOLK+SQagqGnu1m0aeSxb1ixp/P0FGSQWIGA==", "requires": { "bson": "1.0.6", "require_optional": "1.0.1" diff --git a/package.json b/package.json index e121823..1e593d3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vulcain-corejs", - "version": "2.0.0-beta939", + "version": "2.0.0-beta952", "description": "Vulcain micro-service framework", "main": "lib/src/index.js", "scripts": { @@ -23,33 +23,33 @@ "license": "Apache-2.0", "typings": "lib/src/index.d.ts", "devDependencies": { - "@types/amqplib": "^0.5.6", + "@types/amqplib": "^0.5.7", "@types/chai": "^4.1.0", "@types/fast-stats": "0.0.29", "@types/jsonwebtoken": "^7.2.5", - "@types/mocha": "^2.2.46", + "@types/mocha": "^5.0.0", "@types/mongodb": "^3.0.9", "chai": "^4.1.2", - "mocha": "^5.0.0", + "mocha": "^5.0.4", "tslint": "^5.9.1", - "typescript": "^2.6.2" + "typescript": "^2.7.2" }, "dependencies": { "amqplib": "^0.5.2", "fast-stats": "0.0.3", - "graphql": "^0.13.2", - "jaeger-client": "^3.7.0", - "jsonwebtoken": "^8.1.0", - "moment": "^2.20.1", - "mongodb": "^2.2.34", + "graphql": "^0.13.1", + "jaeger-client": "^3.10.0", + "jsonwebtoken": "^8.2.0", + "moment": "^2.21.0", + "mongodb": "^3.0.4", "prom-client": "^11.0.0", "reflect-metadata": "^0.1.3", "router": "^1.3.2", - "rxjs": "^5.5.6", - "swagger-ui-dist": "^3.9.0", + "rxjs": "^5.5.7", + "swagger-ui-dist": "^3.12.1", "unirest": "^0.5.0", "uuid": "^3.0.1", - "validator": "^9.2.0", + "validator": "^9.4.1", "zipkin": "^0.12.0", "zipkin-transport-http": "^0.12.0" } diff --git a/src/application.ts b/src/application.ts index f72c940..2e24f74 100644 --- a/src/application.ts +++ b/src/application.ts @@ -25,6 +25,7 @@ import { ActionHandler } from './pipeline/handlers/action/annotations'; import { GraphQLActionHandler } from './graphql/graphQLHandler'; import { GraphQLAdapter } from "./graphql/graphQLAdapter"; import { HystrixSSEStream as hystrixStream } from './commands/http/hystrixSSEStream'; +import { HandlerProcessor } from '.'; const vulcainExecutablePath = __dirname; const applicationPath = Path.dirname(module.parent.parent.filename); @@ -38,6 +39,7 @@ const applicationPath = Path.dirname(module.parent.parent.filename); */ export class Application { private _domain: Domain; + private _initialized = false; public useMongoProvider(address: string) { this.container.useMongoProvider(address); @@ -93,20 +95,28 @@ export class Application { throw new Error("Domain name is required."); } - Service.defaultDomainName = this.domainName; + Service.setDomainName(this.domainName); this._container = this._container || new Container(); - this.container.registerHTTPEndpoint("GET", Conventions.instance.defaultHystrixPath, hystrixStream.getHandler()); } - - private async init() { + + /** + * Only use it for testing. Used start instead + */ + public async init() { + if (this._initialized) + return; + + this._initialized = true; await DynamicConfiguration.init().startPolling(); - + Service.log.info(null, () => "Starting application"); - + this._container.injectInstance(this, DefaultServiceNames.Application); this._domain = new Domain(this.domainName, this._container); this._container.injectInstance(this._domain, DefaultServiceNames.Domain); + this.container.registerHTTPEndpoint("GET", Conventions.instance.defaultHystrixPath, hystrixStream.getHandler()); + process.on('unhandledRejection', (reason, p) => { Service.log.info(null, () => `Unhandled Rejection at ${p} reason ${reason}")`); }); @@ -124,6 +134,32 @@ export class Application { commandBus.stopReception(); } }); + + let local = new LocalAdapter(); + let eventBus = this.container.get(DefaultServiceNames.EventBusAdapter, true); + if (!eventBus) { + this.container.injectInstance(local, DefaultServiceNames.EventBusAdapter); + eventBus = local; + } + let commandBus = this.container.get(DefaultServiceNames.ActionBusAdapter, true); + if (!commandBus) { + this.container.injectInstance(local, DefaultServiceNames.ActionBusAdapter); + commandBus = local; + } + + this.registerComponents(); + Preloader.instance.runPreloads(this.container, this._domain); + + await eventBus.open(); + await commandBus.open(); + + let scopes = this.container.get(DefaultServiceNames.ScopesDescriptor); + this.defineScopeDescriptions(scopes); + + let descriptors = this.container.get(DefaultServiceNames.ServiceDescriptors); + descriptors.getDescriptions(); // ensures handlers table is created + + this.container.injectSingleton(HandlerProcessor, DefaultServiceNames.HandlerProcessor ); } /** @@ -151,30 +187,6 @@ export class Application { try { await this.init(); - let local = new LocalAdapter(); - let eventBus = this.container.get(DefaultServiceNames.EventBusAdapter, true); - if (!eventBus) { - this.container.injectInstance(local, DefaultServiceNames.EventBusAdapter); - eventBus = local; - } - let commandBus = this.container.get(DefaultServiceNames.ActionBusAdapter, true); - if (!commandBus) { - this.container.injectInstance(local, DefaultServiceNames.ActionBusAdapter); - commandBus = local; - } - - this.registerComponents(); - Preloader.instance.runPreloads(this.container, this._domain); - - await eventBus.open(); - await commandBus.open(); - - let scopes = this.container.get(DefaultServiceNames.ScopesDescriptor); - this.defineScopeDescriptions(scopes); - - let descriptors = this.container.get(DefaultServiceNames.ServiceDescriptors); - descriptors.getDescriptions(); // ensures handlers table is created - let server = new VulcainServer(this.domain.name, this._container); server.start(port); } diff --git a/src/bus/localAdapter.ts b/src/bus/localAdapter.ts index 4d1e283..6679f81 100644 --- a/src/bus/localAdapter.ts +++ b/src/bus/localAdapter.ts @@ -1,39 +1,37 @@ import { IActionBusAdapter, IEventBusAdapter } from '../bus/busAdapter'; import { EventData } from "./messageBus"; import { RequestData } from "../pipeline/common"; +import * as RX from 'rxjs'; export class LocalAdapter implements IActionBusAdapter, IEventBusAdapter { - private eventHandler: (event: EventData) => void; - private commandHandler: (event: RequestData) => void; + private eventQueue: RX.Subject; + private taskQueue: RX.Subject; open() { + this.eventQueue = new RX.Subject(); + this.taskQueue = new RX.Subject(); return Promise.resolve(); } - stopReception() { } + stopReception() { + this.eventQueue = null; + this.taskQueue = null; + } sendEvent(domain: string, event: EventData) { - // console.log("Event: %j", event); - let self = this; - self.eventHandler && setTimeout(function () { - self.eventHandler(event); - }, (1)); + this.eventQueue && this.eventQueue.next(event); } - consumeEvents(domain: string, handler: (event: EventData) => void, queueName?:string) { - this.eventHandler = handler; + consumeEvents(domain: string, handler: (event: EventData) => void, queueName?: string) { + this.eventQueue && this.eventQueue.subscribe(handler); } publishTask(domain: string, serviceId: string, command: RequestData) { - let self = this; - self.commandHandler && setTimeout(function () { - // console.log("Running task: %j", command); - self.commandHandler(command); - }, (1)); + this.taskQueue && this.taskQueue.next(command); } consumeTask(domain: string, serviceId: string, handler: (event: RequestData) => void) { - this.commandHandler = handler; + this.taskQueue && this.taskQueue.subscribe(handler); } } \ No newline at end of file diff --git a/src/bus/messageBus.ts b/src/bus/messageBus.ts index 146bba7..832418d 100644 --- a/src/bus/messageBus.ts +++ b/src/bus/messageBus.ts @@ -15,8 +15,8 @@ export interface EventData extends RequestData { source: string; error?: string; userContext: UserContextData; - startedAt: string; - completedAt?: string; + startedAt: number; + completedAt?: number; status: string; } diff --git a/src/commands/command.ts b/src/commands/command.ts index b3579fd..dfa3f80 100644 --- a/src/commands/command.ts +++ b/src/commands/command.ts @@ -60,6 +60,8 @@ export class HystrixCommand { let result; + this.command.context.logInfo(() => `Executing command ${this.properties.commandName} with properties ${JSON.stringify(this.properties)}`); + // Execution this.hystrixMetrics.incrementExecutionCount(); let recordTotalTime = true; diff --git a/src/configurations/properties/chainedPropertyValue.ts b/src/configurations/properties/chainedPropertyValue.ts index fc9ef92..e8d2b71 100644 --- a/src/configurations/properties/chainedPropertyValue.ts +++ b/src/configurations/properties/chainedPropertyValue.ts @@ -67,4 +67,8 @@ export class ChainedDynamicProperty extends DynamicProperty { v = this._activeProperty.value; return v || this.defaultValue; } + + public toJSON(key: string) { + return key ? String(this.value) : this.name + "=" + String(this.value); + } } diff --git a/src/configurations/properties/dynamicProperty.ts b/src/configurations/properties/dynamicProperty.ts index b0ba630..59e7fb3 100644 --- a/src/configurations/properties/dynamicProperty.ts +++ b/src/configurations/properties/dynamicProperty.ts @@ -74,4 +74,8 @@ export class DynamicProperty implements IDynamicProperty, IUpdatableProper this._propertyChanged = null; this.removed = true; } + + public toJSON(key: string) { + return key ? String(this.value) : this.name + "=" + String(this.value); + } } diff --git a/src/configurations/sources/httpConfigurationSource.ts b/src/configurations/sources/httpConfigurationSource.ts index 5078183..b39cafa 100644 --- a/src/configurations/sources/httpConfigurationSource.ts +++ b/src/configurations/sources/httpConfigurationSource.ts @@ -3,7 +3,6 @@ import { AbstractRemoteSource } from "./abstractRemoteSource"; import { Service } from "../../globals/system"; const rest = require('unirest'); -const moment = require('moment'); export class HttpConfigurationSource extends AbstractRemoteSource { protected lastUpdate: string; @@ -49,7 +48,7 @@ export class HttpConfigurationSource extends AbstractRemoteSource { values = new Map(); let data = response.body; data.value && data.value.forEach(cfg => values.set(cfg.key, cfg)); - self.lastUpdate = moment.utc().format(); + self.lastUpdate = Service.nowAsString(); self.mergeChanges(values); } } diff --git a/src/configurations/sources/vulcainConfigurationSource.ts b/src/configurations/sources/vulcainConfigurationSource.ts index 95212aa..33e8aad 100644 --- a/src/configurations/sources/vulcainConfigurationSource.ts +++ b/src/configurations/sources/vulcainConfigurationSource.ts @@ -2,7 +2,6 @@ import { HttpConfigurationSource } from './httpConfigurationSource'; import { DataSource } from '../abstractions'; import { Service } from '../../globals/system'; const rest = require('unirest'); -const moment = require('moment'); export class VulcainConfigurationSource extends HttpConfigurationSource { diff --git a/src/di/containers.ts b/src/di/containers.ts index a54ebee..2fc2b9a 100644 --- a/src/di/containers.ts +++ b/src/di/containers.ts @@ -169,8 +169,13 @@ export class Container implements IContainer { // Insert always in first position // so 'get' take the last inserted private addResolver(name: string, resolver: IResolver) { - let list = this.resolvers.get(name) || []; - list.unshift(resolver); + let list = this.resolvers.get(name); + if (!list) { + list = [resolver]; + } + else { + list.unshift(resolver); + } this.resolvers.set(name, list); } diff --git a/src/globals/settings.ts b/src/globals/settings.ts index 8c2f72a..ab29595 100644 --- a/src/globals/settings.ts +++ b/src/globals/settings.ts @@ -1,5 +1,4 @@ import { VulcainLogger } from './../log/vulcainLogger'; -import * as moment from 'moment'; import * as fs from 'fs'; import { Conventions } from '../utils/conventions'; import { DefaultServiceNames } from '../di/annotations'; diff --git a/src/globals/system.ts b/src/globals/system.ts index 2351d8d..16fc599 100644 --- a/src/globals/system.ts +++ b/src/globals/system.ts @@ -1,6 +1,5 @@ import { CryptoHelper } from '../utils/crypto'; import { VulcainLogger } from './../log/vulcainLogger'; -import * as moment from 'moment'; import * as fs from 'fs'; import { VulcainManifest } from './manifest'; import { Conventions } from '../utils/conventions'; @@ -37,10 +36,14 @@ export class Service { private static crypter: CryptoHelper; private static _manifest: VulcainManifest; private static _stubManager: IStubManager; - static defaultDomainName: string; private static _serviceStatus: ServiceStatus = ServiceStatus.Starting; private static _statusTimer: NodeJS.Timer; - + private static _fullServiceName: string; + + public static setDomainName(name: string) { + if(name) + Service._domainName = name; + } /** * Settings properties from vulcain.json file */ @@ -120,7 +123,7 @@ export class Service { * @memberOf System */ static nowAsString() { - return moment.utc().format(); + return new Date(Date.now()).toUTCString(); } /** @@ -281,10 +284,7 @@ export class Service { static get serviceName() { if (!Service._serviceName) { let env = process.env[Conventions.instance.ENV_SERVICE_NAME]; - if (env) - Service._serviceName = env; - else - return null; + Service._serviceName = env || "no-name"; } return Service._serviceName; } @@ -300,16 +300,16 @@ export class Service { static get serviceVersion() { if (!Service._serviceVersion) { let env = process.env[Conventions.instance.ENV_SERVICE_VERSION]; - if (env) - Service._serviceVersion = env; - else - return null; + Service._serviceVersion = env || "1.0"; } return Service._serviceVersion; } static get fullServiceName() { - return this.serviceName + "-" + this.serviceVersion; + if (!this._fullServiceName) { + this._fullServiceName = this.serviceName + "-" + this.serviceVersion; + } + return this._fullServiceName; } /** @@ -326,7 +326,7 @@ export class Service { if (env) Service._domainName = env; else - Service._domainName = Service.defaultDomainName; + Service._domainName = "vulcain"; } return Service._domainName; } diff --git a/src/instrumentations/common.ts b/src/instrumentations/common.ts index 82e417c..21b822c 100644 --- a/src/instrumentations/common.ts +++ b/src/instrumentations/common.ts @@ -12,7 +12,7 @@ export enum SpanKind { export interface TrackerId { correlationId?: string; parentId?: string; - spanId: string; + spanId?: string; } export interface ITracker { diff --git a/src/instrumentations/metrics/prometheusMetrics.ts b/src/instrumentations/metrics/prometheusMetrics.ts index 45f222f..844cb2d 100644 --- a/src/instrumentations/metrics/prometheusMetrics.ts +++ b/src/instrumentations/metrics/prometheusMetrics.ts @@ -17,6 +17,7 @@ export class PrometheusMetrics implements IMetrics { private ignoredProperties = ["hystrixProperties", "params"]; constructor(private container: IContainer) { + Service.log.info(null, () => `Providing prometheus metrics from '/metrics'`); container.registerHTTPEndpoint("GET", '/metrics', (req: http.IncomingMessage, resp: http.ServerResponse) => { const chunk = new Buffer(Prometheus.register.metrics(), 'utf8'); diff --git a/src/instrumentations/span.ts b/src/instrumentations/span.ts index 2a6bc66..42c6c7d 100644 --- a/src/instrumentations/span.ts +++ b/src/instrumentations/span.ts @@ -31,17 +31,12 @@ export class Span implements ISpanTracker { return this._tracker; } - private constructor(public context: RequestContext, public kind: SpanKind, private name: string, parent: TrackerId | ISpanTracker) { + private constructor(public context: RequestContext, public kind: SpanKind, private name: string, parentId: TrackerId) { this._logger = context.container.get(DefaultServiceNames.Logger); this.startTime = Date.now(); this.startTick = process.hrtime(); - let parentId = parent; - if ((parent).id) { - parentId = (parent).id; - } - this._id = { correlationId: parentId.correlationId, spanId: !parentId.spanId ? parentId.correlationId : this.randomTraceId(), @@ -50,9 +45,10 @@ export class Span implements ISpanTracker { this.metrics = context.container.get(DefaultServiceNames.Metrics); - this.addTag("name",name); + this.addTag("name", name); this.addTag("domain", Service.domainName); this.addTag("host", os.hostname()); + this.addTag("correlationId", parentId.correlationId); this.convertKind(); } @@ -62,11 +58,11 @@ export class Span implements ISpanTracker { } createCommandTracker(context: RequestContext, commandName: string) { - return new Span(context, SpanKind.Command, commandName, this); + return new Span(context, SpanKind.Command, commandName, this._id); } createCustomTracker(context: RequestContext, name: string, tags?: {[index:string]:string}): ITracker { - let span = new Span(context, SpanKind.Custom, name, this); + let span = new Span(context, SpanKind.Custom, name, this._id); span.trackAction(name, tags); return span; } @@ -144,6 +140,8 @@ export class Span implements ISpanTracker { this.endRequest(); if (this._tracker) { + Object.keys(this.tags).forEach(key => this._tracker.addTag(key, this.tags[key])); + this._tracker.finish(); this._tracker = null; } @@ -153,7 +151,7 @@ export class Span implements ISpanTracker { endCommand() { if (this.action) { // for ignored requests like _servicedependency - this.addTag("error", this.error ? "true" : "false"); + this.addTag("error", this.error ? this.error.toString() : "false"); let metricsName = `vulcain_${this.commandType.toLowerCase()}command_duration_ms`; this.metrics.timing(metricsName, this.durationInMs, this.tags); } @@ -164,8 +162,11 @@ export class Span implements ISpanTracker { private endRequest() { if (this.action) { // for ignored requests like _servicedependency - let hasError = !!this.error || !this.context.response || this.context.response.statusCode && this.context.response.statusCode >= 400; - this.addTag("error", hasError ? "true" : "false"); + if (!this.error && this.kind === SpanKind.Request && this.context.response && this.context.response.statusCode && this.context.response.statusCode >= 400) { + this.error = new Error("Http error " + this.context.response.statusCode); + } + this.addTag("error", this.error ? this.error.toString() : "false"); + this.metrics.timing("vulcain_service_duration_ms", this.durationInMs, this.tags); } @@ -213,7 +214,6 @@ export class Span implements ISpanTracker { value = JSON.stringify(value); } this.tags[name] = value; - this._tracker && this._tracker.addTag(name, value); } catch (e) { this.context.logError(e); diff --git a/src/instrumentations/trackers/JaegerInstrumentation.ts b/src/instrumentations/trackers/JaegerInstrumentation.ts index 64c6c9f..efde8a8 100644 --- a/src/instrumentations/trackers/JaegerInstrumentation.ts +++ b/src/instrumentations/trackers/JaegerInstrumentation.ts @@ -6,6 +6,7 @@ import { ITrackerAdapter, IRequestTrackerFactory } from './index'; import { IRequestContext } from "../../pipeline/common"; import { TrackerId, SpanKind, ISpanTracker } from '../../instrumentations/common'; import { Service } from '../../globals/system'; +import * as URL from 'url'; export class JaegerInstrumentation implements IRequestTrackerFactory { @@ -16,14 +17,17 @@ export class JaegerInstrumentation implements IRequestTrackerFactory { jaegerAddress = "http://" + jaegerAddress; } if (!/:[0-9]+/.test(jaegerAddress)) { - jaegerAddress = jaegerAddress + ':9411'; + jaegerAddress = jaegerAddress + ':6832'; } - - const sender = new UDPSender(); + + let url = URL.parse(jaegerAddress); + const sender = new UDPSender({host:url.hostname, port: url.port}); const tracer = new jaeger.Tracer(Service.fullServiceName, new jaeger.RemoteReporter(sender), new jaeger.RateLimitingSampler(1)); - + + Service.log.info(null, () => `Enabling Jaeger instrumentation at ${jaegerAddress}`); + return new JaegerInstrumentation(tracer); } return null; diff --git a/src/instrumentations/trackers/zipkinInstrumentation.ts b/src/instrumentations/trackers/zipkinInstrumentation.ts index ba20655..b39af5c 100644 --- a/src/instrumentations/trackers/zipkinInstrumentation.ts +++ b/src/instrumentations/trackers/zipkinInstrumentation.ts @@ -29,6 +29,9 @@ export class ZipkinInstrumentation implements IRequestTrackerFactory { if (!/:[0-9]+/.test(zipkinAddress)) { zipkinAddress = zipkinAddress + ':9411'; } + + Service.log.info(null, () => `Enabling Zipkin instrumentation at ${zipkinAddress}`); + const recorder = new BatchRecorder({ logger: new HttpLogger({ endpoint: `${zipkinAddress}/api/v1/spans`, diff --git a/src/pipeline/handlerProcessor.ts b/src/pipeline/handlerProcessor.ts index f3670cd..9662a82 100644 --- a/src/pipeline/handlerProcessor.ts +++ b/src/pipeline/handlerProcessor.ts @@ -13,7 +13,6 @@ import { QueryManager } from "./handlers/query/queryManager"; import { IManager } from "./handlers/definitions"; import { ActionDefinition } from "./handlers/action/definitions"; -@Injectable(LifeTime.Singleton, DefaultServiceNames.HandlerProcessor) export class HandlerProcessor { private actionManager: CommandManager; private queryManager: QueryManager; diff --git a/src/pipeline/handlers/action/actionManager.ts b/src/pipeline/handlers/action/actionManager.ts index 2b0454f..a41c1bc 100644 --- a/src/pipeline/handlers/action/actionManager.ts +++ b/src/pipeline/handlers/action/actionManager.ts @@ -22,10 +22,10 @@ import { Utils } from '../utils'; export interface AsyncTaskData extends RequestData { status?: "Error" | "Success" | "Pending" | "Running"; taskId?: string; - submitAt?: string; - startedAt?: string; + submitAt?: number; + startedAt?: number; userContext?: UserContextData; - completedAt?: string; + completedAt?: number; } export interface ActionResult { @@ -63,7 +63,7 @@ export class CommandManager implements IManager { this.subscribeToEvents(); } - private async validateRequestData(ctx: RequestContext, info:Handler, command: RequestData, skipValidation: boolean) { + private validateRequestData(ctx: RequestContext, info:Handler, command: RequestData, skipValidation: boolean) { let errors; let inputSchema = info.definition.inputSchema; if (inputSchema && inputSchema !== "none") { @@ -81,14 +81,14 @@ export class CommandManager implements IManager { } } if (!skipValidation) { - errors = await schema.validate(ctx, command.params); + errors = schema.validate(ctx, command.params); } } if (!skipValidation && !errors) { // Search if a method naming validate exists let methodName = 'validate' + inputSchema; - errors = info.handler[methodName] && await info.handler[methodName](command.params, command.action); + errors = info.handler[methodName] && info.handler[methodName](command.params, command.action); } } @@ -102,7 +102,7 @@ export class CommandManager implements IManager { action: ctx.requestData.action, schema: ctx.requestData.schema, source: Service.fullServiceName, - startedAt: Service.nowAsString(), + startedAt: Date.now(), value: result && Utils.obfuscateSensibleData(this.domain, ctx.container, result), error: error && error.message, userContext: ctx.user.getUserContext(), @@ -118,7 +118,7 @@ export class CommandManager implements IManager { try { let skipValidation = def.skipDataValidation || (def.name === "delete" && def.skipDataValidation === undefined); - let errors = await this.validateRequestData(ctx, info, command, skipValidation); + let errors = this.validateRequestData(ctx, info, command, skipValidation); if (errors && Object.keys(errors).length > 0) { throw new BadRequestError("Validation errors", errors); } @@ -156,7 +156,7 @@ export class CommandManager implements IManager { else { // Asynchronous task let pendingTask: AsyncTaskData = Object.assign({}, ctx.getRequestDataObject(), { - submitAt: Service.nowAsString(), + submitAt: Date.now(), status: "Pending" }); @@ -193,14 +193,17 @@ export class CommandManager implements IManager { if ((eventDef.mode === EventNotificationMode.successOnly && !error) || eventDef.mode === EventNotificationMode.always) { let event = this.createEvent(ctx, error ? "Error" : "Success", result, error); - event.completedAt = Service.nowAsString(); + event.completedAt = Date.now(); if (eventDef.factory) event = eventDef.factory(ctx, event); + if(eventDef.schema) + event.schema = eventDef.schema; + // Redispatch event in EVENT mode only if this is a new event schema - if (event && (source !== "EVENT" || (eventDef.schema && eventDef.schema !== (def).subscribeToSchema))) { - ctx.logInfo(() => `Sending event ${eventDef.schema || def.outputSchema}`); + if (event && (source !== "EVENT" || (event.schema !== (def).subscribeToSchema))) { + ctx.logInfo(() => `Sending event ${event.schema}`); this.messageBus.sendEvent(event); } @@ -210,7 +213,7 @@ export class CommandManager implements IManager { async processAsyncTask(command: AsyncTaskData) { let ctx = new RequestContext(this.container, Pipeline.AsyncTask, command); - ctx.setSecurityManager(command.userContext); + ctx.setSecurityContext(command.userContext); let processor = this.container.get(DefaultServiceNames.HandlerProcessor); let info = processor.getHandlerInfo(ctx.container, command.schema, command.action); @@ -226,7 +229,7 @@ export class CommandManager implements IManager { try { let res; command.status = "Running"; - command.startedAt = Service.nowAsString(); + command.startedAt = Date.now(); if (taskManager) await taskManager.updateTask(command); @@ -249,7 +252,7 @@ export class CommandManager implements IManager { command.status = "Error"; } finally { - command.completedAt = Service.nowAsString(); + command.completedAt = Date.now(); if (taskManager) await taskManager.updateTask(command); ctx.dispose(); @@ -272,16 +275,18 @@ export class CommandManager implements IManager { // Subscribe to events for a domain, a schema and an action // Get event stream for a domain let events = this.messageBus.getOrCreateEventQueue(def.subscribeToDomain || this.domain.name, def.distributionMode === "once" ? def.distributionKey: null); - events = events.filter(e => !e[MessageBus.LocalEventSymbol]); // already sent ? + events = events.filter(e => !e[MessageBus.LocalEventSymbol] && !!e.schema); // already sent ? // Filtered by schema if (def.subscribeToSchema !== '*') { events = events.filter(e => e.schema === def.subscribeToSchema); } + // Filtered by action if (def.subscribeToAction !== '*') { events = events.filter(e => !e.action || (e.action.toLowerCase() === def.subscribeToAction)); } + // And by custom filter if (def.filter) events = def.filter(events); @@ -294,7 +299,7 @@ export class CommandManager implements IManager { try { try { ctx.requestTracker.trackAction(evt.vulcainVerb); - ctx.setSecurityManager(evt.userContext); + ctx.setSecurityContext(evt.userContext); handler = ctx.container.resolve(info.handler); handler.context = ctx; handler.event = evt; @@ -307,16 +312,17 @@ export class CommandManager implements IManager { let error; try { let res = await handler[info.methodName](evt.value); - if (res !== undefined) - evt.value = res; + evt.value = res; } catch (e) { error = (e instanceof CommandRuntimeError && e.error) ? e.error : e; - ctx.logError(error, () => `Error with event handler ${info.handler.name} event : ${evt}`); + ctx.logError(error, () => `Error with event handler ${info.handler.name} event : ${JSON.stringify(evt)}`); } - let e = this.emitEvent("EVENT", ctx, def, evt.value, error); - MessageBus.emitLocalEvent(def.name, e); + if (evt.value) { + let e = this.emitEvent("EVENT", ctx, def, evt.value, error); + MessageBus.emitLocalEvent(def.name, e); + } } finally { ctx.dispose(); diff --git a/src/pipeline/handlers/query/queryManager.ts b/src/pipeline/handlers/query/queryManager.ts index d8c7e45..711de5f 100644 --- a/src/pipeline/handlers/query/queryManager.ts +++ b/src/pipeline/handlers/query/queryManager.ts @@ -29,7 +29,7 @@ export class QueryManager implements IManager { constructor(public container: IContainer) { } - private async validateRequestData(ctx: RequestContext, info: Handler, query) { + private validateRequestData(ctx: RequestContext, info: Handler, query) { let errors; let inputSchema = info.definition.inputSchema; if (inputSchema && inputSchema !== "none") { @@ -40,13 +40,13 @@ export class QueryManager implements IManager { // Custom binding if any query.params = schema.coerce(query.params); - errors = await schema.validate(ctx, query.params); + errors = schema.validate(ctx, query.params); } if (!errors) { // Search if a method naming validate[Async] exists let methodName = 'validate' + inputSchema; - errors = info.handler[methodName] && await info.handler[methodName](query.params, query.action); + errors = info.handler[methodName] && info.handler[methodName](query.params, query.action); } } return errors; @@ -57,7 +57,7 @@ export class QueryManager implements IManager { let logger = this.container.get(DefaultServiceNames.Logger); try { - let errors = await this.validateRequestData(ctx, info, query); + let errors = this.validateRequestData(ctx, info, query); if (errors && Object.keys(errors).length > 0) { throw new BadRequestError("Validation errors", errors); } diff --git a/src/pipeline/middlewares/authenticationMiddleware.ts b/src/pipeline/middlewares/authenticationMiddleware.ts index 006307b..3f2e5a5 100644 --- a/src/pipeline/middlewares/authenticationMiddleware.ts +++ b/src/pipeline/middlewares/authenticationMiddleware.ts @@ -7,7 +7,7 @@ export class AuthenticationMiddleware extends VulcainMiddleware { async invoke(ctx: RequestContext) { let tenantPolicy = ctx.container.get(DefaultServiceNames.TenantPolicy); - ctx.setSecurityManager(tenantPolicy.resolveTenant(ctx)); + ctx.setSecurityContext(tenantPolicy.resolveTenant(ctx)); await ctx.user.process(ctx); diff --git a/src/pipeline/middlewares/normalizeDataMiddleware.ts b/src/pipeline/middlewares/normalizeDataMiddleware.ts index 168d902..7a4b5d1 100644 --- a/src/pipeline/middlewares/normalizeDataMiddleware.ts +++ b/src/pipeline/middlewares/normalizeDataMiddleware.ts @@ -21,7 +21,7 @@ export class NormalizeDataMiddleware extends VulcainMiddleware { // $pageSize, $page async invoke(ctx: RequestContext) { try { - this.populateData(ctx); + ctx.normalize(); } catch (e) { ctx.logError(e, () => "Bad request format for " + ctx.request.url.pathname); @@ -29,7 +29,7 @@ export class NormalizeDataMiddleware extends VulcainMiddleware { return; } - try { + try { await super.invoke(ctx); if (!ctx.response) { ctx.response = new HttpResponse({}); @@ -39,101 +39,19 @@ export class NormalizeDataMiddleware extends VulcainMiddleware { if (!(e instanceof ApplicationError)) { e = new ApplicationError(e.message, 500); } - ctx.logError(e, () => "Request has error"); + if (!(e instanceof ApplicationError) || e.statusCode !== 405) { + // Don't pollute logs with incorect request + ctx.logError(e, () => "Request has error"); + } ctx.response = HttpResponse.createFromError(e); } // Inject request context in response - if( Service.isTestEnvironment) - ctx.response.addHeader('Access-Control-Allow-Origin', '*'); // CORS + ctx.response.addHeader('Access-Control-Allow-Origin', '*'); // CORS if (Object.getOwnPropertyDescriptor(ctx.response.content, "value") || Object.getOwnPropertyDescriptor(ctx.response.content, "error")) { ctx.response.content.meta = ctx.response.content.meta || {}; ctx.response.content.meta.correlationId = ctx.requestData.correlationId; } } - - private populateData(ctx: RequestContext) { - let action: string; - let schema: string; - - const url = ctx.request.url; - const body = ctx.request.body; - - ctx.requestData.body = body; - - // Try to get schema and action from path - let schemaAction = url.pathname.substr(Conventions.instance.defaultUrlprefix.length + 1); - if (schemaAction) { - if (schemaAction[schemaAction.length - 1] === '/') - schemaAction = schemaAction.substr(0, schemaAction.length - 1); - } - - // Split schema and action (schema is optional) - if (schemaAction) { - if (schemaAction.indexOf('.') >= 0) { - let parts = schemaAction.split('.'); - schema = parts[0]; - action = parts[1]; - } - else { - action = schemaAction; - } - } - // Schema and action can be in the body - if (body) { - // Body contains only data -> create a new action object - if (!ctx.requestData.body.action && !ctx.requestData.body.params && !ctx.requestData.body.schema) { - ctx.requestData.params = ctx.requestData.body; - } - else { - action = ctx.requestData.body.action || action; - schema = ctx.requestData.body.schema || schema; - } - } - else { - url.query && Object.keys(url.query).forEach(k => { - if (k[0] !== "$") { - ctx.requestData.params = ctx.requestData.params || {}; - ctx.requestData.params[k] = url.query[k]; - } - }); - } - - ctx.requestData.params = ctx.requestData.params || {}; - - // Or can be forced in the url query - if (url.query["$action"]) - action = url.query["$action"]; - if (url.query["_schema"]) - schema = url.query["_schema"]; - - ctx.requestData.action = action || (!body && "all") || null; - ctx.requestData.schema = schema; - - // if (ctx.request.verb === "GET" && ctx.requestData.action !== "get") { - ctx.requestData.page = 0; - ctx.requestData.pageSize = 20; - //} - // Normalize option values - Object.keys(url.query).forEach(name => { - try { - switch (name.toLowerCase()) { - case "$page": - ctx.requestData.page = (url.query["$page"] && parseInt(url.query["$page"])) || ctx.requestData.page; - break; - case "$pagesize": - ctx.requestData.pageSize = (url.query[name] && parseInt(url.query[name])) || ctx.requestData.pageSize; - break; - case "$query": - ctx.requestData.params = url.query["$query"] && JSON.parse(url.query["$query"]); - break; - } - } - catch (ex) {/*ignore*/ } - }); - - ctx.requestData.vulcainVerb = ctx.requestData.schema ? `${ctx.requestData.schema}.${ctx.requestData.action}` : ctx.requestData.action; - ctx.requestTracker.trackAction(ctx.requestData.vulcainVerb); - } } \ No newline at end of file diff --git a/src/pipeline/requestContext.ts b/src/pipeline/requestContext.ts index 5e528b1..8355c97 100644 --- a/src/pipeline/requestContext.ts +++ b/src/pipeline/requestContext.ts @@ -12,6 +12,7 @@ import { ISpanRequestTracker, DummySpanTracker, TrackerId } from '../instrumenta import { Span } from '../instrumentations/span'; import { Conventions } from '../utils/conventions'; import { Service, ServiceStatus } from '../globals/system'; +import { URL } from 'url'; export class VulcainHeaderNames { static X_VULCAIN_TENANT = "x-vulcain-tenant"; @@ -163,13 +164,14 @@ export class RequestContext implements IRequestContext { }; } + const parentId = this.pipeline !== Pipeline.Event + ? (this.request && this.request.headers[VulcainHeaderNames.X_VULCAIN_PARENT_ID]) || null + : this.requestData.correlationId; + if(!this.requestData.correlationId) this.requestData.correlationId = (this.request && this.request.headers[VulcainHeaderNames.X_VULCAIN_CORRELATION_ID]) || Conventions.getRandomId(); - + if (this.pipeline !== Pipeline.Test) { - // For event we do not use parentId to chain traces. - // However all traces can be aggregated with the correlationId tag. - const parentId = (this.pipeline !== Pipeline.Event && this.request && this.request.headers[VulcainHeaderNames.X_VULCAIN_PARENT_ID]) || null; const trackerId: TrackerId = { spanId: parentId, correlationId: this.requestData.correlationId }; this._tracker = Span.createRequestTracker(this, trackerId); } @@ -219,7 +221,7 @@ export class RequestContext implements IRequestContext { return this._securityManager; } - setSecurityManager(tenant: string|UserContextData) { + setSecurityContext(tenant: string|UserContextData) { if (!tenant) throw new Error("Tenant can not be null"); let manager = this.container.get(DefaultServiceNames.SecurityManager, true); @@ -310,4 +312,88 @@ export class RequestContext implements IRequestContext { this._tracker.dispose(); this.container.dispose(); } + + normalize() { + let action: string; + let schema: string; + + const url = this.request && this.request.url; + const body = this.request && this.request.body; + + this.requestData.body = body; + + // Try to get schema and action from path + let schemaAction = url && url.pathname.substr(Conventions.instance.defaultUrlprefix.length + 1); + if (schemaAction) { + if (schemaAction[schemaAction.length - 1] === '/') + schemaAction = schemaAction.substr(0, schemaAction.length - 1); + } + + // Split schema and action (schema is optional) + if (schemaAction) { + if (schemaAction.indexOf('.') >= 0) { + let parts = schemaAction.split('.'); + schema = parts[0]; + action = parts[1]; + } + else { + action = schemaAction; + } + } + // Schema and action can be in the body + if (body) { + // Body contains only data -> create a new action object + if (!this.requestData.body.action && !this.requestData.body.params && !this.requestData.body.schema) { + this.requestData.params = this.requestData.body; + } + else { + action = this.requestData.body.action || action; + schema = this.requestData.body.schema || schema; + } + } + else { + url && url.query && Object.keys(url.query).forEach(k => { + if (k[0] !== "$") { + this.requestData.params = this.requestData.params || {}; + this.requestData.params[k] = url.query[k]; + } + }); + } + + this.requestData.params = this.requestData.params || {}; + this.requestData.page = 0; + this.requestData.pageSize = 20; + + // Or can be forced in the url query + if (url && url.query) { + if (url.query["$action"]) + action = url.query["$action"]; + if (url.query["_schema"]) + schema = url.query["_schema"]; + + // Normalize option values + Object.keys(url.query).forEach(name => { + try { + switch (name.toLowerCase()) { + case "$page": + this.requestData.page = (url.query["$page"] && parseInt(url.query["$page"])) || this.requestData.page; + break; + case "$pagesize": + this.requestData.pageSize = (url.query[name] && parseInt(url.query[name])) || this.requestData.pageSize; + break; + case "$query": + this.requestData.params = url.query["$query"] && JSON.parse(url.query["$query"]); + break; + } + } + catch (ex) {/*ignore*/ } + }); + } + + this.requestData.action = action || (!body && "all") || null; + this.requestData.schema = schema; + + this.requestData.vulcainVerb = this.requestData.schema ? `${this.requestData.schema}.${this.requestData.action}` : this.requestData.action; + this.requestTracker.trackAction(this.requestData.vulcainVerb); + } } \ No newline at end of file diff --git a/src/pipeline/serverAdapter.ts b/src/pipeline/serverAdapter.ts index eeafa98..86571b8 100644 --- a/src/pipeline/serverAdapter.ts +++ b/src/pipeline/serverAdapter.ts @@ -139,7 +139,7 @@ export class HttpAdapter extends ServerAdapter { return; } - if (req.method === "OPTIONS" && Service.isTestEnvironment) { + if (req.method === "OPTIONS") { resp.setHeader("Access-Control-Allow-Origin", "*"); resp.setHeader("Access-Control-Allow-Methods", "GET,POST"); resp.setHeader("Access-Control-Allow-Headers", "origin, content-type, accept"); diff --git a/src/pipeline/testContext.ts b/src/pipeline/testContext.ts index 3625188..2b2d11b 100644 --- a/src/pipeline/testContext.ts +++ b/src/pipeline/testContext.ts @@ -6,7 +6,7 @@ import { IContainer } from "../di/resolvers"; import { UserContext } from "../security/securityContext"; import { Container } from "../di/containers"; import { RequestContext } from "./requestContext"; -import { Pipeline } from "./common"; +import { Pipeline, IRequestContext } from "./common"; import { AbstractHandler } from "./handlers/abstractHandlers"; import { DefaultServiceNames } from '../di/annotations'; @@ -15,7 +15,7 @@ export class TestContext extends RequestContext { return this.container; } - constructor(...components: Function[]) { + constructor() { super(new Container(), Pipeline.Test); let domain = new Domain(Service.domainName, this.container); this.container.injectInstance(domain, DefaultServiceNames.Domain); @@ -24,7 +24,7 @@ export class TestContext extends RequestContext { } setUser(user: UserContext) { - this.setSecurityManager(user); + this.setSecurityContext(user); return this; } @@ -33,13 +33,18 @@ export class TestContext extends RequestContext { } get context() { - let ctx = new RequestContext(this.container, Pipeline.Test); - ctx.setSecurityManager("test"); + return TestContext.newContext(this.container); + } + + static newContext(container: IContainer, data?: any): IRequestContext { + let ctx = new RequestContext(container, Pipeline.Test, data); + ctx.setSecurityContext("test"); + ctx.normalize(); return ctx; } createHandler(handler: Function): T { - let ctx = this.context; + let ctx = this.context; let scopedContainer = new Container(this.container, ctx); let h = new (<(container: IContainer) => void>handler)(scopedContainer); h.context = ctx; diff --git a/src/providers/mongo/provider.ts b/src/providers/mongo/provider.ts index 22e68bf..ec2cd7a 100644 --- a/src/providers/mongo/provider.ts +++ b/src/providers/mongo/provider.ts @@ -74,14 +74,14 @@ export class MongoProviderFactory implements IProviderFactory { */ class MongoProvider implements IProvider { - private tenant: string; - private _logger: Logger; + private databaseName: string; + private logger: Logger; public state: { keyPropertyNameBySchemas: Map; uri: string; dispose?: () => void; - _mongo?; + mongoClient?: MongoClient; }; get address() { @@ -103,22 +103,23 @@ class MongoProvider implements IProvider throw new Error("Uri is required for mongodb provider."); } this.state = { uri: uri, keyPropertyNameBySchemas: new Map() }; - this._logger = ctx.container.get(DefaultServiceNames.Logger); + this.logger = ctx.container.get(DefaultServiceNames.Logger); } - initialize( ctx: IRequestContext, tenant: string): () => any { - if (!tenant) - throw new Error("tenant is required"); + initialize( ctx: IRequestContext, tenant?: string): () => any { + // By default, there is a database base by tenant + this.databaseName = tenant; - this.tenant = tenant; - // Insert tenant into connection string + // If database is provided use it as database name and ignore tenant let url = URL.parse(this.state.uri); - // If no database is provided just use the tenant as database name - if( !url.pathname || url.pathname === "/") - url.pathname = tenant; - else - // else suffix the database name with the tenant - url.pathname += "_" + tenant; + if (url.pathname && url.pathname !== "/") { + this.databaseName = url.pathname.replace('/', ''); + url.pathname = "/"; + } + else if (!tenant) { + throw new Error("tenant is required"); + } + this.state.uri = URL.format(url); Service.log.verbose(ctx, () => `MONGODB: Creating provider ${Service.removePasswordFromUrl(this.state.uri)} for tenant ${tenant}`); @@ -126,13 +127,13 @@ class MongoProvider implements IProvider } dispose() { - this.state._mongo.close(); - this.state._mongo = null; + this.state.mongoClient.close(); + this.state.mongoClient = null; this.state.dispose = null; } private async ensureSchemaReady(ctx: IRequestContext, schema: Schema) { - if(!this.state._mongo) + if(!this.state.mongoClient) await this.openDatabase(ctx); let keyPropertyName = this.state.keyPropertyNameBySchemas.get(schema.name); @@ -158,8 +159,9 @@ class MongoProvider implements IProvider return Promise.resolve(); } - const db = this.state._mongo; - let self = this; + const db = this.state.mongoClient.db(this.databaseName); + let uri = this.state.uri; + return new Promise((resolve, reject) => { // Don't use 'this' here to avoid memory leaks // Open connection @@ -167,10 +169,10 @@ class MongoProvider implements IProvider db.createIndex(schema.info.storageName, keys, { w: 1, background: true, name: indexName, unique: true }, (err) => { if (err) { - ctx.logError( err, ()=>`MONGODB: Error when creating index for ${Service.removePasswordFromUrl(self.state.uri)} for schema ${schema.name}`); + ctx.logError( err, ()=>`MONGODB: Error when creating index for ${Service.removePasswordFromUrl(uri)} for schema ${schema.name}`); } else { - ctx.logInfo(()=>`MONGODB: Unique index created for ${Service.removePasswordFromUrl(self.state.uri)} for schema ${schema.name}`); + ctx.logInfo(()=>`MONGODB: Unique index created for ${Service.removePasswordFromUrl(uri)} for schema ${schema.name}`); } resolve(); }); @@ -181,18 +183,19 @@ class MongoProvider implements IProvider return new Promise((resolve, reject) => { // Don't use 'this' here to avoid memory leaks // Open connection - MongoClient.connect(this.state.uri, this.options, (err, db) => { + MongoClient.connect(this.state.uri, this.options, (err, client) => { if (err) { reject(err); - ctx.logError(err, ()=>`MONGODB: Error when opening database ${Service.removePasswordFromUrl(this.state.uri)} for tenant ${this.tenant}`); + ctx.logError(err, ()=>`MONGODB: Error when opening database ${Service.removePasswordFromUrl(this.state.uri)} for tenant ${this.databaseName}`); return; } - this.state._mongo = db; + this.state.mongoClient = client; resolve(); }); }); } + /** * Return a list of entities * @param options @@ -215,7 +218,7 @@ class MongoProvider implements IProvider let self = this; return new Promise(async (resolve, reject) => { try { - let db = self.state._mongo; + let db = self.state.mongoClient.db(this.databaseName); // TODO try with aggregate let total = await db.collection(schema.info.storageName).find(query).count(); let cursor = db.collection(schema.info.storageName).find(query, proj) @@ -253,7 +256,7 @@ class MongoProvider implements IProvider let self = this; return new Promise(async (resolve, reject) => { try { - let db = self.state._mongo; + let db = self.state.mongoClient.db(this.databaseName); db.collection(schema.info.storageName).findOne(filter, (err, res) => { if (err) { ctx.logError(err, ()=>`MONGODB ERROR: Get query on ${Service.removePasswordFromUrl(self.state.uri)} for schema ${schema.name} with id: ${id}`); @@ -296,7 +299,7 @@ class MongoProvider implements IProvider reject(new Error("MONGODB DELETE ERROR: Entity must exists")); return; } - let db = self.state._mongo; + let db = self.state.mongoClient.db(this.databaseName); db.collection(schema.info.storageName).remove(filter, (err, res) => { if (err) { let e = self.normalizeErrors(id, err); @@ -350,7 +353,7 @@ class MongoProvider implements IProvider let self = this; return new Promise(async (resolve, reject) => { try { - let db = self.state._mongo; + let db = self.state.mongoClient.db(this.databaseName); db.collection(schema.info.storageName).insertOne(entity, (err) => { if (err) { let e = self.normalizeErrors(entity[keyPropertyName], err); @@ -390,7 +393,7 @@ class MongoProvider implements IProvider return new Promise(async (resolve, reject) => { try { - let db = self.state._mongo; + let db = self.state.mongoClient.db(this.databaseName); let collection = db.collection(schema.info.storageName); collection.findOne(filter, (err, initial) => { @@ -412,7 +415,7 @@ class MongoProvider implements IProvider initial._updated = new Date().toUTCString(); initial._id = _id; - collection.updateOne(filter, initial, err => { + collection.updateOne(filter, { $set: initial }, err => { if (err) { let e = self.normalizeErrors(id, err); ctx.logError(e, ()=>`MONGODB ERROR : Updating entity on ${Service.removePasswordFromUrl(self.state.uri)} for schema ${schema.name} with id: ${id}`); diff --git a/src/schemas/validator.ts b/src/schemas/validator.ts index 9ee34cf..6fada50 100644 --- a/src/schemas/validator.ts +++ b/src/schemas/validator.ts @@ -9,13 +9,13 @@ export class Validator { constructor(private domain: Domain) { } - async validate(ctx: IRequestContext, schema: Schema, val:any, parentName:string=""): Promise<{ [propertyName: string]: string }> { + validate(ctx: IRequestContext, schema: Schema, val:any, parentName:string=""): { [propertyName: string]: string } { let errors: { [propertyName: string]: string } = {}; if (!schema || !val) return errors; if (schema.extends) { if (schema.extends) { - let errorList = (await this.validate(ctx, schema.extends, val)); + let errorList = this.validate(ctx, schema.extends, val); errors = Object.assign(errors, errorList); } } @@ -38,12 +38,12 @@ export class Validator { if (prop.type === "any" && formatContext.propertyValue && formatContext.propertyValue._schema) { propertyTypeName = formatContext.propertyValue._schema; } - let errors2 = await this.validateReference(ctx, formatContext, prop.type, val); + let errors2 = this.validateReference(ctx, formatContext, prop.type, val); if (errors2) errors = Object.assign(errors, errors2); } else { - let err = await this.validateProperty(ctx, formatContext, val); + let err = this.validateProperty(ctx, formatContext, val); if (err) { errors[propertyName] = err; } @@ -58,7 +58,7 @@ export class Validator { if (schema.info.validate) { formatContext.propertyName = formatContext.propertySchema = formatContext.propertyValue = null; try { - let err = await schema.info.validate(val, ctx); + let err = schema.info.validate(val, ctx); if (err) errors["_"] = this.__formatMessage(err, formatContext, schema); } @@ -69,7 +69,7 @@ export class Validator { return errors; } - private async validateReference(ctx: IRequestContext, formatContext: FormatContext, propertyTypeName: string, entity): Promise<{ [propertyName: string]: string }> { + private validateReference(ctx: IRequestContext, formatContext: FormatContext, propertyTypeName: string, entity): { [propertyName: string]: string } { let errors = {}; const { propertySchema, propertyValue } = formatContext; @@ -90,7 +90,7 @@ export class Validator { if (propertySchema.validators) { for (let validator of propertySchema.validators) { - let msg = validator.validate && await validator.validate(propertyValue, ctx); + let msg = validator.validate && validator.validate(propertyValue, ctx); if (msg) { errors[formatContext.propertyName] = this.__formatMessage(msg, formatContext, propertySchema); return errors; @@ -98,7 +98,7 @@ export class Validator { } } - let err = propertySchema.validate && await propertySchema.validate(propertyValue, ctx); + let err = propertySchema.validate && propertySchema.validate(propertyValue, ctx); if (err) { errors[formatContext.propertyName] = err; return errors; @@ -117,14 +117,14 @@ export class Validator { baseItemSchema = currentItemSchema; } if (currentItemSchema) { - errors = Object.assign(errors, await this.validate(ctx, currentItemSchema, val, formatContext.propertyName + ".")); + errors = Object.assign(errors, this.validate(ctx, currentItemSchema, val, formatContext.propertyName + ".")); } } } return errors; } - private async validateProperty(ctx: IRequestContext, formatContext: FormatContext, entity): Promise { + private validateProperty(ctx: IRequestContext, formatContext: FormatContext, entity): string { const { propertySchema, propertyValue } = formatContext; if (propertySchema.dependsOn && !propertySchema.dependsOn(entity)) return; @@ -137,13 +137,13 @@ export class Validator { } if (propertySchema.validators) { for (let validator of propertySchema.validators) { - let err = validator.validate && await validator.validate(propertyValue, ctx); + let err = validator.validate && validator.validate(propertyValue, ctx); if (err) return this.__formatMessage(err, formatContext, validator); } } if (propertySchema.validate) { - let err = await propertySchema.validate(propertyValue, ctx); + let err = propertySchema.validate(propertyValue, ctx); if (err) return this.__formatMessage(err, formatContext, propertySchema); } } diff --git a/test/schema/validateData.spec.ts b/test/schema/validateData.spec.ts index 79dcfb1..a6db028 100644 --- a/test/schema/validateData.spec.ts +++ b/test/schema/validateData.spec.ts @@ -103,7 +103,7 @@ describe("Validate data", function () { let schema = domain.getSchema("SimpleModel"); let model: SimpleModel = { text: "text", number: 1, baseText: "" }; - let errors = await schema.validate(null, model); + let errors = schema.validate(null, model); expect(errors.baseText); }); @@ -112,7 +112,7 @@ describe("Validate data", function () { let schema = domain.getSchema("SimpleModel"); let model: SimpleModel = { text: "text", number: 1, baseText: "a" }; - let errors = await schema.validate(null, model); + let errors = schema.validate(null, model); expect(errors.baseText); }); @@ -121,7 +121,7 @@ describe("Validate data", function () { let schema = domain.getSchema("SimpleModel"); let model = schema.coerce({ text: "text", number: "1w1", baseText: "text" }); - let errors = await schema.validate(null, model); + let errors = schema.validate(null, model); expect(errors.number); }); @@ -129,7 +129,7 @@ describe("Validate data", function () { let model: SimpleModel = { text: "text", number: 1, baseText: "text" }; let domain = context.rootContainer.get("Domain"); let schema = domain.getSchema("SimpleModel"); - let errors = await schema.validate(null, model); + let errors = schema.validate(null, model); expect(Object.keys(errors).length).equals(0); }); @@ -139,7 +139,7 @@ describe("Validate data", function () { let domain = context.rootContainer.get("Domain"); let schema = domain.getSchema("ReferenceModel"); - let errors = await schema.validate(null, refs); + let errors = schema.validate(null, refs); expect(Object.keys(errors).length).equals(1); }); @@ -150,7 +150,7 @@ describe("Validate data", function () { let domain = context.rootContainer.get("Domain"); let schema = domain.getSchema("ReferenceModel"); - let errors = await schema.validate(null, refs); + let errors = schema.validate(null, refs); expect(Object.keys(errors).length).equals(2); }); @@ -161,7 +161,7 @@ describe("Validate data", function () { let domain = context.rootContainer.get("Domain"); let schema = domain.getSchema("ReferenceModel"); - let errors = await schema.validate(null, refs); + let errors = schema.validate(null, refs); expect(Object.keys(errors).length).equals(1); // TODO really expected ? }); @@ -171,7 +171,7 @@ describe("Validate data", function () { let domain = context.rootContainer.get("Domain"); let schema = domain.getSchema("ReferenceModel"); - let errors = await schema.validate(null, refs); + let errors = schema.validate(null, refs); expect(Object.keys(errors).length).equals(1); }); @@ -183,7 +183,7 @@ describe("Validate data", function () { let model: EmailModel = { email: "first.name@email.com" }; let domain = context.rootContainer.get("Domain"); let schema = domain.getSchema("EmailModel"); - let errors = await schema.validate(null, model); + let errors = schema.validate(null, model); expect(!errors.email); }); @@ -192,7 +192,7 @@ describe("Validate data", function () { let model: EmailModel = { email: "first.name@email" }; let domain = context.rootContainer.get("Domain"); let schema = domain.getSchema("EmailModel"); - let errors = await schema.validate(null, model); + let errors = schema.validate(null, model); expect(errors.email); }); @@ -205,7 +205,7 @@ describe("Validate data", function () { let model: UrlModel = { url: "https://myWebsite.com/#ancre/1" }; let domain = context.rootContainer.get("Domain"); let schema = domain.getSchema("UrlModel"); - let errors = await schema.validate(null, model); + let errors = schema.validate(null, model); expect(Object.keys(errors).length).equals(0); }); @@ -214,7 +214,7 @@ describe("Validate data", function () { let model: UrlModel = { url: "http://site.r" }; let domain = context.rootContainer.get("Domain"); let schema = domain.getSchema("UrlModel"); - let errors = await schema.validate(null, model); + let errors = schema.validate(null, model); expect(Object.keys(errors).length).equals(1); }); @@ -226,7 +226,7 @@ describe("Validate data", function () { let model: AlphanumericModel = { value: "abcde1345fghik6789" }; let domain = context.rootContainer.get("Domain"); let schema = domain.getSchema("AlphanumericModel"); - let errors = await schema.validate(undefined, model); + let errors = schema.validate(undefined, model); expect(Object.keys(errors).length).equals(0); }); @@ -235,7 +235,7 @@ describe("Validate data", function () { let model: AlphanumericModel = { value: "abc123!" }; let domain = context.rootContainer.get("Domain"); let schema = domain.getSchema("AlphanumericModel"); - let errors = await schema.validate(undefined, model); + let errors = schema.validate(undefined, model); expect(Object.keys(errors).length).equals(1); }); @@ -247,7 +247,7 @@ describe("Validate data", function () { let model: DateIsoModel = { date: new Date().toISOString() }; let domain = context.rootContainer.get("Domain"); let schema = domain.getSchema("DateIsoModel"); - let errors = await schema.validate(undefined, model); + let errors = schema.validate(undefined, model); expect(Object.keys(errors).length).equals(0); }); @@ -258,7 +258,7 @@ describe("Validate data", function () { let model: DateIsoModel = { date: new Date().toDateString() }; let domain = context.rootContainer.get("Domain"); let schema = domain.getSchema("DateIsoModel"); - let errors = await schema.validate(undefined, model); + let errors = schema.validate(undefined, model); expect(Object.keys(errors).length).equals(1); }); @@ -270,7 +270,7 @@ describe("Validate data", function () { let model: ArrayOfModel = { enums: ["a", "bb"] }; let domain = context.rootContainer.get("Domain"); let schema = domain.getSchema("ArrayOfModel"); - let errors = await schema.validate(undefined, model); + let errors = schema.validate(undefined, model); expect(Object.keys(errors).length).equals(1); });