Luis Abreu
Luis Abreu

Reputation: 4560

OpenAPI 3.0.x validation with inheritance

We're trying to create a valid OpenAPI doc that will be used to validate incoming requests performed over an existing web api. The initial openapi doc was build with Swagger and the api is written in net 9.0 Unfortunately, it simply didn't work as expected, so I've decided to write manually.

Ok, this is my current version of the document (it's a simplified scenario, but it's enough to reproduce the issue we're seeing):

openapi: 3.0.1
info:
  title: OpenAPI Poc
  description: Testes schema OpenAPI
  version: v1
servers:
  - url: https://localhost:7274
paths:
  /api/equipamentos:
    put:
      tags:
        - Equipamentos
      summary: Modifica os dados de um equipamento existente.
      description: Modifica os dados de um equipamento existente e devolve o seu ID.
      requestBody:
        content:
          application/json:
            schema:
              oneOf:                
                - $ref: '#/components/schemas/MsgAtualizacaoComputador'
                - $ref: '#/components/schemas/MsgAtualizacaoEquipamentoAtivo'
                - $ref: '#/components/schemas/MsgAtualizacaoEquipamentoGenerico'
                - $ref: '#/components/schemas/MsgAtualizacaoImpressora'
                - $ref: '#/components/schemas/MsgAtualizacaoSoftware'
              description: Mensagem que identifica os dados a atualizar no equipamento.
              discriminator:
                propertyName: $type
                mapping:
                  Sra.Assistencias.Dtos.Equipamentos.MsgAtualizacaoComputador, OpenApiPoc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null: '#/components/schemas/MsgAtualizacaoComputador'
                  Sra.Assistencias.Dtos.Equipamentos.MsgAtualizacaoEquipamentoAtivo, OpenApiPoc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null: '#/components/schemas/MsgAtualizacaoEquipamentoAtivo'
                  Sra.Assistencias.Dtos.Equipamentos.MsgAtualizacaoEquipamentoGenerico, OpenApiPoc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null: '#/components/schemas/MsgAtualizacaoEquipamentoGenerico'
                  Sra.Assistencias.Dtos.Equipamentos.MsgAtualizacaoImpressora, OpenApiPoc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null: '#/components/schemas/MsgAtualizacaoImpressora'
                  Sra.Assistencias.Dtos.Equipamentos.MsgAtualizacaoSoftware, OpenApiPoc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null: '#/components/schemas/MsgAtualizacaoSoftware'          
      responses:
        '200':
          description: OK
          content:
            text/plain:
              schema:
                $ref: '#/components/schemas/MsgGravacaoEquipamento'
            application/json:
              schema:
                $ref: '#/components/schemas/MsgGravacaoEquipamento'
            text/json:
              schema:
                $ref: '#/components/schemas/MsgGravacaoEquipamento'
        '401':
          description: Unauthorized
          content:
            text/plain:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
            application/json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
            text/json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
        '500':
          description: Internal Server Error
          content:
            text/plain:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
            application/json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
            text/json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
components:
  schemas:
    DadosGerfipv2:      
        type: object
        properties:
          numeroInventario:
            type: string
            nullable: true
          dataImportacaoInventario:
            type: string
            format: date-time
            nullable: true          
          procedimentoAquisitivo:
            type: string
            nullable: true
        additionalProperties: false
    EquipamentoInfo:
      type: object
      properties:
        id:
          type: integer
          format: int32
        version:
          type: integer
          format: int32
      additionalProperties: false
    EstadoEquipamento:
      enum:
        - 1
        - 2
        - 3
        - 4
        - 5
        - 6
        - 7
        - 8
        - 9
        - 10
        - 11
        - 12
      type: integer
      format: int32
    MsgAtualizacaoComputador:
      allOf:
        - $ref: '#/components/schemas/MsgAtualizacaoEquipamento'
        - type: object
          properties:
            idsEquipamentosAssociar:
              type: array
              items:
                $ref: '#/components/schemas/EquipamentoInfo'
              nullable: true
            idsEquipamentosCancelar:
              type: array
              items:
                $ref: '#/components/schemas/EquipamentoInfo'
              nullable: true
            idsEquipamentosPropagar:
              type: array
              items:
                $ref: '#/components/schemas/EquipamentoInfo'
              nullable: true          
            nomeNetBIOS:
              type: string
              nullable: true
            ip:
              type: string
              nullable: true
            processador:
              type: string
              nullable: true
            memoria:
              type: string
              nullable: true
            disco:
              type: string
              nullable: true
            gateway:
              type: string
              nullable: true
            idTipoComputador:
              type: integer
              format: int32          
    MsgAtualizacaoEquipamento:  
      required:
        - $type
      type: object
      properties:
        $type:
          type: string
        id:
          type: integer
          format: int32
        version:
          type: integer
          format: int32   
        serialNumber:
          type: string
          nullable: true
        tag:
          type: string
          nullable: true
        modelo:
          type: string
          nullable: true
        username:
          type: string
          nullable: true
        partNumber:
          type: string
          nullable: true
        idFabricante:
          type: integer
          format: int32
        dataAquisicao:
          type: string
          format: date-time
        dataFimGarantia:
          type: string
          format: date-time
        data:
          type: string
          format: date-time
        idFornecedor:
          type: integer
          format: int32
        observacoes:
          type: string
          nullable: true
        estadoEquipamento:
          $ref: '#/components/schemas/EstadoEquipamento'
        idLocalTrabalho:
          type: integer
          format: int32
        idFuncionario:
          type: integer
          format: int32
          nullable: true
        localizacaoArmazem:
          type: string
          nullable: true
        importGuid:
          type: string
          format: uuid
        etiquetaImpressaEm:
          type: string
          format: date-time
          nullable: true
        dataRetoma:
          type: string
          format: date-time
          nullable: true
        idImportacao:
          type: integer
          format: int32        
      discriminator:
        propertyName: $type
        mapping:
          Sra.Assistencias.Dtos.Equipamentos.MsgAtualizacaoComputador, OpenApiPoc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null: '#/components/schemas/MsgAtualizacaoComputador'
          Sra.Assistencias.Dtos.Equipamentos.MsgAtualizacaoEquipamentoAtivo, OpenApiPoc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null: '#/components/schemas/MsgAtualizacaoEquipamentoAtivo'
          Sra.Assistencias.Dtos.Equipamentos.MsgAtualizacaoEquipamentoGenerico, OpenApiPoc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null: '#/components/schemas/MsgAtualizacaoEquipamentoGenerico'
          Sra.Assistencias.Dtos.Equipamentos.MsgAtualizacaoImpressora, OpenApiPoc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null: '#/components/schemas/MsgAtualizacaoImpressora'
          Sra.Assistencias.Dtos.Equipamentos.MsgAtualizacaoSoftware, OpenApiPoc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null: '#/components/schemas/MsgAtualizacaoSoftware'
    MsgAtualizacaoEquipamentoAtivo:
      allOf:
        - $ref: '#/components/schemas/MsgAtualizacaoEquipamento'
        - type: object
          properties:
            nome:
              type: string
              nullable: true
            numPortas:
              type: integer
              format: int32
            ip:
              type: string
              nullable: true
            velocidade:
              type: string
              nullable: true
            idTipoEquipamentoAtivo:
              type: integer
              format: int32    
    MsgAtualizacaoEquipamentoGenerico:
      allOf:
        - $ref: '#/components/schemas/MsgAtualizacaoEquipamento'
        - type: object
          properties:
            idTipoEquipamentoGenerico:
              type: integer
              format: int32          
    MsgAtualizacaoImpressora:
      allOf:
        - $ref: '#/components/schemas/MsgAtualizacaoEquipamento'
        - type: object
          properties:
            idTipoImpressora:
              type: integer
              format: int32
            ip:
              type: string
              nullable: true
            nomeNetBIOS:
              type: string
              nullable: true          
    MsgAtualizacaoSoftware:
      allOf:
        - $ref: '#/components/schemas/MsgAtualizacaoEquipamento'
        - type: object
          properties:
            idsEquipamentosAssociar:
              type: array
              items:
                $ref: '#/components/schemas/EquipamentoInfo'
              nullable: true
            idsEquipamentosCancelar:
              type: array
              items:
                $ref: '#/components/schemas/EquipamentoInfo'
              nullable: true
            idsEquipamentosPropagar:
              type: array
              items:
                $ref: '#/components/schemas/EquipamentoInfo'
              nullable: true          
            numLicencasAdquiridas:
              type: integer
              format: int32
            nome:
              type: string
              nullable: true
            versao:
              type: string
              nullable: true
            upgrade:
              type: boolean            
    MsgGravacao:      
      type: object
      properties:        
        id:
          type: integer
          format: int32
        version:
          type: integer
          format: int32
    MsgGravacaoEquipamento:
      allOf:
        - $ref: '#/components/schemas/MsgGravacao'
        - type: object
          properties:
            tag:
              type: string
              nullable: true
          additionalProperties: false
    ProblemDetails:
      type: object
      properties:
        type:
          type: string
          nullable: true
        title:
          type: string
          nullable: true
        status:
          type: integer
          format: int32
          nullable: true
        detail:
          type: string
          nullable: true
        instance:
          type: string
          nullable: true
      additionalProperties: {}

I think I've already applied several recommendations I've picked along the way, but it still keeps validating when the request is checked by our firewall. Here's my current test message in JSON which keeps firing the validation error:

{
    "$type": "Sra.Assistencias.Dtos.Equipamentos.MsgAtualizacaoEquipamentoGenerico, OpenApiPoc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",    
    "idTipoEquipamentoGenerico": 3,    
    "id": 83695,
    "version": 6,
    "serialNumber": "PWOCWVQD",
    "tag": "XPTO_123",
    "modelo": "Some model",
    "username": "",
    "partNumber": "21MR004BPG",
    "idFabricante": 153,
    "dataAquisicao": "2024-12-16T00:00:00+00:00",
    "dataFimGarantia": "2027-12-15T00:00:00+00:00",
    "data": "0001-01-01T00:00:00",
    "idFornecedor": 57,
    "observacoes": "Testing",
    "estadoEquipamento": 4,
    "idLocalTrabalho": 807,
    "idFuncionario": 35112,
    "localizacaoArmazem": "",
    "importGuid": "00000000-0000-0000-0000-000000000000",
    "etiquetaImpressaEm": "2025-01-07T10:27:25.6946923+00:00",
    "dataRetoma": null,
    "idImportacao": 0
}

I've tested the open doc api against swagger and it seems to be fine. In fact, the messages it generates from the open api doc seem to be the ones expected by the service...however, the firewall keeps firing an error about the request body:

    
API Validation violation - Request body validation failure - validation error({"oneOf":{"errors":[{},{},{},{},{}],"instanceRef":"#","schemaRef":"#"}}) : Failed to validate schema openapipoc13.yaml

If required, I can also post the c# code for the classes that were used to build the initial open api doc with swagger. They're simple DTO classes. I've removed some of those types from the openapi doc and copied their propertiesinto the schemas that were reinheriting those base types.

Am I missing something? Surelly, I must be doing something wrong, right? Any ideas?

Thanks again.

Upvotes: 0

Views: 46

Answers (1)

Jeremy Fiel
Jeremy Fiel

Reputation: 3307

because your schemas don't have any other constraints except for required: ["$type"], they will all pass oneOf validation, causing an error for matching more than one.

trying adding a required constraint to all of the second allOf subschemas to force those to be unique.

Some examples here:

MsgAtualizacaoEquipamentoGenerico:
      allOf:
        - $ref: '#/components/schemas/MsgAtualizacaoEquipamento'
        - type: object
          required:
            - idTipoEquipamentoGenerico # << this one
          properties:
            idTipoEquipamentoGenerico:
              type: integer
              format: int32
MsgAtualizacaoImpressora:
      allOf:
        - $ref: '#/components/schemas/MsgAtualizacaoEquipamento'
        - type: object
          required:
            - idTipoImpressora
          properties:
            idTipoImpressora:
              type: integer
              format: int32
            ip:
              type: string
              nullable: true
            nomeNetBIOS:
              type: string
              nullable: true

Upvotes: 0

Related Questions