Closed murl-digital closed 1 week ago
Hey! I'm not sure I fully understand what you're trying to do yet. What TS would you like to get?
We do not support generating these "indexable" types, since there is no real rust equivalent.
If you really need to have a [propName: string]: any
field, then you could add that field with some TypeScript.
type Item = ItemData & { [propName: string]: any };
You could also force ts-rs to generate that for you:
#[derive(ts_rs::TS)]
#[ts(export)]
struct Item {
id: String,
#[ts(flatten)]
props: Props,
}
struct Props;
impl ts_rs::TS for Props {
type WithoutGenerics = Self;
fn decl() -> String { unreachable!() }
fn decl_concrete() -> String { unreachable!() }
fn name() -> String { unreachable!() }
fn inline() -> String { unreachable!() }
fn inline_flattened() -> String { "{ [propName: string]: any }".to_owned() }
}
But this is really abusing the library.
Again, I don't yet understand your use-case, so I can't recommend any alternatives.
type Item<D> = { name: string, /* ... */ } & D;
If this what you'd like it to generate?
Hey! I'm not sure I fully understand what you're trying to do yet. What TS would you like to get?
basically, what i'm trying to achieve is a type that defines an object with a few set fields, and then any field after that. for example, here's what a JSON representation of an Item
{
"__sc_id": "YFCzT4NxQEjYxjBvO_zCc",
"__sc_created_at": "2024-06-27T20:23:30.217589619Z",
"__sc_modified_at": "2024-06-27T20:23:30.217589619Z",
"__sc_published_at": null,
"hi": "hello world!",
"number": 1,
"test": {
"type": "Unit"
}
}
and here's how the D (a struct called Test) is defined:
struct Test {
pub hi: String,
pub number: i32,
#[field(validate)]
pub test: TestEnum,
}
#[doc_enum]
#[derive(Clone)]
enum TestEnum {
Unit,
Struct { eeee: String },
}
the reason i asked for [propName: string]: any
comes from finding this page in the TypeScript docs, in practice i think the cleanest way of allowing this would be some kind of #[ts(rest)]
attribute
the specific application this is for is a cms, where the shape of the user's data isn't known at type generation time, and the type safety's guartunteed elsewhere by a schema
edit: typos and formatting
I see!
What #336 would allow you is to keep your Item
type generic, so you'll end up with type Item<D> = { /* ... */ } & D
.
That seems like a more type-safe option than just allowing any additional fields.
Please let me know if that fits your use-case. If not, and you actually do need to allow for arbitrary additional fields using [anythingElse: string]: any
, we'll need to see if there's something we can do about that, or if the hack I outlined above is good enough.
i'm not fully convinced the generics flattening fit in my use case. the problem is that the concrete type will be provided by the end users, and i don't think it's a good idea to force them to derive (then export) the TS type to make them useful in the editor i'm working on. i do think that doing the hack you offered would be OK, because my usecase is ultra-specific and definitely comes with a healthy amount of "i know what i'm doing". whether or not you want to make a more elegant way to achieve that is up to you.
we'll need to see if there's something we can do about that, or if the hack I outlined above is good enough.
I wouldn't even call it a hack, if the issue is having a generic type that is unknown to Rust be flattened and let TS figure out the generic, having & D
is a much better approach than [x: string]: any
, as the latter will absolutely kill TS's ability to warn you about accessing a property that doesn't exist (e.g. having a typo in a property name)
having & D is a much better approach than [x: string]: any, as the latter will absolutely kill TS's ability to warn you about accessing a property that doesn't exist
That's true! I suspect having a [x: string]: any
field inside your type makes it roughly as usefull as just any
. You wont get any warnings if you write to an unknown field, and you wont get any warnings if you read an unknown field.
Anyway, If you do go with [x: string]: any
, then I'd recommend that you write that in TypeScript (type Item = ItemData & {[x: string]: any}
where ItemType
is what ts-rs
generates for you).
Then you wont have a breakage if we touch the TS
trait, which we do regularly (which is why we're at version 9.0 already ^^)
is there a good way to address the trait bound TS wants for the generic? right now i'm getting this compiler error, which runs up against me not wanting to force end users to generate typescript types
error[E0277]: the trait bound `D: TS` is not satisfied
--> scalar/src/lib.rs:69:17
|
67 | #[derive(Serialize, Deserialize, TS)]
| -- required by a bound introduced by this call
68 | //#[ts(export)]
69 | pub struct Item<D: Document> {
| ^ the trait `TS` is not implemented for `D`
|
note: required by a bound in `visit`
--> /home/draconium/.cargo/registry/src/index.crates.io-6f17d22bba15001f/ts-rs-9.0.0/src/lib.rs:581:17
|
581 | fn visit<T: TS + 'static + ?Sized>(&mut self);
| ^^ required by this bound in `TypeVisitor::visit`
help: consider further restricting this bound
|
69 | pub struct Item<D: Document + ts_rs::TS> {
There's #[ts(bound)]
. If you share what you ended up with, i'd be happy to take a look as well.
#[derive(ts_rs::TS, serde::Serialize)]
#[ts(export, concrete(D = AnythingElse))]
struct Item<D> {
id: String,
#[serde(flatten)]
inner: D,
}
struct AnythingElse;
impl ts_rs::TS for AnythingElse {
type WithoutGenerics = Self;
fn decl() -> String { unreachable!() }
fn decl_concrete() -> String { unreachable!() }
fn name() -> String { unreachable!() }
fn inline() -> String { unreachable!() }
fn inline_flattened() -> String { "{ [propName: string]: any }".to_owned() }
}
this here should should just work as-is
yeah that suggestion works, if you want, you could include the type in ts_rs as IndexibleAny or something, with a stern warning in the docs saying why in most cases it's probably a bad idea
right now i'm getting this compiler error, which runs up against me not wanting to force end users to generate typescript types
You could add a cargo feature to your library that gates the use of ts-rs
, this way users that want to export TS enable the feature and get type safety, while users that don't just don't need to worry about implementing TS
yeah that suggestion works, if you want, you could include the type in ts_rs as IndexibleAny or something, with a stern warning in the docs saying why in most cases it's probably a bad idea
Great! I'd rather not include this, since it's really not something I'd recommend. GitHub issues are nicely searchable, so if someone stumbles across this, I hope they find this issue.
i have a struct with a generic i'm exporting as a typescript type, however due to this struct being a part of a library where the generic will be provided by the user, there's not really a good way to define a type. since it's flattened, what i'd want to do is include a
[propName: string]: any
to the end like the typescript docs recommend, but i don't see a clean way to do that. here's the closest i've been able to get: