mod option_resolver;
mod type_resolver;
use protobuf;
use protobuf::descriptor::descriptor_proto::ReservedRange;
use protobuf::descriptor::field_descriptor_proto;
use protobuf::descriptor::field_descriptor_proto::Type;
use protobuf::descriptor::FieldDescriptorProto;
use protobuf::descriptor::OneofDescriptorProto;
use protobuf::reflect::FileDescriptor;
use protobuf_support::json_name::json_name;
use protobuf_support::text_format::escape_bytes_to;
use crate::case_convert::camel_case;
use crate::path::fs_path_to_proto_path;
use crate::proto_path::ProtoPath;
use crate::protobuf_abs_path::ProtobufAbsPath;
use crate::protobuf_ident::ProtobufIdent;
use crate::pure::convert::option_resolver::OptionResoler;
use crate::pure::convert::option_resolver::ProtobufOptions;
use crate::pure::convert::type_resolver::MessageOrEnum;
use crate::pure::convert::type_resolver::TypeResolver;
use crate::pure::model;
use crate::FileDescriptorPair;
use crate::ProtobufAbsPathRef;
use crate::ProtobufIdentRef;
#[derive(Debug, thiserror::Error)]
enum ConvertError {
#[error("default value is not a string literal")]
DefaultValueIsNotStringLiteral,
#[error("expecting a message for name {0}")]
ExpectingMessage(ProtobufAbsPath),
#[error("expecting an enum for name {0}")]
ExpectingEnum(ProtobufAbsPath),
}
pub struct WithFullName<T> {
full_name: ProtobufAbsPath,
t: T,
}
#[derive(Debug, PartialEq)]
enum TypeResolved {
Int32,
Int64,
Uint32,
Uint64,
Sint32,
Sint64,
Bool,
Fixed64,
Sfixed64,
Double,
String,
Bytes,
Fixed32,
Sfixed32,
Float,
Message(ProtobufAbsPath),
Enum(ProtobufAbsPath),
Group(ProtobufAbsPath),
}
impl TypeResolved {
fn from_field(field: &FieldDescriptorProto) -> TypeResolved {
match field.type_() {
Type::TYPE_DOUBLE => TypeResolved::Double,
Type::TYPE_FLOAT => TypeResolved::Float,
Type::TYPE_INT64 => TypeResolved::Int64,
Type::TYPE_UINT64 => TypeResolved::Uint64,
Type::TYPE_INT32 => TypeResolved::Int32,
Type::TYPE_FIXED64 => TypeResolved::Fixed64,
Type::TYPE_FIXED32 => TypeResolved::Fixed32,
Type::TYPE_UINT32 => TypeResolved::Uint32,
Type::TYPE_SFIXED32 => TypeResolved::Sfixed32,
Type::TYPE_SFIXED64 => TypeResolved::Sfixed64,
Type::TYPE_SINT32 => TypeResolved::Sint32,
Type::TYPE_SINT64 => TypeResolved::Sint64,
Type::TYPE_BOOL => TypeResolved::Bool,
Type::TYPE_STRING => TypeResolved::String,
Type::TYPE_BYTES => TypeResolved::Bytes,
Type::TYPE_GROUP => {
assert!(!field.type_name().is_empty());
TypeResolved::Group(ProtobufAbsPath::new(field.type_name()))
}
Type::TYPE_ENUM => {
assert!(!field.type_name().is_empty());
TypeResolved::Enum(ProtobufAbsPath::new(field.type_name()))
}
Type::TYPE_MESSAGE => {
assert!(!field.type_name().is_empty());
TypeResolved::Message(ProtobufAbsPath::new(field.type_name()))
}
}
}
fn type_enum(&self) -> Type {
match self {
TypeResolved::Bool => Type::TYPE_BOOL,
TypeResolved::Int32 => Type::TYPE_INT32,
TypeResolved::Int64 => Type::TYPE_INT64,
TypeResolved::Uint32 => Type::TYPE_UINT32,
TypeResolved::Uint64 => Type::TYPE_UINT64,
TypeResolved::Sint32 => Type::TYPE_SINT32,
TypeResolved::Sint64 => Type::TYPE_SINT64,
TypeResolved::Fixed32 => Type::TYPE_FIXED32,
TypeResolved::Fixed64 => Type::TYPE_FIXED64,
TypeResolved::Sfixed32 => Type::TYPE_SFIXED32,
TypeResolved::Sfixed64 => Type::TYPE_SFIXED64,
TypeResolved::Float => Type::TYPE_FLOAT,
TypeResolved::Double => Type::TYPE_DOUBLE,
TypeResolved::String => Type::TYPE_STRING,
TypeResolved::Bytes => Type::TYPE_BYTES,
TypeResolved::Message(_) => Type::TYPE_MESSAGE,
TypeResolved::Enum(_) => Type::TYPE_ENUM,
TypeResolved::Group(_) => Type::TYPE_GROUP,
}
}
fn type_name(&self) -> Option<&ProtobufAbsPath> {
match self {
TypeResolved::Message(t) | TypeResolved::Enum(t) | TypeResolved::Group(t) => Some(t),
_ => None,
}
}
}
pub(crate) struct Resolver<'a> {
type_resolver: TypeResolver<'a>,
current_file: &'a model::FileDescriptor,
}
impl<'a> Resolver<'a> {
fn map_entry_name_for_field_name(field_name: &str) -> ProtobufIdent {
ProtobufIdent::from(format!("{}Entry", camel_case(field_name)))
}
fn map_entry_field(
&self,
scope: &ProtobufAbsPath,
name: &str,
number: i32,
field_type: &model::FieldType,
) -> anyhow::Result<protobuf::descriptor::FieldDescriptorProto> {
let mut output = protobuf::descriptor::FieldDescriptorProto::new();
output.set_name(name.to_owned());
output.set_number(number);
let t = self.field_type(&scope, name, field_type)?;
output.set_type(t.type_enum());
if let Some(t_name) = t.type_name() {
output.set_type_name(t_name.path.clone());
}
output.set_label(field_descriptor_proto::Label::LABEL_OPTIONAL);
output.set_json_name(json_name(&name));
Ok(output)
}
fn map_entry_message(
&self,
scope: &ProtobufAbsPath,
field_name: &str,
key: &model::FieldType,
value: &model::FieldType,
) -> anyhow::Result<protobuf::descriptor::DescriptorProto> {
let mut output = protobuf::descriptor::DescriptorProto::new();
output.options.mut_or_insert_default().set_map_entry(true);
output.set_name(Resolver::map_entry_name_for_field_name(field_name).into_string());
output
.field
.push(self.map_entry_field(&scope, "key", 1, key)?);
output
.field
.push(self.map_entry_field(&scope, "value", 2, value)?);
Ok(output)
}
fn group_message(
&self,
scope: &ProtobufAbsPath,
name: &str,
fields: &[model::WithLoc<model::Field>],
) -> anyhow::Result<protobuf::descriptor::DescriptorProto> {
let mut output = protobuf::descriptor::DescriptorProto::new();
output.set_name(name.to_owned());
for f in fields {
output.field.push(self.field(scope, f, None)?);
}
Ok(output)
}
fn message(
&self,
scope: &ProtobufAbsPathRef,
input: &model::Message,
) -> anyhow::Result<protobuf::descriptor::DescriptorProto> {
let mut nested_scope = scope.to_owned();
nested_scope.push_simple(ProtobufIdentRef::new(&input.name));
let mut output = protobuf::descriptor::DescriptorProto::new();
output.set_name(input.name.clone());
let mut nested_messages = Vec::new();
for m in &input.messages {
let message = self.message(&nested_scope, &m.t)?;
nested_messages.push(model::WithLoc {
t: message,
loc: m.loc,
});
}
for f in input.regular_fields_including_in_oneofs() {
match &f.t.typ {
model::FieldType::Map(t) => {
let message = self.map_entry_message(&nested_scope, &f.t.name, &t.0, &t.1)?;
nested_messages.push(model::WithLoc {
t: message,
loc: f.loc,
});
}
model::FieldType::Group(model::Group {
name: group_name,
fields,
..
}) => {
let message = self.group_message(&nested_scope, group_name, fields)?;
nested_messages.push(model::WithLoc {
t: message,
loc: f.loc,
});
}
_ => (),
}
}
nested_messages.sort_by_key(|m| m.loc);
output.nested_type = nested_messages
.into_iter()
.map(|model::WithLoc { t, .. }| t)
.collect();
output.enum_type = input
.enums
.iter()
.map(|e| self.enumeration(scope, e))
.collect::<Result<_, _>>()?;
{
let mut fields = Vec::new();
for fo in &input.fields {
match &fo.t {
model::FieldOrOneOf::Field(f) => {
let oneof_index = if self.is_proto3_optional(f) {
let oneof_index = output.oneof_decl.len() as i32;
let mut oneof = OneofDescriptorProto::new();
oneof.set_name(format!("_{}", f.name));
output.oneof_decl.push(oneof);
Some(oneof_index)
} else {
None
};
fields.push(self.field(&nested_scope, f, oneof_index)?);
}
model::FieldOrOneOf::OneOf(o) => {
let oneof_index = output.oneof_decl.len();
for f in &o.fields {
fields.push(self.field(&nested_scope, f, Some(oneof_index as i32))?);
}
output.oneof_decl.push(self.oneof(scope, o)?);
}
}
}
output.field = fields;
}
for ext in &input.extension_ranges {
let mut extension_range = protobuf::descriptor::descriptor_proto::ExtensionRange::new();
extension_range.set_start(ext.from);
extension_range.set_end(ext.to + 1);
output.extension_range.push(extension_range);
}
for ext in &input.extensions {
let mut extension = self.field(scope, &ext.t.field, None)?;
extension.set_extendee(
self.type_resolver
.resolve_message_or_enum(scope, &ext.t.extendee)?
.full_name
.path,
);
output.extension.push(extension);
}
for reserved in &input.reserved_nums {
let mut reserved_range = ReservedRange::new();
reserved_range.set_start(reserved.from);
reserved_range.set_end(reserved.to + 1);
output.reserved_range.push(reserved_range);
}
output.reserved_name = input.reserved_names.clone().into();
Ok(output)
}
fn service_method(
&self,
input: &model::Method,
) -> anyhow::Result<protobuf::descriptor::MethodDescriptorProto> {
let scope = &self.current_file.package;
let mut output = protobuf::descriptor::MethodDescriptorProto::new();
output.set_name(input.name.clone());
output.set_input_type(
self.type_resolver
.resolve_message_or_enum(scope, &input.input_type)?
.full_name
.to_string(),
);
output.set_output_type(
self.type_resolver
.resolve_message_or_enum(scope, &input.output_type)?
.full_name
.to_string(),
);
Ok(output)
}
fn service(
&self,
input: &model::Service,
) -> anyhow::Result<protobuf::descriptor::ServiceDescriptorProto> {
let mut output = protobuf::descriptor::ServiceDescriptorProto::new();
output.set_name(input.name.clone());
output.method = input
.methods
.iter()
.map(|m| self.service_method(m))
.collect::<Result<_, _>>()?;
Ok(output)
}
fn is_proto3_optional(&self, input: &model::WithLoc<model::Field>) -> bool {
(self.current_file.syntax, input.t.rule)
== (model::Syntax::Proto3, Some(model::Rule::Optional))
}
fn field(
&self,
scope: &ProtobufAbsPathRef,
input: &model::WithLoc<model::Field>,
oneof_index: Option<i32>,
) -> anyhow::Result<protobuf::descriptor::FieldDescriptorProto> {
let mut output = protobuf::descriptor::FieldDescriptorProto::new();
output.set_name(input.t.name.clone());
if let model::FieldType::Map(..) = input.t.typ {
output.set_label(protobuf::descriptor::field_descriptor_proto::Label::LABEL_REPEATED);
} else {
output.set_label(label(input.t.rule));
if self.is_proto3_optional(input) {
output.set_proto3_optional(true);
}
}
let t = self.field_type(scope, &input.t.name, &input.t.typ)?;
output.set_type(t.type_enum());
if let Some(t_name) = t.type_name() {
output.set_type_name(t_name.path.clone());
}
output.set_number(input.t.number);
if let Some(ref default) = input.t.options.as_slice().by_name("default") {
let default = match output.type_() {
protobuf::descriptor::field_descriptor_proto::Type::TYPE_STRING => {
if let &model::ProtobufConstant::String(ref s) = default {
s.decode_utf8()?
} else {
return Err(ConvertError::DefaultValueIsNotStringLiteral.into());
}
}
protobuf::descriptor::field_descriptor_proto::Type::TYPE_BYTES => {
if let &model::ProtobufConstant::String(ref s) = default {
let mut buf = String::new();
escape_bytes_to(&s.decode_bytes()?, &mut buf);
buf
} else {
return Err(ConvertError::DefaultValueIsNotStringLiteral.into());
}
}
_ => default.format(),
};
output.set_default_value(default);
}
if let Some(oneof_index) = oneof_index {
output.set_oneof_index(oneof_index);
}
if let Some(json_name) = input.t.options.as_slice().by_name_string("json_name")? {
output.set_json_name(json_name);
} else {
output.set_json_name(json_name(&input.t.name));
}
Ok(output)
}
fn find_message_by_abs_name(
&self,
abs_path: &ProtobufAbsPath,
) -> anyhow::Result<WithFullName<&'a model::Message>> {
let with_full_name = self
.type_resolver
.find_message_or_enum_by_abs_name(abs_path)?;
match with_full_name.t {
MessageOrEnum::Message(m) => Ok(WithFullName {
t: m,
full_name: with_full_name.full_name,
}),
MessageOrEnum::Enum(..) => Err(ConvertError::ExpectingMessage(abs_path.clone()).into()),
}
}
fn find_enum_by_abs_name(
&self,
abs_path: &ProtobufAbsPath,
) -> anyhow::Result<&'a model::Enumeration> {
match self
.type_resolver
.find_message_or_enum_by_abs_name(abs_path)?
.t
{
MessageOrEnum::Enum(e) => Ok(e),
MessageOrEnum::Message(..) => Err(ConvertError::ExpectingEnum(abs_path.clone()).into()),
}
}
fn field_type(
&self,
scope: &ProtobufAbsPathRef,
name: &str,
input: &model::FieldType,
) -> anyhow::Result<TypeResolved> {
Ok(match *input {
model::FieldType::Bool => TypeResolved::Bool,
model::FieldType::Int32 => TypeResolved::Int32,
model::FieldType::Int64 => TypeResolved::Int64,
model::FieldType::Uint32 => TypeResolved::Uint32,
model::FieldType::Uint64 => TypeResolved::Uint64,
model::FieldType::Sint32 => TypeResolved::Sint32,
model::FieldType::Sint64 => TypeResolved::Sint64,
model::FieldType::Fixed32 => TypeResolved::Fixed32,
model::FieldType::Fixed64 => TypeResolved::Fixed64,
model::FieldType::Sfixed32 => TypeResolved::Sfixed32,
model::FieldType::Sfixed64 => TypeResolved::Sfixed64,
model::FieldType::Float => TypeResolved::Float,
model::FieldType::Double => TypeResolved::Double,
model::FieldType::String => TypeResolved::String,
model::FieldType::Bytes => TypeResolved::Bytes,
model::FieldType::MessageOrEnum(ref name) => {
let t = self.type_resolver.resolve_message_or_enum(scope, &name)?;
match t.t {
MessageOrEnum::Message(..) => TypeResolved::Message(t.full_name),
MessageOrEnum::Enum(..) => TypeResolved::Enum(t.full_name),
}
}
model::FieldType::Map(..) => {
let mut type_name = scope.to_owned();
type_name.push_simple(&Resolver::map_entry_name_for_field_name(name));
TypeResolved::Message(type_name)
}
model::FieldType::Group(model::Group {
name: ref group_name,
..
}) => {
let mut type_name = scope.to_owned();
type_name.push_simple(ProtobufIdentRef::new(group_name));
TypeResolved::Group(type_name)
}
})
}
fn enum_value(
&self,
_scope: &ProtobufAbsPathRef,
input: &model::EnumValue,
) -> anyhow::Result<protobuf::descriptor::EnumValueDescriptorProto> {
let mut output = protobuf::descriptor::EnumValueDescriptorProto::new();
output.set_name(input.name.clone());
output.set_number(input.number);
Ok(output)
}
fn enumeration(
&self,
scope: &ProtobufAbsPathRef,
input: &model::Enumeration,
) -> anyhow::Result<protobuf::descriptor::EnumDescriptorProto> {
let mut output = protobuf::descriptor::EnumDescriptorProto::new();
output.set_name(input.name.clone());
output.value = input
.values
.iter()
.map(|v| self.enum_value(scope, &v))
.collect::<Result<_, _>>()?;
Ok(output)
}
fn oneof(
&self,
_scope: &ProtobufAbsPathRef,
input: &model::OneOf,
) -> anyhow::Result<protobuf::descriptor::OneofDescriptorProto> {
let mut output = protobuf::descriptor::OneofDescriptorProto::new();
output.set_name(input.name.clone());
Ok(output)
}
fn extension(
&self,
scope: &ProtobufAbsPath,
input: &model::Extension,
) -> anyhow::Result<(
protobuf::descriptor::FieldDescriptorProto,
Option<protobuf::descriptor::DescriptorProto>,
)> {
let mut field = self.field(scope, &input.field, None)?;
field.set_extendee(
self.type_resolver
.resolve_message_or_enum(scope, &input.extendee)?
.full_name
.to_string(),
);
let group_messages = if let model::FieldType::Group(g) = &input.field.t.typ {
Some(self.group_message(scope, &g.name, &g.fields)?)
} else {
None
};
Ok((field, group_messages))
}
}
fn syntax(input: model::Syntax) -> String {
match input {
model::Syntax::Proto2 => "proto2".to_owned(),
model::Syntax::Proto3 => "proto3".to_owned(),
}
}
fn label(input: Option<model::Rule>) -> protobuf::descriptor::field_descriptor_proto::Label {
match input {
Some(model::Rule::Optional) => {
protobuf::descriptor::field_descriptor_proto::Label::LABEL_OPTIONAL
}
Some(model::Rule::Required) => {
protobuf::descriptor::field_descriptor_proto::Label::LABEL_REQUIRED
}
Some(model::Rule::Repeated) => {
protobuf::descriptor::field_descriptor_proto::Label::LABEL_REPEATED
}
None => protobuf::descriptor::field_descriptor_proto::Label::LABEL_OPTIONAL,
}
}
pub(crate) fn populate_dependencies(
input: &model::FileDescriptor,
output: &mut protobuf::descriptor::FileDescriptorProto,
) {
for import in &input.imports {
if import.vis == model::ImportVis::Public {
output
.public_dependency
.push(output.dependency.len() as i32);
} else if import.vis == model::ImportVis::Weak {
output.weak_dependency.push(output.dependency.len() as i32);
}
output.dependency.push(import.path.to_string());
}
}
pub(crate) fn file_descriptor(
name: &ProtoPath,
input: &model::FileDescriptor,
deps: &[FileDescriptorPair],
) -> anyhow::Result<protobuf::descriptor::FileDescriptorProto> {
let resolver = Resolver {
current_file: &input,
type_resolver: TypeResolver {
current_file: &input,
deps,
},
};
let mut output = protobuf::descriptor::FileDescriptorProto::new();
output.set_name(fs_path_to_proto_path(name));
output.set_syntax(syntax(input.syntax));
if input.package != ProtobufAbsPath::root() {
output.set_package(input.package.to_root_rel().to_string());
}
populate_dependencies(&input, &mut output);
let mut messages = Vec::new();
let mut services = Vec::new();
let mut extensions = Vec::new();
for e in &input.extensions {
let (ext, group_messages) = resolver.extension(&resolver.current_file.package, &e.t)?;
extensions.push(ext);
messages.extend(group_messages.map(model::WithLoc::with_loc(e.loc)));
}
output.extension = extensions;
for m in &input.messages {
let message = resolver.message(&resolver.current_file.package, &m.t)?;
messages.push(model::WithLoc {
t: message,
loc: m.loc,
});
}
for s in &input.services {
let service = resolver.service(&s.t)?;
services.push(model::WithLoc {
t: service,
loc: s.loc,
})
}
messages.sort_by_key(|m| m.loc);
output.message_type = messages
.into_iter()
.map(|model::WithLoc { t, .. }| t)
.collect();
output.enum_type = input
.enums
.iter()
.map(|e| resolver.enumeration(&resolver.current_file.package, e))
.collect::<Result<_, _>>()?;
output.service = services
.into_iter()
.map(|model::WithLoc { t, .. }| t)
.collect();
let descriptor_without_options = FileDescriptor::new_dynamic(
output.clone(),
&deps
.iter()
.map(|d| d.descriptor.clone())
.collect::<Vec<_>>(),
)?;
let option_resolver = OptionResoler {
resolver: &resolver,
descriptor_without_options,
};
option_resolver.file(&mut output)?;
Ok(output)
}