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

Allow multiple input/output signatures for the same method name #367

Open
steveluscher opened this issue Dec 8, 2022 · 4 comments
Open

Comments

@steveluscher
Copy link

Problem

In the docs for Method it is written:

The method name is used as the method field of the JSON-RPC body. It therefore MUST be unique.

Consider a JSON-RPC API method of ours called getBlock. If you pass transactionDetails: 'full' in the input then the output takes this form:

type GetBlockMethodResponse = {
  transactions: Array<{
    transaction: {
      message: VersionedMessage;
      signatures: string[];
    };
  }>;
}

If, however, you pass transactionDetails: 'accounts' then the output takes this form:

type GetBlockMethodResponse = {
  transactions: Array<{
    transaction: {
      accountsKeys: ParsedMessageAccount[],
      signatures: string[];
    };
  }>;
}

Without the ability to specify two different input/output signatures for the same method name, I don't think I can use OpenRPC to express that API.

Proposed solution

Allow multiple definitions for the same method name, and let the consumers of the specification document figure out how to either:

  1. Generate overloads when appropriate (eg. in a Typescript client)
  2. Merge the definitions into a single input/output specification that's the union of all possible inputs/outputs
@github-actions
Copy link

github-actions bot commented Dec 8, 2022

Welcome to OpenRPC! Thank you for taking the time to create an issue. Please review the guidelines

@etodanik
Copy link

etodanik commented Feb 3, 2024

As a fellow Solana-ite, I support @steveluscher request. That being said, the propose methods would probably provide a spec that is too ambiguous when it comes to API client generation, especially for lower level languages like C/C++.

A generator must know how to generically create the overloads, so the spec must somehow indicate how response schemas change and not leave that work up to a specific client implementation.

The way that OpenAPI 3.0 approaches it is via oneOf with a discriminator:
https://swagger.io/docs/specification/data-models/inheritance-and-polymorphism/

It looks like this

components:
  responses:
    sampleObjectResponse:
      content:
        application/json:
          schema:
            oneOf:
              - $ref: '#/components/schemas/Object1'
              - $ref: '#/components/schemas/Object2'
              - $ref: 'sysObject.json#/sysObject'
            discriminator:
              propertyName: objectType
              mapping:
                obj1: '#/components/schemas/Object1'
		obj2: '#/components/schemas/Object2'
                system: 'sysObject.json#/sysObject'
  …
  schemas:
    Object1:
      type: object
      required:
        - objectType
      properties:
        objectType:
          type: string
      …
    Object2:
      type: object
      required:
        - objectType
      properties:
        objectType:
          type: string
      …

I would imagine that something very similar is doable for OpenRPC.

@etodanik
Copy link

etodanik commented Feb 3, 2024

To add more context, discriminator is currently an OpenAPI-only addition which doesn't break json-schema because json-schema ignore unknown stuff.

Some discussion in OpenAPI about this exact issue:
OAI/OpenAPI-Specification#2116
OAI/OpenAPI-Specification#403
OAI/OpenAPI-Specification#2143 (especially)

@etodanik
Copy link

etodanik commented Feb 3, 2024

So in theory, this kind of OpenRPC schema would be helpful for codegen:

{
  "openrpc": "1.0.0-rc1",
  "info": {
    "version": "1.0.0",
    "title": "Petstore",
    "license": {
      "name": "MIT"
    }
  },
  "servers": [
    {
      "url": "http://localhost:8080"
    }
  ],
  "methods": [
    {
      "name": "list_pets",
      "summary": "List all pets",
      "tags": [
        {
          "name": "pets"
        }
      ],
      "params": [
        {
          "name": "limit",
          "description": "How many items to return at one time (max 100)",
          "required": false,
          "schema": {
            "type": "integer",
            "minimum": 1
          }
        },
        {
          "name": "responseType",
          "description": "Retrieve only pet id",
          "required": true,
          "schema": {
            "enum": ["full", "idOnly"]
          }
        }
      ],
      "result": {
        "name": "pets",
        "description": "A paged array of pets",
        "schema": {
          "oneOf": [
            { "$ref": "#/components/schemas/Pets" },
            { "$ref": "#/components/schemas/PetsIdOnly" }
          ],
          "discriminator": {
            "propertyName": "responseType",
            "mapping": {
              "full": "#/components/schemas/Pets",
              "idOnly": "#/components/schemas/PetsIdOnly"
            }
          }
        }
      },
      "errors": [
        {
          "code": 100,
          "message": "pets busy"
        }
      ],
      "examples": [
        {
          "name": "listPetExample",
          "description": "List pet example",
          "params": [
            {
              "name": "limit",
              "value": 1
            }
          ],
          "result": {
            "name": "listPetResultExample",
            "value": [
              {
                "id": 7,
                "name": "fluffy",
                "tag": "poodle"
              }
            ]
          }
        }
      ]
    },
    {
      "name": "create_pet",
      "summary": "Create a pet",
      "tags": [
        {
          "name": "pets"
        }
      ],
      "params": [
        {
          "name": "newPetName",
          "description": "Name of pet to create",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "newPetTag",
          "description": "Pet tag to create",
          "schema": {
            "type": "string"
          }
        }
      ],
      "examples": [
        {
          "name": "createPetExample",
          "description": "Create pet example",
          "params": [
            {
              "name": "newPetName",
              "value": "fluffy"
            },
            {
              "name": "tag",
              "value": "poodle"
            }
          ],
          "result": {
            "name": "listPetResultExample",
            "value": 7
          }
        }
      ],
      "result": {
        "$ref": "#/components/contentDescriptors/PetId"
      }
    },
    {
      "name": "get_pet",
      "summary": "Info for a specific pet",
      "tags": [
        {
          "name": "pets"
        }
      ],
      "params": [
        {
          "$ref": "#/components/contentDescriptors/PetId"
        }
      ],
      "result": {
        "name": "pet",
        "description": "Expected response to a valid request",
        "schema": {
          "$ref": "#/components/schemas/Pet"
        }
      },
      "examples": [
        {
          "name": "getPetExample",
          "description": "get pet example",
          "params": [
            {
              "name": "petId",
              "value": 7
            }
          ],
          "result": {
            "name": "getPetExampleResult",
            "value": {
              "name": "fluffy",
              "tag": "poodle",
              "id": 7
            }
          }
        }
      ]
    }
  ],
  "components": {
    "contentDescriptors": {
      "PetId": {
        "name": "petId",
        "required": true,
        "description": "The id of the pet to retrieve",
        "schema": {
          "$ref": "#/components/schemas/PetId"
        }
      }
    },
    "schemas": {
      "PetId": {
        "type": "integer",
        "minimum": 0
      },
      "Pet": {
        "type": "object",
        "required": [
          "id",
          "name"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/PetId"
          },
          "name": {
            "type": "string"
          },
          "tag": {
            "type": "string"
          }
        }
      },
      "Pets": {
        "type": "array",
        "items": {
          "$ref": "#/components/schemas/Pet"
        }
      },
      "PetsIdOnly": {
        "type": "array",
        "items": {
          "$ref": "#/components/schemas/PetId"
        }
      }
    }
  }
}

Of course, this passes 'json-schema' validation because it would simply ignore unknown stuff. But it would be nice to have it as an official OpenRPC extension of json-schema rather than a free-for-all implicit abuse of validator permissiveness.

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

No branches or pull requests

2 participants