Skip to main content

olai_codegen/parsing/
mod.rs

1use std::collections::HashMap;
2
3pub use self::http::{HttpPattern, UrlSegment, extract_http_rule_pattern, extract_path_parameters};
4pub use self::models::*;
5use protobuf::descriptor::{FileDescriptorProto, FileDescriptorSet, SourceCodeInfo};
6pub mod types;
7use crate::Result;
8
9mod enum_parser;
10pub mod http;
11mod message;
12mod models;
13mod service;
14
15/// Extract documentation text for a protobuf element at the given source path.
16///
17/// Scans `SourceCodeInfo.location` for an entry whose path matches `path`,
18/// prefers leading comments over trailing comments, and returns the trimmed text.
19pub(super) fn extract_documentation(sci: Option<&SourceCodeInfo>, path: &[i32]) -> Option<String> {
20    let sci = sci?;
21    for location in &sci.location {
22        if location.path.as_slice() == path {
23            let text = if location.has_leading_comments() {
24                location.leading_comments().trim().to_string()
25            } else if location.has_trailing_comments() {
26                location.trailing_comments().trim().to_string()
27            } else {
28                String::new()
29            };
30            if !text.is_empty() {
31                return Some(text);
32            }
33        }
34    }
35    None
36}
37
38// Known extension field numbers
39const GOOGLE_API_HTTP_EXTENSION: u32 = 72295728; // google.api.http
40const GNOSTIC_OPERATION_EXTENSION: u32 = 1143; // gnostic.openapi.v3.operation
41const GOOGLE_API_RESOURCE_EXTENSION: u32 = 1053; // google.api.resource
42const GOOGLE_API_FIELD_BEHAVIOR_EXTENSION: u32 = 1052; // google.api.field_behavior
43const GOOGLE_API_RESOURCE_REFERENCE_EXTENSION: u32 = 1055; // google.api.resource_reference
44
45/// Parse a compiled protobuf [`FileDescriptorSet`] into [`CodeGenMetadata`].
46///
47/// This is the first stage of the code-generation pipeline. It walks every
48/// message, enum, and service in the descriptor set and extracts the annotations
49/// that drive generation (`google.api.http`, `google.api.resource`,
50/// `field_behavior`, `debug_redact`, …) into a structured form.
51///
52/// The full pipeline is three stages:
53///
54/// ```text
55/// FileDescriptorSet
56///   │  parse_file_descriptor_set
57///   ▼
58/// CodeGenMetadata
59///   │  analyze_metadata          (analysis/mod.rs)
60///   ▼
61/// GenerationPlan
62///   │  generate_code             (codegen/mod.rs)
63///   ▼
64/// Rust / Python / TypeScript source written to disk
65/// ```
66///
67/// Typical usage from a build script:
68///
69/// ```ignore
70/// let fds = prost_types::FileDescriptorSet::decode(&bytes[..])?;
71/// let metadata = parse_file_descriptor_set(&fds)?;
72/// let config = CodeGenConfig::new(/* … */);
73/// generate_code(&metadata, &config)?;
74/// ```
75///
76/// Methods without HTTP annotations are not an error here; they are recorded later,
77/// during [`analyze_metadata`](crate::analyze_metadata).
78///
79/// # Errors
80///
81/// Returns:
82///
83/// - [`Error::InvalidAnnotation`](crate::Error::InvalidAnnotation) if a `google.api.http`,
84///   `google.api.resource`, or `field_behavior` annotation is present but malformed (e.g. a
85///   resource pattern or HTTP rule that cannot be interpreted).
86/// - [`Error::Build`](crate::Error::Build) if an annotation extension cannot be decoded from its
87///   raw descriptor bytes.
88pub fn parse_file_descriptor_set(
89    file_descriptor_set: &FileDescriptorSet,
90) -> Result<CodeGenMetadata> {
91    let mut codegen_metadata = CodeGenMetadata {
92        messages: HashMap::new(),
93        enums: HashMap::new(),
94        services: HashMap::new(),
95    };
96
97    // Process each file descriptor
98    for file_descriptor in &file_descriptor_set.file {
99        process_file_descriptor(file_descriptor, &mut codegen_metadata)?;
100    }
101
102    Ok(codegen_metadata)
103}
104
105/// Process a single protobuf file descriptor
106///
107/// Extracts all messages, services, and annotations from the file.
108/// Collects metadata for code generation.
109pub fn process_file_descriptor(
110    file_desc: &FileDescriptorProto,
111    codegen_metadata: &mut CodeGenMetadata,
112) -> Result<()> {
113    let file_name = file_desc.name();
114
115    // Extract source code info for documentation
116    let source_code_info = file_desc.source_code_info.as_ref();
117
118    // Process enums in the file
119    for (enum_index, enum_desc) in file_desc.enum_type.iter().enumerate() {
120        let package_name = file_desc.package();
121        let type_prefix = if package_name.is_empty() {
122            String::new()
123        } else {
124            format!(".{}", package_name)
125        };
126        enum_parser::process_enum(
127            enum_desc,
128            codegen_metadata,
129            &type_prefix,
130            source_code_info,
131            &[5, enum_index as i32], // enum_type is field 5 in FileDescriptorProto
132        )?;
133    }
134
135    // Process messages in the file
136    for (message_index, message) in file_desc.message_type.iter().enumerate() {
137        let package_name = file_desc.package();
138        let type_prefix = if package_name.is_empty() {
139            String::new()
140        } else {
141            format!(".{}", package_name)
142        };
143
144        message::process_message(
145            message,
146            file_name,
147            codegen_metadata,
148            &type_prefix,
149            source_code_info,
150            &[4, message_index as i32], // message_type is field 4 in FileDescriptorProto
151        )?;
152    }
153
154    // Process services in the file
155    let package_name = file_desc.package().to_string();
156    for (service_index, service) in file_desc.service.iter().enumerate() {
157        service::process_service(
158            service,
159            &package_name,
160            codegen_metadata,
161            source_code_info,
162            service_index,
163        )?;
164    }
165
166    Ok(())
167}