utoipa icon indicating copy to clipboard operation
utoipa copied to clipboard

Serde flattened enum as top level fields (not `oneOf`)

Open Flugplatz opened this issue 5 months ago • 0 comments

When using an enum as a struct field with a serde flattened annotation, I would like the enum fields pulled up to the top level instead of being nested under a OneOf definition as three distinct items. My example enum has a three definitions, all with a common appData field and one with an optional appDataHash.

What I would like:


        appData:
          description: |
            This field comes in two forms for backward compatibility. The hash form will eventually 
            stop being accepted.
          anyOf:
            - title: Full App Data
              allOf:
                - $ref: "#/components/schemas/AppData"
              description: 
              type: string
            - $ref: "#/components/schemas/AppDataHash"
        appDataHash:
          description:
          allOf:
            - $ref: "#/components/schemas/AppDataHash"
          nullable: true

What I get instead is these different versions nested under a oneOf parent:

    OrderCreation:
      allOf:
      - oneOf:
        - type: object
          description: Hash is inferred from full app data and validated against expectation.
          required:
          - appData
          - appDataHash
          properties:
            appData:
              type: string
            appDataHash:
              type: string
        - type: object
          description: Backward compatible app data hash.
          required:
          - appData
          properties:
            appData:
              type: string
        - type: object
          description: Hash is inferred from full app data.
          required:
          - appData
          properties:
            appData:
              type: string
        description: The string encoding of a JSON object representing some `appData`.
      - type: object
        required:
        - sellToken
        - buyToken
        properties:
          sellToken:
            type: string
            description: Address of token sold.
            example: 0x6810e776880c02933d47db1b9fc05908e5386b96
          buyToken:
            type: string
            description: Address of token bought.
            example: 0x6810e776880c02933d47db1b9fc05908e5386b96
      description: An order as provided to the POST order endpoint.

Code below:

#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
#[serde(untagged)]
/// The string encoding of a JSON object representing some `appData`.
pub enum OrderCreationAppData {
    /// Hash is inferred from full app data and validated against expectation.
    Both {
        #[serde(rename = "appData")]
        full: String,
        #[serde(rename = "appDataHash")]
        expected: String,
    },
    /// Backward compatible app data hash.
    Hash {
        #[serde(rename = "appData")]
        hash: String,
    },
    /// Hash is inferred from full app data.
    Full {
        #[serde(rename = "appData")]
        full: String,
    },
}

impl Default for OrderCreationAppData {
    fn default() -> Self {
        Self::Hash {
            hash: Default::default(),
        }
    }
}

/// An order as provided to the POST order endpoint.
#[serde_as]
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct OrderCreation {
    /// Address of token sold.
    #[schema(value_type = String, example = "0x6810e776880c02933d47db1b9fc05908e5386b96")]
    pub sell_token: String,
    /// Address of token bought.
    #[schema(value_type = String, example = "0x6810e776880c02933d47db1b9fc05908e5386b96")]
    pub buy_token: String,
    /// The string encoding of a JSON object representing some `appData`.
    #[serde(flatten)]
    #[schema(inline)]
    pub app_data: OrderCreationAppData,
}

Flugplatz avatar Feb 08 '24 08:02 Flugplatz