Implémentation d'une règle de lint Redocly CLI
Transformez une vérification formulée en langage naturel (« chaque schéma de tableau doit déclarer items ») en une règle de lint Redocly fonctionnelle et testée.
L'échelle. Redocly propose trois façons d'effectuer une vérification. La première est la moins coûteuse à maintenir.
- Règle intégrée — une des règles intégrées effectue la vérification.
- Règle configurable — une ou plusieurs entrées
rule/*dansredocly.yamleffectuent la vérification avec des assertions. - Plugin personnalisé — code JavaScript qui implémente votre propre règle avec le modèle visiteur. Utilisez cet échelon en dernier.
Effectuez les vérifications une par une. Commencez chaque vérification à l'échelon 1. Descendez d'un échelon uniquement lorsque l'échelon actuel ne peut pas effectuer la vérification. Tirez toute la syntaxe de la documentation officielle ou du livre de cuisine. Fiez-vous à la documentation, pas à votre mémoire. Utilisez la syntaxe moderne, comme ESM au lieu de CommonJS. Si le projet a son propre style, suivez le projet.
1. Triage
Pour chaque vérification, notez trois choses : le nœud qu'elle examine, ce qui doit être vrai, et à quoi ressemble une violation. Effectuez le triage de chaque vérification séparément. Différentes vérifications peuvent aboutir à différents échelons.
Obtenez les noms des nœuds via la commande inspect-node-types, pas de mémoire.
L'arbre des types est différent dans chaque spécification et chaque version.
Les noms qu'il affiche sont le subject.type d'une règle configurable, et les clés visiteur d'un plugin.
| pour apprendre | exécutez |
|---|---|
| le type à un emplacement | --pointer='#/paths/~1orders' |
| chaque nœud, ou chaque nœud d'un type | pas de drapeau, ou --type=Schema |
| quels types la description utilise | --summary |
| où un type peut se situer | --type=<Type> --parents |
| les ancêtres d'un nœud | --pointer=<pointer> --parents |
Les deux dernières lignes donnent sa portée à une règle.
Lorsqu'un type se situe sous plusieurs parents, la règle a besoin d'une porte where.
Sans cette porte, la règle signale également les branches que vous ne voulez pas.
La commande montre la description devant vous. Elle ne montre pas tout ce que la spécification permet.
Lorsque la commande n'affiche pas une branche, cette branche n'est pas prouvée. Elle n'est pas absente.
La commande ne raccourcit pas l'étape 3. Elle vous donne des noms de types. Elle ne vous dit pas si vos assertions expriment la vérification.
Parcourez l'échelle pour chaque vérification :
- Recherchez dans la liste des règles intégrées le concept.
Une barre oblique finale dans un chemin donne
no-path-trailing-slash. Lorsque la configuration étend déjà un ensemble de règles qui active la règle intégrée, prévenez l'utilisateur. N'ajoutez pas la règle à nouveau. Utilisez la règle intégrée lorsque sa description correspond à la vérification, même si elle signale plus que la règle que vous remplacez. Une règle intégrée est généralement une meilleure version de la même idée, donc ses constatations supplémentaires sont la vérification effectuée correctement. Lorsque vous écrivez le comportement plus restreint à la main, vous échangez une règle maintenue contre du code que vous devez maintenir. Descendez d'un échelon uniquement lorsque la règle intégrée vérifie quelque chose de différent. Signalez quelle règle intégrée vous avez prise, et ce qu'elle ajoute. - Assurez-vous que la règle intégrée prend en charge les versions de spécification que le projet lint.
Certaines règles intégrées ne prennent en charge qu'une seule version.
no-invalid-media-type-examplesprend en charge OpenAPI 3.x, donc elle laisse une vérification Swagger 2.0 non appliquée. Redocly afficheUnused rules found in redocly.yamllorsqu'une règle activée ne peut pas s'appliquer à un document. Corrigez cette ligne. Ce n'est pas du bruit. - Essayez les assertions et une porte
where. Voir « Règles configurables » ci-dessous. Une vérification peut nécessiter plusieurs entréesrule/*, une pour chaque cas ou emplacement. C'est toujours mieux qu'un plugin. - Écrivez un plugin uniquement lorsque les règles configurables ne peuvent pas exprimer la vérification, même en combinaison. Commencez par travailler sur les trois cadrans ci-dessous. Un plugin peut effectuer n'importe quelle vérification, mais c'est du code. Il ajoute de la complexité, de la maintenance et ses propres bugs.
- Dites à l'utilisateur de créer un ticket uniquement lorsqu'aucune règle configurable ne peut combler une lacune réelle.
Le triage est terminé lorsque chaque vérification a un échelon. Donnez une solution pour chaque vérification : l'échelon le plus élevé qui fonctionne. Signalez uniquement ce sur quoi l'utilisateur doit agir : une inadéquation entre une description et une implémentation, une règle non prouvée, ou une décision qui pourrait aller dans l'autre sens. Signalez dans la conversation. N'écrivez jamais de fichier récapitulatif. La configuration enregistre le reste, donc ne racontez pas l'échelon de chaque vérification.
Migrer depuis un autre linter
- Migrez l'ensemble de règles effectif, pas seulement les règles écrites dans le fichier.
Un
extends, ou une base héritée, active plus de règles avec leurs sévérités par défaut. Chacune de ces règles est également une vérification. Résolvez cette base et comptez les règles avant de commencer. Ainsi vous connaîtrez la véritable taille du travail. - Prenez chaque vérification de deux endroits : la description de la règle et son implémentation. Ne la prenez jamais de votre mémoire du nom de la règle. Lorsque les deux sont en désaccord, utilisez la description. La description dit ce que l'auteur voulait, et l'implémentation peut contenir un bug. Informez l'utilisateur de la différence.
- Ignorez une règle avec la sévérité
off. Ne la migrez pas, même pas en tant qu'entrée désactivée. Lorsque les règles activées sont terminées, listez chaque règle ignorée sur une ligne. Ensuite, l'utilisateur peut décider. - Pour Spectral, lisez d'abord le tableau de correspondance dans le guide de migration. De nombreuses règles de base Spectral correspondent à une règle intégrée. Une cellule vide signifie que vous descendez dans l'échelle.
- Lorsque vous copiez une fonction depuis la source, vous copiez également ses bugs dans un hôte plus strict.
Redocly arrête tout le document lorsqu'une règle génère une exception, mais le linter source peut arrêter une seule règle.
Une correspondance non protégée, telle que
v.match(/…/)[0]sur une valeur qui peut ne pas correspondre, supprime toutes les constatations dans ce fichier. Ajoutez une protection au code que vous copiez. Ensuite, lint un document qui contient la forme difficile.
2. Configuration
Écrivez dans le fichier redocly.yaml de l'utilisateur. Utilisez ce nom exact, sans point initial.
Lorsque le fichier existe, lisez-le d'abord et conservez les extends et règles existants.
Définissez chaque règle sur error, sauf si l'utilisateur demande un autre niveau.
Lorsque vous migrez, conservez la sévérité source et choisissez le niveau Redocly le plus proche.
Seuls error, qui fait échouer l'exécution, warn, et off existent, donc info et hint deviennent warn.
Référencez chaque fichier que vous créez à partir de la configuration et exercez-le dans l'exécution lint. Ne laissez aucun fichier mort.
Une configuration sans extends n'hérite de rien — pas de recommended, et même pas de struct.
La base par défaut s'applique uniquement lorsqu'aucun fichier de configuration n'existe.
Donc, un redocly.yaml exécute exactement les règles qu'il nomme.
L'échelon de règle intégrée existe uniquement lorsque extends nomme un ensemble de règles, ou lorsque la configuration liste chaque règle intégrée elle-même.
Sans cela, un document qui enfreint la spécification trois fois passe le lint sans erreur.
Câblez chaque règle.
Un plugin dans plugins: ne fait rien lorsque ses règles sont absentes de rules:, et Redocly ne donne aucun avertissement.
Après avoir écrit la configuration, listez les identifiants de règles qui doivent être actifs.
Confirmez que chaque identifiant est dans rules: ou dans un bloc spécifique à une spécification.
Lorsque toutes les règles sont présentes, comparez votre liste avec les ensembles de règles prédéfinis : minimal, recommended, recommended-strict, et spec.
- Lorsque vos règles sont proches d'un ensemble de règles, étendez-le. Conservez uniquement les différences comme surcharges : autres sévérités, règles à désactiver, et vérifications que l'ensemble de règles ne contient pas.
- Un ensemble de règles active également des règles que l'utilisateur n'a pas demandées.
Lisez ce qu'il ajoute et surchargez chaque règle qui contredit ou répète une règle que vous avez écrite.
Un ensemble de règles qui active une vérification que la source définit sur
offest une erreur à corriger, pas un avantage. - Lorsque les surcharges sont aussi nombreuses que les règles, ou plus, conservez la liste simple et sans
extends.
3. Vérification
Une règle n'est pas prouvée jusqu'à ce qu'une exécution réelle la fasse signaler.
Prouvez chaque règle avec npx redocly lint <fichier-api> dans deux directions :
- un fichier de test violant : la règle signale, à la bonne position ;
- un fichier de test conforme : l'exécution reste silencieuse.
Un faux positif est l'échec courant, donc une règle qui n'a passé que le fichier de test violant n'est pas prouvée. Écrivez le fichier de test violant pour séparer cette règle de la règle adjacente : la règle que vous remplacez, ou sa voisine dans la configuration. Deux erreurs nécessitent cette séparation :
- Mettez une valeur fausse dans le fichier de test violant.
Une règle qui doit rejeter une
descriptionvide doit signaler surdescription: ''. Lorsque vous mettez''uniquement dans le fichier de test conforme, vous ne prouvez rien, car une assertion de présence l'accepte. La règle semble alors correcte et la lacune persiste. - Une règle intégrée qui remplace deux vérifications source a besoin d'un fichier de test qui enfreint uniquement la deuxième vérification.
no-unsafe-markdownsignale<script>, mais jamaiseval(. Un fichier de test qui contient les deux donne à la règle intégrée le crédit d'une vérification qu'elle n'effectue pas.
Pour garder la sortie propre, passez une petite configuration temporaire avec --config.
Supprimez cette configuration et les fichiers de test lorsque vous terminez.
Chaque extrait que vous fournissez doit être correct tel qu'écrit : il s'analyse comme YAML et se comporte comme vous le prétendez.
Vous avez terminé lorsque chaque vérification nomme un fichier de test violant qui a signalé, et un fichier de test conforme qui est resté silencieux.
Nommez ces fichiers de test dans votre rapport.
Un compte n'est pas une preuve.
Une exécution propre sur les propres fichiers du projet n'est pas non plus une preuve : elle montre seulement que rien n'y signale, ce qui est vrai pour une règle correcte et pour une règle morte.
Lorsque vous vérifiez les règles risquées et supposez les autres, la livraison n'est pas prouvée. Dites-le, et nommez chaque règle non prouvée.
Un fichier de test conforme prouve uniquement les formes qu'il contient.
Une version de spécification que vous n'avez pas essayée, ou un emplacement que --parents a montré et que vous n'avez pas exercé, reste non testé. Signalez-le.
Lorsqu'une exécution réelle est impossible, marquez la règle comme non vérifiée. Ne prétendez pas qu'elle fonctionne.
Vous avez également terminé lorsque chaque vérification demandée est dans la configuration ou sur la liste ignorée, et que le projet ne contient que les fichiers de configuration et de plugin que vous avez créés.
Règles configurables
Documentation : règles configurables. Exemples pratiques : cookbook/configurable-rules.
Une règle est une entrée sous rules: avec un nom qui commence par rule/ :
rules:
rule/ma-verification:
subject:
type: Schema # un type de nœud de la liste des types de la documentation
property: title # facultatif
assertions:
defined: true
where: [] # porte facultative
message: Texte de problème personnalisé.
severity: error
rules simple s'applique à chaque document que le projet lint. Gardez les règles là par défaut.
Déplacez une règle sous une clé spécifique à une spécification uniquement lorsque la vérification a besoin de quelque chose qui existe dans une version de spécification, comme nullable dans OAS 3.0, ou lorsque l'utilisateur demande le cadrage.
Les clés sont oas2Rules, oas3_0Rules, oas3_1Rules, oas3_2Rules, async2Rules, async3Rules, arazzo1Rules, et overlay1Rules.
Une règle à l'intérieur d'une clé ne s'exécute pas pour les autres versions, donc une vérification dont le projet a besoin sur deux versions a besoin d'une entrée que les deux versions atteignent.
Câblez une règle de plugin à l'intérieur du plugin à la place, dans sa carte rules.
Ce qu'une assertion teste
Deux faits causent la plupart des faux positifs et la plupart des absences silencieuses.
Une valeur absente passe.
Presque toutes les assertions tiennent lorsque la valeur qu'elles vérifient est absente.
const: array sur property: type correspond à un schéma typé array, et également à chaque schéma sans type.
Donc, lorsqu'une entrée where teste une valeur de propriété, ajoutez defined: true à côté de l'assertion de valeur si la propriété peut être absente.
La présence n'est pas la valeur.
defined, required, requireAny, disallowed, et mutuallyExclusive testent si une clé existe.
Ils ne disent rien sur le contenu, donc defined: true sur description passe pour description: ''.
Affirmez plutôt la valeur : nonEmpty: true rejette une chaîne vide, et const, enum, et pattern testent le contenu.
C'est le défaut le plus fréquent en pratique.
Il semble correct en révision, et seul un fichier de test violant qui contient '' le montre.
Deux résultats de ces faits :
const: <valeur>est également la façon de dire « ce champ doit être faux », car l'assertion tient pour cette valeur et pour un champ absent.requireAnycompte uniquement les clés. « Au moins un de ces champs, et non vide » n'a pas de forme d'assertion. Cette vérification est un vrai cas de plugin.
Ce que l'assertion inspecte
property: name— la valeur de ce champ sur le nœud sujet.property: [name, url, email]— chaque champ listé individuellement. Chaque champ qui échoue devient son propre problème avec son propre pointeur JSON, comme#/info/contact/name.propertyabsent — les clés du nœud sujet. Par exemple,subject: { type: Paths }avecassertions: { notPattern: /.+\/$/ }s'exécute sur les chaînes de chemin elles-mêmes.
Visez le sujet, puis cadrez-le
Trois cadrans visent une règle. Résolvez-les dans cet ordre.
1. Sujet : le nœud qui possède ce que vous affirmez.
- Une vérification sur les clés qu'une carte contient cible le type de carte lui-même.
Les cartes des réponses, en-têtes, propriétés de schéma et chemins sont chacune leur propre type.
Donc « cette carte doit contenir cette clé » est un
requiredsur un sujet. Ne cherchez pas le parent. - Une vérification sur une valeur cible le nœud qui contient le champ, plus
property. type: anyavec unepropertyvérifie cette propriété partout où elle apparaît.- Confirmez le nom du type avec
inspect-node-types, sur un document de la version de spécification que la règle cible. Un type nommé peut exister dans une version et pas dans une autre.HeadersMapest un type dans OpenAPI 3. Dans OpenAPI 2, la carteheadersde la réponse est anonyme et n'a pas de nom de type, donc un sujetHeadersMapsousoas2Rulesne signale jamais. Redocly avertit d'un identifiant de règle non résolu, mais pas d'unsubject.typenon résolu, donc un mauvais type vous donne une règle qui ne signale jamais.
2. Porte : un filtre conditionnel.
3. Assertion : ce qui doit être vrai pour le champ visé.
Pour plus de détails, consultez la documentation officielle de Redocly CLI.