From 3b11292e72e0b796ac7fe7fef1e9462bf40ddb0a Mon Sep 17 00:00:00 2001
From: Rusty Russell <rusty@rustcorp.com.au>
Date: Fri, 1 Apr 2022 14:43:33 +1030
Subject: [PATCH] pyln-testing: add new schema types.

These are useful for requests:
1. "outpoint": <txid>:<outnum>
2. "feerate": strings or a number
3. "outputdesc": bitcoin-style addresses-as-keys
4. "msat_or_all": amount or "all"
4. "msat_or_any": amount or "any"
5. "short_channel_id_dir": scid with /0 or /1.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
---
 contrib/pyln-testing/pyln/testing/fixtures.py | 72 +++++++++++++++++++
 1 file changed, 72 insertions(+)

diff --git a/contrib/pyln-testing/pyln/testing/fixtures.py b/contrib/pyln-testing/pyln/testing/fixtures.py
index 8742bf727..9d55a4a00 100644
--- a/contrib/pyln-testing/pyln/testing/fixtures.py
+++ b/contrib/pyln-testing/pyln/testing/fixtures.py
@@ -266,6 +266,48 @@ def _extra_validator():
                 and txnum >= 0 and txnum < 2**24
                 and outnum >= 0 and outnum < 2**16)
 
+    def is_short_channel_id_dir(checker, instance):
+        """Short channel id with direction"""
+        if not checker.is_type(instance, "string"):
+            return False
+        if not instance.endswith("/0") and not instance.endswith("/1"):
+            return False
+        return is_short_channel_id(checker, instance[:-2])
+
+    def is_outpoint(checker, instance):
+        """Outpoint: txid and outnum"""
+        if not checker.is_type(instance, "string"):
+            return False
+        parts = instance.split(":")
+        if len(parts) != 2:
+            return False
+        if len(parts[0]) != 64 or any(c not in string.hexdigits for c in parts[0]):
+            return False
+        try:
+            outnum = int(parts[1])
+        except ValueError:
+            return False
+        return outnum < 2**32
+
+    def is_feerate(checker, instance):
+        """feerate string or number (optionally ending in perkw/perkb)"""
+        if checker.is_type(instance, "integer"):
+            return True
+        if not checker.is_type(instance, "string"):
+            return False
+        if instance in ("urgent", "normal", "slow"):
+            return True
+        if instance in ("opening", "mutual_close", "unilateral_close", "delayed_to_us", "htlc_resolution", "penalty", "min_acceptable", "max_acceptable"):
+            return True
+        if not instance.endswith("perkw") and not instance.endswith("perkb"):
+            return False
+
+        try:
+            int(instance.rpartition("per")[0])
+        except ValueError:
+            return False
+        return True
+
     def is_pubkey(checker, instance):
         """SEC1 encoded compressed pubkey"""
         if not checker.is_type(instance, "hex"):
@@ -308,6 +350,30 @@ def _extra_validator():
             return False
         return len(instance) == 64
 
+    def is_outputdesc(checker, instance):
+        """Bitcoin-style output object, keys = destination, values = amount"""
+        if not checker.is_type(instance, "object"):
+            return False
+        for k, v in instance.items():
+            if not checker.is_type(k, "string"):
+                return False
+            if v != "all":
+                if not is_msat_request(checker, v):
+                    return False
+        return True
+
+    def is_msat_or_all(checker, instance):
+        """msat field, or 'all'"""
+        if instance == "all":
+            return True
+        return is_msat_request(checker, instance)
+
+    def is_msat_or_any(checker, instance):
+        """msat field, or 'any'"""
+        if instance == "any":
+            return True
+        return is_msat_request(checker, instance)
+
     type_checker = jsonschema.Draft7Validator.TYPE_CHECKER.redefine_many({
         "hex": is_hex,
         "u64": is_u64,
@@ -316,11 +382,17 @@ def _extra_validator():
         "u8": is_u8,
         "pubkey": is_pubkey,
         "msat": is_msat,
+        "msat_or_all": is_msat_or_all,
+        "msat_or_any": is_msat_or_any,
         "txid": is_txid,
         "signature": is_signature,
         "bip340sig": is_bip340sig,
         "point32": is_point32,
         "short_channel_id": is_short_channel_id,
+        "short_channel_id_dir": is_short_channel_id_dir,
+        "outpoint": is_outpoint,
+        "feerate": is_feerate,
+        "outputdesc": is_outputdesc,
     })
 
     return jsonschema.validators.extend(jsonschema.Draft7Validator,