Skip to main content

OpenAPI Data Types

A especificação do OpenAPI tenta ficar o mais próximo que possível do JSON Schema mas não é 100%.

Vamos devolver algum valor para a nossa chamada get para /v1/customers. O conteúdo será um array e os items desse array serão do tipo string.

Um schema em APIs, especialmente em especificações como OpenAPI, é uma definição do formato e das regras dos dados que serão enviados ou recebidos. Ele serve como um contrato que define como os dados devem ser estruturados, quais são os tipos esperados, e as validações que devem ser aplicadas.

Vamos correr um pouquinho e depois voltar a andar.

paths:
/v1/customers:
get:
responses:
'200':
description: List of Customers
content:
application/json: #Estamos indicando que estará no formato JSON
schema: # Aqui estamos definindo o qual será o formato de dados.
type: array
items:
description: Customer Name
type: string
minLength: 2
maxLength: 100

alt text

Antes de prosseguir vamos entender o que podemos ter no content.

Content

O application/json é essencialmente uma indicação formal do formato dos dados trocados, permitindo que o cliente e o servidor entendam como processar a informação. Foi dado ênfase a este objeto pois é o mais comum, mas poderíamos ter outros formatos:

  • application/xml

    <customer>
    <name>John Doe</name>
    <age>30</age>
    </customer>
  • application/yaml ou text/yaml: Para dados no formato YAML.

    name: John Doe
    age: 30
  • application/x-ndjson: Para JSON em formato de linhas separadas (Newline Delimited JSON).

    {"name": "John Doe", "age": 30}
    {"name": "Jane Smith", "age": 25}
  • application/x-www-form-urlencoded: Usado em formulários HTML para enviar dados no formato de pares chave-valor.

    name=John+Doe&age=30
  • text/plain: Para texto simples sem formatação.

    Hello, World!
  • text/csv: Para dados tabulares no formato CSV (valores separados por vírgulas).

    name,age
    John Doe,30
    Jane Smith,25
  • image/jpeg, image/png, image/gif: Usados para transferir imagens.

  • application/octet-stream: Para transferência de arquivos binários genérico como .exe ou .zip.

Veja o que temos em https://datatracker.ietf.org/doc/html/rfc6838 para conhecer mais os possíveis aceitos. Lá você encontrará categorias como:

  • application
  • audio
  • image
  • text
  • video
  • outros

Existe uma negociação entre o client e o servidor para que entendam o que vão trocar de informação tanto no request quanto no response. É perfeitamente possível enviar dados em um formato e receber em outro.

Não se atente ao exemplo, apenas veja que é possível.

paths:
/v1/createcustomer:
post:
summary: "Create a new customer"
requestBody:
content:
application/json: # Envio de dados no formato JSON
schema:
type: object
properties:
name:
type: string
email:
type: string
responses:
'200':
description: "Customer created successfully"
content:
application/yaml: # Resposta em formato YAML
schema:
type: object
properties:
name:
type: string
email:
type: string

alt text

Geralmente esperamos que um schema seja objeto pois dentro de um objeto podemos a propriedade com vários parâmetros possibilitando o crescimento. Poderíamos definir somente como um tipo específico ao invés de objeto (vamos explorar mais abaixo), mas colocar isso dentro do objeto nos dará maior margem para mudanças. Usar sempre objects nos trás:

  • Flexibilidade e Extensibilidade: Você pode adicionar novas propriedades no futuro sem quebrar a compatibilidade com consumidores existentes. Começa com um campo name e depois adiciona email, birthdate, etc., sem mudar a estrutura principal.
  • Escalabilidade: Você pode organizar melhor dados complexos, adicionando sub-objetos ou arrays se necessário.
  • Clareza e Leitura: Um objeto é mais intuitivo para desenvolvedores, pois reflete melhor as estruturas do mundo real (entidades com atributos).
  • Manutenção: Se um campo for removido ou alterado, os consumidores podem ajustar suas implementações de maneira mais previsível.

Tipos Primitivos

Vimos ali palavras como objetct e string. No caso acima temos um de response do tipo objeto com os parâmetros internos name do tipo string e email também do tipo string.

Podemos usar para declarar o type do próprio schema, os types das properties dos objetos, etc,

Poderíamos esperar em um request somente o número apesar do que eu acabei de mensinar acima sobre o uso de objetos.

            application/json:
