mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-12 13:13:45 +01:00
Merge pull request #36842 from overleaf/em-tpds-typescript
Convert third-party-datastore to TypeScript GitOrigin-RevId: 028172063d0cfe03c0f105f48ab66020c79ea50e
This commit is contained in:
@@ -16,6 +16,14 @@ const {
|
||||
const EXIT_DELAY = 500
|
||||
|
||||
const LoggingManager = {
|
||||
/**
|
||||
* The serializers registered by addSerializer, kept here rather than only on
|
||||
* the logger so that they survive a later initialize() call. A module that
|
||||
* registers one as it is imported would otherwise lose it, since a service
|
||||
* initializes its logger after its imports have run.
|
||||
*/
|
||||
customSerializers: {},
|
||||
|
||||
/**
|
||||
* @param {string} name - The name of the logger
|
||||
*/
|
||||
@@ -34,6 +42,7 @@ const LoggingManager = {
|
||||
error: Serializers.err,
|
||||
req: Serializers.req,
|
||||
res: Serializers.res,
|
||||
...this.customSerializers,
|
||||
},
|
||||
streams: options.streams ?? [this._getOutputStreamConfig()],
|
||||
})
|
||||
@@ -44,6 +53,24 @@ const LoggingManager = {
|
||||
return this
|
||||
},
|
||||
|
||||
/**
|
||||
* Register a serializer, which decides what gets recorded for a named log
|
||||
* field, e.g. `logger.error({ dropboxResponseError: err }, '...')`. Callers
|
||||
* add their own on top of the ones initialize() sets up, and keep them across
|
||||
* a later initialize(), so registering one as a module is imported works.
|
||||
*
|
||||
* A serializer takes whatever was logged under that field, so it receives an
|
||||
* unknown and narrows it itself, and returns the value to record in its
|
||||
* place, which is only ever JSON encoded.
|
||||
*
|
||||
* @param {string} name - the log field the serializer applies to
|
||||
* @param {(value: unknown) => unknown} serializer
|
||||
*/
|
||||
addSerializer(name, serializer) {
|
||||
this.customSerializers[name] = serializer
|
||||
this.logger.serializers[name] = serializer
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Record<string, any>|string} attributes - Attributes to log (nice serialization for err, req, res)
|
||||
* @param {string} [message] - Optional message
|
||||
|
||||
@@ -17,6 +17,7 @@ describe('LoggingManager', function () {
|
||||
info: sinon.stub(),
|
||||
level: sinon.stub(),
|
||||
warn: sinon.stub(),
|
||||
serializers: {},
|
||||
}
|
||||
this.Bunyan = {
|
||||
createLogger: sinon.stub().returns(this.bunyanLogger),
|
||||
@@ -126,6 +127,37 @@ describe('LoggingManager', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('addSerializer', function () {
|
||||
beforeEach(function () {
|
||||
this.serializer = sinon.stub()
|
||||
})
|
||||
|
||||
it('registers the serializer on the current logger', function () {
|
||||
this.LoggingManager.addSerializer('custom', this.serializer)
|
||||
expect(this.bunyanLogger.serializers.custom).to.equal(this.serializer)
|
||||
})
|
||||
|
||||
it('keeps the serializer through a later initialize', function () {
|
||||
// A module registering a serializer as it is imported does so before the
|
||||
// service initializes its own logger, which replaces the bunyan instance.
|
||||
this.LoggingManager.addSerializer('custom', this.serializer)
|
||||
this.Bunyan.createLogger.reset()
|
||||
this.LoggingManager.initialize(this.loggerName)
|
||||
expect(
|
||||
this.Bunyan.createLogger.firstCall.args[0].serializers.custom
|
||||
).to.equal(this.serializer)
|
||||
})
|
||||
|
||||
it('keeps the serializers initialize sets up', function () {
|
||||
this.LoggingManager.addSerializer('custom', this.serializer)
|
||||
this.Bunyan.createLogger.reset()
|
||||
this.LoggingManager.initialize(this.loggerName)
|
||||
expect(
|
||||
this.Bunyan.createLogger.firstCall.args[0].serializers
|
||||
).to.include.keys('err', 'error', 'req', 'res')
|
||||
})
|
||||
})
|
||||
|
||||
describe('bunyan logging', function () {
|
||||
beforeEach(function () {
|
||||
this.logArgs = [{ foo: 'bar' }, 'foo', 'bar']
|
||||
|
||||
@@ -1,10 +1,43 @@
|
||||
import { zz } from '../../../zodHelpers'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import mongodb from 'mongodb'
|
||||
import { z } from 'zod'
|
||||
|
||||
const { ObjectId } = mongodb
|
||||
|
||||
describe('zodHelpers', () => {
|
||||
describe('optional', () => {
|
||||
it('parses a present value with the wrapped schema', () => {
|
||||
const parsed = zz.optional(z.string()).safeParse('a value')
|
||||
expect(parsed.success).toBe(true)
|
||||
expect(parsed.data).toBe('a value')
|
||||
})
|
||||
|
||||
it('normalises null to undefined', () => {
|
||||
const parsed = zz.optional(z.string()).safeParse(null)
|
||||
expect(parsed.success).toBe(true)
|
||||
expect(parsed.data).toBe(undefined)
|
||||
})
|
||||
|
||||
it('normalises undefined to undefined', () => {
|
||||
const parsed = zz.optional(z.string()).safeParse(undefined)
|
||||
expect(parsed.success).toBe(true)
|
||||
expect(parsed.data).toBe(undefined)
|
||||
})
|
||||
|
||||
it('leaves a null unparsed by the wrapped schema', () => {
|
||||
// A coercing schema would otherwise turn null into a value of its own,
|
||||
// such as the 0 that Number(null) gives.
|
||||
const parsed = zz.optional(z.coerce.number()).safeParse(null)
|
||||
expect(parsed.success).toBe(true)
|
||||
expect(parsed.data).toBe(undefined)
|
||||
})
|
||||
|
||||
it('reports a present value the wrapped schema rejects', () => {
|
||||
const parsed = zz.optional(z.string()).safeParse(7)
|
||||
expect(parsed.success).toBe(false)
|
||||
})
|
||||
})
|
||||
describe('objectId', () => {
|
||||
it('fails to parse when provided with an invalid ObjectId', () => {
|
||||
const parsed = zz.objectId().safeParse('aa')
|
||||
|
||||
@@ -21,6 +21,23 @@ const datetimeSchema = ({ allowNull, allowUndefined, ...zodOptions } = {}) => {
|
||||
}
|
||||
|
||||
const zz = {
|
||||
/**
|
||||
* A field whose absence its source represents inconsistently: left out of
|
||||
* some records and null in others. JSON has no undefined, and Mongo stores a
|
||||
* $set of undefined as null, so an absent-ish field arrives as null about as
|
||||
* often as it is missing outright.
|
||||
*
|
||||
* Unlike z.optional(), which accepts only undefined, both forms are accepted
|
||||
* and normalised to undefined, so callers have a single absent value to
|
||||
* check rather than a `T | null | undefined` to narrow.
|
||||
*
|
||||
* The one combinator here: it wraps another schema instead of describing a
|
||||
* value type of its own.
|
||||
*
|
||||
* @template Output
|
||||
* @param {z.ZodType<Output>} schema
|
||||
*/
|
||||
optional: schema => schema.nullish().transform(value => value ?? undefined),
|
||||
objectId: () =>
|
||||
z.string().refine(ObjectId.isValid, { message: 'invalid Mongo ObjectId' }),
|
||||
coercedObjectId: () =>
|
||||
|
||||
Reference in New Issue
Block a user