Open steveluscher opened 1 year ago
Welcome to OpenRPC! Thank you for taking the time to create an issue. Please review the guidelines
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.
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:
https://github.com/OAI/OpenAPI-Specification/issues/2116
https://github.com/OAI/OpenAPI-Specification/issues/403
https://github.com/OAI/OpenAPI-Specification/issues/2143 (especially)
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.
Problem
In the docs for
Method
it is written:Consider a JSON-RPC API method of ours called
getBlock
. If you passtransactionDetails: 'full'
in the input then the output takes this form:If, however, you pass
transactionDetails: 'accounts'
then the output takes this form: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: