Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support serde flatten for Typescript #115

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
3 changes: 2 additions & 1 deletion cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,12 +257,13 @@ fn main() {
.filter_map(|dir_entry| dir_entry.path().to_str().map(String::from))
.collect();

let supports_flatten = lang.supports_flatten();
let mut generated_contents = vec![];
let parsed_data = glob_paths
.par_iter()
.map(|filepath| {
let data = std::fs::read_to_string(filepath).unwrap();
let parsed_data = typeshare_core::parser::parse(&data);
let parsed_data = typeshare_core::parser::parse(&data, supports_flatten);
if parsed_data.is_err() {
panic!("{}", parsed_data.err().unwrap());
}
Expand Down
4 changes: 4 additions & 0 deletions core/src/language/go.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,10 @@ impl Language for Go {

writeln!(w, "}}")
}

fn supports_flatten(&self) -> bool {
false
}
}

impl Go {
Expand Down
4 changes: 4 additions & 0 deletions core/src/language/kotlin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,10 @@ impl Language for Kotlin {

writeln!(w, "}}\n")
}

fn supports_flatten(&self) -> bool {
false
}
}

impl Kotlin {
Expand Down
2 changes: 2 additions & 0 deletions core/src/language/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,4 +294,6 @@ pub trait Language {

Ok(())
}
/// whether `#[serde(flatten)]` macro attribute is supported or not
fn supports_flatten(&self) -> bool;
}
4 changes: 4 additions & 0 deletions core/src/language/scala.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,10 @@ impl Language for Scala {
self.write_enum_variants(w, e)?;
writeln!(w, "}}\n")
}

fn supports_flatten(&self) -> bool {
false
}
}

impl Scala {
Expand Down
4 changes: 4 additions & 0 deletions core/src/language/swift.rs
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,10 @@ impl Language for Swift {

writeln!(w, "}}")
}

fn supports_flatten(&self) -> bool {
false
}
}

impl Swift {
Expand Down
30 changes: 28 additions & 2 deletions core/src/language/typescript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,13 +110,32 @@ impl Language for TypeScript {

fn write_struct(&mut self, w: &mut dyn Write, rs: &RustStruct) -> std::io::Result<()> {
self.write_comments(w, 0, &rs.comments)?;
let mut inheritance = "".to_string();
let mut count = 0;
for field in rs.fields.iter() {
if field.flattened {
let ts_ty = self
.format_type(&field.ty, &rs.generic_types)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?;
if count >= 1 {
inheritance.push_str(", ");
}
inheritance.push_str(ts_ty.as_str());
count += 1;
}
}
writeln!(
w,
"export interface {}{} {{",
"export interface {}{}{} {{",
rs.id.renamed,
(!rs.generic_types.is_empty())
.then(|| format!("<{}>", rs.generic_types.join(", ")))
.unwrap_or_default()
.unwrap_or_default(),
if !inheritance.is_empty() {
format!(" extends {inheritance}")
} else {
"".to_string()
}
)?;

rs.fields
Expand Down Expand Up @@ -160,6 +179,10 @@ impl Language for TypeScript {
}
}
}

fn supports_flatten(&self) -> bool {
true
}
}

impl TypeScript {
Expand Down Expand Up @@ -230,6 +253,9 @@ impl TypeScript {
field: &RustField,
generic_types: &[String],
) -> std::io::Result<()> {
if field.flattened {
return Ok(());
}
self.write_comments(w, 1, &field.comments)?;
let ts_ty = self
.format_type(&field.ty, generic_types)
Expand Down
2 changes: 1 addition & 1 deletion core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ pub fn process_input(
language: &mut dyn Language,
out: &mut dyn Write,
) -> Result<(), ProcessInputError> {
let parsed_data = parser::parse(input)?;
let parsed_data = parser::parse(input, language.supports_flatten())?;
language.generate_types(out, &parsed_data)?;
Ok(())
}
20 changes: 14 additions & 6 deletions core/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ pub enum ParseError {
}

/// Parse the given Rust source string into `ParsedData`.
pub fn parse(input: &str) -> Result<ParsedData, ParseError> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

supports_flatten shouldn't be something the parser needs to worry about; it should unconditionally emit the fully parsed data (including #[serde(flatten)] attributes), and then that parsed data can be rejected by individual languages when the output is generated, if necessary.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds fair, but concretely just wondering how you would see it implemented.

pub fn parse(input: &str, supports_flatten: bool) -> Result<ParsedData, ParseError> {
let mut parsed_data = ParsedData::default();

// We will only produce output for files that contain the `#[typeshare]`
Expand All @@ -94,7 +94,7 @@ pub fn parse(input: &str) -> Result<ParsedData, ParseError> {
for item in flatten_items(source.items.iter()) {
match item {
syn::Item::Struct(s) if has_typeshare_annotation(&s.attrs) => {
parsed_data.push_rust_thing(parse_struct(s)?);
parsed_data.push_rust_thing(parse_struct(s, supports_flatten)?);
}
syn::Item::Enum(e) if has_typeshare_annotation(&e.attrs) => {
parsed_data.push_rust_thing(parse_enum(e)?);
Expand Down Expand Up @@ -131,7 +131,7 @@ fn flatten_items<'a>(
///
/// This function can currently return something other than a struct, which is a
/// hack.
fn parse_struct(s: &ItemStruct) -> Result<RustItem, ParseError> {
fn parse_struct(s: &ItemStruct, supports_flatten: bool) -> Result<RustItem, ParseError> {
let serde_rename_all = serde_rename_all(&s.attrs);

let generic_types = s
Expand All @@ -156,6 +156,7 @@ fn parse_struct(s: &ItemStruct) -> Result<RustItem, ParseError> {
}));
}

let mut flattened: bool = false;
Ok(match &s.fields {
// Structs
Fields::Named(f) => {
Expand All @@ -170,9 +171,14 @@ fn parse_struct(s: &ItemStruct) -> Result<RustItem, ParseError> {
RustType::try_from(&f.ty)?
};

if serde_flatten(&f.attrs) {
return Err(ParseError::SerdeFlattenNotAllowed);
}
flattened = if serde_flatten(&f.attrs) {
if !supports_flatten {
return Err(ParseError::SerdeFlattenNotAllowed);
}
true
} else {
false
};

let has_default = serde_default(&f.attrs);
let decorators = get_field_decorators(&f.attrs);
Expand All @@ -183,6 +189,7 @@ fn parse_struct(s: &ItemStruct) -> Result<RustItem, ParseError> {
comments: parse_comment_attrs(&f.attrs),
has_default,
decorators,
flattened,
})
})
.collect::<Result<_, ParseError>>()?;
Expand Down Expand Up @@ -380,6 +387,7 @@ fn parse_enum_variant(
comments: parse_comment_attrs(&f.attrs),
has_default,
decorators,
flattened: false,
})
})
.collect::<Result<Vec<_>, ParseError>>()?,
Expand Down
3 changes: 3 additions & 0 deletions core/src/rust_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ pub struct RustField {
/// Language-specific decorators assigned to a given field.
/// The keys are language names (e.g. SupportedLanguage::TypeScript), the values are decorators (e.g. readonly)
pub decorators: HashMap<SupportedLanguage, HashSet<String>>,
/// Whether the field should be flattened or not,
/// as per `#[serde(flatten)]` definition.
pub flattened: bool,
}

/// A Rust type.
Expand Down
2 changes: 1 addition & 1 deletion core/tests/snapshot_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ fn check(
)?;

let mut typeshare_output: Vec<u8> = Vec::new();
let parsed_data = typeshare_core::parser::parse(&rust_input)?;
let parsed_data = typeshare_core::parser::parse(&rust_input, lang.supports_flatten())?;
lang.generate_types(&mut typeshare_output, &parsed_data)?;

let typeshare_output = String::from_utf8(typeshare_output)?;
Expand Down