@nmnmcc/ability 2.1.3 → 2.1.4

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.
@@ -1 +1 @@
1
- {"version":3,"file":"policy-json.js","names":["PolicyNodes.rulesOf","Json.toJson","Trampoline.run","Json.readList","at","Json.problemAt","Place.at","read","Trampoline.adaptive","Json.read","Json.Problem","Place.pathOf","Json.decodeAt","Json.readString","Rule.make","PolicyNodes.isAlgorithm","Limits.LimitExceeded","PolicyNodes.Rules","PolicyNodes","PolicyNodes.Ceiling"],"sources":["../../src/internal/policy-json.ts"],"sourcesContent":["/**\n * The JSON form of a policy tree. The decoder is a plain walk, as the decoder\n * of a condition is, and the two share one budget of `jsonNodes` objects and\n * items. It makes each node with its constructor, so the limits of the nodes\n * hold for a decoded tree too. The binding of the rules to the domain comes\n * after, in the Schema of the policy.\n */\nimport * as Arr from \"effect/Array\"\nimport * as Effect from \"effect/Effect\"\nimport * as Schema from \"effect/Schema\"\nimport type * as Domain from \"../Domain.ts\"\nimport * as Rule from \"./rule.ts\"\nimport * as Json from \"./json.ts\"\nimport * as Limits from \"./limits.ts\"\nimport * as Place from \"./place.ts\"\nimport * as PolicyNodes from \"./policy-nodes.ts\"\nimport * as Trampoline from \"./trampoline.ts\"\n\ntype Node = PolicyNodes.Node\n\n/**\n * The codec of the `meta` Schema of a domain to and from JSON. The JSON form\n * of a policy must stay the same through `JSON.stringify` and `JSON.parse`, so\n * a `Date` in `meta`, for one, becomes a string in it, and a string again\n * when it is decoded.\n */\nconst metaCodec = (meta: Schema.Top): Schema.Codec<unknown, unknown> =>\n\tSchema.toCodecJson(meta) as unknown as Schema.Codec<unknown, unknown>\n\n/** Gives the JSON form of the `meta` of a rule, or `undefined` for none. */\nexport type MetaJson = (rule: Rule.Rule) => unknown\n\n/**\n * Gives the JSON form of the `meta` of each rule at once, with the `meta`\n * Schema of a domain. `JSON.stringify` needs it at once, so the Schema must\n * not need an async step there.\n */\nexport const syncMeta =\n\t(meta: Schema.Top | undefined): MetaJson =>\n\t(rule) =>\n\t\trule.meta === undefined || meta === undefined\n\t\t\t? undefined\n\t\t\t: Schema.encodeUnknownSync(metaCodec(meta))(rule.meta)\n\n/** The JSON form of the condition of each rule, by the rule. */\ntype WhenJson = (rule: Rule.Rule) => unknown\n\n/** Gives the JSON form of a rule. The domain of a rule is not in it. */\nconst ruleJson = (\n\trule: Rule.Rule,\n\tmetaJson: MetaJson,\n\twhenJson: WhenJson,\n): unknown => {\n\tconst meta = metaJson(rule)\n\treturn {\n\t\t_tag: rule._tag,\n\t\tactions: rule.actions,\n\t\tresources: rule.resources,\n\t\t...(rule.when === undefined ? {} : { when: whenJson(rule) }),\n\t\t...(rule.fields === undefined ? {} : { fields: rule.fields }),\n\t\t...(rule.reason === undefined ? {} : { reason: rule.reason }),\n\t\t...(rule.id === undefined ? {} : { id: rule.id }),\n\t\t// The `meta` of a rule is a decoded value. Its JSON form comes from the\n\t\t// `meta` Schema of the domain.\n\t\t...(meta === undefined ? {} : { meta }),\n\t}\n}\n\n/**\n * Gives the JSON form of a policy tree with no predicate. A condition with a\n * predicate has no JSON form, so it throws an error. The conditions are\n * written first, from a flat list of the rules, so the walk does not go down\n * the levels of the tree and of a condition at once.\n */\nexport const toJson = (node: Node, meta: MetaJson): unknown => {\n\t// A native map, because it must find a rule by the rule itself: two rules\n\t// with the same data are `Equal`.\n\tconst whens = new Map<Rule.Rule, unknown>(\n\t\tArr.map(PolicyNodes.rulesOf(node), (rule) => [\n\t\t\trule,\n\t\t\trule.when === undefined ? undefined : Json.toJson(rule.when as never),\n\t\t]),\n\t)\n\t// The walk of the tree takes the steps of a trampoline, so a deep tree\n\t// does not fill the stack.\n\treturn Trampoline.run(node.depth, (R) => {\n\t\tconst tree = (current: Node): Trampoline.Step<unknown> => {\n\t\t\tswitch (current._tag) {\n\t\t\t\tcase \"Rules\":\n\t\t\t\t\treturn R.succeed({\n\t\t\t\t\t\t_tag: \"Rules\",\n\t\t\t\t\t\talgorithm: current.algorithm,\n\t\t\t\t\t\trules: Arr.map(current.rules, (rule) =>\n\t\t\t\t\t\t\truleJson(rule, meta, (found) => whens.get(found)),\n\t\t\t\t\t\t),\n\t\t\t\t\t})\n\t\t\t\tcase \"All\":\n\t\t\t\tcase \"Any\":\n\t\t\t\tcase \"FirstOf\": {\n\t\t\t\t\tconst tag = current._tag\n\t\t\t\t\treturn R.map(\n\t\t\t\t\t\tR.forEach(current.policies, (child) =>\n\t\t\t\t\t\t\tR.suspend(() => tree(child)),\n\t\t\t\t\t\t),\n\t\t\t\t\t\t(policies) => ({ _tag: tag, policies }),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tcase \"Ceiling\":\n\t\t\t\t\treturn R.flatMap(\n\t\t\t\t\t\tR.suspend(() => tree(current.cap)),\n\t\t\t\t\t\t(cap) =>\n\t\t\t\t\t\t\tR.map(\n\t\t\t\t\t\t\t\tR.suspend(() => tree(current.policy)),\n\t\t\t\t\t\t\t\t(policy) => ({ _tag: \"Ceiling\", cap, policy }),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\treturn tree(node)\n\t})\n}\n\n/**\n * Gives the JSON form of a policy tree as an Effect. The `meta` Schema of the\n * domain may need an async step, so each `meta` is encoded as an Effect.\n */\nexport const encode = (\n\tnode: Node,\n\tmeta: Schema.Top | undefined,\n): Effect.Effect<unknown, Schema.SchemaError> =>\n\tEffect.gen(function* () {\n\t\tconst rules = Arr.filter(\n\t\t\tPolicyNodes.rulesOf(node),\n\t\t\t(rule) => rule.meta !== undefined,\n\t\t)\n\t\tconst encoded =\n\t\t\tmeta === undefined\n\t\t\t\t? []\n\t\t\t\t: yield* Effect.forEach(rules, (rule) =>\n\t\t\t\t\t\tEffect.map(\n\t\t\t\t\t\t\tSchema.encodeUnknownEffect(metaCodec(meta))(rule.meta),\n\t\t\t\t\t\t\t(json) => [rule, json] as const,\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t// A native map, because it must find a rule by the rule itself: two\n\t\t// rules with the same data are `Equal`, and each keeps its own form.\n\t\tconst forms = new Map<Rule.Rule, unknown>(encoded)\n\t\treturn toJson(node, (rule) => forms.get(rule))\n\t})\n\n/** Reads a list of strings. */\nconst readStrings = (\n\tinput: object,\n\tname: string,\n\tat: Place.Place | undefined,\n\tbudget: Json.Budget,\n): ReadonlyArray<string> =>\n\tArr.map(Json.readList(input, name, at, budget), (value, index) => {\n\t\tif (typeof value !== \"string\") {\n\t\t\tthrow Json.problemAt(\n\t\t\t\tPlace.at(at, name, index),\n\t\t\t\t\"The item must be a string\",\n\t\t\t)\n\t\t}\n\t\treturn value\n\t})\n\n/**\n * Reads a field that a rule may leave out. A field that the input has only\n * from the prototype of a class is not plain data, so it is a problem: a rule\n * that loses its condition would allow more.\n */\nconst readOptional = <A>(\n\tinput: object,\n\tname: string,\n\tat: Place.Place | undefined,\n\tread: () => A,\n): A | undefined => {\n\tif (Object.hasOwn(input, name)) {\n\t\treturn read()\n\t}\n\t// A read of the field throws a problem when the field is not data.\n\tJson.read(input, name, at)\n\treturn undefined\n}\n\n/** A value of the input to count, with its place. */\ninterface Part {\n\treadonly value: unknown\n\treadonly at: Place.Place | undefined\n}\n\n/**\n * Counts the objects and items of a value, as the other parts count. It is a\n * walk of the trampoline, so a deep value does not fill the stack.\n */\nconst spendValue = (budget: Json.Budget): ((part: Part) => void) =>\n\tTrampoline.adaptive<Part, void>((R, child) => (part, depth) => {\n\t\tconst { value, at } = part\n\t\tif (typeof value !== \"object\" || value === null) {\n\t\t\treturn R.succeed(undefined)\n\t\t}\n\t\t// The length of an array counts before its items are read, so a sparse\n\t\t// array with a very large length stops at once.\n\t\tconst list = Array.isArray(value)\n\t\tif (list) {\n\t\t\tJson.spend(budget, 1 + value.length, at)\n\t\t}\n\t\tconst names = list\n\t\t\t? Array.from({ length: value.length }, (_, index) => String(index))\n\t\t\t: Object.keys(value)\n\t\tif (!list) {\n\t\t\tJson.spend(budget, 1 + names.length, at)\n\t\t}\n\t\treturn R.map(\n\t\t\tR.forEach(names, (name) =>\n\t\t\t\tchild(\n\t\t\t\t\t{ value: Json.read(value, name, at), at: Place.at(at, name) },\n\t\t\t\t\tdepth + 1,\n\t\t\t\t),\n\t\t\t),\n\t\t\t() => undefined,\n\t\t)\n\t})\n\n/**\n * A rule in the input, with its place, to decode after the tree. The place\n * shares the path of its parent: see `Place`.\n */\ninterface RuleInput {\n\treadonly input: unknown\n\treadonly place: Place.Place\n}\n\n/**\n * The shape of a tree, with the place of each rule in a flat list. `size`\n * counts the nodes and the rules, as the nodes do, so a shared input stops\n * at `policyNodes` before the decoder reads all of it.\n */\ntype Plan =\n\t| {\n\t\t\treadonly _tag: \"Rules\"\n\t\t\treadonly place: Place.Place | undefined\n\t\t\treadonly size: number\n\t\t\treadonly algorithm: PolicyNodes.Algorithm\n\t\t\treadonly rules: ReadonlyArray<number>\n\t }\n\t| {\n\t\t\treadonly _tag: \"All\" | \"Any\" | \"FirstOf\"\n\t\t\treadonly place: Place.Place | undefined\n\t\t\treadonly size: number\n\t\t\treadonly policies: ReadonlyArray<Plan>\n\t }\n\t| {\n\t\t\treadonly _tag: \"Ceiling\"\n\t\t\treadonly place: Place.Place | undefined\n\t\t\treadonly size: number\n\t\t\treadonly cap: Plan\n\t\t\treadonly policy: Plan\n\t }\n\n/**\n * Decodes the `meta` of a rule with the `meta` Schema of the domain, as an\n * Effect, since the Schema may need an async step. The Schema reads all of\n * the value, so its size counts in the budget.\n */\nconst decodeMeta = (\n\trule: RuleInput,\n\tdomain: Domain.Any,\n\tbudget: Json.Budget,\n): Effect.Effect<unknown, Json.Problem> => {\n\tconst { input, place } = rule\n\tconst at = Place.at(place, \"meta\")\n\treturn Effect.flatMap(\n\t\tattempt(() =>\n\t\t\ttypeof input !== \"object\" || input === null\n\t\t\t\t? undefined\n\t\t\t\t: readOptional(input, \"meta\", place, () => {\n\t\t\t\t\t\tconst value = Json.read(input, \"meta\", place)\n\t\t\t\t\t\tif (domain.meta === undefined) {\n\t\t\t\t\t\t\tthrow Json.problemAt(at, \"The domain has no Schema for meta\")\n\t\t\t\t\t\t}\n\t\t\t\t\t\tspendValue(budget)({ value, at })\n\t\t\t\t\t\treturn { value, schema: domain.meta }\n\t\t\t\t\t}),\n\t\t),\n\t\t(found) =>\n\t\t\tfound === undefined\n\t\t\t\t? Effect.void\n\t\t\t\t: Effect.mapError(\n\t\t\t\t\t\tSchema.decodeUnknownEffect(metaCodec(found.schema))(found.value),\n\t\t\t\t\t\t(error) =>\n\t\t\t\t\t\t\tnew Json.Problem({\n\t\t\t\t\t\t\t\tpath: Place.pathOf(at),\n\t\t\t\t\t\t\t\tmessage: error.message,\n\t\t\t\t\t\t\t\tissue: error.issue,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t)\n}\n\nconst decodeRule = (\n\trule: RuleInput,\n\tmeta: unknown,\n\tbudget: Json.Budget,\n\tdomain: Domain.Any,\n): Rule.Rule => {\n\tconst { input, place } = rule\n\tif (typeof input !== \"object\" || input === null) {\n\t\tthrow Json.problemAt(place, \"A rule must be an object\")\n\t}\n\tJson.spend(budget, 1, place)\n\tconst tag = Json.read(input, \"_tag\", place)\n\tif (tag !== \"Allow\" && tag !== \"Deny\") {\n\t\tthrow Json.problemAt(\n\t\t\tPlace.at(place, \"_tag\"),\n\t\t\t'A rule must be \"Allow\" or \"Deny\"',\n\t\t)\n\t}\n\tconst when = readOptional(input, \"when\", place, () =>\n\t\tJson.decodeAt(\n\t\t\tJson.read(input, \"when\", place),\n\t\t\tPlace.at(place, \"when\"),\n\t\t\tbudget,\n\t\t),\n\t)\n\tconst fields = readOptional(input, \"fields\", place, () =>\n\t\treadStrings(input, \"fields\", place, budget),\n\t)\n\tconst reason = readOptional(input, \"reason\", place, () =>\n\t\tJson.readString(input, \"reason\", place),\n\t)\n\tconst id = readOptional(input, \"id\", place, () =>\n\t\tJson.readString(input, \"id\", place),\n\t)\n\t// Each rule gets the domain of the Schema, as a rule from the builder of\n\t// the domain has it.\n\treturn Rule.make({\n\t\t_tag: tag,\n\t\tactions: readStrings(input, \"actions\", place, budget),\n\t\tresources: readStrings(input, \"resources\", place, budget),\n\t\t...(when === undefined ? {} : { when: when as never }),\n\t\t...(fields === undefined ? {} : { fields }),\n\t\t...(reason === undefined ? {} : { reason }),\n\t\t...(id === undefined ? {} : { id }),\n\t\t...(meta === undefined ? {} : { meta }),\n\t\tdomain,\n\t})\n}\n\n/** A node of the input of a tree, with its place. */\ninterface TreePart {\n\treadonly input: unknown\n\treadonly at: Place.Place | undefined\n}\n\n/**\n * Reads the shape of a tree, and puts each rule in `rules`. The rules are\n * decoded after the walk: a condition has levels of its own, and a decode\n * inside the walk would go down the levels of both. The walk is a walk of\n * the trampoline, so a deep tree does not fill the stack.\n */\nconst readTree = (\n\tbudget: Json.Budget,\n\t// The rules of one decode. The list belongs to that decode.\n\trules: Array<RuleInput>,\n): ((part: TreePart) => Plan) => {\n\t// The nodes and rules of the tree that the walk has read. A tree with more\n\t// of them than `policyNodes` does not load, so the walk stops at the first\n\t// one past it, and it does not read the rest of a very large input. The\n\t// count belongs to one decode.\n\tconst count = { nodes: 0 }\n\tconst counted = (more: number, at: Place.Place | undefined): void => {\n\t\tcount.nodes += more\n\t\tconst most = budget.limits.policyNodes\n\t\tif (count.nodes > most) {\n\t\t\tthrow Json.problemAt(\n\t\t\t\tat,\n\t\t\t\t`A policy tree has more than ${String(most)} nodes. The most is ${String(most)}.`,\n\t\t\t)\n\t\t}\n\t}\n\treturn Trampoline.adaptive<TreePart, Plan>((R, child) => (part, depth) => {\n\t\tconst { input, at } = part\n\t\tif (typeof input !== \"object\" || input === null) {\n\t\t\tthrow Json.problemAt(at, \"A node of a policy must be an object\")\n\t\t}\n\t\tJson.spend(budget, 1, at)\n\t\tcounted(1, at)\n\t\tconst below = (value: unknown, ...parts: ReadonlyArray<PropertyKey>) =>\n\t\t\tchild({ input: value, at: Place.at(at, ...parts) }, depth + 1)\n\t\tconst tag = Json.read(input, \"_tag\", at)\n\t\tswitch (tag) {\n\t\t\tcase \"Rules\": {\n\t\t\t\tconst algorithm = Json.readString(input, \"algorithm\", at)\n\t\t\t\tif (!PolicyNodes.isAlgorithm(algorithm)) {\n\t\t\t\t\tthrow Json.problemAt(\n\t\t\t\t\t\tPlace.at(at, \"algorithm\"),\n\t\t\t\t\t\t`\"${algorithm}\" is not a combining algorithm`,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tconst list = Json.readList(input, \"rules\", at, budget)\n\t\t\t\tcounted(list.length, at)\n\t\t\t\tconst found = Arr.map(\n\t\t\t\t\tlist,\n\t\t\t\t\t(rule, index) =>\n\t\t\t\t\t\trules.push({\n\t\t\t\t\t\t\tinput: rule,\n\t\t\t\t\t\t\tplace: Place.at(at, \"rules\", index),\n\t\t\t\t\t\t}) - 1,\n\t\t\t\t)\n\t\t\t\treturn R.succeed(\n\t\t\t\t\tsized(budget, {\n\t\t\t\t\t\t_tag: \"Rules\",\n\t\t\t\t\t\tplace: at,\n\t\t\t\t\t\tsize: 1 + found.length,\n\t\t\t\t\t\talgorithm,\n\t\t\t\t\t\trules: found,\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\t}\n\t\t\tcase \"All\":\n\t\t\tcase \"Any\":\n\t\t\tcase \"FirstOf\":\n\t\t\t\treturn R.map(\n\t\t\t\t\tR.forEach(\n\t\t\t\t\t\tJson.readList(input, \"policies\", at, budget),\n\t\t\t\t\t\t(value, index) => below(value, \"policies\", index),\n\t\t\t\t\t),\n\t\t\t\t\t(policies) =>\n\t\t\t\t\t\tsized(budget, {\n\t\t\t\t\t\t\t_tag: tag,\n\t\t\t\t\t\t\tplace: at,\n\t\t\t\t\t\t\tsize: Arr.reduce(policies, 1, (total, plan) => total + plan.size),\n\t\t\t\t\t\t\tpolicies,\n\t\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\tcase \"Ceiling\": {\n\t\t\t\tconst cap = Json.read(input, \"cap\", at)\n\t\t\t\tconst policy = Json.read(input, \"policy\", at)\n\t\t\t\treturn R.flatMap(below(cap, \"cap\"), (capPlan) =>\n\t\t\t\t\tR.map(below(policy, \"policy\"), (policyPlan) =>\n\t\t\t\t\t\tsized(budget, {\n\t\t\t\t\t\t\t_tag: \"Ceiling\",\n\t\t\t\t\t\t\tplace: at,\n\t\t\t\t\t\t\tsize: 1 + capPlan.size + policyPlan.size,\n\t\t\t\t\t\t\tcap: capPlan,\n\t\t\t\t\t\t\tpolicy: policyPlan,\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\tthrow Json.problemAt(Place.at(at, \"_tag\"), \"The node has no known tag\")\n\t\t}\n\t})\n}\n\n/** Stops a plan past `policyNodes`, with the message of the nodes. */\nconst sized = (budget: Json.Budget, plan: Plan): Plan => {\n\tconst most = budget.limits.policyNodes\n\tif (plan.size > most) {\n\t\tthrow Json.problemAt(\n\t\t\tplan.place,\n\t\t\t`A policy tree has ${String(plan.size)} nodes. The most is ${String(most)}.`,\n\t\t)\n\t}\n\treturn plan\n}\n\n/**\n * Makes the nodes of a plan, with the decoded rules. It is a walk of the\n * trampoline, so a deep plan does not fill the stack.\n */\nconst build = (\n\tbudget: Json.Budget,\n\trules: ReadonlyArray<Rule.Rule>,\n): ((plan: Plan) => Node) =>\n\tTrampoline.adaptive<Plan, Node>((R, child) => (plan, depth) => {\n\t\t// A node past a limit is a problem at its place. The constructor checks\n\t\t// the ceilings, and the decoder checks the limits of the domain, with\n\t\t// the nodes and values of the conditions.\n\t\tconst make = (made: () => Node): Node => {\n\t\t\tconst node = (() => {\n\t\t\t\ttry {\n\t\t\t\t\treturn made()\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (error instanceof Limits.LimitExceeded) {\n\t\t\t\t\t\tthrow Json.problemAt(plan.place, 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.policyNodes\n\t\t\tif (node.size > most) {\n\t\t\t\tthrow Json.problemAt(\n\t\t\t\t\tplan.place,\n\t\t\t\t\t`A policy tree has ${String(node.size)} nodes. The most is ${String(most)}.`,\n\t\t\t\t)\n\t\t\t}\n\t\t\treturn node\n\t\t}\n\t\tswitch (plan._tag) {\n\t\t\tcase \"Rules\":\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 PolicyNodes.Rules({\n\t\t\t\t\t\t\t\talgorithm: plan.algorithm,\n\t\t\t\t\t\t\t\trules: Arr.map(plan.rules, (index) => rules[index]!),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\tcase \"All\":\n\t\t\tcase \"Any\":\n\t\t\tcase \"FirstOf\": {\n\t\t\t\tconst tag = plan._tag\n\t\t\t\treturn R.map(\n\t\t\t\t\tR.forEach(plan.policies, (inner) => child(inner, depth + 1)),\n\t\t\t\t\t(policies) => make(() => new PolicyNodes[tag](policies)),\n\t\t\t\t)\n\t\t\t}\n\t\t\tcase \"Ceiling\":\n\t\t\t\treturn R.flatMap(child(plan.cap, depth + 1), (cap) =>\n\t\t\t\t\tR.map(child(plan.policy, depth + 1), (policy) =>\n\t\t\t\t\t\tmake(() => new PolicyNodes.Ceiling({ cap, policy })),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t}\n\t})\n\n/** Runs a step that may throw a problem, and puts the problem in the error. */\nconst attempt = <A>(step: () => A): Effect.Effect<A, Json.Problem> =>\n\tEffect.try({\n\t\ttry: step,\n\t\tcatch: (error) => {\n\t\t\tif (error instanceof Json.Problem) {\n\t\t\t\treturn error\n\t\t\t}\n\t\t\tthrow error\n\t\t},\n\t})\n\n/**\n * Decodes the JSON form of a policy tree for a domain. It reads the tree\n * first, then the `meta` of each rule as an Effect, then the rules, and it\n * makes the nodes last.\n */\nexport const decode = (\n\tinput: unknown,\n\tdomain: Domain.Any,\n): Effect.Effect<Node, Json.Problem> =>\n\tEffect.gen(function* () {\n\t\tconst budget: Json.Budget = { used: 0, limits: domain.limits }\n\t\t// The rules of this decode. The list belongs to this decode.\n\t\tconst inputs: Array<RuleInput> = []\n\t\tconst plan = yield* attempt(() =>\n\t\t\treadTree(budget, inputs)({ input, at: undefined }),\n\t\t)\n\t\tconst metas = yield* Effect.forEach(inputs, (rule) =>\n\t\t\tdecodeMeta(rule, domain, budget),\n\t\t)\n\t\tconst rules = yield* attempt(() =>\n\t\t\tArr.map(inputs, (rule, index) =>\n\t\t\t\tdecodeRule(rule, metas[index], budget, domain),\n\t\t\t),\n\t\t)\n\t\treturn yield* attempt(() => build(budget, rules)(plan))\n\t})\n"],"mappings":"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"}
1
+ {"version":3,"file":"policy-json.js","names":["PolicyNodes.rulesOf","Json.toJson","Trampoline.run","Json.readList","at","Json.problemAt","Place.at","read","Trampoline.adaptive","Json.read","Json.Problem","Place.pathOf","Json.decodeAt","Json.readString","Rule.make","PolicyNodes.isAlgorithm","Limits.LimitExceeded","PolicyNodes.Rules","PolicyNodes","PolicyNodes.Ceiling"],"sources":["../../src/internal/policy-json.ts"],"sourcesContent":["/**\n * The JSON form of a policy tree. The decoder is a plain walk, as the decoder\n * of a condition is, and the two share one budget of `jsonNodes` objects and\n * items. It makes each node with its constructor, so the limits of the nodes\n * hold for a decoded tree too. The binding of the rules to the domain comes\n * after, in the Schema of the policy.\n */\nimport * as Arr from \"effect/Array\"\nimport * as Effect from \"effect/Effect\"\nimport * as Schema from \"effect/Schema\"\nimport type * as Domain from \"../Domain.ts\"\nimport * as Rule from \"./rule.ts\"\nimport * as Json from \"./json.ts\"\nimport * as Limits from \"./limits.ts\"\nimport * as Place from \"./place.ts\"\nimport * as PolicyNodes from \"./policy-nodes.ts\"\nimport * as Trampoline from \"./trampoline.ts\"\n\ntype Node = PolicyNodes.Node\n\n/**\n * The codec of the `meta` Schema of a domain to and from JSON. The JSON form\n * of a policy must stay the same through `JSON.stringify` and `JSON.parse`, so\n * a `Date` in `meta`, for one, becomes a string in it, and a string again\n * when it is decoded.\n */\nconst metaCodec = (meta: Schema.Top): Schema.Codec<unknown, unknown> =>\n\tSchema.toCodecJson(meta) as unknown as Schema.Codec<unknown, unknown>\n\n/** Gives the JSON form of the `meta` of a rule, or `undefined` for none. */\nexport type MetaJson = (rule: Rule.Rule) => unknown\n\n/**\n * Gives the JSON form of the `meta` of each rule at once, with the `meta`\n * Schema of a domain. `JSON.stringify` needs it at once, so the Schema must\n * not need an async step there.\n */\nexport const syncMeta =\n\t(meta: Schema.Top | undefined): MetaJson =>\n\t(rule) =>\n\t\trule.meta === undefined || meta === undefined\n\t\t\t? undefined\n\t\t\t: Schema.encodeUnknownSync(metaCodec(meta))(rule.meta)\n\n/** The JSON form of the condition of each rule, by the rule. */\ntype WhenJson = (rule: Rule.Rule) => unknown\n\n/** Gives the JSON form of a rule. The domain of a rule is not in it. */\nconst ruleJson = (\n\trule: Rule.Rule,\n\tmetaJson: MetaJson,\n\twhenJson: WhenJson,\n): unknown => {\n\tconst meta = metaJson(rule)\n\treturn {\n\t\t_tag: rule._tag,\n\t\tactions: rule.actions,\n\t\tresources: rule.resources,\n\t\t...(rule.when === undefined ? {} : { when: whenJson(rule) }),\n\t\t...(rule.fields === undefined ? {} : { fields: rule.fields }),\n\t\t...(rule.reason === undefined ? {} : { reason: rule.reason }),\n\t\t...(rule.id === undefined ? {} : { id: rule.id }),\n\t\t// The `meta` of a rule is a decoded value. Its JSON form comes from the\n\t\t// `meta` Schema of the domain.\n\t\t...(meta === undefined ? {} : { meta }),\n\t}\n}\n\n/**\n * Gives the JSON form of a policy tree with no predicate. A condition with a\n * predicate has no JSON form, so it throws an error. The conditions are\n * written first, from a flat list of the rules, so the walk does not go down\n * the levels of the tree and of a condition at once.\n */\nexport const toJson = (node: Node, meta: MetaJson): unknown => {\n\t// A native map, because it must find a rule by the rule itself: two rules\n\t// with the same data are `Equal`.\n\tconst whens = new Map<Rule.Rule, unknown>(\n\t\tArr.map(PolicyNodes.rulesOf(node), (rule) => [\n\t\t\trule,\n\t\t\trule.when === undefined ? undefined : Json.toJson(rule.when as never),\n\t\t]),\n\t)\n\t// The walk of the tree takes the steps of a trampoline, so a deep tree\n\t// does not fill the stack.\n\treturn Trampoline.run(node.depth, (R) => {\n\t\tconst tree = (current: Node): Trampoline.Step<unknown> => {\n\t\t\tswitch (current._tag) {\n\t\t\t\tcase \"Rules\":\n\t\t\t\t\treturn R.succeed({\n\t\t\t\t\t\t_tag: \"Rules\",\n\t\t\t\t\t\talgorithm: current.algorithm,\n\t\t\t\t\t\trules: Arr.map(current.rules, (rule) =>\n\t\t\t\t\t\t\truleJson(rule, meta, (found) => whens.get(found)),\n\t\t\t\t\t\t),\n\t\t\t\t\t})\n\t\t\t\tcase \"All\":\n\t\t\t\tcase \"Any\":\n\t\t\t\tcase \"FirstOf\": {\n\t\t\t\t\tconst tag = current._tag\n\t\t\t\t\treturn R.map(\n\t\t\t\t\t\tR.forEach(current.policies, (child) =>\n\t\t\t\t\t\t\tR.suspend(() => tree(child)),\n\t\t\t\t\t\t),\n\t\t\t\t\t\t(policies) => ({ _tag: tag, policies }),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tcase \"Ceiling\":\n\t\t\t\t\treturn R.flatMap(\n\t\t\t\t\t\tR.suspend(() => tree(current.cap)),\n\t\t\t\t\t\t(cap) =>\n\t\t\t\t\t\t\tR.map(\n\t\t\t\t\t\t\t\tR.suspend(() => tree(current.policy)),\n\t\t\t\t\t\t\t\t(policy) => ({ _tag: \"Ceiling\", cap, policy }),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\treturn tree(node)\n\t})\n}\n\n/**\n * Gives the JSON form of a policy tree as an Effect. The `meta` Schema of the\n * domain may need an async step, so each `meta` is encoded as an Effect.\n */\nexport const encode = (\n\tnode: Node,\n\tmeta: Schema.Top | undefined,\n): Effect.Effect<unknown, Schema.SchemaError> =>\n\tEffect.gen(function* () {\n\t\tconst rules = Arr.filter(\n\t\t\tPolicyNodes.rulesOf(node),\n\t\t\t(rule) => rule.meta !== undefined,\n\t\t)\n\t\tconst encoded =\n\t\t\tmeta === undefined\n\t\t\t\t? []\n\t\t\t\t: yield* Effect.forEach(rules, (rule) =>\n\t\t\t\t\t\tEffect.map(\n\t\t\t\t\t\t\tSchema.encodeUnknownEffect(metaCodec(meta))(rule.meta),\n\t\t\t\t\t\t\t(json) => [rule, json] as const,\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t// A native map, because it must find a rule by the rule itself: two\n\t\t// rules with the same data are `Equal`, and each keeps its own form.\n\t\tconst forms = new Map<Rule.Rule, unknown>(encoded)\n\t\treturn toJson(node, (rule) => forms.get(rule))\n\t})\n\n/** Reads a list of strings. */\nconst readStrings = (\n\tinput: object,\n\tname: string,\n\tat: Place.Place | undefined,\n\tbudget: Json.Budget,\n): ReadonlyArray<string> =>\n\tArr.map(Json.readList(input, name, at, budget), (value, index) => {\n\t\tif (typeof value !== \"string\") {\n\t\t\tthrow Json.problemAt(\n\t\t\t\tPlace.at(at, name, index),\n\t\t\t\t\"The item must be a string\",\n\t\t\t)\n\t\t}\n\t\treturn value\n\t})\n\n/**\n * Reads a field that a rule may leave out. A field that the input has only\n * from the prototype of a class is not plain data, so it is a problem: a rule\n * that loses its condition would allow more.\n */\nconst readOptional = <A>(\n\tinput: object,\n\tname: string,\n\tat: Place.Place | undefined,\n\tread: () => A,\n): A | undefined => {\n\tif (Object.hasOwn(input, name)) {\n\t\treturn read()\n\t}\n\t// A read of the field throws a problem when the field is not data.\n\tJson.read(input, name, at)\n\treturn undefined\n}\n\n/** A value of the input to count, with its place. */\ninterface Part {\n\treadonly value: unknown\n\treadonly at: Place.Place | undefined\n}\n\n/**\n * Counts the objects and items of a value, as the other parts count. It is a\n * walk of the trampoline, so a deep value does not fill the stack.\n */\nconst spendValue = (budget: Json.Budget): ((part: Part) => void) =>\n\tTrampoline.adaptive<Part, void>((R, child) => (part, depth) => {\n\t\tconst { value, at } = part\n\t\tif (typeof value !== \"object\" || value === null) {\n\t\t\treturn R.succeed(undefined)\n\t\t}\n\t\t// The length of an array counts before its items are read, so a sparse\n\t\t// array with a very large length stops at once.\n\t\tconst list = Array.isArray(value)\n\t\tif (list) {\n\t\t\tJson.spend(budget, 1 + value.length, at)\n\t\t}\n\t\tconst names = list\n\t\t\t? Array.from({ length: value.length }, (_, index) => String(index))\n\t\t\t: Object.keys(value)\n\t\tif (!list) {\n\t\t\tJson.spend(budget, 1 + names.length, at)\n\t\t}\n\t\treturn R.map(\n\t\t\tR.forEach(names, (name) =>\n\t\t\t\tchild(\n\t\t\t\t\t{ value: Json.read(value, name, at), at: Place.at(at, name) },\n\t\t\t\t\tdepth + 1,\n\t\t\t\t),\n\t\t\t),\n\t\t\t() => undefined,\n\t\t)\n\t})\n\n/**\n * A rule in the input, with its place, to decode after the tree. The place\n * shares the path of its parent: see `Place`.\n */\ninterface RuleInput {\n\treadonly input: unknown\n\treadonly place: Place.Place\n}\n\n/**\n * The shape of a tree, with the place of each rule in a flat list. `size`\n * counts the nodes and the rules, as the nodes do, so a shared input stops\n * at `policyNodes` before the decoder reads all of it.\n */\ntype Plan =\n\t| {\n\t\t\treadonly _tag: \"Rules\"\n\t\t\treadonly place: Place.Place | undefined\n\t\t\treadonly size: number\n\t\t\treadonly algorithm: PolicyNodes.Algorithm\n\t\t\treadonly rules: ReadonlyArray<number>\n\t }\n\t| {\n\t\t\treadonly _tag: \"All\" | \"Any\" | \"FirstOf\"\n\t\t\treadonly place: Place.Place | undefined\n\t\t\treadonly size: number\n\t\t\treadonly policies: ReadonlyArray<Plan>\n\t }\n\t| {\n\t\t\treadonly _tag: \"Ceiling\"\n\t\t\treadonly place: Place.Place | undefined\n\t\t\treadonly size: number\n\t\t\treadonly cap: Plan\n\t\t\treadonly policy: Plan\n\t }\n\n/**\n * Decodes the `meta` of a rule with the `meta` Schema of the domain, as an\n * Effect, since the Schema may need an async step. The Schema reads all of\n * the value, so its size counts in the budget.\n */\nconst decodeMeta = (\n\trule: RuleInput,\n\tdomain: Domain.Any,\n\tbudget: Json.Budget,\n): Effect.Effect<unknown, Json.Problem> => {\n\tconst { input, place } = rule\n\tconst at = Place.at(place, \"meta\")\n\treturn Effect.flatMap(\n\t\tattempt(() =>\n\t\t\ttypeof input !== \"object\" || input === null\n\t\t\t\t? undefined\n\t\t\t\t: readOptional(input, \"meta\", place, () => {\n\t\t\t\t\t\tconst value = Json.read(input, \"meta\", place)\n\t\t\t\t\t\tif (domain.meta === undefined) {\n\t\t\t\t\t\t\tthrow Json.problemAt(at, \"The domain has no Schema for meta\")\n\t\t\t\t\t\t}\n\t\t\t\t\t\tspendValue(budget)({ value, at })\n\t\t\t\t\t\treturn { value, schema: domain.meta }\n\t\t\t\t\t}),\n\t\t),\n\t\t(found) =>\n\t\t\tfound === undefined\n\t\t\t\t? Effect.void\n\t\t\t\t: Schema.decodeUnknownEffect(metaCodec(found.schema))(found.value).pipe(\n\t\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t\t(error) =>\n\t\t\t\t\t\t\t\tnew Json.Problem({\n\t\t\t\t\t\t\t\t\tpath: Place.pathOf(at),\n\t\t\t\t\t\t\t\t\tmessage: error.message,\n\t\t\t\t\t\t\t\t\tissue: error.issue,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t\t// The decoder of a Schema in Effect calls itself for each level of\n\t\t\t\t\t\t// the value, so a deep value that the budget takes, as 5,000 levels\n\t\t\t\t\t\t// of a recursive `meta` Schema, fills its stack. That is a defect\n\t\t\t\t\t\t// of the decoder, not of the policy, so it is a problem of the\n\t\t\t\t\t\t// input here, and the policy does not load.\n\t\t\t\t\t\tEffect.catchDefect((defect) =>\n\t\t\t\t\t\t\tdefect instanceof RangeError\n\t\t\t\t\t\t\t\t? Effect.fail(\n\t\t\t\t\t\t\t\t\t\tJson.problemAt(\n\t\t\t\t\t\t\t\t\t\t\tat,\n\t\t\t\t\t\t\t\t\t\t\t\"The meta of the rule has too many levels for the Schema of meta\",\n\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t: Effect.die(defect),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t)\n}\n\nconst decodeRule = (\n\trule: RuleInput,\n\tmeta: unknown,\n\tbudget: Json.Budget,\n\tdomain: Domain.Any,\n): Rule.Rule => {\n\tconst { input, place } = rule\n\tif (typeof input !== \"object\" || input === null) {\n\t\tthrow Json.problemAt(place, \"A rule must be an object\")\n\t}\n\tJson.spend(budget, 1, place)\n\tconst tag = Json.read(input, \"_tag\", place)\n\tif (tag !== \"Allow\" && tag !== \"Deny\") {\n\t\tthrow Json.problemAt(\n\t\t\tPlace.at(place, \"_tag\"),\n\t\t\t'A rule must be \"Allow\" or \"Deny\"',\n\t\t)\n\t}\n\tconst when = readOptional(input, \"when\", place, () =>\n\t\tJson.decodeAt(\n\t\t\tJson.read(input, \"when\", place),\n\t\t\tPlace.at(place, \"when\"),\n\t\t\tbudget,\n\t\t),\n\t)\n\tconst fields = readOptional(input, \"fields\", place, () =>\n\t\treadStrings(input, \"fields\", place, budget),\n\t)\n\tconst reason = readOptional(input, \"reason\", place, () =>\n\t\tJson.readString(input, \"reason\", place),\n\t)\n\tconst id = readOptional(input, \"id\", place, () =>\n\t\tJson.readString(input, \"id\", place),\n\t)\n\t// Each rule gets the domain of the Schema, as a rule from the builder of\n\t// the domain has it.\n\treturn Rule.make({\n\t\t_tag: tag,\n\t\tactions: readStrings(input, \"actions\", place, budget),\n\t\tresources: readStrings(input, \"resources\", place, budget),\n\t\t...(when === undefined ? {} : { when: when as never }),\n\t\t...(fields === undefined ? {} : { fields }),\n\t\t...(reason === undefined ? {} : { reason }),\n\t\t...(id === undefined ? {} : { id }),\n\t\t...(meta === undefined ? {} : { meta }),\n\t\tdomain,\n\t})\n}\n\n/** A node of the input of a tree, with its place. */\ninterface TreePart {\n\treadonly input: unknown\n\treadonly at: Place.Place | undefined\n}\n\n/**\n * Reads the shape of a tree, and puts each rule in `rules`. The rules are\n * decoded after the walk: a condition has levels of its own, and a decode\n * inside the walk would go down the levels of both. The walk is a walk of\n * the trampoline, so a deep tree does not fill the stack.\n */\nconst readTree = (\n\tbudget: Json.Budget,\n\t// The rules of one decode. The list belongs to that decode.\n\trules: Array<RuleInput>,\n): ((part: TreePart) => Plan) => {\n\t// The nodes and rules of the tree that the walk has read. A tree with more\n\t// of them than `policyNodes` does not load, so the walk stops at the first\n\t// one past it, and it does not read the rest of a very large input. The\n\t// count belongs to one decode.\n\tconst count = { nodes: 0 }\n\tconst counted = (more: number, at: Place.Place | undefined): void => {\n\t\tcount.nodes += more\n\t\tconst most = budget.limits.policyNodes\n\t\tif (count.nodes > most) {\n\t\t\tthrow Json.problemAt(\n\t\t\t\tat,\n\t\t\t\t`A policy tree has more than ${String(most)} nodes. The most is ${String(most)}.`,\n\t\t\t)\n\t\t}\n\t}\n\treturn Trampoline.adaptive<TreePart, Plan>((R, child) => (part, depth) => {\n\t\tconst { input, at } = part\n\t\tif (typeof input !== \"object\" || input === null) {\n\t\t\tthrow Json.problemAt(at, \"A node of a policy must be an object\")\n\t\t}\n\t\tJson.spend(budget, 1, at)\n\t\tcounted(1, at)\n\t\tconst below = (value: unknown, ...parts: ReadonlyArray<PropertyKey>) =>\n\t\t\tchild({ input: value, at: Place.at(at, ...parts) }, depth + 1)\n\t\tconst tag = Json.read(input, \"_tag\", at)\n\t\tswitch (tag) {\n\t\t\tcase \"Rules\": {\n\t\t\t\tconst algorithm = Json.readString(input, \"algorithm\", at)\n\t\t\t\tif (!PolicyNodes.isAlgorithm(algorithm)) {\n\t\t\t\t\tthrow Json.problemAt(\n\t\t\t\t\t\tPlace.at(at, \"algorithm\"),\n\t\t\t\t\t\t`\"${algorithm}\" is not a combining algorithm`,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tconst list = Json.readList(input, \"rules\", at, budget)\n\t\t\t\tcounted(list.length, at)\n\t\t\t\tconst found = Arr.map(\n\t\t\t\t\tlist,\n\t\t\t\t\t(rule, index) =>\n\t\t\t\t\t\trules.push({\n\t\t\t\t\t\t\tinput: rule,\n\t\t\t\t\t\t\tplace: Place.at(at, \"rules\", index),\n\t\t\t\t\t\t}) - 1,\n\t\t\t\t)\n\t\t\t\treturn R.succeed(\n\t\t\t\t\tsized(budget, {\n\t\t\t\t\t\t_tag: \"Rules\",\n\t\t\t\t\t\tplace: at,\n\t\t\t\t\t\tsize: 1 + found.length,\n\t\t\t\t\t\talgorithm,\n\t\t\t\t\t\trules: found,\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\t}\n\t\t\tcase \"All\":\n\t\t\tcase \"Any\":\n\t\t\tcase \"FirstOf\":\n\t\t\t\treturn R.map(\n\t\t\t\t\tR.forEach(\n\t\t\t\t\t\tJson.readList(input, \"policies\", at, budget),\n\t\t\t\t\t\t(value, index) => below(value, \"policies\", index),\n\t\t\t\t\t),\n\t\t\t\t\t(policies) =>\n\t\t\t\t\t\tsized(budget, {\n\t\t\t\t\t\t\t_tag: tag,\n\t\t\t\t\t\t\tplace: at,\n\t\t\t\t\t\t\tsize: Arr.reduce(policies, 1, (total, plan) => total + plan.size),\n\t\t\t\t\t\t\tpolicies,\n\t\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\tcase \"Ceiling\": {\n\t\t\t\tconst cap = Json.read(input, \"cap\", at)\n\t\t\t\tconst policy = Json.read(input, \"policy\", at)\n\t\t\t\treturn R.flatMap(below(cap, \"cap\"), (capPlan) =>\n\t\t\t\t\tR.map(below(policy, \"policy\"), (policyPlan) =>\n\t\t\t\t\t\tsized(budget, {\n\t\t\t\t\t\t\t_tag: \"Ceiling\",\n\t\t\t\t\t\t\tplace: at,\n\t\t\t\t\t\t\tsize: 1 + capPlan.size + policyPlan.size,\n\t\t\t\t\t\t\tcap: capPlan,\n\t\t\t\t\t\t\tpolicy: policyPlan,\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\tthrow Json.problemAt(Place.at(at, \"_tag\"), \"The node has no known tag\")\n\t\t}\n\t})\n}\n\n/** Stops a plan past `policyNodes`, with the message of the nodes. */\nconst sized = (budget: Json.Budget, plan: Plan): Plan => {\n\tconst most = budget.limits.policyNodes\n\tif (plan.size > most) {\n\t\tthrow Json.problemAt(\n\t\t\tplan.place,\n\t\t\t`A policy tree has ${String(plan.size)} nodes. The most is ${String(most)}.`,\n\t\t)\n\t}\n\treturn plan\n}\n\n/**\n * Makes the nodes of a plan, with the decoded rules. It is a walk of the\n * trampoline, so a deep plan does not fill the stack.\n */\nconst build = (\n\tbudget: Json.Budget,\n\trules: ReadonlyArray<Rule.Rule>,\n): ((plan: Plan) => Node) =>\n\tTrampoline.adaptive<Plan, Node>((R, child) => (plan, depth) => {\n\t\t// A node past a limit is a problem at its place. The constructor checks\n\t\t// the ceilings, and the decoder checks the limits of the domain, with\n\t\t// the nodes and values of the conditions.\n\t\tconst make = (made: () => Node): Node => {\n\t\t\tconst node = (() => {\n\t\t\t\ttry {\n\t\t\t\t\treturn made()\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (error instanceof Limits.LimitExceeded) {\n\t\t\t\t\t\tthrow Json.problemAt(plan.place, 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.policyNodes\n\t\t\tif (node.size > most) {\n\t\t\t\tthrow Json.problemAt(\n\t\t\t\t\tplan.place,\n\t\t\t\t\t`A policy tree has ${String(node.size)} nodes. The most is ${String(most)}.`,\n\t\t\t\t)\n\t\t\t}\n\t\t\treturn node\n\t\t}\n\t\tswitch (plan._tag) {\n\t\t\tcase \"Rules\":\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 PolicyNodes.Rules({\n\t\t\t\t\t\t\t\talgorithm: plan.algorithm,\n\t\t\t\t\t\t\t\trules: Arr.map(plan.rules, (index) => rules[index]!),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\tcase \"All\":\n\t\t\tcase \"Any\":\n\t\t\tcase \"FirstOf\": {\n\t\t\t\tconst tag = plan._tag\n\t\t\t\treturn R.map(\n\t\t\t\t\tR.forEach(plan.policies, (inner) => child(inner, depth + 1)),\n\t\t\t\t\t(policies) => make(() => new PolicyNodes[tag](policies)),\n\t\t\t\t)\n\t\t\t}\n\t\t\tcase \"Ceiling\":\n\t\t\t\treturn R.flatMap(child(plan.cap, depth + 1), (cap) =>\n\t\t\t\t\tR.map(child(plan.policy, depth + 1), (policy) =>\n\t\t\t\t\t\tmake(() => new PolicyNodes.Ceiling({ cap, policy })),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t}\n\t})\n\n/** Runs a step that may throw a problem, and puts the problem in the error. */\nconst attempt = <A>(step: () => A): Effect.Effect<A, Json.Problem> =>\n\tEffect.try({\n\t\ttry: step,\n\t\tcatch: (error) => {\n\t\t\tif (error instanceof Json.Problem) {\n\t\t\t\treturn error\n\t\t\t}\n\t\t\tthrow error\n\t\t},\n\t})\n\n/**\n * Decodes the JSON form of a policy tree for a domain. It reads the tree\n * first, then the `meta` of each rule as an Effect, then the rules, and it\n * makes the nodes last.\n */\nexport const decode = (\n\tinput: unknown,\n\tdomain: Domain.Any,\n): Effect.Effect<Node, Json.Problem> =>\n\tEffect.gen(function* () {\n\t\tconst budget: Json.Budget = { used: 0, limits: domain.limits }\n\t\t// The rules of this decode. The list belongs to this decode.\n\t\tconst inputs: Array<RuleInput> = []\n\t\tconst plan = yield* attempt(() =>\n\t\t\treadTree(budget, inputs)({ input, at: undefined }),\n\t\t)\n\t\tconst metas = yield* Effect.forEach(inputs, (rule) =>\n\t\t\tdecodeMeta(rule, domain, budget),\n\t\t)\n\t\tconst rules = yield* attempt(() =>\n\t\t\tArr.map(inputs, (rule, index) =>\n\t\t\t\tdecodeRule(rule, metas[index], budget, domain),\n\t\t\t),\n\t\t)\n\t\treturn yield* attempt(() => build(budget, rules)(plan))\n\t})\n"],"mappings":"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"}
@@ -8,12 +8,12 @@ import { Ceiling, Rules, policy_nodes_exports, rulesOf } from "./policy-nodes.js
8
8
  import { decode, encode, syncMeta, toJson } from "./policy-json.js";
