Skip to content

Commit

Permalink
feat(rpc/generator): allow renaming and replacing types
Browse files Browse the repository at this point in the history
  • Loading branch information
nils-mathieu committed Oct 24, 2023
1 parent 2a66221 commit 21c1f17
Show file tree
Hide file tree
Showing 10 changed files with 189 additions and 7,918 deletions.
14 changes: 14 additions & 0 deletions crates/starknet-types-rpc/generator/example-fixes.toml
Original file line number Diff line number Diff line change
@@ -1 +1,15 @@
imports = ["stark_felt::Felt"]

removed-symbols = [
# Tagged Enums
"#/components/schemas/DECLARE_TXN_V0/DECLARE_TXN_V0/version",
"#/components/schemas/DECLARE_TXN_V1/DECLARE_TXN_V1/version",
"#/components/schemas/DECLARE_TXN_V2/DECLARE_TXN_V2/version",
]

[tagged-enums]
"#/components/schemas/DECLARE_TXN" = "version"

[renamed-symbols]
# Anonymous Structs
"#/components/schemas/BLOCK_BODY_WITH_TXS/transactions/ANONYMOUS/ANONYMOUS" = "TxnWithHash"
30 changes: 30 additions & 0 deletions crates/starknet-types-rpc/generator/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::collections::{BTreeMap, BTreeSet};

/// Configures the `method-name-constants` feature.
#[derive(Debug, Clone, serde::Deserialize)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
Expand Down Expand Up @@ -167,6 +169,34 @@ pub struct Fixes {
/// A list of imports that should be added at the top of the generated file.
#[serde(default)]
pub imports: Vec<String>,
/// A list of tagged enums that should be generated.
///
/// # Examples
///
/// ```
/// #[derive(Serialize, Deserialize)]
/// #[serde(tag = "version")]
/// struct Foo {
/// #[serde(rename = "0x0")]
/// V0(A),
/// #[serde(rename = "0x1")]
/// V1(B),
/// }
/// ```
///
/// # Notes
///
/// This does not remove the corresponding field in child structs.
pub tagged_enums: BTreeMap<String, String>,
/// A list of symbols to remove.
///
/// That may include types, consts, functions, fields, etc. Note that references to those
/// symbols will *not* be removed. Only the *definition* is removed.
pub removed_symbols: BTreeSet<String>,
/// A list of symbols to rename.
///
/// This is basically mandatory for anonymous types.
pub renamed_symbols: BTreeMap<String, String>,
}

