@nmnmcc/ability 2.1.4 → 2.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/internal/authorizer.js +3 -2
- package/dist/internal/authorizer.js.map +1 -1
- package/dist/internal/bind.js +10 -10
- package/dist/internal/bind.js.map +1 -1
- package/dist/internal/json.js +42 -34
- package/dist/internal/json.js.map +1 -1
- package/package.json +1 -1
- package/src/internal/authorizer.ts +7 -2
- package/src/internal/bind.ts +9 -1
- package/src/internal/json.ts +21 -8
|
@@ -99,7 +99,8 @@ var PrincipalKey = class PrincipalKey {
|
|
|
99
99
|
* to one walk.
|
|
100
100
|
*
|
|
101
101
|
* A value that keeps its data inside, with no own fields, as a `Date`, a
|
|
102
|
-
* `URL` or a `RegExp
|
|
102
|
+
* `URL` or a `RegExp` with the place of its next match, is in the text with
|
|
103
|
+
* its data, and so is an `Option`.
|
|
103
104
|
* For another such value, the walk can not see the data, so it throws, and
|
|
104
105
|
* the principal gets no key: it is not kept.
|
|
105
106
|
*/
|
|
@@ -113,7 +114,7 @@ const orderOf = (root) => {
|
|
|
113
114
|
const items = (list) => R.forEach(list, (item) => child(item, depth + 1));
|
|
114
115
|
if (value instanceof Date) return R.succeed(`date:${String(value.getTime())}`);
|
|
115
116
|
if (value instanceof URL) return R.succeed(`url:${JSON.stringify(value.href)}`);
|
|
116
|
-
if (value instanceof RegExp) return R.succeed(`regexp:${JSON.stringify(String(value))}`);
|
|
117
|
+
if (value instanceof RegExp) return R.succeed(`regexp:${JSON.stringify(String(value))}:${String(value.lastIndex)}`);
|
|
117
118
|
if (Option.isOption(value)) return Option.isSome(value) ? R.map(child(value.value, depth + 1), (text) => `some(${text})`) : R.succeed("none");
|
|
118
119
|
if (Array.isArray(value)) return R.map(items(value), (texts) => `[${texts.join(",")}]`);
|
|
119
120
|
if (value instanceof Map || value instanceof Set || HashMap.isHashMap(value) || HashSet.isHashSet(value) || Chunk.isChunk(value)) return R.map(items(Array.from(value)), (texts) => `<${texts.join(",")}>`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authorizer.js","names":["Trampoline.adaptive","Policies.provideContext","Decide.decideAny","ResourceData.kindOf","DomainData.descriptorOf","Decide.fitsKind","Analysis.decideKind","Decision.reason","Analysis.coverageAny","permittedFields","Decide.permittedFields"],"sources":["../../src/internal/authorizer.ts"],"sourcesContent":["/**\n * The values of authorizers: the errors, the service class and the layer.\n * The types are in `Authorizer.ts`.\n */\n// The service erases the error type and the service type of predicates,\n// principals and deciders to `unknown`. The public types keep them, so the\n// language service check for `unknown` in an Effect is off here.\n// @effect-diagnostics anyUnknownInErrorContext:off\nimport * as Arr from \"effect/Array\"\nimport * as Cache from \"effect/Cache\"\nimport * as Chunk from \"effect/Chunk\"\nimport * as Context from \"effect/Context\"\nimport type * as Duration from \"effect/Duration\"\nimport * as Effect from \"effect/Effect\"\nimport * as Equal from \"effect/Equal\"\nimport * as Hash from \"effect/Hash\"\nimport * as HashMap from \"effect/HashMap\"\nimport * as HashSet from \"effect/HashSet\"\nimport * as Layer from \"effect/Layer\"\nimport * as Option from \"effect/Option\"\nimport * as Result from \"effect/Result\"\nimport * as Schema from \"effect/Schema\"\nimport type * as Authorizer from \"../Authorizer.ts\"\nimport type * as Condition from \"../Condition.ts\"\nimport * as Decision from \"../Decision.ts\"\nimport type * as Domain from \"../Domain.ts\"\nimport type * as Policy from \"../Policy.ts\"\nimport * as Analysis from \"./analysis.ts\"\nimport * as Decide from \"./decide.ts\"\nimport * as DomainData from \"./domain.ts\"\nimport * as Policies from \"./policy.ts\"\nimport * as ResourceData from \"./resource.ts\"\nimport * as Trampoline from \"./trampoline.ts\"\n\ntype AnyServiceClass = Authorizer.AnyServiceClass\n\n/** The error when a policy says no. */\nexport class Forbidden extends Schema.TaggedError<Forbidden>(\n\t\"@nmnmcc/ability/Authorizer/Forbidden\",\n)(\"Forbidden\", {\n\taction: Schema.String,\n\tkind: Schema.String,\n\tfield: Schema.optionalKey(Schema.String),\n\treason: Schema.optionalKey(Schema.String),\n}) {}\n\n/** The error when the policy of the principal can not be loaded. */\nexport class PolicyUnavailable extends Schema.TaggedError<PolicyUnavailable>(\n\t\"@nmnmcc/ability/Authorizer/PolicyUnavailable\",\n)(\"PolicyUnavailable\", { cause: Schema.Defect() }) {}\n\n/** The error when a decider must wait for an answer. */\nexport class Pending extends Schema.TaggedError<Pending>(\n\t\"@nmnmcc/ability/Authorizer/Pending\",\n)(\"Pending\", {\n\tticket: Schema.String,\n\treason: Schema.optionalKey(Schema.String),\n}) {}\n\n/** Makes an authorizer service class for a domain. */\nexport const Service =\n\t() =>\n\t(\n\t\tid: string,\n\t\toptions: {\n\t\t\treadonly domain: Domain.Any\n\t\t\treadonly principal: Effect.Effect<any, any, any>\n\t\t},\n\t\t// The class has no name, and its type uses types of Effect that a\n\t\t// declaration file can not name. So this function gives a `Context.Key`.\n\t\t// The public module gives the full type.\n\t): Context.Key<any, any> => {\n\t\tconst Base = Context.Service<any, any>()(id)\n\t\treturn class extends Base {\n\t\t\tstatic readonly domain = options.domain\n\t\t\tstatic readonly principal = options.principal\n\t\t\tstatic readonly decide = (target: AnyTarget) =>\n\t\t\t\tthis.use((service: any) => service.decide(target))\n\t\t\tstatic readonly check = (target: AnyTarget) =>\n\t\t\t\tthis.use((service: any) => service.check(target))\n\t\t\tstatic readonly guard = (target: AnyTarget, options: unknown) =>\n\t\t\t\tthis.use((service: any) => service.guard(target, options))\n\t\t\tstatic readonly filter = (target: AnyTarget) =>\n\t\t\t\tthis.use((service: any) => service.filter(target))\n\t\t\tstatic readonly coverage = (target: AnyTarget) =>\n\t\t\t\tthis.use((service: any) => service.coverage(target))\n\t\t\tstatic readonly permittedFields = (target: AnyTarget) =>\n\t\t\t\tthis.use((service: any) => service.permittedFields(target))\n\t\t}\n\t}\n\ntype AnyTarget = {\n\treadonly action: string\n\treadonly resource?: object\n\treadonly kind?: string\n\treadonly field?: string\n\treadonly within?: unknown\n}\n\n/**\n * A cache key for a principal. Two keys are equal when they have the same\n * text, so two principals with the same key share one policy. A key from the\n * JSON form also needs equal decoded principals: a Schema may make data when\n * it decodes, as `expired` from a time, so one text may give two principals.\n * The key also keeps the principal, so the cache can load its policy. A plain\n * `Data` value can not do all this, so the key gives its own equality with\n * `Equal.Equal`.\n */\nclass PrincipalKey implements Equal.Equal {\n\treadonly principal: object\n\treadonly text: string\n\t/**\n\t * The order of the parts of the decoded principal, from {@link orderOf},\n\t * when the key is the JSON form. The decoded principals must be equal\n\t * too, with their parts in the same order. `undefined` for a key from\n\t * `cache.key`.\n\t */\n\treadonly order: string | undefined\n\tconstructor(principal: object, text: string, order: string | undefined) {\n\t\tthis.principal = principal\n\t\tthis.text = text\n\t\tthis.order = order\n\t}\n\t[Equal.symbol](that: unknown): boolean {\n\t\treturn (\n\t\t\tthat instanceof PrincipalKey &&\n\t\t\tthat.text === this.text &&\n\t\t\tthat.order === this.order &&\n\t\t\t(this.order === undefined || Equal.equals(that.principal, this.principal))\n\t\t)\n\t}\n\t[Hash.symbol](): number {\n\t\treturn Hash.string(this.text)\n\t}\n}\n\n/**\n * The text of the order of the parts of a value: the names of the fields of\n * each object, and the items of each list, set and map, with each scalar, in\n * the order that a loop sees them. `Equal` does not see the order of the\n * fields of an object or of the items of a map, but a loader may read it, as\n * `Object.values` does. So two keys need the same order too. A scalar is in\n * the text with its type, so the items of a set or a map show their order,\n * and `-0` is not `0`: `Equal` takes them as one, but a loader may not.\n *\n * Each object gets a number at its first visit, and a later visit of it, as\n * of a part that the value shares or of a loop back to a part above, writes\n * `#` and the number. So the walk ends for a value with a loop, and a shared\n * part does not make the text very long. The numbers are in a native `Map`,\n * because the walk must find an object by the object itself. The map belongs\n * to one walk.\n *\n * A value that keeps its data inside, with no own fields, as a `Date`, a\n * `URL` or a `RegExp`, is in the text with its data, and so is an `Option`.\n * For another such value, the walk can not see the data, so it throws, and\n * the principal gets no key: it is not kept.\n */\nconst orderOf = (root: unknown): string => {\n\tconst ids = new Map<object, number>()\n\treturn Trampoline.adaptive<unknown, string>((R, child) => (value, depth) => {\n\t\tif (typeof value !== \"object\" || value === null) {\n\t\t\treturn R.succeed(\n\t\t\t\ttypeof value === \"string\"\n\t\t\t\t\t? JSON.stringify(value)\n\t\t\t\t\t: Object.is(value, -0)\n\t\t\t\t\t\t? \"number:-0\"\n\t\t\t\t\t\t: `${typeof value}:${String(value)}`,\n\t\t\t)\n\t\t}\n\t\tconst known = ids.get(value)\n\t\tif (known !== undefined) {\n\t\t\treturn R.succeed(`#${String(known)}`)\n\t\t}\n\t\tids.set(value, ids.size)\n\t\tconst items = (list: ReadonlyArray<unknown>) =>\n\t\t\tR.forEach(list, (item) => child(item, depth + 1))\n\t\tif (value instanceof Date) {\n\t\t\treturn R.succeed(`date:${String(value.getTime())}`)\n\t\t}\n\t\tif (value instanceof URL) {\n\t\t\treturn R.succeed(`url:${JSON.stringify(value.href)}`)\n\t\t}\n\t\tif (value instanceof RegExp) {\n\t\t\treturn R.succeed(`regexp:${JSON.stringify(String(value))}`)\n\t\t}\n\t\tif (Option.isOption(value)) {\n\t\t\treturn Option.isSome(value)\n\t\t\t\t? R.map(child(value.value, depth + 1), (text) => `some(${text})`)\n\t\t\t\t: R.succeed(\"none\")\n\t\t}\n\t\tif (Array.isArray(value)) {\n\t\t\treturn R.map(items(value), (texts) => `[${texts.join(\",\")}]`)\n\t\t}\n\t\tif (\n\t\t\tvalue instanceof Map ||\n\t\t\tvalue instanceof Set ||\n\t\t\tHashMap.isHashMap(value) ||\n\t\t\tHashSet.isHashSet(value) ||\n\t\t\tChunk.isChunk(value)\n\t\t) {\n\t\t\treturn R.map(\n\t\t\t\titems(Array.from(value as Iterable<unknown>)),\n\t\t\t\t(texts) => `<${texts.join(\",\")}>`,\n\t\t\t)\n\t\t}\n\t\tconst names = Object.keys(value)\n\t\tconst prototype: unknown = Object.getPrototypeOf(value)\n\t\tif (\n\t\t\tnames.length === 0 &&\n\t\t\tprototype !== Object.prototype &&\n\t\t\tprototype !== null\n\t\t) {\n\t\t\tthrow new Error(\"The data of a part of the principal is not in fields\")\n\t\t}\n\t\treturn R.map(\n\t\t\titems(Arr.map(names, (name) => (value as Record<string, unknown>)[name])),\n\t\t\t(texts) =>\n\t\t\t\t`{${Arr.map(names, (name, index) => `${JSON.stringify(name)}:${texts[index]!}`).join(\",\")}}`,\n\t\t)\n\t})(root)\n}\n\n/**\n * Gives the JSON codec of a Schema, or `undefined` when the Schema has no JSON\n * form, as a struct with a symbol key has none.\n */\nconst jsonCodecOf = (\n\tschema: Schema.Top,\n): Schema.Codec<unknown, unknown> | undefined => {\n\ttry {\n\t\treturn Schema.toCodecJson(schema) as unknown as Schema.Codec<\n\t\t\tunknown,\n\t\t\tunknown\n\t\t>\n\t} catch {\n\t\treturn undefined\n\t}\n}\n\n/**\n * Makes the cache key of a principal from its JSON form. The key keeps the\n * principal that the Schema decodes from that form, so the loader sees only\n * the data that the key shows. A principal gets no key when it does not\n * encode, or when the decoded value is not `Equal` to it. Two classes, or a\n * string and a number, may have one JSON form, as in a union, and `Equal`\n * tells them apart. A Schema may make data when it decodes, as the order of\n * the fields of a record, so the key keeps that order too. So two\n * principals with one key are equal, and the cache can not give one the\n * policy of the other.\n */\nconst jsonKey = (\n\tschema: Schema.Top,\n): ((principal: object) => PrincipalKey | undefined) => {\n\tconst codec = jsonCodecOf(schema)\n\t// A principal with no JSON form is never kept.\n\tif (codec === undefined) {\n\t\treturn () => undefined\n\t}\n\tconst encode = Schema.encodeUnknownResult(codec)\n\tconst decode = Schema.decodeUnknownResult(codec)\n\tconst keyOf = (principal: object): PrincipalKey | undefined =>\n\t\tResult.match(encode(principal), {\n\t\t\tonFailure: () => undefined,\n\t\t\tonSuccess: (json) => {\n\t\t\t\t// The key is the text, so the check decodes the text. The encoded\n\t\t\t\t// value may hold more than its text shows, as an object of a\n\t\t\t\t// `Schema.Any` field does: `JSON.stringify` leaves out a map.\n\t\t\t\tconst text = JSON.stringify(json)\n\t\t\t\treturn Result.match(decode(JSON.parse(text)), {\n\t\t\t\t\tonFailure: () => undefined,\n\t\t\t\t\tonSuccess: (decoded) =>\n\t\t\t\t\t\tEqual.equals(decoded, principal)\n\t\t\t\t\t\t\t? new PrincipalKey(decoded as object, text, orderOf(decoded))\n\t\t\t\t\t\t\t: undefined,\n\t\t\t\t})\n\t\t\t},\n\t\t})\n\treturn (principal) => {\n\t\t// A very deep principal may fill the stack of the encoder or of\n\t\t// `Equal`. Such a principal is not kept.\n\t\ttry {\n\t\t\treturn keyOf(principal)\n\t\t} catch {\n\t\t\treturn undefined\n\t\t}\n\t}\n}\n\nconst forbidden = (\n\ttarget: AnyTarget,\n\tkind: string,\n\treason: string | undefined,\n): Forbidden =>\n\tForbidden.make({\n\t\taction: target.action,\n\t\tkind,\n\t\t...(target.field === undefined ? {} : { field: target.field }),\n\t\t...(reason === undefined ? {} : { reason }),\n\t})\n\n/** Makes the layer of an authorizer. */\nexport const makeLayer = (\n\tservice: AnyServiceClass,\n\toptions: {\n\t\treadonly policy: (\n\t\t\tprincipal: object,\n\t\t) => Effect.Effect<Policy.Any, unknown, unknown>\n\t\treadonly decider?: (\n\t\t\trequest: object,\n\t\t) => Effect.Effect<Decision.Decision, unknown, unknown>\n\t\treadonly cache?:\n\t\t\t| {\n\t\t\t\t\treadonly capacity?: number\n\t\t\t\t\treadonly timeToLive?: Duration.Input\n\t\t\t\t\treadonly key?: (principal: object) => string | undefined\n\t\t\t }\n\t\t\t| false\n\t},\n) => {\n\tconst domain: Domain.Any = service.domain\n\t// The JSON form of a principal, and the copy that the Schema decodes from\n\t// it. The loader always gets the copy, with a cache or not, so a cache can\n\t// not change an answer: a form may lose data that `Equal` does not see,\n\t// as `-0` becomes `0`. A principal with no such form gives `undefined`.\n\tconst canonicalOf = jsonKey(domain.principal)\n\tconst custom = options.cache === false ? undefined : options.cache?.key\n\t// It gives `undefined` when the principal has no key. Such a principal does\n\t// not use the cache.\n\tconst keyOf = (\n\t\tprincipal: object,\n\t\tcanonical: PrincipalKey | undefined,\n\t): PrincipalKey | undefined => {\n\t\tif (custom === undefined) {\n\t\t\treturn canonical\n\t\t}\n\t\tconst text = custom(principal)\n\t\treturn text === undefined\n\t\t\t? undefined\n\t\t\t: new PrincipalKey(canonical?.principal ?? principal, text, undefined)\n\t}\n\treturn Layer.effect(\n\t\tservice,\n\t\tEffect.gen(function* () {\n\t\t\t// The services of the policy loader and of the predicates come from the\n\t\t\t// layer, so a check does not need them.\n\t\t\tconst services = yield* Effect.context<never>()\n\n\t\t\t// The predicates of the policy get the services of the layer, so the\n\t\t\t// policy and its residual conditions need no services.\n\t\t\t// A policy of another domain may have the same types, but its resources\n\t\t\t// may have other paths. So it fails closed, as a policy that can not be\n\t\t\t// loaded.\n\t\t\tconst load = (principal: object) =>\n\t\t\t\toptions.policy(principal).pipe(\n\t\t\t\t\tEffect.provideContext(services),\n\t\t\t\t\tEffect.filterOrFail(\n\t\t\t\t\t\t(value) => value.domain === domain,\n\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\tnew globalThis.Error(\n\t\t\t\t\t\t\t\t\"The loaded policy is not a policy of this domain\",\n\t\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t\tEffect.mapError((cause) => PolicyUnavailable.make({ cause })),\n\t\t\t\t\tEffect.map((value) =>\n\t\t\t\t\t\tPolicies.provideContext(value, services as never),\n\t\t\t\t\t),\n\t\t\t\t)\n\n\t\t\tconst cache =\n\t\t\t\toptions.cache === false\n\t\t\t\t\t? undefined\n\t\t\t\t\t: yield* Cache.make({\n\t\t\t\t\t\t\tcapacity: options.cache?.capacity ?? 1000,\n\t\t\t\t\t\t\ttimeToLive: options.cache?.timeToLive ?? \"30 seconds\",\n\t\t\t\t\t\t\tlookup: (key: PrincipalKey) => load(key.principal),\n\t\t\t\t\t\t})\n\n\t\t\tconst policyFor = (principal: object) => {\n\t\t\t\tconst canonical = canonicalOf(principal)\n\t\t\t\tconst key =\n\t\t\t\t\tcache === undefined ? undefined : keyOf(principal, canonical)\n\t\t\t\tif (cache === undefined || key === undefined) {\n\t\t\t\t\treturn load(canonical?.principal ?? principal)\n\t\t\t\t}\n\t\t\t\t// A failed lookup is kept in the cache. Remove it, so the next check\n\t\t\t\t// tries again.\n\t\t\t\treturn Cache.get(cache, key).pipe(\n\t\t\t\t\tEffect.tapError(() => Cache.invalidate(cache, key)),\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tconst withServices = <A, E>(\n\t\t\t\teffect: Effect.Effect<A, E, unknown>,\n\t\t\t): Effect.Effect<A, E, unknown> => Effect.provideContext(effect, services)\n\n\t\t\tconst current = Effect.gen(function* () {\n\t\t\t\tconst principal = yield* service.principal\n\t\t\t\tconst value = yield* policyFor(principal)\n\t\t\t\treturn { principal, policy: value }\n\t\t\t})\n\n\t\t\t// The principal and its policy, loaded one time. A check that asks\n\t\t\t// the policy two times, such as `guard`, uses one snapshot for both,\n\t\t\t// so the two answers come from one principal.\n\t\t\ttype Snapshot = Effect.Success<typeof current>\n\n\t\t\tconst decideWith = Effect.fnUntraced(function* (\n\t\t\t\t{ principal, policy: value }: Snapshot,\n\t\t\t\ttarget: AnyTarget,\n\t\t\t) {\n\t\t\t\tconst decision: Decision.Decision = yield* withServices(\n\t\t\t\t\tDecide.decideAny(value, { ...target, principal } as never),\n\t\t\t\t)\n\t\t\t\treturn decision\n\t\t\t})\n\n\t\t\tconst decide = (target: AnyTarget) =>\n\t\t\t\tEffect.flatMap(current, (snapshot) => decideWith(snapshot, target))\n\n\t\t\tconst checkWith = Effect.fnUntraced(function* (\n\t\t\t\t{ principal, policy: value }: Snapshot,\n\t\t\t\ttarget: AnyTarget,\n\t\t\t) {\n\t\t\t\tconst kind =\n\t\t\t\t\ttarget.resource === undefined\n\t\t\t\t\t\t? (target.kind ?? \"\")\n\t\t\t\t\t\t: (ResourceData.kindOf(target.resource) ?? \"\")\n\t\t\t\t// A request that does not fit its kind gets a \"no\" at once. The\n\t\t\t\t// decider must not say yes to an action that the kind does not have.\n\t\t\t\tconst descriptor = DomainData.descriptorOf(domain, kind)\n\t\t\t\tif (\n\t\t\t\t\tdescriptor === undefined ||\n\t\t\t\t\t!Decide.fitsKind(descriptor, target, domain.limits.fieldPathParts)\n\t\t\t\t) {\n\t\t\t\t\treturn yield* forbidden(target, kind, undefined)\n\t\t\t\t}\n\t\t\t\tconst fromPolicy: Decision.Decision =\n\t\t\t\t\ttarget.resource === undefined\n\t\t\t\t\t\t? Analysis.decideKind(value, { ...target, principal } as never)\n\t\t\t\t\t\t: yield* withServices(\n\t\t\t\t\t\t\t\tDecide.decideAny(value, { ...target, principal } as never),\n\t\t\t\t\t\t\t)\n\t\t\t\t// When no rule gives an answer, the decider gives it.\n\t\t\t\tconst decision: Decision.Decision =\n\t\t\t\t\tfromPolicy._tag === \"NotApplicable\" && options.decider !== undefined\n\t\t\t\t\t\t? yield* options.decider({ ...target, principal })\n\t\t\t\t\t\t: fromPolicy\n\t\t\t\tif (decision._tag !== \"Allow\") {\n\t\t\t\t\treturn yield* forbidden(target, kind, Decision.reason(decision))\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tconst check = (target: AnyTarget) =>\n\t\t\t\tEffect.flatMap(current, (snapshot) => checkWith(snapshot, target))\n\n\t\t\tconst guard = Effect.fnUntraced(function* (\n\t\t\t\ttarget: AnyTarget,\n\t\t\t\tguardOptions: {\n\t\t\t\t\treadonly read?: string\n\t\t\t\t\treadonly hide: () => unknown\n\t\t\t\t},\n\t\t\t) {\n\t\t\t\tconst snapshot = yield* current\n\t\t\t\tconst read = guardOptions.read ?? \"read\"\n\t\t\t\tconst { field: _field, ...rest } = target\n\t\t\t\tconst visible: Decision.Decision = yield* decideWith(snapshot, {\n\t\t\t\t\t...rest,\n\t\t\t\t\taction: read,\n\t\t\t\t})\n\t\t\t\tif (visible._tag !== \"Allow\") {\n\t\t\t\t\treturn yield* Effect.fail(guardOptions.hide())\n\t\t\t\t}\n\t\t\t\tif (target.action !== read || target.field !== undefined) {\n\t\t\t\t\tyield* checkWith(snapshot, target)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\t// The filter is the condition of the coverage: the scope for `All`,\n\t\t\t// `False` for `None`, and else the residual condition in the scope.\n\t\t\t// The residual throws an error when it is too large, so the filter\n\t\t\t// fails.\n\t\t\tconst filter = (\n\t\t\t\ttarget: AnyTarget,\n\t\t\t): Effect.Effect<Condition.Ast, unknown, unknown> =>\n\t\t\t\tEffect.map(\n\t\t\t\t\tcurrent,\n\t\t\t\t\t({ principal, policy: value }): Condition.Ast =>\n\t\t\t\t\t\tAnalysis.coverageAny(value, { ...target, principal } as never)\n\t\t\t\t\t\t\t.condition as Condition.Ast,\n\t\t\t\t)\n\n\t\t\tconst coverage = (\n\t\t\t\ttarget: AnyTarget,\n\t\t\t): Effect.Effect<Analysis.AnyCoverage, unknown, unknown> =>\n\t\t\t\tEffect.map(current, ({ principal, policy: value }) =>\n\t\t\t\t\tAnalysis.coverageAny(value, { ...target, principal } as never),\n\t\t\t\t)\n\n\t\t\tconst permittedFields = Effect.fnUntraced(function* (target: AnyTarget) {\n\t\t\t\tconst { principal, policy: value } = yield* current\n\t\t\t\treturn yield* withServices(\n\t\t\t\t\tDecide.permittedFields(value, { ...target, principal } as never),\n\t\t\t\t)\n\t\t\t})\n\n\t\t\tconst invalidate = (principal?: object) => {\n\t\t\t\tif (cache === undefined) {\n\t\t\t\t\treturn Effect.void\n\t\t\t\t}\n\t\t\t\tif (principal === undefined) {\n\t\t\t\t\treturn Cache.invalidateAll(cache)\n\t\t\t\t}\n\t\t\t\tconst key = keyOf(principal, canonicalOf(principal))\n\t\t\t\treturn key === undefined ? Effect.void : Cache.invalidate(cache, key)\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tdomain,\n\t\t\t\tprincipal: service.principal,\n\t\t\t\tpolicy: Effect.map(current, ({ policy: value }) => value),\n\t\t\t\tdecide,\n\t\t\t\tcheck,\n\t\t\t\tguard,\n\t\t\t\tfilter,\n\t\t\t\tcoverage,\n\t\t\t\tpermittedFields,\n\t\t\t\tinvalidate,\n\t\t\t} as never\n\t\t}),\n\t)\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"authorizer.js","names":["Trampoline.adaptive","Policies.provideContext","Decide.decideAny","ResourceData.kindOf","DomainData.descriptorOf","Decide.fitsKind","Analysis.decideKind","Decision.reason","Analysis.coverageAny","permittedFields","Decide.permittedFields"],"sources":["../../src/internal/authorizer.ts"],"sourcesContent":["/**\n * The values of authorizers: the errors, the service class and the layer.\n * The types are in `Authorizer.ts`.\n */\n// The service erases the error type and the service type of predicates,\n// principals and deciders to `unknown`. The public types keep them, so the\n// language service check for `unknown` in an Effect is off here.\n// @effect-diagnostics anyUnknownInErrorContext:off\nimport * as Arr from \"effect/Array\"\nimport * as Cache from \"effect/Cache\"\nimport * as Chunk from \"effect/Chunk\"\nimport * as Context from \"effect/Context\"\nimport type * as Duration from \"effect/Duration\"\nimport * as Effect from \"effect/Effect\"\nimport * as Equal from \"effect/Equal\"\nimport * as Hash from \"effect/Hash\"\nimport * as HashMap from \"effect/HashMap\"\nimport * as HashSet from \"effect/HashSet\"\nimport * as Layer from \"effect/Layer\"\nimport * as Option from \"effect/Option\"\nimport * as Result from \"effect/Result\"\nimport * as Schema from \"effect/Schema\"\nimport type * as Authorizer from \"../Authorizer.ts\"\nimport type * as Condition from \"../Condition.ts\"\nimport * as Decision from \"../Decision.ts\"\nimport type * as Domain from \"../Domain.ts\"\nimport type * as Policy from \"../Policy.ts\"\nimport * as Analysis from \"./analysis.ts\"\nimport * as Decide from \"./decide.ts\"\nimport * as DomainData from \"./domain.ts\"\nimport * as Policies from \"./policy.ts\"\nimport * as ResourceData from \"./resource.ts\"\nimport * as Trampoline from \"./trampoline.ts\"\n\ntype AnyServiceClass = Authorizer.AnyServiceClass\n\n/** The error when a policy says no. */\nexport class Forbidden extends Schema.TaggedError<Forbidden>(\n\t\"@nmnmcc/ability/Authorizer/Forbidden\",\n)(\"Forbidden\", {\n\taction: Schema.String,\n\tkind: Schema.String,\n\tfield: Schema.optionalKey(Schema.String),\n\treason: Schema.optionalKey(Schema.String),\n}) {}\n\n/** The error when the policy of the principal can not be loaded. */\nexport class PolicyUnavailable extends Schema.TaggedError<PolicyUnavailable>(\n\t\"@nmnmcc/ability/Authorizer/PolicyUnavailable\",\n)(\"PolicyUnavailable\", { cause: Schema.Defect() }) {}\n\n/** The error when a decider must wait for an answer. */\nexport class Pending extends Schema.TaggedError<Pending>(\n\t\"@nmnmcc/ability/Authorizer/Pending\",\n)(\"Pending\", {\n\tticket: Schema.String,\n\treason: Schema.optionalKey(Schema.String),\n}) {}\n\n/** Makes an authorizer service class for a domain. */\nexport const Service =\n\t() =>\n\t(\n\t\tid: string,\n\t\toptions: {\n\t\t\treadonly domain: Domain.Any\n\t\t\treadonly principal: Effect.Effect<any, any, any>\n\t\t},\n\t\t// The class has no name, and its type uses types of Effect that a\n\t\t// declaration file can not name. So this function gives a `Context.Key`.\n\t\t// The public module gives the full type.\n\t): Context.Key<any, any> => {\n\t\tconst Base = Context.Service<any, any>()(id)\n\t\treturn class extends Base {\n\t\t\tstatic readonly domain = options.domain\n\t\t\tstatic readonly principal = options.principal\n\t\t\tstatic readonly decide = (target: AnyTarget) =>\n\t\t\t\tthis.use((service: any) => service.decide(target))\n\t\t\tstatic readonly check = (target: AnyTarget) =>\n\t\t\t\tthis.use((service: any) => service.check(target))\n\t\t\tstatic readonly guard = (target: AnyTarget, options: unknown) =>\n\t\t\t\tthis.use((service: any) => service.guard(target, options))\n\t\t\tstatic readonly filter = (target: AnyTarget) =>\n\t\t\t\tthis.use((service: any) => service.filter(target))\n\t\t\tstatic readonly coverage = (target: AnyTarget) =>\n\t\t\t\tthis.use((service: any) => service.coverage(target))\n\t\t\tstatic readonly permittedFields = (target: AnyTarget) =>\n\t\t\t\tthis.use((service: any) => service.permittedFields(target))\n\t\t}\n\t}\n\ntype AnyTarget = {\n\treadonly action: string\n\treadonly resource?: object\n\treadonly kind?: string\n\treadonly field?: string\n\treadonly within?: unknown\n}\n\n/**\n * A cache key for a principal. Two keys are equal when they have the same\n * text, so two principals with the same key share one policy. A key from the\n * JSON form also needs equal decoded principals: a Schema may make data when\n * it decodes, as `expired` from a time, so one text may give two principals.\n * The key also keeps the principal, so the cache can load its policy. A plain\n * `Data` value can not do all this, so the key gives its own equality with\n * `Equal.Equal`.\n */\nclass PrincipalKey implements Equal.Equal {\n\treadonly principal: object\n\treadonly text: string\n\t/**\n\t * The order of the parts of the decoded principal, from {@link orderOf},\n\t * when the key is the JSON form. The decoded principals must be equal\n\t * too, with their parts in the same order. `undefined` for a key from\n\t * `cache.key`.\n\t */\n\treadonly order: string | undefined\n\tconstructor(principal: object, text: string, order: string | undefined) {\n\t\tthis.principal = principal\n\t\tthis.text = text\n\t\tthis.order = order\n\t}\n\t[Equal.symbol](that: unknown): boolean {\n\t\treturn (\n\t\t\tthat instanceof PrincipalKey &&\n\t\t\tthat.text === this.text &&\n\t\t\tthat.order === this.order &&\n\t\t\t(this.order === undefined || Equal.equals(that.principal, this.principal))\n\t\t)\n\t}\n\t[Hash.symbol](): number {\n\t\treturn Hash.string(this.text)\n\t}\n}\n\n/**\n * The text of the order of the parts of a value: the names of the fields of\n * each object, and the items of each list, set and map, with each scalar, in\n * the order that a loop sees them. `Equal` does not see the order of the\n * fields of an object or of the items of a map, but a loader may read it, as\n * `Object.values` does. So two keys need the same order too. A scalar is in\n * the text with its type, so the items of a set or a map show their order,\n * and `-0` is not `0`: `Equal` takes them as one, but a loader may not.\n *\n * Each object gets a number at its first visit, and a later visit of it, as\n * of a part that the value shares or of a loop back to a part above, writes\n * `#` and the number. So the walk ends for a value with a loop, and a shared\n * part does not make the text very long. The numbers are in a native `Map`,\n * because the walk must find an object by the object itself. The map belongs\n * to one walk.\n *\n * A value that keeps its data inside, with no own fields, as a `Date`, a\n * `URL` or a `RegExp` with the place of its next match, is in the text with\n * its data, and so is an `Option`.\n * For another such value, the walk can not see the data, so it throws, and\n * the principal gets no key: it is not kept.\n */\nconst orderOf = (root: unknown): string => {\n\tconst ids = new Map<object, number>()\n\treturn Trampoline.adaptive<unknown, string>((R, child) => (value, depth) => {\n\t\tif (typeof value !== \"object\" || value === null) {\n\t\t\treturn R.succeed(\n\t\t\t\ttypeof value === \"string\"\n\t\t\t\t\t? JSON.stringify(value)\n\t\t\t\t\t: Object.is(value, -0)\n\t\t\t\t\t\t? \"number:-0\"\n\t\t\t\t\t\t: `${typeof value}:${String(value)}`,\n\t\t\t)\n\t\t}\n\t\tconst known = ids.get(value)\n\t\tif (known !== undefined) {\n\t\t\treturn R.succeed(`#${String(known)}`)\n\t\t}\n\t\tids.set(value, ids.size)\n\t\tconst items = (list: ReadonlyArray<unknown>) =>\n\t\t\tR.forEach(list, (item) => child(item, depth + 1))\n\t\tif (value instanceof Date) {\n\t\t\treturn R.succeed(`date:${String(value.getTime())}`)\n\t\t}\n\t\tif (value instanceof URL) {\n\t\t\treturn R.succeed(`url:${JSON.stringify(value.href)}`)\n\t\t}\n\t\tif (value instanceof RegExp) {\n\t\t\t// A `RegExp` with the flag `g` or `y` keeps the place of its next\n\t\t\t// match in `lastIndex`, which `Equal` does not see.\n\t\t\treturn R.succeed(\n\t\t\t\t`regexp:${JSON.stringify(String(value))}:${String(value.lastIndex)}`,\n\t\t\t)\n\t\t}\n\t\tif (Option.isOption(value)) {\n\t\t\treturn Option.isSome(value)\n\t\t\t\t? R.map(child(value.value, depth + 1), (text) => `some(${text})`)\n\t\t\t\t: R.succeed(\"none\")\n\t\t}\n\t\tif (Array.isArray(value)) {\n\t\t\treturn R.map(items(value), (texts) => `[${texts.join(\",\")}]`)\n\t\t}\n\t\tif (\n\t\t\tvalue instanceof Map ||\n\t\t\tvalue instanceof Set ||\n\t\t\tHashMap.isHashMap(value) ||\n\t\t\tHashSet.isHashSet(value) ||\n\t\t\tChunk.isChunk(value)\n\t\t) {\n\t\t\treturn R.map(\n\t\t\t\titems(Array.from(value as Iterable<unknown>)),\n\t\t\t\t(texts) => `<${texts.join(\",\")}>`,\n\t\t\t)\n\t\t}\n\t\tconst names = Object.keys(value)\n\t\tconst prototype: unknown = Object.getPrototypeOf(value)\n\t\tif (\n\t\t\tnames.length === 0 &&\n\t\t\tprototype !== Object.prototype &&\n\t\t\tprototype !== null\n\t\t) {\n\t\t\tthrow new Error(\"The data of a part of the principal is not in fields\")\n\t\t}\n\t\treturn R.map(\n\t\t\titems(Arr.map(names, (name) => (value as Record<string, unknown>)[name])),\n\t\t\t(texts) =>\n\t\t\t\t`{${Arr.map(names, (name, index) => `${JSON.stringify(name)}:${texts[index]!}`).join(\",\")}}`,\n\t\t)\n\t})(root)\n}\n\n/**\n * Gives the JSON codec of a Schema, or `undefined` when the Schema has no JSON\n * form, as a struct with a symbol key has none.\n */\nconst jsonCodecOf = (\n\tschema: Schema.Top,\n): Schema.Codec<unknown, unknown> | undefined => {\n\ttry {\n\t\treturn Schema.toCodecJson(schema) as unknown as Schema.Codec<\n\t\t\tunknown,\n\t\t\tunknown\n\t\t>\n\t} catch {\n\t\treturn undefined\n\t}\n}\n\n/**\n * Makes the cache key of a principal from its JSON form. The key keeps the\n * principal that the Schema decodes from that form, so the loader sees only\n * the data that the key shows. A principal gets no key when it does not\n * encode, or when the decoded value is not `Equal` to it. Two classes, or a\n * string and a number, may have one JSON form, as in a union, and `Equal`\n * tells them apart. A Schema may make data when it decodes, as the order of\n * the fields of a record, so the key keeps that order too. So two\n * principals with one key are equal, and the cache can not give one the\n * policy of the other.\n */\nconst jsonKey = (\n\tschema: Schema.Top,\n): ((principal: object) => PrincipalKey | undefined) => {\n\tconst codec = jsonCodecOf(schema)\n\t// A principal with no JSON form is never kept.\n\tif (codec === undefined) {\n\t\treturn () => undefined\n\t}\n\tconst encode = Schema.encodeUnknownResult(codec)\n\tconst decode = Schema.decodeUnknownResult(codec)\n\tconst keyOf = (principal: object): PrincipalKey | undefined =>\n\t\tResult.match(encode(principal), {\n\t\t\tonFailure: () => undefined,\n\t\t\tonSuccess: (json) => {\n\t\t\t\t// The key is the text, so the check decodes the text. The encoded\n\t\t\t\t// value may hold more than its text shows, as an object of a\n\t\t\t\t// `Schema.Any` field does: `JSON.stringify` leaves out a map.\n\t\t\t\tconst text = JSON.stringify(json)\n\t\t\t\treturn Result.match(decode(JSON.parse(text)), {\n\t\t\t\t\tonFailure: () => undefined,\n\t\t\t\t\tonSuccess: (decoded) =>\n\t\t\t\t\t\tEqual.equals(decoded, principal)\n\t\t\t\t\t\t\t? new PrincipalKey(decoded as object, text, orderOf(decoded))\n\t\t\t\t\t\t\t: undefined,\n\t\t\t\t})\n\t\t\t},\n\t\t})\n\treturn (principal) => {\n\t\t// A very deep principal may fill the stack of the encoder or of\n\t\t// `Equal`. Such a principal is not kept.\n\t\ttry {\n\t\t\treturn keyOf(principal)\n\t\t} catch {\n\t\t\treturn undefined\n\t\t}\n\t}\n}\n\nconst forbidden = (\n\ttarget: AnyTarget,\n\tkind: string,\n\treason: string | undefined,\n): Forbidden =>\n\tForbidden.make({\n\t\taction: target.action,\n\t\tkind,\n\t\t...(target.field === undefined ? {} : { field: target.field }),\n\t\t...(reason === undefined ? {} : { reason }),\n\t})\n\n/** Makes the layer of an authorizer. */\nexport const makeLayer = (\n\tservice: AnyServiceClass,\n\toptions: {\n\t\treadonly policy: (\n\t\t\tprincipal: object,\n\t\t) => Effect.Effect<Policy.Any, unknown, unknown>\n\t\treadonly decider?: (\n\t\t\trequest: object,\n\t\t) => Effect.Effect<Decision.Decision, unknown, unknown>\n\t\treadonly cache?:\n\t\t\t| {\n\t\t\t\t\treadonly capacity?: number\n\t\t\t\t\treadonly timeToLive?: Duration.Input\n\t\t\t\t\treadonly key?: (principal: object) => string | undefined\n\t\t\t }\n\t\t\t| false\n\t},\n) => {\n\tconst domain: Domain.Any = service.domain\n\t// The JSON form of a principal, and the copy that the Schema decodes from\n\t// it. The loader always gets the copy, with a cache or not, so a cache can\n\t// not change an answer: a form may lose data that `Equal` does not see,\n\t// as `-0` becomes `0`. A principal with no such form gives `undefined`.\n\tconst canonicalOf = jsonKey(domain.principal)\n\tconst custom = options.cache === false ? undefined : options.cache?.key\n\t// It gives `undefined` when the principal has no key. Such a principal does\n\t// not use the cache.\n\tconst keyOf = (\n\t\tprincipal: object,\n\t\tcanonical: PrincipalKey | undefined,\n\t): PrincipalKey | undefined => {\n\t\tif (custom === undefined) {\n\t\t\treturn canonical\n\t\t}\n\t\tconst text = custom(principal)\n\t\treturn text === undefined\n\t\t\t? undefined\n\t\t\t: new PrincipalKey(canonical?.principal ?? principal, text, undefined)\n\t}\n\treturn Layer.effect(\n\t\tservice,\n\t\tEffect.gen(function* () {\n\t\t\t// The services of the policy loader and of the predicates come from the\n\t\t\t// layer, so a check does not need them.\n\t\t\tconst services = yield* Effect.context<never>()\n\n\t\t\t// The predicates of the policy get the services of the layer, so the\n\t\t\t// policy and its residual conditions need no services.\n\t\t\t// A policy of another domain may have the same types, but its resources\n\t\t\t// may have other paths. So it fails closed, as a policy that can not be\n\t\t\t// loaded.\n\t\t\tconst load = (principal: object) =>\n\t\t\t\toptions.policy(principal).pipe(\n\t\t\t\t\tEffect.provideContext(services),\n\t\t\t\t\tEffect.filterOrFail(\n\t\t\t\t\t\t(value) => value.domain === domain,\n\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\tnew globalThis.Error(\n\t\t\t\t\t\t\t\t\"The loaded policy is not a policy of this domain\",\n\t\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t\tEffect.mapError((cause) => PolicyUnavailable.make({ cause })),\n\t\t\t\t\tEffect.map((value) =>\n\t\t\t\t\t\tPolicies.provideContext(value, services as never),\n\t\t\t\t\t),\n\t\t\t\t)\n\n\t\t\tconst cache =\n\t\t\t\toptions.cache === false\n\t\t\t\t\t? undefined\n\t\t\t\t\t: yield* Cache.make({\n\t\t\t\t\t\t\tcapacity: options.cache?.capacity ?? 1000,\n\t\t\t\t\t\t\ttimeToLive: options.cache?.timeToLive ?? \"30 seconds\",\n\t\t\t\t\t\t\tlookup: (key: PrincipalKey) => load(key.principal),\n\t\t\t\t\t\t})\n\n\t\t\tconst policyFor = (principal: object) => {\n\t\t\t\tconst canonical = canonicalOf(principal)\n\t\t\t\tconst key =\n\t\t\t\t\tcache === undefined ? undefined : keyOf(principal, canonical)\n\t\t\t\tif (cache === undefined || key === undefined) {\n\t\t\t\t\treturn load(canonical?.principal ?? principal)\n\t\t\t\t}\n\t\t\t\t// A failed lookup is kept in the cache. Remove it, so the next check\n\t\t\t\t// tries again.\n\t\t\t\treturn Cache.get(cache, key).pipe(\n\t\t\t\t\tEffect.tapError(() => Cache.invalidate(cache, key)),\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tconst withServices = <A, E>(\n\t\t\t\teffect: Effect.Effect<A, E, unknown>,\n\t\t\t): Effect.Effect<A, E, unknown> => Effect.provideContext(effect, services)\n\n\t\t\tconst current = Effect.gen(function* () {\n\t\t\t\tconst principal = yield* service.principal\n\t\t\t\tconst value = yield* policyFor(principal)\n\t\t\t\treturn { principal, policy: value }\n\t\t\t})\n\n\t\t\t// The principal and its policy, loaded one time. A check that asks\n\t\t\t// the policy two times, such as `guard`, uses one snapshot for both,\n\t\t\t// so the two answers come from one principal.\n\t\t\ttype Snapshot = Effect.Success<typeof current>\n\n\t\t\tconst decideWith = Effect.fnUntraced(function* (\n\t\t\t\t{ principal, policy: value }: Snapshot,\n\t\t\t\ttarget: AnyTarget,\n\t\t\t) {\n\t\t\t\tconst decision: Decision.Decision = yield* withServices(\n\t\t\t\t\tDecide.decideAny(value, { ...target, principal } as never),\n\t\t\t\t)\n\t\t\t\treturn decision\n\t\t\t})\n\n\t\t\tconst decide = (target: AnyTarget) =>\n\t\t\t\tEffect.flatMap(current, (snapshot) => decideWith(snapshot, target))\n\n\t\t\tconst checkWith = Effect.fnUntraced(function* (\n\t\t\t\t{ principal, policy: value }: Snapshot,\n\t\t\t\ttarget: AnyTarget,\n\t\t\t) {\n\t\t\t\tconst kind =\n\t\t\t\t\ttarget.resource === undefined\n\t\t\t\t\t\t? (target.kind ?? \"\")\n\t\t\t\t\t\t: (ResourceData.kindOf(target.resource) ?? \"\")\n\t\t\t\t// A request that does not fit its kind gets a \"no\" at once. The\n\t\t\t\t// decider must not say yes to an action that the kind does not have.\n\t\t\t\tconst descriptor = DomainData.descriptorOf(domain, kind)\n\t\t\t\tif (\n\t\t\t\t\tdescriptor === undefined ||\n\t\t\t\t\t!Decide.fitsKind(descriptor, target, domain.limits.fieldPathParts)\n\t\t\t\t) {\n\t\t\t\t\treturn yield* forbidden(target, kind, undefined)\n\t\t\t\t}\n\t\t\t\tconst fromPolicy: Decision.Decision =\n\t\t\t\t\ttarget.resource === undefined\n\t\t\t\t\t\t? Analysis.decideKind(value, { ...target, principal } as never)\n\t\t\t\t\t\t: yield* withServices(\n\t\t\t\t\t\t\t\tDecide.decideAny(value, { ...target, principal } as never),\n\t\t\t\t\t\t\t)\n\t\t\t\t// When no rule gives an answer, the decider gives it.\n\t\t\t\tconst decision: Decision.Decision =\n\t\t\t\t\tfromPolicy._tag === \"NotApplicable\" && options.decider !== undefined\n\t\t\t\t\t\t? yield* options.decider({ ...target, principal })\n\t\t\t\t\t\t: fromPolicy\n\t\t\t\tif (decision._tag !== \"Allow\") {\n\t\t\t\t\treturn yield* forbidden(target, kind, Decision.reason(decision))\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tconst check = (target: AnyTarget) =>\n\t\t\t\tEffect.flatMap(current, (snapshot) => checkWith(snapshot, target))\n\n\t\t\tconst guard = Effect.fnUntraced(function* (\n\t\t\t\ttarget: AnyTarget,\n\t\t\t\tguardOptions: {\n\t\t\t\t\treadonly read?: string\n\t\t\t\t\treadonly hide: () => unknown\n\t\t\t\t},\n\t\t\t) {\n\t\t\t\tconst snapshot = yield* current\n\t\t\t\tconst read = guardOptions.read ?? \"read\"\n\t\t\t\tconst { field: _field, ...rest } = target\n\t\t\t\tconst visible: Decision.Decision = yield* decideWith(snapshot, {\n\t\t\t\t\t...rest,\n\t\t\t\t\taction: read,\n\t\t\t\t})\n\t\t\t\tif (visible._tag !== \"Allow\") {\n\t\t\t\t\treturn yield* Effect.fail(guardOptions.hide())\n\t\t\t\t}\n\t\t\t\tif (target.action !== read || target.field !== undefined) {\n\t\t\t\t\tyield* checkWith(snapshot, target)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\t// The filter is the condition of the coverage: the scope for `All`,\n\t\t\t// `False` for `None`, and else the residual condition in the scope.\n\t\t\t// The residual throws an error when it is too large, so the filter\n\t\t\t// fails.\n\t\t\tconst filter = (\n\t\t\t\ttarget: AnyTarget,\n\t\t\t): Effect.Effect<Condition.Ast, unknown, unknown> =>\n\t\t\t\tEffect.map(\n\t\t\t\t\tcurrent,\n\t\t\t\t\t({ principal, policy: value }): Condition.Ast =>\n\t\t\t\t\t\tAnalysis.coverageAny(value, { ...target, principal } as never)\n\t\t\t\t\t\t\t.condition as Condition.Ast,\n\t\t\t\t)\n\n\t\t\tconst coverage = (\n\t\t\t\ttarget: AnyTarget,\n\t\t\t): Effect.Effect<Analysis.AnyCoverage, unknown, unknown> =>\n\t\t\t\tEffect.map(current, ({ principal, policy: value }) =>\n\t\t\t\t\tAnalysis.coverageAny(value, { ...target, principal } as never),\n\t\t\t\t)\n\n\t\t\tconst permittedFields = Effect.fnUntraced(function* (target: AnyTarget) {\n\t\t\t\tconst { principal, policy: value } = yield* current\n\t\t\t\treturn yield* withServices(\n\t\t\t\t\tDecide.permittedFields(value, { ...target, principal } as never),\n\t\t\t\t)\n\t\t\t})\n\n\t\t\tconst invalidate = (principal?: object) => {\n\t\t\t\tif (cache === undefined) {\n\t\t\t\t\treturn Effect.void\n\t\t\t\t}\n\t\t\t\tif (principal === undefined) {\n\t\t\t\t\treturn Cache.invalidateAll(cache)\n\t\t\t\t}\n\t\t\t\tconst key = keyOf(principal, canonicalOf(principal))\n\t\t\t\treturn key === undefined ? Effect.void : Cache.invalidate(cache, key)\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tdomain,\n\t\t\t\tprincipal: service.principal,\n\t\t\t\tpolicy: Effect.map(current, ({ policy: value }) => value),\n\t\t\t\tdecide,\n\t\t\t\tcheck,\n\t\t\t\tguard,\n\t\t\t\tfilter,\n\t\t\t\tcoverage,\n\t\t\t\tpermittedFields,\n\t\t\t\tinvalidate,\n\t\t\t} as never\n\t\t}),\n\t)\n}\n"],"mappings":"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"}
|
package/dist/internal/bind.js
CHANGED
|
@@ -287,7 +287,7 @@ const conditionIssues = (context, root, rootAt, seen) => run(root.depth, (R) =>
|
|
|
287
287
|
return here(Arr.flatMap(node.values, (operand, index) => operandIssues(context, node.field, fields, false, operand, ["values", index])));
|
|
288
288
|
}
|
|
289
289
|
case "Exists": return room(0) ? here(readIssues(context, node.field, ["field"])) : [];
|
|
290
|
-
case "Under": return here(underIssues(context, node.prefix, []));
|
|
290
|
+
case "Under": return spendChecks(context.memo, 1 + node.prefix.length, context.paths.length) ? here(underIssues(context, node.prefix, [])) : [];
|
|
291
291
|
default: return [];
|
|
292
292
|
}
|
|
293
293
|
};
|
|
@@ -310,8 +310,8 @@ const hasAction = (descriptor, actions) => actions.includes("*") || Arr.some(act
|
|
|
310
310
|
* full path is made only for an issue that the binding keeps: a rule with
|
|
311
311
|
* very many problems deep in a tree does not make a full path for each one.
|
|
312
312
|
*/
|
|
313
|
-
const ruleIssues = (domain, rule, at$
|
|
314
|
-
const here = (issues) => under(memo, at$
|
|
313
|
+
const ruleIssues = (domain, rule, at$1, memo = makeMemo(domain.limits)) => {
|
|
314
|
+
const here = (issues) => under(memo, at$1, issues);
|
|
315
315
|
const all = rule.resources.includes("*");
|
|
316
316
|
const names = Array.from(new Set(Arr.filter(rule.resources, (kind) => kind !== "*")));
|
|
317
317
|
const kinds = Array.from(HashMap.keys(domain.resources));
|
|
@@ -332,7 +332,7 @@ const ruleIssues = (domain, rule, at$2, memo = makeMemo(domain.limits)) => {
|
|
|
332
332
|
const whenIssues = rule.when === void 0 ? [] : !isNode(rule.when) ? here([notWhole(["when"])]) : rule.when.weight > conditionNodes ? here([{
|
|
333
333
|
path: ["when"],
|
|
334
334
|
issue: `A condition has ${String(rule.when.weight)} nodes and values. The most is ${String(conditionNodes)}.`
|
|
335
|
-
}]) : conditionIssues(context, rule.when, at(at$
|
|
335
|
+
}]) : conditionIssues(context, rule.when, at(at$1, "when"), /* @__PURE__ */ new Set());
|
|
336
336
|
const work = memo.work.steps > memo.limits.searchSteps && !memo.work.noted;
|
|
337
337
|
memo.work.noted ||= work;
|
|
338
338
|
return [
|
|
@@ -347,7 +347,7 @@ const ruleIssues = (domain, rule, at$2, memo = makeMemo(domain.limits)) => {
|
|
|
347
347
|
...fieldIssues,
|
|
348
348
|
...whenIssues,
|
|
349
349
|
...work ? [{
|
|
350
|
-
path: pathOf(at$
|
|
350
|
+
path: pathOf(at$1),
|
|
351
351
|
issue: `The policy takes more than ${String(memo.limits.searchSteps)} steps to check: its field patterns, and the fields and values of its tests on each resource. The most is ${String(memo.limits.searchSteps)}.`
|
|
352
352
|
}] : []
|
|
353
353
|
];
|
|
@@ -355,26 +355,26 @@ const ruleIssues = (domain, rule, at$2, memo = makeMemo(domain.limits)) => {
|
|
|
355
355
|
/** Checks each rule of a policy tree against a domain. */
|
|
356
356
|
const treeIssues = (domain, root) => {
|
|
357
357
|
const found = [];
|
|
358
|
-
adaptive((R, child) => ({ node, at: at$
|
|
358
|
+
adaptive((R, child) => ({ node, at: at$2 }, level) => {
|
|
359
359
|
const below = (parts) => R.map(R.forEach(parts, (part) => child(part, level + 1)), () => void 0);
|
|
360
360
|
switch (node._tag) {
|
|
361
361
|
case "Rules":
|
|
362
362
|
Arr.forEach(node.rules, (rule, index) => {
|
|
363
|
-
found.push([rule, at(at$
|
|
363
|
+
found.push([rule, at(at$2, "rules", index)]);
|
|
364
364
|
});
|
|
365
365
|
return R.succeed(void 0);
|
|
366
366
|
case "All":
|
|
367
367
|
case "Any":
|
|
368
368
|
case "FirstOf": return below(Arr.map(node.policies, (inner, index) => ({
|
|
369
369
|
node: inner,
|
|
370
|
-
at: at(at$
|
|
370
|
+
at: at(at$2, "policies", index)
|
|
371
371
|
})));
|
|
372
372
|
case "Ceiling": return below([{
|
|
373
373
|
node: node.cap,
|
|
374
|
-
at: at(at$
|
|
374
|
+
at: at(at$2, "cap")
|
|
375
375
|
}, {
|
|
376
376
|
node: node.policy,
|
|
377
|
-
at: at(at$
|
|
377
|
+
at: at(at$2, "policy")
|
|
378
378
|
}]);
|
|
379
379
|
}
|
|
380
380
|
})({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bind.js","names":["Catalog.accepts","Catalog.fieldsOf","Catalog.lookup","Patterns.parse","Patterns.fitsCatalog","Catalog.anyKinds","Place.pathOf","Trampoline.run","Place.at","at","Nodes.isNode","Trampoline.adaptive"],"sources":["../../src/internal/bind.ts"],"sourcesContent":["/**\n * The binding of rules to a domain. It is the one check of a rule, for a rule\n * from the builder, from `Policy.rule`, and from JSON alike. It reads the\n * catalogs of the resources and of the principal, and it gives one issue for\n * each problem, with the path of the problem and, for a name with a mistake,\n * the name that it may mean.\n */\nimport * as Arr from \"effect/Array\"\nimport * as HashMap from \"effect/HashMap\"\nimport * as Option from \"effect/Option\"\nimport * as Result from \"effect/Result\"\nimport type * as Domain from \"../Domain.ts\"\nimport type * as Resource from \"../Resource.ts\"\nimport * as Catalog from \"./catalog.ts\"\nimport * as Limits from \"./limits.ts\"\nimport * as Nodes from \"./nodes.ts\"\nimport * as Patterns from \"./patterns.ts\"\nimport * as Place from \"./place.ts\"\nimport { withSuggestion } from \"./suggest.ts\"\nimport * as Trampoline from \"./trampoline.ts\"\n\n/** One problem of a rule or a policy, with its path. */\nexport interface Issue {\n\treadonly path: ReadonlyArray<PropertyKey>\n\treadonly issue: string\n}\n\n/** The data of a rule that the binding reads. */\nexport interface RuleData {\n\treadonly actions: ReadonlyArray<string>\n\treadonly resources: ReadonlyArray<string>\n\treadonly when?: unknown\n\treadonly fields?: ReadonlyArray<string>\n}\n\n/**\n * The memo of one binding, for all its rules. A policy may name one wrong\n * field or one pattern in very many places, so the work on each is done one\n * time. It goes away when the binding ends.\n */\ninterface Memo {\n\t/**\n\t * The suggestion for each name, by the place where it was looked for. The\n\t * names come from a policy, so the map is native, as `fits` is.\n\t */\n\treadonly suggestions: Map<string, string>\n\t/**\n\t * If a pattern matches a field of a kind, by the kind and the pattern. The\n\t * patterns come from a policy, so the map is native: the hash of a string\n\t * in Effect has no random seed, and a policy could hold very many\n\t * patterns with one hash. The engine hashes a string with a random seed.\n\t */\n\treadonly fits: Map<string, boolean>\n\t/** The count of the issues of conditions so far. It belongs to one binding. */\n\treadonly issues: { count: number; noted: boolean }\n\t/** The steps of the checks of patterns so far. It belongs to one binding. */\n\treadonly work: { steps: number; noted: boolean }\n\t/** The limits of the domain. */\n\treadonly limits: Limits.Limits\n}\n\nconst makeMemo = (limits: Limits.Limits): Memo => ({\n\tsuggestions: new Map(),\n\tfits: new Map(),\n\tissues: { count: 0, noted: false },\n\twork: { steps: 0, noted: false },\n\tlimits,\n})\n\n/**\n * The most names that one binding makes a suggestion for. A suggestion\n * compares the name with every name of a catalog, so a policy with very many\n * wrong names would take a long time.\n */\nconst maxSuggestions = 32\n\n/** Adds a suggestion to a message, with the memo of the binding. */\nconst suggestIn = (\n\tmemo: Memo,\n\tmessage: string,\n\tname: string,\n\tscope: string,\n\tnames: () => ReadonlyArray<string>,\n): string => {\n\tconst key = `${scope}\\u0000${name}`\n\tconst known = memo.suggestions.get(key)\n\tif (known !== undefined) {\n\t\treturn known\n\t}\n\tif (memo.suggestions.size >= maxSuggestions) {\n\t\treturn message\n\t}\n\tconst made = withSuggestion(message, name, names())\n\tmemo.suggestions.set(key, made)\n\treturn made\n}\n\n/** The kinds that a rule may run on, from the view of its checks. */\ninterface Context {\n\treadonly domain: Domain.Any\n\treadonly memo: Memo\n\t/** The rule is for every resource (`\"*\"`). */\n\treadonly all: boolean\n\t/** The resources that the rule names and that the domain has. */\n\treadonly named: ReadonlyArray<Resource.Descriptor>\n\t/** The named resources that have one of the actions of the rule. */\n\treadonly applicable: ReadonlyArray<Resource.Descriptor>\n\t/**\n\t * The resources whose paths a path test may read: every resource with one\n\t * of the actions, for a rule for every resource.\n\t */\n\treadonly paths: ReadonlyArray<Resource.Descriptor>\n}\n\ntype Operand = Nodes.Operand\n\n/**\n * Tests if a field may hold a value of the type of a fixed value. A test with\n * `null` is a test that the field is missing, so `null` always fits. A number\n * fits any field of numbers: a test of an integer field with `1.5` or\n * `Infinity` has a meaning, as `lt` shows.\n */\nconst fitsLiteral = (kinds: Catalog.Kinds, value: unknown): boolean =>\n\tvalue === null ||\n\t(typeof value === \"number\" && kinds.number !== \"none\") ||\n\tCatalog.accepts(kinds, value)\n\n/**\n * Tests if two fields may hold one scalar. A principal field that is `null`\n * or that holds an object is a missing value, and a test with a missing value\n * is false, so only scalars count.\n */\nconst meet = (left: Catalog.Kinds, right: Catalog.Kinds): boolean =>\n\t(left.string && right.string) ||\n\t(left.boolean && right.boolean) ||\n\t(left.number !== \"none\" && right.number !== \"none\") ||\n\tArr.some(left.literals, (value) => Catalog.accepts(right, value)) ||\n\tArr.some(right.literals, (value) => Catalog.accepts(left, value))\n\nconst hasStrings = (kinds: Catalog.Kinds): boolean =>\n\tkinds.string || Arr.some(kinds.literals, (value) => typeof value === \"string\")\n\nconst hasNumbers = (kinds: Catalog.Kinds): boolean =>\n\tkinds.number !== \"none\" ||\n\tArr.some(kinds.literals, (value) => typeof value === \"number\")\n\n/** Tests if a field may hold a value with an order: a number or a string. */\nconst isOrdered = (kinds: Catalog.Kinds): boolean =>\n\thasStrings(kinds) || hasNumbers(kinds)\n\n/** Tests if two fields may hold values of one ordered type. */\nconst orderedMeet = (left: Catalog.Kinds, right: Catalog.Kinds): boolean =>\n\t(hasStrings(left) && hasStrings(right)) ||\n\t(hasNumbers(left) && hasNumbers(right))\n\n/**\n * The texts of the fields of a catalog that have only names, for\n * suggestions, with the limits of the domain. The domain checked the size of\n * the list when it was made.\n */\nconst namesOf = (\n\tcatalog: Catalog.Catalog,\n\tlimits: Limits.Limits,\n): ReadonlyArray<string> =>\n\tArr.map(\n\t\tArr.filter(\n\t\t\tCatalog.fieldsOf(catalog, limits.fieldPathParts, limits.catalogFields),\n\t\t\t(field) =>\n\t\t\t\tArr.every(field.path, (segment) => typeof segment === \"string\"),\n\t\t),\n\t\t(field) => field.text,\n\t)\n\n/** The issue of a field that is not a field of every resource of the rule. */\nconst fieldIssue = (\n\tcontext: Context,\n\tfield: string,\n\tat: ReadonlyArray<PropertyKey>,\n): ReadonlyArray<Issue> => {\n\tconst parts = context.memo.limits.fieldPathParts\n\tif (field.split(\".\", parts + 1).length > parts) {\n\t\treturn [\n\t\t\t{\n\t\t\t\tpath: at,\n\t\t\t\tissue: `The field path \"${field}\" has more than ${String(parts)} parts. The most is ${String(parts)}.`,\n\t\t\t},\n\t\t]\n\t}\n\tconst message = `The field \"${field}\" is not a field of every resource of the rule`\n\tif (context.all || context.named.length === 0) {\n\t\treturn [{ path: at, issue: message }]\n\t}\n\tconst without = Arr.filter(\n\t\tcontext.named,\n\t\t(descriptor) =>\n\t\t\tCatalog.lookup(descriptor.catalog, field, parts) === undefined,\n\t)\n\treturn without.length === 0\n\t\t? []\n\t\t: [\n\t\t\t\t{\n\t\t\t\t\tpath: at,\n\t\t\t\t\tissue: suggestIn(context.memo, message, field, without[0]!.kind, () =>\n\t\t\t\t\t\tnamesOf(without[0]!.catalog, context.memo.limits),\n\t\t\t\t\t),\n\t\t\t\t},\n\t\t\t]\n}\n\n/**\n * The issues of a field that a condition reads: a field that is not a field\n * of every resource of the rule, or a field that is not plain data in one of\n * them. A read of a value that is not plain data, as a getter of a class, is\n * a defect in a decision. A residual may leave the read out, so the residual\n * would say \"yes\" where the decision fails. So the binding does not take\n * such a read: see `Catalog.Field.plain`.\n */\nconst readIssues = (\n\tcontext: Context,\n\tfield: string,\n\tat: ReadonlyArray<PropertyKey>,\n): ReadonlyArray<Issue> => {\n\tconst exists = fieldIssue(context, field, at)\n\tif (exists.length > 0) {\n\t\treturn exists\n\t}\n\treturn Arr.some(\n\t\tcontext.named,\n\t\t(descriptor) =>\n\t\t\t!(\n\t\t\t\tCatalog.lookup(\n\t\t\t\t\tdescriptor.catalog,\n\t\t\t\t\tfield,\n\t\t\t\t\tcontext.memo.limits.fieldPathParts,\n\t\t\t\t)?.plain ?? true\n\t\t\t),\n\t)\n\t\t? [\n\t\t\t\t{\n\t\t\t\t\tpath: at,\n\t\t\t\t\tissue: `A condition may not read the field \"${field}\". A value above it may be an object that does not have it as a data field, as a class with a getter, a date or a list. Use a predicate for it.`,\n\t\t\t\t},\n\t\t\t]\n\t\t: []\n}\n\n/**\n * The issues of a field pattern: a pattern that does not parse, or that\n * matches no field of the resources of the rule, as a pattern with a mistake\n * in a name does.\n */\nconst patternIssues = (\n\tmemo: Memo,\n\tknown: ReadonlyArray<Resource.Descriptor>,\n\tpattern: string,\n\tat: ReadonlyArray<PropertyKey>,\n): ReadonlyArray<Issue> =>\n\tResult.match(Patterns.parse(pattern, memo.limits.patternParts), {\n\t\tonFailure: (issue) => [{ path: at, issue }],\n\t\tonSuccess: (parsed) => {\n\t\t\t// A rule with no known resource has an issue for that already. The\n\t\t\t// pattern is checked on every kind, not only up to the first that it\n\t\t\t// fits: a decision on a kind does this check, so the limit of the\n\t\t\t// work below is a limit of the work of a decision too.\n\t\t\tconst fits =\n\t\t\t\tknown.length === 0 ||\n\t\t\t\tArr.reduce(known, false, (before, descriptor) => {\n\t\t\t\t\tconst key = `${descriptor.kind}\\u0000${pattern}`\n\t\t\t\t\treturn (\n\t\t\t\t\t\tOption.getOrElse(Option.fromUndefinedOr(memo.fits.get(key)), () => {\n\t\t\t\t\t\t\t// Each check of a pattern reads the steps of a catalog. The\n\t\t\t\t\t\t\t// work of one binding has a limit, so very many patterns stop\n\t\t\t\t\t\t\t// with one issue, and the policy does not load.\n\t\t\t\t\t\t\tmemo.work.steps +=\n\t\t\t\t\t\t\t\tMath.max(descriptor.catalog.edges, 1) *\n\t\t\t\t\t\t\t\tMath.max(parsed.length, 1)\n\t\t\t\t\t\t\tif (memo.work.steps > memo.limits.searchSteps) {\n\t\t\t\t\t\t\t\treturn true\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst found = Patterns.fitsCatalog(\n\t\t\t\t\t\t\t\tparsed,\n\t\t\t\t\t\t\t\tdescriptor.catalog,\n\t\t\t\t\t\t\t\tmemo.limits.fieldPathParts,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\tmemo.fits.set(key, found)\n\t\t\t\t\t\t\treturn found\n\t\t\t\t\t\t}) || before\n\t\t\t\t\t)\n\t\t\t\t})\n\t\t\t// Past the limit of the work, `ruleIssues` gives one issue for it.\n\t\t\treturn fits || memo.work.steps > memo.limits.searchSteps\n\t\t\t\t? []\n\t\t\t\t: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tpath: at,\n\t\t\t\t\t\t\tissue: `The field pattern \"${pattern}\" matches no field of the resources of the rule`,\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t},\n\t})\n\n/** The fields of the resources that a test of the rule may read. */\nconst fieldsAt = (\n\tcontext: Context,\n\tfield: string,\n): ReadonlyArray<Catalog.Field> =>\n\tArr.flatMap(context.applicable, (descriptor) => {\n\t\tconst found = Catalog.lookup(\n\t\t\tdescriptor.catalog,\n\t\t\tfield,\n\t\t\tcontext.memo.limits.fieldPathParts,\n\t\t)\n\t\treturn found === undefined ? [] : [found]\n\t})\n\n/** The field of the principal that an operand names, or the issue when there is none. */\nconst principalField = (\n\tcontext: Context,\n\tref: string,\n\tat: ReadonlyArray<PropertyKey>,\n): Catalog.Field | Issue => {\n\tconst found = Catalog.lookup(\n\t\tcontext.domain.principalCatalog,\n\t\tref,\n\t\tcontext.memo.limits.fieldPathParts,\n\t)\n\t// A read of the principal that is not plain data is a defect, as a read\n\t// of a resource is: see `readIssues`.\n\tif (found !== undefined && !found.plain) {\n\t\treturn {\n\t\t\tpath: at,\n\t\t\tissue: `A condition may not read the field \"${ref}\" of the principal. A value above it may be an object that does not have it as a data field, as a class with a getter, a date or a list. Use a predicate for it.`,\n\t\t}\n\t}\n\treturn found !== undefined\n\t\t? found\n\t\t: {\n\t\t\t\tpath: at,\n\t\t\t\tissue: suggestIn(\n\t\t\t\t\tcontext.memo,\n\t\t\t\t\t`The principal has no field \"${ref}\"`,\n\t\t\t\t\tref,\n\t\t\t\t\t\"~principal\",\n\t\t\t\t\t() => namesOf(context.domain.principalCatalog, context.memo.limits),\n\t\t\t\t),\n\t\t\t}\n}\n\nconst isIssue = (value: Catalog.Field | Issue): value is Issue =>\n\t\"issue\" in value\n\n/**\n * The fields that a test reads, with one field for each distinct kind of\n * value. A rule for very many kinds may have one field type in all of them,\n * so each value of the test is checked one time for each distinct kind, not\n * one time for each resource. The kinds are found by their text in a native\n * `Map`, which belongs to this call.\n */\nconst distinctFields = (\n\tcontext: Context,\n\tfield: string,\n): ReadonlyArray<Catalog.Field> =>\n\tArray.from(\n\t\tnew Map(\n\t\t\tArr.map(\n\t\t\t\tfieldsAt(context, field),\n\t\t\t\t(found) => [JSON.stringify(found.kinds), found] as const,\n\t\t\t),\n\t\t).values(),\n\t)\n\n/**\n * Counts the work of the checks of the values of a test, as the work of the\n * field patterns counts. Past the limit, the values are not checked, and\n * `ruleIssues` gives one issue for the work.\n */\nconst spendChecks = (memo: Memo, values: number, fields: number): boolean => {\n\tmemo.work.steps += values * Math.max(fields, 1)\n\treturn memo.work.steps <= memo.limits.searchSteps\n}\n\n/** The issues of one operand of a test of a field, with its distinct fields. */\nconst operandIssues = (\n\tcontext: Context,\n\tfield: string,\n\tfields: ReadonlyArray<Catalog.Field>,\n\tordered: boolean,\n\toperand: Operand,\n\tat: ReadonlyArray<PropertyKey>,\n): ReadonlyArray<Issue> => {\n\tif (operand._tag === \"Literal\") {\n\t\tconst value = operand.value\n\t\tif (ordered && typeof value !== \"string\" && typeof value !== \"number\") {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tpath: at,\n\t\t\t\t\tissue: `The field \"${field}\" has an ordered test with a value that has no order`,\n\t\t\t\t},\n\t\t\t]\n\t\t}\n\t\treturn [\n\t\t\t...(Arr.some(fields, (found) => !fitsLiteral(found.kinds, value))\n\t\t\t\t? [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tpath: at,\n\t\t\t\t\t\t\tissue: `The field \"${field}\" can not hold the value ${JSON.stringify(value)}`,\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t: []),\n\t\t\t...(ordered && Arr.some(fields, (found) => !isOrdered(found.kinds))\n\t\t\t\t? [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tpath: at,\n\t\t\t\t\t\t\tissue: `The field \"${field}\" has an ordered test with a value that has no order`,\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t: []),\n\t\t]\n\t}\n\tconst principal = principalField(context, operand.path, at)\n\tif (isIssue(principal)) {\n\t\treturn [principal]\n\t}\n\treturn [\n\t\t...(Arr.some(fields, (found) => !meet(found.kinds, principal.kinds))\n\t\t\t? [\n\t\t\t\t\t{\n\t\t\t\t\t\tpath: at,\n\t\t\t\t\t\tissue: `The field \"${field}\" can not hold a value of the principal field \"${operand.path}\"`,\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t: []),\n\t\t...(ordered &&\n\t\tArr.some(fields, (found) => !orderedMeet(found.kinds, principal.kinds))\n\t\t\t? [\n\t\t\t\t\t{\n\t\t\t\t\t\tpath: at,\n\t\t\t\t\t\tissue: `The field \"${field}\" has an ordered test with a value that has no order`,\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t: []),\n\t]\n}\n\n/**\n * Tests if the path of a resource may start with a prefix: it is as long as\n * the prefix, and each fixed string of the prefix is the string of the path\n * at its place, when the path has a string there.\n */\nconst mayStartWith = (\n\tdescriptor: Resource.Descriptor,\n\tstrings: ReadonlyArray<string | null>,\n): boolean =>\n\t(descriptor.path?.length ?? 0) >= strings.length &&\n\tArr.every(strings, (string, index) => {\n\t\tconst segment = descriptor.path?.[index]\n\t\treturn string === null || typeof segment !== \"string\" || segment === string\n\t})\n\n/** The kinds of the part of the path of a resource at a place. */\nconst pathPartKinds = (\n\tdescriptor: Resource.Descriptor,\n\tindex: number,\n): Catalog.Kinds | string | undefined => {\n\tconst segment = descriptor.path?.[index]\n\treturn segment === undefined || typeof segment === \"string\"\n\t\t? segment\n\t\t: (Catalog.lookup(descriptor.catalog, segment.field)?.kinds ??\n\t\t\t\tCatalog.anyKinds)\n}\n\n/** The issues of the prefix of a path test. */\nconst underIssues = (\n\tcontext: Context,\n\tprefix: ReadonlyArray<Operand>,\n\tat: ReadonlyArray<PropertyKey>,\n): ReadonlyArray<Issue> => {\n\tconst strings = Arr.map(prefix, (operand) =>\n\t\toperand._tag === \"Literal\" && typeof operand.value === \"string\"\n\t\t\t? operand.value\n\t\t\t: null,\n\t)\n\t// The test can match only a path that starts with the fixed strings of\n\t// the prefix, so the other resources are left out.\n\tconst candidates = Arr.filter(context.paths, (descriptor) =>\n\t\tmayStartWith(descriptor, strings),\n\t)\n\treturn Arr.flatMap(prefix, (operand, index): ReadonlyArray<Issue> => {\n\t\tconst place = [...at, \"prefix\", index]\n\t\tif (operand._tag === \"Literal\") {\n\t\t\treturn Arr.some(candidates, (descriptor) => {\n\t\t\t\tconst kinds = pathPartKinds(descriptor, index)\n\t\t\t\treturn typeof kinds === \"object\" && !fitsLiteral(kinds, operand.value)\n\t\t\t})\n\t\t\t\t? [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tpath: place,\n\t\t\t\t\t\t\tissue: `The path part ${String(index)} can not hold the value ${JSON.stringify(operand.value)}`,\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t: []\n\t\t}\n\t\tconst principal = principalField(context, operand.path, place)\n\t\tif (isIssue(principal)) {\n\t\t\treturn [principal]\n\t\t}\n\t\treturn Arr.some(candidates, (descriptor) => {\n\t\t\tconst kinds = pathPartKinds(descriptor, index)\n\t\t\treturn kinds === undefined\n\t\t\t\t? true\n\t\t\t\t: typeof kinds === \"string\"\n\t\t\t\t\t? !Catalog.accepts(principal.kinds, kinds)\n\t\t\t\t\t: !meet(kinds, principal.kinds)\n\t\t})\n\t\t\t? [\n\t\t\t\t\t{\n\t\t\t\t\t\tpath: place,\n\t\t\t\t\t\tissue: `The principal field \"${operand.path}\" can not hold a part of the path of the resource`,\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t: []\n\t})\n}\n\nconst notWhole = (at: ReadonlyArray<PropertyKey>): Issue => ({\n\tpath: at,\n\tissue: \"The condition is not a whole condition tree\",\n})\n\n/**\n * The most issues that the walk of the conditions of one binding gives. A\n * policy may have very many wrong tests, and each issue keeps its path.\n */\nconst maxIssues = 100\n\n/**\n * The place of a part of a policy, which a walk turns into a full path only\n * for an issue: see `Place`.\n */\ntype At = Place.Place | undefined\n\n/**\n * Puts the path of a node before the paths of its issues. A walk keeps no\n * path for a node with no issue, so a deep and wide condition costs little.\n */\nconst under = (\n\tmemo: Memo,\n\tat: At,\n\tissues: ReadonlyArray<Issue>,\n): ReadonlyArray<Issue> => {\n\tif (issues.length === 0) {\n\t\treturn issues\n\t}\n\tconst room = maxIssues - memo.issues.count\n\tmemo.issues.count += issues.length\n\tconst kept = Arr.map(Arr.take(issues, Math.max(room, 0)), (issue) => ({\n\t\tpath: [...Place.pathOf(at), ...issue.path],\n\t\tissue: issue.issue,\n\t}))\n\tconst first = memo.issues.count > maxIssues && !memo.issues.noted\n\tmemo.issues.noted ||= first\n\treturn first\n\t\t? [\n\t\t\t\t...kept,\n\t\t\t\t{\n\t\t\t\t\tpath: [],\n\t\t\t\t\tissue: `The policy has more problems. The first ${String(maxIssues)} are here.`,\n\t\t\t\t},\n\t\t\t]\n\t\t: kept\n}\n\n/**\n * The issues of a condition. The constructors of the nodes checked the shape\n * of each node, so this checks the names and the types against the domain. A\n * node that the condition uses in many places is checked one time. The nodes\n * it has seen are in a native `Set`, because it must find a node by the node\n * itself: `Equal` compares nodes by structure.\n */\nconst conditionIssues = (\n\tcontext: Context,\n\troot: Nodes.Node,\n\trootAt: At,\n\tseen: Set<Nodes.Node>,\n): ReadonlyArray<Issue> =>\n\t// The walk takes the steps of a trampoline, so a deep condition does not\n\t// fill the stack.\n\tTrampoline.run(root.depth, (R) => {\n\t\t// A test looks its field up in each resource of the rule. A rule for\n\t\t// very many kinds with very many tests is very much work, so it counts.\n\t\t// Past the limit, `ruleIssues` gives one issue for it, and the policy\n\t\t// does not load.\n\t\tconst room = (values: number) =>\n\t\t\tspendChecks(context.memo, 1 + values, context.named.length)\n\t\tconst leaf = (node: Nodes.Node, at: At): ReadonlyArray<Issue> => {\n\t\t\tconst here = (issues: ReadonlyArray<Issue>) =>\n\t\t\t\tunder(context.memo, at, issues)\n\t\t\tswitch (node._tag) {\n\t\t\t\tcase \"Compare\": {\n\t\t\t\t\tif (!room(1)) {\n\t\t\t\t\t\treturn []\n\t\t\t\t\t}\n\t\t\t\t\tconst exists = readIssues(context, node.field, [\"field\"])\n\t\t\t\t\treturn here(\n\t\t\t\t\t\texists.length > 0\n\t\t\t\t\t\t\t? exists\n\t\t\t\t\t\t\t: operandIssues(\n\t\t\t\t\t\t\t\t\tcontext,\n\t\t\t\t\t\t\t\t\tnode.field,\n\t\t\t\t\t\t\t\t\tdistinctFields(context, node.field),\n\t\t\t\t\t\t\t\t\tnode.op !== \"eq\" && node.op !== \"ne\",\n\t\t\t\t\t\t\t\t\tnode.value,\n\t\t\t\t\t\t\t\t\t[\"value\"],\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tcase \"In\": {\n\t\t\t\t\tif (!room(0)) {\n\t\t\t\t\t\treturn []\n\t\t\t\t\t}\n\t\t\t\t\tconst exists = readIssues(context, node.field, [\"field\"])\n\t\t\t\t\tif (exists.length > 0) {\n\t\t\t\t\t\treturn here(exists)\n\t\t\t\t\t}\n\t\t\t\t\tconst fields = distinctFields(context, node.field)\n\t\t\t\t\tif (!spendChecks(context.memo, node.values.length, fields.length)) {\n\t\t\t\t\t\treturn []\n\t\t\t\t\t}\n\t\t\t\t\treturn here(\n\t\t\t\t\t\tArr.flatMap(node.values, (operand, index) =>\n\t\t\t\t\t\t\toperandIssues(context, node.field, fields, false, operand, [\n\t\t\t\t\t\t\t\t\"values\",\n\t\t\t\t\t\t\t\tindex,\n\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tcase \"Exists\":\n\t\t\t\t\treturn room(0) ? here(readIssues(context, node.field, [\"field\"])) : []\n\t\t\t\tcase \"Under\":\n\t\t\t\t\treturn here(underIssues(context, node.prefix, []))\n\t\t\t\tdefault:\n\t\t\t\t\treturn []\n\t\t\t}\n\t\t}\n\t\tconst go = (\n\t\t\tnode: Nodes.Node,\n\t\t\tat: At,\n\t\t): Trampoline.Step<ReadonlyArray<Issue>> => {\n\t\t\tif (seen.has(node)) {\n\t\t\t\treturn R.succeed([])\n\t\t\t}\n\t\t\tseen.add(node)\n\t\t\tswitch (node._tag) {\n\t\t\t\tcase \"And\":\n\t\t\t\tcase \"Or\":\n\t\t\t\t\treturn R.map(\n\t\t\t\t\t\tR.forEach(node.conditions, (child, index) =>\n\t\t\t\t\t\t\tR.suspend(() => go(child, Place.at(at, \"conditions\", index))),\n\t\t\t\t\t\t),\n\t\t\t\t\t\tArr.flatten,\n\t\t\t\t\t)\n\t\t\t\tcase \"Not\":\n\t\t\t\t\treturn R.suspend(() => go(node.condition, Place.at(at, \"condition\")))\n\t\t\t\tdefault:\n\t\t\t\t\treturn R.succeed(leaf(node, at))\n\t\t\t}\n\t\t}\n\t\treturn go(root, rootAt)\n\t})\n\nconst hasAction = (\n\tdescriptor: Resource.Descriptor,\n\tactions: ReadonlyArray<string>,\n): boolean =>\n\tactions.includes(\"*\") ||\n\tArr.some(actions, (action) => descriptor.actions.includes(action))\n\n/**\n * Checks one rule against a domain. It gives one issue for each problem, up\n * to the most issues of the binding. `at` is the place of the rule, and its\n * full path is made only for an issue that the binding keeps: a rule with\n * very many problems deep in a tree does not make a full path for each one.\n */\nexport const ruleIssues = (\n\tdomain: Domain.Any,\n\trule: RuleData,\n\tat: At,\n\tmemo: Memo = makeMemo(domain.limits),\n): ReadonlyArray<Issue> => {\n\tconst here = (issues: ReadonlyArray<Issue>) => under(memo, at, issues)\n\tconst all = rule.resources.includes(\"*\")\n\t// Each kind is checked one time, also when the list names it again. The\n\t// names come from a policy, so a native `Set` finds the copies:\n\t// `Arr.dedupe` puts the names in groups by the hash of Effect, which has\n\t// no random seed, and very many names with one hash make one long group.\n\tconst names = Array.from(\n\t\tnew Set(Arr.filter(rule.resources, (kind) => kind !== \"*\")),\n\t)\n\tconst kinds = Array.from(HashMap.keys(domain.resources))\n\tconst named = Arr.flatMap(names, (kind) =>\n\t\tOption.toArray(HashMap.get(domain.resources, kind)),\n\t)\n\tconst known = all ? Array.from(HashMap.values(domain.resources)) : named\n\t// The rule never runs on a kind without one of its actions, so the types\n\t// of its tests are checked only on the kinds with one.\n\tconst applicable = Arr.filter(named, (descriptor) =>\n\t\thasAction(descriptor, rule.actions),\n\t)\n\tconst context: Context = {\n\t\tdomain,\n\t\tmemo,\n\t\tall,\n\t\tnamed,\n\t\tapplicable,\n\t\tpaths: all\n\t\t\t? Arr.filter(known, (descriptor) => hasAction(descriptor, rule.actions))\n\t\t\t: applicable,\n\t}\n\t// A native `Set` finds copies about ten times as fast as `Arr.dedupe`.\n\tconst actions = Array.from(\n\t\tnew Set(Arr.flatMap(known, (descriptor) => descriptor.actions)),\n\t)\n\tconst fieldIssues = Arr.flatMap(rule.fields ?? [], (field, index) =>\n\t\there(\n\t\t\tfield.includes(\"*\")\n\t\t\t\t? patternIssues(memo, known, field, [\"fields\", index])\n\t\t\t\t: fieldIssue(context, field, [\"fields\", index]),\n\t\t),\n\t)\n\tconst conditionNodes = memo.limits.conditionNodes\n\tconst whenIssues =\n\t\trule.when === undefined\n\t\t\t? []\n\t\t\t: !Nodes.isNode(rule.when)\n\t\t\t\t? here([notWhole([\"when\"])])\n\t\t\t\t: // The constructors of the nodes check the ceiling, so the binding\n\t\t\t\t\t// checks the limit of the domain.\n\t\t\t\t\trule.when.weight > conditionNodes\n\t\t\t\t\t? here([\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tpath: [\"when\"],\n\t\t\t\t\t\t\t\tissue: `A condition has ${String(rule.when.weight)} nodes and values. The most is ${String(conditionNodes)}.`,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t])\n\t\t\t\t\t: conditionIssues(context, rule.when, Place.at(at, \"when\"), new Set())\n\t// The work is known only after every check of the rule. Past the limit, a\n\t// check gives no issue, so this issue must come, or the policy would load\n\t// with checks left out.\n\tconst work = memo.work.steps > memo.limits.searchSteps && !memo.work.noted\n\tmemo.work.noted ||= work\n\treturn [\n\t\t...Arr.flatMap(\n\t\t\tArr.filter(names, (kind) => !HashMap.has(domain.resources, kind)),\n\t\t\t(kind: string) =>\n\t\t\t\there([\n\t\t\t\t\t{\n\t\t\t\t\t\tpath: [\"resources\"],\n\t\t\t\t\t\tissue: suggestIn(\n\t\t\t\t\t\t\tmemo,\n\t\t\t\t\t\t\t`There is no resource with the kind \"${kind}\"`,\n\t\t\t\t\t\t\tkind,\n\t\t\t\t\t\t\t\"~kinds\",\n\t\t\t\t\t\t\t() => kinds,\n\t\t\t\t\t\t),\n\t\t\t\t\t},\n\t\t\t\t]),\n\t\t),\n\t\t...Arr.flatMap(\n\t\t\tArr.filter(\n\t\t\t\trule.actions,\n\t\t\t\t(action) => action !== \"*\" && !actions.includes(action),\n\t\t\t),\n\t\t\t(action: string) =>\n\t\t\t\there([\n\t\t\t\t\t{\n\t\t\t\t\t\tpath: [\"actions\"],\n\t\t\t\t\t\tissue: suggestIn(\n\t\t\t\t\t\t\tmemo,\n\t\t\t\t\t\t\t`The action \"${action}\" is not an action of these resources`,\n\t\t\t\t\t\t\taction,\n\t\t\t\t\t\t\t`~actions\\u0000${names.join(\",\")}`,\n\t\t\t\t\t\t\t() => actions,\n\t\t\t\t\t\t),\n\t\t\t\t\t},\n\t\t\t\t]),\n\t\t),\n\t\t...fieldIssues,\n\t\t...whenIssues,\n\t\t// The issue of the work is not one of the most issues of the binding,\n\t\t// so a policy past the limit always says why.\n\t\t...(work\n\t\t\t? [\n\t\t\t\t\t{\n\t\t\t\t\t\tpath: Place.pathOf(at),\n\t\t\t\t\t\tissue: `The policy takes more than ${String(memo.limits.searchSteps)} steps to check: its field patterns, and the fields and values of its tests on each resource. The most is ${String(memo.limits.searchSteps)}.`,\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t: []),\n\t]\n}\n\n/** The JSON form of a policy tree, or a policy tree, as the binding reads it. */\ntype TreeNode =\n\t| { readonly _tag: \"Rules\"; readonly rules: ReadonlyArray<RuleData> }\n\t| {\n\t\t\treadonly _tag: \"All\" | \"Any\" | \"FirstOf\"\n\t\t\treadonly policies: ReadonlyArray<TreeNode>\n\t }\n\t| {\n\t\t\treadonly _tag: \"Ceiling\"\n\t\t\treadonly cap: TreeNode\n\t\t\treadonly policy: TreeNode\n\t }\n\n/** Checks each rule of a policy tree against a domain. */\nexport const treeIssues = (\n\tdomain: Domain.Any,\n\troot: TreeNode,\n): ReadonlyArray<Issue> => {\n\t// The rules come out of the tree first, and each one is checked after.\n\t// A check of a condition goes down its levels, so a check inside the walk\n\t// of the tree would go down the levels of both. The place of each rule\n\t// shares the path of its parent. The tree may be the JSON form, with no\n\t// depth that a node knows, so the walk is a walk of the trampoline for an\n\t// input of an unknown depth.\n\t// The rules go into one list, in order: a walk that joined the lists of\n\t// the children at each node would copy the list of a deep tree at each\n\t// level. The list belongs to this walk.\n\tconst found: Array<readonly [RuleData, Place.Place]> = []\n\tconst rulesOf = Trampoline.adaptive<\n\t\t{ readonly node: TreeNode; readonly at: At },\n\t\tundefined\n\t>((R, child) => ({ node, at }, level) => {\n\t\tconst below = (\n\t\t\tparts: ReadonlyArray<{ readonly node: TreeNode; readonly at: At }>,\n\t\t) =>\n\t\t\tR.map(\n\t\t\t\tR.forEach(parts, (part) => child(part, level + 1)),\n\t\t\t\t() => undefined,\n\t\t\t)\n\t\tswitch (node._tag) {\n\t\t\tcase \"Rules\":\n\t\t\t\tArr.forEach(node.rules, (rule, index) => {\n\t\t\t\t\tfound.push([rule, Place.at(at, \"rules\", index)])\n\t\t\t\t})\n\t\t\t\treturn R.succeed(undefined)\n\t\t\tcase \"All\":\n\t\t\tcase \"Any\":\n\t\t\tcase \"FirstOf\":\n\t\t\t\treturn below(\n\t\t\t\t\tArr.map(node.policies, (inner, index) => ({\n\t\t\t\t\t\tnode: inner,\n\t\t\t\t\t\tat: Place.at(at, \"policies\", index),\n\t\t\t\t\t})),\n\t\t\t\t)\n\t\t\tcase \"Ceiling\":\n\t\t\t\treturn below([\n\t\t\t\t\t{ node: node.cap, at: Place.at(at, \"cap\") },\n\t\t\t\t\t{ node: node.policy, at: Place.at(at, \"policy\") },\n\t\t\t\t])\n\t\t}\n\t})\n\trulesOf({ node: root, at: undefined })\n\tconst memo = makeMemo(domain.limits)\n\treturn Arr.flatMap(found, ([rule, place]) =>\n\t\truleIssues(domain, rule, place, memo),\n\t)\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AA6DA,MAAM,YAAY,YAAiC;CAClD,6BAAa,IAAI,IAAI;CACrB,sBAAM,IAAI,IAAI;CACd,QAAQ;EAAE,OAAO;EAAG,OAAO;CAAM;CACjC,MAAM;EAAE,OAAO;EAAG,OAAO;CAAM;CAC/B;AACD;;;;;;AAOA,MAAM,iBAAiB;;AAGvB,MAAM,aACL,MACA,SACA,MACA,OACA,UACY;CACZ,MAAM,MAAM,GAAG,MAAM,QAAQ;CAC7B,MAAM,QAAQ,KAAK,YAAY,IAAI,GAAG;CACtC,IAAI,UAAU,KAAA,GACb,OAAO;CAER,IAAI,KAAK,YAAY,QAAQ,gBAC5B,OAAO;CAER,MAAM,OAAO,eAAe,SAAS,MAAM,MAAM,CAAC;CAClD,KAAK,YAAY,IAAI,KAAK,IAAI;CAC9B,OAAO;AACR;;;;;;;AA2BA,MAAM,eAAe,OAAsB,UAC1C,UAAU,QACT,OAAO,UAAU,YAAY,MAAM,WAAW,UAC/CA,QAAgB,OAAO,KAAK;;;;;;AAO7B,MAAM,QAAQ,MAAqB,UACjC,KAAK,UAAU,MAAM,UACrB,KAAK,WAAW,MAAM,WACtB,KAAK,WAAW,UAAU,MAAM,WAAW,UAC5C,IAAI,KAAK,KAAK,WAAW,UAAUA,QAAgB,OAAO,KAAK,CAAC,KAChE,IAAI,KAAK,MAAM,WAAW,UAAUA,QAAgB,MAAM,KAAK,CAAC;AAEjE,MAAM,cAAc,UACnB,MAAM,UAAU,IAAI,KAAK,MAAM,WAAW,UAAU,OAAO,UAAU,QAAQ;AAE9E,MAAM,cAAc,UACnB,MAAM,WAAW,UACjB,IAAI,KAAK,MAAM,WAAW,UAAU,OAAO,UAAU,QAAQ;;AAG9D,MAAM,aAAa,UAClB,WAAW,KAAK,KAAK,WAAW,KAAK;;AAGtC,MAAM,eAAe,MAAqB,UACxC,WAAW,IAAI,KAAK,WAAW,KAAK,KACpC,WAAW,IAAI,KAAK,WAAW,KAAK;;;;;;AAOtC,MAAM,WACL,SACA,WAEA,IAAI,IACH,IAAI,OACHC,SAAiB,SAAS,OAAO,gBAAgB,OAAO,aAAa,IACpE,UACA,IAAI,MAAM,MAAM,OAAO,YAAY,OAAO,YAAY,QAAQ,CAChE,IACC,UAAU,MAAM,IAClB;;AAGD,MAAM,cACL,SACA,OACA,OAC0B;CAC1B,MAAM,QAAQ,QAAQ,KAAK,OAAO;CAClC,IAAI,MAAM,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,OACxC,OAAO,CACN;EACC,MAAM;EACN,OAAO,mBAAmB,MAAM,kBAAkB,OAAO,KAAK,EAAE,sBAAsB,OAAO,KAAK,EAAE;CACrG,CACD;CAED,MAAM,UAAU,cAAc,MAAM;CACpC,IAAI,QAAQ,OAAO,QAAQ,MAAM,WAAW,GAC3C,OAAO,CAAC;EAAE,MAAM;EAAI,OAAO;CAAQ,CAAC;CAErC,MAAM,UAAU,IAAI,OACnB,QAAQ,QACP,eACAC,OAAe,WAAW,SAAS,OAAO,KAAK,MAAM,KAAA,CACvD;CACA,OAAO,QAAQ,WAAW,IACvB,CAAC,IACD,CACA;EACC,MAAM;EACN,OAAO,UAAU,QAAQ,MAAM,SAAS,OAAO,QAAQ,EAAE,CAAE,YAC1D,QAAQ,QAAQ,EAAE,CAAE,SAAS,QAAQ,KAAK,MAAM,CACjD;CACD,CACD;AACH;;;;;;;;;AAUA,MAAM,cACL,SACA,OACA,OAC0B;CAC1B,MAAM,SAAS,WAAW,SAAS,OAAO,EAAE;CAC5C,IAAI,OAAO,SAAS,GACnB,OAAO;CAER,OAAO,IAAI,KACV,QAAQ,QACP,eACA,EACCA,OACC,WAAW,SACX,OACA,QAAQ,KAAK,OAAO,cACrB,CAAC,EAAE,SAAS,KAEf,IACG,CACA;EACC,MAAM;EACN,OAAO,uCAAuC,MAAM;CACrD,CACD,IACC,CAAC;AACL;;;;;;AAOA,MAAM,iBACL,MACA,OACA,SACA,OAEA,OAAO,MAAMC,MAAe,SAAS,KAAK,OAAO,YAAY,GAAG;CAC/D,YAAY,UAAU,CAAC;EAAE,MAAM;EAAI;CAAM,CAAC;CAC1C,YAAY,WAAW;EA+BtB,OAzBC,MAAM,WAAW,KACjB,IAAI,OAAO,OAAO,QAAQ,QAAQ,eAAe;GAChD,MAAM,MAAM,GAAG,WAAW,KAAK,QAAQ;GACvC,OACC,OAAO,UAAU,OAAO,gBAAgB,KAAK,KAAK,IAAI,GAAG,CAAC,SAAS;IAIlE,KAAK,KAAK,SACT,KAAK,IAAI,WAAW,QAAQ,OAAO,CAAC,IACpC,KAAK,IAAI,OAAO,QAAQ,CAAC;IAC1B,IAAI,KAAK,KAAK,QAAQ,KAAK,OAAO,aACjC,OAAO;IAER,MAAM,QAAQC,YACb,QACA,WAAW,SACX,KAAK,OAAO,cACb;IACA,KAAK,KAAK,IAAI,KAAK,KAAK;IACxB,OAAO;GACR,CAAC,KAAK;EAER,CAAC,KAEa,KAAK,KAAK,QAAQ,KAAK,OAAO,cAC1C,CAAC,IACD,CACA;GACC,MAAM;GACN,OAAO,sBAAsB,QAAQ;EACtC,CACD;CACH;AACD,CAAC;;AAGF,MAAM,YACL,SACA,UAEA,IAAI,QAAQ,QAAQ,aAAa,eAAe;CAC/C,MAAM,QAAQF,OACb,WAAW,SACX,OACA,QAAQ,KAAK,OAAO,cACrB;CACA,OAAO,UAAU,KAAA,IAAY,CAAC,IAAI,CAAC,KAAK;AACzC,CAAC;;AAGF,MAAM,kBACL,SACA,KACA,OAC2B;CAC3B,MAAM,QAAQA,OACb,QAAQ,OAAO,kBACf,KACA,QAAQ,KAAK,OAAO,cACrB;CAGA,IAAI,UAAU,KAAA,KAAa,CAAC,MAAM,OACjC,OAAO;EACN,MAAM;EACN,OAAO,uCAAuC,IAAI;CACnD;CAED,OAAO,UAAU,KAAA,IACd,QACA;EACA,MAAM;EACN,OAAO,UACN,QAAQ,MACR,+BAA+B,IAAI,IACnC,KACA,oBACM,QAAQ,QAAQ,OAAO,kBAAkB,QAAQ,KAAK,MAAM,CACnE;CACD;AACH;AAEA,MAAM,WAAW,UAChB,WAAW;;;;;;;;AASZ,MAAM,kBACL,SACA,UAEA,MAAM,KACL,IAAI,IACH,IAAI,IACH,SAAS,SAAS,KAAK,IACtB,UAAU,CAAC,KAAK,UAAU,MAAM,KAAK,GAAG,KAAK,CAC/C,CACD,CAAC,CAAC,OAAO,CACV;;;;;;AAOD,MAAM,eAAe,MAAY,QAAgB,WAA4B;CAC5E,KAAK,KAAK,SAAS,SAAS,KAAK,IAAI,QAAQ,CAAC;CAC9C,OAAO,KAAK,KAAK,SAAS,KAAK,OAAO;AACvC;;AAGA,MAAM,iBACL,SACA,OACA,QACA,SACA,SACA,OAC0B;CAC1B,IAAI,QAAQ,SAAS,WAAW;EAC/B,MAAM,QAAQ,QAAQ;EACtB,IAAI,WAAW,OAAO,UAAU,YAAY,OAAO,UAAU,UAC5D,OAAO,CACN;GACC,MAAM;GACN,OAAO,cAAc,MAAM;EAC5B,CACD;EAED,OAAO,CACN,GAAI,IAAI,KAAK,SAAS,UAAU,CAAC,YAAY,MAAM,OAAO,KAAK,CAAC,IAC7D,CACA;GACC,MAAM;GACN,OAAO,cAAc,MAAM,2BAA2B,KAAK,UAAU,KAAK;EAC3E,CACD,IACC,CAAC,GACJ,GAAI,WAAW,IAAI,KAAK,SAAS,UAAU,CAAC,UAAU,MAAM,KAAK,CAAC,IAC/D,CACA;GACC,MAAM;GACN,OAAO,cAAc,MAAM;EAC5B,CACD,IACC,CAAC,CACL;CACD;CACA,MAAM,YAAY,eAAe,SAAS,QAAQ,MAAM,EAAE;CAC1D,IAAI,QAAQ,SAAS,GACpB,OAAO,CAAC,SAAS;CAElB,OAAO,CACN,GAAI,IAAI,KAAK,SAAS,UAAU,CAAC,KAAK,MAAM,OAAO,UAAU,KAAK,CAAC,IAChE,CACA;EACC,MAAM;EACN,OAAO,cAAc,MAAM,iDAAiD,QAAQ,KAAK;CAC1F,CACD,IACC,CAAC,GACJ,GAAI,WACJ,IAAI,KAAK,SAAS,UAAU,CAAC,YAAY,MAAM,OAAO,UAAU,KAAK,CAAC,IACnE,CACA;EACC,MAAM;EACN,OAAO,cAAc,MAAM;CAC5B,CACD,IACC,CAAC,CACL;AACD;;;;;;AAOA,MAAM,gBACL,YACA,aAEC,WAAW,MAAM,UAAU,MAAM,QAAQ,UAC1C,IAAI,MAAM,UAAU,QAAQ,UAAU;CACrC,MAAM,UAAU,WAAW,OAAO;CAClC,OAAO,WAAW,QAAQ,OAAO,YAAY,YAAY,YAAY;AACtE,CAAC;;AAGF,MAAM,iBACL,YACA,UACwC;CACxC,MAAM,UAAU,WAAW,OAAO;CAClC,OAAO,YAAY,KAAA,KAAa,OAAO,YAAY,WAChD,UACCA,OAAe,WAAW,SAAS,QAAQ,KAAK,CAAC,EAAE,SACpDG;AACJ;;AAGA,MAAM,eACL,SACA,QACA,OAC0B;CAC1B,MAAM,UAAU,IAAI,IAAI,SAAS,YAChC,QAAQ,SAAS,aAAa,OAAO,QAAQ,UAAU,WACpD,QAAQ,QACR,IACJ;CAGA,MAAM,aAAa,IAAI,OAAO,QAAQ,QAAQ,eAC7C,aAAa,YAAY,OAAO,CACjC;CACA,OAAO,IAAI,QAAQ,SAAS,SAAS,UAAgC;EACpE,MAAM,QAAQ;GAAC,GAAG;GAAI;GAAU;EAAK;EACrC,IAAI,QAAQ,SAAS,WACpB,OAAO,IAAI,KAAK,aAAa,eAAe;GAC3C,MAAM,QAAQ,cAAc,YAAY,KAAK;GAC7C,OAAO,OAAO,UAAU,YAAY,CAAC,YAAY,OAAO,QAAQ,KAAK;EACtE,CAAC,IACE,CACA;GACC,MAAM;GACN,OAAO,iBAAiB,OAAO,KAAK,EAAE,0BAA0B,KAAK,UAAU,QAAQ,KAAK;EAC7F,CACD,IACC,CAAC;EAEL,MAAM,YAAY,eAAe,SAAS,QAAQ,MAAM,KAAK;EAC7D,IAAI,QAAQ,SAAS,GACpB,OAAO,CAAC,SAAS;EAElB,OAAO,IAAI,KAAK,aAAa,eAAe;GAC3C,MAAM,QAAQ,cAAc,YAAY,KAAK;GAC7C,OAAO,UAAU,KAAA,IACd,OACA,OAAO,UAAU,WAChB,CAACL,QAAgB,UAAU,OAAO,KAAK,IACvC,CAAC,KAAK,OAAO,UAAU,KAAK;EACjC,CAAC,IACE,CACA;GACC,MAAM;GACN,OAAO,wBAAwB,QAAQ,KAAK;EAC7C,CACD,IACC,CAAC;CACL,CAAC;AACF;AAEA,MAAM,YAAY,QAA2C;CAC5D,MAAM;CACN,OAAO;AACR;;;;;AAMA,MAAM,YAAY;;;;;AAYlB,MAAM,SACL,MACA,IACA,WAC0B;CAC1B,IAAI,OAAO,WAAW,GACrB,OAAO;CAER,MAAM,OAAO,YAAY,KAAK,OAAO;CACrC,KAAK,OAAO,SAAS,OAAO;CAC5B,MAAM,OAAO,IAAI,IAAI,IAAI,KAAK,QAAQ,KAAK,IAAI,MAAM,CAAC,CAAC,IAAI,WAAW;EACrE,MAAM,CAAC,GAAGM,OAAa,EAAE,GAAG,GAAG,MAAM,IAAI;EACzC,OAAO,MAAM;CACd,EAAE;CACF,MAAM,QAAQ,KAAK,OAAO,QAAQ,aAAa,CAAC,KAAK,OAAO;CAC5D,KAAK,OAAO,UAAU;CACtB,OAAO,QACJ,CACA,GAAG,MACH;EACC,MAAM,CAAC;EACP,OAAO,2CAA2C,OAAO,SAAS,EAAE;CACrE,CACD,IACC;AACJ;;;;;;;;AASA,MAAM,mBACL,SACA,MACA,QACA,SAIAC,IAAe,KAAK,QAAQ,MAAM;CAKjC,MAAM,QAAQ,WACb,YAAY,QAAQ,MAAM,IAAI,QAAQ,QAAQ,MAAM,MAAM;CAC3D,MAAM,QAAQ,MAAkB,OAAiC;EAChE,MAAM,QAAQ,WACb,MAAM,QAAQ,MAAM,IAAI,MAAM;EAC/B,QAAQ,KAAK,MAAb;GACC,KAAK,WAAW;IACf,IAAI,CAAC,KAAK,CAAC,GACV,OAAO,CAAC;IAET,MAAM,SAAS,WAAW,SAAS,KAAK,OAAO,CAAC,OAAO,CAAC;IACxD,OAAO,KACN,OAAO,SAAS,IACb,SACA,cACA,SACA,KAAK,OACL,eAAe,SAAS,KAAK,KAAK,GAClC,KAAK,OAAO,QAAQ,KAAK,OAAO,MAChC,KAAK,OACL,CAAC,OAAO,CACT,CACH;GACD;GACA,KAAK,MAAM;IACV,IAAI,CAAC,KAAK,CAAC,GACV,OAAO,CAAC;IAET,MAAM,SAAS,WAAW,SAAS,KAAK,OAAO,CAAC,OAAO,CAAC;IACxD,IAAI,OAAO,SAAS,GACnB,OAAO,KAAK,MAAM;IAEnB,MAAM,SAAS,eAAe,SAAS,KAAK,KAAK;IACjD,IAAI,CAAC,YAAY,QAAQ,MAAM,KAAK,OAAO,QAAQ,OAAO,MAAM,GAC/D,OAAO,CAAC;IAET,OAAO,KACN,IAAI,QAAQ,KAAK,SAAS,SAAS,UAClC,cAAc,SAAS,KAAK,OAAO,QAAQ,OAAO,SAAS,CAC1D,UACA,KACD,CAAC,CACF,CACD;GACD;GACA,KAAK,UACJ,OAAO,KAAK,CAAC,IAAI,KAAK,WAAW,SAAS,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC;GACtE,KAAK,SACJ,OAAO,KAAK,YAAY,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC;GAClD,SACC,OAAO,CAAC;EACV;CACD;CACA,MAAM,MACL,MACA,SAC2C;EAC3C,IAAI,KAAK,IAAI,IAAI,GAChB,OAAO,EAAE,QAAQ,CAAC,CAAC;EAEpB,KAAK,IAAI,IAAI;EACb,QAAQ,KAAK,MAAb;GACC,KAAK;GACL,KAAK,MACJ,OAAO,EAAE,IACR,EAAE,QAAQ,KAAK,aAAa,OAAO,UAClC,EAAE,cAAc,GAAG,OAAOC,GAASC,MAAI,cAAc,KAAK,CAAC,CAAC,CAC7D,GACA,IAAI,OACL;GACD,KAAK,OACJ,OAAO,EAAE,cAAc,GAAG,KAAK,WAAWD,GAASC,MAAI,WAAW,CAAC,CAAC;GACrE,SACC,OAAO,EAAE,QAAQ,KAAK,MAAMA,IAAE,CAAC;EACjC;CACD;CACA,OAAO,GAAG,MAAM,MAAM;AACvB,CAAC;AAEF,MAAM,aACL,YACA,YAEA,QAAQ,SAAS,GAAG,KACpB,IAAI,KAAK,UAAU,WAAW,WAAW,QAAQ,SAAS,MAAM,CAAC;;;;;;;AAQlE,MAAa,cACZ,QACA,MACA,MACA,OAAa,SAAS,OAAO,MAAM,MACT;CAC1B,MAAM,QAAQ,WAAiC,MAAM,MAAMA,MAAI,MAAM;CACrE,MAAM,MAAM,KAAK,UAAU,SAAS,GAAG;CAKvC,MAAM,QAAQ,MAAM,KACnB,IAAI,IAAI,IAAI,OAAO,KAAK,YAAY,SAAS,SAAS,GAAG,CAAC,CAC3D;CACA,MAAM,QAAQ,MAAM,KAAK,QAAQ,KAAK,OAAO,SAAS,CAAC;CACvD,MAAM,QAAQ,IAAI,QAAQ,QAAQ,SACjC,OAAO,QAAQ,QAAQ,IAAI,OAAO,WAAW,IAAI,CAAC,CACnD;CACA,MAAM,QAAQ,MAAM,MAAM,KAAK,QAAQ,OAAO,OAAO,SAAS,CAAC,IAAI;CAGnE,MAAM,aAAa,IAAI,OAAO,QAAQ,eACrC,UAAU,YAAY,KAAK,OAAO,CACnC;CACA,MAAM,UAAmB;EACxB;EACA;EACA;EACA;EACA;EACA,OAAO,MACJ,IAAI,OAAO,QAAQ,eAAe,UAAU,YAAY,KAAK,OAAO,CAAC,IACrE;CACJ;CAEA,MAAM,UAAU,MAAM,KACrB,IAAI,IAAI,IAAI,QAAQ,QAAQ,eAAe,WAAW,OAAO,CAAC,CAC/D;CACA,MAAM,cAAc,IAAI,QAAQ,KAAK,UAAU,CAAC,IAAI,OAAO,UAC1D,KACC,MAAM,SAAS,GAAG,IACf,cAAc,MAAM,OAAO,OAAO,CAAC,UAAU,KAAK,CAAC,IACnD,WAAW,SAAS,OAAO,CAAC,UAAU,KAAK,CAAC,CAChD,CACD;CACA,MAAM,iBAAiB,KAAK,OAAO;CACnC,MAAM,aACL,KAAK,SAAS,KAAA,IACX,CAAC,IACD,CAACC,OAAa,KAAK,IAAI,IACtB,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,IAG1B,KAAK,KAAK,SAAS,iBACjB,KAAK,CACL;EACC,MAAM,CAAC,MAAM;EACb,OAAO,mBAAmB,OAAO,KAAK,KAAK,MAAM,EAAE,iCAAiC,OAAO,cAAc,EAAE;CAC5G,CACD,CAAC,IACA,gBAAgB,SAAS,KAAK,MAAMF,GAASC,MAAI,MAAM,mBAAG,IAAI,IAAI,CAAC;CAIzE,MAAM,OAAO,KAAK,KAAK,QAAQ,KAAK,OAAO,eAAe,CAAC,KAAK,KAAK;CACrE,KAAK,KAAK,UAAU;CACpB,OAAO;EACN,GAAG,IAAI,QACN,IAAI,OAAO,QAAQ,SAAS,CAAC,QAAQ,IAAI,OAAO,WAAW,IAAI,CAAC,IAC/D,SACA,KAAK,CACJ;GACC,MAAM,CAAC,WAAW;GAClB,OAAO,UACN,MACA,uCAAuC,KAAK,IAC5C,MACA,gBACM,KACP;EACD,CACD,CAAC,CACH;EACA,GAAG,IAAI,QACN,IAAI,OACH,KAAK,UACJ,WAAW,WAAW,OAAO,CAAC,QAAQ,SAAS,MAAM,CACvD,IACC,WACA,KAAK,CACJ;GACC,MAAM,CAAC,SAAS;GAChB,OAAO,UACN,MACA,eAAe,OAAO,wCACtB,QACA,iBAAiB,MAAM,KAAK,GAAG,WACzB,OACP;EACD,CACD,CAAC,CACH;EACA,GAAG;EACH,GAAG;EAGH,GAAI,OACD,CACA;GACC,MAAMH,OAAaG,IAAE;GACrB,OAAO,8BAA8B,OAAO,KAAK,OAAO,WAAW,EAAE,4GAA4G,OAAO,KAAK,OAAO,WAAW,EAAE;EAClN,CACD,IACC,CAAC;CACL;AACD;;AAgBA,MAAa,cACZ,QACA,SAC0B;CAU1B,MAAM,QAAiD,CAAC;CAkCxD,UA9BG,GAAG,WAAW,EAAE,MAAM,IAAA,QAAM,UAAU;EACxC,MAAM,SACL,UAEA,EAAE,IACD,EAAE,QAAQ,QAAQ,SAAS,MAAM,MAAM,QAAQ,CAAC,CAAC,SAC3C,KAAA,CACP;EACD,QAAQ,KAAK,MAAb;GACC,KAAK;IACJ,IAAI,QAAQ,KAAK,QAAQ,MAAM,UAAU;KACxC,MAAM,KAAK,CAAC,MAAMD,GAASC,MAAI,SAAS,KAAK,CAAC,CAAC;IAChD,CAAC;IACD,OAAO,EAAE,QAAQ,KAAA,CAAS;GAC3B,KAAK;GACL,KAAK;GACL,KAAK,WACJ,OAAO,MACN,IAAI,IAAI,KAAK,WAAW,OAAO,WAAW;IACzC,MAAM;IACN,IAAID,GAASC,MAAI,YAAY,KAAK;GACnC,EAAE,CACH;GACD,KAAK,WACJ,OAAO,MAAM,CACZ;IAAE,MAAM,KAAK;IAAK,IAAID,GAASC,MAAI,KAAK;GAAE,GAC1C;IAAE,MAAM,KAAK;IAAQ,IAAID,GAASC,MAAI,QAAQ;GAAE,CACjD,CAAC;EACH;CACD,CACM,CAAC,CAAC;EAAE,MAAM;EAAM,IAAI,KAAA;CAAU,CAAC;CACrC,MAAM,OAAO,SAAS,OAAO,MAAM;CACnC,OAAO,IAAI,QAAQ,QAAQ,CAAC,MAAM,WACjC,WAAW,QAAQ,MAAM,OAAO,IAAI,CACrC;AACD"}
|
|
1
|
+
{"version":3,"file":"bind.js","names":["Catalog.accepts","Catalog.fieldsOf","Catalog.lookup","Patterns.parse","Patterns.fitsCatalog","Catalog.anyKinds","Place.pathOf","Trampoline.run","Place.at","at","Nodes.isNode","Trampoline.adaptive"],"sources":["../../src/internal/bind.ts"],"sourcesContent":["/**\n * The binding of rules to a domain. It is the one check of a rule, for a rule\n * from the builder, from `Policy.rule`, and from JSON alike. It reads the\n * catalogs of the resources and of the principal, and it gives one issue for\n * each problem, with the path of the problem and, for a name with a mistake,\n * the name that it may mean.\n */\nimport * as Arr from \"effect/Array\"\nimport * as HashMap from \"effect/HashMap\"\nimport * as Option from \"effect/Option\"\nimport * as Result from \"effect/Result\"\nimport type * as Domain from \"../Domain.ts\"\nimport type * as Resource from \"../Resource.ts\"\nimport * as Catalog from \"./catalog.ts\"\nimport * as Limits from \"./limits.ts\"\nimport * as Nodes from \"./nodes.ts\"\nimport * as Patterns from \"./patterns.ts\"\nimport * as Place from \"./place.ts\"\nimport { withSuggestion } from \"./suggest.ts\"\nimport * as Trampoline from \"./trampoline.ts\"\n\n/** One problem of a rule or a policy, with its path. */\nexport interface Issue {\n\treadonly path: ReadonlyArray<PropertyKey>\n\treadonly issue: string\n}\n\n/** The data of a rule that the binding reads. */\nexport interface RuleData {\n\treadonly actions: ReadonlyArray<string>\n\treadonly resources: ReadonlyArray<string>\n\treadonly when?: unknown\n\treadonly fields?: ReadonlyArray<string>\n}\n\n/**\n * The memo of one binding, for all its rules. A policy may name one wrong\n * field or one pattern in very many places, so the work on each is done one\n * time. It goes away when the binding ends.\n */\ninterface Memo {\n\t/**\n\t * The suggestion for each name, by the place where it was looked for. The\n\t * names come from a policy, so the map is native, as `fits` is.\n\t */\n\treadonly suggestions: Map<string, string>\n\t/**\n\t * If a pattern matches a field of a kind, by the kind and the pattern. The\n\t * patterns come from a policy, so the map is native: the hash of a string\n\t * in Effect has no random seed, and a policy could hold very many\n\t * patterns with one hash. The engine hashes a string with a random seed.\n\t */\n\treadonly fits: Map<string, boolean>\n\t/** The count of the issues of conditions so far. It belongs to one binding. */\n\treadonly issues: { count: number; noted: boolean }\n\t/** The steps of the checks of patterns so far. It belongs to one binding. */\n\treadonly work: { steps: number; noted: boolean }\n\t/** The limits of the domain. */\n\treadonly limits: Limits.Limits\n}\n\nconst makeMemo = (limits: Limits.Limits): Memo => ({\n\tsuggestions: new Map(),\n\tfits: new Map(),\n\tissues: { count: 0, noted: false },\n\twork: { steps: 0, noted: false },\n\tlimits,\n})\n\n/**\n * The most names that one binding makes a suggestion for. A suggestion\n * compares the name with every name of a catalog, so a policy with very many\n * wrong names would take a long time.\n */\nconst maxSuggestions = 32\n\n/** Adds a suggestion to a message, with the memo of the binding. */\nconst suggestIn = (\n\tmemo: Memo,\n\tmessage: string,\n\tname: string,\n\tscope: string,\n\tnames: () => ReadonlyArray<string>,\n): string => {\n\tconst key = `${scope}\\u0000${name}`\n\tconst known = memo.suggestions.get(key)\n\tif (known !== undefined) {\n\t\treturn known\n\t}\n\tif (memo.suggestions.size >= maxSuggestions) {\n\t\treturn message\n\t}\n\tconst made = withSuggestion(message, name, names())\n\tmemo.suggestions.set(key, made)\n\treturn made\n}\n\n/** The kinds that a rule may run on, from the view of its checks. */\ninterface Context {\n\treadonly domain: Domain.Any\n\treadonly memo: Memo\n\t/** The rule is for every resource (`\"*\"`). */\n\treadonly all: boolean\n\t/** The resources that the rule names and that the domain has. */\n\treadonly named: ReadonlyArray<Resource.Descriptor>\n\t/** The named resources that have one of the actions of the rule. */\n\treadonly applicable: ReadonlyArray<Resource.Descriptor>\n\t/**\n\t * The resources whose paths a path test may read: every resource with one\n\t * of the actions, for a rule for every resource.\n\t */\n\treadonly paths: ReadonlyArray<Resource.Descriptor>\n}\n\ntype Operand = Nodes.Operand\n\n/**\n * Tests if a field may hold a value of the type of a fixed value. A test with\n * `null` is a test that the field is missing, so `null` always fits. A number\n * fits any field of numbers: a test of an integer field with `1.5` or\n * `Infinity` has a meaning, as `lt` shows.\n */\nconst fitsLiteral = (kinds: Catalog.Kinds, value: unknown): boolean =>\n\tvalue === null ||\n\t(typeof value === \"number\" && kinds.number !== \"none\") ||\n\tCatalog.accepts(kinds, value)\n\n/**\n * Tests if two fields may hold one scalar. A principal field that is `null`\n * or that holds an object is a missing value, and a test with a missing value\n * is false, so only scalars count.\n */\nconst meet = (left: Catalog.Kinds, right: Catalog.Kinds): boolean =>\n\t(left.string && right.string) ||\n\t(left.boolean && right.boolean) ||\n\t(left.number !== \"none\" && right.number !== \"none\") ||\n\tArr.some(left.literals, (value) => Catalog.accepts(right, value)) ||\n\tArr.some(right.literals, (value) => Catalog.accepts(left, value))\n\nconst hasStrings = (kinds: Catalog.Kinds): boolean =>\n\tkinds.string || Arr.some(kinds.literals, (value) => typeof value === \"string\")\n\nconst hasNumbers = (kinds: Catalog.Kinds): boolean =>\n\tkinds.number !== \"none\" ||\n\tArr.some(kinds.literals, (value) => typeof value === \"number\")\n\n/** Tests if a field may hold a value with an order: a number or a string. */\nconst isOrdered = (kinds: Catalog.Kinds): boolean =>\n\thasStrings(kinds) || hasNumbers(kinds)\n\n/** Tests if two fields may hold values of one ordered type. */\nconst orderedMeet = (left: Catalog.Kinds, right: Catalog.Kinds): boolean =>\n\t(hasStrings(left) && hasStrings(right)) ||\n\t(hasNumbers(left) && hasNumbers(right))\n\n/**\n * The texts of the fields of a catalog that have only names, for\n * suggestions, with the limits of the domain. The domain checked the size of\n * the list when it was made.\n */\nconst namesOf = (\n\tcatalog: Catalog.Catalog,\n\tlimits: Limits.Limits,\n): ReadonlyArray<string> =>\n\tArr.map(\n\t\tArr.filter(\n\t\t\tCatalog.fieldsOf(catalog, limits.fieldPathParts, limits.catalogFields),\n\t\t\t(field) =>\n\t\t\t\tArr.every(field.path, (segment) => typeof segment === \"string\"),\n\t\t),\n\t\t(field) => field.text,\n\t)\n\n/** The issue of a field that is not a field of every resource of the rule. */\nconst fieldIssue = (\n\tcontext: Context,\n\tfield: string,\n\tat: ReadonlyArray<PropertyKey>,\n): ReadonlyArray<Issue> => {\n\tconst parts = context.memo.limits.fieldPathParts\n\tif (field.split(\".\", parts + 1).length > parts) {\n\t\treturn [\n\t\t\t{\n\t\t\t\tpath: at,\n\t\t\t\tissue: `The field path \"${field}\" has more than ${String(parts)} parts. The most is ${String(parts)}.`,\n\t\t\t},\n\t\t]\n\t}\n\tconst message = `The field \"${field}\" is not a field of every resource of the rule`\n\tif (context.all || context.named.length === 0) {\n\t\treturn [{ path: at, issue: message }]\n\t}\n\tconst without = Arr.filter(\n\t\tcontext.named,\n\t\t(descriptor) =>\n\t\t\tCatalog.lookup(descriptor.catalog, field, parts) === undefined,\n\t)\n\treturn without.length === 0\n\t\t? []\n\t\t: [\n\t\t\t\t{\n\t\t\t\t\tpath: at,\n\t\t\t\t\tissue: suggestIn(context.memo, message, field, without[0]!.kind, () =>\n\t\t\t\t\t\tnamesOf(without[0]!.catalog, context.memo.limits),\n\t\t\t\t\t),\n\t\t\t\t},\n\t\t\t]\n}\n\n/**\n * The issues of a field that a condition reads: a field that is not a field\n * of every resource of the rule, or a field that is not plain data in one of\n * them. A read of a value that is not plain data, as a getter of a class, is\n * a defect in a decision. A residual may leave the read out, so the residual\n * would say \"yes\" where the decision fails. So the binding does not take\n * such a read: see `Catalog.Field.plain`.\n */\nconst readIssues = (\n\tcontext: Context,\n\tfield: string,\n\tat: ReadonlyArray<PropertyKey>,\n): ReadonlyArray<Issue> => {\n\tconst exists = fieldIssue(context, field, at)\n\tif (exists.length > 0) {\n\t\treturn exists\n\t}\n\treturn Arr.some(\n\t\tcontext.named,\n\t\t(descriptor) =>\n\t\t\t!(\n\t\t\t\tCatalog.lookup(\n\t\t\t\t\tdescriptor.catalog,\n\t\t\t\t\tfield,\n\t\t\t\t\tcontext.memo.limits.fieldPathParts,\n\t\t\t\t)?.plain ?? true\n\t\t\t),\n\t)\n\t\t? [\n\t\t\t\t{\n\t\t\t\t\tpath: at,\n\t\t\t\t\tissue: `A condition may not read the field \"${field}\". A value above it may be an object that does not have it as a data field, as a class with a getter, a date or a list. Use a predicate for it.`,\n\t\t\t\t},\n\t\t\t]\n\t\t: []\n}\n\n/**\n * The issues of a field pattern: a pattern that does not parse, or that\n * matches no field of the resources of the rule, as a pattern with a mistake\n * in a name does.\n */\nconst patternIssues = (\n\tmemo: Memo,\n\tknown: ReadonlyArray<Resource.Descriptor>,\n\tpattern: string,\n\tat: ReadonlyArray<PropertyKey>,\n): ReadonlyArray<Issue> =>\n\tResult.match(Patterns.parse(pattern, memo.limits.patternParts), {\n\t\tonFailure: (issue) => [{ path: at, issue }],\n\t\tonSuccess: (parsed) => {\n\t\t\t// A rule with no known resource has an issue for that already. The\n\t\t\t// pattern is checked on every kind, not only up to the first that it\n\t\t\t// fits: a decision on a kind does this check, so the limit of the\n\t\t\t// work below is a limit of the work of a decision too.\n\t\t\tconst fits =\n\t\t\t\tknown.length === 0 ||\n\t\t\t\tArr.reduce(known, false, (before, descriptor) => {\n\t\t\t\t\tconst key = `${descriptor.kind}\\u0000${pattern}`\n\t\t\t\t\treturn (\n\t\t\t\t\t\tOption.getOrElse(Option.fromUndefinedOr(memo.fits.get(key)), () => {\n\t\t\t\t\t\t\t// Each check of a pattern reads the steps of a catalog. The\n\t\t\t\t\t\t\t// work of one binding has a limit, so very many patterns stop\n\t\t\t\t\t\t\t// with one issue, and the policy does not load.\n\t\t\t\t\t\t\tmemo.work.steps +=\n\t\t\t\t\t\t\t\tMath.max(descriptor.catalog.edges, 1) *\n\t\t\t\t\t\t\t\tMath.max(parsed.length, 1)\n\t\t\t\t\t\t\tif (memo.work.steps > memo.limits.searchSteps) {\n\t\t\t\t\t\t\t\treturn true\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst found = Patterns.fitsCatalog(\n\t\t\t\t\t\t\t\tparsed,\n\t\t\t\t\t\t\t\tdescriptor.catalog,\n\t\t\t\t\t\t\t\tmemo.limits.fieldPathParts,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\tmemo.fits.set(key, found)\n\t\t\t\t\t\t\treturn found\n\t\t\t\t\t\t}) || before\n\t\t\t\t\t)\n\t\t\t\t})\n\t\t\t// Past the limit of the work, `ruleIssues` gives one issue for it.\n\t\t\treturn fits || memo.work.steps > memo.limits.searchSteps\n\t\t\t\t? []\n\t\t\t\t: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tpath: at,\n\t\t\t\t\t\t\tissue: `The field pattern \"${pattern}\" matches no field of the resources of the rule`,\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t},\n\t})\n\n/** The fields of the resources that a test of the rule may read. */\nconst fieldsAt = (\n\tcontext: Context,\n\tfield: string,\n): ReadonlyArray<Catalog.Field> =>\n\tArr.flatMap(context.applicable, (descriptor) => {\n\t\tconst found = Catalog.lookup(\n\t\t\tdescriptor.catalog,\n\t\t\tfield,\n\t\t\tcontext.memo.limits.fieldPathParts,\n\t\t)\n\t\treturn found === undefined ? [] : [found]\n\t})\n\n/** The field of the principal that an operand names, or the issue when there is none. */\nconst principalField = (\n\tcontext: Context,\n\tref: string,\n\tat: ReadonlyArray<PropertyKey>,\n): Catalog.Field | Issue => {\n\tconst found = Catalog.lookup(\n\t\tcontext.domain.principalCatalog,\n\t\tref,\n\t\tcontext.memo.limits.fieldPathParts,\n\t)\n\t// A read of the principal that is not plain data is a defect, as a read\n\t// of a resource is: see `readIssues`.\n\tif (found !== undefined && !found.plain) {\n\t\treturn {\n\t\t\tpath: at,\n\t\t\tissue: `A condition may not read the field \"${ref}\" of the principal. A value above it may be an object that does not have it as a data field, as a class with a getter, a date or a list. Use a predicate for it.`,\n\t\t}\n\t}\n\treturn found !== undefined\n\t\t? found\n\t\t: {\n\t\t\t\tpath: at,\n\t\t\t\tissue: suggestIn(\n\t\t\t\t\tcontext.memo,\n\t\t\t\t\t`The principal has no field \"${ref}\"`,\n\t\t\t\t\tref,\n\t\t\t\t\t\"~principal\",\n\t\t\t\t\t() => namesOf(context.domain.principalCatalog, context.memo.limits),\n\t\t\t\t),\n\t\t\t}\n}\n\nconst isIssue = (value: Catalog.Field | Issue): value is Issue =>\n\t\"issue\" in value\n\n/**\n * The fields that a test reads, with one field for each distinct kind of\n * value. A rule for very many kinds may have one field type in all of them,\n * so each value of the test is checked one time for each distinct kind, not\n * one time for each resource. The kinds are found by their text in a native\n * `Map`, which belongs to this call.\n */\nconst distinctFields = (\n\tcontext: Context,\n\tfield: string,\n): ReadonlyArray<Catalog.Field> =>\n\tArray.from(\n\t\tnew Map(\n\t\t\tArr.map(\n\t\t\t\tfieldsAt(context, field),\n\t\t\t\t(found) => [JSON.stringify(found.kinds), found] as const,\n\t\t\t),\n\t\t).values(),\n\t)\n\n/**\n * Counts the work of the checks of the values of a test, as the work of the\n * field patterns counts. Past the limit, the values are not checked, and\n * `ruleIssues` gives one issue for the work.\n */\nconst spendChecks = (memo: Memo, values: number, fields: number): boolean => {\n\tmemo.work.steps += values * Math.max(fields, 1)\n\treturn memo.work.steps <= memo.limits.searchSteps\n}\n\n/** The issues of one operand of a test of a field, with its distinct fields. */\nconst operandIssues = (\n\tcontext: Context,\n\tfield: string,\n\tfields: ReadonlyArray<Catalog.Field>,\n\tordered: boolean,\n\toperand: Operand,\n\tat: ReadonlyArray<PropertyKey>,\n): ReadonlyArray<Issue> => {\n\tif (operand._tag === \"Literal\") {\n\t\tconst value = operand.value\n\t\tif (ordered && typeof value !== \"string\" && typeof value !== \"number\") {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tpath: at,\n\t\t\t\t\tissue: `The field \"${field}\" has an ordered test with a value that has no order`,\n\t\t\t\t},\n\t\t\t]\n\t\t}\n\t\treturn [\n\t\t\t...(Arr.some(fields, (found) => !fitsLiteral(found.kinds, value))\n\t\t\t\t? [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tpath: at,\n\t\t\t\t\t\t\tissue: `The field \"${field}\" can not hold the value ${JSON.stringify(value)}`,\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t: []),\n\t\t\t...(ordered && Arr.some(fields, (found) => !isOrdered(found.kinds))\n\t\t\t\t? [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tpath: at,\n\t\t\t\t\t\t\tissue: `The field \"${field}\" has an ordered test with a value that has no order`,\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t: []),\n\t\t]\n\t}\n\tconst principal = principalField(context, operand.path, at)\n\tif (isIssue(principal)) {\n\t\treturn [principal]\n\t}\n\treturn [\n\t\t...(Arr.some(fields, (found) => !meet(found.kinds, principal.kinds))\n\t\t\t? [\n\t\t\t\t\t{\n\t\t\t\t\t\tpath: at,\n\t\t\t\t\t\tissue: `The field \"${field}\" can not hold a value of the principal field \"${operand.path}\"`,\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t: []),\n\t\t...(ordered &&\n\t\tArr.some(fields, (found) => !orderedMeet(found.kinds, principal.kinds))\n\t\t\t? [\n\t\t\t\t\t{\n\t\t\t\t\t\tpath: at,\n\t\t\t\t\t\tissue: `The field \"${field}\" has an ordered test with a value that has no order`,\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t: []),\n\t]\n}\n\n/**\n * Tests if the path of a resource may start with a prefix: it is as long as\n * the prefix, and each fixed string of the prefix is the string of the path\n * at its place, when the path has a string there.\n */\nconst mayStartWith = (\n\tdescriptor: Resource.Descriptor,\n\tstrings: ReadonlyArray<string | null>,\n): boolean =>\n\t(descriptor.path?.length ?? 0) >= strings.length &&\n\tArr.every(strings, (string, index) => {\n\t\tconst segment = descriptor.path?.[index]\n\t\treturn string === null || typeof segment !== \"string\" || segment === string\n\t})\n\n/** The kinds of the part of the path of a resource at a place. */\nconst pathPartKinds = (\n\tdescriptor: Resource.Descriptor,\n\tindex: number,\n): Catalog.Kinds | string | undefined => {\n\tconst segment = descriptor.path?.[index]\n\treturn segment === undefined || typeof segment === \"string\"\n\t\t? segment\n\t\t: (Catalog.lookup(descriptor.catalog, segment.field)?.kinds ??\n\t\t\t\tCatalog.anyKinds)\n}\n\n/** The issues of the prefix of a path test. */\nconst underIssues = (\n\tcontext: Context,\n\tprefix: ReadonlyArray<Operand>,\n\tat: ReadonlyArray<PropertyKey>,\n): ReadonlyArray<Issue> => {\n\tconst strings = Arr.map(prefix, (operand) =>\n\t\toperand._tag === \"Literal\" && typeof operand.value === \"string\"\n\t\t\t? operand.value\n\t\t\t: null,\n\t)\n\t// The test can match only a path that starts with the fixed strings of\n\t// the prefix, so the other resources are left out.\n\tconst candidates = Arr.filter(context.paths, (descriptor) =>\n\t\tmayStartWith(descriptor, strings),\n\t)\n\treturn Arr.flatMap(prefix, (operand, index): ReadonlyArray<Issue> => {\n\t\tconst place = [...at, \"prefix\", index]\n\t\tif (operand._tag === \"Literal\") {\n\t\t\treturn Arr.some(candidates, (descriptor) => {\n\t\t\t\tconst kinds = pathPartKinds(descriptor, index)\n\t\t\t\treturn typeof kinds === \"object\" && !fitsLiteral(kinds, operand.value)\n\t\t\t})\n\t\t\t\t? [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tpath: place,\n\t\t\t\t\t\t\tissue: `The path part ${String(index)} can not hold the value ${JSON.stringify(operand.value)}`,\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t: []\n\t\t}\n\t\tconst principal = principalField(context, operand.path, place)\n\t\tif (isIssue(principal)) {\n\t\t\treturn [principal]\n\t\t}\n\t\treturn Arr.some(candidates, (descriptor) => {\n\t\t\tconst kinds = pathPartKinds(descriptor, index)\n\t\t\treturn kinds === undefined\n\t\t\t\t? true\n\t\t\t\t: typeof kinds === \"string\"\n\t\t\t\t\t? !Catalog.accepts(principal.kinds, kinds)\n\t\t\t\t\t: !meet(kinds, principal.kinds)\n\t\t})\n\t\t\t? [\n\t\t\t\t\t{\n\t\t\t\t\t\tpath: place,\n\t\t\t\t\t\tissue: `The principal field \"${operand.path}\" can not hold a part of the path of the resource`,\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t: []\n\t})\n}\n\nconst notWhole = (at: ReadonlyArray<PropertyKey>): Issue => ({\n\tpath: at,\n\tissue: \"The condition is not a whole condition tree\",\n})\n\n/**\n * The most issues that the walk of the conditions of one binding gives. A\n * policy may have very many wrong tests, and each issue keeps its path.\n */\nconst maxIssues = 100\n\n/**\n * The place of a part of a policy, which a walk turns into a full path only\n * for an issue: see `Place`.\n */\ntype At = Place.Place | undefined\n\n/**\n * Puts the path of a node before the paths of its issues. A walk keeps no\n * path for a node with no issue, so a deep and wide condition costs little.\n */\nconst under = (\n\tmemo: Memo,\n\tat: At,\n\tissues: ReadonlyArray<Issue>,\n): ReadonlyArray<Issue> => {\n\tif (issues.length === 0) {\n\t\treturn issues\n\t}\n\tconst room = maxIssues - memo.issues.count\n\tmemo.issues.count += issues.length\n\tconst kept = Arr.map(Arr.take(issues, Math.max(room, 0)), (issue) => ({\n\t\tpath: [...Place.pathOf(at), ...issue.path],\n\t\tissue: issue.issue,\n\t}))\n\tconst first = memo.issues.count > maxIssues && !memo.issues.noted\n\tmemo.issues.noted ||= first\n\treturn first\n\t\t? [\n\t\t\t\t...kept,\n\t\t\t\t{\n\t\t\t\t\tpath: [],\n\t\t\t\t\tissue: `The policy has more problems. The first ${String(maxIssues)} are here.`,\n\t\t\t\t},\n\t\t\t]\n\t\t: kept\n}\n\n/**\n * The issues of a condition. The constructors of the nodes checked the shape\n * of each node, so this checks the names and the types against the domain. A\n * node that the condition uses in many places is checked one time. The nodes\n * it has seen are in a native `Set`, because it must find a node by the node\n * itself: `Equal` compares nodes by structure.\n */\nconst conditionIssues = (\n\tcontext: Context,\n\troot: Nodes.Node,\n\trootAt: At,\n\tseen: Set<Nodes.Node>,\n): ReadonlyArray<Issue> =>\n\t// The walk takes the steps of a trampoline, so a deep condition does not\n\t// fill the stack.\n\tTrampoline.run(root.depth, (R) => {\n\t\t// A test looks its field up in each resource of the rule. A rule for\n\t\t// very many kinds with very many tests is very much work, so it counts.\n\t\t// Past the limit, `ruleIssues` gives one issue for it, and the policy\n\t\t// does not load.\n\t\tconst room = (values: number) =>\n\t\t\tspendChecks(context.memo, 1 + values, context.named.length)\n\t\tconst leaf = (node: Nodes.Node, at: At): ReadonlyArray<Issue> => {\n\t\t\tconst here = (issues: ReadonlyArray<Issue>) =>\n\t\t\t\tunder(context.memo, at, issues)\n\t\t\tswitch (node._tag) {\n\t\t\t\tcase \"Compare\": {\n\t\t\t\t\tif (!room(1)) {\n\t\t\t\t\t\treturn []\n\t\t\t\t\t}\n\t\t\t\t\tconst exists = readIssues(context, node.field, [\"field\"])\n\t\t\t\t\treturn here(\n\t\t\t\t\t\texists.length > 0\n\t\t\t\t\t\t\t? exists\n\t\t\t\t\t\t\t: operandIssues(\n\t\t\t\t\t\t\t\t\tcontext,\n\t\t\t\t\t\t\t\t\tnode.field,\n\t\t\t\t\t\t\t\t\tdistinctFields(context, node.field),\n\t\t\t\t\t\t\t\t\tnode.op !== \"eq\" && node.op !== \"ne\",\n\t\t\t\t\t\t\t\t\tnode.value,\n\t\t\t\t\t\t\t\t\t[\"value\"],\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tcase \"In\": {\n\t\t\t\t\tif (!room(0)) {\n\t\t\t\t\t\treturn []\n\t\t\t\t\t}\n\t\t\t\t\tconst exists = readIssues(context, node.field, [\"field\"])\n\t\t\t\t\tif (exists.length > 0) {\n\t\t\t\t\t\treturn here(exists)\n\t\t\t\t\t}\n\t\t\t\t\tconst fields = distinctFields(context, node.field)\n\t\t\t\t\tif (!spendChecks(context.memo, node.values.length, fields.length)) {\n\t\t\t\t\t\treturn []\n\t\t\t\t\t}\n\t\t\t\t\treturn here(\n\t\t\t\t\t\tArr.flatMap(node.values, (operand, index) =>\n\t\t\t\t\t\t\toperandIssues(context, node.field, fields, false, operand, [\n\t\t\t\t\t\t\t\t\"values\",\n\t\t\t\t\t\t\t\tindex,\n\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tcase \"Exists\":\n\t\t\t\t\treturn room(0) ? here(readIssues(context, node.field, [\"field\"])) : []\n\t\t\t\tcase \"Under\":\n\t\t\t\t\t// The check reads the path of each resource of the rule for each\n\t\t\t\t\t// part of the prefix, so it counts that work first.\n\t\t\t\t\treturn spendChecks(\n\t\t\t\t\t\tcontext.memo,\n\t\t\t\t\t\t1 + node.prefix.length,\n\t\t\t\t\t\tcontext.paths.length,\n\t\t\t\t\t)\n\t\t\t\t\t\t? here(underIssues(context, node.prefix, []))\n\t\t\t\t\t\t: []\n\t\t\t\tdefault:\n\t\t\t\t\treturn []\n\t\t\t}\n\t\t}\n\t\tconst go = (\n\t\t\tnode: Nodes.Node,\n\t\t\tat: At,\n\t\t): Trampoline.Step<ReadonlyArray<Issue>> => {\n\t\t\tif (seen.has(node)) {\n\t\t\t\treturn R.succeed([])\n\t\t\t}\n\t\t\tseen.add(node)\n\t\t\tswitch (node._tag) {\n\t\t\t\tcase \"And\":\n\t\t\t\tcase \"Or\":\n\t\t\t\t\treturn R.map(\n\t\t\t\t\t\tR.forEach(node.conditions, (child, index) =>\n\t\t\t\t\t\t\tR.suspend(() => go(child, Place.at(at, \"conditions\", index))),\n\t\t\t\t\t\t),\n\t\t\t\t\t\tArr.flatten,\n\t\t\t\t\t)\n\t\t\t\tcase \"Not\":\n\t\t\t\t\treturn R.suspend(() => go(node.condition, Place.at(at, \"condition\")))\n\t\t\t\tdefault:\n\t\t\t\t\treturn R.succeed(leaf(node, at))\n\t\t\t}\n\t\t}\n\t\treturn go(root, rootAt)\n\t})\n\nconst hasAction = (\n\tdescriptor: Resource.Descriptor,\n\tactions: ReadonlyArray<string>,\n): boolean =>\n\tactions.includes(\"*\") ||\n\tArr.some(actions, (action) => descriptor.actions.includes(action))\n\n/**\n * Checks one rule against a domain. It gives one issue for each problem, up\n * to the most issues of the binding. `at` is the place of the rule, and its\n * full path is made only for an issue that the binding keeps: a rule with\n * very many problems deep in a tree does not make a full path for each one.\n */\nexport const ruleIssues = (\n\tdomain: Domain.Any,\n\trule: RuleData,\n\tat: At,\n\tmemo: Memo = makeMemo(domain.limits),\n): ReadonlyArray<Issue> => {\n\tconst here = (issues: ReadonlyArray<Issue>) => under(memo, at, issues)\n\tconst all = rule.resources.includes(\"*\")\n\t// Each kind is checked one time, also when the list names it again. The\n\t// names come from a policy, so a native `Set` finds the copies:\n\t// `Arr.dedupe` puts the names in groups by the hash of Effect, which has\n\t// no random seed, and very many names with one hash make one long group.\n\tconst names = Array.from(\n\t\tnew Set(Arr.filter(rule.resources, (kind) => kind !== \"*\")),\n\t)\n\tconst kinds = Array.from(HashMap.keys(domain.resources))\n\tconst named = Arr.flatMap(names, (kind) =>\n\t\tOption.toArray(HashMap.get(domain.resources, kind)),\n\t)\n\tconst known = all ? Array.from(HashMap.values(domain.resources)) : named\n\t// The rule never runs on a kind without one of its actions, so the types\n\t// of its tests are checked only on the kinds with one.\n\tconst applicable = Arr.filter(named, (descriptor) =>\n\t\thasAction(descriptor, rule.actions),\n\t)\n\tconst context: Context = {\n\t\tdomain,\n\t\tmemo,\n\t\tall,\n\t\tnamed,\n\t\tapplicable,\n\t\tpaths: all\n\t\t\t? Arr.filter(known, (descriptor) => hasAction(descriptor, rule.actions))\n\t\t\t: applicable,\n\t}\n\t// A native `Set` finds copies about ten times as fast as `Arr.dedupe`.\n\tconst actions = Array.from(\n\t\tnew Set(Arr.flatMap(known, (descriptor) => descriptor.actions)),\n\t)\n\tconst fieldIssues = Arr.flatMap(rule.fields ?? [], (field, index) =>\n\t\there(\n\t\t\tfield.includes(\"*\")\n\t\t\t\t? patternIssues(memo, known, field, [\"fields\", index])\n\t\t\t\t: fieldIssue(context, field, [\"fields\", index]),\n\t\t),\n\t)\n\tconst conditionNodes = memo.limits.conditionNodes\n\tconst whenIssues =\n\t\trule.when === undefined\n\t\t\t? []\n\t\t\t: !Nodes.isNode(rule.when)\n\t\t\t\t? here([notWhole([\"when\"])])\n\t\t\t\t: // The constructors of the nodes check the ceiling, so the binding\n\t\t\t\t\t// checks the limit of the domain.\n\t\t\t\t\trule.when.weight > conditionNodes\n\t\t\t\t\t? here([\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tpath: [\"when\"],\n\t\t\t\t\t\t\t\tissue: `A condition has ${String(rule.when.weight)} nodes and values. The most is ${String(conditionNodes)}.`,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t])\n\t\t\t\t\t: conditionIssues(context, rule.when, Place.at(at, \"when\"), new Set())\n\t// The work is known only after every check of the rule. Past the limit, a\n\t// check gives no issue, so this issue must come, or the policy would load\n\t// with checks left out.\n\tconst work = memo.work.steps > memo.limits.searchSteps && !memo.work.noted\n\tmemo.work.noted ||= work\n\treturn [\n\t\t...Arr.flatMap(\n\t\t\tArr.filter(names, (kind) => !HashMap.has(domain.resources, kind)),\n\t\t\t(kind: string) =>\n\t\t\t\there([\n\t\t\t\t\t{\n\t\t\t\t\t\tpath: [\"resources\"],\n\t\t\t\t\t\tissue: suggestIn(\n\t\t\t\t\t\t\tmemo,\n\t\t\t\t\t\t\t`There is no resource with the kind \"${kind}\"`,\n\t\t\t\t\t\t\tkind,\n\t\t\t\t\t\t\t\"~kinds\",\n\t\t\t\t\t\t\t() => kinds,\n\t\t\t\t\t\t),\n\t\t\t\t\t},\n\t\t\t\t]),\n\t\t),\n\t\t...Arr.flatMap(\n\t\t\tArr.filter(\n\t\t\t\trule.actions,\n\t\t\t\t(action) => action !== \"*\" && !actions.includes(action),\n\t\t\t),\n\t\t\t(action: string) =>\n\t\t\t\there([\n\t\t\t\t\t{\n\t\t\t\t\t\tpath: [\"actions\"],\n\t\t\t\t\t\tissue: suggestIn(\n\t\t\t\t\t\t\tmemo,\n\t\t\t\t\t\t\t`The action \"${action}\" is not an action of these resources`,\n\t\t\t\t\t\t\taction,\n\t\t\t\t\t\t\t`~actions\\u0000${names.join(\",\")}`,\n\t\t\t\t\t\t\t() => actions,\n\t\t\t\t\t\t),\n\t\t\t\t\t},\n\t\t\t\t]),\n\t\t),\n\t\t...fieldIssues,\n\t\t...whenIssues,\n\t\t// The issue of the work is not one of the most issues of the binding,\n\t\t// so a policy past the limit always says why.\n\t\t...(work\n\t\t\t? [\n\t\t\t\t\t{\n\t\t\t\t\t\tpath: Place.pathOf(at),\n\t\t\t\t\t\tissue: `The policy takes more than ${String(memo.limits.searchSteps)} steps to check: its field patterns, and the fields and values of its tests on each resource. The most is ${String(memo.limits.searchSteps)}.`,\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t: []),\n\t]\n}\n\n/** The JSON form of a policy tree, or a policy tree, as the binding reads it. */\ntype TreeNode =\n\t| { readonly _tag: \"Rules\"; readonly rules: ReadonlyArray<RuleData> }\n\t| {\n\t\t\treadonly _tag: \"All\" | \"Any\" | \"FirstOf\"\n\t\t\treadonly policies: ReadonlyArray<TreeNode>\n\t }\n\t| {\n\t\t\treadonly _tag: \"Ceiling\"\n\t\t\treadonly cap: TreeNode\n\t\t\treadonly policy: TreeNode\n\t }\n\n/** Checks each rule of a policy tree against a domain. */\nexport const treeIssues = (\n\tdomain: Domain.Any,\n\troot: TreeNode,\n): ReadonlyArray<Issue> => {\n\t// The rules come out of the tree first, and each one is checked after.\n\t// A check of a condition goes down its levels, so a check inside the walk\n\t// of the tree would go down the levels of both. The place of each rule\n\t// shares the path of its parent. The tree may be the JSON form, with no\n\t// depth that a node knows, so the walk is a walk of the trampoline for an\n\t// input of an unknown depth.\n\t// The rules go into one list, in order: a walk that joined the lists of\n\t// the children at each node would copy the list of a deep tree at each\n\t// level. The list belongs to this walk.\n\tconst found: Array<readonly [RuleData, Place.Place]> = []\n\tconst rulesOf = Trampoline.adaptive<\n\t\t{ readonly node: TreeNode; readonly at: At },\n\t\tundefined\n\t>((R, child) => ({ node, at }, level) => {\n\t\tconst below = (\n\t\t\tparts: ReadonlyArray<{ readonly node: TreeNode; readonly at: At }>,\n\t\t) =>\n\t\t\tR.map(\n\t\t\t\tR.forEach(parts, (part) => child(part, level + 1)),\n\t\t\t\t() => undefined,\n\t\t\t)\n\t\tswitch (node._tag) {\n\t\t\tcase \"Rules\":\n\t\t\t\tArr.forEach(node.rules, (rule, index) => {\n\t\t\t\t\tfound.push([rule, Place.at(at, \"rules\", index)])\n\t\t\t\t})\n\t\t\t\treturn R.succeed(undefined)\n\t\t\tcase \"All\":\n\t\t\tcase \"Any\":\n\t\t\tcase \"FirstOf\":\n\t\t\t\treturn below(\n\t\t\t\t\tArr.map(node.policies, (inner, index) => ({\n\t\t\t\t\t\tnode: inner,\n\t\t\t\t\t\tat: Place.at(at, \"policies\", index),\n\t\t\t\t\t})),\n\t\t\t\t)\n\t\t\tcase \"Ceiling\":\n\t\t\t\treturn below([\n\t\t\t\t\t{ node: node.cap, at: Place.at(at, \"cap\") },\n\t\t\t\t\t{ node: node.policy, at: Place.at(at, \"policy\") },\n\t\t\t\t])\n\t\t}\n\t})\n\trulesOf({ node: root, at: undefined })\n\tconst memo = makeMemo(domain.limits)\n\treturn Arr.flatMap(found, ([rule, place]) =>\n\t\truleIssues(domain, rule, place, memo),\n\t)\n}\n"],"mappings":"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"}
|
package/dist/internal/json.js
CHANGED
|
@@ -46,15 +46,15 @@ const read = (input, name, at$5) => {
|
|
|
46
46
|
throw problemAt(at(at$5, name), `The field "${name}" is not plain data`);
|
|
47
47
|
}
|
|
48
48
|
};
|
|
49
|
-
const readString = (input, name, at$
|
|
50
|
-
const value = read(input, name, at$
|
|
51
|
-
if (typeof value !== "string") throw problemAt(at(at$
|
|
49
|
+
const readString = (input, name, at$1) => {
|
|
50
|
+
const value = read(input, name, at$1);
|
|
51
|
+
if (typeof value !== "string") throw problemAt(at(at$1, name), `The field "${name}" must be a string`);
|
|
52
52
|
return value;
|
|
53
53
|
};
|
|
54
54
|
/** Reads the items of an array. The place of an item is `at`, `name`, index. */
|
|
55
|
-
const readList = (input, name, at$
|
|
56
|
-
const value = read(input, name, at$
|
|
57
|
-
const place = at(at$
|
|
55
|
+
const readList = (input, name, at$2, budget) => {
|
|
56
|
+
const value = read(input, name, at$2);
|
|
57
|
+
const place = at(at$2, name);
|
|
58
58
|
if (!Array.isArray(value)) throw problemAt(place, `The field "${name}" must be an array`);
|
|
59
59
|
const list = value;
|
|
60
60
|
spend(budget, list.length, place);
|
|
@@ -71,17 +71,17 @@ const readPath = (input, name, at$7, budget) => {
|
|
|
71
71
|
return path;
|
|
72
72
|
};
|
|
73
73
|
const isJsonScalar = (value) => isScalar(value) && (typeof value !== "number" || Number.isFinite(value));
|
|
74
|
-
const decodeOperand = (input, at$
|
|
75
|
-
const tag = read(input, "_tag", at$
|
|
74
|
+
const decodeOperand = (input, at$3, budget) => {
|
|
75
|
+
const tag = read(input, "_tag", at$3);
|
|
76
76
|
if (tag === "Literal") {
|
|
77
77
|
const own = typeof input === "object" && input !== null ? Object.getOwnPropertyDescriptor(input, "value") : void 0;
|
|
78
|
-
if (own === void 0 || "value" in own && own.value === void 0) throw problemAt(at(at$
|
|
79
|
-
const value = read(input, "value", at$
|
|
80
|
-
if (!isJsonScalar(value)) throw problemAt(at(at$
|
|
78
|
+
if (own === void 0 || "value" in own && own.value === void 0) throw problemAt(at(at$3, "value"), "A fixed value is missing");
|
|
79
|
+
const value = read(input, "value", at$3);
|
|
80
|
+
if (!isJsonScalar(value)) throw problemAt(at(at$3, "value"), "A fixed value must be a string, a finite number, a boolean or null");
|
|
81
81
|
return literal(value);
|
|
82
82
|
}
|
|
83
|
-
if (tag === "Principal") return principalRef(readPath(input, "path", at$
|
|
84
|
-
throw problemAt(at(at$
|
|
83
|
+
if (tag === "Principal") return principalRef(readPath(input, "path", at$3, budget));
|
|
84
|
+
throw problemAt(at(at$3, "_tag"), "An operand must have the tag \"Literal\" or \"Principal\"");
|
|
85
85
|
};
|
|
86
86
|
const decodeOperands = (input, name, at$6, budget) => Arr.map(readList(input, name, at$6, budget), (operand, index) => decodeOperand(operand, at(at$6, name, index), budget));
|
|
87
87
|
/**
|
|
@@ -92,53 +92,61 @@ const decodeOperands = (input, name, at$6, budget) => Arr.map(readList(input, na
|
|
|
92
92
|
const decoder = (budget) => {
|
|
93
93
|
const count = { nodes: 0 };
|
|
94
94
|
return adaptive((R, child) => (part, depth) => {
|
|
95
|
-
const { input, at: at$
|
|
96
|
-
if (typeof input !== "object" || input === null) throw problemAt(at$
|
|
97
|
-
spend(budget, 1, at$
|
|
98
|
-
|
|
99
|
-
|
|
95
|
+
const { input, at: at$4 } = part;
|
|
96
|
+
if (typeof input !== "object" || input === null) throw problemAt(at$4, "A condition node must be an object");
|
|
97
|
+
spend(budget, 1, at$4);
|
|
98
|
+
const counted = (more) => {
|
|
99
|
+
count.nodes += more;
|
|
100
|
+
if (count.nodes > budget.limits.conditionNodes) throw problemAt(at$4, `A condition has more than ${String(budget.limits.conditionNodes)} nodes and values. The most is ${String(budget.limits.conditionNodes)}.`);
|
|
101
|
+
};
|
|
102
|
+
counted(1);
|
|
103
|
+
const operands = (name) => {
|
|
104
|
+
const list = read(input, name, at$4);
|
|
105
|
+
if (Array.isArray(list)) counted(list.length);
|
|
106
|
+
return decodeOperands(input, name, at$4, budget);
|
|
107
|
+
};
|
|
100
108
|
const make = (build) => {
|
|
101
109
|
const node = (() => {
|
|
102
110
|
try {
|
|
103
111
|
return build();
|
|
104
112
|
} catch (error) {
|
|
105
|
-
if (error instanceof LimitExceeded) throw problemAt(at$
|
|
113
|
+
if (error instanceof LimitExceeded) throw problemAt(at$4, error.message);
|
|
106
114
|
throw error;
|
|
107
115
|
}
|
|
108
116
|
})();
|
|
109
117
|
const most = budget.limits.conditionNodes;
|
|
110
|
-
if (node.weight > most) throw problemAt(at$
|
|
118
|
+
if (node.weight > most) throw problemAt(at$4, `A condition has ${String(node.weight)} nodes and values. The most is ${String(most)}.`);
|
|
111
119
|
return node;
|
|
112
120
|
};
|
|
113
|
-
const children = (name) => R.forEach(readList(input, name, at$
|
|
121
|
+
const children = (name) => R.forEach(readList(input, name, at$4, budget), (item, index) => child({
|
|
114
122
|
input: item,
|
|
115
|
-
at: at(at$
|
|
123
|
+
at: at(at$4, name, index)
|
|
116
124
|
}, depth + 1));
|
|
117
|
-
switch (read(input, "_tag", at$
|
|
125
|
+
switch (read(input, "_tag", at$4)) {
|
|
118
126
|
case "True": return R.succeed(always);
|
|
119
127
|
case "False": return R.succeed(never);
|
|
120
128
|
case "Compare": {
|
|
121
|
-
const op = readString(input, "op", at$
|
|
122
|
-
if (!isCompareOperator(op)) throw problemAt(at(at$
|
|
129
|
+
const op = readString(input, "op", at$4);
|
|
130
|
+
if (!isCompareOperator(op)) throw problemAt(at(at$4, "op"), `"${op}" is not a compare operator`);
|
|
123
131
|
return R.succeed(make(() => new Compare({
|
|
124
132
|
op,
|
|
125
|
-
field: readPath(input, "field", at$
|
|
126
|
-
value: decodeOperand(read(input, "value", at$
|
|
133
|
+
field: readPath(input, "field", at$4, budget),
|
|
134
|
+
value: decodeOperand(read(input, "value", at$4), at(at$4, "value"), budget)
|
|
127
135
|
})));
|
|
128
136
|
}
|
|
129
137
|
case "In": return R.succeed(make(() => new In({
|
|
130
|
-
field: readPath(input, "field", at$
|
|
131
|
-
values:
|
|
138
|
+
field: readPath(input, "field", at$4, budget),
|
|
139
|
+
values: operands("values")
|
|
132
140
|
})));
|
|
133
|
-
case "Exists": return R.succeed(make(() => new Exists({ field: readPath(input, "field", at$
|
|
134
|
-
case "Under": return R.succeed(make(() => new Under({ prefix:
|
|
141
|
+
case "Exists": return R.succeed(make(() => new Exists({ field: readPath(input, "field", at$4, budget) })));
|
|
142
|
+
case "Under": return R.succeed(make(() => new Under({ prefix: operands("prefix") })));
|
|
135
143
|
case "And": return R.map(children("conditions"), (conditions) => make(() => new And(conditions)));
|
|
136
144
|
case "Or": return R.map(children("conditions"), (conditions) => make(() => new Or(conditions)));
|
|
137
145
|
case "Not": return R.map(child({
|
|
138
|
-
input: read(input, "condition", at$
|
|
139
|
-
at: at(at$
|
|
146
|
+
input: read(input, "condition", at$4),
|
|
147
|
+
at: at(at$4, "condition")
|
|
140
148
|
}, depth + 1), (condition) => make(() => new Not(condition)));
|
|
141
|
-
default: throw problemAt(at(at$
|
|
149
|
+
default: throw problemAt(at(at$4, "_tag"), "The node has no known tag");
|
|
142
150
|
}
|
|
143
151
|
});
|
|
144
152
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"json.js","names":["Nodes.toJson","Place.pathOf","Place.at","at","Nodes.isScalar","Nodes.literal","Nodes.principalRef","Trampoline.adaptive","Limits.LimitExceeded","Nodes.always","Nodes.never","Nodes.isCompareOperator","Nodes.Compare","Nodes.In","Nodes.Exists","Nodes.Under","Nodes.And","Nodes.Or","Nodes.Not","Limits.defaults"],"sources":["../../src/internal/json.ts"],"sourcesContent":["/**\n * The JSON form of a condition. The decoder is a walk of the trampoline, so a\n * deep input does not fill the stack. It reads at most `jsonNodes` objects\n * and array items, with a shared object counted for each place, so no input\n * can take a very long time. It makes\n * each node with its constructor, and it reads only the own data fields of\n * the input, as a condition reads a value.\n */\nimport * as Arr from \"effect/Array\"\nimport * as Data from \"effect/Data\"\nimport * as Result from \"effect/Result\"\nimport type * as SchemaIssue from \"effect/SchemaIssue\"\nimport * as Limits from \"./limits.ts\"\nimport * as Nodes from \"./nodes.ts\"\nimport * as Place from \"./place.ts\"\nimport { readPart } from \"./read.ts\"\nimport * as Trampoline from \"./trampoline.ts\"\n\ntype Node = Nodes.Node\n\n/** Gives the JSON form of a condition with no predicate. See `Nodes.toJson`. */\nexport const toJson = (node: Node): unknown => Nodes.toJson(node)\n\n/** A problem of an input, with the path of the problem. */\nexport class Problem extends Data.TaggedError(\"Problem\")<{\n\treadonly path: ReadonlyArray<PropertyKey>\n\treadonly message: string\n\t/** The issue of a Schema that the decode used, as for `meta`. */\n\treadonly issue?: SchemaIssue.Issue | undefined\n}> {}\n\n/**\n * The count of the objects and items that one decode has read, with the\n * limits of the decode. It belongs to one decode.\n */\nexport interface Budget {\n\tused: number\n\treadonly limits: Limits.Limits\n}\n\nexport const problem = (path: ReadonlyArray<PropertyKey>, message: string) =>\n\tnew Problem({ path, message })\n\n/**\n * The problem at a place of the input. A decoder keeps the place of each\n * part, which shares the path of its parent, and makes the full path only for\n * a problem: see `Place`.\n */\nexport const problemAt = (at: Place.Place | undefined, message: string) =>\n\tproblem(Place.pathOf(at), message)\n\n/** Counts `count` more objects or items, and stops past the limit. */\nexport const spend = (\n\tbudget: Budget,\n\tcount: number,\n\tat: Place.Place | undefined,\n): void => {\n\tbudget.used += count\n\tconst most = budget.limits.jsonNodes\n\tif (budget.used > most) {\n\t\tthrow problemAt(\n\t\t\tat,\n\t\t\t`The input has more than ${String(most)} objects and items. The most is ${String(most)}.`,\n\t\t)\n\t}\n}\n\n/**\n * Reads a field of the input at the place `at`. A field that is not plain\n * data is a problem.\n */\nexport const read = (\n\tinput: unknown,\n\tname: string,\n\tat: Place.Place | undefined,\n): unknown => {\n\ttry {\n\t\treturn readPart(input, name)\n\t} catch {\n\t\tthrow problemAt(Place.at(at, name), `The field \"${name}\" is not plain data`)\n\t}\n}\n\nexport const readString = (\n\tinput: unknown,\n\tname: string,\n\tat: Place.Place | undefined,\n): string => {\n\tconst value = read(input, name, at)\n\tif (typeof value !== \"string\") {\n\t\tthrow problemAt(Place.at(at, name), `The field \"${name}\" must be a string`)\n\t}\n\treturn value\n}\n\n/** Reads the items of an array. The place of an item is `at`, `name`, index. */\nexport const readList = (\n\tinput: unknown,\n\tname: string,\n\tat: Place.Place | undefined,\n\tbudget: Budget,\n): ReadonlyArray<unknown> => {\n\tconst value = read(input, name, at)\n\tconst place = Place.at(at, name)\n\tif (!Array.isArray(value)) {\n\t\tthrow problemAt(place, `The field \"${name}\" must be an array`)\n\t}\n\tconst list: ReadonlyArray<unknown> = value\n\tspend(budget, list.length, place)\n\t// `Array.makeBy` of Effect gives at least one item, so it can not make an\n\t// empty list. An array-like object with a length makes one with no read\n\t// of the items before the read rule.\n\treturn Array.from({ length: list.length }, (_, index) =>\n\t\tread(list, String(index), place),\n\t)\n}\n\n/**\n * Reads a field path, and stops one with more parts than the limit of the\n * decode. A node checks the ceiling only, since it does not know its domain.\n */\nconst readPath = (\n\tinput: unknown,\n\tname: string,\n\tat: Place.Place | undefined,\n\tbudget: Budget,\n): string => {\n\tconst path = readString(input, name, at)\n\tconst most = budget.limits.fieldPathParts\n\tconst parts = path.split(\".\", most + 1).length\n\tif (parts > most) {\n\t\tthrow problemAt(\n\t\t\tPlace.at(at, name),\n\t\t\t`A field path has more than ${String(most)} parts. The most is ${String(most)}.`,\n\t\t)\n\t}\n\treturn path\n}\n\nconst isJsonScalar = (value: unknown): value is Nodes.Scalar =>\n\tNodes.isScalar(value) && (typeof value !== \"number\" || Number.isFinite(value))\n\nconst decodeOperand = (\n\tinput: unknown,\n\tat: Place.Place | undefined,\n\tbudget: Budget,\n): Nodes.Operand => {\n\tconst tag = read(input, \"_tag\", at)\n\tif (tag === \"Literal\") {\n\t\t// A missing value is not `null`: a test with `null` has its own\n\t\t// meaning, and a lost value must not become one.\n\t\tconst own =\n\t\t\ttypeof input === \"object\" && input !== null\n\t\t\t\t? Object.getOwnPropertyDescriptor(input, \"value\")\n\t\t\t\t: undefined\n\t\tif (own === undefined || (\"value\" in own && own.value === undefined)) {\n\t\t\tthrow problemAt(Place.at(at, \"value\"), \"A fixed value is missing\")\n\t\t}\n\t\tconst value = read(input, \"value\", at)\n\t\tif (!isJsonScalar(value)) {\n\t\t\tthrow problemAt(\n\t\t\t\tPlace.at(at, \"value\"),\n\t\t\t\t\"A fixed value must be a string, a finite number, a boolean or null\",\n\t\t\t)\n\t\t}\n\t\treturn Nodes.literal(value)\n\t}\n\tif (tag === \"Principal\") {\n\t\treturn Nodes.principalRef(readPath(input, \"path\", at, budget))\n\t}\n\tthrow problemAt(\n\t\tPlace.at(at, \"_tag\"),\n\t\t'An operand must have the tag \"Literal\" or \"Principal\"',\n\t)\n}\n\nconst decodeOperands = (\n\tinput: unknown,\n\tname: string,\n\tat: Place.Place | undefined,\n\tbudget: Budget,\n): ReadonlyArray<Nodes.Operand> =>\n\tArr.map(readList(input, name, at, budget), (operand, index) =>\n\t\tdecodeOperand(operand, Place.at(at, name, index), budget),\n\t)\n\n/** A part of the input to decode, with its place. */\ninterface Part {\n\treadonly input: unknown\n\treadonly at: Place.Place | undefined\n}\n\n/**\n * The decoder of a condition, with the budget of one decode. It is a walk of\n * the trampoline for an input of unknown depth, so a deep input does not fill\n * the stack.\n */\nconst decoder = (budget: Budget): ((part: Part) => Node) => {\n\t// The nodes of this condition that the decoder has read. A condition with\n\t// more nodes than `conditionNodes` does not load, so the decoder stops at\n\t// the first node past it, and it does not read the rest of a very large\n\t// input. The count belongs to the decode of one condition.\n\tconst count = { nodes: 0 }\n\treturn Trampoline.adaptive<Part, Node>((R, child) => (part, depth) => {\n\t\tconst { input, at } = part\n\t\tif (typeof input !== \"object\" || input === null) {\n\t\t\tthrow problemAt(at, \"A condition node must be an object\")\n\t\t}\n\t\tspend(budget, 1, at)\n\t\tcount.nodes += 1\n\t\tif (count.nodes > budget.limits.conditionNodes) {\n\t\t\tthrow problemAt(\n\t\t\t\tat,\n\t\t\t\t`A condition has more than ${String(budget.limits.conditionNodes)} nodes and values. The most is ${String(budget.limits.conditionNodes)}.`,\n\t\t\t)\n\t\t}\n\t\t// A node past a limit, such as a field path with too many parts, is a\n\t\t// problem at its place. The constructor checks the ceilings, and the\n\t\t// decoder checks the limits of the decode.\n\t\tconst make = (build: () => Node): Node => {\n\t\t\tconst node = (() => {\n\t\t\t\ttry {\n\t\t\t\t\treturn build()\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (error instanceof Limits.LimitExceeded) {\n\t\t\t\t\t\tthrow problemAt(at, error.message)\n\t\t\t\t\t}\n\t\t\t\t\tthrow error\n\t\t\t\t}\n\t\t\t})()\n\t\t\tconst most = budget.limits.conditionNodes\n\t\t\tif (node.weight > most) {\n\t\t\t\tthrow problemAt(\n\t\t\t\t\tat,\n\t\t\t\t\t`A condition has ${String(node.weight)} nodes and values. The most is ${String(most)}.`,\n\t\t\t\t)\n\t\t\t}\n\t\t\treturn node\n\t\t}\n\t\tconst children = (name: string) =>\n\t\t\tR.forEach(readList(input, name, at, budget), (item, index) =>\n\t\t\t\tchild({ input: item, at: Place.at(at, name, index) }, depth + 1),\n\t\t\t)\n\t\tconst tag = read(input, \"_tag\", at)\n\t\tswitch (tag) {\n\t\t\tcase \"True\":\n\t\t\t\treturn R.succeed(Nodes.always)\n\t\t\tcase \"False\":\n\t\t\t\treturn R.succeed(Nodes.never)\n\t\t\tcase \"Compare\": {\n\t\t\t\tconst op = readString(input, \"op\", at)\n\t\t\t\tif (!Nodes.isCompareOperator(op)) {\n\t\t\t\t\tthrow problemAt(\n\t\t\t\t\t\tPlace.at(at, \"op\"),\n\t\t\t\t\t\t`\"${op}\" is not a compare operator`,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\treturn R.succeed(\n\t\t\t\t\tmake(\n\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\tnew Nodes.Compare({\n\t\t\t\t\t\t\t\top,\n\t\t\t\t\t\t\t\tfield: readPath(input, \"field\", at, budget),\n\t\t\t\t\t\t\t\tvalue: decodeOperand(\n\t\t\t\t\t\t\t\t\tread(input, \"value\", at),\n\t\t\t\t\t\t\t\t\tPlace.at(at, \"value\"),\n\t\t\t\t\t\t\t\t\tbudget,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\t\t\tcase \"In\":\n\t\t\t\treturn R.succeed(\n\t\t\t\t\tmake(\n\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\tnew Nodes.In({\n\t\t\t\t\t\t\t\tfield: readPath(input, \"field\", at, budget),\n\t\t\t\t\t\t\t\tvalues: decodeOperands(input, \"values\", at, budget),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\tcase \"Exists\":\n\t\t\t\treturn R.succeed(\n\t\t\t\t\tmake(\n\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\tnew Nodes.Exists({ field: readPath(input, \"field\", at, budget) }),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\tcase \"Under\":\n\t\t\t\treturn R.succeed(\n\t\t\t\t\tmake(\n\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\tnew Nodes.Under({\n\t\t\t\t\t\t\t\tprefix: decodeOperands(input, \"prefix\", at, budget),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\tcase \"And\":\n\t\t\t\treturn R.map(children(\"conditions\"), (conditions) =>\n\t\t\t\t\tmake(() => new Nodes.And(conditions)),\n\t\t\t\t)\n\t\t\tcase \"Or\":\n\t\t\t\treturn R.map(children(\"conditions\"), (conditions) =>\n\t\t\t\t\tmake(() => new Nodes.Or(conditions)),\n\t\t\t\t)\n\t\t\tcase \"Not\":\n\t\t\t\treturn R.map(\n\t\t\t\t\tchild(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tinput: read(input, \"condition\", at),\n\t\t\t\t\t\t\tat: Place.at(at, \"condition\"),\n\t\t\t\t\t\t},\n\t\t\t\t\t\tdepth + 1,\n\t\t\t\t\t),\n\t\t\t\t\t(condition) => make(() => new Nodes.Not(condition)),\n\t\t\t\t)\n\t\t\tdefault:\n\t\t\t\tthrow problemAt(Place.at(at, \"_tag\"), \"The node has no known tag\")\n\t\t}\n\t})\n}\n\n/**\n * Decodes the JSON form of a condition at a path, with the budget of a larger\n * decode. It throws a {@link Problem}.\n */\nexport const decodeAt = (\n\tinput: unknown,\n\tat: Place.Place | undefined,\n\tbudget: Budget,\n): Node => decoder(budget)({ input, at })\n\n/** Decodes the JSON form of a condition, with the default limits. */\nexport const decode = (input: unknown): Result.Result<Node, Problem> => {\n\ttry {\n\t\treturn Result.succeed(\n\t\t\tdecoder({ used: 0, limits: Limits.defaults })({ input, at: undefined }),\n\t\t)\n\t} catch (error) {\n\t\tif (error instanceof Problem) {\n\t\t\treturn Result.fail(error)\n\t\t}\n\t\tthrow error\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"json.js","names":["Nodes.toJson","Place.pathOf","Place.at","at","Nodes.isScalar","Nodes.literal","Nodes.principalRef","Trampoline.adaptive","Limits.LimitExceeded","Nodes.always","Nodes.never","Nodes.isCompareOperator","Nodes.Compare","Nodes.In","Nodes.Exists","Nodes.Under","Nodes.And","Nodes.Or","Nodes.Not","Limits.defaults"],"sources":["../../src/internal/json.ts"],"sourcesContent":["/**\n * The JSON form of a condition. The decoder is a walk of the trampoline, so a\n * deep input does not fill the stack. It reads at most `jsonNodes` objects\n * and array items, with a shared object counted for each place, so no input\n * can take a very long time. It makes\n * each node with its constructor, and it reads only the own data fields of\n * the input, as a condition reads a value.\n */\nimport * as Arr from \"effect/Array\"\nimport * as Data from \"effect/Data\"\nimport * as Result from \"effect/Result\"\nimport type * as SchemaIssue from \"effect/SchemaIssue\"\nimport * as Limits from \"./limits.ts\"\nimport * as Nodes from \"./nodes.ts\"\nimport * as Place from \"./place.ts\"\nimport { readPart } from \"./read.ts\"\nimport * as Trampoline from \"./trampoline.ts\"\n\ntype Node = Nodes.Node\n\n/** Gives the JSON form of a condition with no predicate. See `Nodes.toJson`. */\nexport const toJson = (node: Node): unknown => Nodes.toJson(node)\n\n/** A problem of an input, with the path of the problem. */\nexport class Problem extends Data.TaggedError(\"Problem\")<{\n\treadonly path: ReadonlyArray<PropertyKey>\n\treadonly message: string\n\t/** The issue of a Schema that the decode used, as for `meta`. */\n\treadonly issue?: SchemaIssue.Issue | undefined\n}> {}\n\n/**\n * The count of the objects and items that one decode has read, with the\n * limits of the decode. It belongs to one decode.\n */\nexport interface Budget {\n\tused: number\n\treadonly limits: Limits.Limits\n}\n\nexport const problem = (path: ReadonlyArray<PropertyKey>, message: string) =>\n\tnew Problem({ path, message })\n\n/**\n * The problem at a place of the input. A decoder keeps the place of each\n * part, which shares the path of its parent, and makes the full path only for\n * a problem: see `Place`.\n */\nexport const problemAt = (at: Place.Place | undefined, message: string) =>\n\tproblem(Place.pathOf(at), message)\n\n/** Counts `count` more objects or items, and stops past the limit. */\nexport const spend = (\n\tbudget: Budget,\n\tcount: number,\n\tat: Place.Place | undefined,\n): void => {\n\tbudget.used += count\n\tconst most = budget.limits.jsonNodes\n\tif (budget.used > most) {\n\t\tthrow problemAt(\n\t\t\tat,\n\t\t\t`The input has more than ${String(most)} objects and items. The most is ${String(most)}.`,\n\t\t)\n\t}\n}\n\n/**\n * Reads a field of the input at the place `at`. A field that is not plain\n * data is a problem.\n */\nexport const read = (\n\tinput: unknown,\n\tname: string,\n\tat: Place.Place | undefined,\n): unknown => {\n\ttry {\n\t\treturn readPart(input, name)\n\t} catch {\n\t\tthrow problemAt(Place.at(at, name), `The field \"${name}\" is not plain data`)\n\t}\n}\n\nexport const readString = (\n\tinput: unknown,\n\tname: string,\n\tat: Place.Place | undefined,\n): string => {\n\tconst value = read(input, name, at)\n\tif (typeof value !== \"string\") {\n\t\tthrow problemAt(Place.at(at, name), `The field \"${name}\" must be a string`)\n\t}\n\treturn value\n}\n\n/** Reads the items of an array. The place of an item is `at`, `name`, index. */\nexport const readList = (\n\tinput: unknown,\n\tname: string,\n\tat: Place.Place | undefined,\n\tbudget: Budget,\n): ReadonlyArray<unknown> => {\n\tconst value = read(input, name, at)\n\tconst place = Place.at(at, name)\n\tif (!Array.isArray(value)) {\n\t\tthrow problemAt(place, `The field \"${name}\" must be an array`)\n\t}\n\tconst list: ReadonlyArray<unknown> = value\n\tspend(budget, list.length, place)\n\t// `Array.makeBy` of Effect gives at least one item, so it can not make an\n\t// empty list. An array-like object with a length makes one with no read\n\t// of the items before the read rule.\n\treturn Array.from({ length: list.length }, (_, index) =>\n\t\tread(list, String(index), place),\n\t)\n}\n\n/**\n * Reads a field path, and stops one with more parts than the limit of the\n * decode. A node checks the ceiling only, since it does not know its domain.\n */\nconst readPath = (\n\tinput: unknown,\n\tname: string,\n\tat: Place.Place | undefined,\n\tbudget: Budget,\n): string => {\n\tconst path = readString(input, name, at)\n\tconst most = budget.limits.fieldPathParts\n\tconst parts = path.split(\".\", most + 1).length\n\tif (parts > most) {\n\t\tthrow problemAt(\n\t\t\tPlace.at(at, name),\n\t\t\t`A field path has more than ${String(most)} parts. The most is ${String(most)}.`,\n\t\t)\n\t}\n\treturn path\n}\n\nconst isJsonScalar = (value: unknown): value is Nodes.Scalar =>\n\tNodes.isScalar(value) && (typeof value !== \"number\" || Number.isFinite(value))\n\nconst decodeOperand = (\n\tinput: unknown,\n\tat: Place.Place | undefined,\n\tbudget: Budget,\n): Nodes.Operand => {\n\tconst tag = read(input, \"_tag\", at)\n\tif (tag === \"Literal\") {\n\t\t// A missing value is not `null`: a test with `null` has its own\n\t\t// meaning, and a lost value must not become one.\n\t\tconst own =\n\t\t\ttypeof input === \"object\" && input !== null\n\t\t\t\t? Object.getOwnPropertyDescriptor(input, \"value\")\n\t\t\t\t: undefined\n\t\tif (own === undefined || (\"value\" in own && own.value === undefined)) {\n\t\t\tthrow problemAt(Place.at(at, \"value\"), \"A fixed value is missing\")\n\t\t}\n\t\tconst value = read(input, \"value\", at)\n\t\tif (!isJsonScalar(value)) {\n\t\t\tthrow problemAt(\n\t\t\t\tPlace.at(at, \"value\"),\n\t\t\t\t\"A fixed value must be a string, a finite number, a boolean or null\",\n\t\t\t)\n\t\t}\n\t\treturn Nodes.literal(value)\n\t}\n\tif (tag === \"Principal\") {\n\t\treturn Nodes.principalRef(readPath(input, \"path\", at, budget))\n\t}\n\tthrow problemAt(\n\t\tPlace.at(at, \"_tag\"),\n\t\t'An operand must have the tag \"Literal\" or \"Principal\"',\n\t)\n}\n\nconst decodeOperands = (\n\tinput: unknown,\n\tname: string,\n\tat: Place.Place | undefined,\n\tbudget: Budget,\n): ReadonlyArray<Nodes.Operand> =>\n\tArr.map(readList(input, name, at, budget), (operand, index) =>\n\t\tdecodeOperand(operand, Place.at(at, name, index), budget),\n\t)\n\n/** A part of the input to decode, with its place. */\ninterface Part {\n\treadonly input: unknown\n\treadonly at: Place.Place | undefined\n}\n\n/**\n * The decoder of a condition, with the budget of one decode. It is a walk of\n * the trampoline for an input of unknown depth, so a deep input does not fill\n * the stack.\n */\nconst decoder = (budget: Budget): ((part: Part) => Node) => {\n\t// The nodes of this condition that the decoder has read. A condition with\n\t// more nodes than `conditionNodes` does not load, so the decoder stops at\n\t// the first node past it, and it does not read the rest of a very large\n\t// input. The count belongs to the decode of one condition.\n\tconst count = { nodes: 0 }\n\treturn Trampoline.adaptive<Part, Node>((R, child) => (part, depth) => {\n\t\tconst { input, at } = part\n\t\tif (typeof input !== \"object\" || input === null) {\n\t\t\tthrow problemAt(at, \"A condition node must be an object\")\n\t\t}\n\t\tspend(budget, 1, at)\n\t\tconst counted = (more: number): void => {\n\t\t\tcount.nodes += more\n\t\t\tif (count.nodes > budget.limits.conditionNodes) {\n\t\t\t\tthrow problemAt(\n\t\t\t\t\tat,\n\t\t\t\t\t`A condition has more than ${String(budget.limits.conditionNodes)} nodes and values. The most is ${String(budget.limits.conditionNodes)}.`,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\tcounted(1)\n\t\t// The values of an `In` or an `Under` test count in the weight of the\n\t\t// condition. The length of the list counts before the decoder reads\n\t\t// any value, so a very long list stops before it takes memory.\n\t\tconst operands = (name: string): ReadonlyArray<Nodes.Operand> => {\n\t\t\tconst list = read(input, name, at)\n\t\t\tif (Array.isArray(list)) {\n\t\t\t\tcounted(list.length)\n\t\t\t}\n\t\t\treturn decodeOperands(input, name, at, budget)\n\t\t}\n\t\t// A node past a limit, such as a field path with too many parts, is a\n\t\t// problem at its place. The constructor checks the ceilings, and the\n\t\t// decoder checks the limits of the decode.\n\t\tconst make = (build: () => Node): Node => {\n\t\t\tconst node = (() => {\n\t\t\t\ttry {\n\t\t\t\t\treturn build()\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (error instanceof Limits.LimitExceeded) {\n\t\t\t\t\t\tthrow problemAt(at, error.message)\n\t\t\t\t\t}\n\t\t\t\t\tthrow error\n\t\t\t\t}\n\t\t\t})()\n\t\t\tconst most = budget.limits.conditionNodes\n\t\t\tif (node.weight > most) {\n\t\t\t\tthrow problemAt(\n\t\t\t\t\tat,\n\t\t\t\t\t`A condition has ${String(node.weight)} nodes and values. The most is ${String(most)}.`,\n\t\t\t\t)\n\t\t\t}\n\t\t\treturn node\n\t\t}\n\t\tconst children = (name: string) =>\n\t\t\tR.forEach(readList(input, name, at, budget), (item, index) =>\n\t\t\t\tchild({ input: item, at: Place.at(at, name, index) }, depth + 1),\n\t\t\t)\n\t\tconst tag = read(input, \"_tag\", at)\n\t\tswitch (tag) {\n\t\t\tcase \"True\":\n\t\t\t\treturn R.succeed(Nodes.always)\n\t\t\tcase \"False\":\n\t\t\t\treturn R.succeed(Nodes.never)\n\t\t\tcase \"Compare\": {\n\t\t\t\tconst op = readString(input, \"op\", at)\n\t\t\t\tif (!Nodes.isCompareOperator(op)) {\n\t\t\t\t\tthrow problemAt(\n\t\t\t\t\t\tPlace.at(at, \"op\"),\n\t\t\t\t\t\t`\"${op}\" is not a compare operator`,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\treturn R.succeed(\n\t\t\t\t\tmake(\n\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\tnew Nodes.Compare({\n\t\t\t\t\t\t\t\top,\n\t\t\t\t\t\t\t\tfield: readPath(input, \"field\", at, budget),\n\t\t\t\t\t\t\t\tvalue: decodeOperand(\n\t\t\t\t\t\t\t\t\tread(input, \"value\", at),\n\t\t\t\t\t\t\t\t\tPlace.at(at, \"value\"),\n\t\t\t\t\t\t\t\t\tbudget,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\t\t\tcase \"In\":\n\t\t\t\treturn R.succeed(\n\t\t\t\t\tmake(\n\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\tnew Nodes.In({\n\t\t\t\t\t\t\t\tfield: readPath(input, \"field\", at, budget),\n\t\t\t\t\t\t\t\tvalues: operands(\"values\"),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\tcase \"Exists\":\n\t\t\t\treturn R.succeed(\n\t\t\t\t\tmake(\n\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\tnew Nodes.Exists({ field: readPath(input, \"field\", at, budget) }),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\tcase \"Under\":\n\t\t\t\treturn R.succeed(\n\t\t\t\t\tmake(\n\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\tnew Nodes.Under({\n\t\t\t\t\t\t\t\tprefix: operands(\"prefix\"),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\tcase \"And\":\n\t\t\t\treturn R.map(children(\"conditions\"), (conditions) =>\n\t\t\t\t\tmake(() => new Nodes.And(conditions)),\n\t\t\t\t)\n\t\t\tcase \"Or\":\n\t\t\t\treturn R.map(children(\"conditions\"), (conditions) =>\n\t\t\t\t\tmake(() => new Nodes.Or(conditions)),\n\t\t\t\t)\n\t\t\tcase \"Not\":\n\t\t\t\treturn R.map(\n\t\t\t\t\tchild(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tinput: read(input, \"condition\", at),\n\t\t\t\t\t\t\tat: Place.at(at, \"condition\"),\n\t\t\t\t\t\t},\n\t\t\t\t\t\tdepth + 1,\n\t\t\t\t\t),\n\t\t\t\t\t(condition) => make(() => new Nodes.Not(condition)),\n\t\t\t\t)\n\t\t\tdefault:\n\t\t\t\tthrow problemAt(Place.at(at, \"_tag\"), \"The node has no known tag\")\n\t\t}\n\t})\n}\n\n/**\n * Decodes the JSON form of a condition at a path, with the budget of a larger\n * decode. It throws a {@link Problem}.\n */\nexport const decodeAt = (\n\tinput: unknown,\n\tat: Place.Place | undefined,\n\tbudget: Budget,\n): Node => decoder(budget)({ input, at })\n\n/** Decodes the JSON form of a condition, with the default limits. */\nexport const decode = (input: unknown): Result.Result<Node, Problem> => {\n\ttry {\n\t\treturn Result.succeed(\n\t\t\tdecoder({ used: 0, limits: Limits.defaults })({ input, at: undefined }),\n\t\t)\n\t} catch (error) {\n\t\tif (error instanceof Problem) {\n\t\t\treturn Result.fail(error)\n\t\t}\n\t\tthrow error\n\t}\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -151,7 +151,8 @@ class PrincipalKey implements Equal.Equal {
|
|
|
151
151
|
* to one walk.
|
|
152
152
|
*
|
|
153
153
|
* A value that keeps its data inside, with no own fields, as a `Date`, a
|
|
154
|
-
* `URL` or a `RegExp
|
|
154
|
+
* `URL` or a `RegExp` with the place of its next match, is in the text with
|
|
155
|
+
* its data, and so is an `Option`.
|
|
155
156
|
* For another such value, the walk can not see the data, so it throws, and
|
|
156
157
|
* the principal gets no key: it is not kept.
|
|
157
158
|
*/
|
|
@@ -181,7 +182,11 @@ const orderOf = (root: unknown): string => {
|
|
|
181
182
|
return R.succeed(`url:${JSON.stringify(value.href)}`)
|
|
182
183
|
}
|
|
183
184
|
if (value instanceof RegExp) {
|
|
184
|
-
|
|
185
|
+
// A `RegExp` with the flag `g` or `y` keeps the place of its next
|
|
186
|
+
// match in `lastIndex`, which `Equal` does not see.
|
|
187
|
+
return R.succeed(
|
|
188
|
+
`regexp:${JSON.stringify(String(value))}:${String(value.lastIndex)}`,
|
|
189
|
+
)
|
|
185
190
|
}
|
|
186
191
|
if (Option.isOption(value)) {
|
|
187
192
|
return Option.isSome(value)
|
package/src/internal/bind.ts
CHANGED
|
@@ -638,7 +638,15 @@ const conditionIssues = (
|
|
|
638
638
|
case "Exists":
|
|
639
639
|
return room(0) ? here(readIssues(context, node.field, ["field"])) : []
|
|
640
640
|
case "Under":
|
|
641
|
-
|
|
641
|
+
// The check reads the path of each resource of the rule for each
|
|
642
|
+
// part of the prefix, so it counts that work first.
|
|
643
|
+
return spendChecks(
|
|
644
|
+
context.memo,
|
|
645
|
+
1 + node.prefix.length,
|
|
646
|
+
context.paths.length,
|
|
647
|
+
)
|
|
648
|
+
? here(underIssues(context, node.prefix, []))
|
|
649
|
+
: []
|
|
642
650
|
default:
|
|
643
651
|
return []
|
|
644
652
|
}
|
package/src/internal/json.ts
CHANGED
|
@@ -207,12 +207,25 @@ const decoder = (budget: Budget): ((part: Part) => Node) => {
|
|
|
207
207
|
throw problemAt(at, "A condition node must be an object")
|
|
208
208
|
}
|
|
209
209
|
spend(budget, 1, at)
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
210
|
+
const counted = (more: number): void => {
|
|
211
|
+
count.nodes += more
|
|
212
|
+
if (count.nodes > budget.limits.conditionNodes) {
|
|
213
|
+
throw problemAt(
|
|
214
|
+
at,
|
|
215
|
+
`A condition has more than ${String(budget.limits.conditionNodes)} nodes and values. The most is ${String(budget.limits.conditionNodes)}.`,
|
|
216
|
+
)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
counted(1)
|
|
220
|
+
// The values of an `In` or an `Under` test count in the weight of the
|
|
221
|
+
// condition. The length of the list counts before the decoder reads
|
|
222
|
+
// any value, so a very long list stops before it takes memory.
|
|
223
|
+
const operands = (name: string): ReadonlyArray<Nodes.Operand> => {
|
|
224
|
+
const list = read(input, name, at)
|
|
225
|
+
if (Array.isArray(list)) {
|
|
226
|
+
counted(list.length)
|
|
227
|
+
}
|
|
228
|
+
return decodeOperands(input, name, at, budget)
|
|
216
229
|
}
|
|
217
230
|
// A node past a limit, such as a field path with too many parts, is a
|
|
218
231
|
// problem at its place. The constructor checks the ceilings, and the
|
|
@@ -276,7 +289,7 @@ const decoder = (budget: Budget): ((part: Part) => Node) => {
|
|
|
276
289
|
() =>
|
|
277
290
|
new Nodes.In({
|
|
278
291
|
field: readPath(input, "field", at, budget),
|
|
279
|
-
values:
|
|
292
|
+
values: operands("values"),
|
|
280
293
|
}),
|
|
281
294
|
),
|
|
282
295
|
)
|
|
@@ -292,7 +305,7 @@ const decoder = (budget: Budget): ((part: Part) => Node) => {
|
|
|
292
305
|
make(
|
|
293
306
|
() =>
|
|
294
307
|
new Nodes.Under({
|
|
295
|
-
prefix:
|
|
308
|
+
prefix: operands("prefix"),
|
|
296
309
|
}),
|
|
297
310
|
),
|
|
298
311
|
)
|