Implémentation des Règles de Validation Protovalidate pour Protocol Buffers

Architecture et Configuration des Règles

Protovalidate s'impose comme une solution de validation contractuelle pour les définitions Protocol Buffers, offrant une compatibilité multiplateforme avec les environnements Go, Java, Python et C++. L'application des contraintes s'effectue de manière déclarative directement dans les fichiers .proto via les options d'extension buf.validate. Cette approche garantit que les messages échangés respectent strictement le schéma défini, externalisant la logique de validation métier hors du code applicatif.

Les spécifications techniques et les tests de conformité du projet illustrent ces contraintes à travers différentes versions de syntaxe (proto2, proto3, et les éditions récentes). L'exemple suivant démontre l'initialisation d'une contrainet de longueur sur un champ textuel :

string database_name = 1 [(buf.validate.field).string = {min_len: 3, max_len: 64}];

Contraintes sur les Types Primitifs

Validation Booléenne

Pour les scénarios nécessitant un consentement explicite ou un état bloquant, la validation booléenne permet d'imposer une valeur stricte, empêchant la propagation de valeurs par défaut non désirées.

bool has_accepted_terms = 1 [(buf.validate.field).bool.const = true];

Validation Numérique

Les types numériques, qu'il s'agisse d'entiers ou de nombres à virgule flottante, peuvent être restreints par des bornes mathématiques ou des valeurs exactes, assurant la cohérence des données quantitatives.

Bornes d'intervalles : Limitation des valeurs minimales et maximales pour une mesure physique.

float sensor_temperature = 1 [(buf.validate.field).float = {gte: -40.0, lte: 85.0}];

Égalité stricte : Imposition d'une valeur constante pour identifier un état système spécifique.

uint32 protocol_version = 2 [(buf.validate.field).uint32.const = 4];

Validation Textuelle

Le traitement des chaînes de caractères bénéficie de validateurs formatés intégrés (URI, IPv4, IPv6, UUID) et de limites de taille pour prévenir les attaques par déni de service ou les injections.

string client_ipv6 = 1 [(buf.validate.field).string.ipv6 = true];
string session_token = 2 [(buf.validate.field).string = {uuid: true, min_len: 36}];

Validation des Structures Complexes

Énumérations

Il est possible d'exclure certaines valeurs d'une énumération ou d'exiger que la valeur transmise soit explicitement définie dans le schéma, évitant ainsi les états indéterminés.

enum AccessLevel {
  ACCESS_LEVEL_UNKNOWN = 0;
  ACCESS_LEVEL_READ = 1;
  ACCESS_LEVEL_WRITE = 2;
  ACCESS_LEVEL_ADMIN = 3;
}

AccessLevel user_role = 1 [(buf.validate.field).enum = {defined_only: true, not_in: [0, 3]}];

Messages Imbriqués

La validation s'étend de manière récursive. Lorsqu'un message contient d'autres messages, l'option required garantit l'instanciation de l'objet enfant, dont les propres règles seront ensuite évaluées par le moteur de validation.

message GeoPoint {
  double latitude = 1 [(buf.validate.field).double = {gte: -90, lte: 90}];
  double longitude = 2 [(buf.validate.field).double = {gte: -180, lte: 180}];
}

message DeliveryZone {
  GeoPoint center = 1 [(buf.validate.field).message.required = true];
}

Collections Répétées

Les tableaux (champs repeated) peuvent être contraints sur leur cardinalité et l'unicité de leurs éléments, ce qui est crucial pour les listes d'identifiants ou de configurations.

repeated string authorized_roles = 1 [(buf.validate.field).repeated = {min_items: 1, max_items: 5, unique: true}];

Scénario d'Intégration et Bonnes Pratiquees

L'exemple suivant démontre l'agrégation de multiples contraintes au sein d'une charge utile de transaction financière. Chaque champ est sécurisé selon sa sémantique métier sans nécessiter de code supplémentaire dans les contrôleurs.

syntax = "proto3";
package financial;

import "buf/validate/validate.proto";

message TransactionPayload {
  string transaction_id = 1 [(buf.validate.field).string.uuid = true];
  string merchant_domain = 2 [(buf.validate.field).string.hostname = true];
  int32 quantity = 3 [(buf.validate.field).int32.gt = 0];
  double amount_usd = 4 [(buf.validate.field).double.gt = 0.0];
  string receipt_email = 5 [(buf.validate.field).string.email = true];
  repeated string discount_codes = 6 [(buf.validate.field).repeated.max_items = 2];
  bool fraud_check_passed = 7 [(buf.validate.field).bool.const = true];
}

Optimisation des Contraintes

  • Approche modulaire avec CEL : Combinez les validateurs standards avec des expressions CEL (Common Expression Language) pour orchestrer des logiques de validation croisées entre plusieurs champs d'un même message.
  • Personnalisation des erreurs : Utilisez le champ message dans les options de validation pour générer des réponses d'erreur explicites, facilitant l'intégration côté client et le débogage des API gRPC.
  • Maintenance du schéma : Centralisez les règles communes dans des messages partagés pour éviter la duplication des définitions de validation à travers les différents microservices de votre architecture.

Étiquettes: Protovalidate Protocol Buffers Protobuf grpc Data Validation

Publié le 3 juillet à 16h11