Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ContractSpec): add JsonSchema method for corresponding JSON schema #171

Closed
wants to merge 5 commits into from

Conversation

willemneal
Copy link
Member

@willemneal willemneal commented Nov 13, 2023

This will allow tooling like react forms to use the JSON schema to generate UI that is valid for the ContractSpec's encoding of args.

This is also useful validating inputs before encoding JS to XDR.

For reference here is the kitchen sink example from our tests:
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "definitions": {
    "U64": {
      "type": "string",
      "pattern": "^[0-9]+$",
      "minLength": 1,
      "maxLength": 20
    },
    "I64": {
      "type": "string",
      "pattern": "^-?^[0-9]+$",
      "minLength": 1,
      "maxLength": 21
    },
    "U32": {
      "type": "integer",
      "minimum": 0,
      "maximum": 4294967295
    },
    "I32": {
      "type": "integer",
      "minimum": -2147483648,
      "maximum": 2147483647
    },
    "U128": {
      "type": "string",
      "pattern": "^[0-9]+$",
      "minLength": 1,
      "maxLength": 39
    },
    "I128": {
      "type": "string",
      "pattern": "^-?[0-9]+$",
      "minLength": 1,
      "maxLength": 40
    },
    "U256": {
      "type": "string",
      "pattern": "^[0-9]+$",
      "minLength": 1,
      "maxLength": 78
    },
    "I256": {
      "type": "string",
      "pattern": "^-?[0-9]+$",
      "minLength": 1,
      "maxLength": 79
    },
    "Address": {
      "type": "string",
      "description": "Address can be a public key or contract id"
    },
    "ScString": {
      "type": "string",
      "description": "ScString is a string"
    },
    "ScSymbol": {
      "type": "string",
      "description": "ScString is a string"
    },
    "DataUrl": {
      "type": "string",
      "format": "data-url"
    },
    "Test": {
      "description": "This is from the rust doc above the struct Test",
      "properties": {
        "a": {
          "$ref": "#/definitions/U32"
        },
        "b": {
          "type": "boolean"
        },
        "c": {
          "$ref": "#/definitions/ScSymbol"
        },
        "additionalProperties": false
      },
      "required": [
        "a",
        "b",
        "c"
      ],
      "type": "object"
    },
    "SimpleEnum": {
      "oneOf": [
        {
          "tag": "First"
        },
        {
          "tag": "Second"
        },
        {
          "tag": "Third"
        }
      ]
    },
    "RoyalCard": {
      "oneOf": [
        {
          "description": "",
          "title": "Jack",
          "enum": [
            11
          ],
          "type": "number"
        },
        {
          "description": "",
          "title": "Queen",
          "enum": [
            12
          ],
          "type": "number"
        },
        {
          "description": "",
          "title": "King",
          "enum": [
            13
          ],
          "type": "number"
        }
      ]
    },
    "TupleStruct": {
      "description": "",
      "properties": {
        "0": {
          "$ref": "#/definitions/Test"
        },
        "1": {
          "$ref": "#/definitions/SimpleEnum"
        },
        "additionalProperties": false
      },
      "required": [
        "0",
        "1"
      ],
      "type": "object"
    },
    "ComplexEnum": {
      "oneOf": [
        {
          "tag": "Struct",
          "values": [
            {
              "$ref": "#/definitions/Test"
            }
          ]
        },
        {
          "tag": "Tuple",
          "values": [
            {
              "$ref": "#/definitions/TupleStruct"
            }
          ]
        },
        {
          "tag": "Enum",
          "values": [
            {
              "$ref": "#/definitions/SimpleEnum"
            }
          ]
        },
        {
          "tag": "Asset",
          "values": [
            {
              "$ref": "#/definitions/Address"
            },
            {
              "$ref": "#/definitions/I128"
            }
          ]
        },
        {
          "tag": "Void"
        }
      ]
    },
    "hello": {
      "additionalProperties": false,
      "contractMethod": "view",
      "properties": {
        "args": {
          "additionalProperties": false,
          "properties": {
            "hello": {
              "$ref": "#/definitions/ScSymbol"
            }
          },
          "type": "object",
          "required": [
            "hello"
          ]
        }
      }
    },
    "woid": {
      "additionalProperties": false,
      "contractMethod": "view",
      "properties": {
        "args": {
          "additionalProperties": false,
          "properties": {},
          "type": "object"
        }
      }
    },
    "val": {
      "additionalProperties": false,
      "contractMethod": "view",
      "properties": {
        "args": {
          "additionalProperties": false,
          "properties": {},
          "type": "object"
        }
      }
    },
    "u32_fail_on_even": {
      "additionalProperties": false,
      "contractMethod": "view",
      "properties": {
        "args": {
          "additionalProperties": false,
          "properties": {
            "u32_": {
              "$ref": "#/definitions/U32"
            }
          },
          "type": "object",
          "required": [
            "u32_"
          ]
        }
      }
    },
    "u32_": {
      "additionalProperties": false,
      "contractMethod": "view",
      "properties": {
        "args": {
          "additionalProperties": false,
          "properties": {
            "u32_": {
              "$ref": "#/definitions/U32"
            }
          },
          "type": "object",
          "required": [
            "u32_"
          ]
        }
      }
    },
    "i32_": {
      "additionalProperties": false,
      "contractMethod": "view",
      "properties": {
        "args": {
          "additionalProperties": false,
          "properties": {
            "i32_": {
              "$ref": "#/definitions/I32"
            }
          },
          "type": "object",
          "required": [
            "i32_"
          ]
        }
      }
    },
    "i64_": {
      "additionalProperties": false,
      "contractMethod": "view",
      "properties": {
        "args": {
          "additionalProperties": false,
          "properties": {
            "i64_": {
              "$ref": "#/definitions/I64"
            }
          },
          "type": "object",
          "required": [
            "i64_"
          ]
        }
      }
    },
    "strukt_hel": {
      "additionalProperties": false,
      "contractMethod": "view",
      "properties": {
        "args": {
          "additionalProperties": false,
          "properties": {
            "strukt": {
              "$ref": "#/definitions/Test"
            }
          },
          "type": "object",
          "required": [
            "strukt"
          ]
        }
      },
      "description": "Example contract method which takes a struct"
    },
    "strukt": {
      "additionalProperties": false,
      "contractMethod": "view",
      "properties": {
        "args": {
          "additionalProperties": false,
          "properties": {
            "strukt": {
              "$ref": "#/definitions/Test"
            }
          },
          "type": "object",
          "required": [
            "strukt"
          ]
        }
      }
    },
    "simple": {
      "additionalProperties": false,
      "contractMethod": "view",
      "properties": {
        "args": {
          "additionalProperties": false,
          "properties": {
            "simple": {
              "$ref": "#/definitions/SimpleEnum"
            }
          },
          "type": "object",
          "required": [
            "simple"
          ]
        }
      }
    },
    "complex": {
      "additionalProperties": false,
      "contractMethod": "view",
      "properties": {
        "args": {
          "additionalProperties": false,
          "properties": {
            "complex": {
              "$ref": "#/definitions/ComplexEnum"
            }
          },
          "type": "object",
          "required": [
            "complex"
          ]
        }
      }
    },
    "addresse": {
      "additionalProperties": false,
      "contractMethod": "view",
      "properties": {
        "args": {
          "additionalProperties": false,
          "properties": {
            "addresse": {
              "$ref": "#/definitions/Address"
            }
          },
          "type": "object",
          "required": [
            "addresse"
          ]
        }
      }
    },
    "bytes": {
      "additionalProperties": false,
      "contractMethod": "view",
      "properties": {
        "args": {
          "additionalProperties": false,
          "properties": {
            "bytes": {
              "$ref": "#/definitions/DataUrl"
            }
          },
          "type": "object",
          "required": [
            "bytes"
          ]
        }
      }
    },
    "bytes_n": {
      "additionalProperties": false,
      "contractMethod": "view",
      "properties": {
        "args": {
          "additionalProperties": false,
          "properties": {
            "bytes_n": {
              "$ref": "#/definitions/DataUrl",
              "maxLength": 9
            }
          },
          "type": "object",
          "required": [
            "bytes_n"
          ]
        }
      }
    },
    "card": {
      "additionalProperties": false,
      "contractMethod": "view",
      "properties": {
        "args": {
          "additionalProperties": false,
          "properties": {
            "card": {
              "$ref": "#/definitions/RoyalCard"
            }
          },
          "type": "object",
          "required": [
            "card"
          ]
        }
      }
    },
    "boolean": {
      "additionalProperties": false,
      "contractMethod": "view",
      "properties": {
        "args": {
          "additionalProperties": false,
          "properties": {
            "boolean": {
              "type": "boolean"
            }
          },
          "type": "object",
          "required": [
            "boolean"
          ]
        }
      }
    },
    "not": {
      "additionalProperties": false,
      "contractMethod": "view",
      "properties": {
        "args": {
          "additionalProperties": false,
          "properties": {
            "boolean": {
              "type": "boolean"
            }
          },
          "type": "object",
          "required": [
            "boolean"
          ]
        }
      },
      "description": "Negates a boolean value"
    },
    "i128": {
      "additionalProperties": false,
      "contractMethod": "view",
      "properties": {
        "args": {
          "additionalProperties": false,
          "properties": {
            "i128": {
              "$ref": "#/definitions/I128"
            }
          },
          "type": "object",
          "required": [
            "i128"
          ]
        }
      }
    },
    "u128": {
      "additionalProperties": false,
      "contractMethod": "view",
      "properties": {
        "args": {
          "additionalProperties": false,
          "properties": {
            "u128": {
              "$ref": "#/definitions/U128"
            }
          },
          "type": "object",
          "required": [
            "u128"
          ]
        }
      }
    },
    "multi_args": {
      "additionalProperties": false,
      "contractMethod": "view",
      "properties": {
        "args": {
          "additionalProperties": false,
          "properties": {
            "a": {
              "$ref": "#/definitions/U32"
            },
            "b": {
              "type": "boolean"
            }
          },
          "type": "object",
          "required": [
            "a",
            "b"
          ]
        }
      }
    },
    "map": {
      "additionalProperties": false,
      "contractMethod": "view",
      "properties": {
        "args": {
          "additionalProperties": false,
          "properties": {
            "map": {
              "type": "object",
              "patternProperties": {
                "^[a-zA-Z0-9]+$": {
                  "type": "boolean"
                }
              },
              "additionalProperties": false
            }
          },
          "type": "object",
          "required": [
            "map"
          ]
        }
      }
    },
    "vec": {
      "additionalProperties": false,
      "contractMethod": "view",
      "properties": {
        "args": {
          "additionalProperties": false,
          "properties": {
            "vec": {
              "type": "array",
              "items": {
                "$ref": "#/definitions/U32"
              }
            }
          },
          "type": "object",
          "required": [
            "vec"
          ]
        }
      }
    },
    "tuple": {
      "additionalProperties": false,
      "contractMethod": "view",
      "properties": {
        "args": {
          "additionalProperties": false,
          "properties": {
            "tuple": {
              "type": "array",
              "items": [
                {
                  "$ref": "#/definitions/ScSymbol"
                },
                {
                  "$ref": "#/definitions/U32"
                }
              ],
              "minItems": 2,
              "maxItems": 2
            }
          },
          "type": "object",
          "required": [
            "tuple"
          ]
        }
      }
    },
    "option": {
      "additionalProperties": false,
      "contractMethod": "view",
      "properties": {
        "args": {
          "additionalProperties": false,
          "properties": {
            "option": {
              "$ref": "#/definitions/U32"
            }
          },
          "type": "object"
        }
      },
      "description": "Example of an optional argument"
    },
    "u256": {
      "additionalProperties": false,
      "contractMethod": "view",
      "properties": {
        "args": {
          "additionalProperties": false,
          "properties": {
            "u256": {
              "$ref": "#/definitions/U256"
            }
          },
          "type": "object",
          "required": [
            "u256"
          ]
        }
      }
    },
    "i256": {
      "additionalProperties": false,
      "contractMethod": "view",
      "properties": {
        "args": {
          "additionalProperties": false,
          "properties": {
            "i256": {
              "$ref": "#/definitions/I256"
            }
          },
          "type": "object",
          "required": [
            "i256"
          ]
        }
      }
    },
    "string": {
      "additionalProperties": false,
      "contractMethod": "view",
      "properties": {
        "args": {
          "additionalProperties": false,
          "properties": {
            "string": {
              "$ref": "#/definitions/ScString"
            }
          },
          "type": "object",
          "required": [
            "string"
          ]
        }
      }
    },
    "tuple_strukt": {
      "additionalProperties": false,
      "contractMethod": "view",
      "properties": {
        "args": {
          "additionalProperties": false,
          "properties": {
            "tuple_strukt": {
              "$ref": "#/definitions/TupleStruct"
            }
          },
          "type": "object",
          "required": [
            "tuple_strukt"
          ]
        }
      }
    }
  }
}

src/contract_spec.ts Outdated Show resolved Hide resolved
src/contract_spec.ts Outdated Show resolved Hide resolved
Co-authored-by: George <Shaptic@users.noreply.github.com>
@Shaptic
Copy link
Contributor

Shaptic commented Nov 30, 2023

@willemneal is this RFR? I haven't given it a proper look because it's in Draft. Also, can you target the stable branch? We should release this alongside the other, final, stable Protocol 20 changes.

@willemneal
Copy link
Member Author

I want it to be, but should I move it to js-stellar-sdk? If so which branch should I target there? Is it compatible with testnet?

@willemneal willemneal changed the base branch from main to stable December 1, 2023 18:11
@Shaptic
Copy link
Contributor

Shaptic commented Dec 5, 2023

@willemneal sorry for the delay. Yeah SDK might be better just so that we don't need to port it over later. There's a stable branch to target, see here: stellar/js-stellar-sdk#886

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants