mitsuhiko / insta

A snapshot testing library for rust
https://insta.rs
Apache License 2.0
2.13k stars 96 forks source link

Redaction macro #291

Open irevoire opened 1 year ago

irevoire commented 1 year ago

Hey, thanks for this great project; the more I use it, the more I love it.

I just found out about the redaction features, which basically solve all the remaining issues I had with insta :tada: I was wondering, in terms of API, if you ever thought of providing a macro to autogenerate and store the redaction somewhere.

To explain furthermore, here is an example (my current use case); I have this big structure:

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TaskView {
    pub uid: TaskId,
    pub index_uid: Option<String>,
    pub status: Status,
    #[serde(rename = "type")]
    pub kind: Kind,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub details: Option<Details>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub error: Option<ResponseError>,

    #[serde(
        serialize_with = "serialize_duration",
        skip_serializing_if = "Option::is_none"
    )]
    pub duration: Option<Duration>,
    #[serde(with = "time::serde::rfc3339")]
    pub enqueued_at: OffsetDateTime,
    #[serde(
        with = "time::serde::rfc3339::option",
        skip_serializing_if = "Option::is_none"
    )]
    pub started_at: Option<OffsetDateTime>,
    #[serde(
        with = "time::serde::rfc3339::option",
        skip_serializing_if = "Option::is_none"
    )]
    pub finished_at: Option<OffsetDateTime>,
}

As you can see, there are a bunch of dates and a Duration that I'm not interested in 99% of the time. And thus most of my tests look like that;

        assert_json_snapshot!(task, 
            { "[].enqueuedAt" => "date", "[].startedAt" => "date", "[].finishedAt" => "date", "[].duration" => "duration" }
            ,@r###"
        [
          {
            "uid": 0,
            "indexUid": "doggos",
            "status": "succeeded",
            "type": "documentAddition",
            "details": {
              "receivedDocuments": 1,
              "indexedDocuments": 1
            },
            "duration": "[duration]",
            "enqueuedAt": "[date]",
            "startedAt": "[date]",
            "finishedAt": "[date]"
          }
        ]
        "###);

Overall I'm super happy with this result. But after copy-pasting this test a bunch of times, I started to think that it should be doable to write an horrendous proc-macro that hardcode my redactions for my structure specifically.

Something like that, I guess;

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, insta::Redaction)]
#[serde(rename_all = "camelCase")]
pub struct TaskView {
    pub uid: TaskId,
    pub index_uid: Option<String>,
    pub status: Status,
    #[serde(rename = "type")]
    pub kind: Kind,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub details: Option<Details>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub error: Option<ResponseError>,

    #[serde(
        serialize_with = "serialize_duration",
        skip_serializing_if = "Option::is_none"
    )]
    #[insta(redaction = "duration")]
    pub duration: Option<Duration>,
    #[serde(with = "time::serde::rfc3339")]
    #[insta(redaction = "time")]
    pub enqueued_at: OffsetDateTime,
    #[serde(
        with = "time::serde::rfc3339::option",
        skip_serializing_if = "Option::is_none"
    )]
    #[insta(redaction = "time")]
    pub started_at: Option<OffsetDateTime>,
    #[serde(
        with = "time::serde::rfc3339::option",
        skip_serializing_if = "Option::is_none"
    )]
    #[insta(redaction = "time")]
    pub finished_at: Option<OffsetDateTime>,
}

Do you think this is a direction insta could take in the future?

mitsuhiko commented 1 year ago

I would be curious to know if binding redactions manually to the settings would work for you:

fn enable_redactions() -> insta::internals::SettingsBindDropGuard {
    let mut settings = insta::Settings::clone_current();
    settings.add_redaction("[].enqueuedAt", "date");
    settings.bind_to_scope()
}

And then

#[test]
fn my_test() {
    let _guard = enable_redactions();
    assert_json_snapshot!(task, @"...");
}
irevoire commented 1 year ago

Ooooh, I totally missed this one in the doc! Yeah, I think it would work, it's not ideal but it's enough for me, thanks! :grin:

mitsuhiko commented 1 year ago

I will keep this open for now. It would definitely be possible to do, but it does come with quite a bit of complexity.