Fix European trip return heuristic for weekend location tracking
Adjust European short trip heuristic from >3 days to >1 day to correctly detect when user has returned home from European trips. This fixes the April 29-30, 2023 case where the location incorrectly showed "Sankt Georg, Hamburg" instead of "Bristol" when the user was free (no events scheduled) after the foss-north trip ended on April 27. The previous logic required more than 3 days to pass before assuming return home from European countries, but for short European trips by rail/ferry, users typically return within 1-2 days. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
663dc479c2
commit
ea4980a5d7
6407 changed files with 1072847 additions and 18 deletions
411
node_modules/ajv/lib/compile/jtd/parse.ts
generated
vendored
Normal file
411
node_modules/ajv/lib/compile/jtd/parse.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,411 @@
|
|||
import type Ajv from "../../core"
|
||||
import type {SchemaObject} from "../../types"
|
||||
import {jtdForms, JTDForm, SchemaObjectMap} from "./types"
|
||||
import {SchemaEnv, getCompilingSchema} from ".."
|
||||
import {_, str, and, or, nil, not, CodeGen, Code, Name, SafeExpr} from "../codegen"
|
||||
import MissingRefError from "../ref_error"
|
||||
import N from "../names"
|
||||
import {hasPropFunc} from "../../vocabularies/code"
|
||||
import {hasRef} from "../../vocabularies/jtd/ref"
|
||||
import {intRange, IntType} from "../../vocabularies/jtd/type"
|
||||
import {parseJson, parseJsonNumber, parseJsonString} from "../../runtime/parseJson"
|
||||
import {useFunc} from "../util"
|
||||
import validTimestamp from "../../runtime/timestamp"
|
||||
|
||||
type GenParse = (cxt: ParseCxt) => void
|
||||
|
||||
const genParse: {[F in JTDForm]: GenParse} = {
|
||||
elements: parseElements,
|
||||
values: parseValues,
|
||||
discriminator: parseDiscriminator,
|
||||
properties: parseProperties,
|
||||
optionalProperties: parseProperties,
|
||||
enum: parseEnum,
|
||||
type: parseType,
|
||||
ref: parseRef,
|
||||
}
|
||||
|
||||
interface ParseCxt {
|
||||
readonly gen: CodeGen
|
||||
readonly self: Ajv // current Ajv instance
|
||||
readonly schemaEnv: SchemaEnv
|
||||
readonly definitions: SchemaObjectMap
|
||||
schema: SchemaObject
|
||||
data: Code
|
||||
parseName: Name
|
||||
char: Name
|
||||
}
|
||||
|
||||
export default function compileParser(
|
||||
this: Ajv,
|
||||
sch: SchemaEnv,
|
||||
definitions: SchemaObjectMap
|
||||
): SchemaEnv {
|
||||
const _sch = getCompilingSchema.call(this, sch)
|
||||
if (_sch) return _sch
|
||||
const {es5, lines} = this.opts.code
|
||||
const {ownProperties} = this.opts
|
||||
const gen = new CodeGen(this.scope, {es5, lines, ownProperties})
|
||||
const parseName = gen.scopeName("parse")
|
||||
const cxt: ParseCxt = {
|
||||
self: this,
|
||||
gen,
|
||||
schema: sch.schema as SchemaObject,
|
||||
schemaEnv: sch,
|
||||
definitions,
|
||||
data: N.data,
|
||||
parseName,
|
||||
char: gen.name("c"),
|
||||
}
|
||||
|
||||
let sourceCode: string | undefined
|
||||
try {
|
||||
this._compilations.add(sch)
|
||||
sch.parseName = parseName
|
||||
parserFunction(cxt)
|
||||
gen.optimize(this.opts.code.optimize)
|
||||
const parseFuncCode = gen.toString()
|
||||
sourceCode = `${gen.scopeRefs(N.scope)}return ${parseFuncCode}`
|
||||
const makeParse = new Function(`${N.scope}`, sourceCode)
|
||||
const parse: (json: string) => unknown = makeParse(this.scope.get())
|
||||
this.scope.value(parseName, {ref: parse})
|
||||
sch.parse = parse
|
||||
} catch (e) {
|
||||
if (sourceCode) this.logger.error("Error compiling parser, function code:", sourceCode)
|
||||
delete sch.parse
|
||||
delete sch.parseName
|
||||
throw e
|
||||
} finally {
|
||||
this._compilations.delete(sch)
|
||||
}
|
||||
return sch
|
||||
}
|
||||
|
||||
const undef = _`undefined`
|
||||
|
||||
function parserFunction(cxt: ParseCxt): void {
|
||||
const {gen, parseName, char} = cxt
|
||||
gen.func(parseName, _`${N.json}, ${N.jsonPos}, ${N.jsonPart}`, false, () => {
|
||||
gen.let(N.data)
|
||||
gen.let(char)
|
||||
gen.assign(_`${parseName}.message`, undef)
|
||||
gen.assign(_`${parseName}.position`, undef)
|
||||
gen.assign(N.jsonPos, _`${N.jsonPos} || 0`)
|
||||
gen.const(N.jsonLen, _`${N.json}.length`)
|
||||
parseCode(cxt)
|
||||
skipWhitespace(cxt)
|
||||
gen.if(N.jsonPart, () => {
|
||||
gen.assign(_`${parseName}.position`, N.jsonPos)
|
||||
gen.return(N.data)
|
||||
})
|
||||
gen.if(_`${N.jsonPos} === ${N.jsonLen}`, () => gen.return(N.data))
|
||||
jsonSyntaxError(cxt)
|
||||
})
|
||||
}
|
||||
|
||||
function parseCode(cxt: ParseCxt): void {
|
||||
let form: JTDForm | undefined
|
||||
for (const key of jtdForms) {
|
||||
if (key in cxt.schema) {
|
||||
form = key
|
||||
break
|
||||
}
|
||||
}
|
||||
if (form) parseNullable(cxt, genParse[form])
|
||||
else parseEmpty(cxt)
|
||||
}
|
||||
|
||||
const parseBoolean = parseBooleanToken(true, parseBooleanToken(false, jsonSyntaxError))
|
||||
|
||||
function parseNullable(cxt: ParseCxt, parseForm: GenParse): void {
|
||||
const {gen, schema, data} = cxt
|
||||
if (!schema.nullable) return parseForm(cxt)
|
||||
tryParseToken(cxt, "null", parseForm, () => gen.assign(data, null))
|
||||
}
|
||||
|
||||
function parseElements(cxt: ParseCxt): void {
|
||||
const {gen, schema, data} = cxt
|
||||
parseToken(cxt, "[")
|
||||
const ix = gen.let("i", 0)
|
||||
gen.assign(data, _`[]`)
|
||||
parseItems(cxt, "]", () => {
|
||||
const el = gen.let("el")
|
||||
parseCode({...cxt, schema: schema.elements, data: el})
|
||||
gen.assign(_`${data}[${ix}++]`, el)
|
||||
})
|
||||
}
|
||||
|
||||
function parseValues(cxt: ParseCxt): void {
|
||||
const {gen, schema, data} = cxt
|
||||
parseToken(cxt, "{")
|
||||
gen.assign(data, _`{}`)
|
||||
parseItems(cxt, "}", () => parseKeyValue(cxt, schema.values))
|
||||
}
|
||||
|
||||
function parseItems(cxt: ParseCxt, endToken: string, block: () => void): void {
|
||||
tryParseItems(cxt, endToken, block)
|
||||
parseToken(cxt, endToken)
|
||||
}
|
||||
|
||||
function tryParseItems(cxt: ParseCxt, endToken: string, block: () => void): void {
|
||||
const {gen} = cxt
|
||||
gen.for(_`;${N.jsonPos}<${N.jsonLen} && ${jsonSlice(1)}!==${endToken};`, () => {
|
||||
block()
|
||||
tryParseToken(cxt, ",", () => gen.break(), hasItem)
|
||||
})
|
||||
|
||||
function hasItem(): void {
|
||||
tryParseToken(cxt, endToken, () => {}, jsonSyntaxError)
|
||||
}
|
||||
}
|
||||
|
||||
function parseKeyValue(cxt: ParseCxt, schema: SchemaObject): void {
|
||||
const {gen} = cxt
|
||||
const key = gen.let("key")
|
||||
parseString({...cxt, data: key})
|
||||
parseToken(cxt, ":")
|
||||
parsePropertyValue(cxt, key, schema)
|
||||
}
|
||||
|
||||
function parseDiscriminator(cxt: ParseCxt): void {
|
||||
const {gen, data, schema} = cxt
|
||||
const {discriminator, mapping} = schema
|
||||
parseToken(cxt, "{")
|
||||
gen.assign(data, _`{}`)
|
||||
const startPos = gen.const("pos", N.jsonPos)
|
||||
const value = gen.let("value")
|
||||
const tag = gen.let("tag")
|
||||
tryParseItems(cxt, "}", () => {
|
||||
const key = gen.let("key")
|
||||
parseString({...cxt, data: key})
|
||||
parseToken(cxt, ":")
|
||||
gen.if(
|
||||
_`${key} === ${discriminator}`,
|
||||
() => {
|
||||
parseString({...cxt, data: tag})
|
||||
gen.assign(_`${data}[${key}]`, tag)
|
||||
gen.break()
|
||||
},
|
||||
() => parseEmpty({...cxt, data: value}) // can be discarded/skipped
|
||||
)
|
||||
})
|
||||
gen.assign(N.jsonPos, startPos)
|
||||
gen.if(_`${tag} === undefined`)
|
||||
parsingError(cxt, str`discriminator tag not found`)
|
||||
for (const tagValue in mapping) {
|
||||
gen.elseIf(_`${tag} === ${tagValue}`)
|
||||
parseSchemaProperties({...cxt, schema: mapping[tagValue]}, discriminator)
|
||||
}
|
||||
gen.else()
|
||||
parsingError(cxt, str`discriminator value not in schema`)
|
||||
gen.endIf()
|
||||
}
|
||||
|
||||
function parseProperties(cxt: ParseCxt): void {
|
||||
const {gen, data} = cxt
|
||||
parseToken(cxt, "{")
|
||||
gen.assign(data, _`{}`)
|
||||
parseSchemaProperties(cxt)
|
||||
}
|
||||
|
||||
function parseSchemaProperties(cxt: ParseCxt, discriminator?: string): void {
|
||||
const {gen, schema, data} = cxt
|
||||
const {properties, optionalProperties, additionalProperties} = schema
|
||||
parseItems(cxt, "}", () => {
|
||||
const key = gen.let("key")
|
||||
parseString({...cxt, data: key})
|
||||
parseToken(cxt, ":")
|
||||
gen.if(false)
|
||||
parseDefinedProperty(cxt, key, properties)
|
||||
parseDefinedProperty(cxt, key, optionalProperties)
|
||||
if (discriminator) {
|
||||
gen.elseIf(_`${key} === ${discriminator}`)
|
||||
const tag = gen.let("tag")
|
||||
parseString({...cxt, data: tag}) // can be discarded, it is already assigned
|
||||
}
|
||||
gen.else()
|
||||
if (additionalProperties) {
|
||||
parseEmpty({...cxt, data: _`${data}[${key}]`})
|
||||
} else {
|
||||
parsingError(cxt, str`property ${key} not allowed`)
|
||||
}
|
||||
gen.endIf()
|
||||
})
|
||||
if (properties) {
|
||||
const hasProp = hasPropFunc(gen)
|
||||
const allProps: Code = and(
|
||||
...Object.keys(properties).map((p): Code => _`${hasProp}.call(${data}, ${p})`)
|
||||
)
|
||||
gen.if(not(allProps), () => parsingError(cxt, str`missing required properties`))
|
||||
}
|
||||
}
|
||||
|
||||
function parseDefinedProperty(cxt: ParseCxt, key: Name, schemas: SchemaObjectMap = {}): void {
|
||||
const {gen} = cxt
|
||||
for (const prop in schemas) {
|
||||
gen.elseIf(_`${key} === ${prop}`)
|
||||
parsePropertyValue(cxt, key, schemas[prop] as SchemaObject)
|
||||
}
|
||||
}
|
||||
|
||||
function parsePropertyValue(cxt: ParseCxt, key: Name, schema: SchemaObject): void {
|
||||
parseCode({...cxt, schema, data: _`${cxt.data}[${key}]`})
|
||||
}
|
||||
|
||||
function parseType(cxt: ParseCxt): void {
|
||||
const {gen, schema, data, self} = cxt
|
||||
switch (schema.type) {
|
||||
case "boolean":
|
||||
parseBoolean(cxt)
|
||||
break
|
||||
case "string":
|
||||
parseString(cxt)
|
||||
break
|
||||
case "timestamp": {
|
||||
parseString(cxt)
|
||||
const vts = useFunc(gen, validTimestamp)
|
||||
const {allowDate, parseDate} = self.opts
|
||||
const notValid = allowDate ? _`!${vts}(${data}, true)` : _`!${vts}(${data})`
|
||||
const fail: Code = parseDate
|
||||
? or(notValid, _`(${data} = new Date(${data}), false)`, _`isNaN(${data}.valueOf())`)
|
||||
: notValid
|
||||
gen.if(fail, () => parsingError(cxt, str`invalid timestamp`))
|
||||
break
|
||||
}
|
||||
case "float32":
|
||||
case "float64":
|
||||
parseNumber(cxt)
|
||||
break
|
||||
default: {
|
||||
const t = schema.type as IntType
|
||||
if (!self.opts.int32range && (t === "int32" || t === "uint32")) {
|
||||
parseNumber(cxt, 16) // 2 ** 53 - max safe integer
|
||||
if (t === "uint32") {
|
||||
gen.if(_`${data} < 0`, () => parsingError(cxt, str`integer out of range`))
|
||||
}
|
||||
} else {
|
||||
const [min, max, maxDigits] = intRange[t]
|
||||
parseNumber(cxt, maxDigits)
|
||||
gen.if(_`${data} < ${min} || ${data} > ${max}`, () =>
|
||||
parsingError(cxt, str`integer out of range`)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parseString(cxt: ParseCxt): void {
|
||||
parseToken(cxt, '"')
|
||||
parseWith(cxt, parseJsonString)
|
||||
}
|
||||
|
||||
function parseEnum(cxt: ParseCxt): void {
|
||||
const {gen, data, schema} = cxt
|
||||
const enumSch = schema.enum
|
||||
parseToken(cxt, '"')
|
||||
// TODO loopEnum
|
||||
gen.if(false)
|
||||
for (const value of enumSch) {
|
||||
const valueStr = JSON.stringify(value).slice(1) // remove starting quote
|
||||
gen.elseIf(_`${jsonSlice(valueStr.length)} === ${valueStr}`)
|
||||
gen.assign(data, str`${value}`)
|
||||
gen.add(N.jsonPos, valueStr.length)
|
||||
}
|
||||
gen.else()
|
||||
jsonSyntaxError(cxt)
|
||||
gen.endIf()
|
||||
}
|
||||
|
||||
function parseNumber(cxt: ParseCxt, maxDigits?: number): void {
|
||||
const {gen} = cxt
|
||||
skipWhitespace(cxt)
|
||||
gen.if(
|
||||
_`"-0123456789".indexOf(${jsonSlice(1)}) < 0`,
|
||||
() => jsonSyntaxError(cxt),
|
||||
() => parseWith(cxt, parseJsonNumber, maxDigits)
|
||||
)
|
||||
}
|
||||
|
||||
function parseBooleanToken(bool: boolean, fail: GenParse): GenParse {
|
||||
return (cxt) => {
|
||||
const {gen, data} = cxt
|
||||
tryParseToken(
|
||||
cxt,
|
||||
`${bool}`,
|
||||
() => fail(cxt),
|
||||
() => gen.assign(data, bool)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function parseRef(cxt: ParseCxt): void {
|
||||
const {gen, self, definitions, schema, schemaEnv} = cxt
|
||||
const {ref} = schema
|
||||
const refSchema = definitions[ref]
|
||||
if (!refSchema) throw new MissingRefError(self.opts.uriResolver, "", ref, `No definition ${ref}`)
|
||||
if (!hasRef(refSchema)) return parseCode({...cxt, schema: refSchema})
|
||||
const {root} = schemaEnv
|
||||
const sch = compileParser.call(self, new SchemaEnv({schema: refSchema, root}), definitions)
|
||||
partialParse(cxt, getParser(gen, sch), true)
|
||||
}
|
||||
|
||||
function getParser(gen: CodeGen, sch: SchemaEnv): Code {
|
||||
return sch.parse
|
||||
? gen.scopeValue("parse", {ref: sch.parse})
|
||||
: _`${gen.scopeValue("wrapper", {ref: sch})}.parse`
|
||||
}
|
||||
|
||||
function parseEmpty(cxt: ParseCxt): void {
|
||||
parseWith(cxt, parseJson)
|
||||
}
|
||||
|
||||
function parseWith(cxt: ParseCxt, parseFunc: {code: string}, args?: SafeExpr): void {
|
||||
partialParse(cxt, useFunc(cxt.gen, parseFunc), args)
|
||||
}
|
||||
|
||||
function partialParse(cxt: ParseCxt, parseFunc: Name, args?: SafeExpr): void {
|
||||
const {gen, data} = cxt
|
||||
gen.assign(data, _`${parseFunc}(${N.json}, ${N.jsonPos}${args ? _`, ${args}` : nil})`)
|
||||
gen.assign(N.jsonPos, _`${parseFunc}.position`)
|
||||
gen.if(_`${data} === undefined`, () => parsingError(cxt, _`${parseFunc}.message`))
|
||||
}
|
||||
|
||||
function parseToken(cxt: ParseCxt, tok: string): void {
|
||||
tryParseToken(cxt, tok, jsonSyntaxError)
|
||||
}
|
||||
|
||||
function tryParseToken(cxt: ParseCxt, tok: string, fail: GenParse, success?: GenParse): void {
|
||||
const {gen} = cxt
|
||||
const n = tok.length
|
||||
skipWhitespace(cxt)
|
||||
gen.if(
|
||||
_`${jsonSlice(n)} === ${tok}`,
|
||||
() => {
|
||||
gen.add(N.jsonPos, n)
|
||||
success?.(cxt)
|
||||
},
|
||||
() => fail(cxt)
|
||||
)
|
||||
}
|
||||
|
||||
function skipWhitespace({gen, char: c}: ParseCxt): void {
|
||||
gen.code(
|
||||
_`while((${c}=${N.json}[${N.jsonPos}],${c}===" "||${c}==="\\n"||${c}==="\\r"||${c}==="\\t"))${N.jsonPos}++;`
|
||||
)
|
||||
}
|
||||
|
||||
function jsonSlice(len: number | Name): Code {
|
||||
return len === 1
|
||||
? _`${N.json}[${N.jsonPos}]`
|
||||
: _`${N.json}.slice(${N.jsonPos}, ${N.jsonPos}+${len})`
|
||||
}
|
||||
|
||||
function jsonSyntaxError(cxt: ParseCxt): void {
|
||||
parsingError(cxt, _`"unexpected token " + ${N.json}[${N.jsonPos}]`)
|
||||
}
|
||||
|
||||
function parsingError({gen, parseName}: ParseCxt, msg: Code): void {
|
||||
gen.assign(_`${parseName}.message`, msg)
|
||||
gen.assign(_`${parseName}.position`, N.jsonPos)
|
||||
gen.return(undef)
|
||||
}
|
||||
266
node_modules/ajv/lib/compile/jtd/serialize.ts
generated
vendored
Normal file
266
node_modules/ajv/lib/compile/jtd/serialize.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,266 @@
|
|||
import type Ajv from "../../core"
|
||||
import type {SchemaObject} from "../../types"
|
||||
import {jtdForms, JTDForm, SchemaObjectMap} from "./types"
|
||||
import {SchemaEnv, getCompilingSchema} from ".."
|
||||
import {_, str, and, getProperty, CodeGen, Code, Name} from "../codegen"
|
||||
import MissingRefError from "../ref_error"
|
||||
import N from "../names"
|
||||
import {isOwnProperty} from "../../vocabularies/code"
|
||||
import {hasRef} from "../../vocabularies/jtd/ref"
|
||||
import {useFunc} from "../util"
|
||||
import quote from "../../runtime/quote"
|
||||
|
||||
const genSerialize: {[F in JTDForm]: (cxt: SerializeCxt) => void} = {
|
||||
elements: serializeElements,
|
||||
values: serializeValues,
|
||||
discriminator: serializeDiscriminator,
|
||||
properties: serializeProperties,
|
||||
optionalProperties: serializeProperties,
|
||||
enum: serializeString,
|
||||
type: serializeType,
|
||||
ref: serializeRef,
|
||||
}
|
||||
|
||||
interface SerializeCxt {
|
||||
readonly gen: CodeGen
|
||||
readonly self: Ajv // current Ajv instance
|
||||
readonly schemaEnv: SchemaEnv
|
||||
readonly definitions: SchemaObjectMap
|
||||
schema: SchemaObject
|
||||
data: Code
|
||||
}
|
||||
|
||||
export default function compileSerializer(
|
||||
this: Ajv,
|
||||
sch: SchemaEnv,
|
||||
definitions: SchemaObjectMap
|
||||
): SchemaEnv {
|
||||
const _sch = getCompilingSchema.call(this, sch)
|
||||
if (_sch) return _sch
|
||||
const {es5, lines} = this.opts.code
|
||||
const {ownProperties} = this.opts
|
||||
const gen = new CodeGen(this.scope, {es5, lines, ownProperties})
|
||||
const serializeName = gen.scopeName("serialize")
|
||||
const cxt: SerializeCxt = {
|
||||
self: this,
|
||||
gen,
|
||||
schema: sch.schema as SchemaObject,
|
||||
schemaEnv: sch,
|
||||
definitions,
|
||||
data: N.data,
|
||||
}
|
||||
|
||||
let sourceCode: string | undefined
|
||||
try {
|
||||
this._compilations.add(sch)
|
||||
sch.serializeName = serializeName
|
||||
gen.func(serializeName, N.data, false, () => {
|
||||
gen.let(N.json, str``)
|
||||
serializeCode(cxt)
|
||||
gen.return(N.json)
|
||||
})
|
||||
gen.optimize(this.opts.code.optimize)
|
||||
const serializeFuncCode = gen.toString()
|
||||
sourceCode = `${gen.scopeRefs(N.scope)}return ${serializeFuncCode}`
|
||||
const makeSerialize = new Function(`${N.scope}`, sourceCode)
|
||||
const serialize: (data: unknown) => string = makeSerialize(this.scope.get())
|
||||
this.scope.value(serializeName, {ref: serialize})
|
||||
sch.serialize = serialize
|
||||
} catch (e) {
|
||||
if (sourceCode) this.logger.error("Error compiling serializer, function code:", sourceCode)
|
||||
delete sch.serialize
|
||||
delete sch.serializeName
|
||||
throw e
|
||||
} finally {
|
||||
this._compilations.delete(sch)
|
||||
}
|
||||
return sch
|
||||
}
|
||||
|
||||
function serializeCode(cxt: SerializeCxt): void {
|
||||
let form: JTDForm | undefined
|
||||
for (const key of jtdForms) {
|
||||
if (key in cxt.schema) {
|
||||
form = key
|
||||
break
|
||||
}
|
||||
}
|
||||
serializeNullable(cxt, form ? genSerialize[form] : serializeEmpty)
|
||||
}
|
||||
|
||||
function serializeNullable(cxt: SerializeCxt, serializeForm: (_cxt: SerializeCxt) => void): void {
|
||||
const {gen, schema, data} = cxt
|
||||
if (!schema.nullable) return serializeForm(cxt)
|
||||
gen.if(
|
||||
_`${data} === undefined || ${data} === null`,
|
||||
() => gen.add(N.json, _`"null"`),
|
||||
() => serializeForm(cxt)
|
||||
)
|
||||
}
|
||||
|
||||
function serializeElements(cxt: SerializeCxt): void {
|
||||
const {gen, schema, data} = cxt
|
||||
gen.add(N.json, str`[`)
|
||||
const first = gen.let("first", true)
|
||||
gen.forOf("el", data, (el) => {
|
||||
addComma(cxt, first)
|
||||
serializeCode({...cxt, schema: schema.elements, data: el})
|
||||
})
|
||||
gen.add(N.json, str`]`)
|
||||
}
|
||||
|
||||
function serializeValues(cxt: SerializeCxt): void {
|
||||
const {gen, schema, data} = cxt
|
||||
gen.add(N.json, str`{`)
|
||||
const first = gen.let("first", true)
|
||||
gen.forIn("key", data, (key) => serializeKeyValue(cxt, key, schema.values, first))
|
||||
gen.add(N.json, str`}`)
|
||||
}
|
||||
|
||||
function serializeKeyValue(cxt: SerializeCxt, key: Name, schema: SchemaObject, first?: Name): void {
|
||||
const {gen, data} = cxt
|
||||
addComma(cxt, first)
|
||||
serializeString({...cxt, data: key})
|
||||
gen.add(N.json, str`:`)
|
||||
const value = gen.const("value", _`${data}${getProperty(key)}`)
|
||||
serializeCode({...cxt, schema, data: value})
|
||||
}
|
||||
|
||||
function serializeDiscriminator(cxt: SerializeCxt): void {
|
||||
const {gen, schema, data} = cxt
|
||||
const {discriminator} = schema
|
||||
gen.add(N.json, str`{${JSON.stringify(discriminator)}:`)
|
||||
const tag = gen.const("tag", _`${data}${getProperty(discriminator)}`)
|
||||
serializeString({...cxt, data: tag})
|
||||
gen.if(false)
|
||||
for (const tagValue in schema.mapping) {
|
||||
gen.elseIf(_`${tag} === ${tagValue}`)
|
||||
const sch = schema.mapping[tagValue]
|
||||
serializeSchemaProperties({...cxt, schema: sch}, discriminator)
|
||||
}
|
||||
gen.endIf()
|
||||
gen.add(N.json, str`}`)
|
||||
}
|
||||
|
||||
function serializeProperties(cxt: SerializeCxt): void {
|
||||
const {gen} = cxt
|
||||
gen.add(N.json, str`{`)
|
||||
serializeSchemaProperties(cxt)
|
||||
gen.add(N.json, str`}`)
|
||||
}
|
||||
|
||||
function serializeSchemaProperties(cxt: SerializeCxt, discriminator?: string): void {
|
||||
const {gen, schema, data} = cxt
|
||||
const {properties, optionalProperties} = schema
|
||||
const props = keys(properties)
|
||||
const optProps = keys(optionalProperties)
|
||||
const allProps = allProperties(props.concat(optProps))
|
||||
let first = !discriminator
|
||||
let firstProp: Name | undefined
|
||||
|
||||
for (const key of props) {
|
||||
if (first) first = false
|
||||
else gen.add(N.json, str`,`)
|
||||
serializeProperty(key, properties[key], keyValue(key))
|
||||
}
|
||||
if (first) firstProp = gen.let("first", true)
|
||||
for (const key of optProps) {
|
||||
const value = keyValue(key)
|
||||
gen.if(and(_`${value} !== undefined`, isOwnProperty(gen, data, key)), () => {
|
||||
addComma(cxt, firstProp)
|
||||
serializeProperty(key, optionalProperties[key], value)
|
||||
})
|
||||
}
|
||||
if (schema.additionalProperties) {
|
||||
gen.forIn("key", data, (key) =>
|
||||
gen.if(isAdditional(key, allProps), () => serializeKeyValue(cxt, key, {}, firstProp))
|
||||
)
|
||||
}
|
||||
|
||||
function keys(ps?: SchemaObjectMap): string[] {
|
||||
return ps ? Object.keys(ps) : []
|
||||
}
|
||||
|
||||
function allProperties(ps: string[]): string[] {
|
||||
if (discriminator) ps.push(discriminator)
|
||||
if (new Set(ps).size !== ps.length) {
|
||||
throw new Error("JTD: properties/optionalProperties/disciminator overlap")
|
||||
}
|
||||
return ps
|
||||
}
|
||||
|
||||
function keyValue(key: string): Name {
|
||||
return gen.const("value", _`${data}${getProperty(key)}`)
|
||||
}
|
||||
|
||||
function serializeProperty(key: string, propSchema: SchemaObject, value: Name): void {
|
||||
gen.add(N.json, str`${JSON.stringify(key)}:`)
|
||||
serializeCode({...cxt, schema: propSchema, data: value})
|
||||
}
|
||||
|
||||
function isAdditional(key: Name, ps: string[]): Code | true {
|
||||
return ps.length ? and(...ps.map((p) => _`${key} !== ${p}`)) : true
|
||||
}
|
||||
}
|
||||
|
||||
function serializeType(cxt: SerializeCxt): void {
|
||||
const {gen, schema, data} = cxt
|
||||
switch (schema.type) {
|
||||
case "boolean":
|
||||
gen.add(N.json, _`${data} ? "true" : "false"`)
|
||||
break
|
||||
case "string":
|
||||
serializeString(cxt)
|
||||
break
|
||||
case "timestamp":
|
||||
gen.if(
|
||||
_`${data} instanceof Date`,
|
||||
() => gen.add(N.json, _`'"' + ${data}.toISOString() + '"'`),
|
||||
() => serializeString(cxt)
|
||||
)
|
||||
break
|
||||
default:
|
||||
serializeNumber(cxt)
|
||||
}
|
||||
}
|
||||
|
||||
function serializeString({gen, data}: SerializeCxt): void {
|
||||
gen.add(N.json, _`${useFunc(gen, quote)}(${data})`)
|
||||
}
|
||||
|
||||
function serializeNumber({gen, data}: SerializeCxt): void {
|
||||
gen.add(N.json, _`"" + ${data}`)
|
||||
}
|
||||
|
||||
function serializeRef(cxt: SerializeCxt): void {
|
||||
const {gen, self, data, definitions, schema, schemaEnv} = cxt
|
||||
const {ref} = schema
|
||||
const refSchema = definitions[ref]
|
||||
if (!refSchema) throw new MissingRefError(self.opts.uriResolver, "", ref, `No definition ${ref}`)
|
||||
if (!hasRef(refSchema)) return serializeCode({...cxt, schema: refSchema})
|
||||
const {root} = schemaEnv
|
||||
const sch = compileSerializer.call(self, new SchemaEnv({schema: refSchema, root}), definitions)
|
||||
gen.add(N.json, _`${getSerialize(gen, sch)}(${data})`)
|
||||
}
|
||||
|
||||
function getSerialize(gen: CodeGen, sch: SchemaEnv): Code {
|
||||
return sch.serialize
|
||||
? gen.scopeValue("serialize", {ref: sch.serialize})
|
||||
: _`${gen.scopeValue("wrapper", {ref: sch})}.serialize`
|
||||
}
|
||||
|
||||
function serializeEmpty({gen, data}: SerializeCxt): void {
|
||||
gen.add(N.json, _`JSON.stringify(${data})`)
|
||||
}
|
||||
|
||||
function addComma({gen}: SerializeCxt, first?: Name): void {
|
||||
if (first) {
|
||||
gen.if(
|
||||
first,
|
||||
() => gen.assign(first, false),
|
||||
() => gen.add(N.json, str`,`)
|
||||
)
|
||||
} else {
|
||||
gen.add(N.json, str`,`)
|
||||
}
|
||||
}
|
||||
16
node_modules/ajv/lib/compile/jtd/types.ts
generated
vendored
Normal file
16
node_modules/ajv/lib/compile/jtd/types.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import type {SchemaObject} from "../../types"
|
||||
|
||||
export type SchemaObjectMap = {[Ref in string]?: SchemaObject}
|
||||
|
||||
export const jtdForms = [
|
||||
"elements",
|
||||
"values",
|
||||
"discriminator",
|
||||
"properties",
|
||||
"optionalProperties",
|
||||
"enum",
|
||||
"type",
|
||||
"ref",
|
||||
] as const
|
||||
|
||||
export type JTDForm = typeof jtdForms[number]
|
||||
Loading…
Add table
Add a link
Reference in a new issue