diff --git a/ts/src/header-validator/validate-json.ts b/ts/src/header-validator/validate-json.ts index 5a51bb064c..eee10e1f16 100644 --- a/ts/src/header-validator/validate-json.ts +++ b/ts/src/header-validator/validate-json.ts @@ -3,9 +3,9 @@ import * as uuid from 'uuid' import * as constants from '../constants' import { SourceType } from '../source-type' import { VendorSpecificValues } from '../vendor-specific-values' -import { Context, PathComponent, ValidationResult } from './context' +import { Context, ValidationResult } from './context' import { Maybe } from './maybe' -import { CtxFunc } from './validate' +import { CtxFunc, ItemErrorAction, isCollection } from './validate' import * as validate from './validate' import * as privacy from '../flexible-event/privacy' @@ -411,7 +411,7 @@ function endTimes( return array(ctx, j, endTime, { minLength: 1, maxLength: 5, - keepGoing: false, // suppress unhelpful cascaded errors + itemErrorAction: ItemErrorAction.earlyExit, // suppress unhelpful cascaded errors }) } @@ -448,24 +448,6 @@ function legacyDuration(ctx: Context, j: Json): Maybe { }) } -function isCollection

( - ctx: C, - js: Iterable<[P, Json]>, - f: CtxFunc>, - keepGoing: boolean = true -): boolean { - let ok = true - for (const [c, j] of js) { - let itemOk = false - ctx.scope(c, () => f(ctx, [c, j]).peek(() => (itemOk = true))) - if (!itemOk && !keepGoing) { - return false - } - ok = ok && itemOk - } - return ok -} - type SetOpts = ListOpts & { requireDistinct?: boolean } @@ -500,7 +482,7 @@ function set( } type ArrayOpts = ListOpts & { - keepGoing?: boolean + itemErrorAction?: ItemErrorAction } function array( @@ -509,18 +491,9 @@ function array( f: CtxFunc>, opts?: ArrayOpts ): Maybe { - const arr: T[] = [] - - return list(ctx, j, opts) - .filter((js) => - isCollection( - ctx, - js.entries(), - (ctx, [_i, j]) => f(ctx, j).peek((v) => arr.push(v)), - opts?.keepGoing - ) - ) - .map(() => arr) + return list(ctx, j, opts).map((js) => + validate.array(ctx, js.entries(), f, opts?.itemErrorAction) + ) } function filterDataKeyValue( @@ -876,7 +849,7 @@ function summaryBuckets( minLength: 1, maxLength, maxLengthErrSuffix: ' (max_event_level_reports)', - keepGoing: false, // suppress unhelpful cascaded errors + itemErrorAction: ItemErrorAction.earlyExit, // suppress unhelpful cascaded errors }) } @@ -1596,7 +1569,7 @@ function triggerSummaryBucket(ctx: Context, j: Json): Maybe<[number, number]> { return array(ctx, j, endpoint, { minLength: 2, maxLength: 2, - keepGoing: false, + itemErrorAction: ItemErrorAction.earlyExit, }) as Maybe<[number, number]> } diff --git a/ts/src/header-validator/validate-os.ts b/ts/src/header-validator/validate-os.ts index 3bb9c4259f..77eb1f4704 100644 --- a/ts/src/header-validator/validate-os.ts +++ b/ts/src/header-validator/validate-os.ts @@ -1,5 +1,6 @@ import { Context, ValidationResult } from './context' import { Maybe } from './maybe' +import * as validate from './validate' import { InnerList, Item, @@ -13,10 +14,10 @@ export type OsItem = { debugReporting: boolean } -function parseItem(ctx: Context, member: InnerList | Item): OsItem | undefined { +function parseItem(ctx: Context, member: InnerList | Item): Maybe { if (typeof member[0] !== 'string') { ctx.warning('ignored, must be a string') - return + return Maybe.None } let url @@ -24,7 +25,7 @@ function parseItem(ctx: Context, member: InnerList | Item): OsItem | undefined { url = new URL(member[0]) } catch { ctx.warning('ignored, must contain a valid URL') - return + return Maybe.None } let debugReporting = false @@ -43,7 +44,7 @@ function parseItem(ctx: Context, member: InnerList | Item): OsItem | undefined { }) } - return { url, debugReporting } + return Maybe.some({ url, debugReporting }) } export function validateOsRegistration( @@ -59,16 +60,13 @@ export function validateOsRegistration( return [ctx.finish(msg), Maybe.None] } - const items: OsItem[] = [] - list.forEach((member, i) => - ctx.scope(i, () => { - const item = parseItem(ctx, member) - if (item) { - items.push(item) - } - }) + const items = validate.array( + ctx, + list.entries(), + parseItem, + validate.ItemErrorAction.ignore ) - return [ctx.finish(), Maybe.some(items)] + return [ctx.finish(), items] } export function serializeOsRegistration(items: OsItem[]): string { diff --git a/ts/src/header-validator/validate.ts b/ts/src/header-validator/validate.ts index fb4af4410b..dfae2b1a11 100644 --- a/ts/src/header-validator/validate.ts +++ b/ts/src/header-validator/validate.ts @@ -1,4 +1,4 @@ -import { Context } from './context' +import { Context, PathComponent } from './context' import { Maybe, Maybeable } from './maybe' export type CtxFunc = (ctx: C, i: I) => O @@ -132,3 +132,56 @@ export function make( struct: struct(unknownKeys, warnUnknownMsg), } } + +export enum ItemErrorAction { + ignore, + reportButKeepGoing, + earlyExit, +} + +export function isCollection< + P extends PathComponent, + V, + C extends Context = Context, +>( + ctx: C, + vs: Iterable<[P, V]>, + f: CtxFunc>, + itemErrorAction: ItemErrorAction = ItemErrorAction.reportButKeepGoing +): boolean { + let ok = true + for (const [c, v] of vs) { + let itemOk = false + ctx.scope(c, () => f(ctx, [c, v]).peek(() => (itemOk = true))) + if (!itemOk) { + if (itemErrorAction === ItemErrorAction.earlyExit) { + return false + } + if (itemErrorAction === ItemErrorAction.reportButKeepGoing) { + ok = false + } + } + } + return ok +} + +export function array( + ctx: C, + vs: Iterable<[number, V]>, + f: CtxFunc>, + itemErrorAction: ItemErrorAction = ItemErrorAction.reportButKeepGoing +): Maybe { + const arr: T[] = [] + + if ( + !isCollection( + ctx, + vs, + (ctx, [_i, v]) => f(ctx, v).peek((v) => arr.push(v)), + itemErrorAction + ) + ) { + return Maybe.None + } + return Maybe.some(arr) +}