# fieldmasker
[](https://crates.io/crates/fieldmasker)
[](https://docs.rs/fieldmasker)
[](#license)
A high-performance, type-safe implementation of **response field masks** in Rust,
inspired by Google APIs ([FieldMask][protobuf]) and [Google Maps API Field Mask][gmaps]
behavior.
Field masks let API clients request only the fields they need, which:
- Reduces **response size** and **serialization cost**.
- Avoids unnecessary **CPU work** (you can skip computing unneeded fields).
- Allows **usage tracking** per field.
- Provides **strong validation**: unknown fields are rejected.
---
## Features
- **Pure Rust core**, no HTTP framework dependencies.
- **Serde integration**, mask filtering happens during serialization.
- **Derive macro** (`#[derive(MaskSpec)]`) generates field mask metadata from your types.
- **Axum integration** (optional feature), extractors for `?fields=` query or `x-fields` header.
- **Strict validation**, unknown field paths produce an error.
- **Wildcard support** (`*` to select all fields at a level).
- **Nested paths** like `places.displayName.text` are supported.
- Early-gating helpers (`contains_exact`, `intersects`) to skip expensive computations.
---
## Basic usage
```toml
[dependencies]
fieldmasker = "0.1"
# For Axum extractors:
# fieldmasker = { version = "0.1", features = ["axum"] }
```
```rust
use fieldmasker::{FieldMask, Masked, MaskSpec};
use serde::Serialize;
#[derive(Serialize, MaskSpec)]
#[serde(rename_all = "camelCase")]
struct DisplayName {
text: String,
language_code: String,
}
#[derive(Serialize, MaskSpec)]
#[serde(rename_all = "camelCase")]
struct Place {
id: String,
formatted_address: String,
display_name: DisplayName,
}
#[derive(Serialize, MaskSpec)]
struct SearchResponse {
places: Vec<Place>,
}
fn main() {
let data = SearchResponse {
places: vec![Place {
id: "abc".into(),
formatted_address: "1 High St".into(),
display_name: DisplayName {
text: "Foo".into(),
language_code: "en".into(),
},
}],
};
let mask = FieldMask::parse("places.formattedAddress,places.displayName.text").unwrap();
let masked = Masked::new(data, mask);
println!("{}", serde_json::to_string_pretty(&masked).unwrap());
}
```
Output:
```json
{
"places": [
{
"formattedAddress": "1 High St",
"displayName": {
"text": "Foo"
}
}
]
}
```
# Axum integration
Enable the axum feature to get request extractors that validate and parse masks:
```rust
use axum::{routing::get, Json, Router};
use fieldmasker::{Masked, MaskSpec};
use fieldmasker::axum_integration::{MaskRequired, MaskRejection};
use serde::Serialize;
#[derive(Serialize, MaskSpec)]
struct Item {
id: String,
name: String,
#[serde(skip)]
secret: String
}
#[derive(Serialize, MaskSpec)]
struct List {
items: Vec<Item>
}
async fn list(
MaskRequired::<List>(mask, _): MaskRequired<List>
) -> Result<Json<Masked<List>>, MaskRejection> {
let data = List {
items: vec![
Item { id: "1".into(), name: "A".into(), secret: "s1".into() },
Item { id: "2".into(), name: "B".into(), secret: "s2".into() },
],
};
Ok(Json(Masked::new(data, mask)))
}
#[tokio::main]
async fn main() {
let app = Router::new().route("/list", get(list));
axum::serve(tokio::net::TcpListener::bind("127.0.0.1:3000").await.unwrap(), app)
.await
.unwrap();
}
```
Example request:
```bash
$ curl 'http://localhost:3000/list?fields=items.name'
```
Response:
```json
{
"items": [
{
"name": "A"
},
{
"name": "B"
}
]
}
```
[protobuf]: https://protobuf.dev/reference/protobuf/google.protobuf/#fieldmask
[gmaps]: https://developers.google.com/maps/documentation/places/web-service/field-masks