Entrée (schéma .proto)

Sortie (GraphQL SDL)

Ce que fait cet outil

Tu as un schéma Protocol Buffers et une gateway GraphQL devant (ou tu es sur le point d’en construire une). Des outils comme rejoiner de Google branchent les messages proto sur un endpoint GraphQL en direct au runtime, mais pour esquisser le schéma, faire de la revue de code ou amorcer une couche de resolvers écrite à la main, tu veux juste voir à quoi ressemble le SDL. Ce convertisseur fait ça dans ton navigateur — colle, copie la sortie, dépose-la dans schema.graphql.

Chaque message devient un type GraphQL ; chaque enum devient un enum GraphQL. Les noms de champs sont convertis du snake_case (convention proto) vers du camelCase (la convention encodée dans la spec GraphQL et imposée par tous les linters). Les scalaires et enums sont émis non nuls (String!, OrderStatus!) parce que les champs proto3 ont toujours une valeur sur le wire — même non définis, ils prennent leur valeur par défaut. Les messages imbriqués singuliers sont émis nullables, ce qui correspond à la sémantique has-value de proto3.

Les champs repeated se rendent en [T!]! — liste non nulle d’éléments non nuls — parce que les champs repeated en proto3 ne sont jamais null et ne contiennent jamais d’entrées null. Les maps demandent une astuce : GraphQL n’a pas de type map natif, donc map<string, string> sur un champ metadata devient [OrderMetadataEntry!]! avec un type OrderMetadataEntry { key: String! value: String! } synthétique, le même schéma utilisé par Apollo et la plupart des gateways proto-vers-GraphQL en production. Toute la conversion tourne côté client ; rien de ton schéma ne quitte la page.

Comment l’utiliser

Trois étapes. La sortie est prête à coller dans n’importe quel serveur GraphQL qui accepte le SDL.

1

Colle ton schéma .proto

Dépose le schéma dans l’éditeur de gauche. syntax = "proto3"; en tête est optionnel — le parser gère les blocs message imbriqués, les déclarations enum, oneof, map<K, V> et les options de champ. Les imports sont reconnus mais ignorés, donc colle les types importés en ligne si tu en dépends.

La conversion des noms de champs est automatique : order_id en .proto devient orderId en GraphQL. Les noms de types et d’enums restent en PascalCase. Les champs map reçoivent un type d’entrée synthétique nommé <Parent><Champ>Entry.

2

Lis la sortie

À droite : SDL GraphQL avec les enums d’abord, puis les types d’entrée des maps, puis les messages dans l’ordre de déclaration. Les types imbriqués passent avant leurs parents pour que le fichier se lise du haut vers le bas. Dépose-le dans un fichier .graphql que ton serveur charge via graphql-tools ou buildSchema.

3

Branche les resolvers

Le schéma te donne la forme ; il te reste à écrire les resolvers (ou à les générer depuis ton service gRPC). Pour aller vite, pointe un Apollo Server sur ce SDL avec des resolvers stub, puis remplace chaque stub par un appel à ton backend gRPC. Ajuste la nullabilité si ton contrat runtime diffère des valeurs par défaut de proto3.

Quand ça fait vraiment gagner du temps

Démarrer une gateway GraphQL au-dessus d’un backend gRPC

Ton équipe a un service gRPC et le produit veut un endpoint GraphQL pour le client web. Tu as besoin d’un schéma de départ pour discuter avec les frontend avant d’écrire le moindre resolver. Colle le proto, copie le SDL, mets-le dans un doc — fini.

Revoir un changement d’API Protobuf

Un collègue backend a ajouté des champs à un message. Tu veux voir comment ça affecte la surface GraphQL publique sans relancer toute la chaîne de codegen. Colle le nouveau .proto, fais un diff du SDL contre ton schéma actuel, laisse un commentaire de revue ciblé.

Documentation et discussions de design

Tu rédiges une RFC sur un nouveau service gRPC qui devra finir avec un GraphQL devant. Mettre les deux formes dans le doc — proto d’un côté, SDL de l’autre — rend la conversation concrète. Ce convertisseur te donne le côté SDL sans monter de build.

Migrer de REST ou gRPC vers GraphQL

Tu as hérité d’un service défini en Protobuf et le nouveau brief produit demande une API GraphQL. Génère ici un brouillon de schéma comme point de départ pour la migration, puis itère à la main sur les noms de champs, la nullabilité et la pagination.

Questions fréquentes

Mon schéma est-il envoyé quelque part ?

Non. Le parser .proto et l’émetteur SDL tournent tous les deux en JavaScript dans ton navigateur. Ouvre les DevTools et regarde l’onglet Network pendant que tu colles — zéro requête. Utile quand ton schéma contient des noms de types internes, des chemins de package ou tout ce que tu ne voudrais pas envoyer à un service tiers.

Pourquoi les entiers 64 bits sortent en String ?

Le scalaire Int intégré de GraphQL est signé sur 32 bits selon la spec GraphQL, ce qui ne peut pas contenir un int64 ou uint64 proto. La parade conventionnelle est de définir un scalaire personnalisé (souvent appelé BigInt, Long ou Int64) et de sérialiser la valeur en string. Ce convertisseur émet String pour tous les types entiers 64 bits afin que le schéma soit valide d’emblée ; remplace ces occurrences par le nom de ton scalaire personnalisé une fois qu’il est défini côté serveur.

Comment sont gérés les champs map ?

GraphQL n’a pas de type map natif — il n’y a pas de forme { String: String }. La parade standard est de dérouler la map en une liste de paires { key, value }. Donc map<string, string> metadata = 8; sur un message Order devient metadata: [OrderMetadataEntry!]! avec un type OrderMetadataEntry { key: String! value: String! } synthétique. Le nom du type d’entrée vient du message parent + nom du champ en PascalCase + Entry, c’est la convention utilisée par la plupart des gateways proto-vers-GraphQL, dont rejoiner.

Comment est géré oneof ?

Chaque champ d’un oneof est émis comme un champ nullable normal avec un commentaire marquant le groupe. GraphQL n’a pas de concept natif d’union discriminée qui se mappe proprement au oneof de proto — l’analogue le plus proche est un union type personnalisé ou une directive @oneOf côté input (récemment ajoutée à la spec pour les inputs). Pour les types de sortie, la plupart des schémas émettent juste chaque cas du oneof en nullable et documentent la contrainte, c’est ce qu’on fait ici. Édite la sortie à la main si tu veux des union types stricts.

Pourquoi les scalaires sont non nuls mais les messages nullables ?

Ça correspond à la sémantique proto3. Les scalaires en proto3 ont toujours une valeur sur le wire — un champ string non défini prend "" par défaut, un int32 non défini prend 0. Il n’y a aucun moyen de distinguer "défini à la valeur par défaut" de "non défini" pour les scalaires sans utiliser optional ou des types wrapper. En revanche, les messages imbriqués singuliers ont bien une sémantique has-value — le field tag peut être totalement absent — donc ils mappent naturellement à des champs GraphQL nullables. Les champs repeated sont toujours des listes non nulles d’éléments non nuls, encore une fois conformes à proto3.

Et google.protobuf.Timestamp et les autres types well-known ?

Les types well-known sont émis en String par défaut, vu que GraphQL n’a pas de scalaire DateTime intégré. La plupart des schémas en production définissent un scalaire DateTime personnalisé (ou ISO8601String) et remplacent les occurrences de String après génération. Pareil pour google.protobuf.Duration, Any et Value — émis en String d’abord, échangés contre des scalaires personnalisés une fois définis.

Champs bytes — pourquoi String ?

GraphQL n’a pas de scalaire binaire natif. L’encodage conventionnel est le base64 dans un champ String, c’est aussi ce qu’utilise le mapping JSON proto3 pour bytes. Si tu veux un typage plus strict, définis un scalaire personnalisé Base64String côté serveur et remplace String sur ces champs.

Outils associés

Si tu travailles avec Protobuf, GraphQL et JSON, ces outils s’associent bien :