@nmnmcc/ability 2.0.7 → 2.1.2
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/README.md +52 -29
- package/dist/Authorizer.d.ts +21 -12
- package/dist/Authorizer.d.ts.map +1 -1
- package/dist/Authorizer.js.map +1 -1
- package/dist/Condition.d.ts +121 -19
- package/dist/Condition.d.ts.map +1 -1
- package/dist/Condition.js +32 -21
- package/dist/Condition.js.map +1 -1
- package/dist/Domain.d.ts +9 -0
- package/dist/Domain.d.ts.map +1 -1
- package/dist/Domain.js +5 -0
- package/dist/Domain.js.map +1 -1
- package/dist/Limits.d.ts +44 -13
- package/dist/Limits.d.ts.map +1 -1
- package/dist/Limits.js +52 -22
- package/dist/Limits.js.map +1 -1
- package/dist/Policy.d.ts +102 -18
- package/dist/Policy.d.ts.map +1 -1
- package/dist/Policy.js +7 -6
- package/dist/Policy.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/internal/algebra.js +2 -2
- package/dist/internal/analysis.js +81 -75
- package/dist/internal/analysis.js.map +1 -1
- package/dist/internal/authorizer.d.ts.map +1 -1
- package/dist/internal/authorizer.js +11 -7
- package/dist/internal/authorizer.js.map +1 -1
- package/dist/internal/bind.js +86 -64
- package/dist/internal/bind.js.map +1 -1
- package/dist/internal/catalog.d.ts +33 -10
- package/dist/internal/catalog.d.ts.map +1 -1
- package/dist/internal/catalog.js +170 -77
- package/dist/internal/catalog.js.map +1 -1
- package/dist/internal/decide.js +27 -31
- package/dist/internal/decide.js.map +1 -1
- package/dist/internal/domain.js +19 -5
- package/dist/internal/domain.js.map +1 -1
- package/dist/internal/json.js +120 -76
- package/dist/internal/json.js.map +1 -1
- package/dist/internal/limits.d.ts +28 -0
- package/dist/internal/limits.d.ts.map +1 -1
- package/dist/internal/limits.js +75 -16
- package/dist/internal/limits.js.map +1 -1
- package/dist/internal/nodes.d.ts.map +1 -1
- package/dist/internal/nodes.js +53 -44
- package/dist/internal/nodes.js.map +1 -1
- package/dist/internal/patterns.js +50 -15
- package/dist/internal/patterns.js.map +1 -1
- package/dist/internal/policy-json.js +169 -127
- package/dist/internal/policy-json.js.map +1 -1
- package/dist/internal/policy-nodes.d.ts.map +1 -1
- package/dist/internal/policy-nodes.js +16 -13
- package/dist/internal/policy-nodes.js.map +1 -1
- package/dist/internal/policy.d.ts.map +1 -1
- package/dist/internal/policy.js +38 -22
- package/dist/internal/policy.js.map +1 -1
- package/dist/internal/resource.js +1 -1
- package/dist/internal/rule.d.ts.map +1 -1
- package/dist/internal/rule.js +6 -5
- package/dist/internal/rule.js.map +1 -1
- package/dist/internal/search.d.ts.map +1 -1
- package/dist/internal/search.js +106 -92
- package/dist/internal/search.js.map +1 -1
- package/dist/internal/trampoline.js +19 -5
- package/dist/internal/trampoline.js.map +1 -1
- package/package.json +1 -1
- package/src/Authorizer.ts +30 -20
- package/src/Condition.ts +317 -86
- package/src/Domain.ts +9 -0
- package/src/Limits.ts +69 -29
- package/src/Policy.ts +143 -21
- package/src/internal/analysis.ts +154 -124
- package/src/internal/authorizer.ts +29 -14
- package/src/internal/bind.ts +193 -136
- package/src/internal/catalog.ts +320 -118
- package/src/internal/decide.ts +61 -50
- package/src/internal/domain.ts +39 -3
- package/src/internal/json.ts +212 -117
- package/src/internal/limits.ts +101 -31
- package/src/internal/nodes.ts +65 -47
- package/src/internal/patterns.ts +79 -25
- package/src/internal/policy-json.ts +266 -197
- package/src/internal/policy-nodes.ts +31 -23
- package/src/internal/policy.ts +63 -23
- package/src/internal/rule.ts +16 -3
- package/src/internal/search.ts +186 -136
- package/src/internal/trampoline.ts +51 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decide.js","names":["RuleData.matchesTarget","Condition.evaluate","Decision.Allow","Decision.Deny","Decision.NotApplicable","Catalog.lookup","ResourceData.kindOf","DomainData.descriptorOf","RuleData.fitsOf","RuleData.matchesInside"],"sources":["../../src/internal/decide.ts"],"sourcesContent":["/**\n * The decision walk of a policy: `decide`, `explain` and `permittedFields`.\n * The types are in `Policy.ts`.\n */\n// A policy keeps the error type and the service type of its predicates in the\n// public types. The code below works on the nodes with no types, where they\n// are `unknown`, so the language service check for `unknown` in an Effect is\n// off here.\n// @effect-diagnostics anyUnknownInErrorContext:off\nimport * as Arr from \"effect/Array\"\nimport * as Chunk from \"effect/Chunk\"\nimport * as Effect from \"effect/Effect\"\nimport * as Option from \"effect/Option\"\nimport * as Ref from \"effect/Ref\"\nimport * 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 type * as Resource from \"../Resource.ts\"\nimport * as Catalog from \"./catalog.ts\"\nimport * as DomainData from \"./domain.ts\"\nimport type * as PolicyNodes from \"./policy-nodes.ts\"\nimport { nodeOf } from \"./policy.ts\"\nimport * as ResourceData from \"./resource.ts\"\nimport * as RuleData from \"./rule.ts\"\n\ntype Any = Policy.Any\ntype AnyRequest = Policy.AnyRequest\ntype AnyRule = Policy.AnyRule\ntype Explanation = Policy.Explanation\ntype Step = Policy.Step\n\ninterface DecideContext {\n\treadonly action: string\n\treadonly kind: string\n\treadonly field: string | undefined\n\treadonly input: Condition.Input\n\t/** The resource of the kind, for the fields that a rule may grant. */\n\treadonly descriptor: Resource.Descriptor\n\t/** The test of `RuleData.fitsOf` for the catalog, for this decision. */\n\treadonly fits: (pattern: string) => boolean\n\t/** The rules that were tested, for {@link explain}. */\n\treadonly trace: Ref.Ref<Chunk.Chunk<Step>> | undefined\n}\n\nconst ruleApplies = (\n\tvalue: RuleData.Rule,\n\tcontext: DecideContext,\n\tpath: ReadonlyArray<number>,\n): Effect.Effect<boolean, unknown, unknown> => {\n\tif (!RuleData.matchesTarget(value, context)) {\n\t\treturn Effect.succeed(false)\n\t}\n\tconst test =\n\t\tvalue.when === undefined\n\t\t\t? Effect.succeed(true)\n\t\t\t: // A rule of a node keeps no type of what it reads. The decision gives\n\t\t\t\t// it the value and the principal of the request.\n\t\t\t\tCondition.evaluate(value.when as Condition.Any, context.input)\n\tconst trace = context.trace\n\treturn trace === undefined\n\t\t? test\n\t\t: Effect.tap(test, (applies) =>\n\t\t\t\t// A chunk adds a step with no copy of the steps before.\n\t\t\t\tRef.update(trace, Chunk.append({ path, rule: typed(value), applies })),\n\t\t\t)\n}\n\n/** A rule of a node, with the type of a rule of no domain. */\nconst typed = (value: RuleData.Rule): AnyRule => value as never\n\nconst fromRule = (value: RuleData.Rule): Decision.Decision =>\n\tvalue._tag === \"Allow\"\n\t\t? Decision.Allow({ rule: typed(value) })\n\t\t: value.reason === undefined\n\t\t\t? Decision.Deny({ rule: typed(value) })\n\t\t\t: Decision.Deny({ rule: typed(value), reason: value.reason })\n\n/** Gives the first rule that applies, in order. */\nconst firstApplying = (\n\tcandidates: ReadonlyArray<readonly [RuleData.Rule, number]>,\n\tcontext: DecideContext,\n\tpath: ReadonlyArray<number>,\n): Effect.Effect<Option.Option<RuleData.Rule>, unknown, unknown> =>\n\tEffect.map(\n\t\tEffect.findFirst(candidates, ([value, index]) =>\n\t\t\truleApplies(value, context, [...path, index]),\n\t\t),\n\t\tOption.map(([value]) => value),\n\t)\n\n/** Gives the first result that is found, and tests `next` only when none is. */\nconst orElse = <A, E, R>(\n\tfirst: Effect.Effect<Option.Option<A>, E, R>,\n\tnext: () => Effect.Effect<Option.Option<A>, E, R>,\n): Effect.Effect<Option.Option<A>, E, R> =>\n\tEffect.filterOrElse(first, Option.isSome, next)\n\nconst decideRules = (\n\tnode: PolicyNodes.Rules,\n\tcontext: DecideContext,\n\tpath: ReadonlyArray<number>,\n): Effect.Effect<Decision.Decision, unknown, unknown> => {\n\tconst indexed = Arr.map(node.rules, (value, index) => [value, index] as const)\n\tconst denies = Arr.filter(indexed, ([value]) => value._tag === \"Deny\")\n\tconst allows = Arr.filter(indexed, ([value]) => value._tag === \"Allow\")\n\tconst first = (candidates: ReadonlyArray<readonly [RuleData.Rule, number]>) =>\n\t\tfirstApplying(candidates, context, path)\n\tconst found = (() => {\n\t\tswitch (node.algorithm) {\n\t\t\tcase \"denyOverrides\":\n\t\t\t\treturn orElse(first(denies), () => first(allows))\n\t\t\tcase \"permitOverrides\":\n\t\t\t\treturn orElse(first(allows), () => first(denies))\n\t\t\tcase \"firstApplicable\":\n\t\t\t\treturn first(indexed)\n\t\t\tcase \"lastApplicable\":\n\t\t\t\treturn first(Arr.reverse(indexed))\n\t\t}\n\t})()\n\treturn Effect.map(\n\t\tfound,\n\t\tOption.match({ onNone: () => Decision.NotApplicable, onSome: fromRule }),\n\t)\n}\n\n/** The next step of a walk over children: stop with a decision, or go on. */\ntype WalkStep<S> =\n\t| { readonly _tag: \"Stop\"; readonly decision: Decision.Decision }\n\t| { readonly _tag: \"Next\"; readonly state: S }\n\n/**\n * Decides the children in order. After each child, `step` stops with a\n * decision, or gives the state for the next child. After the last child,\n * `end` gives the decision. A child after a stop is not decided, so its\n * predicates do not run.\n */\nconst walkChildren = <S>(\n\tchildren: ReadonlyArray<PolicyNodes.Node>,\n\tcontext: DecideContext,\n\tpath: ReadonlyArray<number>,\n\tstart: S,\n\tstep: (state: S, decision: Decision.Decision) => WalkStep<S>,\n\tend: (state: S) => Decision.Decision,\n): Effect.Effect<Decision.Decision, unknown, unknown> => {\n\tconst go = (\n\t\tindex: number,\n\t\tstate: S,\n\t): Effect.Effect<Decision.Decision, unknown, unknown> =>\n\t\tindex === children.length\n\t\t\t? Effect.succeed(end(state))\n\t\t\t: Effect.flatMap(\n\t\t\t\t\tdecideNode(children[index]!, context, [...path, index]),\n\t\t\t\t\t(decision) => {\n\t\t\t\t\t\tconst next = step(state, decision)\n\t\t\t\t\t\treturn next._tag === \"Stop\"\n\t\t\t\t\t\t\t? Effect.succeed(next.decision)\n\t\t\t\t\t\t\t: go(index + 1, next.state)\n\t\t\t\t\t},\n\t\t\t\t)\n\treturn go(0, start)\n}\n\nconst stop = (decision: Decision.Decision): WalkStep<never> => ({\n\t_tag: \"Stop\",\n\tdecision,\n})\n\nconst next = <S>(state: S): WalkStep<S> => ({ _tag: \"Next\", state })\n\n/**\n * Decides a node. A shared child is decided again for each place, so its\n * predicates run each time, as the policy says. The limits of the nodes keep\n * this work small.\n */\nexport const decideNode = (\n\tnode: PolicyNodes.Node,\n\tcontext: DecideContext,\n\tpath: ReadonlyArray<number>,\n): Effect.Effect<Decision.Decision, unknown, unknown> => {\n\tswitch (node._tag) {\n\t\tcase \"Rules\":\n\t\t\treturn decideRules(node, context, path)\n\t\tcase \"All\":\n\t\t\t// One deny is enough to deny. Every child must allow.\n\t\t\treturn walkChildren(\n\t\t\t\tnode.policies,\n\t\t\t\tcontext,\n\t\t\t\tpath,\n\t\t\t\t{\n\t\t\t\t\tfirstAllow: Option.none<Decision.Decision>(),\n\t\t\t\t\tallAllow: node.policies.length > 0,\n\t\t\t\t},\n\t\t\t\t(state, decision) =>\n\t\t\t\t\tdecision._tag === \"Deny\"\n\t\t\t\t\t\t? stop(decision)\n\t\t\t\t\t\t: next({\n\t\t\t\t\t\t\t\tfirstAllow: Option.orElse(state.firstAllow, () =>\n\t\t\t\t\t\t\t\t\tdecision._tag === \"Allow\"\n\t\t\t\t\t\t\t\t\t\t? Option.some(decision)\n\t\t\t\t\t\t\t\t\t\t: Option.none(),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\tallAllow: state.allAllow && decision._tag === \"Allow\",\n\t\t\t\t\t\t\t}),\n\t\t\t\t(state) =>\n\t\t\t\t\tstate.allAllow\n\t\t\t\t\t\t? Option.getOrElse(state.firstAllow, () => Decision.NotApplicable)\n\t\t\t\t\t\t: Decision.NotApplicable,\n\t\t\t)\n\t\tcase \"Any\":\n\t\t\t// One allow is enough. Without one, the first deny denies.\n\t\t\treturn walkChildren(\n\t\t\t\tnode.policies,\n\t\t\t\tcontext,\n\t\t\t\tpath,\n\t\t\t\tOption.none<Decision.Decision>(),\n\t\t\t\t(firstDeny, decision) =>\n\t\t\t\t\tdecision._tag === \"Allow\"\n\t\t\t\t\t\t? stop(decision)\n\t\t\t\t\t\t: next(\n\t\t\t\t\t\t\t\tOption.orElse(firstDeny, () =>\n\t\t\t\t\t\t\t\t\tdecision._tag === \"Deny\"\n\t\t\t\t\t\t\t\t\t\t? Option.some(decision)\n\t\t\t\t\t\t\t\t\t\t: Option.none(),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t),\n\t\t\t\t(firstDeny) =>\n\t\t\t\t\tOption.getOrElse(firstDeny, () => Decision.NotApplicable),\n\t\t\t)\n\t\tcase \"FirstOf\":\n\t\t\treturn walkChildren(\n\t\t\t\tnode.policies,\n\t\t\t\tcontext,\n\t\t\t\tpath,\n\t\t\t\tundefined,\n\t\t\t\t(_, decision) =>\n\t\t\t\t\tdecision._tag === \"NotApplicable\" ? next(undefined) : stop(decision),\n\t\t\t\t() => Decision.NotApplicable,\n\t\t\t)\n\t\tcase \"Ceiling\":\n\t\t\t// A deny of the cap wins. Else the policy decides.\n\t\t\treturn Effect.filterOrElse(\n\t\t\t\tdecideNode(node.cap, context, [...path, 0]),\n\t\t\t\t(capped) => capped._tag === \"Deny\",\n\t\t\t\t() => decideNode(node.policy, context, [...path, 1]),\n\t\t\t)\n\t}\n}\n\n/**\n * Tests if a request fits its kind: the action is an action of the kind, and\n * the field, if there is one, is a field of the kind.\n */\nexport const fitsKind = (\n\tdescriptor: Resource.Descriptor,\n\trequest: { readonly action: string; readonly field?: string | undefined },\n): boolean =>\n\tdescriptor.actions.includes(request.action) &&\n\t(request.field === undefined ||\n\t\t(Catalog.lookup(descriptor.catalog, request.field)?.listed ?? false))\n\nconst makeContext = (\n\tdomain: Domain.Any,\n\trequest: AnyRequest,\n\ttrace: Ref.Ref<Chunk.Chunk<Step>> | undefined,\n): DecideContext | undefined => {\n\tconst kind = ResourceData.kindOf(request.resource)\n\tconst descriptor =\n\t\tkind === undefined ? undefined : DomainData.descriptorOf(domain, kind)\n\tif (\n\t\tkind === undefined ||\n\t\tdescriptor === undefined ||\n\t\t!fitsKind(descriptor, request)\n\t) {\n\t\treturn undefined\n\t}\n\treturn {\n\t\taction: request.action,\n\t\tkind,\n\t\tfield: request.field,\n\t\tinput: {\n\t\t\tvalue: request.resource,\n\t\t\tprincipal: request.principal,\n\t\t\tpath: descriptor.path,\n\t\t},\n\t\tdescriptor,\n\t\tfits: RuleData.fitsOf(descriptor.catalog),\n\t\ttrace,\n\t}\n}\n\nexport const decideAny = (\n\tpolicy: Any,\n\trequest: AnyRequest,\n): Effect.Effect<Decision.Decision, unknown, unknown> =>\n\t// The context reads the resource value, so it is made for each run.\n\tEffect.suspend(() => {\n\t\tconst context = makeContext(policy.domain, request, undefined)\n\t\treturn context === undefined\n\t\t\t? Effect.succeed(Decision.NotApplicable)\n\t\t\t: decideNode(nodeOf(policy), context, [])\n\t})\n\n/** Gives the decision of a policy with no predicates, at once. */\nexport const decideSync = (\n\tpolicy: Any,\n\trequest: AnyRequest,\n): Decision.Decision =>\n\tEffect.runSync(\n\t\t// The public type of `decideSync` takes only policies with no\n\t\t// predicates, so this Effect has no error and needs no services.\n\t\t// @effect-diagnostics-next-line unsafeEffectTypeAssertion:off\n\t\tdecideAny(policy, request) as Effect.Effect<Decision.Decision>,\n\t)\n\n/** Gives the decision of a policy and the rules that were tested to make it. */\nexport const explain = (\n\tpolicy: Any,\n\trequest: AnyRequest,\n): Effect.Effect<Explanation, unknown, unknown> =>\n\t// Each run of the Effect needs its own trace, so the trace is made inside\n\t// the Effect.\n\tEffect.gen(function* () {\n\t\tconst trace = yield* Ref.make(Chunk.empty<Step>())\n\t\tconst context = makeContext(policy.domain, request, trace)\n\t\tif (context === undefined) {\n\t\t\treturn { decision: Decision.NotApplicable, steps: [] }\n\t\t}\n\t\tconst decision = yield* decideNode(nodeOf(policy), context, [])\n\t\treturn { decision, steps: Chunk.toReadonlyArray(yield* Ref.get(trace)) }\n\t})\n\n/**\n * Tests if a deny rule of the request names a part inside `field`.\n */\nconst deniesInside = (\n\tnode: PolicyNodes.Node,\n\ttarget: { readonly action: string; readonly kind: string },\n\tfield: string,\n): boolean => {\n\tswitch (node._tag) {\n\t\tcase \"Rules\":\n\t\t\treturn node.rules.some(\n\t\t\t\t(value) =>\n\t\t\t\t\tvalue._tag === \"Deny\" &&\n\t\t\t\t\tvalue.fields !== undefined &&\n\t\t\t\t\tvalue.fields.some((pattern) =>\n\t\t\t\t\t\tRuleData.matchesInside(pattern, field),\n\t\t\t\t\t) &&\n\t\t\t\t\t(value.actions.includes(\"*\") ||\n\t\t\t\t\t\tvalue.actions.includes(target.action)) &&\n\t\t\t\t\t(value.resources.includes(\"*\") ||\n\t\t\t\t\t\tvalue.resources.includes(target.kind)),\n\t\t\t)\n\t\tcase \"All\":\n\t\tcase \"Any\":\n\t\tcase \"FirstOf\":\n\t\t\treturn node.policies.some((child) => deniesInside(child, target, field))\n\t\tcase \"Ceiling\":\n\t\t\treturn (\n\t\t\t\tdeniesInside(node.cap, target, field) ||\n\t\t\t\tdeniesInside(node.policy, target, field)\n\t\t\t)\n\t}\n}\n\n/** Gives the fields of a resource value that the policy lets a request use. */\nexport const permittedFields = (\n\tpolicy: Any,\n\trequest: Omit<AnyRequest, \"field\">,\n): Effect.Effect<ReadonlyArray<string>, unknown, unknown> => {\n\tconst kind = ResourceData.kindOf(request.resource)\n\tconst descriptor: Resource.Descriptor | undefined =\n\t\tkind === undefined\n\t\t\t? undefined\n\t\t\t: DomainData.descriptorOf(policy.domain, kind)\n\tif (descriptor === undefined) {\n\t\treturn Effect.succeed([])\n\t}\n\t// The fields with names only: a list can not name every key of a record.\n\tconst listed = Arr.filter(\n\t\tdescriptor.catalog.fields,\n\t\t(field) =>\n\t\t\tfield.listed &&\n\t\t\tArr.every(field.path, (segment) => typeof segment === \"string\"),\n\t)\n\treturn Effect.map(\n\t\tEffect.filter(listed, (field) =>\n\t\t\tEffect.map(\n\t\t\t\tdecideAny(policy, { ...request, field: field.text }),\n\t\t\t\t(result) =>\n\t\t\t\t\tresult._tag === \"Allow\" &&\n\t\t\t\t\t!(\n\t\t\t\t\t\tfield.open &&\n\t\t\t\t\t\tdeniesInside(\n\t\t\t\t\t\t\tnodeOf(policy),\n\t\t\t\t\t\t\t{ ...request, kind: descriptor.kind },\n\t\t\t\t\t\t\tfield.text,\n\t\t\t\t\t\t)\n\t\t\t\t\t),\n\t\t\t),\n\t\t),\n\t\tArr.map((field) => field.text),\n\t)\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"decide.js","names":["RuleData.matchesTarget","Condition.evaluate","Place.pathOf","Decision.Allow","Decision.Deny","Place.at","at","Decision.NotApplicable","Catalog.lookup","ResourceData.kindOf","DomainData.descriptorOf","RuleData.fitsOf","RuleData.matchesInside","PolicyNodes.rulesOf","Catalog.fieldsOf"],"sources":["../../src/internal/decide.ts"],"sourcesContent":["/**\n * The decision walk of a policy: `decide`, `explain` and `permittedFields`.\n * The types are in `Policy.ts`.\n */\n// A policy keeps the error type and the service type of its predicates in the\n// public types. The code below works on the nodes with no types, where they\n// are `unknown`, so the language service check for `unknown` in an Effect is\n// off here.\n// @effect-diagnostics anyUnknownInErrorContext:off\nimport * as Arr from \"effect/Array\"\nimport * as Chunk from \"effect/Chunk\"\nimport * as Effect from \"effect/Effect\"\nimport * as Option from \"effect/Option\"\nimport * as Ref from \"effect/Ref\"\nimport * 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 type * as Resource from \"../Resource.ts\"\nimport * as Catalog from \"./catalog.ts\"\nimport * as DomainData from \"./domain.ts\"\nimport * as Place from \"./place.ts\"\nimport * as PolicyNodes from \"./policy-nodes.ts\"\nimport { nodeOf } from \"./policy.ts\"\nimport * as ResourceData from \"./resource.ts\"\nimport * as RuleData from \"./rule.ts\"\n\ntype Any = Policy.Any\ntype AnyRequest = Policy.AnyRequest\ntype AnyRule = Policy.AnyRule\ntype Explanation = Policy.Explanation\ntype Step = Policy.Step\n\ninterface DecideContext {\n\treadonly action: string\n\treadonly kind: string\n\treadonly field: string | undefined\n\treadonly input: Condition.Input\n\t/** The resource of the kind, for the fields that a rule may grant. */\n\treadonly descriptor: Resource.Descriptor\n\t/** The test of `RuleData.fitsOf` for the catalog, for this decision. */\n\treadonly fits: (pattern: string) => boolean\n\t/** The most parts of a field path in the domain. */\n\treadonly parts: number\n\t/** The rules that were tested, for {@link explain}. */\n\treadonly trace: Ref.Ref<Chunk.Chunk<Step>> | undefined\n}\n\n/**\n * The place of a node or a rule in a tree: the index of each child, then\n * the index of the rule. It shares the path of its parent, and a decision\n * makes the full path only for a step of `explain`: see `Place`.\n */\ntype At = Place.Place | undefined\n\nconst ruleApplies = (\n\tvalue: RuleData.Rule,\n\tcontext: DecideContext,\n\tat: Place.Place,\n): Effect.Effect<boolean, unknown, unknown> => {\n\tif (!RuleData.matchesTarget(value, context)) {\n\t\treturn Effect.succeed(false)\n\t}\n\tconst test =\n\t\tvalue.when === undefined\n\t\t\t? Effect.succeed(true)\n\t\t\t: // A rule of a node keeps no type of what it reads. The decision gives\n\t\t\t\t// it the value and the principal of the request.\n\t\t\t\tCondition.evaluate(value.when as Condition.Any, context.input)\n\tconst trace = context.trace\n\treturn trace === undefined\n\t\t? test\n\t\t: Effect.tap(test, (applies) =>\n\t\t\t\t// A chunk adds a step with no copy of the steps before.\n\t\t\t\tRef.update(\n\t\t\t\t\ttrace,\n\t\t\t\t\tChunk.append({\n\t\t\t\t\t\tpath: Place.pathOf(at) as ReadonlyArray<number>,\n\t\t\t\t\t\trule: typed(value),\n\t\t\t\t\t\tapplies,\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t)\n}\n\n/** A rule of a node, with the type of a rule of no domain. */\nconst typed = (value: RuleData.Rule): AnyRule => value as never\n\nconst fromRule = (value: RuleData.Rule): Decision.Decision =>\n\tvalue._tag === \"Allow\"\n\t\t? Decision.Allow({ rule: typed(value) })\n\t\t: value.reason === undefined\n\t\t\t? Decision.Deny({ rule: typed(value) })\n\t\t\t: Decision.Deny({ rule: typed(value), reason: value.reason })\n\n/** Gives the first rule that applies, in order. */\nconst firstApplying = (\n\tcandidates: ReadonlyArray<readonly [RuleData.Rule, number]>,\n\tcontext: DecideContext,\n\tat: At,\n): Effect.Effect<Option.Option<RuleData.Rule>, unknown, unknown> =>\n\tEffect.map(\n\t\tEffect.findFirst(candidates, ([value, index]) =>\n\t\t\truleApplies(value, context, Place.at(at, index)),\n\t\t),\n\t\tOption.map(([value]) => value),\n\t)\n\n/** Gives the first result that is found, and tests `next` only when none is. */\nconst orElse = <A, E, R>(\n\tfirst: Effect.Effect<Option.Option<A>, E, R>,\n\tnext: () => Effect.Effect<Option.Option<A>, E, R>,\n): Effect.Effect<Option.Option<A>, E, R> =>\n\tEffect.filterOrElse(first, Option.isSome, next)\n\nconst decideRules = (\n\tnode: PolicyNodes.Rules,\n\tcontext: DecideContext,\n\tat: At,\n): Effect.Effect<Decision.Decision, unknown, unknown> => {\n\tconst indexed = Arr.map(node.rules, (value, index) => [value, index] as const)\n\tconst denies = Arr.filter(indexed, ([value]) => value._tag === \"Deny\")\n\tconst allows = Arr.filter(indexed, ([value]) => value._tag === \"Allow\")\n\tconst first = (candidates: ReadonlyArray<readonly [RuleData.Rule, number]>) =>\n\t\tfirstApplying(candidates, context, at)\n\tconst found = (() => {\n\t\tswitch (node.algorithm) {\n\t\t\tcase \"denyOverrides\":\n\t\t\t\treturn orElse(first(denies), () => first(allows))\n\t\t\tcase \"permitOverrides\":\n\t\t\t\treturn orElse(first(allows), () => first(denies))\n\t\t\tcase \"firstApplicable\":\n\t\t\t\treturn first(indexed)\n\t\t\tcase \"lastApplicable\":\n\t\t\t\treturn first(Arr.reverse(indexed))\n\t\t}\n\t})()\n\treturn Effect.map(\n\t\tfound,\n\t\tOption.match({ onNone: () => Decision.NotApplicable, onSome: fromRule }),\n\t)\n}\n\n/** The next step of a walk over children: stop with a decision, or go on. */\ntype WalkStep<S> =\n\t| { readonly _tag: \"Stop\"; readonly decision: Decision.Decision }\n\t| { readonly _tag: \"Next\"; readonly state: S }\n\n/**\n * Decides the children in order. After each child, `step` stops with a\n * decision, or gives the state for the next child. After the last child,\n * `end` gives the decision. A child after a stop is not decided, so its\n * predicates do not run.\n */\nconst walkChildren = <S>(\n\tchildren: ReadonlyArray<PolicyNodes.Node>,\n\tcontext: DecideContext,\n\tat: At,\n\tstart: S,\n\tstep: (state: S, decision: Decision.Decision) => WalkStep<S>,\n\tend: (state: S) => Decision.Decision,\n): Effect.Effect<Decision.Decision, unknown, unknown> => {\n\tconst go = (\n\t\tindex: number,\n\t\tstate: S,\n\t): Effect.Effect<Decision.Decision, unknown, unknown> =>\n\t\tindex === children.length\n\t\t\t? Effect.succeed(end(state))\n\t\t\t: Effect.flatMap(\n\t\t\t\t\t// Each call for a child is a step of the Effect, so a deep tree\n\t\t\t\t\t// does not fill the stack.\n\t\t\t\t\tEffect.suspend(() =>\n\t\t\t\t\t\tdecideNode(children[index]!, context, Place.at(at, index)),\n\t\t\t\t\t),\n\t\t\t\t\t(decision) => {\n\t\t\t\t\t\tconst next = step(state, decision)\n\t\t\t\t\t\treturn next._tag === \"Stop\"\n\t\t\t\t\t\t\t? Effect.succeed(next.decision)\n\t\t\t\t\t\t\t: go(index + 1, next.state)\n\t\t\t\t\t},\n\t\t\t\t)\n\treturn go(0, start)\n}\n\nconst stop = (decision: Decision.Decision): WalkStep<never> => ({\n\t_tag: \"Stop\",\n\tdecision,\n})\n\nconst next = <S>(state: S): WalkStep<S> => ({ _tag: \"Next\", state })\n\n/**\n * Decides a node. A shared child is decided again for each place, so its\n * predicates run each time, as the policy says. The limits of the nodes keep\n * this work small.\n */\nexport const decideNode = (\n\tnode: PolicyNodes.Node,\n\tcontext: DecideContext,\n\tat: At,\n): Effect.Effect<Decision.Decision, unknown, unknown> => {\n\tswitch (node._tag) {\n\t\tcase \"Rules\":\n\t\t\treturn decideRules(node, context, at)\n\t\tcase \"All\":\n\t\t\t// One deny is enough to deny. Every child must allow.\n\t\t\treturn walkChildren(\n\t\t\t\tnode.policies,\n\t\t\t\tcontext,\n\t\t\t\tat,\n\t\t\t\t{\n\t\t\t\t\tfirstAllow: Option.none<Decision.Decision>(),\n\t\t\t\t\tallAllow: node.policies.length > 0,\n\t\t\t\t},\n\t\t\t\t(state, decision) =>\n\t\t\t\t\tdecision._tag === \"Deny\"\n\t\t\t\t\t\t? stop(decision)\n\t\t\t\t\t\t: next({\n\t\t\t\t\t\t\t\tfirstAllow: Option.orElse(state.firstAllow, () =>\n\t\t\t\t\t\t\t\t\tdecision._tag === \"Allow\"\n\t\t\t\t\t\t\t\t\t\t? Option.some(decision)\n\t\t\t\t\t\t\t\t\t\t: Option.none(),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\tallAllow: state.allAllow && decision._tag === \"Allow\",\n\t\t\t\t\t\t\t}),\n\t\t\t\t(state) =>\n\t\t\t\t\tstate.allAllow\n\t\t\t\t\t\t? Option.getOrElse(state.firstAllow, () => Decision.NotApplicable)\n\t\t\t\t\t\t: Decision.NotApplicable,\n\t\t\t)\n\t\tcase \"Any\":\n\t\t\t// One allow is enough. Without one, the first deny denies.\n\t\t\treturn walkChildren(\n\t\t\t\tnode.policies,\n\t\t\t\tcontext,\n\t\t\t\tat,\n\t\t\t\tOption.none<Decision.Decision>(),\n\t\t\t\t(firstDeny, decision) =>\n\t\t\t\t\tdecision._tag === \"Allow\"\n\t\t\t\t\t\t? stop(decision)\n\t\t\t\t\t\t: next(\n\t\t\t\t\t\t\t\tOption.orElse(firstDeny, () =>\n\t\t\t\t\t\t\t\t\tdecision._tag === \"Deny\"\n\t\t\t\t\t\t\t\t\t\t? Option.some(decision)\n\t\t\t\t\t\t\t\t\t\t: Option.none(),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t),\n\t\t\t\t(firstDeny) =>\n\t\t\t\t\tOption.getOrElse(firstDeny, () => Decision.NotApplicable),\n\t\t\t)\n\t\tcase \"FirstOf\":\n\t\t\treturn walkChildren(\n\t\t\t\tnode.policies,\n\t\t\t\tcontext,\n\t\t\t\tat,\n\t\t\t\tundefined,\n\t\t\t\t(_, decision) =>\n\t\t\t\t\tdecision._tag === \"NotApplicable\" ? next(undefined) : stop(decision),\n\t\t\t\t() => Decision.NotApplicable,\n\t\t\t)\n\t\tcase \"Ceiling\":\n\t\t\t// A deny of the cap wins. Else the policy decides.\n\t\t\treturn Effect.filterOrElse(\n\t\t\t\tEffect.suspend(() => decideNode(node.cap, context, Place.at(at, 0))),\n\t\t\t\t(capped) => capped._tag === \"Deny\",\n\t\t\t\t() => decideNode(node.policy, context, Place.at(at, 1)),\n\t\t\t)\n\t}\n}\n\n/**\n * Tests if a request fits its kind: the action is an action of the kind, and\n * the field, if there is one, is a field of the kind.\n */\nexport const fitsKind = (\n\tdescriptor: Resource.Descriptor,\n\trequest: { readonly action: string; readonly field?: string | undefined },\n\tparts: number,\n): boolean =>\n\tdescriptor.actions.includes(request.action) &&\n\t(request.field === undefined ||\n\t\t(Catalog.lookup(descriptor.catalog, request.field, parts)?.listed ?? false))\n\nconst makeContext = (\n\tdomain: Domain.Any,\n\trequest: AnyRequest,\n\ttrace: Ref.Ref<Chunk.Chunk<Step>> | undefined,\n): DecideContext | undefined => {\n\tconst kind = ResourceData.kindOf(request.resource)\n\tconst descriptor =\n\t\tkind === undefined ? undefined : DomainData.descriptorOf(domain, kind)\n\tif (\n\t\tkind === undefined ||\n\t\tdescriptor === undefined ||\n\t\t!fitsKind(descriptor, request, domain.limits.fieldPathParts)\n\t) {\n\t\treturn undefined\n\t}\n\treturn {\n\t\taction: request.action,\n\t\tkind,\n\t\tfield: request.field,\n\t\tinput: {\n\t\t\tvalue: request.resource,\n\t\t\tprincipal: request.principal,\n\t\t\tpath: descriptor.path,\n\t\t},\n\t\tdescriptor,\n\t\tfits: RuleData.fitsOf(descriptor.catalog, domain.limits.fieldPathParts),\n\t\tparts: domain.limits.fieldPathParts,\n\t\ttrace,\n\t}\n}\n\nexport const decideAny = (\n\tpolicy: Any,\n\trequest: AnyRequest,\n): Effect.Effect<Decision.Decision, unknown, unknown> =>\n\t// The context reads the resource value, so it is made for each run.\n\tEffect.suspend(() => {\n\t\tconst context = makeContext(policy.domain, request, undefined)\n\t\treturn context === undefined\n\t\t\t? Effect.succeed(Decision.NotApplicable)\n\t\t\t: decideNode(nodeOf(policy), context, undefined)\n\t})\n\n/** Gives the decision of a policy with no predicates, at once. */\nexport const decideSync = (\n\tpolicy: Any,\n\trequest: AnyRequest,\n): Decision.Decision =>\n\tEffect.runSync(\n\t\t// The public type of `decideSync` takes only policies with no\n\t\t// predicates, so this Effect has no error and needs no services.\n\t\t// @effect-diagnostics-next-line unsafeEffectTypeAssertion:off\n\t\tdecideAny(policy, request) as Effect.Effect<Decision.Decision>,\n\t)\n\n/** Gives the decision of a policy and the rules that were tested to make it. */\nexport const explain = (\n\tpolicy: Any,\n\trequest: AnyRequest,\n): Effect.Effect<Explanation, unknown, unknown> =>\n\t// Each run of the Effect needs its own trace, so the trace is made inside\n\t// the Effect.\n\tEffect.gen(function* () {\n\t\tconst trace = yield* Ref.make(Chunk.empty<Step>())\n\t\tconst context = makeContext(policy.domain, request, trace)\n\t\tif (context === undefined) {\n\t\t\treturn { decision: Decision.NotApplicable, steps: [] }\n\t\t}\n\t\tconst decision = yield* decideNode(nodeOf(policy), context, undefined)\n\t\treturn { decision, steps: Chunk.toReadonlyArray(yield* Ref.get(trace)) }\n\t})\n\n/**\n * Tests if a deny rule of the request names a part inside `field`.\n */\nconst deniesInside = (\n\trules: ReadonlyArray<RuleData.Rule>,\n\ttarget: { readonly action: string; readonly kind: string },\n\tfield: string,\n): boolean =>\n\tArr.some(\n\t\trules,\n\t\t(value) =>\n\t\t\tvalue._tag === \"Deny\" &&\n\t\t\tvalue.fields !== undefined &&\n\t\t\tvalue.fields.some((pattern) => RuleData.matchesInside(pattern, field)) &&\n\t\t\t(value.actions.includes(\"*\") || value.actions.includes(target.action)) &&\n\t\t\t(value.resources.includes(\"*\") || value.resources.includes(target.kind)),\n\t)\n\n/** Gives the fields of a resource value that the policy lets a request use. */\nexport const permittedFields = (\n\tpolicy: Any,\n\trequest: Omit<AnyRequest, \"field\">,\n): Effect.Effect<ReadonlyArray<string>, unknown, unknown> => {\n\tconst kind = ResourceData.kindOf(request.resource)\n\tconst descriptor: Resource.Descriptor | undefined =\n\t\tkind === undefined\n\t\t\t? undefined\n\t\t\t: DomainData.descriptorOf(policy.domain, kind)\n\tif (descriptor === undefined) {\n\t\treturn Effect.succeed([])\n\t}\n\t// The rules of the tree, in one flat list for all the fields.\n\tconst rules = policy.pipe(nodeOf, PolicyNodes.rulesOf)\n\t// The fields with names only: a list can not name every key of a record.\n\tconst listed = Arr.filter(\n\t\t// The list for the limit of the parts of a path of the domain, as the\n\t\t// decision of each field reads it.\n\t\tCatalog.fieldsOf(descriptor.catalog, policy.domain.limits.fieldPathParts),\n\t\t(field) =>\n\t\t\tfield.listed &&\n\t\t\tArr.every(field.path, (segment) => typeof segment === \"string\"),\n\t)\n\treturn Effect.map(\n\t\tEffect.filter(listed, (field) =>\n\t\t\tEffect.map(\n\t\t\t\tdecideAny(policy, { ...request, field: field.text }),\n\t\t\t\t(result) =>\n\t\t\t\t\tresult._tag === \"Allow\" &&\n\t\t\t\t\t!(\n\t\t\t\t\t\tfield.open &&\n\t\t\t\t\t\tdeniesInside(\n\t\t\t\t\t\t\trules,\n\t\t\t\t\t\t\t{ ...request, kind: descriptor.kind },\n\t\t\t\t\t\t\tfield.text,\n\t\t\t\t\t\t)\n\t\t\t\t\t),\n\t\t\t),\n\t\t),\n\t\tArr.map((field) => field.text),\n\t)\n}\n"],"mappings":"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"}
|
package/dist/internal/domain.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { exceeded, make as make$1 } from "./limits.js";
|
|
2
|
+
import { catalogOf, fieldsOf } from "./catalog.js";
|
|
2
3
|
import { descriptor } from "./resource.js";
|
|
4
|
+
import * as Arr from "effect/Array";
|
|
3
5
|
import * as HashMap from "effect/HashMap";
|
|
4
6
|
import * as Data from "effect/Data";
|
|
5
|
-
import * as Arr from "effect/Array";
|
|
6
7
|
import * as Option from "effect/Option";
|
|
7
8
|
// src/internal/domain.ts
|
|
8
9
|
/**
|
|
@@ -10,19 +11,32 @@ import * as Option from "effect/Option";
|
|
|
10
11
|
*/
|
|
11
12
|
/** The class of every domain value. `isDomain` uses it to know a domain. */
|
|
12
13
|
var DomainData = class extends Data.Class {};
|
|
13
|
-
/**
|
|
14
|
+
/** Stops a catalog with more fields in its list than the domain takes. */
|
|
15
|
+
const checkCatalog = (catalog, limits, name) => {
|
|
16
|
+
const fields = fieldsOf(catalog, limits.fieldPathParts).length;
|
|
17
|
+
if (fields > limits.catalogFields) throw exceeded(name, "catalogFields", "fields", fields, limits.catalogFields);
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Makes a domain. Each resource must have its own kind name. The limits that
|
|
21
|
+
* the domain does not set have their defaults.
|
|
22
|
+
*/
|
|
14
23
|
const make = (options) => {
|
|
24
|
+
const limits = make$1(options.limits);
|
|
15
25
|
const descriptors = Arr.map(options.resources, descriptor);
|
|
16
26
|
const kinds = Arr.map(descriptors, (descriptor) => descriptor.kind);
|
|
17
27
|
const twice = Arr.findFirst(kinds, (kind, index) => kinds.indexOf(kind) !== index);
|
|
18
28
|
if (Option.isSome(twice)) throw new Error(`Two resources have the kind name "${twice.value}"`);
|
|
19
29
|
if (kinds.includes("*")) throw new Error(`A resource may not have the kind name "*"`);
|
|
20
30
|
const resources = HashMap.fromIterable(Arr.map(descriptors, (descriptor) => [descriptor.kind, descriptor]));
|
|
31
|
+
const principalCatalog = catalogOf(options.principal.ast);
|
|
32
|
+
checkCatalog(principalCatalog, limits, "The Schema of the principal");
|
|
33
|
+
Arr.forEach(descriptors, (descriptor) => checkCatalog(descriptor.catalog, limits, `The Schema of the resource "${descriptor.kind}"`));
|
|
21
34
|
return new DomainData({
|
|
22
35
|
principal: options.principal,
|
|
23
|
-
principalCatalog
|
|
36
|
+
principalCatalog,
|
|
24
37
|
resources,
|
|
25
|
-
meta: options.meta
|
|
38
|
+
meta: options.meta,
|
|
39
|
+
limits
|
|
26
40
|
});
|
|
27
41
|
};
|
|
28
42
|
/** Gives the descriptor of the resource with a kind name. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"domain.js","names":["ResourceData.descriptor","Catalog.catalogOf"],"sources":["../../src/internal/domain.ts"],"sourcesContent":["/**\n * The values of domains. The types are in `Domain.ts`.\n */\nimport * as Arr from \"effect/Array\"\nimport * as Data from \"effect/Data\"\nimport * as HashMap from \"effect/HashMap\"\nimport * as Option from \"effect/Option\"\nimport type * as Schema from \"effect/Schema\"\nimport type * as Domain from \"../Domain.ts\"\nimport type * as Resource from \"../Resource.ts\"\nimport * as Catalog from \"./catalog.ts\"\nimport * as ResourceData from \"./resource.ts\"\n\n/** The class of every domain value. `isDomain` uses it to know a domain. */\nclass DomainData extends Data.Class<{\n\treadonly principal: Schema.Top\n\treadonly principalCatalog: Catalog.Catalog\n\treadonly resources: HashMap.HashMap<string, Resource.Descriptor>\n\treadonly meta: Schema.Top | undefined\n}> {}\n\n/** Makes a domain. Each resource must have its own kind name. */\nexport const make = (options: {\n\treadonly principal: Schema.Top\n\treadonly resources: ReadonlyArray<Resource.Any>\n\treadonly meta?: Schema.Top\n}): DomainData => {\n\tconst descriptors = Arr.map(options.resources, ResourceData.descriptor)\n\tconst kinds = Arr.map(descriptors, (descriptor) => descriptor.kind)\n\t// Two resources with one kind name is a mistake in the code, not in the\n\t// data, so it is a defect.\n\tconst twice = Arr.findFirst(\n\t\tkinds,\n\t\t(kind, index) => kinds.indexOf(kind) !== index,\n\t)\n\tif (Option.isSome(twice)) {\n\t\tthrow new Error(`Two resources have the kind name \"${twice.value}\"`)\n\t}\n\tif (kinds.includes(\"*\")) {\n\t\tthrow new Error(`A resource may not have the kind name \"*\"`)\n\t}\n\tconst resources = HashMap.fromIterable(\n\t\tArr.map(\n\t\t\tdescriptors,\n\t\t\t(descriptor) => [descriptor.kind, descriptor] as const,\n\t\t),\n\t)\n\t// The catalog checks the field names of the principal, so a bad name is a\n\t// defect when the domain is made.\n\treturn new DomainData({\n\t\tprincipal: options.principal,\n\t\tprincipalCatalog
|
|
1
|
+
{"version":3,"file":"domain.js","names":["Catalog.fieldsOf","Limits.exceeded","Limits.make","ResourceData.descriptor","Catalog.catalogOf"],"sources":["../../src/internal/domain.ts"],"sourcesContent":["/**\n * The values of domains. The types are in `Domain.ts`.\n */\nimport * as Arr from \"effect/Array\"\nimport * as Data from \"effect/Data\"\nimport * as HashMap from \"effect/HashMap\"\nimport * as Option from \"effect/Option\"\nimport type * as Schema from \"effect/Schema\"\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 ResourceData from \"./resource.ts\"\n\n/** The class of every domain value. `isDomain` uses it to know a domain. */\nclass DomainData extends Data.Class<{\n\treadonly principal: Schema.Top\n\treadonly principalCatalog: Catalog.Catalog\n\treadonly resources: HashMap.HashMap<string, Resource.Descriptor>\n\treadonly meta: Schema.Top | undefined\n\treadonly limits: Limits.Limits\n}> {}\n\n/** Stops a catalog with more fields in its list than the domain takes. */\nconst checkCatalog = (\n\tcatalog: Catalog.Catalog,\n\tlimits: Limits.Limits,\n\tname: string,\n): void => {\n\tconst fields = Catalog.fieldsOf(catalog, limits.fieldPathParts).length\n\tif (fields > limits.catalogFields) {\n\t\tthrow Limits.exceeded(\n\t\t\tname,\n\t\t\t\"catalogFields\",\n\t\t\t\"fields\",\n\t\t\tfields,\n\t\t\tlimits.catalogFields,\n\t\t)\n\t}\n}\n\n/**\n * Makes a domain. Each resource must have its own kind name. The limits that\n * the domain does not set have their defaults.\n */\nexport const make = (options: {\n\treadonly principal: Schema.Top\n\treadonly resources: ReadonlyArray<Resource.Any>\n\treadonly meta?: Schema.Top\n\treadonly limits?: Partial<Limits.Limits>\n}): DomainData => {\n\tconst limits = Limits.make(options.limits)\n\tconst descriptors = Arr.map(options.resources, ResourceData.descriptor)\n\tconst kinds = Arr.map(descriptors, (descriptor) => descriptor.kind)\n\t// Two resources with one kind name is a mistake in the code, not in the\n\t// data, so it is a defect.\n\tconst twice = Arr.findFirst(\n\t\tkinds,\n\t\t(kind, index) => kinds.indexOf(kind) !== index,\n\t)\n\tif (Option.isSome(twice)) {\n\t\tthrow new Error(`Two resources have the kind name \"${twice.value}\"`)\n\t}\n\tif (kinds.includes(\"*\")) {\n\t\tthrow new Error(`A resource may not have the kind name \"*\"`)\n\t}\n\tconst resources = HashMap.fromIterable(\n\t\tArr.map(\n\t\t\tdescriptors,\n\t\t\t(descriptor) => [descriptor.kind, descriptor] as const,\n\t\t),\n\t)\n\t// The catalog checks the field names of the principal, so a bad name is a\n\t// defect when the domain is made. A Schema with more fields than the\n\t// domain takes is a defect too.\n\tconst principalCatalog = Catalog.catalogOf(options.principal.ast)\n\tcheckCatalog(principalCatalog, limits, \"The Schema of the principal\")\n\tArr.forEach(descriptors, (descriptor) =>\n\t\tcheckCatalog(\n\t\t\tdescriptor.catalog,\n\t\t\tlimits,\n\t\t\t`The Schema of the resource \"${descriptor.kind}\"`,\n\t\t),\n\t)\n\treturn new DomainData({\n\t\tprincipal: options.principal,\n\t\tprincipalCatalog,\n\t\tresources,\n\t\tmeta: options.meta,\n\t\tlimits,\n\t})\n}\n\n/** Gives the descriptor of the resource with a kind name. */\nexport const descriptorOf = (\n\tdomain: Domain.Any,\n\tkind: string,\n): Resource.Descriptor | undefined =>\n\tOption.getOrUndefined(HashMap.get(domain.resources, kind))\n\n/** Tests if a value is a domain. */\nexport const isDomain = (u: unknown): u is Domain.Any => u instanceof DomainData\n"],"mappings":";;;;;;;;;;;;AAeA,IAAM,aAAN,cAAyB,KAAK,MAM3B,CAAC;;AAGJ,MAAM,gBACL,SACA,QACA,SACU;CACV,MAAM,SAASA,SAAiB,SAAS,OAAO,cAAc,CAAC,CAAC;CAChE,IAAI,SAAS,OAAO,eACnB,MAAMC,SACL,MACA,iBACA,UACA,QACA,OAAO,aACR;AAEF;;;;;AAMA,MAAa,QAAQ,YAKH;CACjB,MAAM,SAASC,OAAY,QAAQ,MAAM;CACzC,MAAM,cAAc,IAAI,IAAI,QAAQ,WAAWC,UAAuB;CACtE,MAAM,QAAQ,IAAI,IAAI,cAAc,eAAe,WAAW,IAAI;CAGlE,MAAM,QAAQ,IAAI,UACjB,QACC,MAAM,UAAU,MAAM,QAAQ,IAAI,MAAM,KAC1C;CACA,IAAI,OAAO,OAAO,KAAK,GACtB,MAAM,IAAI,MAAM,qCAAqC,MAAM,MAAM,EAAE;CAEpE,IAAI,MAAM,SAAS,GAAG,GACrB,MAAM,IAAI,MAAM,2CAA2C;CAE5D,MAAM,YAAY,QAAQ,aACzB,IAAI,IACH,cACC,eAAe,CAAC,WAAW,MAAM,UAAU,CAC7C,CACD;CAIA,MAAM,mBAAmBC,UAAkB,QAAQ,UAAU,GAAG;CAChE,aAAa,kBAAkB,QAAQ,6BAA6B;CACpE,IAAI,QAAQ,cAAc,eACzB,aACC,WAAW,SACX,QACA,+BAA+B,WAAW,KAAK,EAChD,CACD;CACA,OAAO,IAAI,WAAW;EACrB,WAAW,QAAQ;EACnB;EACA;EACA,MAAM,QAAQ;EACd;CACD,CAAC;AACF;;AAGA,MAAa,gBACZ,QACA,SAEA,OAAO,eAAe,QAAQ,IAAI,OAAO,WAAW,IAAI,CAAC"}
|
package/dist/internal/json.js
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
import { LimitExceeded,
|
|
1
|
+
import { LimitExceeded, defaults } from "./limits.js";
|
|
2
|
+
import { adaptive } from "./trampoline.js";
|
|
2
3
|
import { And, Compare, Exists, In, Not, Or, Under, always, isCompareOperator, isScalar, literal, never, principalRef, toJson as toJson$1 } from "./nodes.js";
|
|
3
4
|
import { readPart } from "./read.js";
|
|
5
|
+
import { at, pathOf } from "./place.js";
|
|
6
|
+
import * as Arr from "effect/Array";
|
|
4
7
|
import * as Result from "effect/Result";
|
|
5
8
|
import * as Data from "effect/Data";
|
|
6
|
-
import * as Arr from "effect/Array";
|
|
7
9
|
// src/internal/json.ts
|
|
8
10
|
/**
|
|
9
|
-
* The JSON form of a condition. The decoder is a
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
11
|
+
* The JSON form of a condition. The decoder is a walk of the trampoline, so a
|
|
12
|
+
* deep input does not fill the stack. It reads at most `jsonNodes` objects
|
|
13
|
+
* and array items, with a shared object counted for each place, so no input
|
|
14
|
+
* can take a very long time. It makes
|
|
13
15
|
* each node with its constructor, and it reads only the own data fields of
|
|
14
16
|
* the input, as a condition reads a value.
|
|
15
17
|
*/
|
|
@@ -21,106 +23,148 @@ const problem = (path, message) => new Problem({
|
|
|
21
23
|
path,
|
|
22
24
|
message
|
|
23
25
|
});
|
|
26
|
+
/**
|
|
27
|
+
* The problem at a place of the input. A decoder keeps the place of each
|
|
28
|
+
* part, which shares the path of its parent, and makes the full path only for
|
|
29
|
+
* a problem: see `Place`.
|
|
30
|
+
*/
|
|
31
|
+
const problemAt = (at, message) => problem(pathOf(at), message);
|
|
24
32
|
/** Counts `count` more objects or items, and stops past the limit. */
|
|
25
|
-
const spend = (budget, count,
|
|
33
|
+
const spend = (budget, count, at) => {
|
|
26
34
|
budget.used += count;
|
|
27
|
-
|
|
35
|
+
const most = budget.limits.jsonNodes;
|
|
36
|
+
if (budget.used > most) throw problemAt(at, `The input has more than ${String(most)} objects and items. The most is ${String(most)}.`);
|
|
28
37
|
};
|
|
29
|
-
/**
|
|
30
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Reads a field of the input at the place `at`. A field that is not plain
|
|
40
|
+
* data is a problem.
|
|
41
|
+
*/
|
|
42
|
+
const read = (input, name, at$5) => {
|
|
31
43
|
try {
|
|
32
44
|
return readPart(input, name);
|
|
33
45
|
} catch {
|
|
34
|
-
throw
|
|
46
|
+
throw problemAt(at(at$5, name), `The field "${name}" is not plain data`);
|
|
35
47
|
}
|
|
36
48
|
};
|
|
37
|
-
const readString = (input, name,
|
|
38
|
-
const value = read(input, name,
|
|
39
|
-
if (typeof value !== "string") throw
|
|
49
|
+
const readString = (input, name, at$2) => {
|
|
50
|
+
const value = read(input, name, at$2);
|
|
51
|
+
if (typeof value !== "string") throw problemAt(at(at$2, name), `The field "${name}" must be a string`);
|
|
40
52
|
return value;
|
|
41
53
|
};
|
|
42
|
-
/** Reads the items of an array. */
|
|
43
|
-
const readList = (input, name,
|
|
44
|
-
const value = read(input, name,
|
|
45
|
-
const
|
|
46
|
-
if (!Array.isArray(value)) throw
|
|
54
|
+
/** Reads the items of an array. The place of an item is `at`, `name`, index. */
|
|
55
|
+
const readList = (input, name, at$3, budget) => {
|
|
56
|
+
const value = read(input, name, at$3);
|
|
57
|
+
const place = at(at$3, name);
|
|
58
|
+
if (!Array.isArray(value)) throw problemAt(place, `The field "${name}" must be an array`);
|
|
47
59
|
const list = value;
|
|
48
|
-
spend(budget, list.length,
|
|
49
|
-
return Array.from({ length: list.length }, (_, index) => read(list, String(index),
|
|
60
|
+
spend(budget, list.length, place);
|
|
61
|
+
return Array.from({ length: list.length }, (_, index) => read(list, String(index), place));
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Reads a field path, and stops one with more parts than the limit of the
|
|
65
|
+
* decode. A node checks the ceiling only, since it does not know its domain.
|
|
66
|
+
*/
|
|
67
|
+
const readPath = (input, name, at$7, budget) => {
|
|
68
|
+
const path = readString(input, name, at$7);
|
|
69
|
+
const most = budget.limits.fieldPathParts;
|
|
70
|
+
if (path.split(".", most + 1).length > most) throw problemAt(at(at$7, name), `A field path has more than ${String(most)} parts. The most is ${String(most)}.`);
|
|
71
|
+
return path;
|
|
50
72
|
};
|
|
51
73
|
const isJsonScalar = (value) => isScalar(value) && (typeof value !== "number" || Number.isFinite(value));
|
|
52
|
-
const decodeOperand = (input,
|
|
53
|
-
const tag = read(input, "_tag",
|
|
74
|
+
const decodeOperand = (input, at$4, budget) => {
|
|
75
|
+
const tag = read(input, "_tag", at$4);
|
|
54
76
|
if (tag === "Literal") {
|
|
55
77
|
const own = typeof input === "object" && input !== null ? Object.getOwnPropertyDescriptor(input, "value") : void 0;
|
|
56
|
-
if (own === void 0 || "value" in own && own.value === void 0) throw
|
|
57
|
-
const value = read(input, "value",
|
|
58
|
-
if (!isJsonScalar(value)) throw
|
|
78
|
+
if (own === void 0 || "value" in own && own.value === void 0) throw problemAt(at(at$4, "value"), "A fixed value is missing");
|
|
79
|
+
const value = read(input, "value", at$4);
|
|
80
|
+
if (!isJsonScalar(value)) throw problemAt(at(at$4, "value"), "A fixed value must be a string, a finite number, a boolean or null");
|
|
59
81
|
return literal(value);
|
|
60
82
|
}
|
|
61
|
-
if (tag === "Principal") return principalRef(
|
|
62
|
-
throw
|
|
83
|
+
if (tag === "Principal") return principalRef(readPath(input, "path", at$4, budget));
|
|
84
|
+
throw problemAt(at(at$4, "_tag"), "An operand must have the tag \"Literal\" or \"Principal\"");
|
|
63
85
|
};
|
|
64
|
-
const decodeOperands = (input, name,
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
+
/**
|
|
88
|
+
* The decoder of a condition, with the budget of one decode. It is a walk of
|
|
89
|
+
* the trampoline for an input of unknown depth, so a deep input does not fill
|
|
90
|
+
* the stack.
|
|
91
|
+
*/
|
|
92
|
+
const decoder = (budget) => {
|
|
93
|
+
const count = { nodes: 0 };
|
|
94
|
+
return adaptive((R, child) => (part, depth) => {
|
|
95
|
+
const { input, at: at$1 } = part;
|
|
96
|
+
if (typeof input !== "object" || input === null) throw problemAt(at$1, "A condition node must be an object");
|
|
97
|
+
spend(budget, 1, at$1);
|
|
98
|
+
count.nodes += 1;
|
|
99
|
+
if (count.nodes > budget.limits.conditionNodes) throw problemAt(at$1, `A condition has more than ${String(budget.limits.conditionNodes)} nodes and values. The most is ${String(budget.limits.conditionNodes)}.`);
|
|
100
|
+
const make = (build) => {
|
|
101
|
+
const node = (() => {
|
|
102
|
+
try {
|
|
103
|
+
return build();
|
|
104
|
+
} catch (error) {
|
|
105
|
+
if (error instanceof LimitExceeded) throw problemAt(at$1, error.message);
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
})();
|
|
109
|
+
const most = budget.limits.conditionNodes;
|
|
110
|
+
if (node.weight > most) throw problemAt(at$1, `A condition has ${String(node.weight)} nodes and values. The most is ${String(most)}.`);
|
|
111
|
+
return node;
|
|
112
|
+
};
|
|
113
|
+
const children = (name) => R.forEach(readList(input, name, at$1, budget), (item, index) => child({
|
|
114
|
+
input: item,
|
|
115
|
+
at: at(at$1, name, index)
|
|
116
|
+
}, depth + 1));
|
|
117
|
+
switch (read(input, "_tag", at$1)) {
|
|
118
|
+
case "True": return R.succeed(always);
|
|
119
|
+
case "False": return R.succeed(never);
|
|
120
|
+
case "Compare": {
|
|
121
|
+
const op = readString(input, "op", at$1);
|
|
122
|
+
if (!isCompareOperator(op)) throw problemAt(at(at$1, "op"), `"${op}" is not a compare operator`);
|
|
123
|
+
return R.succeed(make(() => new Compare({
|
|
124
|
+
op,
|
|
125
|
+
field: readPath(input, "field", at$1, budget),
|
|
126
|
+
value: decodeOperand(read(input, "value", at$1), at(at$1, "value"), budget)
|
|
127
|
+
})));
|
|
128
|
+
}
|
|
129
|
+
case "In": return R.succeed(make(() => new In({
|
|
130
|
+
field: readPath(input, "field", at$1, budget),
|
|
131
|
+
values: decodeOperands(input, "values", at$1, budget)
|
|
132
|
+
})));
|
|
133
|
+
case "Exists": return R.succeed(make(() => new Exists({ field: readPath(input, "field", at$1, budget) })));
|
|
134
|
+
case "Under": return R.succeed(make(() => new Under({ prefix: decodeOperands(input, "prefix", at$1, budget) })));
|
|
135
|
+
case "And": return R.map(children("conditions"), (conditions) => make(() => new And(conditions)));
|
|
136
|
+
case "Or": return R.map(children("conditions"), (conditions) => make(() => new Or(conditions)));
|
|
137
|
+
case "Not": return R.map(child({
|
|
138
|
+
input: read(input, "condition", at$1),
|
|
139
|
+
at: at(at$1, "condition")
|
|
140
|
+
}, depth + 1), (condition) => make(() => new Not(condition)));
|
|
141
|
+
default: throw problemAt(at(at$1, "_tag"), "The node has no known tag");
|
|
97
142
|
}
|
|
98
|
-
|
|
99
|
-
field: readString(input, "field", path),
|
|
100
|
-
values: decodeOperands(input, "values", path, budget)
|
|
101
|
-
});
|
|
102
|
-
case "Exists": return new Exists({ field: readString(input, "field", path) });
|
|
103
|
-
case "Under": return new Under({ prefix: decodeOperands(input, "prefix", path, budget) });
|
|
104
|
-
case "And": return new And(children("conditions"));
|
|
105
|
-
case "Or": return new Or(children("conditions"));
|
|
106
|
-
case "Not": return new Not(decodeNode(read(input, "condition", path), [...path, "condition"], depth + 1, budget));
|
|
107
|
-
default: throw problem([...path, "_tag"], "The node has no known tag");
|
|
108
|
-
}
|
|
143
|
+
});
|
|
109
144
|
};
|
|
110
145
|
/**
|
|
111
146
|
* Decodes the JSON form of a condition at a path, with the budget of a larger
|
|
112
147
|
* decode. It throws a {@link Problem}.
|
|
113
148
|
*/
|
|
114
|
-
const decodeAt = (input,
|
|
115
|
-
|
|
149
|
+
const decodeAt = (input, at, budget) => decoder(budget)({
|
|
150
|
+
input,
|
|
151
|
+
at
|
|
152
|
+
});
|
|
153
|
+
/** Decodes the JSON form of a condition, with the default limits. */
|
|
116
154
|
const decode = (input) => {
|
|
117
155
|
try {
|
|
118
|
-
return Result.succeed(
|
|
156
|
+
return Result.succeed(decoder({
|
|
157
|
+
used: 0,
|
|
158
|
+
limits: defaults
|
|
159
|
+
})({
|
|
160
|
+
input,
|
|
161
|
+
at: void 0
|
|
162
|
+
}));
|
|
119
163
|
} catch (error) {
|
|
120
164
|
if (error instanceof Problem) return Result.fail(error);
|
|
121
165
|
throw error;
|
|
122
166
|
}
|
|
123
167
|
};
|
|
124
|
-
export { Problem, decode, decodeAt, problem, read, readList, readString, spend, toJson };
|
|
168
|
+
export { Problem, decode, decodeAt, problem, problemAt, read, readList, readString, spend, toJson };
|
|
125
169
|
|
|
126
170
|
//# sourceMappingURL=json.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"json.js","names":["Nodes.toJson","Limits.jsonNodes","Nodes.isScalar","Nodes.literal","Nodes.principalRef","Limits.LimitExceeded","Nodes.always","Nodes.never","Nodes.isCompareOperator","Nodes.Compare","Nodes.In","Nodes.Exists","Nodes.Under","Nodes.And","Nodes.Or","Nodes.Not"],"sources":["../../src/internal/json.ts"],"sourcesContent":["/**\n * The JSON form of a condition. The decoder is a plain walk with two limits:\n * it goes at most `conditionDepth` levels down, and it reads at most\n * `jsonNodes` objects and array items, with a shared object counted for each\n * place. So no input can fill the stack or 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 { readPart } from \"./read.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/** The count of the objects and items that one decode has read. It belongs to one decode. */\nexport interface Budget {\n\tused: number\n}\n\nexport const problem = (path: ReadonlyArray<PropertyKey>, message: string) =>\n\tnew Problem({ path, message })\n\n/** Counts `count` more objects or items, and stops past the limit. */\nexport const spend = (\n\tbudget: Budget,\n\tcount: number,\n\tpath: ReadonlyArray<PropertyKey>,\n): void => {\n\tbudget.used += count\n\tif (budget.used > Limits.jsonNodes) {\n\t\tthrow problem(\n\t\t\tpath,\n\t\t\t`The input has more than ${String(Limits.jsonNodes)} objects and items. The most is ${String(Limits.jsonNodes)}.`,\n\t\t)\n\t}\n}\n\n/** Reads a field of the input. A field that is not plain data is a problem. */\nexport const read = (\n\tinput: unknown,\n\tname: string,\n\tpath: ReadonlyArray<PropertyKey>,\n): unknown => {\n\ttry {\n\t\treturn readPart(input, name)\n\t} catch {\n\t\tthrow problem([...path, name], `The field \"${name}\" is not plain data`)\n\t}\n}\n\nexport const readString = (\n\tinput: unknown,\n\tname: string,\n\tpath: ReadonlyArray<PropertyKey>,\n): string => {\n\tconst value = read(input, name, path)\n\tif (typeof value !== \"string\") {\n\t\tthrow problem([...path, name], `The field \"${name}\" must be a string`)\n\t}\n\treturn value\n}\n\n/** Reads the items of an array. */\nexport const readList = (\n\tinput: unknown,\n\tname: string,\n\tpath: ReadonlyArray<PropertyKey>,\n\tbudget: Budget,\n): ReadonlyArray<unknown> => {\n\tconst value = read(input, name, path)\n\tconst at = [...path, name]\n\tif (!Array.isArray(value)) {\n\t\tthrow problem(at, `The field \"${name}\" must be an array`)\n\t}\n\tconst list: ReadonlyArray<unknown> = value\n\tspend(budget, list.length, at)\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), at),\n\t)\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\tpath: ReadonlyArray<PropertyKey>,\n): Nodes.Operand => {\n\tconst tag = read(input, \"_tag\", path)\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 problem([...path, \"value\"], \"A fixed value is missing\")\n\t\t}\n\t\tconst value = read(input, \"value\", path)\n\t\tif (!isJsonScalar(value)) {\n\t\t\tthrow problem(\n\t\t\t\t[...path, \"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(readString(input, \"path\", path))\n\t}\n\tthrow problem(\n\t\t[...path, \"_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\tpath: ReadonlyArray<PropertyKey>,\n\tbudget: Budget,\n): ReadonlyArray<Nodes.Operand> =>\n\tArr.map(readList(input, name, path, budget), (operand, index) =>\n\t\tdecodeOperand(operand, [...path, name, index]),\n\t)\n\nconst decodeNode = (\n\tinput: unknown,\n\tpath: ReadonlyArray<PropertyKey>,\n\tdepth: number,\n\tbudget: Budget,\n): Node => {\n\tif (depth > Limits.conditionDepth) {\n\t\tthrow problem(\n\t\t\tpath,\n\t\t\t`A condition has more than ${String(Limits.conditionDepth)} levels. The most is ${String(Limits.conditionDepth)}.`,\n\t\t)\n\t}\n\tif (typeof input !== \"object\" || input === null) {\n\t\tthrow problem(path, \"A condition node must be an object\")\n\t}\n\tspend(budget, 1, path)\n\tconst children = (name: string) =>\n\t\tArr.map(readList(input, name, path, budget), (child, index) =>\n\t\t\tdecodeNode(child, [...path, name, index], depth + 1, budget),\n\t\t)\n\ttry {\n\t\treturn makeNode(input, path, depth, budget, children)\n\t} catch (error) {\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.\n\t\tif (error instanceof Limits.LimitExceeded) {\n\t\t\tthrow problem(path, error.message)\n\t\t}\n\t\tthrow error\n\t}\n}\n\nconst makeNode = (\n\tinput: object,\n\tpath: ReadonlyArray<PropertyKey>,\n\tdepth: number,\n\tbudget: Budget,\n\tchildren: (name: string) => ReadonlyArray<Node>,\n): Node => {\n\tconst tag = read(input, \"_tag\", path)\n\tswitch (tag) {\n\t\tcase \"True\":\n\t\t\treturn Nodes.always\n\t\tcase \"False\":\n\t\t\treturn Nodes.never\n\t\tcase \"Compare\": {\n\t\t\tconst op = readString(input, \"op\", path)\n\t\t\tif (!Nodes.isCompareOperator(op)) {\n\t\t\t\tthrow problem([...path, \"op\"], `\"${op}\" is not a compare operator`)\n\t\t\t}\n\t\t\treturn new Nodes.Compare({\n\t\t\t\top,\n\t\t\t\tfield: readString(input, \"field\", path),\n\t\t\t\tvalue: decodeOperand(read(input, \"value\", path), [...path, \"value\"]),\n\t\t\t})\n\t\t}\n\t\tcase \"In\":\n\t\t\treturn new Nodes.In({\n\t\t\t\tfield: readString(input, \"field\", path),\n\t\t\t\tvalues: decodeOperands(input, \"values\", path, budget),\n\t\t\t})\n\t\tcase \"Exists\":\n\t\t\treturn new Nodes.Exists({ field: readString(input, \"field\", path) })\n\t\tcase \"Under\":\n\t\t\treturn new Nodes.Under({\n\t\t\t\tprefix: decodeOperands(input, \"prefix\", path, budget),\n\t\t\t})\n\t\tcase \"And\":\n\t\t\treturn new Nodes.And(children(\"conditions\"))\n\t\tcase \"Or\":\n\t\t\treturn new Nodes.Or(children(\"conditions\"))\n\t\tcase \"Not\":\n\t\t\treturn new Nodes.Not(\n\t\t\t\tdecodeNode(\n\t\t\t\t\tread(input, \"condition\", path),\n\t\t\t\t\t[...path, \"condition\"],\n\t\t\t\t\tdepth + 1,\n\t\t\t\t\tbudget,\n\t\t\t\t),\n\t\t\t)\n\t\tdefault:\n\t\t\tthrow problem([...path, \"_tag\"], \"The node has no known tag\")\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\tpath: ReadonlyArray<PropertyKey>,\n\tbudget: Budget,\n): Node => decodeNode(input, path, 1, budget)\n\n/** Decodes the JSON form of a condition. */\nexport const decode = (input: unknown): Result.Result<Node, Problem> => {\n\ttry {\n\t\treturn Result.succeed(decodeNode(input, [], 1, { used: 0 }))\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\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,4 +1,32 @@
|
|
|
1
1
|
import * as Schema from "effect/Schema";
|
|
2
|
+
/** The values of all the limits. */
|
|
3
|
+
export interface Limits {
|
|
4
|
+
/** The most parts of a field path, as in `settings.model`. */
|
|
5
|
+
readonly fieldPathParts: number;
|
|
6
|
+
/** The most parts of a field pattern, as in `**.secret`. */
|
|
7
|
+
readonly patternParts: number;
|
|
8
|
+
/**
|
|
9
|
+
* The most nodes of one condition. A child that a condition uses in many
|
|
10
|
+
* places is counted for each place.
|
|
11
|
+
*/
|
|
12
|
+
readonly conditionNodes: number;
|
|
13
|
+
/** The most nodes of one residual condition. */
|
|
14
|
+
readonly residualNodes: number;
|
|
15
|
+
/**
|
|
16
|
+
* The most nodes of a policy tree, with its rules and the nodes of their
|
|
17
|
+
* conditions. A part that the tree uses in many places is counted for
|
|
18
|
+
* each place.
|
|
19
|
+
*/
|
|
20
|
+
readonly policyNodes: number;
|
|
21
|
+
/** The most objects and arrays in one input to a decoder. */
|
|
22
|
+
readonly jsonNodes: number;
|
|
23
|
+
/** The most fields in the list of the fields of one Schema. */
|
|
24
|
+
readonly catalogFields: number;
|
|
25
|
+
/** The most steps of one search for a value. */
|
|
26
|
+
readonly searchSteps: number;
|
|
27
|
+
/** The most fields that one search for a value may test. */
|
|
28
|
+
readonly searchFields: number;
|
|
29
|
+
}
|
|
2
30
|
declare const LimitExceeded_base: Schema.Class<LimitExceeded, Schema.TaggedStruct<"LimitExceeded", {
|
|
3
31
|
readonly limit: Schema.String;
|
|
4
32
|
readonly size: Schema.Finite;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"limits.d.ts","names":[],"sources":["../../src/internal/limits.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"limits.d.ts","names":[],"sources":["../../src/internal/limits.ts"],"mappings":";;iBAQiB;;WAEP;;WAEA;;;;;WAKA;;WAEA;;;;;;WAMA;;WAEA;;WAEA;;WAEA;;WAEA;;;;;;;;;;;;qBAyFG,sBAAsB"}
|