autodocodec icon indicating copy to clipboard operation
autodocodec copied to clipboard

Incorrect openapi when using two `parseAlternative`s

Open TheOddler opened this issue 3 years ago • 3 comments

It looks like the openapi output is incorrect when using multiple parseAlternatives.

We have the following code:

data UserIDWithExternalIDs = UserIDWithExternalIDs
  { userIDWithExternalIDsUserID :: !PublicID,
    userIDWithExternalIDsExternalIDs :: !ExternalIDs
  }
  deriving stock (Eq, Show, Generic)
  deriving (ToJSON, FromJSON, OpenAPI.ToSchema) via (Autodocodec UserIDWithExternalIDs)

instance HasCodec UserIDWithExternalIDs where
  codec =
    object "UserIDWithExternalIDs" $
      UserIDWithExternalIDs
        <$> parseAlternative (requiredField "userId" "User ID (backwards compatible)") (requiredField "userID" "User ID") .= userIDWithExternalIDsUserID
        <*> parseAlternative (requiredField "externalIds" "All external ids associated with this user (backwards compatible)") (requiredField "externalIDs" "All external ids associated with this user") .= userIDWithExternalIDsExternalIDs

data ExternalIDs = ...

Which produces the following openapi definition:

"UserIDWithExternalIDs": {
    "anyOf": [
        {
            "required": [
                "externalIds"
            ],
            "type": "object",
            "properties": {
                "externalIds": {
                    "$ref": "#/components/schemas/ExternalIDs"
                }
            }
        },
        {
            "required": [
                "externalIDs"
            ],
            "type": "object",
            "properties": {
                "externalIDs": {
                    "$ref": "#/components/schemas/ExternalIDs"
                }
            }
        }
    ],
    "additionalProperties": true
},

This does not include the userId as a field.

When I remove one of the parseAlternatives:

instance HasCodec UserIDWithExternalIDs where
  codec =
    object "UserIDWithExternalIDs" $
      UserIDWithExternalIDs
        <$> requiredField "userId" "User ID (backwards compatible)" .= userIDWithExternalIDsUserID -- no more parseAlternative here
        <*> parseAlternative (requiredField "externalIds" "External IDs (backwards compatible)") (requiredField "externalIDs" "External IDs") .= userIDWithExternalIDsExternalIDs

The openapi definition does include the userId:

"UserIDWithExternalIDs": {
    "anyOf": [
        {
            "required": [
                "externalIds"
            ],
            "type": "object",
            "properties": {
                "externalIds": {
                    "$ref": "#/components/schemas/ExternalIDs"
                }
            }
        },
        {
            "required": [
                "externalIDs"
            ],
            "type": "object",
            "properties": {
                "externalIDs": {
                    "$ref": "#/components/schemas/ExternalIDs"
                }
            }
        }
    ],
    "required": [
        "userId"
    ],
    "additionalProperties": true,
    "type": "object",
    "properties": {
        "userId": {
            "type": "string",
            "description": "User ID (backwards compatible)\n..."
        }
    }
},

TheOddler avatar Jan 20 '22 13:01 TheOddler

That's a very interesting issue. Great digging! PR Welcome

NorfairKing avatar Jan 20 '22 19:01 NorfairKing

@TheOddler Is this issue still there in the latest release? I would appreciate a PR to fix this.

NorfairKing avatar Apr 27 '22 09:04 NorfairKing

I'll have to check it out. Haven't had time/energy to look at it yet.

TheOddler avatar May 04 '22 08:05 TheOddler

PR welcome

NorfairKing avatar Mar 06 '23 15:03 NorfairKing