{
  "id": "P147",
  "slug": "webhook-router",
  "key": "P147-webhook-router",
  "title": "Webhook router",
  "summary": "A payment provider's events through five handlers written before it added a kind - if-else, no else, a default, an error, a hold queue - each one's buckets and revenue set against what every kind means.",
  "entry": "main.eml",
  "ui": "terminal",
  "readme": "# P147 - Webhook router\n\nA payment provider's events through five handlers written before the\nprovider added a kind, each handler's buckets and revenue set against what\nevery kind means.\n\n- `main.eml` - the menu, the events, the handlers' buckets and revenue,\n  what each handler does with each kind, and adding an event\n- `router.eml` - the handlers, the events so far, what each kind means\n  (stated apart from every handler), the route an event takes, the tally,\n  the true revenue, and the verdict on each handler's answer\n\nHow each part works:\n\n- The handlers were written for payment, refund, ping and test: a payment\n  adds, a refund subtracts, a ping or a test is ignored. Since then the\n  provider sends chargeback, which takes money back.\n- As in the corpus case `new-enum-value-fallthrough`, the new kind meets\n  each handler's \"anything else\": if-else's else adds the chargeback as a\n  payment; a table with a default ignores it; a table that raises fails\n  with an error; a table with a quarantine puts it in a hold queue. The\n  first two answer wrong without a word.\n- As in the corpus case `event-router-ignored-branches`, ignoring ping and\n  test is written as a branch, and a handler with the same branches and no\n  else does nothing at all with the chargeback: it goes into no bucket,\n  and only the total, 7 of 8 accounted for, shows it.\n- The revenue: the truth is 75.00; if-else says 125.00; every other\n  handler 100.00 - for raise and quarantine until the events they failed\n  or held are handled. A kind nobody has defined should wait for a person,\n  which only the quarantine does.\n\nWhat is checked: menu choices 1 to 5; a kind of 1 to 14 letters a to z or\nunderscores, and an amount in whole units from 0 to 9999. An empty answer\ncancels.\n\nSessions: `sessions/basic.in` gives:\n- the events, the handlers and each kind;\n- a dispute_won of 30, a kind nobody has defined, and another chargeback;\n  the handlers and each kind again.\n\n`sessions/bad-input.in` gives:\n- menu choices 0 and x;\n- kinds: a cancel, Payment and one of 15 letters; amounts: a cancel, 10000\n  and x;\n- a refund of 12, which every handler gets right; the handlers.\n\nBuilt on the verified corpus cases `event-router-ignored-branches` (every\nevent accounted for in exactly one bucket, and ignoring as a branch) and\n`new-enum-value-fallthrough` (four ways a consumer handles a value added\nlater, right, silently wrong, or loud).\n",
  "modules": [
    {
      "name": "main.eml",
      "eml": "# P147 webhook router: a payment provider's events, five handlers' buckets\n# and revenue set against what each kind means, what each handler does with\n# each kind, and new events, of a kind seen before or never seen.\nimport router\n\ndef plain(s):\n    0 => i\n    len(s) => j\n    while i < j and s[i] == \" \":\n        i + 1 => i\n    while j > i and s[j - 1] == \" \":\n        j - 1 => j\n    return s[i:j]\n\ndef pad(s, n):\n    s => out\n    while len(out) < n:\n        out + \" \" => out\n    return out\n\ndef lpad(s, n):\n    s => out\n    while len(out) < n:\n        \" \" + out => out\n    return out\n\ndef money(c):\n    if c < 0:\n        return \"-\" + money(0 - c)\n    c % 100 => p\n    if p < 10:\n        return str(int((c - p) / 100)) + \".0\" + str(p)\n    return str(int((c - p) / 100)) + \".\" + str(p)\n\ndef times(n, one, many):\n    if n == 1:\n        return \"1 \" + one\n    return str(n) + \" \" + many\n\ndef show_events(events):\n    \"The events, in the order they came:\" ^0\n    1 => k\n    for e in events:\n        router.truth(e[0]) => m\n        if m == \"hold\":\n            \"nobody has said; hold it for a person\" => m\n        (\"  \" + lpad(str(k), 2) + \"  \" + pad(e[0], 18) + lpad(money(e[1]), 8) + \"   means: \" + m) ^0\n        k + 1 => k\n\ndef run(events):\n    (\"  \" + pad(\"handler\", 12) + \"revenue  added  subtracted  ignored  held  failed  accounted\") ^0\n    for r in router.routers():\n        router.tally(r, events) => t\n        (\"  \" + pad(r, 12) + lpad(money(t[0]), 7) + lpad(str(t[1]), 7) + lpad(str(t[2]), 12) + lpad(str(t[3]), 9) + lpad(str(t[4]), 6) + lpad(str(t[5]), 8) + lpad(str(t[6]) + \" of \" + str(len(events)), 11)) ^0\n    router.true_revenue(events) => truth\n    (\"  \" + pad(\"the truth\", 12) + lpad(money(truth), 7)) ^0\n    for r in router.routers():\n        router.tally(r, events) => t\n        0 => wrong\n        0 => lost\n        0 => pending\n        for e in events:\n            router.verdict(r, e[0]) => v\n            if v == \"wrong\":\n                wrong + 1 => wrong\n            if v == \"lost\":\n                lost + 1 => lost\n            if v == \"held\" or v == \"failed\":\n                if router.truth(e[0]) == \"add\":\n                    pending + e[1] => pending\n                if router.truth(e[0]) == \"subtract\":\n                    pending - e[1] => pending\n        [] => parts\n        if wrong > 0:\n            parts + [times(wrong, \"event\", \"events\") + \" handled wrong without a word\"] => parts\n        if lost > 0:\n            parts + [times(lost, \"event\", \"events\") + \" went nowhere, which only the total shows: \" + str(t[6]) + \" of \" + str(len(events)) + \" accounted for\"] => parts\n        if t[5] > 0:\n            parts + [times(t[5], \"event\", \"events\") + \" failed with an error, so someone hears of it\"] => parts\n        if t[4] > 0:\n            parts + [times(t[4], \"event waits\", \"events wait\") + \" in the hold queue for a person\"] => parts\n        \"\" => until\n        if t[0] + pending == truth and pending != 0:\n            \", until those are handled\" => until\n        if t[0] > truth:\n            parts + [\"revenue \" + money(t[0] - truth) + \" above the truth\" + until] => parts\n        \"\" => line\n        for p in parts:\n            if line == \"\":\n                p => line\n            else:\n                line + \"; \" + p => line\n        (\"  \" + r + \": \" + line + \".\") ^0\n\ndef kinds(events):\n    [] => out\n    for e in events:\n        False => have\n        for k in out:\n            if k == e[0]:\n                True => have\n        if not have:\n            out + [e[0]] => out\n    return out\n\ndef per_kind(events):\n    (\"  \" + pad(\"kind\", 14) + pad(\"means\", 10)) => head\n    for r in router.routers():\n        if r == \"quarantine\":\n            head + r => head\n        else:\n            head + pad(r, 16) => head\n    head ^0\n    for k in kinds(events):\n        (\"  \" + pad(k, 14) + pad(router.truth(k), 10)) => line\n        for r in router.routers():\n            router.route(r, k) => a\n            if a == \"\":\n                \"(nothing)\" => a\n            router.verdict(r, k) => v\n            if v != \"right\":\n                a + \" \" + v => a\n            if r == \"quarantine\":\n                line + a => line\n            else:\n                line + pad(a, 16) => line\n        line ^0\n    (\"The handlers were written for: payment, refund, ping and test.\") ^0\n\ndef add(events):\n    plain(input(\"kind> \")) => k\n    if k == \"\":\n        \"Cancelled.\" ^0\n        return events\n    if len(k) > 14:\n        \"Type a kind of 1 to 14 letters a to z or underscores.\" ^0\n        return events\n    for c in k:\n        if not ((c >= \"a\" and c <= \"z\") or c == \"_\"):\n            \"Type a kind of 1 to 14 letters a to z or underscores.\" ^0\n            return events\n    plain(input(\"amount in whole units (0 to 9999)> \")) => t\n    if t == \"\":\n        \"Cancelled.\" ^0\n        return events\n    if len(t) > 4:\n        \"Type a whole number from 0 to 9999.\" ^0\n        return events\n    for c in t:\n        if c < \"0\" or c > \"9\":\n            \"Type a whole number from 0 to 9999.\" ^0\n            return events\n    int(t) * 100 => amount\n    if router.truth(k) == \"hold\":\n        (\"Event \" + str(len(events) + 1) + \": \" + k + \" \" + money(amount) + \", a kind nobody has defined: it should wait for a person.\") ^0\n    else:\n        (\"Event \" + str(len(events) + 1) + \": \" + k + \" \" + money(amount) + \", which means \" + router.truth(k) + \".\") ^0\n    for r in router.routers():\n        router.route(r, k) => a\n        if a == \"\":\n            \"nothing at all\" => a\n        (\"  \" + pad(r, 12) + pad(a, 16) + router.verdict(r, k)) ^0\n    return events + [[k, amount]]\n\nrouter.opening() => events\n\n\"== Webhook router ==\" ^0\n\"A payment provider's events through five handlers written before it added a kind.\" ^0\nTrue => running\nwhile running:\n    \"\" ^0\n    \"1) events  2) run the handlers  3) each kind  4) add an event  5) quit\" ^0\n    plain(input(\"choice> \")) => choice\n    if choice == \"1\":\n        show_events(events)\n    elif choice == \"2\":\n        run(events)\n    elif choice == \"3\":\n        per_kind(events)\n    elif choice == \"4\":\n        add(events) => events\n    elif choice == \"5\":\n        False => running\n    else:\n        \"Pick a number from 1 to 5.\" ^0\n\"Bye.\" ^0\n",
      "python": "import router\n\ndef plain(s):\n    i = 0\n    j = len(s)\n    while i < j and s[i] == \" \":\n        i = i + 1\n    while j > i and s[j - 1] == \" \":\n        j = j - 1\n    return s[i:j]\n\ndef pad(s, n):\n    out = s\n    while len(out) < n:\n        out = out + \" \"\n    return out\n\ndef lpad(s, n):\n    out = s\n    while len(out) < n:\n        out = \" \" + out\n    return out\n\ndef money(c):\n    if c < 0:\n        return \"-\" + money(0 - c)\n    p = c % 100\n    if p < 10:\n        return str(int((c - p) / 100)) + \".0\" + str(p)\n    return str(int((c - p) / 100)) + \".\" + str(p)\n\ndef times(n, one, many):\n    if n == 1:\n        return \"1 \" + one\n    return str(n) + \" \" + many\n\ndef show_events(events):\n    print(\"The events, in the order they came:\")\n    k = 1\n    for e in events:\n        m = router.truth(e[0])\n        if m == \"hold\":\n            m = \"nobody has said; hold it for a person\"\n        print(\"  \" + lpad(str(k), 2) + \"  \" + pad(e[0], 18) + lpad(money(e[1]), 8) + \"   means: \" + m)\n        k = k + 1\n\ndef run(events):\n    print(\"  \" + pad(\"handler\", 12) + \"revenue  added  subtracted  ignored  held  failed  accounted\")\n    for r in router.routers():\n        t = router.tally(r, events)\n        print(\"  \" + pad(r, 12) + lpad(money(t[0]), 7) + lpad(str(t[1]), 7) + lpad(str(t[2]), 12) + lpad(str(t[3]), 9) + lpad(str(t[4]), 6) + lpad(str(t[5]), 8) + lpad(str(t[6]) + \" of \" + str(len(events)), 11))\n    truth = router.true_revenue(events)\n    print(\"  \" + pad(\"the truth\", 12) + lpad(money(truth), 7))\n    for r in router.routers():\n        t = router.tally(r, events)\n        wrong = 0\n        lost = 0\n        pending = 0\n        for e in events:\n            v = router.verdict(r, e[0])\n            if v == \"wrong\":\n                wrong = wrong + 1\n            if v == \"lost\":\n                lost = lost + 1\n            if v == \"held\" or v == \"failed\":\n                if router.truth(e[0]) == \"add\":\n                    pending = pending + e[1]\n                if router.truth(e[0]) == \"subtract\":\n                    pending = pending - e[1]\n        parts = []\n        if wrong > 0:\n            parts = parts + [times(wrong, \"event\", \"events\") + \" handled wrong without a word\"]\n        if lost > 0:\n            parts = parts + [times(lost, \"event\", \"events\") + \" went nowhere, which only the total shows: \" + str(t[6]) + \" of \" + str(len(events)) + \" accounted for\"]\n        if t[5] > 0:\n            parts = parts + [times(t[5], \"event\", \"events\") + \" failed with an error, so someone hears of it\"]\n        if t[4] > 0:\n            parts = parts + [times(t[4], \"event waits\", \"events wait\") + \" in the hold queue for a person\"]\n        until = \"\"\n        if t[0] + pending == truth and pending != 0:\n            until = \", until those are handled\"\n        if t[0] > truth:\n            parts = parts + [\"revenue \" + money(t[0] - truth) + \" above the truth\" + until]\n        line = \"\"\n        for p in parts:\n            if line == \"\":\n                line = p\n            else:\n                line = line + \"; \" + p\n        print(\"  \" + r + \": \" + line + \".\")\n\ndef kinds(events):\n    out = []\n    for e in events:\n        have = False\n        for k in out:\n            if k == e[0]:\n                have = True\n        if not have:\n            out = out + [e[0]]\n    return out\n\ndef per_kind(events):\n    head = \"  \" + pad(\"kind\", 14) + pad(\"means\", 10)\n    for r in router.routers():\n        if r == \"quarantine\":\n            head = head + r\n        else:\n            head = head + pad(r, 16)\n    print(head)\n    for k in kinds(events):\n        line = \"  \" + pad(k, 14) + pad(router.truth(k), 10)\n        for r in router.routers():\n            a = router.route(r, k)\n            if a == \"\":\n                a = \"(nothing)\"\n            v = router.verdict(r, k)\n            if v != \"right\":\n                a = a + \" \" + v\n            if r == \"quarantine\":\n                line = line + a\n            else:\n                line = line + pad(a, 16)\n        print(line)\n    print(\"The handlers were written for: payment, refund, ping and test.\")\n\ndef add(events):\n    k = plain(input(\"kind> \"))\n    if k == \"\":\n        print(\"Cancelled.\")\n        return events\n    if len(k) > 14:\n        print(\"Type a kind of 1 to 14 letters a to z or underscores.\")\n        return events\n    for c in k:\n        if not (c >= \"a\" and c <= \"z\" or c == \"_\"):\n            print(\"Type a kind of 1 to 14 letters a to z or underscores.\")\n            return events\n    t = plain(input(\"amount in whole units (0 to 9999)> \"))\n    if t == \"\":\n        print(\"Cancelled.\")\n        return events\n    if len(t) > 4:\n        print(\"Type a whole number from 0 to 9999.\")\n        return events\n    for c in t:\n        if c < \"0\" or c > \"9\":\n            print(\"Type a whole number from 0 to 9999.\")\n            return events\n    amount = int(t) * 100\n    if router.truth(k) == \"hold\":\n        print(\"Event \" + str(len(events) + 1) + \": \" + k + \" \" + money(amount) + \", a kind nobody has defined: it should wait for a person.\")\n    else:\n        print(\"Event \" + str(len(events) + 1) + \": \" + k + \" \" + money(amount) + \", which means \" + router.truth(k) + \".\")\n    for r in router.routers():\n        a = router.route(r, k)\n        if a == \"\":\n            a = \"nothing at all\"\n        print(\"  \" + pad(r, 12) + pad(a, 16) + router.verdict(r, k))\n    return events + [[k, amount]]\n\nevents = router.opening()\nprint(\"== Webhook router ==\")\nprint(\"A payment provider's events through five handlers written before it added a kind.\")\nrunning = True\nwhile running:\n    print(\"\")\n    print(\"1) events  2) run the handlers  3) each kind  4) add an event  5) quit\")\n    choice = plain(input(\"choice> \"))\n    if choice == \"1\":\n        show_events(events)\n    elif choice == \"2\":\n        run(events)\n    elif choice == \"3\":\n        per_kind(events)\n    elif choice == \"4\":\n        events = add(events)\n    elif choice == \"5\":\n        running = False\n    else:\n        print(\"Pick a number from 1 to 5.\")\nprint(\"Bye.\")\n"
    },
    {
      "name": "router.eml",
      "eml": "# P147 webhook router - a payment provider's events routed by five\n# handlers written before the provider added a new kind. As in the corpus\n# case event-router-ignored-branches, ignoring an event is a decision that\n# needs a branch, and only the total of every bucket shows an event that\n# went nowhere. As in the corpus case new-enum-value-fallthrough, a kind\n# added later meets each handler's \"anything else\": an else that answers\n# for it, a default that guesses, an error, or a hold queue. An event is\n# [kind, amount in cents]; an action is add, subtract, ignore, hold, fail,\n# or \"\" when the handler does nothing with it at all.\n\ndef routers():\n    return [\"if-else\", \"no else\", \"default\", \"raise\", \"quarantine\"]\n\ndef what():\n    return [\"refund subtracts, ping and test pass, else adds\", \"the same branches with no else at all\", \"a table, and ignore for a kind it lacks\", \"a table, and an error for a kind it lacks\", \"a table, and a hold queue for a kind it lacks\"]\n\ndef opening():\n    return [[\"ping\", 0], [\"payment\", 5000], [\"test\", 0], [\"payment\", 2500], [\"refund\", 1500], [\"ping\", 0], [\"payment\", 4000], [\"chargeback\", 2500]]\n\ndef known():\n    # the kinds the handlers were written for\n    return [\"payment\", \"refund\", \"ping\", \"test\"]\n\ndef truth(kind):\n    # what each kind means, stated apart from every handler; a kind nobody\n    # has defined should wait for a person\n    if kind == \"payment\":\n        return \"add\"\n    if kind == \"refund\" or kind == \"chargeback\":\n        return \"subtract\"\n    if kind == \"ping\" or kind == \"test\":\n        return \"ignore\"\n    return \"hold\"\n\ndef in_table(kind):\n    for k in known():\n        if k == kind:\n            return True\n    return False\n\ndef route(router, kind):\n    if router == \"if-else\":\n        if kind == \"refund\":\n            return \"subtract\"\n        elif kind == \"ping\" or kind == \"test\":\n            return \"ignore\"\n        else:\n            return \"add\"\n    if router == \"no else\":\n        if kind == \"payment\":\n            return \"add\"\n        if kind == \"refund\":\n            return \"subtract\"\n        if kind == \"ping\" or kind == \"test\":\n            return \"ignore\"\n        return \"\"\n    if in_table(kind):\n        return truth(kind)\n    if router == \"default\":\n        return \"ignore\"\n    if router == \"raise\":\n        return \"fail\"\n    return \"hold\"\n\ndef tally(router, events):\n    # [revenue, added, subtracted, ignored, held, failed, accounted]\n    [0, 0, 0, 0, 0, 0, 0] => t\n    for e in events:\n        route(router, e[0]) => a\n        if a == \"add\":\n            [t[0] + e[1], t[1] + 1, t[2], t[3], t[4], t[5], t[6] + 1] => t\n        elif a == \"subtract\":\n            [t[0] - e[1], t[1], t[2] + 1, t[3], t[4], t[5], t[6] + 1] => t\n        elif a == \"ignore\":\n            [t[0], t[1], t[2], t[3] + 1, t[4], t[5], t[6] + 1] => t\n        elif a == \"hold\":\n            [t[0], t[1], t[2], t[3], t[4] + 1, t[5], t[6] + 1] => t\n        elif a == \"fail\":\n            [t[0], t[1], t[2], t[3], t[4], t[5] + 1, t[6] + 1] => t\n    return t\n\ndef true_revenue(events):\n    0 => r\n    for e in events:\n        truth(e[0]) => a\n        if a == \"add\":\n            r + e[1] => r\n        elif a == \"subtract\":\n            r - e[1] => r\n    return r\n\ndef verdict(router, kind):\n    # right, held, failed, lost (nothing done and nothing counted), or wrong\n    # (a definite answer, and not the true one)\n    route(router, kind) => a\n    if a == truth(kind):\n        return \"right\"\n    if a == \"\":\n        return \"lost\"\n    if a == \"hold\":\n        return \"held\"\n    if a == \"fail\":\n        return \"failed\"\n    return \"wrong\"\n",
      "python": "def routers():\n    return [\"if-else\", \"no else\", \"default\", \"raise\", \"quarantine\"]\n\ndef what():\n    return [\"refund subtracts, ping and test pass, else adds\", \"the same branches with no else at all\", \"a table, and ignore for a kind it lacks\", \"a table, and an error for a kind it lacks\", \"a table, and a hold queue for a kind it lacks\"]\n\ndef opening():\n    return [[\"ping\", 0], [\"payment\", 5000], [\"test\", 0], [\"payment\", 2500], [\"refund\", 1500], [\"ping\", 0], [\"payment\", 4000], [\"chargeback\", 2500]]\n\ndef known():\n    return [\"payment\", \"refund\", \"ping\", \"test\"]\n\ndef truth(kind):\n    if kind == \"payment\":\n        return \"add\"\n    if kind == \"refund\" or kind == \"chargeback\":\n        return \"subtract\"\n    if kind == \"ping\" or kind == \"test\":\n        return \"ignore\"\n    return \"hold\"\n\ndef in_table(kind):\n    for k in known():\n        if k == kind:\n            return True\n    return False\n\ndef route(router, kind):\n    if router == \"if-else\":\n        if kind == \"refund\":\n            return \"subtract\"\n        elif kind == \"ping\" or kind == \"test\":\n            return \"ignore\"\n        else:\n            return \"add\"\n    if router == \"no else\":\n        if kind == \"payment\":\n            return \"add\"\n        if kind == \"refund\":\n            return \"subtract\"\n        if kind == \"ping\" or kind == \"test\":\n            return \"ignore\"\n        return \"\"\n    if in_table(kind):\n        return truth(kind)\n    if router == \"default\":\n        return \"ignore\"\n    if router == \"raise\":\n        return \"fail\"\n    return \"hold\"\n\ndef tally(router, events):\n    t = [0, 0, 0, 0, 0, 0, 0]\n    for e in events:\n        a = route(router, e[0])\n        if a == \"add\":\n            t = [t[0] + e[1], t[1] + 1, t[2], t[3], t[4], t[5], t[6] + 1]\n        elif a == \"subtract\":\n            t = [t[0] - e[1], t[1], t[2] + 1, t[3], t[4], t[5], t[6] + 1]\n        elif a == \"ignore\":\n            t = [t[0], t[1], t[2], t[3] + 1, t[4], t[5], t[6] + 1]\n        elif a == \"hold\":\n            t = [t[0], t[1], t[2], t[3], t[4] + 1, t[5], t[6] + 1]\n        elif a == \"fail\":\n            t = [t[0], t[1], t[2], t[3], t[4], t[5] + 1, t[6] + 1]\n    return t\n\ndef true_revenue(events):\n    r = 0\n    for e in events:\n        a = truth(e[0])\n        if a == \"add\":\n            r = r + e[1]\n        elif a == \"subtract\":\n            r = r - e[1]\n    return r\n\ndef verdict(router, kind):\n    a = route(router, kind)\n    if a == truth(kind):\n        return \"right\"\n    if a == \"\":\n        return \"lost\"\n    if a == \"hold\":\n        return \"held\"\n    if a == \"fail\":\n        return \"failed\"\n    return \"wrong\"\n"
    }
  ],
  "sessions": [
    {
      "name": "bad-input",
      "input": "0\nx\n4\n\n4\nPayment\n4\nfifteen_letters\n4\nrefund\n\n4\nrefund\n10000\n4\nrefund\nx\n4\nrefund\n12\n2\n5\n",
      "screen": "== Webhook router ==\nA payment provider's events through five handlers written before it added a kind.\n\n1) events  2) run the handlers  3) each kind  4) add an event  5) quit\nchoice> 0\nPick a number from 1 to 5.\n\n1) events  2) run the handlers  3) each kind  4) add an event  5) quit\nchoice> x\nPick a number from 1 to 5.\n\n1) events  2) run the handlers  3) each kind  4) add an event  5) quit\nchoice> 4\nkind> \nCancelled.\n\n1) events  2) run the handlers  3) each kind  4) add an event  5) quit\nchoice> 4\nkind> Payment\nType a kind of 1 to 14 letters a to z or underscores.\n\n1) events  2) run the handlers  3) each kind  4) add an event  5) quit\nchoice> 4\nkind> fifteen_letters\nType a kind of 1 to 14 letters a to z or underscores.\n\n1) events  2) run the handlers  3) each kind  4) add an event  5) quit\nchoice> 4\nkind> refund\namount in whole units (0 to 9999)> \nCancelled.\n\n1) events  2) run the handlers  3) each kind  4) add an event  5) quit\nchoice> 4\nkind> refund\namount in whole units (0 to 9999)> 10000\nType a whole number from 0 to 9999.\n\n1) events  2) run the handlers  3) each kind  4) add an event  5) quit\nchoice> 4\nkind> refund\namount in whole units (0 to 9999)> x\nType a whole number from 0 to 9999.\n\n1) events  2) run the handlers  3) each kind  4) add an event  5) quit\nchoice> 4\nkind> refund\namount in whole units (0 to 9999)> 12\nEvent 9: refund 12.00, which means subtract.\n  if-else     subtract        right\n  no else     subtract        right\n  default     subtract        right\n  raise       subtract        right\n  quarantine  subtract        right\n\n1) events  2) run the handlers  3) each kind  4) add an event  5) quit\nchoice> 2\n  handler     revenue  added  subtracted  ignored  held  failed  accounted\n  if-else      113.00      4           2        3     0       0     9 of 9\n  no else       88.00      3           2        3     0       0     8 of 9\n  default       88.00      3           2        4     0       0     9 of 9\n  raise         88.00      3           2        3     0       1     9 of 9\n  quarantine    88.00      3           2        3     1       0     9 of 9\n  the truth     63.00\n  if-else: 1 event handled wrong without a word; revenue 50.00 above the truth.\n  no else: 1 event went nowhere, which only the total shows: 8 of 9 accounted for; revenue 25.00 above the truth.\n  default: 1 event handled wrong without a word; revenue 25.00 above the truth.\n  raise: 1 event failed with an error, so someone hears of it; revenue 25.00 above the truth, until those are handled.\n  quarantine: 1 event waits in the hold queue for a person; revenue 25.00 above the truth, until those are handled.\n\n1) events  2) run the handlers  3) each kind  4) add an event  5) quit\nchoice> 5\nBye.\n",
      "interpreter": "equal"
    },
    {
      "name": "basic",
      "input": "1\n2\n3\n4\ndispute_won\n30\n4\nchargeback\n10\n2\n3\n5\n",
      "screen": "== Webhook router ==\nA payment provider's events through five handlers written before it added a kind.\n\n1) events  2) run the handlers  3) each kind  4) add an event  5) quit\nchoice> 1\nThe events, in the order they came:\n   1  ping                  0.00   means: ignore\n   2  payment              50.00   means: add\n   3  test                  0.00   means: ignore\n   4  payment              25.00   means: add\n   5  refund               15.00   means: subtract\n   6  ping                  0.00   means: ignore\n   7  payment              40.00   means: add\n   8  chargeback           25.00   means: subtract\n\n1) events  2) run the handlers  3) each kind  4) add an event  5) quit\nchoice> 2\n  handler     revenue  added  subtracted  ignored  held  failed  accounted\n  if-else      125.00      4           1        3     0       0     8 of 8\n  no else      100.00      3           1        3     0       0     7 of 8\n  default      100.00      3           1        4     0       0     8 of 8\n  raise        100.00      3           1        3     0       1     8 of 8\n  quarantine   100.00      3           1        3     1       0     8 of 8\n  the truth     75.00\n  if-else: 1 event handled wrong without a word; revenue 50.00 above the truth.\n  no else: 1 event went nowhere, which only the total shows: 7 of 8 accounted for; revenue 25.00 above the truth.\n  default: 1 event handled wrong without a word; revenue 25.00 above the truth.\n  raise: 1 event failed with an error, so someone hears of it; revenue 25.00 above the truth, until those are handled.\n  quarantine: 1 event waits in the hold queue for a person; revenue 25.00 above the truth, until those are handled.\n\n1) events  2) run the handlers  3) each kind  4) add an event  5) quit\nchoice> 3\n  kind          means     if-else         no else         default         raise           quarantine\n  ping          ignore    ignore          ignore          ignore          ignore          ignore\n  payment       add       add             add             add             add             add\n  test          ignore    ignore          ignore          ignore          ignore          ignore\n  refund        subtract  subtract        subtract        subtract        subtract        subtract\n  chargeback    subtract  add wrong       (nothing) lost  ignore wrong    fail failed     hold held\nThe handlers were written for: payment, refund, ping and test.\n\n1) events  2) run the handlers  3) each kind  4) add an event  5) quit\nchoice> 4\nkind> dispute_won\namount in whole units (0 to 9999)> 30\nEvent 9: dispute_won 30.00, a kind nobody has defined: it should wait for a person.\n  if-else     add             wrong\n  no else     nothing at all  lost\n  default     ignore          wrong\n  raise       fail            failed\n  quarantine  hold            right\n\n1) events  2) run the handlers  3) each kind  4) add an event  5) quit\nchoice> 4\nkind> chargeback\namount in whole units (0 to 9999)> 10\nEvent 10: chargeback 10.00, which means subtract.\n  if-else     add             wrong\n  no else     nothing at all  lost\n  default     ignore          wrong\n  raise       fail            failed\n  quarantine  hold            held\n\n1) events  2) run the handlers  3) each kind  4) add an event  5) quit\nchoice> 2\n  handler     revenue  added  subtracted  ignored  held  failed  accounted\n  if-else      165.00      6           1        3     0       0   10 of 10\n  no else      100.00      3           1        3     0       0    7 of 10\n  default      100.00      3           1        6     0       0   10 of 10\n  raise        100.00      3           1        3     0       3   10 of 10\n  quarantine   100.00      3           1        3     3       0   10 of 10\n  the truth     65.00\n  if-else: 3 events handled wrong without a word; revenue 100.00 above the truth.\n  no else: 3 events went nowhere, which only the total shows: 7 of 10 accounted for; revenue 35.00 above the truth.\n  default: 3 events handled wrong without a word; revenue 35.00 above the truth.\n  raise: 3 events failed with an error, so someone hears of it; revenue 35.00 above the truth, until those are handled.\n  quarantine: 3 events wait in the hold queue for a person; revenue 35.00 above the truth, until those are handled.\n\n1) events  2) run the handlers  3) each kind  4) add an event  5) quit\nchoice> 3\n  kind          means     if-else         no else         default         raise           quarantine\n  ping          ignore    ignore          ignore          ignore          ignore          ignore\n  payment       add       add             add             add             add             add\n  test          ignore    ignore          ignore          ignore          ignore          ignore\n  refund        subtract  subtract        subtract        subtract        subtract        subtract\n  chargeback    subtract  add wrong       (nothing) lost  ignore wrong    fail failed     hold held\n  dispute_won   hold      add wrong       (nothing) lost  ignore wrong    fail failed     hold\nThe handlers were written for: payment, refund, ping and test.\n\n1) events  2) run the handlers  3) each kind  4) add an event  5) quit\nchoice> 5\nBye.\n",
      "interpreter": "equal"
    }
  ],
  "builtOn": [
    {
      "slug": "event-router-ignored-branches",
      "caseId": "187-event-router-ignored-branches",
      "title": "Event router: branches that do nothing, on purpose"
    },
    {
      "slug": "new-enum-value-fallthrough",
      "caseId": "284-new-enum-value-fallthrough",
      "title": "New enum value fallthrough — the else branch answers for a case it never saw"
    }
  ],
  "updated": "2026-10-18"
}