schema:
type: integer
example: 42 # O exemplo funciona como parte da documentação para ajudar a entender melhor o dado esperado.

A especificação OpenAPI oferece muitas opções para descrever seus tipos, mas também muitas opções para descrevê-los de forma solta. É uma boa prática escolher os tipos com precisão (usando os formatos definidos pelo OpenAPI) para melhorar a documentação, evitar ambiguidade para os usuários finais e caso esteja usando alguma gerador de código evitará problemas.

A especificação do OpenAPI aceita a maioria dos dados primitivos que já usados em outras linguagems que provavelmente você já conhece.

string

É um type bem flexível usado para representar muitas coisas.

  • Poder ser usado para representar outros tipos (“true”, “100”, “{\\“some\\”: \\”object\\”}”).
  • Suporta uma série de formatos que sobrepõem restrições ao tipo de dados representados sendo útil para mapear para tipos em várias linguagens.
  • Muitos formatos podem ser usados, como email, uuid, uri, hostname, ipv4, ipv6, etc. Uma tabela com exemplos:
TypeFormatoExplicaçãoExemplo
stringdateRespeitando as normas da RFC3339 para formatação de date“2022-01-30”
stringdate-timeRespeitando as normas da RFC3339 para formatação de date-time“2019-10-12T07:20:50.52Z”
stringpasswordAvisa que possui dados sensíveis“mySecretWord1234”
stringbyteDados encodados em base64“U3BlYWtlYXN5IG1ha2VzIHdvcmtpbmcgd2l0aCBBUElzIGZ1biE=”
stringbinaryUsado para representar sequência binária“01010101110001”

## Sem formatação, esperando uma string simples
application/json:
schema:
type: string
example: "Não temos nenhum tipo de formato!"
        ## Com formatação, esperando uma string com um date-timie
application/json:
schema:
type: string
format: date
example: “2022-01-30”

O exemplo ajudará ao usuário a entender o que se espera do campo.

alt text

Podemos utilizar expressões regulares (regex) em uma string para validar dados ao invés de format. O format praticamente são regex já prontos, mas em casos específicos usamos o pattern.

        application/json:
schema:
type: string
pattern: ^[a-zA-Z0-9_]*$ # Aceita letras minúsculas, maiúsculas, números e _

Algumas validações podems serem feitas usando string.

-minLength: Define o comprimento mínimo para uma string -maxLength: Define o comprimento máximo para uma string -enum: Específica um conjunto de valores aceitos

integer/number

Um numero pode ser de vários formatos assim como uma string. Definir o formato de um number é específicar o que estamos esperando daquele número.

Se vamos trabalhar com inteiros usamos o tipo integer podendo varíar o quanto esse número pode ser grande. Para isso definimos o formato desse número para int32 ou int64.

A mesma coisa temos com o number. Se definirmos um type number estamos esperando trabalhar com número decimais (ponto flutuante) e não um número inteiro. Para isso especificamos que o formato deve ser float ou double.

Não é obrigatório definir o format para integer ou number. A definição serve para restrição. Definir um integer sem formato esperamos qualquer tamanho de número, int32 ou int64 o mesmo valendo para number, podendo receber um float(32 bits) ou um double (float com 64 bits).

  • integer
    • int32
    • int64
  • number
    • float
    • double

É recomendado que você seja explícito com o formato do seu tipo de número e sempre preencha o atributo format.

Algumas validações podem ser feitas sobre esses valores.

  • minimum
  • maximum
  • exclusiveMinimum
  • exclusiveMaximum
  • multipleOf
          schema:
type: integer
format: int32
# O valor deve ser maior ou igual ao número especificado.
minimum: 10 # 10 é permitido ou seja é o <=
example: 15
          schema:
type: integer
format: int32
# O valor deve ser maior ou igual ao número especificado.
minimum: 10 # 10 NÃO é permitido pois exclusive exclusiveMinimum é true remover o =, sendo permitido somente > (maior)
exclusiveMinimum: true
example: 15
          schema:
type: number
format: float
# Esperamos valores entre 0 e 1. 0.89 passa, 1.2 não passa.
minimum: 0
maximum: 1 # Definindo um número máximo
          schema:
