입력 (.proto 스키마)

출력 (Rust)

이 도구가 하는 일

Protocol Buffers 스키마가 있고, 그것과 통신할 Rust 서비스나 클라이언트가 있다고 합시다. 표준 경로는 build.rs 스크립트에 prost(또는 gRPC라면 tonic)를 연결하는 것입니다 — 견고하지만, 그저 타입을 스케치하고 코드와 모양을 맞춰보고 싶을 뿐일 때는 과합니다. 이 변환기는 브라우저 안에서 스키마를 구조체 정의로 바꿔줍니다. 붙여넣고, 복사하고, src/types.rs에 떨어뜨리고, 계속 작업하세요.

출력은 모든 구조체에 #[derive(Debug, Default, Clone, Serialize, Deserialize)]를 붙여 serde를 통한 JSON 라운드트립이 가능하게 하고, #[repr(i32)] enum의 zero-value 변형에 #[default]를 표시해 Default::default()가 proto3 시맨틱과 일치하도록 합니다. 단수형 중첩 메시지는 Option<T>가 됩니다 — Rust는 명시적인 nullability를 요구하고, 이는 prost가 생성하는 형태와 같습니다. repeated 필드의 Vec<T>와 map의 HashMap<K, V>는 모두 #[serde(default)]를 받아, 누락된 필드는 에러 대신 빈 컨테이너로 디시리얼라이즈됩니다.

타입 매핑은 proto3 JSON 매핑 스펙을 따릅니다: string/bytesString/Vec<u8>, 다양한 크기의 정수는 i32/i64/u32/u64로, 부동소수점은 f32/f64로 매핑됩니다. 필드 이름은 snake_case로 변환되고(이미 Rust 관례라 대부분 그대로 통과합니다), enum 변형은 외치는 듯한 SCREAMING_SNAKE 접두사를 떨어냅니다 — enum OrderStatusORDER_STATUS_UNSPECIFIED는 손으로 쓸 법한 Unspecified가 됩니다. 변환은 전적으로 클라이언트 사이드에서 동작합니다. 스키마는 페이지 밖으로 나가지 않습니다.

사용 방법

세 단계. 출력은 의존성에 serde가 있는 어떤 Rust 크레이트든 그대로 붙여 쓸 수 있습니다.

1

.proto 스키마 붙여넣기

왼쪽 에디터에 스키마를 떨어뜨리세요. 맨 위의 syntax = "proto3";는 선택입니다 — 파서는 중첩된 message 블록, enum 선언, oneof, map<K, V>, 필드 옵션을 처리합니다. import는 인식하지만 건너뜁니다. 의존하는 타입이 있다면 본문에 인라인으로 붙여넣으세요.

필드 이름은 자동으로 Rust 관례를 따릅니다: order_idorder_id 그대로, 스키마에 camelCase shippingAddress가 있다면 shipping_address가 됩니다. 구조체와 enum 이름은 PascalCase 그대로입니다.

2

출력 읽기

오른쪽: pub structpub enum 선언이 평면적으로 나열됩니다. enum이 먼저, 그다음 message가 선언 순서대로, 중첩 타입은 부모 앞에. use serde::{Deserialize, Serialize}; 줄은 항상 있고, use std::collections::HashMap;은 스키마에 map 필드가 있을 때만 나타납니다.

3

크레이트에 투입

출력을 모듈 파일에 붙여넣으세요. Cargo.tomlserde = { version = "1", features = ["derive"] }serde_json이 있으면 컴파일됩니다. 그다음부터는 평범한 Rust입니다 — reqwest 클라이언트, Cloudflare Worker, axum 핸들러 등 메시지 데이터에 타입 안전하게 접근해야 하는 어디에든 연결하면 됩니다.

실제로 시간을 아끼는 순간

새 Rust 서비스 타입 스케치

gRPC-gateway 엔드포인트를 소비하는 Rust 서비스를 만들고 있는데, 아직 prost로 본격적인 build.rs를 셋업하고 싶지는 않습니다. .proto를 붙여넣고, 구조체를 types.rs에 떨어뜨리고, 응답을 JSON으로 디코드하고, 프로토타입을 출시하세요.

Protobuf API 변경 리뷰

