wobbly dobbly impl of proto->wit

This commit is contained in:
Sandipsinh Rathod 2024-12-22 20:12:16 -05:00
parent 5801f4e724
commit f84b496cc1
8 changed files with 161 additions and 25 deletions

@ -4,11 +4,6 @@ package core.todo.v1;
message TodoListRequest {string user_id = 1;} message TodoListRequest {string user_id = 1;}
enum Error {
UNKNOWN = 0;
NOT_FOUND = 1;
}
message TodoAddRequest { message TodoAddRequest {
string user_id = 1; string user_id = 1;
string task = 2; string task = 2;

@ -74,7 +74,7 @@ pub struct Function {
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, MergeRight)] #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, MergeRight)]
pub struct ReturnTy { pub struct ReturnTy {
pub return_type: String, pub return_type: String,
pub error_type: String, pub error_type: Option<String>,
} }
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, MergeRight)] #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, MergeRight)]

@ -170,7 +170,7 @@ impl Function {
}; };
format!( format!(
"func {}({}){}", "{}: func({}){};",
generate_wit_name(&self.name), generate_wit_name(&self.name),
params, params,
return_type return_type
@ -180,14 +180,10 @@ impl Function {
impl ReturnTy { impl ReturnTy {
pub fn to_wit(&self) -> String { pub fn to_wit(&self) -> String {
if self.error_type.is_empty() { if let Some(err) = self.error_type.as_ref() {
self.return_type.clone() format!("result<{}, {}>", self.return_type, err)
} else { } else {
format!( format!("option<{}>", self.return_type)
"result<{}, {}>",
self.return_type,
self.error_type
)
} }
} }
} }

@ -40,6 +40,21 @@ pub enum WitType {
} }
impl WitType { impl WitType {
pub fn from_primitive_proto_type(proto_ty: &str) -> Valid<Self, anyhow::Error, anyhow::Error> {
let binding = proto_ty.to_lowercase();
let ty = binding.strip_prefix("type_").unwrap_or(proto_ty);
match ty {
"double" | "float" => Valid::succeed(WitType::Float64),
"int32" | "sint32" | "fixed32" | "sfixed32" => Valid::succeed(WitType::S32),
"int64" | "sint64" | "fixed64" | "sfixed64" => Valid::succeed(WitType::S64),
"uint32" => Valid::succeed(WitType::U32),
"uint64" => Valid::succeed(WitType::U64),
"bool" => Valid::succeed(WitType::Bool),
"string" => Valid::succeed(WitType::String),
// Ideally, this should never be reached
_ => Valid::fail(anyhow::anyhow!("Unknown/Complex type: {}", ty)),
}
}
pub fn from_schema( pub fn from_schema(
schema: &Schema, schema: &Schema,
openapi: &OpenApiSpec<Resolved>, openapi: &OpenApiSpec<Resolved>,

@ -0,0 +1,88 @@
use convert_case::Case;
use protox::prost_reflect::prost_types::{FileDescriptorSet, ServiceDescriptorProto};
use tailcall_valid::{Valid, Validator};
use crate::config::config::{Config, Function, Interface, Parameter, ReturnTy, UseStatement};
use convert_case::Casing;
use crate::proto::proto::process_ty;
fn handle_service(config: &Config, services: &[ServiceDescriptorProto]) -> Valid<Vec<Interface>, anyhow::Error, anyhow::Error> {
Valid::from_iter(services.iter(), |service| {
let name = service.name().to_case(Case::Kebab);
Valid::from_iter(service.method.iter(), |method| {
let name = method.name().to_case(Case::Kebab);
let input_ty = if let Some(input) = method.input_type.as_ref() {
process_ty(input).map(|v| Some(v))
} else {
Valid::succeed(None)
};
let output_ty = if let Some(output) = method.output_type.as_ref() {
process_ty(output).map(|v| Some(v))
} else {
Valid::succeed(None)
};
input_ty.zip(output_ty).map(|(a, b)| {
let mut parameters = vec![];
let mut return_type = ReturnTy {
return_type: "unit".to_string(),
// TODO: I am not yet sure about error type
error_type: None,
};
if let Some(a) = a {
// Protobuf only supports one input parameter,
// so we can assume that the name is "input"
parameters.push(Parameter {
name: "input".to_string(),
parameter_type: a.to_wit(None),
});
}
if let Some(b) = b {
return_type.return_type = b.to_wit(None);
}
Function {
name,
parameters,
return_type,
}
})
}).and_then(|functions| {
Valid::from_iter(config.interfaces.iter(), |interface| {
let use_name = interface.name.to_string();
let mut imports = vec![];
imports.extend(interface.records.iter().map(|v| v.name.to_string()));
imports.extend(interface.varients.iter().map(|(k, _)| k.to_string()));
Valid::succeed(
UseStatement {
name: use_name,
items: imports,
}
)
}).and_then(|uses| {
Valid::succeed(Interface {
name,
uses,
functions,
..Default::default()
})
})
})
})
}
pub fn handle_services(config: Config, proto: &[FileDescriptorSet]) -> Valid<Config, anyhow::Error, anyhow::Error> {
Valid::succeed(config)
.and_then(|mut config| {
Valid::from_iter(proto.iter(), |file| {
Valid::from_iter(file.file.iter(), |file| {
handle_service(&config, &file.service)
})
.and_then(|interfaces| {
config.interfaces.extend(interfaces.into_iter().flatten().collect::<Vec<_>>());
Valid::succeed(())
})
}).and_then(|_| Valid::succeed(config))
})
}

@ -1,24 +1,49 @@
use std::collections::{BTreeMap, BTreeSet}; use std::collections::{BTreeMap, BTreeSet};
use anyhow::anyhow;
use convert_case::Case;
use protox::prost_reflect::prost_types::{EnumDescriptorProto, FileDescriptorProto, FileDescriptorSet}; use protox::prost_reflect::prost_types::{DescriptorProto, EnumDescriptorProto, FileDescriptorProto, FileDescriptorSet};
use tailcall_valid::{Valid, Validator}; use tailcall_valid::{Valid, Validator};
use crate::config::config::{Config, Interface, Record}; use crate::config::config::{Config, Field, Interface, Record};
use crate::config::wit_types::WitType; use crate::config::wit_types::WitType;
use convert_case::Casing;
use crate::proto::proto::process_ty;
fn append_enums(_: &Config, file: &[EnumDescriptorProto]) -> Valid<BTreeMap<String, WitType>, anyhow::Error, anyhow::Error> { fn append_enums(file: &[EnumDescriptorProto]) -> Valid<BTreeMap<String, WitType>, anyhow::Error, anyhow::Error> {
Valid::from_iter(file.iter().enumerate(), |(i, enum_)| { Valid::from_iter(file.iter().enumerate(), |(i, enum_)| {
let enum_name = enum_.name(); let enum_name = enum_.name().to_case(Case::Kebab);
Valid::from_iter(enum_.value.iter().enumerate(), |(j, value)| { Valid::from_iter(enum_.value.iter().enumerate(), |(j, value)| {
Valid::succeed(value.name().to_string()) Valid::succeed(value.name().to_case(Case::Kebab))
}).and_then(|varients| { }).and_then(|varients| {
Valid::succeed((enum_name.to_string(), WitType::Enum(varients))) Valid::succeed((enum_name, WitType::Enum(varients)))
}) })
}).and_then(|rec| Valid::succeed(rec.into_iter().collect())) }).and_then(|rec| Valid::succeed(rec.into_iter().collect()))
} }
fn append_message(config: &Config, file: &FileDescriptorProto) -> Valid<Vec<Record>, anyhow::Error, anyhow::Error> { fn append_message(messages: &[DescriptorProto]) -> Valid<Vec<Record>, anyhow::Error, anyhow::Error> {
Valid::succeed(vec![]) Valid::from_iter(messages.iter(), |message| {
let record_name = message.name().to_case(Case::Kebab);
Valid::from_iter(message.field.iter().enumerate(), |(i, field)| {
if let Some(ty_) = field.type_name.as_ref() {
process_ty(ty_).map(|ty| (field.name().to_case(Case::Kebab), ty))
} else {
Valid::from(WitType::from_primitive_proto_type(field.r#type().as_str_name()))
.map(|ty| (field.name().to_case(Case::Kebab), ty))
}.and_then(|(name, ty)| {
Valid::succeed(Field {
name,
field_type: ty,
})
})
}).and_then(|fields| {
Valid::succeed(Record {
name: record_name,
fields: fields.into_iter().collect(),
..Default::default()
})
})
})
} }
pub fn handle_types(config: Config, proto: &[FileDescriptorSet], package: String) -> Valid<Config, anyhow::Error, anyhow::Error> { pub fn handle_types(config: Config, proto: &[FileDescriptorSet], package: String) -> Valid<Config, anyhow::Error, anyhow::Error> {
@ -28,8 +53,8 @@ pub fn handle_types(config: Config, proto: &[FileDescriptorSet], package: String
let mut map = BTreeMap::new(); let mut map = BTreeMap::new();
let mut records = BTreeSet::new(); let mut records = BTreeSet::new();
Valid::from_iter(set.file.iter(), |file| { Valid::from_iter(set.file.iter(), |file| {
append_enums(&config, &file.enum_type).and_then(|varients| { append_enums(&file.enum_type).and_then(|varients| {
append_message(&config, file) append_message(&file.message_type)
.and_then(|recs| { .and_then(|recs| {
map.extend(varients); map.extend(varients);
records.extend(BTreeSet::from_iter(recs.into_iter())); records.extend(BTreeSet::from_iter(recs.into_iter()));

@ -1,2 +1,3 @@
mod proto; mod proto;
mod handle_types; mod handle_types;
mod handle_services;

@ -1,6 +1,9 @@
use anyhow::anyhow;
use protox::prost_reflect::prost_types::FileDescriptorSet; use protox::prost_reflect::prost_types::FileDescriptorSet;
use tailcall_valid::{Valid, Validator}; use tailcall_valid::{Valid, Validator};
use crate::config::config::Config; use crate::config::config::Config;
use crate::config::wit_types::WitType;
use crate::proto::handle_services::handle_services;
use crate::proto::handle_types::handle_types; use crate::proto::handle_types::handle_types;
pub struct Proto(Vec<FileDescriptorSet>); pub struct Proto(Vec<FileDescriptorSet>);
@ -12,6 +15,19 @@ impl Proto {
pub fn to_config(&self) -> Valid<Config, anyhow::Error, anyhow::Error> { pub fn to_config(&self) -> Valid<Config, anyhow::Error, anyhow::Error> {
Valid::succeed(Config::default()) Valid::succeed(Config::default())
.and_then(|config| handle_types(config, &self.0, "api:todos@1.0.0".to_string())) .and_then(|config| handle_types(config, &self.0, "api:todos@1.0.0".to_string()))
.and_then(|config| handle_services(config, &self.0))
}
}
pub fn process_ty(name: &str) -> Valid<WitType, anyhow::Error, anyhow::Error> {
if !name.starts_with('.') {
return Valid::fail(anyhow!("Expected fully-qualified name for reference type but got {name}. This is a bug!"));
}
let name = &name[1..];
if let Some((_package, name)) = name.rsplit_once('.') {
Valid::succeed(WitType::FieldTy(name.to_string()))
}else {
Valid::succeed(WitType::FieldTy(name.to_string()))
} }
} }