pb33f / wiretap

The world's coolest API Validation and compliance tool. Validate APIs against OpenAPI specifications and much more
https://pb33f.io/wiretap/
Other
125 stars 18 forks source link

request unexpectedly fails validation #96

Closed TristanSpeakEasy closed 7 months ago

TristanSpeakEasy commented 10 months ago

Consider this spec:

openapi: 3.1.0
info:
  title: Test
  version: 0.1.0
paths:
  /requestbody#map:
    post:
      operationId: requestBodyPostApplicationJsonMap
      servers:
        - url: http://localhost:35456
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/mapValue"
        required: true
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                title: res
                type: object
                additionalProperties:
                  $ref: "#/components/schemas/simpleObject"
  /requestbody#arrayCamelCase:
    post:
      operationId: requestBodyPostApplicationJsonArrayCamelCase
      servers:
        - url: http://localhost:35456
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/arrValueCamelCase"
        required: true
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                title: res
                type: array
                items:
                  $ref: "#/components/schemas/simpleObjectCamelCase"
components:
  schemas:
    arrValueCamelCase:
      type: array
      items:
        $ref: "#/components/schemas/simpleObjectCamelCase"
    mapValue:
      type: object
      additionalProperties:
        $ref: "#/components/schemas/simpleObject"
    simpleObject:
      description: "A simple object that uses all our supported primitive types and enums and has optional properties."
      externalDocs:
        description: "A link to the external docs."
        url: "https://docs.speakeasyapi.dev"
      type: object
      properties:
        str:
          type: string
          description: "A string property."
          example: "test"
        bool:
          type: boolean
          description: "A boolean property."
          example: true
        int:
          type: integer
          description: "An integer property."
          example: 1
        int32:
          type: integer
          format: int32
          description: "An int32 property."
          example: 1
        num:
          type: number
          description: "A number property."
          example: 1.1
        float32:
          type: number
          format: float
          description: "A float32 property."
          example: 1.1
        enum:
          $ref: "#/components/schemas/enum"
        date:
          type: string
          format: date
          description: "A date property."
          example: "2020-01-01"
        dateTime:
          type: string
          format: date-time
          description: "A date-time property."
          example: "2020-01-01T00:00:00.000001Z"
        any:
          description: "An any property."
          example: "any"
        strOpt:
          type: string
          description: "An optional string property."
          example: "testOptional"
        boolOpt:
          type: boolean
          description: "An optional boolean property."
          example: true
        intOptNull:
          type: integer
          description: "An optional integer property will be null for tests."
        numOptNull:
          type: number
          description: "An optional number property will be null for tests."
        intEnum:
          type: integer
          description: "An integer enum property."
          enum:
            - 1
            - 2
            - 3
          example: 2
          x-speakeasy-enums:
            - First
            - Second
            - Third
        int32Enum:
          type: integer
          format: int32
          description: "An int32 enum property."
          enum:
            - 55
            - 69
            - 181
          example: 55
        bigint:
          type: integer
          format: bigint
          example: 8821239038968084
        bigintStr:
          type: string
          format: bigint
          example: "9223372036854775808"
        decimal:
          type: number
          format: decimal
          example: 3.141592653589793
        decimalStr:
          type: string
          format: decimal
          example: "3.14159265358979344719667586"
      required:
        - str
        - bool
        - int
        - int32
        - num
        - float32
        - enum
        - date
        - dateTime
        - any
        - intEnum
        - int32Enum
    simpleObjectCamelCase:
      description: "A simple object that uses all our supported primitive types and enums and has optional properties."
      externalDocs:
        description: "A link to the external docs."
        url: "https://docs.speakeasyapi.dev"
      type: object
      properties:
        str_val:
          type: string
          description: "A string property."
          example: "example"
        bool_val:
          type: boolean
          description: "A boolean property."
          example: true
        int_val:
          type: integer
          description: "An integer property."
          example: 999999
        int32_val:
          type: integer
          format: int32
          description: "An int32 property."
          example: 1
        num_val:
          type: number
          description: "A number property."
          example: 1.1
        float32_val:
          type: number
          format: float
          description: "A float32 property."
          example: 2.2222222
        enum_val:
          $ref: "#/components/schemas/enum"
        date_val:
          type: string
          format: date
          description: "A date property."
          example: "2020-01-01"
        date_time_val:
          type: string
          format: date-time
          description: "A date-time property."
          example: "2020-01-01T00:00:00Z"
        any_val:
          description: "An any property."
          example: "any example"
        str_opt_val:
          type: string
          description: "An optional string property."
          example: "optional example"
        bool_opt_val:
          type: boolean
          description: "An optional boolean property."
          example: true
        int_opt_null_val:
          type: integer
          description: "An optional integer property will be null for tests."
          example: 999999
        num_opt_null_val:
          type: number
          description: "An optional number property will be null for tests."
          example: 1.1
        int_enum_val:
          type: integer
          description: "An integer enum property."
          enum:
            - 1
            - 2
            - 3
          example: 3
          x-speakeasy-enums:
            - First
            - Second
            - Third
        int32_enum_val:
          type: integer
          format: int32
          description: "An int32 enum property."
          enum:
            - 55
            - 69
            - 181
          example: 69
        bigint_val:
          type: integer
          format: bigint
        bigint_str_val:
          type: string
          format: bigint
        decimal_val:
          type: number
          format: decimal
      required:
        - str_val
        - bool_val
        - int_val
        - int32_val
        - num_val
        - float32_val
        - enum_val
        - date_val
        - date_time_val
        - any_val
        - int_enum_val
        - int32_enum_val
    enum:
      type: string
      description: "A string based enum"
      enum:
        - "one"
        - "two"
        - "three"
        - "four_and_more"
      example: "one"
  securitySchemes:
    apiKeyAuth:
      type: apiKey
      in: header
      name: Authorization
      description: Authenticate using an API Key generated via our platform.

