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

Allow Configuration of BigInt Serialization #34

Closed
wants to merge 3 commits into from
Closed
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
20 changes: 19 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,28 @@

#[cfg(all(feature = "json", not(feature = "js")))]
pub use gloo_utils::format::JsValueSerdeExt;
#[cfg(feature = "js")]
pub use serde_wasm_bindgen;
pub use tsify_macros::*;
#[cfg(feature = "wasm-bindgen")]
use wasm_bindgen::{JsCast, JsValue};

pub struct SerializationConfig {
pub missing_as_null: bool,
pub hashmap_as_object: bool,
pub large_number_types_as_bigints: bool,
}

pub trait Tsify {
#[cfg(feature = "wasm-bindgen")]
type JsType: JsCast;

const DECL: &'static str;
const SERIALIZATION_CONFIG: SerializationConfig = SerializationConfig {
missing_as_null: false,
hashmap_as_object: false,
large_number_types_as_bigints: false,
};

#[cfg(all(feature = "json", not(feature = "js")))]
#[inline]
Expand All @@ -36,7 +49,12 @@ pub trait Tsify {
where
Self: serde::Serialize,
{
serde_wasm_bindgen::to_value(self).map(JsCast::unchecked_from_js)
let config = <Self as Tsify>::SERIALIZATION_CONFIG;
let serializer = serde_wasm_bindgen::Serializer::new()
.serialize_missing_as_null(config.missing_as_null)
.serialize_maps_as_objects(config.hashmap_as_object)
.serialize_large_number_types_as_bigints(config.large_number_types_as_bigints);
self.serialize(&serializer).map(JsCast::unchecked_from_js)
}

#[cfg(feature = "js")]
Expand Down
116 changes: 116 additions & 0 deletions tests/affixes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#![allow(dead_code)]

use indoc::indoc;
use pretty_assertions::assert_eq;
use tsify::Tsify;

#[test]
fn test_prefix() {
type MyType = u32;

#[derive(Tsify)]
#[tsify(type_prefix = "Special")]
struct PrefixedStruct {
// Make sure that prefix isn't applied to builtin types
x: u32,
y: MyType,
}

assert_eq!(
PrefixedStruct::DECL,
indoc! {"
export interface SpecialPrefixedStruct {
x: number;
y: SpecialMyType;
}"
}
);

#[derive(Tsify)]
#[tsify(type_prefix = "Special")]
enum PrefixedEnum {
VariantA(MyType),
VariantB(u32),
}

assert_eq!(
PrefixedEnum::DECL,
indoc! {"
export type SpecialPrefixedEnum = { VariantA: SpecialMyType } | { VariantB: number };"
}
);
}

#[test]
fn test_suffix() {
type MyType = u32;

#[derive(Tsify)]
#[tsify(type_suffix = "Special")]
struct SuffixedStruct {
// Make sure that prefix isn't applied to builtin types
x: u32,
y: MyType,
}

assert_eq!(
SuffixedStruct::DECL,
indoc! {"
export interface SuffixedStructSpecial {
x: number;
y: MyTypeSpecial;
}"
}
);

#[derive(Tsify)]
#[tsify(type_suffix = "Special")]
enum SuffixedEnum {
VariantA(MyType),
VariantB(u32),
}

assert_eq!(
SuffixedEnum::DECL,
indoc! {"
export type SuffixedEnumSpecial = { VariantA: MyTypeSpecial } | { VariantB: number };"
}
);
}

#[test]
fn test_prefix_suffix() {
type MyType = u32;

#[derive(Tsify)]
#[tsify(type_prefix = "Pre", type_suffix = "Suf")]
struct DoubleAffixedStruct {
// Make sure that prefix isn't applied to builtin types
x: u32,
y: MyType,
}

assert_eq!(
DoubleAffixedStruct::DECL,
indoc! {"
export interface PreDoubleAffixedStructSuf {
x: number;
y: PreMyTypeSuf;
}"
}
);

#[derive(Tsify)]
#[tsify(type_prefix = "Pre", type_suffix = "Suf")]
enum DoubleAffixedEnum {
VariantA(MyType),
VariantB(u32),
}

assert_eq!(
DoubleAffixedEnum::DECL,
indoc! {"
export type PreDoubleAffixedEnumSuf = { VariantA: PreMyTypeSuf } | { VariantB: number };"
}
);
}
5 changes: 5 additions & 0 deletions tests/expand/borrow.expanded.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ const _: () = {
impl<'a> Tsify for Borrow<'a> {
type JsType = JsType;
const DECL: &'static str = "export interface Borrow {\n raw: string;\n cow: string;\n}";
const SERIALIZATION_CONFIG: tsify::SerializationConfig = tsify::SerializationConfig {
missing_as_null: false,
hashmap_as_object: false,
large_number_types_as_bigints: false,
};
}
#[wasm_bindgen(typescript_custom_section)]
const TS_APPEND_CONTENT: &'static str = "export interface Borrow {\n raw: string;\n cow: string;\n}";
Expand Down
5 changes: 5 additions & 0 deletions tests/expand/generic_enum.expanded.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ const _: () = {
impl<T, U> Tsify for GenericEnum<T, U> {
type JsType = JsType;
const DECL: &'static str = "export type GenericEnum<T, U> = \"Unit\" | { NewType: T } | { Seq: [T, U] } | { Map: { x: T; y: U } };";
const SERIALIZATION_CONFIG: tsify::SerializationConfig = tsify::SerializationConfig {
missing_as_null: false,
hashmap_as_object: false,
large_number_types_as_bigints: false,
};
}
#[wasm_bindgen(typescript_custom_section)]
const TS_APPEND_CONTENT: &'static str = "export type GenericEnum<T, U> = \"Unit\" | { NewType: T } | { Seq: [T, U] } | { Map: { x: T; y: U } };";
Expand Down
10 changes: 10 additions & 0 deletions tests/expand/generic_struct.expanded.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ const _: () = {
impl<T> Tsify for GenericStruct<T> {
type JsType = JsType;
const DECL: &'static str = "export interface GenericStruct<T> {\n x: T;\n}";
const SERIALIZATION_CONFIG: tsify::SerializationConfig = tsify::SerializationConfig {
missing_as_null: false,
hashmap_as_object: false,
large_number_types_as_bigints: false,
};
}
#[wasm_bindgen(typescript_custom_section)]
const TS_APPEND_CONTENT: &'static str = "export interface GenericStruct<T> {\n x: T;\n}";
Expand Down Expand Up @@ -89,6 +94,11 @@ const _: () = {
impl<T> Tsify for GenericNewtype<T> {
type JsType = JsType;
const DECL: &'static str = "export type GenericNewtype<T> = T;";
const SERIALIZATION_CONFIG: tsify::SerializationConfig = tsify::SerializationConfig {
missing_as_null: false,
hashmap_as_object: false,
large_number_types_as_bigints: false,
};
}
#[wasm_bindgen(typescript_custom_section)]
const TS_APPEND_CONTENT: &'static str = "export type GenericNewtype<T> = T;";
Expand Down
56 changes: 56 additions & 0 deletions tests/options.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#![cfg(feature = "js")]
#![allow(dead_code)]

use std::collections::HashMap;

use indoc::indoc;
use pretty_assertions::assert_eq;
use tsify::Tsify;

#[test]
fn test_transparent() {
#[derive(Tsify)]
#[tsify(missing_as_null)]
struct Optional {
a: Option<u32>,
}

assert_eq!(
Optional::DECL,
indoc! {"
export interface Optional {
a: number | null;
}"
}
);

#[derive(Tsify)]
#[tsify(hashmap_as_object)]
struct MapWrap {
a: HashMap<u32, u32>,
}

assert_eq!(
MapWrap::DECL,
indoc! {"
export interface MapWrap {
a: Record<number, number>;
}"
}
);

#[derive(Tsify)]
#[tsify(large_number_types_as_bigints)]
struct BigNumber {
a: u64,
}

assert_eq!(
BigNumber::DECL,
indoc! {"
export interface BigNumber {
a: bigint;
}"
}
)
}
90 changes: 82 additions & 8 deletions tsify-macros/src/attrs.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,36 @@
use serde_derive_internals::ast::Field;

#[derive(Debug, Default)]
pub struct TsifyContainerAttars {
pub struct TsifyContainerAttrs {
pub into_wasm_abi: bool,
pub from_wasm_abi: bool,
pub namespace: bool,
pub ty_config: TypeGenerationConfig,
}

impl TsifyContainerAttars {
#[derive(Debug, Default)]
pub struct TypeGenerationConfig {
pub type_prefix: Option<String>,
pub type_suffix: Option<String>,
pub missing_as_null: bool,
pub hashmap_as_object: bool,
pub large_number_types_as_bigints: bool,
}
impl TypeGenerationConfig {
pub fn format_name(&self, mut name: String) -> String {
if let Some(ref prefix) = self.type_prefix {
name.insert_str(0, prefix);
}
if let Some(ref suffix) = self.type_suffix {
name.push_str(suffix);
}
name
}
}

impl TsifyContainerAttrs {
pub fn from_derive_input(input: &syn::DeriveInput) -> syn::Result<Self> {
let mut attrs = Self {
into_wasm_abi: false,
from_wasm_abi: false,
namespace: false,
};
let mut attrs = Self::default();

for attr in &input.attrs {
if !attr.path().is_ident("tsify") {
Expand Down Expand Up @@ -48,7 +65,64 @@ impl TsifyContainerAttars {
return Ok(());
}

Err(meta.error("unsupported tsify attribute, expected one of `into_wasm_abi`, `from_wasm_abi`, `namespace`"))
if meta.path.is_ident("type_prefix") {
if attrs.ty_config.type_prefix.is_some() {
return Err(meta.error("duplicate attribute"));
}
let lit: syn::LitStr = meta.value()?.parse()?;
attrs.ty_config.type_prefix = Some(lit.value());
return Ok(());
}

if meta.path.is_ident("type_suffix") {
if attrs.ty_config.type_suffix.is_some() {
return Err(meta.error("duplicate attribute"));
}
let lit: syn::LitStr = meta.value()?.parse()?;
attrs.ty_config.type_suffix = Some(lit.value());
return Ok(());
}

if meta.path.is_ident("missing_as_null") {
if attrs.ty_config.missing_as_null {
return Err(meta.error("duplicate attribute"));
}
if cfg!(not(feature = "js")) {
return Err(meta.error(
"#[tsify(missing_as_null)] requires the `js` feature",
));
}
attrs.ty_config.missing_as_null = true;
return Ok(());
}

if meta.path.is_ident("hashmap_as_object") {
if attrs.ty_config.hashmap_as_object {
return Err(meta.error("duplicate attribute"));
}
if cfg!(not(feature = "js")) {
return Err(meta.error(
"#[tsify(hashmap_as_object)] requires the `js` feature",
));
}
attrs.ty_config.hashmap_as_object = true;
return Ok(());
}

if meta.path.is_ident("large_number_types_as_bigints") {
if attrs.ty_config.large_number_types_as_bigints {
return Err(meta.error("duplicate attribute"));
}
if cfg!(not(feature = "js")) {
return Err(meta.error(
"#[tsify(large_number_types_as_bigints)] requires the `js` feature",
));
}
attrs.ty_config.large_number_types_as_bigints = true;
return Ok(());
}

Err(meta.error("unsupported tsify attribute, expected one of `into_wasm_abi`, `from_wasm_abi`, `namespace`, 'type_prefix', 'type_suffix', 'missing_as_null', 'hashmap_as_object', 'large_number_types_as_bigints'"))
})?;
}

Expand Down
Loading