Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
dc6a467
Add support for byte translation
MOmarMiraj Feb 18, 2025
1b0693d
fmt fix
MOmarMiraj Feb 18, 2025
5bc8645
instead of hardcoding type, use the value inside the typeshare.toml
MOmarMiraj Feb 18, 2025
d24601d
Reduce code dupe and throw it in a function
MOmarMiraj Feb 18, 2025
3ff5cec
add todo and comments
MOmarMiraj Feb 18, 2025
8c07d74
update python and typescript replacer/reviver code as well as update …
MOmarMiraj Feb 19, 2025
99dcebe
fix logic in reviver/replacer as welll as improve logic to include fu…
MOmarMiraj Feb 19, 2025
ca2933c
remove unneeded comment
MOmarMiraj Feb 19, 2025
5282554
turn is_bytes false after its turned on
MOmarMiraj Feb 19, 2025
a101237
update python serialization to a cleaner format and on a field level …
MOmarMiraj Feb 19, 2025
1a14865
fix is_bytes logic
MOmarMiraj Feb 19, 2025
ebee1ed
remove unneeded function
MOmarMiraj Feb 19, 2025
ea56003
change to Vec<u8> for .get() for better readability
MOmarMiraj Feb 19, 2025
f7bd889
fix typeshare printing for all values
MOmarMiraj Feb 19, 2025
5619a59
fix spacing and logic for python and js
MOmarMiraj Feb 19, 2025
784fb62
fix typeshare names add spaces between function and make python custo…
MOmarMiraj Feb 20, 2025
396b58a
change to doc comment
MOmarMiraj Feb 20, 2025
7165acd
add spaces between functions
MOmarMiraj Feb 20, 2025
1b7f297
refactor python parsing logic and update test cases
MOmarMiraj Feb 21, 2025
3739f44
remove todo link in go where its not neeeded
MOmarMiraj Feb 21, 2025
71958f6
update test as its suppose to be isInteger not IsInteger
MOmarMiraj Feb 21, 2025
8b64a32
remove nested id function and impl display traits for special rust ty…
MOmarMiraj Feb 21, 2025
f6c1901
make code more cleaner and fix some edge cases
MOmarMiraj Feb 21, 2025
f79fcaa
remove unnecessary diff
MOmarMiraj Feb 21, 2025
5c45105
remove unnecessary diff
MOmarMiraj Feb 21, 2025
dd318a0
update python to be more generic when acquiring custom translation types
MOmarMiraj Feb 21, 2025
b92746e
update python to have JSON in custom translation logic, remove unnces…
MOmarMiraj Feb 21, 2025
caca8ce
update ts comment
MOmarMiraj Feb 21, 2025
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
5 changes: 5 additions & 0 deletions core/data/tests/test_byte_translation/input.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#[typeshare]
#[serde(rename_all = "camelCase")]
pub struct Foo {
pub bytes: Vec<u8>,
}
7 changes: 7 additions & 0 deletions core/data/tests/test_byte_translation/output.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package proto

import "encoding/json"

type Foo struct {
Bytes []byte `json:"bytes"`
}
18 changes: 18 additions & 0 deletions core/data/tests/test_byte_translation/output.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from __future__ import annotations

from pydantic import BaseModel, field_serializer, field_validator


class Foo(BaseModel):
bytes: bytes


@field_serializer("content")
def serialize_data(self, value: bytes) -> list[int]:
return list(value)

@field_validator("content", mode="before")
def deserialize_data(cls, value):
if isinstance(value, list):
return bytes(value)
return value
18 changes: 18 additions & 0 deletions core/data/tests/test_byte_translation/output.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export interface Foo {
bytes: Uint8Array;
}

// Reviver code - required for JSON deserialization
export function TypeshareReviver(key: string, value: unknown): unknown {
return Array.isArray(value) && value.every(Number.isFinite)
? new Uint8Array(value)
: value;
}

// Replacer code - required for JSON serialization
export function TypeshareReplacer(key: string, value: unknown): unknown {
if (value instanceof Uint8Array) {
return Array.from(value);
}
return value;
}
9 changes: 9 additions & 0 deletions core/data/tests/test_serde_iso8601/output.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from __future__ import annotations

from datetime import datetime
from pydantic import BaseModel


class Foo(BaseModel):
time: datetime

13 changes: 9 additions & 4 deletions core/src/language/go.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::io::Write;

