Skip to content

Commit

Permalink
Merge pull request #11 from cwfitzgerald/configuration
Browse files Browse the repository at this point in the history
Support Serialization and Prefixes/Affixes on Types
  • Loading branch information
siefkenj committed Feb 6, 2024
2 parents 145ed4c + 71de564 commit aeaaead
Show file tree
Hide file tree
Showing 14 changed files with 486 additions and 103 deletions.
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 @@ -24,6 +24,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 @@ -25,6 +25,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 @@ -22,6 +22,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 @@ -112,6 +117,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;
}"
}
)
}
85 changes: 82 additions & 3 deletions tsify-macros/src/attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,41 @@ use serde_derive_internals::ast::Field;
use crate::comments::extract_doc_comments;

#[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,
pub comments: Vec<String>,
}

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,
ty_config: TypeGenerationConfig::default(),
comments: extract_doc_comments(&input.attrs),
};

Expand Down Expand Up @@ -52,7 +74,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

0 comments on commit aeaaead

Please sign in to comment.