use std::collections::HashSet;
use std::error::Error;
use std::fmt;
use std::marker::PhantomData;
use std::marker::PhantomPinned;
use std::mem;
use std::path::Path;
use std::pin::Pin;
use cxx::let_cxx_string;
use crate::internal::{unsafe_ffi_conversions, CInt, ProtobufPath};
use crate::io::DynZeroCopyInputStream;
use crate::{DescriptorDatabase, FileDescriptorProto, FileDescriptorSet, OperationFailedError};
#[cxx::bridge(namespace = "protobuf_native::compiler")]
pub(crate) mod ffi {
#[derive(Debug)]
struct FileLoadError {
filename: String,
line: i64,
column: i64,
message: String,
warning: bool,
}
unsafe extern "C++" {
include!("protobuf-native/src/compiler.h");
include!("protobuf-native/src/internal.h");
#[namespace = "protobuf_native::internal"]
type CInt = crate::internal::CInt;
#[namespace = "absl"]
type string_view<'a> = crate::internal::StringView<'a>;
#[namespace = "google::protobuf"]
type FileDescriptorProto = crate::ffi::FileDescriptorProto;
#[namespace = "google::protobuf::io"]
type ZeroCopyInputStream = crate::io::ffi::ZeroCopyInputStream;
type SimpleErrorCollector;
fn NewSimpleErrorCollector() -> *mut SimpleErrorCollector;
unsafe fn DeleteSimpleErrorCollector(collector: *mut SimpleErrorCollector);
fn Errors(self: Pin<&mut SimpleErrorCollector>) -> Pin<&mut CxxVector<FileLoadError>>;
#[namespace = "google::protobuf::compiler"]
type MultiFileErrorCollector;
fn RecordError(
self: Pin<&mut MultiFileErrorCollector>,
filename: string_view,
line: CInt,
column: CInt,
message: string_view,
);
fn RecordWarning(
self: Pin<&mut MultiFileErrorCollector>,
filename: string_view,
line: CInt,
column: CInt,
message: string_view,
);
#[namespace = "google::protobuf::compiler"]
type SourceTree;
fn Open(self: Pin<&mut SourceTree>, filename: string_view) -> *mut ZeroCopyInputStream;
fn SourceTreeGetLastErrorMessage(source_tree: Pin<&mut SourceTree>) -> String;
#[namespace = "google::protobuf::compiler"]
type SourceTreeDescriptorDatabase;
unsafe fn NewSourceTreeDescriptorDatabase(
source_tree: *mut SourceTree,
) -> *mut SourceTreeDescriptorDatabase;
unsafe fn DeleteSourceTreeDescriptorDatabase(
source_tree: *mut SourceTreeDescriptorDatabase,
);
unsafe fn FindFileByName(
self: Pin<&mut SourceTreeDescriptorDatabase>,
filename: &CxxString,
output: *mut FileDescriptorProto,
) -> bool;
unsafe fn RecordErrorsTo(
self: Pin<&mut SourceTreeDescriptorDatabase>,
error_collector: *mut MultiFileErrorCollector,
);
type VirtualSourceTree;
fn NewVirtualSourceTree() -> *mut VirtualSourceTree;
unsafe fn DeleteVirtualSourceTree(tree: *mut VirtualSourceTree);
fn AddFile(self: Pin<&mut VirtualSourceTree>, filename: string_view, contents: Vec<u8>);
#[namespace = "google::protobuf::compiler"]
type DiskSourceTree;
fn NewDiskSourceTree() -> *mut DiskSourceTree;
unsafe fn DeleteDiskSourceTree(tree: *mut DiskSourceTree);
fn MapPath(
self: Pin<&mut DiskSourceTree>,
virtual_path: string_view,
disk_path: string_view,
);
}
}
pub trait MultiFileErrorCollector: multi_file_error_collector::Sealed {
fn add_error(self: Pin<&mut Self>, filename: &str, line: i32, column: i32, message: &str) {
self.upcast_mut().RecordError(
filename.into(),
CInt::expect_from(line),
CInt::expect_from(column),
message.into(),
)
}
fn add_warning(self: Pin<&mut Self>, filename: &str, line: i32, column: i32, message: &str) {
self.upcast_mut().RecordWarning(
filename.into(),
CInt::expect_from(line),
CInt::expect_from(column),
message.into(),
)
}
}
mod multi_file_error_collector {
use std::pin::Pin;
use super::ffi;
pub trait Sealed {
fn upcast(&self) -> &ffi::MultiFileErrorCollector;
fn upcast_mut(self: Pin<&mut Self>) -> Pin<&mut ffi::MultiFileErrorCollector>;
unsafe fn upcast_mut_ptr(self: Pin<&mut Self>) -> *mut ffi::MultiFileErrorCollector {
self.upcast_mut().get_unchecked_mut() as *mut _
}
}
}
pub struct SimpleErrorCollector {
_opaque: PhantomPinned,
}
impl Drop for SimpleErrorCollector {
fn drop(&mut self) {
unsafe { ffi::DeleteSimpleErrorCollector(self.as_ffi_mut_ptr_unpinned()) }
}
}
impl SimpleErrorCollector {
pub fn new() -> Pin<Box<SimpleErrorCollector>> {
let collector = ffi::NewSimpleErrorCollector();
unsafe { Self::from_ffi_owned(collector) }
}
unsafe_ffi_conversions!(ffi::SimpleErrorCollector);
}
impl<'a> Iterator for Pin<&'a mut SimpleErrorCollector> {
type Item = FileLoadError;
fn next(&mut self) -> Option<FileLoadError> {
self.as_mut().as_ffi_mut().Errors().pop().map(Into::into)
}
}
impl MultiFileErrorCollector for SimpleErrorCollector {}
impl multi_file_error_collector::Sealed for SimpleErrorCollector {
fn upcast(&self) -> &ffi::MultiFileErrorCollector {
unsafe { mem::transmute(self) }
}
fn upcast_mut(self: Pin<&mut Self>) -> Pin<&mut ffi::MultiFileErrorCollector> {
unsafe { mem::transmute(self) }
}
}
pub struct SourceTreeDescriptorDatabase<'a> {
_opaque: PhantomPinned,
_lifetime: PhantomData<&'a ()>,
}
impl<'a> Drop for SourceTreeDescriptorDatabase<'a> {
fn drop(&mut self) {
unsafe { ffi::DeleteSourceTreeDescriptorDatabase(self.as_ffi_mut_ptr_unpinned()) }
}
}
impl<'a> SourceTreeDescriptorDatabase<'a> {
pub fn new(
source_tree: Pin<&'a mut dyn SourceTree>,
) -> Pin<Box<SourceTreeDescriptorDatabase<'a>>> {
let db = unsafe { ffi::NewSourceTreeDescriptorDatabase(source_tree.upcast_mut_ptr()) };
unsafe { Self::from_ffi_owned(db) }
}
pub fn record_errors_to(
self: Pin<&mut Self>,
error_collector: Pin<&'a mut dyn MultiFileErrorCollector>,
) {
unsafe {
self.as_ffi_mut()
.RecordErrorsTo(error_collector.upcast_mut_ptr())
}
}
pub fn build_file_descriptor_set<P>(
mut self: Pin<&mut Self>,
roots: &[P],
) -> Result<Pin<Box<FileDescriptorSet>>, OperationFailedError>
where
P: AsRef<Path>,
{
let mut out = FileDescriptorSet::new();
let mut seen = HashSet::new();
let mut stack = vec![];
for root in roots {
let root = root.as_ref();
stack.push(self.as_mut().find_file_by_name(root)?);
seen.insert(ProtobufPath::from(root).as_bytes().to_vec());
}
while let Some(file) = stack.pop() {
out.as_mut().add_file().copy_from(&file);
for i in 0..file.dependency_size() {
let dep_path = ProtobufPath::from(file.dependency(i));
if !seen.contains(dep_path.as_bytes()) {
let dep = self
.as_mut()
.find_file_by_name(dep_path.as_path().as_ref())?;
stack.push(dep);
seen.insert(dep_path.as_bytes().to_vec());
}
}
}
Ok(out)
}
unsafe_ffi_conversions!(ffi::SourceTreeDescriptorDatabase);
}
impl<'a> DescriptorDatabase for SourceTreeDescriptorDatabase<'a> {
fn find_file_by_name(
self: Pin<&mut Self>,
filename: &Path,
) -> Result<Pin<Box<FileDescriptorProto>>, OperationFailedError> {
let mut fd = FileDescriptorProto::new();
let_cxx_string!(filename = ProtobufPath::from(filename).as_bytes());
if unsafe {
self.as_ffi_mut()
.FindFileByName(&filename, fd.as_mut().as_ffi_mut_ptr())
} {
Ok(fd)
} else {
Err(OperationFailedError)
}
}
}
pub trait SourceTree: source_tree::Sealed {
fn open<'a>(
self: Pin<&'a mut Self>,
filename: &Path,
) -> Result<Pin<Box<DynZeroCopyInputStream<'a>>>, FileOpenError> {
let filename = ProtobufPath::from(filename);
let mut source_tree = self.upcast_mut();
let stream = source_tree.as_mut().Open(filename.into());
if stream.is_null() {
Err(FileOpenError(ffi::SourceTreeGetLastErrorMessage(
source_tree,
)))
} else {
Ok(unsafe { DynZeroCopyInputStream::from_ffi_owned(stream) })
}
}
}
mod source_tree {
use std::pin::Pin;
use super::ffi;
pub trait Sealed {
fn upcast(&self) -> &ffi::SourceTree;
fn upcast_mut(self: Pin<&mut Self>) -> Pin<&mut ffi::SourceTree>;
unsafe fn upcast_mut_ptr(self: Pin<&mut Self>) -> *mut ffi::SourceTree {
self.upcast_mut().get_unchecked_mut() as *mut _
}
}
}
pub struct VirtualSourceTree {
_opaque: PhantomPinned,
}
impl Drop for VirtualSourceTree {
fn drop(&mut self) {
unsafe { ffi::DeleteVirtualSourceTree(self.as_ffi_mut_ptr_unpinned()) }
}
}
impl VirtualSourceTree {
pub fn new() -> Pin<Box<VirtualSourceTree>> {
let tree = ffi::NewVirtualSourceTree();
unsafe { Self::from_ffi_owned(tree) }
}
pub fn add_file(self: Pin<&mut Self>, filename: &Path, contents: Vec<u8>) {
let filename = ProtobufPath::from(filename);
self.as_ffi_mut().AddFile(filename.into(), contents)
}
unsafe_ffi_conversions!(ffi::VirtualSourceTree);
}
impl SourceTree for VirtualSourceTree {}
impl source_tree::Sealed for VirtualSourceTree {
fn upcast(&self) -> &ffi::SourceTree {
unsafe { mem::transmute(self) }
}
fn upcast_mut(self: Pin<&mut Self>) -> Pin<&mut ffi::SourceTree> {
unsafe { mem::transmute(self) }
}
}
pub struct DiskSourceTree {
_opaque: PhantomPinned,
}
impl Drop for DiskSourceTree {
fn drop(&mut self) {
unsafe { ffi::DeleteDiskSourceTree(self.as_ffi_mut_ptr_unpinned()) }
}
}
impl DiskSourceTree {
pub fn new() -> Pin<Box<DiskSourceTree>> {
let tree = ffi::NewDiskSourceTree();
unsafe { Self::from_ffi_owned(tree) }
}
pub fn map_path(self: Pin<&mut Self>, virtual_path: &Path, disk_path: &Path) {
let virtual_path = ProtobufPath::from(virtual_path);
let disk_path = ProtobufPath::from(disk_path);
self.as_ffi_mut()
.MapPath(virtual_path.into(), disk_path.into())
}
unsafe_ffi_conversions!(ffi::DiskSourceTree);
}
impl SourceTree for DiskSourceTree {}
impl source_tree::Sealed for DiskSourceTree {
fn upcast(&self) -> &ffi::SourceTree {
unsafe { mem::transmute(self) }
}
fn upcast_mut(self: Pin<&mut Self>) -> Pin<&mut ffi::SourceTree> {
unsafe { mem::transmute(self) }
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct FileOpenError(String);
impl fmt::Display for FileOpenError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(&self.0)
}
}
impl Error for FileOpenError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Severity {
Error,
Warning,
}
impl fmt::Display for Severity {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Severity::Warning => f.write_str("warning"),
Severity::Error => f.write_str("error"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Location {
pub line: i64,
pub column: i64,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct FileLoadError {
pub filename: String,
pub message: String,
pub severity: Severity,
pub location: Option<Location>,
}
impl From<ffi::FileLoadError> for FileLoadError {
fn from(ffi: ffi::FileLoadError) -> FileLoadError {
let location = (ffi.line >= 0).then(|| Location {
line: ffi.line + 1,
column: ffi.column + 1,
});
FileLoadError {
filename: ffi.filename,
message: ffi.message,
severity: if ffi.warning {
Severity::Warning
} else {
Severity::Error
},
location,
}
}
}
impl fmt::Display for FileLoadError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}:", self.filename)?;
if let Some(location) = &self.location {
write!(f, "{}:{}:", location.line, location.column)?;
}
write!(f, " {}: {}", self.severity, self.message)
}
}