Skip to content

Conversation

@haydenhoang
Copy link
Contributor

Change Summary

This PR added support for more attributes:

Collection-level attributes

Added support for token_separators and symbols_to_index:

#[derive(Typesense, Serialize, Deserialize)]
#[typesense(
    collection_name = "kitchen_sink_products",
    token_separators = ["-", "/"],
    symbols_to_index = ["+"]
)]
struct KitchenSinkProduct {}

Field-level attributes

  • Boolean attributes: sort, index, store, infix, stem, range_index, optional
  • Value-only attributes: locale, vec_dist, num_dim
  • Special attributes:
    • type: override the auto inferred type
    • rename: rename a Typesense field (This must be used with #[serde(rename = "new_name")])
    • flatten: generate schema for fields of nested structs
    • skip: skip generating schema for a field

Boolean attributes can be specified as a shorthand flag e.g. #[typesense(index)]or explicitly set a value e.g.#[typesense(index = false)])

Full example:

#[derive(Typesense, Serialize, Deserialize)]
#[typesense(
    collection_name = "mega_products",
    default_sorting_field = "price",
    enable_nested_fields = true,
    token_separators = ["-", "/"],
    symbols_to_index = ["+"]
)]
struct MegaProduct {
    id: String,

    #[typesense(infix, stem)]
    title: String,

    #[typesense(rename = "product_name")]
    #[serde(rename = "product_name")]
    official_name: String,

    #[typesense(facet)]
    brand: String,

    #[typesense(sort)]
    price: f32,

    #[typesense(range_index)]
    review_score: f32,

    #[typesense(index = false, store = false)]
    internal_sku: Option<String>,

    #[typesense(type = "geopoint")]
    location: (f32, f32),

    #[typesense(num_dim = 4, vec_dist = "cosine")]
    embedding: Vec<f32>,

    #[typesense(flatten, skip)]
    details: ProductDetails,

    #[typesense(flatten, rename = "logistics_data")]
    #[serde(rename = "logistics_data")]
    logistics: Logistics,

    manufacturer: Manufacturer,

    #[typesense(flatten)]
    parts: Vec<Part>,

    tags: Option<Vec<String>>,

    #[typesense(rename = "primary_address.city")]
    #[serde(rename = "primary_address.city")]
    primary_city: String,
}

Bug fix

This PR also fixed the partial struct derive wrapping an already-optional field in an Option<>

@RoDmitry
Copy link
Collaborator

RoDmitry commented Nov 5, 2025

the partial struct derive wrapping an already-optional field in an Option<>

I thought it's supposed to wrap. Because there are optional fields possible, no? How else would you update a field and set it to null? That needs to be added to tests.

@RoDmitry RoDmitry self-requested a review November 5, 2025 09:22
Comment on lines +316 to +328
let flattened_fields = quote! {
<#inner_type as typesense::prelude::Document>::collection_schema().fields
.into_iter()
.map(|mut f| {
// Use the dynamically determined prefix here
f.name = format!("{}.{}", #prefix, f.name);
if #is_vec && !f.r#type.ends_with("[]") {
f.r#type.push_str("[]");
}
f
})
.collect::<Vec<_>>()
};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't do it in a runtime, at least use const logic.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how to optimize this. Could you clarify "const logic"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would really appreciate if you can optimize it.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I will look into it later.

fn collection_schema() -> ::typesense::models::CollectionSchema {
let name = Self::COLLECTION_NAME.to_owned();
let fields = vec![#(#typesense_fields,)*];
let fields = vec![#(#typesense_fields,)*].into_iter().flatten().collect();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue: must not be .flatten().collect() in runtime.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Used .extend() instead of .flatten().collect(), is this the ideal approach?

let mut fields = Vec::new();
#(fields.extend(#typesense_fields);)*

@RoDmitry
Copy link
Collaborator

RoDmitry commented Nov 5, 2025

Attributes locale and optional are missing in tests (derive_integration_test.rs)

@haydenhoang
Copy link
Contributor Author

How else would you update a field and set it to null? That needs to be added to tests.

Yes, you're right. I have reverted the change and added test for it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants