Skip to content

Commit ccc135f

Browse files
committed
feat(logger): new loglevel 'verbose' and some more log statements
new log statements for: - toDb - fromDb other: - include failing value in mapping errors
1 parent 083f9b7 commit ccc135f

17 files changed

Lines changed: 279 additions & 40 deletions

src/logger/log-level.type.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ export enum LogLevel {
99
WARNING = 2,
1010
INFO = 3,
1111
DEBUG = 4,
12+
VERBOSE = 5,
1213
}

src/logger/logger.spec.ts

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
import { Employee } from '../../test/models'
1+
import { Employee, SimpleModel } from '../../test/models'
22
import { updateDynamoEasyConfig } from '../config/update-config.function'
33
import { DynamoStore } from '../dynamo/dynamo-store'
44
import { LogInfo } from './log-info.type'
5+
import { LogLevel } from './log-level.type'
56
import { LogReceiver } from './log-receiver.type'
7+
import { createLogger, createOptModelLogger, Logger, OptModelLogger } from './logger'
68

79
describe('log receiver', () => {
810
let logs: LogInfo[] = []
@@ -21,3 +23,95 @@ describe('log receiver', () => {
2123
expect(logs[0].modelConstructor).toBe(Employee.name)
2224
})
2325
})
26+
27+
describe('createLogger', () => {
28+
let logReceiver: jest.Mock
29+
let logger: Logger
30+
beforeEach(() => {
31+
logReceiver = jest.fn()
32+
updateDynamoEasyConfig({ logReceiver })
33+
logger = createLogger('MyClass', SimpleModel)
34+
})
35+
it('creates correct Logger instance with working warn function', () => {
36+
logger.warn('warn')
37+
expect(logReceiver).toBeCalledTimes(1)
38+
expect(logReceiver.mock.calls[0][0]).toBeDefined()
39+
expect(logReceiver.mock.calls[0][0].className).toEqual('MyClass')
40+
expect(logReceiver.mock.calls[0][0].modelConstructor).toEqual('SimpleModel')
41+
expect(logReceiver.mock.calls[0][0].message).toEqual('warn')
42+
expect(logReceiver.mock.calls[0][0].level).toEqual(LogLevel.WARNING)
43+
})
44+
it('creates correct Logger instance with working info function', () => {
45+
logger.info('info')
46+
expect(logReceiver).toBeCalledTimes(1)
47+
expect(logReceiver.mock.calls[0][0]).toBeDefined()
48+
expect(logReceiver.mock.calls[0][0].className).toEqual('MyClass')
49+
expect(logReceiver.mock.calls[0][0].modelConstructor).toEqual('SimpleModel')
50+
expect(logReceiver.mock.calls[0][0].message).toEqual('info')
51+
expect(logReceiver.mock.calls[0][0].level).toEqual(LogLevel.INFO)
52+
})
53+
it('creates correct Logger instance with working debug function', () => {
54+
logger.debug('debug')
55+
expect(logReceiver).toBeCalledTimes(1)
56+
expect(logReceiver.mock.calls[0][0]).toBeDefined()
57+
expect(logReceiver.mock.calls[0][0].className).toEqual('MyClass')
58+
expect(logReceiver.mock.calls[0][0].modelConstructor).toEqual('SimpleModel')
59+
expect(logReceiver.mock.calls[0][0].message).toEqual('debug')
60+
expect(logReceiver.mock.calls[0][0].level).toEqual(LogLevel.DEBUG)
61+
})
62+
it('creates correct Logger instance with working debug function', () => {
63+
logger.verbose('verbose')
64+
expect(logReceiver).toBeCalledTimes(1)
65+
expect(logReceiver.mock.calls[0][0]).toBeDefined()
66+
expect(logReceiver.mock.calls[0][0].className).toEqual('MyClass')
67+
expect(logReceiver.mock.calls[0][0].modelConstructor).toEqual('SimpleModel')
68+
expect(logReceiver.mock.calls[0][0].message).toEqual('verbose')
69+
expect(logReceiver.mock.calls[0][0].level).toEqual(LogLevel.VERBOSE)
70+
})
71+
})
72+
73+
describe('createOptModelLogger', () => {
74+
let logReceiver: jest.Mock
75+
let logger: OptModelLogger
76+
beforeEach(() => {
77+
logReceiver = jest.fn()
78+
updateDynamoEasyConfig({ logReceiver })
79+
logger = createOptModelLogger('MyClass')
80+
})
81+
it('creates correct OptModelLogger instance with working warn function', () => {
82+
logger.warn('warn', SimpleModel)
83+
expect(logReceiver).toBeCalledTimes(1)
84+
expect(logReceiver.mock.calls[0][0]).toBeDefined()
85+
expect(logReceiver.mock.calls[0][0].className).toEqual('MyClass')
86+
expect(logReceiver.mock.calls[0][0].modelConstructor).toEqual('SimpleModel')
87+
expect(logReceiver.mock.calls[0][0].message).toEqual('warn')
88+
expect(logReceiver.mock.calls[0][0].level).toEqual(LogLevel.WARNING)
89+
})
90+
it('creates correct OptModelLogger instance with working info function', () => {
91+
logger.info('info', SimpleModel)
92+
expect(logReceiver).toBeCalledTimes(1)
93+
expect(logReceiver.mock.calls[0][0]).toBeDefined()
94+
expect(logReceiver.mock.calls[0][0].className).toEqual('MyClass')
95+
expect(logReceiver.mock.calls[0][0].modelConstructor).toEqual('SimpleModel')
96+
expect(logReceiver.mock.calls[0][0].message).toEqual('info')
97+
expect(logReceiver.mock.calls[0][0].level).toEqual(LogLevel.INFO)
98+
})
99+
it('creates correct OptModelLogger instance with working debug function', () => {
100+
logger.debug('debug', SimpleModel)
101+
expect(logReceiver).toBeCalledTimes(1)
102+
expect(logReceiver.mock.calls[0][0]).toBeDefined()
103+
expect(logReceiver.mock.calls[0][0].className).toEqual('MyClass')
104+
expect(logReceiver.mock.calls[0][0].modelConstructor).toEqual('SimpleModel')
105+
expect(logReceiver.mock.calls[0][0].message).toEqual('debug')
106+
expect(logReceiver.mock.calls[0][0].level).toEqual(LogLevel.DEBUG)
107+
})
108+
it('creates correct OptModelLogger instance with working verbose function', () => {
109+
logger.verbose('verbose', SimpleModel)
110+
expect(logReceiver).toBeCalledTimes(1)
111+
expect(logReceiver.mock.calls[0][0]).toBeDefined()
112+
expect(logReceiver.mock.calls[0][0].className).toEqual('MyClass')
113+
expect(logReceiver.mock.calls[0][0].modelConstructor).toEqual('SimpleModel')
114+
expect(logReceiver.mock.calls[0][0].message).toEqual('verbose')
115+
expect(logReceiver.mock.calls[0][0].level).toEqual(LogLevel.VERBOSE)
116+
})
117+
})

src/logger/logger.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,30 @@ import { LogLevel } from './log-level.type'
1313
*/
1414
export type LogFn = (message: string, data?: any) => void
1515

16+
export type OptModelLogFn = (
17+
message: string,
18+
modelConstructor: ModelConstructor<any> | undefined | null,
19+
data?: any,
20+
) => void
21+
1622
/**
1723
* @hidden
1824
*/
1925
export interface Logger {
2026
warn: LogFn
2127
info: LogFn
2228
debug: LogFn
29+
verbose: LogFn
30+
}
31+
32+
/**
33+
* @hidden
34+
*/
35+
export interface OptModelLogger {
36+
warn: OptModelLogFn
37+
info: OptModelLogFn
38+
debug: OptModelLogFn
39+
verbose: OptModelLogFn
2340
}
2441

2542
/**
@@ -38,6 +55,22 @@ function getLogFn(className: string, modelConstructor: string, level: LogLevel):
3855
}
3956
}
4057

58+
/**
59+
* @hidden
60+
*/
61+
function getOptModelLogFn(className: string, level: LogLevel): OptModelLogFn {
62+
return (message: string, modelConstructor: ModelConstructor<any> | undefined | null, data?: any) => {
63+
dynamoEasyConfig.logReceiver({
64+
className,
65+
modelConstructor: (modelConstructor && modelConstructor.name) || 'NO_MODEL',
66+
level,
67+
message,
68+
data,
69+
timestamp: Date.now(),
70+
})
71+
}
72+
}
73+
4174
/**
4275
* @hidden
4376
*/
@@ -46,5 +79,17 @@ export function createLogger(className: string, modelConstructor: ModelConstruct
4679
warn: getLogFn(className, modelConstructor.name, LogLevel.WARNING),
4780
info: getLogFn(className, modelConstructor.name, LogLevel.INFO),
4881
debug: getLogFn(className, modelConstructor.name, LogLevel.DEBUG),
82+
verbose: getLogFn(className, modelConstructor.name, LogLevel.VERBOSE),
83+
}
84+
}
85+
/**
86+
* @hidden
87+
*/
88+
export function createOptModelLogger(className: string): OptModelLogger {
89+
return {
90+
warn: getOptModelLogFn(className, LogLevel.WARNING),
91+
info: getOptModelLogFn(className, LogLevel.INFO),
92+
debug: getOptModelLogFn(className, LogLevel.DEBUG),
93+
verbose: getOptModelLogFn(className, LogLevel.VERBOSE),
4994
}
5095
}

src/mapper/for-type/boolean.mapper.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@
44
import { BooleanAttribute } from '../type/attribute.type'
55
import { MapperForType } from './base.mapper'
66

7-
function booleanFromDb(dbValue: BooleanAttribute): boolean {
8-
if (dbValue.BOOL === undefined) {
9-
throw new Error('only attribute values with BOOL value can be mapped to a boolean')
7+
function booleanFromDb(attributeValue: BooleanAttribute): boolean {
8+
if (attributeValue.BOOL === undefined) {
9+
throw new Error(`there is no BOOL(ean) value defined on given attribute value: ${JSON.stringify(attributeValue)}`)
1010
}
11-
return dbValue.BOOL === true
11+
return attributeValue.BOOL === true
1212
}
1313

1414
function booleanToDb(modelValue: boolean): BooleanAttribute {
1515
if (!(modelValue === true || modelValue === false)) {
16-
throw new Error('only boolean values are mapped to a BOOl attribute')
16+
throw new Error(`only boolean values are mapped to a BOOl attribute, given: ${JSON.stringify(modelValue)}`)
1717
}
1818
return { BOOL: modelValue }
1919
}

src/mapper/for-type/collection.mapper.spec.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,16 @@ describe('collection mapper', () => {
6464
it('heterogeneous set should throw', () => {
6565
expect(() => CollectionMapper.toDb(new Set(['value1', 10]))).toThrow()
6666
})
67+
68+
/*
69+
* neither set nor arr or not primitive in set
70+
*/
71+
it('should throw if neither array nor set', () => {
72+
expect(() => CollectionMapper.toDb(<any>{ aValue: true })).toThrow()
73+
})
74+
it('should throw if set of non primitives', () => {
75+
expect(() => CollectionMapper.toDb(new Set([{ aValue: true }]))).toThrow()
76+
})
6777
})
6878

6979
describe('with metadata', () => {
@@ -117,6 +127,42 @@ describe('collection mapper', () => {
117127
expect(attributeValue.L.length).toBe(2)
118128
expect(attributeValue.L).toEqual([{ N: '5' }, { N: '10' }])
119129
})
130+
131+
it('set with generic number type', () => {
132+
const meta: PropertyMetadata<any, NumberSetAttribute> = {
133+
name: 'aName',
134+
nameDb: 'aName',
135+
typeInfo: {
136+
type: Set,
137+
genericType: Number,
138+
},
139+
}
140+
const r = CollectionMapper.toDb(new Set([1, 2, 3, 5]), <any>meta)
141+
expect(r).toEqual({ NS: ['1', '2', '3', '5'] })
142+
})
143+
144+
it('without generic type but actually set of numbers', () => {
145+
const meta: PropertyMetadata<any, NumberSetAttribute> = {
146+
name: 'aName',
147+
nameDb: 'aName',
148+
typeInfo: {
149+
type: Set,
150+
},
151+
}
152+
const r = CollectionMapper.toDb(new Set([1, 2, 3, 5]), <any>meta)
153+
expect(r).toEqual({ NS: ['1', '2', '3', '5'] })
154+
})
155+
156+
it('throws if explicit type is neither array nor set', () => {
157+
const meta: PropertyMetadata<any, NumberSetAttribute> = {
158+
name: 'aName',
159+
nameDb: 'aName',
160+
typeInfo: {
161+
type: Number,
162+
},
163+
}
164+
expect(() => CollectionMapper.toDb(new Set([0, 1, 1, 2, 3, 5]), <any>meta)).toThrow()
165+
})
120166
})
121167

122168
describe('with CollectionProperty decorator', () => {
@@ -195,6 +241,10 @@ describe('collection mapper', () => {
195241
expect(numberSet.size).toBe(2)
196242
expect(typeof Array.from(numberSet)[0]).toBe('number')
197243
})
244+
245+
it('throws if not a L|SS|NS|BS', () => {
246+
expect(() => CollectionMapper.fromDb(<any>{ S: 'not a list' })).toThrow()
247+
})
198248
})
199249
})
200250
})

