Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: input tracking #5496

Draft
wants to merge 46 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
93d2931
Save
thsig Nov 21, 2023
595e652
test: add test and first take at implementation for simple, 1 ref
stefreak Nov 21, 2023
9d9ecf0
test: add simple test for ensuring we get leafs
stefreak Nov 21, 2023
e2ebf08
fix: resolve references only once. Uncomment the throw.
stefreak Nov 21, 2023
3c06ecd
fix: build
stefreak Nov 21, 2023
73dcad9
test: records template references (array, 1 reference)
stefreak Nov 22, 2023
c125de4
fix: path accounting
stefreak Nov 22, 2023
00d8a82
fix: add reference target logic
thsig Nov 22, 2023
4bff56a
Save
thsig Nov 22, 2023
24ea2e1
test: more test cases
stefreak Nov 22, 2023
fcc37d4
wip
stefreak Nov 24, 2023
d47d5e1
wip (does not compile)
stefreak Nov 24, 2023
c80a2cf
wip (compiles now)
stefreak Nov 24, 2023
3f9fa88
wip: rename the types in inputs.ts
stefreak Nov 24, 2023
49ca406
wip: temporary hack to avoid changing the parser right now
stefreak Nov 27, 2023
1f7ee2a
wip: remove recorder. compiles, but all tests fail.
stefreak Nov 27, 2023
1484e9d
wip: fix some tests
stefreak Nov 27, 2023
5504c56
wip: start implementing template string AST
stefreak Nov 29, 2023
3e72e3d
wip: all template tests pass, except for blocks and partial
stefreak Nov 30, 2023
e99008c
ast: all template string tests pass
stefreak Dec 1, 2023
cac9d94
undo accidental change
stefreak Dec 4, 2023
a9ef07e
wip
stefreak Dec 4, 2023
a1b5405
progress: lazy evaluation
stefreak Dec 5, 2023
81f5dea
wip: lazy evaluation
stefreak Dec 5, 2023
f507d35
wip: fix tests
stefreak Dec 6, 2023
6db6747
lazy: all resolveTemplateString and input tracking tests pass with full
stefreak Dec 6, 2023
6a5504b
proxy: wip
stefreak Dec 7, 2023
88e67b2
wip: working proxy and more tests
stefreak Dec 7, 2023
5320506
add test for partial proxy
stefreak Dec 7, 2023
70670d9
partial proxy & logical operators
stefreak Dec 7, 2023
30eece3
wip
stefreak Dec 8, 2023
dd634d4
wip: mutable proxy overlay
stefreak Dec 8, 2023
7a3f170
wip: make proxy read-only and record changes during validation / refi…
stefreak Dec 11, 2023
f342808
test(validation): add failing test for withContext and schema override
stefreak Dec 11, 2023
04adf6b
fix: ensure Object.keys works correctly on array
TimBeyer Dec 11, 2023
e944ea2
chore: remove confusing lazy class
stefreak Dec 11, 2023
dfbee86
chore: add some docs for diffing logic
TimBeyer Dec 11, 2023
f8fc3f4
refactor: do overlays on the config object
TimBeyer Dec 11, 2023
3960f9a
wip
stefreak Dec 11, 2023
58e4297
allow refining multiple times
stefreak Dec 11, 2023
1ad69f9
wip: add joi refine method
stefreak Dec 12, 2023
edab666
wip: integrate 1. next: pickEnvironment
stefreak Dec 12, 2023
7fb1f04
wip: provider initialization
stefreak Dec 13, 2023
da81199
wip: scanAndAddConfigs
stefreak Dec 13, 2023
44a0a5e
wip. next: renderConfigTemplate
stefreak Dec 14, 2023
9038449
wip: renderModules and renderConfigTemplate
stefreak Dec 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 88 additions & 22 deletions core/src/config/template-contexts/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,42 @@
import type Joi from "@hapi/joi"
import { isString } from "lodash-es"
import { ConfigurationError } from "../../exceptions.js"
import {
resolveTemplateString,
TemplateStringMissingKeyException,
TemplateStringPassthroughException,
} from "../../template-string/template-string.js"
import { resolveTemplateString } from "../../template-string/template-string.js"
import type { CustomObjectSchema } from "../common.js"
import { isPrimitive, joi, joiIdentifier } from "../common.js"
import { KeyedSet } from "../../util/keyed-set.js"
import { naturalList } from "../../util/string.js"
import { styles } from "../../logger/styles.js"
import type { CollectionOrValue } from "../../template-string/inputs.js"
import { TemplateLeaf, isTemplateLeafValue, isTemplateLeaf } from "../../template-string/inputs.js"
import { deepMap } from "../../util/objects.js"

export type ContextKeySegment = string | number
export type ContextKey = ContextKeySegment[]

export type ObjectPath = (string | number)[]