type: number
format: double
# Menor que 100, Não podendo ser igual.
maximum: 100
exclusiveMaximum: true
          schema:
type: integer
format: int64
multipleOf: 5

boolean

É o de sempre, só permite true ou false não aceitando zero ou um.

          schema:
type: boolean
# default pode ser usado para qualquer tipo caso não for fornecido o valor esse será assumido.
default: false

array

Um array é uma lista de coisas items do mesmo tipo. Obrigatoriamente precisamos definir o que será um item do array.

          # array de string
schema:
type: array
items:
type: string
          # array de objeto. Cada objeto do array terá um nome e idade
schema:
type: array
items:
type: object
properties:
name:
type: string
age:
type: integer

Também podemos fazer validações nos arrays.

  • minItems:
  • maxItems
  • uniqueItems
          schema:
type: array
items:
type: number
format: float
minItems: 1
schema:
type: array
items:
type: string
maxItems: 10

# Tem que possuir exatamente 3 itens.
schema:
type: array
items:
type: boolean
minItems: 3
maxItems: 3

# Não aceita itens duplicados no array.
schema:
type: array
items:
type: string
uniqueItems: true

objects

É o tipo de objeto mais flexivel. Permite dicionários e objetos de forma livre, juntamente com uma série de atributos para controlar a validação.

Para resumir isso, um objeto pode conter outros objetos dentros (aninhados), tipos simples, arrays, e qualquer outra coisa que você queira.

          schema:
type: object
properties:
name:
type: string
age:
type: integer
format: int32
active:
type: boolean
address: # Objeto dentro do objeto
type: object
properties:
street:
type: string
city:
type: string
state:
type: string
country:
type: string
zip:
type: string
children: # Array dentro do objeto
type: array
items:
type: string
description: List of children's names

Estaríamos esperando algo assim em se fosse em json.

{
"name": "David Puziol",
"age": 30,
"active": true,
"address": {
"street": "123 Main St",
"city": "Vila Velha",
"state": "ES",
"county": "Brazil",
"zip": "62701"
},
"children": ["Marina", "Catarina"]
}

Claro que poderíamos ter validações em cada um dos campos de cada um dos objetos e arrays o que tornaria o exemplo extenso.

Objetos com propriedades têm acesso a alguns atributos adicionais que permitem que os objetos sejam validados de várias maneiras:

required

A chave required é usada para indicar quais propriedades de um objeto são obrigatórias.

Essas propriedades devem ser incluídas no objeto quando enviados em uma requisição (no caso de requestBody) ou quando esperado em uma resposta (no caso de response).

Estamos dizendo que um objeto pode ser complexo suficiente mas não precisamos receber ou enviar todos os campos.

Vamos pensar que em um método POST para cadastrar um usuário precisamo o mínimo necessário será nome e idade mas endereço também seria interessante caso fosse passado.


schema:
type: object
required:
- name
- age
properties:
name:
type: string
age:
type: integer
address: #(Opcional)
type: string

Exemplo de uso: Definir um objeto onde o name e o age são propriedades obrigatórias, enquanto o address é opcional.

Podemos usar o required tanto no request quanto no response. Na entrada geralmente entendemos o motivo, mas na resposta é um poucos mais difícil. Não seria interessante devolver todos os campos sempre?

As vezes não sabemos se o valor foi definido pelo sistema ou pelo usuário anteriormente, principalmente se for um valor default. Outro detalhe é que a performance do sistema que poderia ser afetada. Não estamos aqui para julgar o que é ou não melhor fazer, mas o que é possívle fazer.

readyOnly (Uma propriedade que só está disponível em uma resposta)

Uma propriedade que só está disponível em uma resposta (response), ou seja, ela não deve ser incluída em uma requisição (request).

Exemplo de uso: Uma propriedade como um ID que é gerado no servidor e não pode ser enviado pelo cliente, mas é retornado na resposta.

        schema:
type: object
properties:
id:
type: string
readOnly: true
name:
type: string

Geralmente isso é usado quas fazemos reaproveitamente de schemas fazendo reusabilidade de código. Criamos um schema que pode ser usando no request e no response ao mesmo tempo porém tem campos que são usando em um e em outro.

Só para ilustrar até chegar o momento certo de aprender isso. Usamos uma referência para o schema.