When sending the request body like so:

[{"any_val":"any","bigint_str_val":"1344719667586153181419716641724567886890850696275767987106294472017884974410332069524504824747437757","bigint_val":8821239038968084,"bool_opt_val":true,"bool_val":true,"date_time_val":"2020-01-01T00:00:00.000000001Z","date_val":"2020-01-01","enum_val":"one","float32_val":1.1,"int32_enum_val":55,"int32_val":1,"int_enum_val":2,"int_val":1,"num_val":1.1,"str_opt_val":"testOptional","str_val":"test"}]

I get this error from wiretap:

{"type":"https://pb33f.io/wiretap/error","title":"unable to serve mocked response","status":500,"detail":"Error: POST request body for '/requestbody' failed to validate schema, Reason: The request body is defined as an object. However, it does not meet the schema requirements of the specification, Validation Errors: [Reason: expected object, but got array, Location: /type], Line: 55, Column: 7"}

I believe this is related to https://github.com/pb33f/wiretap/issues/78

Basically I think its validating against the wrong operation and it seems to be non-deterministic (prob due to map order in Go) as sometimes I start wiretap and it works and sometimes I start it and it doesn't work.

I imagine this would be sorted by looping through all operations that match the path instead of just the first found?

daveshanley commented 7 months ago

After attempting to solve this, I was confused as to why the correct path could not be found.

The http.Request.URL.Fragment was always missing when making a curl request or via postman etc. I was unable to perform any kind of lookup when a fragment was used, because any request made to wiretap using a fragment was just missing the fragment each time.

So then I looked a little deeper. I found this:

https://github.com/golang/go/issues/3805

Fragments are never sent in HTTP.  They're purely a local browser concept.

There is no way for me determine which path is being requested when the OpenAPI specification uses a fragment as a delimiter. The fragment is never picked up by the standard library.

Which pulls into question the legitimacy of using fragments at all in OpenAPI specs. They are quite useless it seems and should not be used as delimiters for paths.

Marking this as a wontfix, because there is nothing I can do to make it work.