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

Approach to handle serde(rename) references. #210

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ use typeshare_core::{
TypeScript,
},
parser::ParsedData,
reconcile::reconcile_aliases,
};

use crate::{
Expand All @@ -46,8 +47,6 @@ fn main() -> anyhow::Result<()> {

let options = Args::parse();

info!("typeshare started generating types");

if let Some(options) = options.subcommand {
match options {
Command::Completions { shell } => {
Expand All @@ -68,6 +67,8 @@ fn main() -> anyhow::Result<()> {
return Ok(());
}

info!("typeshare started generating types");

let config = config::load_config(config_file).context("Unable to read configuration file")?;
let config = override_configuration(config, &options)?;

Expand Down Expand Up @@ -146,11 +147,13 @@ fn main() -> anyhow::Result<()> {
// and implement a `ParallelVisitor` that builds up the mapping of parsed
// data. That way both walking and parsing are in parallel.
// https://docs.rs/ignore/latest/ignore/struct.WalkParallel.html
let crate_parsed_data = parse_input(
let mut crate_parsed_data = parse_input(
parser_inputs(walker_builder, language_type, multi_file).par_bridge(),
&parse_context,
)?;

reconcile_aliases(&mut crate_parsed_data);

// Collect all the types into a map of the file name they
// belong too and the list of type names. Used for generating
// imports in generated files.
Expand Down
9 changes: 5 additions & 4 deletions cli/src/parse.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Source file parsing.
use anyhow::Context;
use ignore::WalkBuilder;
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use rayon::iter::ParallelIterator;
use std::{
collections::{hash_map::Entry, BTreeMap, HashMap},
path::PathBuf,
Expand Down Expand Up @@ -93,7 +93,6 @@ pub fn parse_input(
parse_context: &ParseContext,
) -> anyhow::Result<BTreeMap<CrateName, ParsedData>> {
inputs
.into_par_iter()
.try_fold(
BTreeMap::new,
|mut parsed_crates: BTreeMap<CrateName, ParsedData>,
Expand All @@ -102,17 +101,19 @@ pub fn parse_input(
file_name,
crate_name,
}| {
let fp = file_path.as_os_str().to_str().unwrap_or("").to_string();

let parse_file_context = ParseFileContext {
source_code: std::fs::read_to_string(&file_path)
.with_context(|| format!("Failed to read input: {file_name}"))?,
.with_context(|| format!("Failed to read input: {file_path:?}"))?,
crate_name: crate_name.clone(),
file_name: file_name.clone(),
file_path,
};

let parsed_result =
typeshare_core::parser::parse(parse_context, parse_file_context)
.with_context(|| format!("Failed to parse: {file_name}"))?;
.with_context(|| format!("Failed to parse: {fp}"))?;

if let Some(parsed_data) = parsed_result {
parsed_crates
Expand Down
2 changes: 1 addition & 1 deletion core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ The test suite can of course be run normally without updating any expectations:
cargo test -p typeshare-core
```

If you find yourself needing to update expectations for a specific test only, run the following (subsituting the name of your test in for the last arg):
If you find yourself needing to update expectations for a specific test only, run the following (substituting the name of your test in for the last arg):

```
env UPDATE_EXPECT=1 cargo test -p typeshare-core --test snapshot_tests -- can_handle_serde_rename_all::swift
Expand Down
27 changes: 27 additions & 0 deletions core/data/tests/serde_rename_references/input.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//! Test references to a type that has been renamed via serde(rename)
//!

#[derive(Serialize)]
#[serde(rename = "SomethingFoo")]
#[typeshare]
pub enum Foo {
A,
}

#[derive(Serialize)]
#[typeshare]
#[serde(tag = "type", content = "value")]
pub enum Parent {
B(Foo),
}

#[derive(Serialize)]
#[typeshare]
pub struct Test {
field1: Foo,
field2: Option<Foo>,
}

#[derive(Serialize)]
#[typeshare]
pub type AliasTest = Vec<Foo>;
68 changes: 68 additions & 0 deletions core/data/tests/serde_rename_references/output.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package proto

import "encoding/json"

type AliasTest []SomethingFoo

type Test struct {
Field1 SomethingFoo `json:"field1"`
Field2 *SomethingFoo `json:"field2,omitempty"`
}
type Foo string
const (
FooA Foo = "A"
)
type ParentTypes string
const (
ParentTypeVariantB ParentTypes = "B"
)
type Parent struct{
Type ParentTypes `json:"type"`
value interface{}
}

func (p *Parent) UnmarshalJSON(data []byte) error {
var enum struct {
Tag ParentTypes `json:"type"`
Content json.RawMessage `json:"value"`
}
if err := json.Unmarshal(data, &enum); err != nil {
return err
}

p.Type = enum.Tag
switch p.Type {
case ParentTypeVariantB:
var res SomethingFoo
p.value = &res

}
if err := json.Unmarshal(enum.Content, &p.value); err != nil {
return err
}

return nil
}

func (p Parent) MarshalJSON() ([]byte, error) {
var enum struct {
Tag ParentTypes `json:"type"`
Content interface{} `json:"value,omitempty"`
}
enum.Tag = p.Type
enum.Content = p.value
return json.Marshal(enum)
}

func (p Parent) B() SomethingFoo {
res, _ := p.value.(*SomethingFoo)
return *res
}

func NewParentTypeVariantB(content SomethingFoo) Parent {
return Parent{
Type: ParentTypeVariantB,
value: &content,
}
}

26 changes: 26 additions & 0 deletions core/data/tests/serde_rename_references/output.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.agilebits.onepassword

import kotlinx.serialization.Serializable
import kotlinx.serialization.SerialName

typealias AliasTest = List<SomethingFoo>

@Serializable
data class Test (
val field1: SomethingFoo,
val field2: SomethingFoo? = null
)

@Serializable
enum class SomethingFoo(val string: String) {
@SerialName("A")
A("A"),
}

@Serializable
sealed class Parent {
@Serializable
@SerialName("B")
data class B(val value: SomethingFoo): Parent()
}

33 changes: 33 additions & 0 deletions core/data/tests/serde_rename_references/output.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.agilebits

package object onepassword {

type AliasTest = Vector[SomethingFoo]

}
package onepassword {

case class Test (
field1: SomethingFoo,
field2: Option[SomethingFoo] = None
)

sealed trait SomethingFoo {
def serialName: String
}
object SomethingFoo {
case object A extends SomethingFoo {
val serialName: String = "A"
}
}

sealed trait Parent {
def serialName: String
}
object Parent {
case class B(value: SomethingFoo) extends Parent {
val serialName: String = "B"
}
}

}
52 changes: 52 additions & 0 deletions core/data/tests/serde_rename_references/output.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import Foundation

public typealias AliasTest = [SomethingFoo]

public struct Test: Codable {
public let field1: SomethingFoo
public let field2: SomethingFoo?

public init(field1: SomethingFoo, field2: SomethingFoo?) {
self.field1 = field1
self.field2 = field2
}
}

public enum SomethingFoo: String, Codable {
case a = "A"
}

public enum Parent: Codable {
case b(SomethingFoo)

enum CodingKeys: String, CodingKey, Codable {
case b = "B"
}

private enum ContainerCodingKeys: String, CodingKey {
case type, value
}

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: ContainerCodingKeys.self)
if let type = try? container.decode(CodingKeys.self, forKey: .type) {
switch type {
case .b:
if let content = try? container.decode(SomethingFoo.self, forKey: .value) {
self = .b(content)
return
}
}
}
throw DecodingError.typeMismatch(Parent.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for Parent"))
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: ContainerCodingKeys.self)
switch self {
case .b(let content):
try container.encode(CodingKeys.b, forKey: .type)
try container.encode(content, forKey: .value)
}
}
}
14 changes: 14 additions & 0 deletions core/data/tests/serde_rename_references/output.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export type AliasTest = SomethingFoo[];

export interface Test {
field1: SomethingFoo;
field2?: SomethingFoo;
}

export enum SomethingFoo {
A = "A",
}

export type Parent =
| { type: "B", value: SomethingFoo };

1 change: 1 addition & 0 deletions core/src/language/kotlin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ impl Language for Kotlin {
id: Id {
original: String::from("value"),
renamed: String::from("value"),
serde_rename: false,
},
ty: ty.r#type.clone(),
comments: vec![],
Expand Down
1 change: 1 addition & 0 deletions core/src/language/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,7 @@ pub trait Language {
id: Id {
original: struct_name.clone(),
renamed: struct_name.clone(),
serde_rename: false
},
fields: fields.clone(),
generic_types,
Expand Down
1 change: 1 addition & 0 deletions core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub mod context;
pub mod language;
/// Parsing Rust code into a format the `language` modules can understand
pub mod parser;
pub mod reconcile;
mod rename;
/// Codifying Rust types and how they convert to various languages.
pub mod rust_types;
Expand Down
Loading
Loading