src/mapper/for-type/collection.mapper.ts

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import { hasGenericType, PropertyMetadata } from '../../decorator/metadata/property-metadata.model'
55
import { notNull } from '../../helper/not-null.function'
66
import { fromDb, fromDbOne, toDb, toDbOne } from '../mapper'
7-
import { AttributeCollectionType, AttributeType } from '../type/attribute-type.type'
7+
import { AttributeCollectionType } from '../type/attribute-type.type'
88
import {
99
BinarySetAttribute,
1010
ListAttribute,
@@ -43,14 +43,18 @@ function collectionFromDb(
4343
}
4444

4545
// if [(N|S|B)S]et
46-
if ('SS' in attributeValue) {
46+
else if ('SS' in attributeValue) {
4747
arr = attributeValue.SS
4848
} else if ('NS' in attributeValue) {
4949
arr = attributeValue.NS.map(parseFloat)
5050
} else if ('BS' in attributeValue) {
5151
arr = attributeValue.BS
5252
} else {
53-
throw new Error('No Collection Data (SS | NS | BS | L) was found in attribute data')
53+
throw new Error(
54+
`No Collection Data (SS | NS | BS | L) was found in attribute data, given attributeValue: ${JSON.stringify(
55+
attributeValue,
56+
)}`,
57+
)
5458
}
5559
return explicitType && explicitType === Array ? arr : new Set(arr)
5660
}
@@ -60,24 +64,25 @@ function collectionToDb(
6064
propertyMetadata?: PropertyMetadata<any, CollectionAttributeTypes>,
6165
): CollectionAttributeTypes | null {
6266
if (!(Array.isArray(propertyValue) || isSet(propertyValue))) {
63-
throw new Error(`Given value must be either Array or Set ${propertyValue}`)
67+
throw new Error(`Given value must be either Array or Set ${JSON.stringify(propertyValue)}`)
6468
}
6569

66-
let collectionType: AttributeType
70+
let collectionType: AttributeCollectionType
6771
// detect collection type
6872
if (propertyMetadata) {
6973
// based on metadata
7074
collectionType = detectCollectionTypeFromMetadata(propertyMetadata, propertyValue)
7175
} else {
7276
// based on value
77+
// or throw if not a collectionType
7378
collectionType = detectCollectionTypeFromValue(propertyValue)
7479
}
7580

7681
// convert to array if we deal with a set for same behaviour
7782
propertyValue = isSet(propertyValue) ? Array.from(propertyValue) : propertyValue
7883

7984
// empty values are not allowed for S(et) types only for L(ist)
80-
if ((collectionType === 'SS' || collectionType === 'NS' || collectionType === 'BS') && propertyValue.length === 0) {
85+
if (collectionType !== 'L' && propertyValue.length === 0) {
8186
return null
8287
}
8388

@@ -104,8 +109,7 @@ function collectionToDb(
104109
.filter(notNull),
105110
}
106111
}
107-
default:
108-
throw new Error(`Collection type must be one of SS | NS | BS | L found type ${collectionType}`)
112+
// no 'default' necessary, all possible cases caught
109113
}
110114
}
111115

@@ -121,7 +125,11 @@ function detectCollectionTypeFromMetadata(
121125
const explicitType = propertyMetadata && propertyMetadata.typeInfo ? propertyMetadata.typeInfo.type : null
122126

123127
if (!(explicitType === Array || explicitType === Set)) {
124-
throw new Error(`only 'Array' and 'Set' are valid values for explicit type, found ${explicitType}`)
128+
throw new Error(
129+
`only 'Array' and 'Set' are valid values for explicit type, found ${explicitType} on value ${JSON.stringify(
130+
propertyValue,
131+
)}`,
132+
)
125133
}
126134

127135
if (propertyMetadata.isSortedCollection) {

src/mapper/for-type/enum.mapper.spec.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,15 +51,27 @@ describe('enum mapper', () => {
5151
})
5252

5353
it('should throw', () => {
54-
expect(() => {
55-
EnumMapper.fromDb(<any>{ S: '2' }, propertyMetadata)
56-
}).toThrowError()
54+
expect(() => EnumMapper.fromDb(<any>{ S: '2' }, propertyMetadata)).toThrow()
5755
})
5856

5957
it('should throw', () => {
60-
expect(() => {
61-
EnumMapper.fromDb(<any>{ S: '2' })
62-
}).toThrowError()
58+
expect(() => EnumMapper.fromDb(<any>{ S: '2' })).toThrow()
59+
})
60+
61+
it('should throw', () => {
62+
enum anEnum {
63+
OK,
64+
NOK,
65+
}
66+
const meta: PropertyMetadata<any> = <any>{
67+
name: 'aName',
68+
nameDb: 'sameName',
69+
typeInfo: {
70+
type: String,
71+
genericType: anEnum,
72+
},
73+
}
74+
expect(() => EnumMapper.fromDb(<any>{ N: '2' }, <any>meta)).toThrow()
6375
})
6476
})
6577
})

0 commit comments

Comments
 (0)