Skip to content

Commit

Permalink
Update validation event schema (#35692)
Browse files Browse the repository at this point in the history
  • Loading branch information
heiskr authored Mar 21, 2023
1 parent 2060e7c commit 5f458a7
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 33 deletions.
18 changes: 13 additions & 5 deletions src/events/middleware.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import express from 'express'
import { omit } from 'lodash-es'
import { omit, without, mapValues } from 'lodash-es'
import Ajv from 'ajv'
import addFormats from 'ajv-formats'
import { eventSchema, hydroNames } from './schema.js'
import { schemas, hydroNames } from './schema.js'
import catchMiddlewareError from '../../middleware/catch-middleware-error.js'
import { noCacheControl } from '../../middleware/cache-control.js'
import Hydro from './hydro.js'
Expand All @@ -12,19 +12,27 @@ const router = express.Router()
const ajv = new Ajv()
addFormats(ajv)
const OMIT_FIELDS = ['type']
const allowedTypes = new Set(without(Object.keys(schemas), 'validation'))
const isDev = process.env.NODE_ENV === 'development'
const validations = mapValues(schemas, (schema) => ajv.compile(schema))

router.post(
'/',
catchMiddlewareError(async function postEvents(req, res) {
const isDev = process.env.NODE_ENV === 'development'
noCacheControl(res)

if (!ajv.validate(eventSchema, req.body)) {
// Make sure the type is supported before continuing
const { type } = req.body
if (!type || !allowedTypes.has(type)) {
return res.status(400).json({ message: 'Invalid type' })
}

// Validate the data matches the corresponding data schema
if (!validations[type](req.body)) {
return res.status(400).json(isDev ? ajv.errorsText() : {})
}

res.json({})

if (hydro.maySend()) {
try {
await hydro.publish(hydroNames[req.body.type], omit(req.body, OMIT_FIELDS))
Expand Down
90 changes: 62 additions & 28 deletions src/events/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { allVersionKeys } from '../../lib/all-versions.js'
import { productIds } from '../../lib/all-products.js'
import { allTools } from '../../lib/all-tools.js'

const versionPattern = '^\\d+(\\.\\d+)?(\\.\\d+)?$' // eslint-disable-line

const context = {
type: 'object',
additionalProperties: false,
Expand All @@ -23,7 +25,7 @@ const context = {
version: {
type: 'string',
description: 'The version of the event schema.',
pattern: '^\\d+(\\.\\d+)?(\\.\\d+)?$', // eslint-disable-line
pattern: versionPattern,
},
created: {
type: 'string',
Expand Down Expand Up @@ -157,7 +159,7 @@ const context = {
},
}

const pageSchema = {
const page = {
type: 'object',
additionalProperties: false,
required: ['type', 'context'],
Expand All @@ -170,7 +172,7 @@ const pageSchema = {
},
}

const exitSchema = {
const exit = {
type: 'object',
additionalProperties: false,
required: ['type', 'context'],
Expand Down Expand Up @@ -223,7 +225,7 @@ const exitSchema = {
},
}

const linkSchema = {
const link = {
type: 'object',
additionalProperties: false,
required: ['type', 'context', 'link_url'],
Expand All @@ -246,7 +248,7 @@ const linkSchema = {
},
}

const hoverSchema = {
const hover = {
type: 'object',
additionalProperties: false,
required: ['type', 'context', 'hover_url'],
Expand All @@ -269,7 +271,7 @@ const hoverSchema = {
},
}

const searchSchema = {
const search = {
type: 'object',
additionalProperties: false,
required: ['type', 'context', 'search_query'],
Expand All @@ -290,7 +292,7 @@ const searchSchema = {
},
}

const searchResultSchema = {
const searchResult = {
type: 'object',
additionalProperties: false,
required: [
Expand Down Expand Up @@ -332,7 +334,7 @@ const searchResultSchema = {
},
}

const navigateSchema = {
const navigate = {
type: 'object',
additionalProperties: false,
required: ['type', 'context'],
Expand All @@ -349,7 +351,7 @@ const navigateSchema = {
},
}

const surveySchema = {
const survey = {
type: 'object',
additionalProperties: false,
required: ['type', 'context', 'survey_vote'],
Expand All @@ -375,7 +377,7 @@ const surveySchema = {
},
}

const experimentSchema = {
const experiment = {
type: 'object',
additionalProperties: false,
required: ['type', 'context', 'experiment_name', 'experiment_variation'],
Expand All @@ -402,7 +404,7 @@ const experimentSchema = {
},
}

const clipboardSchema = {
const clipboard = {
type: 'object',
additionalProperties: false,
required: ['type', 'context', 'clipboard_operation'],
Expand All @@ -420,7 +422,7 @@ const clipboardSchema = {
},
}

const printSchema = {
const print = {
type: 'object',
additionalProperties: false,
required: ['type', 'context'],
Expand All @@ -433,7 +435,7 @@ const printSchema = {
},
}

const preferenceSchema = {
const preference = {
type: 'object',
additionalProperties: false,
required: ['type', 'context', 'preference_name', 'preference_value'],
Expand Down Expand Up @@ -465,27 +467,49 @@ const preferenceSchema = {
},
}

export const eventSchema = {
oneOf: [
pageSchema,
exitSchema,
linkSchema,
hoverSchema,
searchSchema,
searchResultSchema,
navigateSchema,
surveySchema,
experimentSchema,
clipboardSchema,
printSchema,
preferenceSchema,
],
const validation = {
type: 'object',
additionalProperties: false,
properties: {
event_id: { type: 'string', format: 'uuid' },
version: { type: 'string', pattern: versionPattern },
created: { type: 'string', format: 'date-time' },
raw: { type: 'string' },
// https://ajv.js.org/api.html#error-objects
keyword: { type: 'string' },
instance_path: { type: 'string' },
schema_path: { type: 'string' },
params: { type: 'string' },
property_name: { type: 'string' },
message: { type: 'string' },
schema: { type: 'string' },
parent_schema: { type: 'string' },
data: { type: 'string' },
},
}

// We are not using `oneOf` to keep the list of errors short.
export const schemas = {
page,
exit,
link,
hover,
search,
searchResult,
navigate,
survey,
experiment,
clipboard,
print,
preference,
validation,
}

export const hydroNames = {
page: 'docs.v0.PageEvent',
exit: 'docs.v0.ExitEvent',
link: 'docs.v0.LinkEvent',
hover: 'docs.v0.HoverEvent',
search: 'docs.v0.SearchEvent',
searchResult: 'docs.v0.SearchResultEvent',
navigate: 'docs.v0.NavigateEvent',
Expand All @@ -494,4 +518,14 @@ export const hydroNames = {
clipboard: 'docs.v0.ClipboardEvent',
print: 'docs.v0.PrintEvent',
preference: 'docs.v0.PreferenceEvent',
validation: 'docs.v0.ValidationEvent',
}

const schemasKeys = Object.keys(schemas)
const hydroNamesKeys = Object.keys(hydroNames)
if (
schemasKeys.length !== hydroNamesKeys.length ||
!schemasKeys.every((k) => hydroNamesKeys.includes(k))
) {
throw new Error("The keys in 'schemas' doesn't match with the keys in 'hydroNames'")
}

0 comments on commit 5f458a7

Please sign in to comment.