#...
paths:
/users:
post:
summary: Criar um novo usuário
description: Cria um novo usuário e retorna o `id` gerado pelo sistema.
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/User'
responses:
'201':
description: Usuário criado com sucesso
content:
application/json:
schema:
$ref: '#/components/schemas/User'

components:
schemas:
User:
type: object
properties:
id:
type: string
readOnly: true
description: ID gerado automaticamente para o usuário
name:
type: string
description: Nome do usuário
required:
- name

writeOnly (Uma propriedade que só está disponível em uma resposta)

Uma propriedade que só está disponível em uma requisição (request), ou seja, ela não deve ser incluída em uma response (Reposta).

Exemplo de uso: Senhas ou outras informações sensíveis que não devem ser retornadas após o envio.

    schema:
type: object
required:
- password
- email
properties:
password: #chave
type: string
writeOnly: true
email:
type: string

Os Objetos podem ser usados para representar dicionários ou mapas, coleções de chave valor. As chaves (keys) geralmente são do tipo string, e os valores (values) podem ser de qualquer tipo que possa ser descrito pela especificação OpenAPI, como string, integer, array, boolean, entre outros.

  • Chaves (keys): São sempre do tipo string, ou seja, o nome da chave é uma string.
  • Valores (values): Podem ser de qualquer tipo que o OpenAPI permita, como números, strings, arrays, objetos aninhados, etc.

O tipo de objeto também pode ser usado para descrever dicionários/mapas/etc. que usam strings para chaves e suportam qualquer tipo de valor que pode ser descrito pela especificação OpenAPI.

        schema:
type: object
# Name existirá e ainda será required
properties:
name:
type: string # Must match type of additionalProperties
required:
- name
# outros além de name podem existir desde que sejam strings
additionalProperties:
type: string
# AMBOS OS CASOS ABAIXO PERMITIRIA QUE FOSSE QUALQUER COISA.
# additionalProperties: true
# additionalProperties: {
minProperties: 1 # Avisa que terá outra propriedade além do name.

Outras validações podem ser feitas, independente de usar additionalProperties ou não.

  • minProperties: The minimum number of properties allowed in the object.
  • maxProperties: The maximum number of properties allowed in the object.

Enums

É usado para definir um conjunto fixo de valores válidos, mas o cliente ou sistema deve escolher apenas um desses valores. Em outras palavras: Enum é escolha única quando usado sozinho, não múltipla.

Vamos diretamente ao exemplo já utilizando os components ir se familiarizando.

paths:
/v1/customers:
get:
responses:
200:
description: List of Customers
content:
application/json:
schema:
# Usado para ilustrar propriedades de string
maxItems: 100
minItems: 1
type: array
description: List of Customers
items: # aqui vamos usar a referencia, seria a mesma coisa que fazer um include de tudo aqui aqui dentro.
$ref: '#/components/schemas/inline_response_200'
/v1/beers:
get:
responses:
200:
description: List of Beers
404:
description: No Beers Found
components:
schemas:
v1customers_address:
type: object
properties:
line1:
type: string
example: 123 main
city:
type: string
example: St Pete
stateCode:
# Uma vez que fixamos os valores usando o enum isso seria redundante
# Definir o máximo e o mínimo poderiam ficar por uma questão informativa somente.
maxLength: 2
minLength: 2
type: string
description: 2 Letter State Code
## Escolha única como exemplo
enum:
- AL
- AK
- AZ
- AR
- CA
zipCode:
type: string
example: "33701"
inline_response_200:
type: object
properties:
id:
type: string
format: uuid
firstName:
maxLength: 100
minLength: 2
type: string
example: John
lastName:
maxLength: 100
minLength: 2
type: string
example: Thompson
address:
$ref: '#/components/schemas/v1customers_address'
description: customer object

Como Simular Múltipla Escolha no OpenAPI?

Se você quiser permitir múltiplos valores, pode usar o tipo array combinado com enum para definir os valores válidos dentro do array:

type: array
items:
type: string
enum:
- red
- green
- blue

Comportamento esperado:

Permite enviar um array contendo um ou mais valores válidos.

  • Valores válidos:
    • ["red"]
    • ["red", "green"] -["green", "blue"]
  • Valores inválidos:
    • ["red", "yellow"] (porque yellow não está no enum)
    • "red, green" (porque não é um array, mas uma string única).