Crates.io | fieldmasker |
lib.rs | fieldmasker |
version | 0.0.1 |
created_at | 2025-08-26 11:35:59.237366+00 |
updated_at | 2025-08-26 11:35:59.237366+00 |
description | A utility for selecting and filtering response fields via field masks. |
homepage | https://github.com/punkeel/fieldmasker |
repository | https://github.com/punkeel/fieldmasker |
max_upload_size | |
id | 1811049 |
size | 98,269 |
A high-performance, type-safe implementation of response field masks in Rust,
inspired by Google APIs (FieldMask) and Google Maps API Field Mask
behavior.
Field masks let API clients request only the fields they need, which:
#[derive(MaskSpec)]
) generates field mask metadata from your types.?fields=
query or x-fields
header.*
to select all fields at a level).places.displayName.text
are supported.contains_exact
, intersects
) to skip expensive computations.[dependencies]
fieldmasker = "0.1"
# For Axum extractors:
# fieldmasker = { version = "0.1", features = ["axum"] }
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:
{
"places": [
{
"formattedAddress": "1 High St",
"displayName": {
"text": "Foo"
}
}
]
}
Enable the axum feature to get request extractors that validate and parse masks:
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:
$ curl 'http://localhost:3000/list?fields=items.name'
Response:
{
"items": [
{
"name": "A"
},
{
"name": "B"
}
]
}