Primeros pasos con Spectral (Parte 2) : Implementar una Regla Custom

Publicado por Víctor Madrid el

Arquitectura de SolucionesSpectralAPI LinterLinterRegla CustomRegla PersonalizadaCustom Rule

En este segundo artículo de Spectral aprenderemos a crear nuestras propias reglas (reglas custom) con cierta calidad ya que tendremos en cuenta una propuesta de procedimiento para hacerlo y la parte de testing de las reglas.

cabecera---Enmilocalfunciona---Spectral---1400x400px-3

Vamos a aprovechar esta funcionalidad de customización de Spectral para cubrir unas necesidades específicas que nos inventemos.

Para ello, deberemos aprender a utilizar unas cuantas herramientas que nos podrán facilitar el desarrollo de una regla.

A modo de recordatorio pongo los enlaces a los artículos anteriores :

Este es el índice que se va a utilizar para estructurar este artículo

Definición de una regla custom

¿Qué es una regla custom?

Componente de Spectral que funciona como filtro de un objeto respecto al conjunto de valores objetivo y especifican la funcion con la que se van a evaluar para verificar su cumplimiento.

Es decir, cuando se trabaja sobre un fichero JSON / YAML de entrada, una regla sería la validación que se quiere hacer sobre uno o varios de sus campos y/o sobre sus valores.

Para ello, Spectral proporciona un Domain Specific Language para implementar reglas custom

En este caso, se considera que es custom cuando ha sido definida por un desarrollador y esta puede o no cumplir alguna especificación que no esté soportada por defecto por la herramienta

Ejemplo de regla custom: open-api-version-3

rules:
    open-api-version-3:
        description: APIs should support OpenAPI V3
        given: $.openapi
        severity: error
        then:
            function: pattern
            functionOptions:
                match: '^3'
  ...

La regla se denomina "open-api-version-3" y se encarga de comprobar que la versión de openapi declarada en el documento es de la familia de versiones 3 (v3).

Familia de versiones 3

Pertenecer a la familia de versiones 3 significa que dada una versión de un artefacto con el formato X.Y.Z entonces el valor de X será 3 -> 3.Y.Z
Esto significa que hará uso de la versión principal del artefacto declarada como 3 y que portanto tendrá definidas unas funcionalidades específicas.

La descripción trata de ser clara con el objetivo de la regla.

La expresión de localización (given) indicará el campo sobre el que trabajar dentro del documento.

La severidad de su incumplimiento se considerará "error".

La comprobación de la función hará uso de la funcionalidad de pattern detección de un patrón expresado como una expresión regular.

Ejemplo de regla custom : schema-names-pascal-case

rules: 
  schema-names-pascal-case: 
    description: Schema names MUST be written in PascalCase 
    message: "component '' " 
    severity: error 
    given: '$.components.schemas.*~' 
    then: 
        function: casing 
        functionOptions: 
            type: pascal 
  ...

La regla se denomina "schema-names-pascal-case" y se encarga de comprobar que el nombre de los schemas declarados en los componentes se deberían de escribir con la notación "Pascal Case"

Notación "Pascal Case"

Tipo de notación que se utiliza para hacer más elegibles los nombres utilizados.
Su característica principal es la de combinar las palabras directamente, sin usar ningún símbolo, estableciendo que la primera letra de cada palabra esté en mayúscula sin excepciones, estando el resto de letras letras en minúsculas
Se usa en muy tipicamente en los nombres de las clases de muchos lenguajes
Ejemplo : UserService

La descripción trata de ser clara con el objetivo de la regla.

La expresión de localización (given) indicará el campo sobre el que trabajar dentro del documento.

La severidad de su incumplimiento se considerará "error".

La comprobación de la función hará uso de la funcionalidad de casing que realiza la comprobación de la notación solicitada en base a las notaciones que ya tiene Spectral declaradas.

Pruebas sobre una Regla Custom

El testing siempre es un punto importante en cualquier desarrollo y en este caso no iba ser menos. Por lo tanto, una de las partes más importantes a la hora de crear una regla custom es todo lo relacionado con las pruebas de esa regla.

Con el testing de la regla se tratará de minimizar:

  • Falsos positivos: la regla SÍ encuentra un problema cuando NO lo hay
  • Falsos negativos: la regla NO encuentra un problema cuando SÍ lo hay