export interface ContextResolveOpts {
// Allow templates to be partially resolved (used to defer runtime template resolution, for example)
allowPartial?: boolean
// a list of previously resolved paths, used to detect circular references
stack?: string[]
// Unescape escaped template strings
unescape?: boolean

/**
* The real YAML path after parsing YAML, e.g. ["spec", "foobar", 0, "$merge"]
*/
yamlPath?: ObjectPath
/**
* The actualy key path is different than the result key path; So in case we are evaluating expression objects we also track the result path.
*
* Example: ["spec", "foobar", 0] (Can't contain specialy keys like $merge etc)
*
* This option is used when recording references to template variables while evaluating expression objects like $if, $merge, $forEach.
*
*/
resultPath?: ObjectPath
}

export interface ContextResolveParams {
Expand All @@ -41,7 +56,10 @@ export interface ContextResolveParams {
export interface ContextResolveOutput {
message?: string
partial?: boolean
resolved: any
result: CollectionOrValue
cached: boolean
// for input tracking
// ResolvedResult: ResolvedResult
}

export function schema(joiSchema: Joi.Schema) {
Expand All @@ -56,9 +74,10 @@ export interface ConfigContextType {
}

// Note: we're using classes here to be able to use decorators to describe each context node and key
// TODO-steffen&thor: Make all instance variables of all config context classes read-only.
export abstract class ConfigContext {
private readonly _rootContext: ConfigContext
private readonly _resolvedValues: { [path: string]: any }
private readonly _resolvedValues: { [path: string]: CollectionOrValue }

// This is used for special-casing e.g. runtime.* resolution
protected _alwaysAllowPartial: boolean
Expand Down Expand Up @@ -87,10 +106,10 @@ export abstract class ConfigContext {
const fullPath = renderKeyPath(nodePath.concat(key))

// if the key has previously been resolved, return it directly
const resolved = this._resolvedValues[path]
const cachedResult = this._resolvedValues[path]

if (resolved) {
return { resolved }
if (cachedResult) {
return { cached: true, result: cachedResult }
}

opts.stack = [...(opts.stack || [])]
Expand Down Expand Up @@ -150,9 +169,14 @@ export abstract class ConfigContext {
if (remainder.length > 0) {
opts.stack.push(stackEntry)
const res = value.resolve({ key: remainder, nodePath: nestedNodePath, opts })
value = res.resolved
value = res.result
message = res.message
partial = !!res.partial
} else {
// TODO: improve error message
throw new ConfigurationError({
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe instead of error, it should just result in a plain object with all the values the context contains.

message: `Resolving to a context is not allowed.`,
})
}
break
}
Expand All @@ -163,6 +187,10 @@ export abstract class ConfigContext {
value = resolveTemplateString({ string: value, context: this._rootContext, contextOpts: opts })
}

if (isTemplateLeaf(value)) {
break
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Break is not the right thing to do here for LazyValue; We need to unwrap the lazy value and continue diving into it, actually.

}

if (value === undefined) {
break
}
Expand Down Expand Up @@ -191,26 +219,60 @@ export abstract class ConfigContext {
if (this._alwaysAllowPartial) {
// We use a separate exception type when contexts are specifically indicating that unresolvable keys should
// be passed through. This is caught in the template parser code.
throw new TemplateStringPassthroughException({
message,
})
// throw new TemplateStringPassthroughException({
// message,
// })
} else if (opts.allowPartial) {
throw new TemplateStringMissingKeyException({
message,
})
// throw new TemplateStringMissingKeyException({
// message,
// })
} else {
// Otherwise we return the undefined value, so that any logical expressions can be evaluated appropriately.
// The template resolver will throw the error later if appropriate.
return { resolved: undefined, message }
return {
message,
cached: false,
result: new TemplateLeaf({
expr: undefined,
value: undefined,
inputs: {},
}),
}
}
}

let result: CollectionOrValue

if (isTemplateLeaf(value)) {
result = value
}
// Wrap normal data using deepMap
else if (isTemplateLeafValue(value)) {
result = new TemplateLeaf({
expr: undefined,
value,
inputs: {},
})
} else {
// value is a collection
result = deepMap(value, (v) => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be templatePrimitiveDeepMap

if (isTemplateLeaf(v)) {
return v
}
return new TemplateLeaf({
expr: undefined,
value: v,
inputs: {},
})
})
}

// Cache result, unless it is a partial resolution
if (!partial) {
this._resolvedValues[path] = value
this._resolvedValues[path] = result
}

return { resolved: value }
return { cached: false, result }
}
}

Expand Down Expand Up @@ -242,7 +304,11 @@ export class ScanContext extends ConfigContext {
override resolve({ key, nodePath }: ContextResolveParams) {
const fullKey = nodePath.concat(key)
this.foundKeys.add(fullKey)
return { resolved: renderTemplateString(fullKey), partial: true }
return {
partial: true,
cached: false,
result: { value: renderTemplateString(fullKey), expr: undefined, inputs: {} },
}
}
}

Expand Down Expand Up @@ -319,7 +385,7 @@ function renderTemplateString(key: ContextKeySegment[]) {
/**
* Given all the segments of a template string, return a string path for the key.
*/
function renderKeyPath(key: ContextKeySegment[]): string {
export function renderKeyPath(key: ContextKeySegment[]): string {
// Note: We don't support bracket notation for the first part in a template string
if (key.length === 0) {
return ""
Expand Down
Loading