google-apis-rs icon indicating copy to clipboard operation
google-apis-rs copied to clipboard

Provide support for specifying fields in output

Open philippeitis opened this issue 3 years ago • 3 comments

It would be nice if the APIs provided an include_field API with definitions for fields that could be included. Given that all possible fields are known, it seems like it'd be possible to generate a field enum for each method, which includes all of the possible fields.

This would be helpful for both discoverability (since this might not be the most well-known feature), and correctness (by only including fields which exist as enum variants, every include_field call would be correct, and we can guarantee that the fields parameter syntax would be correct).

Example usage (current):

drive
        .files()
        .list()
        .param("fields", "nextPageToken,files(id,hasThumbnail)")
        .page_size(5);

Example usage (desired):

drive
        .files()
        .list()
        .include_field(ListField::NextPageToken)
        .include_field(ListField::Files(vec![FileField::Id, FileField::HasThumbnail]))
        .page_size(5);

philippeitis avatar Oct 02 '22 21:10 philippeitis

That would certainly be nice to have! And it would even be backwards compatible.

The implementation would probably have to use the schema information to build types to build a sibling structure like proposed here. I wonder if it could be made available through the returned structure itself.

For instance, if files().list() returns a File type, could File::FIELD::NextPagetoken be a way to access it? That we we don't have even more types to deal with, hopefully eliminating the potential for clashes entirely.

Byron avatar Oct 04 '22 01:10 Byron

The only obvious solution I can see that would allow for the proposed solution is using associated trait types:

trait Fields {
    type Fields;
}

struct File {
    id: String,
    name: String,
}


trait ToParam {
    fn to_param(&self) -> String;
}

mod fields {
    use super::ToParam;

    pub enum File {
        Id,
        Name
    }

    
    impl ToParam for File {
        fn to_param(&self) -> String {
            match self {
                File::Id => "id",
                File::Name => "name"
            }.to_string()
        }
    }
}


impl Fields for File {
    type Fields = fields::File;
}

fn main() {
    println!("{}", <File as Fields>::Fields::Id.to_param());
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=14d7b70af2bdc88ffdc303c81e01d3b3

This solution could use a separate module for the fields to avoid collisions wherever possible (the names can be further disambiguated by using the full method chain).

Your proposal of using File::FIELD::NextPageToken syntax, which would quite nice (example: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=ae62cc52907647b81a09d202ba7eaf4b) is not currently supported, per https://github.com/rust-lang/rust/issues/8995.

However, using the trait-based associated type approach would not prevent File::FIELD::NextPageToken syntax in the future.

philippeitis avatar Oct 04 '22 01:10 philippeitis

This looks good to me, and I think it would help tremendously to know exactly how to access a types fields, instead of associating them by naming scheme for example. The latter would of course work, too, i.e. struct File has its field in fields::File or something like that. Maybe both can be employed at the same time as well, leaving using the associated type optional, even for us - after all it ads complexity which might better be avoided or added later once it's clear it's worth the cost.

In any case, my suggestion is to go ahead with a minimal implementation that associates field enums by name, and optionally associate the struct and enums by trait in a follow-up.

Byron avatar Oct 07 '22 02:10 Byron