/// Defines function that return the default value of some config fields.
Expand Down
38 changes: 28 additions & 10 deletions crates/starknet-types-rpc/generator/src/gen/general.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@ use super::Generator;
/// declared in the provided document.
pub fn generate(gen: &mut Generator) -> io::Result<()> {
for ty in &gen.spec.types {
if gen.fixes.removed_symbols.contains(&ty.path) {
continue;
}

let name = gen.fixes.renamed_symbols.get(&ty.path).unwrap_or(&ty.name);

match &ty.kind {
TypeKind::Struct(fields) => {
generate_struct(gen, &ty.path, &ty.name, ty.documentation.as_deref(), fields)?;
generate_struct(gen, &ty.path, name, ty.documentation.as_deref(), fields)?;
}
TypeKind::Enum(variants) => {
generate_enum(
gen,
&ty.path,
&ty.name,
ty.documentation.as_deref(),
variants,
)?;
generate_enum(gen, &ty.path, name, ty.documentation.as_deref(), variants)?;
}
_ => (),
}
Expand Down Expand Up @@ -57,14 +57,20 @@ fn generate_struct(
}
writeln!(gen, "pub struct {name} {{")?;
for field in fields {
if gen.fixes.removed_symbols.contains(&field.path) {
continue;
}

if gen.config.print_debug_path {
writeln!(gen, " // {}", field.path)?;
}
if let Some(doc) = &field.documentation {
writeln!(gen, " #[doc = r#\"{}\"#]", doc)?;
}

let ident = if &field.name == "type" {
let ident = if let Some(name) = gen.fixes.renamed_symbols.get(&field.path) {
name
} else if field.name == "type" {
"ty"
} else {
&field.name
Expand Down Expand Up @@ -120,16 +126,28 @@ fn generate_enum(
}
writeln!(gen, ")]")?;
}
let tag = gen.fixes.tagged_enums.get(path);
if let Some(tag) = tag {
writeln!(gen, "#[serde(tag = \"{}\")]", tag)?;
}
writeln!(gen, "pub enum {name} {{")?;
for variant in variants {
if gen.fixes.removed_symbols.contains(&variant.path) {
continue;
}

if gen.config.print_debug_path {
writeln!(gen, " // {}", variant.path)?;
}
if let Some(doc) = &variant.documentation {
writeln!(gen, " #[doc = r#\"{}\"#]", doc)?;
}

let ident = &variant.name;
let ident = gen
.fixes
.renamed_symbols
.get(&variant.path)
.unwrap_or(&variant.name);

write!(gen, " {ident}")?;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,26 @@ pub fn generate(gen: &mut Generator) -> io::Result<()> {
};

for method in &gen.spec.methods {
let ident = config
.strip_prefix
.as_ref()
.and_then(|prefix| method.name.strip_prefix(prefix))
.unwrap_or(&method.name)
.to_case(Case::ScreamingSnake);
if gen.fixes.removed_symbols.contains(&method.path) {
continue;
}

let ident = if let Some(renamed) = gen.fixes.renamed_symbols.get(&method.path) {
renamed.clone()
} else {
config
.strip_prefix
.as_ref()
.and_then(|prefix| method.name.strip_prefix(prefix))
.unwrap_or(&method.name)
.to_case(Case::ScreamingSnake)
};

let name = method.name.as_str();

if gen.config.print_debug_path {
writeln!(gen, "// {}", method.path)?;
}
writeln!(gen, "#[doc = r#\"`{name}`\"#]")?;
if let Some(flag) = &config.feature_flag {
writeln!(gen, "#[cfg(feature = \"{flag}\")]")?;
Expand Down
31 changes: 24 additions & 7 deletions crates/starknet-types-rpc/generator/src/gen/method_param_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,33 @@ pub fn generate(gen: &mut crate::gen::Generator) -> io::Result<()> {
};

for method in &gen.spec.methods {
let mut ident = config
.strip_prefix
.as_ref()
.and_then(|prefix| method.name.strip_prefix(prefix))
.unwrap_or(&method.name)
.to_case(Case::Pascal);
ident.push_str("Params");
if gen
.fixes
.removed_symbols
.contains(&format!("{}/params", method.path))
{
continue;
}

let ident = if let Some(renamed) = gen.fixes.renamed_symbols.get(&method.path) {
renamed.clone()
} else {
let mut ident = config
.strip_prefix
.as_ref()
.and_then(|prefix| method.name.strip_prefix(prefix))
.unwrap_or(&method.name)
.to_case(Case::Pascal);
ident.push_str("Params");

ident
};

let name = method.name.as_str();

if gen.config.print_debug_path {
writeln!(gen, "// {}/params", method.path)?;
}
writeln!(
gen,
"#[doc = r#\"The parameters of the `{name}` method.\"#]"
Expand Down
30 changes: 23 additions & 7 deletions crates/starknet-types-rpc/generator/src/gen/method_result_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,32 @@ pub fn generate(gen: &mut Generator) -> io::Result<()> {
};

for method in &gen.spec.methods {
let mut ident = config
.strip_prefix
.as_ref()
.and_then(|prefix| method.name.strip_prefix(prefix))
.unwrap_or(&method.name)
.to_case(Case::Pascal);
ident.push_str("Result");
if gen
.fixes
.removed_symbols
.contains(&format!("{}/result", method.path))
{
continue;
}

let ident = if let Some(renamed) = gen.fixes.renamed_symbols.get(&method.path) {
renamed.clone()
} else {
let mut ident = config
.strip_prefix
.as_ref()
.and_then(|prefix| method.name.strip_prefix(prefix))
.unwrap_or(&method.name)
.to_case(Case::Pascal);
ident.push_str("Result");
ident
};

let name = method.name.as_str();

if gen.config.print_debug_path {
writeln!(gen, "// {}/result", method.path)?;
}
writeln!(gen, "#[doc = r#\"The result of the `{name}` method.\"#]")?;
if let Some(flag) = &config.feature_flag {
writeln!(gen, "#[cfg(feature = \"{flag}\")]")?;
Expand Down
7 changes: 6 additions & 1 deletion crates/starknet-types-rpc/generator/src/gen/type_aliases.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ pub fn generate(gen: &mut Generator) -> io::Result<()> {
_ => continue,
};

let ident = &ty.name;
let ident = if let Some(renamed) = gen.fixes.renamed_symbols.get(&ty.path) {
renamed
} else {
&ty.name
};

let ref_ident = reference.ident;

if gen.config.print_debug_path {
Expand Down
59 changes: 38 additions & 21 deletions crates/starknet-types-rpc/generator/src/openrpc/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ pub fn parse_openrpc(openrpc: model::OpenRpc) -> Document {
types = components
.schemas
.into_iter()
.map(|(name, ty)| parse_type(&mut ctx, name, ty))
.map(|(name, ty)| parse_type(&mut ctx, &name, ty))
.collect();
ctx.pop_level();

Expand Down Expand Up @@ -165,8 +165,12 @@ fn parse_parameter(ctx: &mut ParsingContext, item: model::ContentDescriptor) ->

/// Computes the name of a parameter from its [`model::Schema`].
fn parse_type_ref(ctx: &mut ParsingContext, schema: model::Schema) -> TypeRef {
let ty = parse_type(ctx, "ANONYMOUS".into(), schema);
let ty = parse_type(ctx, "ANONYMOUS", schema);
create_ref_for_existing_def(ctx, ty)
}

/// Creates a reference to an existing type definition.
fn create_ref_for_existing_def(ctx: &mut ParsingContext, ty: TypeDefinition) -> TypeRef {
// If the parsed type is a newtype, we can just return the type directly to
// avoid creating one newtype per type reference.
if let TypeKind::Newtype(inner) = ty.kind {
Expand All @@ -180,8 +184,8 @@ fn parse_type_ref(ctx: &mut ParsingContext, schema: model::Schema) -> TypeRef {
}

/// Parses a [`model::Schema`] into a [`TypeKind`].
fn parse_type(ctx: &mut ParsingContext, name: String, schema: model::Schema) -> TypeDefinition {
ctx.push_level(&name);
fn parse_type(ctx: &mut ParsingContext, name: &str, schema: model::Schema) -> TypeDefinition {
ctx.push_level(name);

let kind = if let Some(path) = schema.reference {
TypeKind::Newtype(TypeRef::Reference(path))
Expand All @@ -201,11 +205,18 @@ fn parse_type(ctx: &mut ParsingContext, name: String, schema: model::Schema) ->
}
} else if let Some(s) = schema.complex {
match s {
model::ComplexSchema::OneOf(c) => TypeKind::Enum(parse_enum_variants(ctx, c)),
model::ComplexSchema::AllOf(c) => TypeKind::Struct(parse_flatten_struct(ctx, true, &c)),
model::ComplexSchema::AnyOf(c) => {
TypeKind::Struct(parse_flatten_struct(ctx, false, &c))
}
model::ComplexSchema::OneOf(c) => match &*c {
[val] => parse_type(ctx, name.clone(), val.clone()).kind,
_ => TypeKind::Enum(parse_enum_variants(ctx, c)),
},
model::ComplexSchema::AllOf(c) => match &*c {
[val] => parse_type(ctx, name.clone(), val.clone()).kind,
_ => TypeKind::Struct(parse_flatten_struct(ctx, true, &c)),
},
model::ComplexSchema::AnyOf(c) => match &*c {
[val] => parse_type(ctx, name.clone(), val.clone()).kind,
_ => TypeKind::Struct(parse_flatten_struct(ctx, false, &c)),
},
}
} else {
TypeKind::Newtype(TypeRef::Broken(ctx.current_level().into()))
Expand Down Expand Up @@ -289,34 +300,40 @@ fn parse_flatten_struct(
) -> Vec<StructField> {
parts
.iter()
.map(|s| {
.flat_map(|s| {
ctx.push_level(&s.title.as_deref().unwrap_or("unknown").to_case(Case::Snake));
let path = ctx.current_level().into();

let name = if let Some(title) = &s.title {
title.to_case(Case::Snake)
title
} else if let Some(reference) = &s.reference {
reference
.rsplit('/')
.next()
.unwrap_or(reference)
.to_case(Case::Snake)
reference.rsplit('/').next().unwrap_or(reference)
} else {
"unknown".into()
"unknown"
};

let ty = parse_type_ref(ctx, s.clone());
let ty = parse_type(ctx, name, s.clone());

// If the provided type is a struct, we can just move all the fields
// here since we're flattening the struct.
if let TypeKind::Struct(fields) = ty.kind {
ctx.pop_level();
return fields;
}

// If it's not possible, we have to create a ref.
let ty = create_ref_for_existing_def(ctx, ty);

ctx.pop_level();

StructField {
vec![StructField {
path,
name,
name: name.to_case(Case::Snake),
documentation: s.description.clone(),
ty,
required,
flatten: true,
}
}]
})
.collect()
}
Expand Down
Loading

0 comments on commit 21c1f17

Please sign in to comment.