9
9
  import * as Arr from "effect/Array";
10
10
  import * as Effect from "effect/Effect";
11
+ import * as Option from "effect/Option";
11
12
  import * as Schema from "effect/Schema";
12
13
  import * as Data from "effect/Data";
13
14
  import { dual } from "effect/Function";
14
15
  import * as SchemaIssue from "effect/SchemaIssue";
15
16
  import * as SchemaTransformation from "effect/SchemaTransformation";
16
- import * as Option from "effect/Option";
17
17
  // src/internal/policy.ts
18
18
  /**
19
19
  * The class of every policy value. Two policies of one domain with the same
@@ -4,9 +4,9 @@ import { always, never } from "./nodes.js";
4
4
  import { and, keyOf, not, or, simplify, testCompare } from "./algebra.js";
5
5
  import { anyKinds, lookup } from "./catalog.js";
6
6
  import * as Arr from "effect/Array";
7
+ import * as Option from "effect/Option";
7
8
  import * as Data from "effect/Data";
8
9
  import { isObject } from "effect/Predicate";
9
- import * as Option from "effect/Option";
10
10
  // src/internal/search.ts
11
11
  /**
12
12
  * The search for a value that makes a condition true. Each test of a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nmnmcc/ability",
3
- "version": "2.1.3",
3
+ "version": "2.1.4",
4
4
  "description": "Typed, composable authorization policies for Effect programs.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -17,6 +17,7 @@ import * as Hash from "effect/Hash"
17
17
  import * as HashMap from "effect/HashMap"
18
18
  import * as HashSet from "effect/HashSet"
19
19
  import * as Layer from "effect/Layer"
20
+ import * as Option from "effect/Option"
20
21
  import * as Result from "effect/Result"
21
22
  import * as Schema from "effect/Schema"
22
23
  import type * as Authorizer from "../Authorizer.ts"
@@ -148,6 +149,11 @@ class PrincipalKey implements Equal.Equal {
148
149
  * part does not make the text very long. The numbers are in a native `Map`,
149
150
  * because the walk must find an object by the object itself. The map belongs
150
151
  * to one walk.
152
+ *
153
+ * A value that keeps its data inside, with no own fields, as a `Date`, a
154
+ * `URL` or a `RegExp`, is in the text with its data, and so is an `Option`.
155
+ * For another such value, the walk can not see the data, so it throws, and
156
+ * the principal gets no key: it is not kept.
151
157
  */
152
158
  const orderOf = (root: unknown): string => {
153
159
  const ids = new Map<object, number>()
@@ -168,6 +174,20 @@ const orderOf = (root: unknown): string => {
168
174
  ids.set(value, ids.size)
169
175
  const items = (list: ReadonlyArray<unknown>) =>
170
176
  R.forEach(list, (item) => child(item, depth + 1))
177
+ if (value instanceof Date) {
178
+ return R.succeed(`date:${String(value.getTime())}`)
179
+ }
180
+ if (value instanceof URL) {
181
+ return R.succeed(`url:${JSON.stringify(value.href)}`)
182
+ }
183
+ if (value instanceof RegExp) {
184
+ return R.succeed(`regexp:${JSON.stringify(String(value))}`)
185
+ }
186
+ if (Option.isOption(value)) {
187
+ return Option.isSome(value)
188
+ ? R.map(child(value.value, depth + 1), (text) => `some(${text})`)
189
+ : R.succeed("none")
190
+ }
171
191
  if (Array.isArray(value)) {
172
192
  return R.map(items(value), (texts) => `[${texts.join(",")}]`)
173
193
  }
@@ -184,6 +204,14 @@ const orderOf = (root: unknown): string => {
184
204
  )
185
205
  }
186
206
  const names = Object.keys(value)
207
+ const prototype: unknown = Object.getPrototypeOf(value)
208
+ if (
209
+ names.length === 0 &&
210
+ prototype !== Object.prototype &&
211
+ prototype !== null
212
+ ) {
213
+ throw new Error("The data of a part of the principal is not in fields")
214
+ }
187
215
  return R.map(
188
216
  items(Arr.map(names, (name) => (value as Record<string, unknown>)[name])),
189
217
  (texts) =>
@@ -153,11 +153,20 @@ const orderedMeet = (left: Catalog.Kinds, right: Catalog.Kinds): boolean =>
153
153
  (hasStrings(left) && hasStrings(right)) ||
154
154
  (hasNumbers(left) && hasNumbers(right))
155
155
 
156
- /** The texts of the fields of a catalog that have only names, for suggestions. */
157
- const namesOf = (catalog: Catalog.Catalog): ReadonlyArray<string> =>
156
+ /**
157
+ * The texts of the fields of a catalog that have only names, for
158
+ * suggestions, with the limits of the domain. The domain checked the size of
159
+ * the list when it was made.
160
+ */
161
+ const namesOf = (
162
+ catalog: Catalog.Catalog,
163
+ limits: Limits.Limits,
164
+ ): ReadonlyArray<string> =>
158
165
  Arr.map(
159
- Arr.filter(catalog.fields, (field) =>
160
- Arr.every(field.path, (segment) => typeof segment === "string"),
166
+ Arr.filter(
167
+ Catalog.fieldsOf(catalog, limits.fieldPathParts, limits.catalogFields),
168
+ (field) =>
169
+ Arr.every(field.path, (segment) => typeof segment === "string"),
161
170
  ),
162
171
  (field) => field.text,
163
172
  )
@@ -192,7 +201,7 @@ const fieldIssue = (
192
201
  {
193
202
  path: at,
194
203
  issue: suggestIn(context.memo, message, field, without[0]!.kind, () =>
195
- namesOf(without[0]!.catalog),
204
+ namesOf(without[0]!.catalog, context.memo.limits),
196
205
  ),
197
206
  },
198
207
  ]
@@ -332,7 +341,7 @@ const principalField = (
332
341
  `The principal has no field "${ref}"`,
333
342
  ref,
334
343
  "~principal",
335
- () => namesOf(context.domain.principalCatalog),
344
+ () => namesOf(context.domain.principalCatalog, context.memo.limits),
336
345
  ),
337
346
  }
338
347
  }
@@ -175,12 +175,6 @@ export interface Branch {
175
175
 
176
176
  /** The catalog of the fields of a Schema. */
177
177
  export interface Catalog {
178
- /**
179
- * The fields that a list shows, a field before the fields below it. For a
180
- * recursive Schema, the list goes into each recursive place one time on
181
- * each path, so it is finite.
182
- */
183
- readonly fields: ReadonlyArray<Field>
184
178
  /** The places, by id. The root is the place with the id 0. */
185
179
  readonly branches: ReadonlyArray<Branch>
186
180
  /**
@@ -304,6 +298,13 @@ const isStruct = (
304
298
  ast !== undefined &&
305
299
  (ast.propertySignatures.length > 0 || ast.indexSignatures.length > 0)
306
300
 
301
+ /** Tests if a Schema holds only values that are not objects, as `bigint`. */
302
+ const isPrimitive = (ast: SchemaAST.AST): boolean =>
303
+ ast._tag === "BigInt" ||
304
+ ast._tag === "Symbol" ||
305
+ ast._tag === "UniqueSymbol" ||
306
+ (ast._tag === "Literal" && typeof ast.literal === "bigint")
307
+
307
308
  /** The kinds of the values of a Schema that is not a struct. */
308
309
  const kindsOf = (ast: SchemaAST.AST): Kinds => {
309
310
  switch (ast._tag) {
@@ -481,6 +482,15 @@ const placeOf = (place: Place): Made => {
481
482
  const valueKinds = Arr.reduce(values, noKinds, (kinds, schema) =>
482
483
  joinKinds(kinds, kindsOf(structOf(schema) ?? schema)),
483
484
  )
485
+ // A value of a branch may be an object, with parts that the Schema does
486
+ // not name. A `bigint` or a `symbol` is not a scalar that a test takes,
487
+ // so its kinds say `object`, but it has no parts: a read below it gives
488
+ // `null`, as below a string.
489
+ const objects = Arr.some(
490
+ values,
491
+ (schema) =>
492
+ !isPrimitive(schema) && kindsOf(structOf(schema) ?? schema).object,
493
+ )
484
494
  const kinds = place.top
485
495
  ? anyKinds
486
496
  : joinKinds(
@@ -502,8 +512,7 @@ const placeOf = (place: Place): Made => {
502
512
  // decision may get a value made by hand. A value that may be any
503
513
  // object, as one of `Schema.Unknown` may, may have the name with any
504
514
  // value too.
505
- const top =
506
- place.top || valueKinds.object || properties.length < structs.length
515
+ const top = place.top || objects || properties.length < structs.length
507
516
  const below: Place = {
508
517
  schemas: Arr.flatMap(properties, (property) =>
509
518
  alternatives(property.type, new Set()),
@@ -522,8 +531,7 @@ const placeOf = (place: Place): Made => {
522
531
  return [name, below] as const
523
532
  })
524
533
  const signatures = Arr.flatMap(records, (struct) => struct.indexSignatures)
525
- const keyTop =
526
- place.top || valueKinds.object || records.length < structs.length
534
+ const keyTop = place.top || objects || records.length < structs.length
527
535
  return {
528
536
  branch: {
529
537
  kinds,
@@ -531,11 +539,7 @@ const placeOf = (place: Place): Made => {
531
539
  structs: structs.length > 0,
532
540
  // A field that is a scalar in one branch of a union and a struct in
533
541
  // another holds parts too.
534
- open:
535
- records.length > 0 ||
536
- structs.length > 0 ||
537
- valueKinds.object ||
538
- place.top,
542
+ open: records.length > 0 || structs.length > 0 || objects || place.top,
539
543
  plain: place.plain,
540
544
  key:
541
545
  signatures.length === 0
@@ -651,13 +655,18 @@ const fieldAt = (
651
655
  * The fields that a list shows, a field before the fields below it, down to
652
656
  * `parts` parts. A path goes into a place that is on it already one time
653
657
  * only, so the list of a recursive Schema is finite. The walk keeps a stack
654
- * of the places to visit, so a deep Schema does not fill the stack. The
655
- * stack belongs to this walk.
658
+ * of the places to visit, so a deep Schema does not fill the stack. A Schema
659
+ * that shares one part in many places may have very many fields, so the
660
+ * walk counts the fields that it makes, and it stops at the first one past
661
+ * `most`. The stack and the count belong to this walk.
656
662
  */
657
663
  const listOf = (
658
664
  branches: ReadonlyArray<Branch>,
659
665
  parts: number,
666
+ most: number,
667
+ thing: string,
660
668
  ): ReadonlyArray<Field> => {
669
+ const count = { fields: 0 }
661
670
  interface Visit {
662
671
  readonly branch: Branch
663
672
  readonly path: ReadonlyArray<Segment>
@@ -708,13 +717,14 @@ const listOf = (
708
717
  visit.path.length === 0
709
718
  ? []
710
719
  : [fieldAt(visit.branch, visit.path, parts)]
711
- if (field.length > 0 && rest.length + 1 > Limits.ceilings.catalogFields) {
720
+ count.fields += field.length
721
+ if (count.fields > most) {
712
722
  throw Limits.exceeded(
713
- "A Schema",
723
+ thing,
714
724
  "catalogFields",
715
725
  "fields",
716
- rest.length + 1,
717
- Limits.ceilings.catalogFields,
726
+ count.fields,
727
+ most,
718
728
  )
719
729
  }
720
730
  return Option.some([field, rest] as const)
@@ -723,22 +733,25 @@ const listOf = (
723
733
  }
724
734
 
725
735
  /**
726
- * The fields that a list shows for paths of at most `parts` parts, as for a
727
- * domain with that limit. See `Catalog.fields`.
736
+ * The fields that a list shows, a field before the fields below it, for
737
+ * paths of at most `parts` parts. For a recursive Schema, the list goes into
738
+ * each recursive place one time on each path, so it is finite. A list with
739
+ * more than `most` fields is an error, as for a domain with these limits.
740
+ * Each call makes the list again: a domain checks its size one time, and a
741
+ * list of permitted fields reads it.
728
742
  */
729
743
  export const fieldsOf = (
730
744
  catalog: Catalog,
731
- parts: number,
732
- ): ReadonlyArray<Field> =>
733
- parts === Limits.fieldPathParts
734
- ? catalog.fields
735
- : listOf(catalog.branches, parts)
745
+ parts: number = Limits.fieldPathParts,
746
+ most: number = Limits.ceilings.catalogFields,
747
+ thing: string = "A Schema",
748
+ ): ReadonlyArray<Field> => listOf(catalog.branches, parts, most, thing)
736
749
 
737
750
  /**
738
- * Makes the catalog of a Schema, with the list of its fields for the default
739
- * limit of the parts of a path. It throws when a field name has a dot or is
740
- * empty, and when the Schema has more places or fields than the ceiling of
741
- * `catalogFields`. A domain checks the size of the list with its own limit.
751
+ * Makes the catalog of a Schema. It throws when a field name has a dot or is
752
+ * empty, and when the Schema has more places than the ceiling of
753
+ * `catalogFields`. A domain checks the size of the list of the fields with
754
+ * its own limit: see {@link fieldsOf}.
742
755
  */
743
756
  export const catalogOf = (ast: SchemaAST.AST): Catalog => {
744
757
  const branches = branchesOf({
@@ -748,7 +761,6 @@ export const catalogOf = (ast: SchemaAST.AST): Catalog => {
748
761
  plain: true,
749
762
  })
750
763
  return {
751
- fields: listOf(branches, Limits.fieldPathParts),
752
764
  branches,
753
765
  edges: Arr.reduce(
754
766
  branches,
@@ -390,7 +390,11 @@ export const permittedFields = (
390
390
  const listed = Arr.filter(
391
391
  // The list for the limit of the parts of a path of the domain, as the
392
392
  // decision of each field reads it.
393
- Catalog.fieldsOf(descriptor.catalog, policy.domain.limits.fieldPathParts),
393
+ Catalog.fieldsOf(
394
+ descriptor.catalog,
395
+ policy.domain.limits.fieldPathParts,
396
+ policy.domain.limits.catalogFields,
397
+ ),
394
398
  (field) =>
395
399
  field.listed &&
396
400
  Arr.every(field.path, (segment) => typeof segment === "string"),
@@ -21,22 +21,17 @@ class DomainData extends Data.Class<{
21
21
  readonly limits: Limits.Limits
22
22
  }> {}
23
23
 
24
- /** Stops a catalog with more fields in its list than the domain takes. */
24
+ /**
25
+ * Stops a catalog with more fields in its list than the domain takes. The
26
+ * list stops at the first field past the limit, so a Schema with very many
27
+ * fields does not take very much memory first.
28
+ */
25
29
  const checkCatalog = (
26
30
  catalog: Catalog.Catalog,
27
31
  limits: Limits.Limits,
28
32
  name: string,
29
33
  ): void => {
30
- const fields = Catalog.fieldsOf(catalog, limits.fieldPathParts).length
31
- if (fields > limits.catalogFields) {
32
- throw Limits.exceeded(
33
- name,
34
- "catalogFields",
35
- "fields",
36
- fields,
37
- limits.catalogFields,
38
- )
39
- }
34
+ Catalog.fieldsOf(catalog, limits.fieldPathParts, limits.catalogFields, name)
40
35
  }
41
36
 
42
37
  /**
@@ -287,14 +287,30 @@ const decodeMeta = (
287
287
  (found) =>
288
288
  found === undefined
289
289
  ? Effect.void
290
- : Effect.mapError(
291
- Schema.decodeUnknownEffect(metaCodec(found.schema))(found.value),
292
- (error) =>
293
- new Json.Problem({
294
- path: Place.pathOf(at),
295
- message: error.message,
296
- issue: error.issue,
297
- }),
290
+ : Schema.decodeUnknownEffect(metaCodec(found.schema))(found.value).pipe(
291
+ Effect.mapError(
292
+ (error) =>
293
+ new Json.Problem({
294
+ path: Place.pathOf(at),
295
+ message: error.message,
296
+ issue: error.issue,
297
+ }),
298
+ ),
299
+ // The decoder of a Schema in Effect calls itself for each level of
300
+ // the value, so a deep value that the budget takes, as 5,000 levels
301
+ // of a recursive `meta` Schema, fills its stack. That is a defect
302
+ // of the decoder, not of the policy, so it is a problem of the
303
+ // input here, and the policy does not load.
304
+ Effect.catchDefect((defect) =>
305
+ defect instanceof RangeError
306
+ ? Effect.fail(
307
+ Json.problemAt(
308
+ at,
309
+ "The meta of the rule has too many levels for the Schema of meta",
310
+ ),
311
+ )
312
+ : Effect.die(defect),
313
+ ),
298
314
  ),
299
315
  )
300
316
  }