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

anyOf generated code cannot be serialized #710

Open
elmattic opened this issue Dec 5, 2024 · 3 comments
Open

anyOf generated code cannot be serialized #710

elmattic opened this issue Dec 5, 2024 · 3 comments

Comments

@elmattic
Copy link

elmattic commented Dec 5, 2024

In our codebase, we are using the following structs, which are part of an ETH RPC API built on top of our Filecoin node implementation:

#[derive(PartialEq, Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub enum Predefined {
    Earliest,
    Pending,
    #[default]
    Latest,
    Safe,
    Finalized,
}

#[derive(PartialEq, Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct BlockNumber {
    block_number: EthInt64,
}

#[derive(PartialEq, Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct BlockHash {
    block_hash: EthHash,
    require_canonical: bool,
}

#[derive(PartialEq, Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(untagged)]
pub enum BlockNumberOrHash {
    #[schemars(with = "String")]
    PredefinedBlock(Predefined),
    BlockNumber(EthInt64),
    BlockHash(EthHash),
    BlockNumberObject(BlockNumber),
    BlockHashObject(BlockHash),
}

We're using typify to create a JSON OpenRPC specification to establish a Common Node API.

Our issue is that the untagged BlockNumberOrHash enum is translated into a definition that uses anyOf (see attached json file). The Rust code generated from this JSON specification is used in some tooling, and when trying to use the string variant to create an RPC request, we encounter the following error:

        couldn't serialize params
        can only flatten structs and maps (got a string)

Do you have any ideas on how to work around this kind of issue? For example, could we generate a oneOf (XOR) constraint instead?

Many thanks.

my_types.json

@ahl
Copy link
Collaborator

ahl commented Dec 24, 2024

The answer is both simple and disappointing: our current anyOf handling is terrible. See #414.

It it's possible to generate a oneOf, typify is going to better with that.

pub enum BlockNumberOrHash {
    #[schemars(with = "String")]
    PredefinedBlock(Predefined),
    BlockNumber(EthInt64),
    BlockHash(EthHash),
    BlockNumberObject(BlockNumber),
    BlockHashObject(BlockHash),
}

Looking at this definition, we've got

  • String (any string, rather than just the specific values of Predefined
  • EthInt64, another unconstrained string
  • EthHash, another unconstrained string
  • and two objects

Generating a useful JSON schema from this type is going to be tricky. While code typify generates is lousy... even an optimal implementation wouldn't be that great given the lack of precision in the schema.

I'm not exactly sure what the serializations are for these variants, but I'd suggest a custom schemars::JsonSchema impl at least for BlockNumberOrHash to make sure it produces a oneOf. Note that schemars will use anyOf for untagged enums as it can't determine that the variants are intrinsically mutually exclusive.

@elmattic
Copy link
Author

elmattic commented Jan 8, 2025

Thanks! I've tried adding more precision to the three strings:

So respectively using patterns "^0x[0-9a-fA-F]{1,16}$" and "^0x[a-fA-F0-9]{64}$" for EthUin64 and EthHash and an enum for Predefined:

    "Predefined": {
        "type": "string",
        "enum": [
          "earliest",
          "pending",
          "latest",
          "safe",
          "finalized"
        ]
      },

But I still face the same error. I will try the custom schemars::JsonSchema solution.

@ahl
Copy link
Collaborator

ahl commented Jan 8, 2025

Yes: you'll probably need to hand-roll your JsonSchema impl to produce a oneOf

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