백엔드 동료가 메시지에 필드를 추가했습니다. 크레이트 전체를 다시 빌드하지 않고 Rust 쪽 모양이 어떻게 바뀌는지 보고 싶습니다. 새 .proto를 붙여넣고, 출력을 기존 타입과 diff하고, 핵심을 짚는 리뷰 코멘트를 남기세요.

prost 생성 코드와 대조 확인

빌드에서 prost를 커스텀 type_attribute 규칙과 함께 사용 중이고, 손대지 않은 타입이 어떻게 보이는지 깔끔한 레퍼런스가 필요합니다. 여기에 스키마를 붙여 좌우로 비교하세요. 속성 변경을 계획하거나 다른 codegen 파이프라인으로 옮길 때 유용합니다.

CLI 도구와 일회성 스크립트

Protobuf 기반 JSON API를 호출하는 작은 Rust CLI를 작성하고 있습니다. 100줄짜리 도구를 위해 prost를 연결하는 건 작업 규모에 비해 의식이 너무 거창합니다. 여기서 구조체를 가져오고, serde를 derive하고, 바이너리를 출시하세요.

자주 묻는 질문

제 스키마가 어디로 전송되나요?

아니요. 파서와 Rust 에미터 모두 브라우저 안에서 JavaScript로 동작합니다. DevTools를 열고 Network 탭을 보면서 붙여넣어 보세요 — 요청은 0건입니다. 스키마에 내부 타입 이름, 패키지 경로, 또는 서드파티 서비스에 보내고 싶지 않은 것이 포함되어 있을 때 유용합니다.

왜 중첩된 메시지가 Option으로 감싸지나요?

Rust에는 Java나 Go처럼 nullable 참조가 없습니다 — 값은 존재하거나, 부재를 명시하기 위해 Option<T>를 사용합니다. proto3의 단수형 서브메시지는 와이어 상에서 nullable이고(필드 태그가 통째로 빠질 수 있습니다), 그래서 Rust에서 관용적인 형태는 Address가 아니라 Option<Address>입니다. 이는 prost가 생성하는 것과 일치합니다. 스칼라와 enum은 감싸지 않습니다 — 그들의 zero value(빈 문자열, 0, false, #[default] enum 변형)가 유효한 Rust 값이기 때문입니다.

왜 64비트 정수가 string이 아니라 i64/u64로 타입이 매겨지나요?

Rust에는 정밀도 문제가 없습니다 — i64u64는 1급 타입이며, 64비트 값이 string인 JavaScript와는 다릅니다. 엄격한 proto3 JSON 인코더가 만든 JSON을 디시리얼라이즈한다면 값은 string으로 도착하고, serde는 자동으로 변환하지 않습니다. 그런 경우엔 해당 필드에 #[serde(with = "serde_with::DisplayFromStr")] 또는 커스텀 디시리얼라이저를 추가하세요.

왜 BTreeMap이 아니라 HashMap인가요?

HashMap은 prost가 map<K, V> 필드에 대해 내보내는 것과 일치하고, 적절한 기본값입니다 — proto의 map 필드는 순서가 없습니다. 안정적인 반복 순서가 필요하다면 손으로 타입을 BTreeMap으로 바꾸세요. 나머지 코드는 바꿀 필요가 없습니다.

이 출력을 prost나 tonic과 함께 써야 하나요?

이건 평범한 serde-derive 구조체이지, prost로 인코딩된 게 아닙니다. serde_json을 통해 proto3 JSON 라운드트립은 되지만, 바이너리 와이어 포맷은 인코드/디코드하지 않습니다. 바이너리 protobuf 인코딩이 필요하다면 build.rs 단계에서 prost를 돌리세요. gRPC가 필요하다면 tonic을 쓰세요. 이 변환기는 JSON-over-HTTP 케이스(gRPC-gateway, Connect, JSON 트랜스코딩) 전용입니다.

oneof를 처리하나요?

oneof의 각 필드는 일반 구조체 필드로 출력됩니다. 출력은 oneof가 의미하는 "정확히 하나" 제약을 강제하지 않습니다 — 그러려면 케이스별 변형을 가진 Rust enum이 필요하고, 그건 런타임 시맨틱에 따라 달라집니다. 엄격한 oneof 처리가 필요하다면 출력을 손으로 pub enum으로 다듬으세요.

관련 도구

Protobuf, JSON, Rust로 작업 중이라면 다음이 잘 어울립니다: