biscuit-auth / biscuit-rust

Rust implementation of the Biscuit authorization token
https://www.biscuitsec.org
206 stars 28 forks source link

Consider adding `impl TryFrom<Fact> for String` #198

Open wez opened 8 months ago

wez commented 8 months ago

I wanted to do this:

let hostnames: Vec<String> = a.query("data($name) <- hostname($name)")?;

but:

error[E0277]: the trait bound `std::string::String: From<biscuit_auth::builder::Fact>` is not satisfied
   --> src/tokens.rs:270:44
    |
270 |             let hostnames: Vec<String> = a.query("data($name) <- hostname($name)")?;
    |                                            ^^^^^ the trait `From<biscuit_auth::builder::Fact>` is not implemented for `std::string::String`
    |

I found that I can do this using tuple syntax and then converting, but it is somewhat verbose and since the order of the generics puts T as the second parameter, it becomes impossible to try to make this into a one-liner because all of the required generics are not easily named in a one liner:

let hostnames : Vec<String> = {
    let data: Vec<(String,)> = a.query("data($name) <- hostname($name)")?;
    data.into_iter().map(|t| t.0).collect()
};

I also tried making a newtype wrapper around string to handle the conversion, but it does not solve the need for an additional remapping of the array into the underlying type.

I suppose this request generalizes to allowing non-tuple variants of the other types that work with the (T,) syntax.

See also:

wez commented 8 months ago

I've thrown together a couple of helper functions to make this a little more ergonomic in my application, sharing here in case it is helpful, and/or influences the query API here in this crate.

I'm sorting the results because that matters for determinism and matching in tests, but I can see how that may not always be 100% desirable.

let hostnames: Vec<String> =
    query_facts_scalar(&mut a, "data($name) <- hostname($name)")?;
/// Query facts and return them in a stable order.
fn query_facts<T, R, E>(authorizer: &mut Authorizer, query: R) -> Result<Vec<T>, Token>
where
    R: TryInto<Rule>,
    T: TryFrom<Fact, Error = E> + Ord,
    E: Into<Token>,
    Token: From<<R as TryInto<Rule>>::Error>,
{
    let mut data = authorizer.query(query)?;
    data.sort();
    Ok(data)
}

/// Query a set of scalar facts and return them in a stable order.
fn query_facts_scalar<T, R, E>(authorizer: &mut Authorizer, query: R) -> Result<Vec<T>, Token>
where
    R: TryInto<Rule>,
    E: Into<Token>,
    Token: From<<R as TryInto<Rule>>::Error>,
    (T,): TryFrom<Fact, Error = E> + Ord,
{
    let mut data: Vec<(T,)> = query_facts(authorizer, query)?;
    let data: Vec<T> = data.into_iter().map(|t| t.0).collect();
    Ok(data)
}