Y sobre todo encontrar y definir claramente los datos válidos con los que funciona y los datos inválidos con los que presenta un error.

Consideraciones sobre pruebas de una Regla Custom

Hay que tener en cuenta una serie de consideraciones :

  • Analizar las necesidades de cumplimiento de la regla de forma adecuada
  • Hay que definir la regla de la mejor manera posible
    • Campos aplicados
    • Función de validación
    • ...
  • Disponer de un buen juego de datos para pruebas de documentos
  • Diponer de un buen juego de datos para pruebas de expresiones regulares
    • Valores válidos
    • Valores inválidos
    • Valores límites
  • ...

Herramientas online de ayuda

En Internet existen muchos recursos / herramientas online y en este caso vamos a hacer uso de algunos de ellos para ayudarnos a definir una Regla Custom de Spectral

Los recursos / herramientas online que pueden ser necesarios su uso engloban funcionalidades como:

  • Conversión / transformación entre formatos
  • Localización de elementos
  • Validación
  • Diferenciadores de textos
  • Test
  • ...

Recursos Online

Conversor de YAML a JSON Online para realizar transformaciones del documento entre los formatos YAML y JSON

Screenshot-2023-06-13-at-19.18.44

Evaluador JSONPath Online para probar expresiones JSONPath

Screenshot-2023-06-13-at-19.20.48

Evaluador Regex Online para probar expresiones regulares (Regex)

Screenshot-2023-06-13-at-19.21.20

AutoRegex IA que ayuda a convertir de Inglés a RegEx

Screenshot-2023-06-13-at-19.24.05

CodePal IA que ayuda a encontrar expresiones regulares

Screenshot-2023-06-13-at-19.25.27

Construir una nueva Regla de Spectral

1. Analizar la funcionalidad que debería de cumplir la Regla

Identificar de forma clara cuál va a ser el objetivo de la Regla Custom que vamos a utilizar.

Un buen ejemplo para saber si se ha entendido la funcionalidad sería definir el campo "descripción" de la regla.

A partir de la descripción se podría establecer el identificador de la regla.

Caso práctico

  • Análisis del requerimiento funcional.

"Queremos tener una regla que se encargue de validar que cualquier contrato de OpenAPI en YAML se encuentre en versión 3"

Sabemos que existe un campo en la definición de la especificación de OpenAPI donde se declara ese aspecto -> "openapi".

Por lo tanto, parece que se podría hacer.

  • Análisis de la severidad.

Hay que identificar qué tipo de aviso queremos que tenga con su no cumplimiento.

En este caso parece que tiene sentido que su NO cumplimiento sea bloqueante por lo que se considará del tipo "error".

  • Según el requerimiento funcional se tendrá que hacer uso o no de un tipo concreto de función validadora

Existen 2

Conviene identificar si se va a poder realizar con la opción de inicio de Spectral

En nuestro caso se ha decidido utilizar la función por defecto pattern que permite trabajar con expresiones regulares.

Sino, habría que planter el desarrollo de una función validadores custom.

  • Con el análisis validado podríamos tratar de definir la descripción de la regla.
APIs should support OpenAPI V3
  • Con la descripción finalizada podríamos tratar de establecer su identificador.
open-api-version-3

2. Identificar la tipología de documentos sobre los que se va a definir la Regla

Identificar el tipo de documento sobre el que se va a trabajar.

Puede ser de sólo un tipo o de varios dependerá de la funcionalidad a cubrir.

Se deberían de conseguir uno o varios documentos de ejemplo para poder trabajar con menos posibilidades de error y así tener mayor casuística de juego de datos a utilizar

Este aspecto esta muy relacionado con el testing.

Caso práctico

Conseguimos un ejemplo de ese tipo de documento

openapi: 3.0.3
info:
  title: Employees API Spec
  description: Working with employee details
  version: v1
paths:
  /api/v1/employees:
    get:
      summary: Get list of employees
      responses:
        "200":
          description: Request to get list of employees
          content:
            application/json:
              schema:
                type: array
                items:
                  type: string

Se trata de un documento OpenAPI en formato YAML que como se puede comprobar tiene un campo principal que se denomina "openapi" que indica la versión del documento utilizada.