use crate::language::SupportedLanguage;
use crate::language::{get_vec_u8_conversion, SupportedLanguage};
use crate::parser::ParsedData;
use crate::rename::RenameExt;
use crate::rust_types::{RustConst, RustConstExpr, RustItem, RustTypeFormatError, SpecialRustType};
Expand Down Expand Up @@ -119,7 +119,14 @@ impl Language for Go {
generic_types: &[String],
) -> Result<String, RustTypeFormatError> {
Ok(match special_ty {
SpecialRustType::Vec(rtype) => format!("[]{}", self.format_type(rtype, generic_types)?),
SpecialRustType::Vec(rtype) => {
// TODO: https://github.com/1Password/typeshare/issues/231
if let Some(conversion) = get_vec_u8_conversion(special_ty, self.type_map(), rtype)
{
return Ok(conversion);
}
format!("[]{}", self.format_type(rtype, generic_types)?)
}
SpecialRustType::Array(rtype, len) => {
format!("[{}]{}", len, self.format_type(rtype, generic_types)?)
}
Expand Down Expand Up @@ -491,14 +498,12 @@ func ({short_name} {full_name}) MarshalJSON() ([]byte, error) {{
}

write_comments(w, 1, &field.comments)?;

let type_name = match field.type_override(SupportedLanguage::Go) {
Some(type_override) => type_override.to_owned(),
None => self
.format_type(&field.ty, generic_types)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?,
};

let go_type = self.acronyms_to_uppercase(&type_name);
let is_optional = field.ty.is_optional() || field.has_default;
let formatted_renamed_id = format!("{:?}", &field.id.renamed);
Expand Down
14 changes: 14 additions & 0 deletions core/src/language/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,20 @@ fn used_imports<'a, 'b: 'a>(
}
used_imports
}
/// this function will acquire the byte translation for the target language that is located inside
/// the typeshare.toml
fn get_vec_u8_conversion(
special_type: &SpecialRustType,
type_mappings: &HashMap<String, String>,
inner_type: &RustType,
) -> Option<String> {
let type_key = format!("{}<{}>", special_type.id(), inner_type.id());

if inner_type.contains_type(SpecialRustType::U8.id()) {
return type_mappings.get(&type_key).map(ToString::to_string);
}
None
}

#[cfg(test)]
mod test {
Expand Down
44 changes: 38 additions & 6 deletions core/src/language/python.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use std::hash::Hash;
use std::sync::OnceLock;
use std::{collections::HashMap, io::Write};

use super::CrateTypes;
use super::{get_vec_u8_conversion, CrateTypes};

use convert_case::{Case, Casing};

Expand Down Expand Up @@ -137,6 +137,7 @@ impl Language for Python {
parameters: &[RustType],
generic_types: &[String],
) -> Result<String, RustTypeFormatError> {
self.add_imports(base);
if let Some(mapped) = self.type_map().get(base) {
Ok(mapped.into())
} else {
Expand Down Expand Up @@ -174,9 +175,16 @@ impl Language for Python {
generic_types: &[String],
) -> Result<String, RustTypeFormatError> {
match special_ty {
SpecialRustType::Vec(rtype)
| SpecialRustType::Array(rtype, _)
| SpecialRustType::Slice(rtype) => {
SpecialRustType::Vec(rtype) => {
// TODO: https://github.com/1Password/typeshare/issues/231
if let Some(conversion) = get_vec_u8_conversion(special_ty, self.type_map(), rtype)
{
return Ok(conversion);
}
self.add_import("typing".to_string(), "List".to_string());
Ok(format!("List[{}]", self.format_type(rtype, generic_types)?))
}
SpecialRustType::Array(rtype, _) | SpecialRustType::Slice(rtype) => {
self.add_import("typing".to_string(), "List".to_string());
Ok(format!("List[{}]", self.format_type(rtype, generic_types)?))
}
Expand Down Expand Up @@ -267,6 +275,7 @@ impl Language for Python {
}

fn write_struct(&mut self, w: &mut dyn Write, rs: &RustStruct) -> std::io::Result<()> {
self.add_import("pydantic".to_string(), "BaseModel".to_string());
{
rs.generic_types
.iter()
Expand Down Expand Up @@ -294,8 +303,31 @@ impl Language for Python {
write!(w, " pass")?
}
writeln!(w)?;
self.add_import("pydantic".to_string(), "BaseModel".to_string());
Ok(())
rs.fields.iter().try_for_each(|field| {
let python_type = self
.format_type(&field.ty, &rs.generic_types)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
// Ideally, we should statically compare this to SpecialRustType::Bytes
// This would cause too much of a refactor
if python_type == "bytes" {
self.add_import("pydantic".to_owned(), "field_validator".to_owned());
self.add_import("pydantic".to_owned(), "field_serializer".to_owned());
return writeln!(
w,
r#"
@field_serializer("content")
def serialize_data(self, value: bytes) -> list[int]:
return list(value)

@field_validator("content", mode="before")
def deserialize_data(cls, value):
if isinstance(value, list):
return bytes(value)
return value"#
);
}
Ok(())
})
}

fn write_enum(&mut self, w: &mut dyn Write, e: &RustEnum) -> std::io::Result<()> {
Expand Down
34 changes: 32 additions & 2 deletions core/src/language/typescript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use std::{
io::{self, Write},
};

use super::ScopedCrateTypes;
use super::{get_vec_u8_conversion, ScopedCrateTypes};

/// All information needed to generate Typescript type-code
#[derive(Default)]
Expand All @@ -38,6 +38,11 @@ impl Language for TypeScript {
) -> Result<String, RustTypeFormatError> {
match special_ty {
SpecialRustType::Vec(rtype) => {
// TODO: https://github.com/1Password/typeshare/issues/231
if let Some(conversion) = get_vec_u8_conversion(special_ty, self.type_map(), rtype)
{
return Ok(conversion);
}
Ok(format!("{}[]", self.format_type(rtype, generic_types)?))
}
SpecialRustType::Array(rtype, len) => {
Expand Down Expand Up @@ -153,7 +158,32 @@ impl Language for TypeScript {
.iter()
.try_for_each(|f| self.write_field(w, f, rs.generic_types.as_slice()))?;

writeln!(w, "}}\n")
writeln!(w, "}}\n")?;
rs.fields.iter().try_for_each(|field| {
let typescript_type = self
.format_type(&field.ty, &rs.generic_types)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
if typescript_type == "Uint8Array" {
return writeln!(
w,
r#"// Reviver code - required for JSON deserialization
export function TypeshareReviver(key: string, value: unknown): unknown {{
return Array.isArray(value) && value.every(Number.isFinite)
? new {typescript_type}(value)
: value;
}}

// Replacer code - required for JSON serialization
export function TypeshareReplacer(key: string, value: unknown): unknown {{
if (value instanceof {typescript_type}) {{
return Array.from(value);
}}
return value;
}}"#
);
}
Ok(())
})
}

fn write_enum(&mut self, w: &mut dyn Write, e: &RustEnum) -> io::Result<()> {
Expand Down
2 changes: 2 additions & 0 deletions core/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ pub enum ParseError {
RustConstTypeInvalid,
#[error("the serde flatten attribute is not currently supported")]
SerdeFlattenNotAllowed,
#[error("the binary data decorator isn't allowed on types other than Vec<u8>")]
BinaryDataOnTypesOtherThanBytesNotAllowed,
#[error("IO error: {0}")]
IOError(String),
}
Expand Down
53 changes: 41 additions & 12 deletions core/tests/snapshot_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -424,24 +424,36 @@ static SWIFT_MAPPINGS: Lazy<HashMap<String, String>> = Lazy::new(|| {
});

static TYPESCRIPT_MAPPINGS: Lazy<HashMap<String, String>> = Lazy::new(|| {
[("Url", "string"), ("DateTime", "string")]
.iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect()
[
("Url", "string"),
("DateTime", "string"),
("Vec<u8>", "Uint8Array"),
]
.iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect()
});

static GO_MAPPINGS: Lazy<HashMap<String, String>> = Lazy::new(|| {
[("Url", "string"), ("DateTime", "string")]
.iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect()
[
("Url", "string"),
("DateTime", "string"),
("Vec<u8>", "[]byte"),
]
.iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect()
});

static PYTHON_MAPPINGS: Lazy<HashMap<String, String>> = Lazy::new(|| {
[("Url", "AnyUrl"), ("DateTime", "datetime")]
.iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect()
[
("Url", "AnyUrl"),
("DateTime", "datetime"),
("Vec<u8>", "bytes"),
]
.iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect()
});

tests! {
Expand Down Expand Up @@ -586,6 +598,9 @@ tests! {
go {
type_mappings: super::GO_MAPPINGS.clone(),
},
python {
type_mappings: super::PYTHON_MAPPINGS.clone(),
}
];
test_serde_url: [
swift {
Expand Down Expand Up @@ -674,4 +689,18 @@ tests! {
excluded_by_target_os: [ swift, kotlin, scala, typescript, go,python ] target_os: ["android", "macos"];
// excluded_by_target_os_full_module: [swift] target_os: "ios";
serde_rename_references: [ swift, kotlin, scala, typescript, go ];
test_byte_translation: [
go
{
type_mappings: super::GO_MAPPINGS.clone(),
},
python
{
type_mappings: super::PYTHON_MAPPINGS.clone(),
},
typescript
{
type_mappings: super::TYPESCRIPT_MAPPINGS.clone(),
}
];
}
Loading