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 overrides for disallowed types (e.g. 64 bit integers) #140

Open
wants to merge 4 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
10 changes: 10 additions & 0 deletions core/data/tests/can_override_disallowed_types/input.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#[typeshare]
struct DisallowedType {
#[typeshare(typescript(type = "bigint"))]
disallowed_type: u64,
#[typeshare(typescript(type = "number"))]
another_disallowed_type: i64,
#[typeshare(typescript(type = "string"))]
#[serde(with = "my_string_serde_impl")]
disallowed_type_serde_with: u64,
}
9 changes: 9 additions & 0 deletions core/data/tests/can_override_disallowed_types/output.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package proto

import "encoding/json"

type DisallowedType struct {
DisallowedType uint64 `json:"disallowed_type"`
AnotherDisallowedType int64 `json:"another_disallowed_type"`
DisallowedTypeSerdeWith uint64 `json:"disallowed_type_serde_with"`
}
12 changes: 12 additions & 0 deletions core/data/tests/can_override_disallowed_types/output.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.agilebits.onepassword

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

@Serializable
data class DisallowedType (
val disallowed_type: ULong,
val another_disallowed_type: Long,
val disallowed_type_serde_with: ULong
)

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

package object onepassword {

type UByte = Byte
type UShort = Short
type UInt = Int
type ULong = Int

}
package onepassword {

case class DisallowedType (
disallowed_type: ULong,
another_disallowed_type: Long,
disallowed_type_serde_with: ULong
)

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

public struct DisallowedType: Codable {
public let disallowed_type: UInt64
public let another_disallowed_type: Int64
public let disallowed_type_serde_with: UInt64

public init(disallowed_type: UInt64, another_disallowed_type: Int64, disallowed_type_serde_with: UInt64) {
self.disallowed_type = disallowed_type
self.another_disallowed_type = another_disallowed_type
self.disallowed_type_serde_with = disallowed_type_serde_with
}
}
6 changes: 6 additions & 0 deletions core/data/tests/can_override_disallowed_types/output.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface DisallowedType {
disallowed_type: bigint;
another_disallowed_type: number;
disallowed_type_serde_with: string;
}

5 changes: 4 additions & 1 deletion core/src/language/typescript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,10 @@ impl Language for TypeScript {
| SpecialRustType::I64
| SpecialRustType::ISize
| SpecialRustType::USize => {
panic!("64 bit types not allowed in Typeshare")
panic!(
"64 bit integer types require an explicit output type. \
See: https://1password.github.io/typeshare/usage/annotations.html#special-note-on-64-bit-integer-types for more information."
)
}
}
}
Expand Down
7 changes: 4 additions & 3 deletions core/src/rust_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,13 +305,14 @@ impl TryFrom<&syn::Type> for RustType {
"u16" => Self::Special(SpecialRustType::U16),
"u32" => Self::Special(SpecialRustType::U32),
"U53" => Self::Special(SpecialRustType::U53),
"u64" | "i64" | "usize" | "isize" => {
return Err(RustTypeParseError::UnsupportedType(vec![id]))
}
"u64" => Self::Special(SpecialRustType::U64),
"usize" => Self::Special(SpecialRustType::USize),
"i8" => Self::Special(SpecialRustType::I8),
"i16" => Self::Special(SpecialRustType::I16),
"i32" => Self::Special(SpecialRustType::I32),
"I54" => Self::Special(SpecialRustType::I54),
"i64" => Self::Special(SpecialRustType::I64),
"isize" => Self::Special(SpecialRustType::ISize),
"f32" => Self::Special(SpecialRustType::F32),
"f64" => Self::Special(SpecialRustType::F64),
_ => {
Expand Down
63 changes: 0 additions & 63 deletions core/tests/agnostic_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ use std::io::Write;
use typeshare_core::{
language::{CrateTypes, Language, TypeScript},
parser::{self, ParseError},
rust_types::RustTypeParseError,
ProcessInputError,
};
/// Parse and generate types for a single Rust input file.
Expand Down Expand Up @@ -33,68 +32,6 @@ pub fn process_input(
Ok(())
}

mod blocklisted_types {
use std::collections::HashMap;

use super::*;

fn assert_type_is_blocklisted(ty: &str, blocklisted_type: &str) {
let source = format!(
r##"
#[typeshare]
#[serde(default, rename_all = "camelCase")]
pub struct Foo {{
pub bar: {ty},
}}
"##,
ty = ty
);

let mut out: Vec<u8> = Vec::new();
assert!(matches!(
process_input(&source, &mut TypeScript::default(), &HashMap::new(), &mut out),
Err(ProcessInputError::ParseError(
ParseError::RustTypeParseError(RustTypeParseError::UnsupportedType(contents))
)) if contents == vec![blocklisted_type.to_owned()]
));
}

#[test]
fn test_i64_blocklisted_struct() {
assert_type_is_blocklisted("i64", "i64");
}

#[test]
fn test_u64_blocklisted_struct() {
assert_type_is_blocklisted("u64", "u64");
}

#[test]
fn test_isize_blocklisted_struct() {
assert_type_is_blocklisted("isize", "isize");
}

#[test]
fn test_usize_blocklisted_in_struct() {
assert_type_is_blocklisted("usize", "usize");
}

#[test]
fn test_optional_blocklisted_struct() {
assert_type_is_blocklisted("Option<i64>", "i64");
}

#[test]
fn test_vec_blocklisted_struct() {
assert_type_is_blocklisted("Vec<i64>", "i64");
}

#[test]
fn test_hashmap_blocklisted_struct() {
assert_type_is_blocklisted("HashMap<String, i64>", "i64");
}
}

mod serde_attributes_on_enums {
use std::collections::HashMap;

Expand Down
1 change: 1 addition & 0 deletions core/tests/snapshot_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,7 @@ tests! {
go
];
can_override_types: [swift, kotlin, scala, typescript, go];
can_override_disallowed_types: [swift, kotlin, scala, typescript, go];

/// Structs
can_generate_simple_struct_with_a_comment: [kotlin, swift, typescript, scala, go];
Expand Down
56 changes: 56 additions & 0 deletions docs/src/usage/annotations.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,62 @@ This would generate the following Kotlin code:
typealias Options = String
```

### Override Type for a Field

You can also use language-specific arguments to tell Typeshare to treat
a field as a type in a particular output language. For example,
```rust
#[typeshare]
struct MyStruct {
#[typeshare(typescript(type = "0 | 1"))]
oneOrZero: u8,
}
```
would generate the following Typescript code:
```typescript
export interface MyStruct {
oneOrZero: 0 | 1;
}
```
The `type` argument is supported for all output languages, however Typescript
also supports the optional `readonly` argument (e.g. `typescript(readonly, type= "0 | 1")`)
to make the output property readonly.

### Special Note on 64 Bit Integer Types

The default behavior for 64 bit integer types when outputting TypeScript is to
panic. The reasoning behind this is that in JavaScript runtimes integers are not
sufficient to fully represent the set of all 64 bit integers, that is,
`Number.MIN_SAFE_INTEGER` and `Number.MAX_SAFE_INTEGER` are less in magnitude
than `i64::MIN` and `u64::MAX`, respectively. There are a few ways one can still
use 64 bit integer types, however, and a Typeshare attribute to override the
field type can be applied to accommodate the particular approach one chooses to
take. Here are a few examples:

**Serializing 64 bit integer fields to strings using `serde(with = ...)`**
```rust
struct MyStruct {
#[typeshare(typescript(type = "string"))]
#[serde(with = "my_string_serde_impl")]
my_field: u64
}
```

**Using a third-party JSON parser that provides support for larger integer types via `bigint`**
```rust
struct MyStruct {
#[typeshare(typescript(type = "bigint"))]
my_field: u64
}
```

**Throwing all caution to the wind and just using `number`**
```rust
struct MyStruct {
#[typeshare(typescript(type = "number"))]
my_field: u64
}
```


## The `#[serde]` Attribute
Expand Down