3. Convertir de YAML a JSON

Se va a preparar el documento para poder encontrar el localizador JSONPath que nos permite ubicarnos en la estructura.

Se hará uso de una herramienta que convierte de yaml a json

Caso práctico

Pasos a seguir:

  1. Copiaremos el siguiente texto en formato YAML
openapi: 3.0.3
info:
  title: Employees API Spec
  description: Working with employee details
  version: v1
paths:
  /api/v1/employees:
    get:
      summary: Get list of employees
      responses:
        "200":
          description: Request to get list of employees
          content:
            application/json:
              schema:
                type: array
                items:
                  type: string
  1. Convertiremos de YAML a JSON
{
  "openapi": "3.0.3",
  "info": {
    "title": "Employees API Spec",
    "description": "Working with employee details",
    "version": "v1"
  },
  "paths": {
    "/api/v1/employees": {
      "get": {
        "summary": "Get list of employees",
        "responses": {
          "200": {
            "description": "Request to get list of employees",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

Aquí se muestra una captura de pantalla de ejemplo

Screenshot-2023-06-15-at-10.05.49

  1. (Opcional) Guardaremos el fichero JSON en un fichero de texto
  • Se pueden aplicar estrategias de versionado del fichero
  • Se pueden aplicar estrategias de almacenamiento del fichero
  • ...

4. Usar una herramienta para validar el JSONPath de la Regla

Teniendo el documento en formato JSON vamos a tratar de implementar el localizador JSONPath para que nos permita ubicarnos en la sección del documento que queremos.

De esta forma podremos :

  • Saber si existe el campo buscado
  • Saber su ubicación dentro del documento
  • Saber cuáles pueden ser sus valores asociados

Se hará uso de una herramienta que permite probarJSONPath

Caso práctico

Pasos a seguir:

  1. Se cargará el JSON generado en paso anterior
  2. Evaluaremos diferentes expresiones JSONPath para ver cual es la que mejor localizando el o los elementos considerados

Teníamos claro desde el punto 1 sobre que campo ibamos a actuar

  1. Seleccionar la mejor expresión JSONPath
$.openapi
  1. (Si el campo existe) Comprobar los valores de salida

En nuestro caso se muestra

[
  "3.0.3"
]
  1. Analizar los resultados

Conviene analizar las características de los resultados obtenidos (tipo de dato, formato, significado, etc.)

Esto nos hace pensar que las versiones del documento de resultado vienen en formato semver con X.Y.Z y esto nos da una idea que tipos de juego de datos podemos preparar para probar la validación

Aquí se muestra una captura de pantalla de ejemplo

Screenshot-2023-06-15-at-10.18.24

Nota
Si NO se encuentran el campo o los valores deseados habría que volver a analizar cuales son las necesidades de la regla y volver a empezar

5. Preparar el juego de datos de prueba

Una vez identificados y analizados los posibles valores de resultado que puede tener un campo entonces es momento de estudiar, entender y preparar un juego de datos

El juego de datos definido aquí tendrá que tener su representación en los documentos a lintar : documento sin fallos, documento con fallos, documento con varios fallos, etc.

Caso práctico

Se ha visto en el paso anterior que uno de los resultados de la expresión JSONPath utilizada en el ejemplo es "3.0.3".

Este ejemplo más el análisis del tipo de valor que representa ayuda a definir el juego de datos a utilizar:

  • Datos que se deberían de considerar válidos
3.0.3
3.7.8
3.7
3
  • Datos que se deberían de considerar inválidos
1.0
1.0.0
1.2.3
2.4.7
4.8.9

6. (Opcional) Usar validaciones con expresiones regulares

Importante

Este apartado es opcional a que se haga uso de expresiones regulares en la validación de la función

Este paso se encarga de comprobar la expresión regular sobre el juego de datos preparado anteriormente.

Se hará uso de una herramienta para validar expresiones regulares regex

Caso práctico

Pasos a seguir:

  1. Se cargará el juego de datos de prueba para probar si la expresión regular afecta a los valores deseados
  2. Evaluaremos expresiones regulares para ver cuál es la que mejor cumple con los requisitos establecidos
  3. Seleccionar la mejor expresión JSONPath
^3
  1. Verificar si la expresión regular encuentra los datos considerados válidos y descarta los inválidos

Aquí se muestra una captura de pantalla de ejemplo

Screenshot-2023-06-15-at-16.11.59

7. Implementar la regla

Una vez probado todo lo anterior se puede realizar la definición de la regla según el DSL de Spectral

Caso práctico

Pasos a seguir:

  1. Implementar la regla con toda la información que ha sido recopilada en los pasos anteriores
rules:
    open-api-version-3:
        description: APIs should support OpenAPI V3
        given: $.openapi
        severity: error
        then:
            function: pattern
            functionOptions:
                match: '^3'
  1. Implementar la regla según el enfoque con el que se haya decidido su creación

En este caso se hará uso de un fichero individual que contenga la regla

  1. Integrar la regla según el enfoque con el que se haya decidido incorporarla sobre un Ruleset

En este caso se hará uso

  • Definición de Ruleset sobre el fichero por defecto de Spectral
  • La regla definida se añadirá como referencia en la extensión
extends:
- spectral:oas
- ./spectral/rules/open-api-version-3.yaml
rules: {}

8. Probar la Regla desde Spectral

Tras su implementación lo que hay que hacer es probar con diferentes documentos a lintar

Caso práctico

Pasos a seguir:

  1. Probar desde Spectral con los documentos de prueba definidos el cumplimiento de la regla
  2. Validar si es correcta la validación frente a estos documentos

En caso contrario volver a repetir todo el proceso

Ejemplo de lintado con un documento válido

Se ha ejecutrado el siguiente comando

spectral lint ./examples/oas3-test.yaml

Aquí se puede ver una captura de pantalla de la ejecución

Screenshot-2023-06-15-at-16.01.37

No se muestra el incumplimiento de la regla : open-api-version-3

Ejemplo de lintado con un documento inválido

Se ha ejecutrado el siguiente comando

spectral lint ./examples/oas3-test-error-version.yaml

Aquí se puede ver una captura de pantalla de la ejecución

Screenshot-2023-06-15-at-16.04.26

Sí se muestra el incumplimiento de la regla : open-api-version-3

Ejemplo de lintado con varios documentos de todo tipo

Se ha ejecutrado el siguiente comando

spectral lint ./examples/*

Aquí se puede ver una captura de pantalla de la ejecución

Screenshot-2023-06-15-at-16.30.26

Sí se muestra el incumplimiento de la regla en algunos de los documentos : open-api-version-3

Con esto se podría comprobar el correcto funcionamiento de la regla, aunque sería interesante seguír haciendo pruebas

Ejemplo de Uso

Para enseñar a utilizarlo y así practicar se ha habilitado un repositorio, este repositorio se reutilizará para otros artículos con Spectral.

La parte de que tiene que ver con este artículo se encuentra en el apartado de custom-rule/

En el fichero README.md de esta sección nos encontraremos los diferentes escenarios de prueba utilizados en el apartado Uso, conviene entender muy bien las explicaciones de cada uno de ellos.

Toda la parte de definición de la regla estará definida en el artículo web, aquí nos encontraremos la regla ya implementada tras los pasos explicados.

Conclusiones

Creo que con este ejemplo guiado se ha podido comprobar toda la potencia en temática de personalización de reglas que proporciona Spectral y como se ha podido comprobar es una locura de lo fácil que se puede hacer.

Hay que recordar que para la persona que no tenga experiencia con esta herramienta esto ha podido ser complejo, pero os aseguro que con el tiempo y sobre todo con la experiencia los pasos explicados se harán muy rápido y con pocos fallos. Podemos librarnos de bronca porque era nuestra primera vez.

Además, para aquellos que van muy perdidos y que necesitan una guía "paso a paso" para hacer las cosas se ha proporcionado una propuesta de procedimiento de creación de reglas y por lo tanto el inicio de una metodología para el gobierno de reglas customizadas.

Poder definir nuestras propias reglas va a ayudar mucho a definir nuestra propia guía de estilo. Pero esto mejor lo dejamos para otro artículo :-)

Ya hemos nuestro segundo pasito con la herramienta

Autor

Víctor Madrid

Líder Técnico de la Comunidad de Arquitectura de Soluciones en atSistemas. Aprendiz de mucho y maestro de nada. Técnico, artista y polifacético a partes iguales ;-)