changeset 172:9dc67ed1079b

Rust generator: Early start Fetching documents and deserialization works. For #3
author Lewin Bormann <lbo@spheniscida.de>
date Sun, 13 Dec 2020 23:40:03 +0100
parents 714165191d0d
children
files generate_rs/Cargo.toml generate_rs/src/discovery_v1_types.rs generate_rs/src/main.rs
diffstat 3 files changed, 883 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/generate_rs/Cargo.toml	Sun Dec 13 23:40:03 2020 +0100
@@ -0,0 +1,16 @@
+[package]
+name = "generate_rs"
+version = "0.1.0"
+authors = ["Lewin Bormann <lewin@lewin-bormann.info>"]
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+async-google-apis-common = { path = "../async-google-apis-common" }
+serde = "~1.0"
+serde_json = "~1.0"
+reqwest = "~0.10"
+tokio = { version = "~0.2" }
+clap = "~2.33"
+anyhow = "~1.0"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/generate_rs/src/discovery_v1_types.rs	Sun Dec 13 23:40:03 2020 +0100
@@ -0,0 +1,769 @@
+#![allow(unused_variables, unused_mut, dead_code)]
+//! This file was generated by async-google-apis. (https://github.com/dermesser/async-google-apis)
+//!
+//! (c) 2020 Lewin Bormann <lbo@spheniscida.de>
+//!
+//! ## Getting started
+//!
+//! **Tip**: Take a look at those types ending in `...Service`. These represent API resources
+//! and contain methods to interact with an API. The remaining types are used by those methods
+//! and can be explored starting from a method you want to use.
+//!
+//! I'd be happy if you let me know about your use case of this code.
+//!
+//! THIS FILE HAS BEEN GENERATED -- SAVE ANY MODIFICATIONS BEFORE REPLACING.
+
+use async_google_apis_common::*;
+
+///
+#[derive(Serialize, Deserialize, Debug, Clone, Default)]
+pub struct RestResource {
+    /// Sub-resources on this resource.
+    #[serde(rename = "resources")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub resources: Option<HashMap<String, Box<RestResource>>>,
+    /// Methods on this resource.
+    #[serde(rename = "methods")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub methods: Option<HashMap<String, RestMethod>>,
+}
+
+/// The schema for the request.
+#[derive(Serialize, Deserialize, Debug, Clone, Default)]
+pub struct RestMethodRequest {
+    /// Schema ID for the request schema.
+    #[serde(rename = "$ref")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub dollarref: Option<String>,
+    /// parameter name.
+    #[serde(rename = "parameterName")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub parameter_name: Option<String>,
+}
+
+/// The schema for the response.
+#[derive(Serialize, Deserialize, Debug, Clone, Default)]
+pub struct RestMethodResponse {
+    /// Schema ID for the response schema.
+    #[serde(rename = "$ref")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub dollarref: Option<String>,
+}
+
+/// Supports the Resumable Media Upload protocol.
+#[derive(Serialize, Deserialize, Debug, Clone, Default)]
+pub struct RestMethodMediaUploadProtocolsResumable {
+    /// The URI path to be used for upload. Should be used in conjunction with the basePath property at the api-level.
+    #[serde(rename = "path")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub path: Option<String>,
+    /// True if this endpoint supports uploading multipart media.
+    #[serde(rename = "multipart")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub multipart: Option<bool>,
+}
+
+/// Supports uploading as a single HTTP request.
+#[derive(Serialize, Deserialize, Debug, Clone, Default)]
+pub struct RestMethodMediaUploadProtocolsSimple {
+    /// The URI path to be used for upload. Should be used in conjunction with the basePath property at the api-level.
+    #[serde(rename = "path")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub path: Option<String>,
+    /// True if this endpoint supports upload multipart media.
+    #[serde(rename = "multipart")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub multipart: Option<bool>,
+}
+
+/// Supported upload protocols.
+#[derive(Serialize, Deserialize, Debug, Clone, Default)]
+pub struct RestMethodMediaUploadProtocols {
+    /// Supports the Resumable Media Upload protocol.
+    #[serde(rename = "resumable")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub resumable: Option<RestMethodMediaUploadProtocolsResumable>,
+    /// Supports uploading as a single HTTP request.
+    #[serde(rename = "simple")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub simple: Option<RestMethodMediaUploadProtocolsSimple>,
+}
+
+/// Media upload parameters.
+#[derive(Serialize, Deserialize, Debug, Clone, Default)]
+pub struct RestMethodMediaUpload {
+    /// MIME Media Ranges for acceptable media uploads to this method.
+    #[serde(rename = "accept")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub accept: Option<Vec<String>>,
+    /// Maximum size of a media upload, such as "1MB", "2GB" or "3TB".
+    #[serde(rename = "maxSize")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub max_size: Option<String>,
+    /// Supported upload protocols.
+    #[serde(rename = "protocols")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub protocols: Option<RestMethodMediaUploadProtocols>,
+}
+
+///
+#[derive(Serialize, Deserialize, Debug, Clone, Default)]
+pub struct RestMethod {
+    /// A unique ID for this method. This property can be used to match methods between different versions of Discovery.
+    #[serde(rename = "id")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub id: Option<String>,
+    /// The schema for the request.
+    #[serde(rename = "request")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub request: Option<RestMethodRequest>,
+    /// Description of this method.
+    #[serde(rename = "description")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub description: Option<String>,
+    /// Ordered list of required parameters, serves as a hint to clients on how to structure their method signatures. The array is ordered such that the "most-significant" parameter appears first.
+    #[serde(rename = "parameterOrder")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub parameter_order: Option<Vec<String>>,
+    /// Details for all parameters in this method.
+    #[serde(rename = "parameters")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub parameters: Option<HashMap<String, JsonSchema>>,
+    /// Whether this method supports subscriptions.
+    #[serde(rename = "supportsSubscription")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub supports_subscription: Option<bool>,
+    /// Whether this method requires an ETag to be specified. The ETag is sent as an HTTP If-Match or If-None-Match header.
+    #[serde(rename = "etagRequired")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub etag_required: Option<bool>,
+    /// Indicates that downloads from this method should use the download service URL (i.e. "/download"). Only applies if the method supports media download.
+    #[serde(rename = "useMediaDownloadService")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub use_media_download_service: Option<bool>,
+    /// The schema for the response.
+    #[serde(rename = "response")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub response: Option<RestMethodResponse>,
+    /// Whether this method supports media downloads.
+    #[serde(rename = "supportsMediaDownload")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub supports_media_download: Option<bool>,
+    /// Whether this method supports media uploads.
+    #[serde(rename = "supportsMediaUpload")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub supports_media_upload: Option<bool>,
+    /// The URI path of this REST method. Should be used in conjunction with the basePath property at the api-level.
+    #[serde(rename = "path")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub path: Option<String>,
+    /// HTTP method used by this method.
+    #[serde(rename = "httpMethod")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub http_method: Option<String>,
+    /// OAuth 2.0 scopes applicable to this method.
+    #[serde(rename = "scopes")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub scopes: Option<Vec<String>>,
+    /// Media upload parameters.
+    #[serde(rename = "mediaUpload")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub media_upload: Option<RestMethodMediaUpload>,
+}
+
+/// Links to 16x16 and 32x32 icons representing the API.
+#[derive(Serialize, Deserialize, Debug, Clone, Default)]
+pub struct RestDescriptionIcons {
+    /// The URL of the 16x16 icon.
+    #[serde(rename = "x16")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub x16: Option<String>,
+    /// The URL of the 32x32 icon.
+    #[serde(rename = "x32")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub x32: Option<String>,
+}
+
+/// The scope value.
+#[derive(Serialize, Deserialize, Debug, Clone, Default)]
+pub struct RestDescriptionAuthOauth2Scopes {
+    /// Description of scope.
+    #[serde(rename = "description")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub description: Option<String>,
+}
+
+/// OAuth 2.0 authentication information.
+#[derive(Serialize, Deserialize, Debug, Clone, Default)]
+pub struct RestDescriptionAuthOauth2 {
+    /// Available OAuth 2.0 scopes.
+    #[serde(rename = "scopes")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub scopes: Option<HashMap<String, RestDescriptionAuthOauth2Scopes>>,
+}
+
+/// Authentication information.
+#[derive(Serialize, Deserialize, Debug, Clone, Default)]
+pub struct RestDescriptionAuth {
+    /// OAuth 2.0 authentication information.
+    #[serde(rename = "oauth2")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub oauth2: Option<RestDescriptionAuthOauth2>,
+}
+
+///
+#[derive(Serialize, Deserialize, Debug, Clone, Default)]
+pub struct RestDescription {
+    /// API-level methods for this API.
+    #[serde(rename = "methods")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub methods: Option<HashMap<String, RestMethod>>,
+    #[serde(rename = "version_module")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub version_module: Option<bool>,
+    /// The base path for all REST requests.
+    #[serde(rename = "servicePath")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub service_path: Option<String>,
+    /// Enable exponential backoff for suitable methods in the generated clients.
+    #[serde(rename = "exponentialBackoffDefault")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub exponential_backoff_default: Option<bool>,
+    /// Links to 16x16 and 32x32 icons representing the API.
+    #[serde(rename = "icons")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub icons: Option<RestDescriptionIcons>,
+    /// The domain of the owner of this API. Together with the ownerName and a packagePath values, this can be used to generate a library for this API which would have a unique fully qualified name.
+    #[serde(rename = "ownerDomain")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub owner_domain: Option<String>,
+    /// The ID of this API.
+    #[serde(rename = "id")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub id: Option<String>,
+    /// The root URL under which all API services live.
+    #[serde(rename = "rootUrl")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub root_url: Option<String>,
+    /// The name of the owner of this API. See ownerDomain.
+    #[serde(rename = "ownerName")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub owner_name: Option<String>,
+    /// The version of this API.
+    #[serde(rename = "version")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub version: Option<String>,
+    /// The version of this API.
+    #[serde(rename = "revision")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub revision: Option<String>,
+    /// A link to human readable documentation for the API.
+    #[serde(rename = "documentationLink")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub documentation_link: Option<String>,
+    /// The path for REST batch requests.
+    #[serde(rename = "batchPath")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub batch_path: Option<String>,
+    /// The name of this API.
+    #[serde(rename = "name")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub name: Option<String>,
+    /// The description of this API.
+    #[serde(rename = "description")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub description: Option<String>,
+    /// A list of supported features for this API.
+    #[serde(rename = "features")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub features: Option<Vec<String>>,
+    /// [DEPRECATED] The base path for REST requests.
+    #[serde(rename = "basePath")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub base_path: Option<String>,
+    /// Common parameters that apply across all apis.
+    #[serde(rename = "parameters")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub parameters: Option<HashMap<String, JsonSchema>>,
+    /// Indicate the version of the Discovery API used to generate this doc.
+    #[serde(rename = "discoveryVersion")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub discovery_version: Option<String>,
+    /// Authentication information.
+    #[serde(rename = "auth")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub auth: Option<RestDescriptionAuth>,
+    /// The title of this API.
+    #[serde(rename = "title")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub title: Option<String>,
+    /// [DEPRECATED] The base URL for REST requests.
+    #[serde(rename = "baseUrl")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub base_url: Option<String>,
+    /// The kind for this response.
+    #[serde(rename = "kind")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub kind: Option<String>,
+    /// Labels for the status of this API, such as labs or deprecated.
+    #[serde(rename = "labels")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub labels: Option<Vec<String>>,
+    /// The ETag for this response.
+    #[serde(rename = "etag")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub etag: Option<String>,
+    /// The package of the owner of this API. See ownerDomain.
+    #[serde(rename = "packagePath")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub package_path: Option<String>,
+    /// The protocol described by this document.
+    #[serde(rename = "protocol")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub protocol: Option<String>,
+    /// The schemas for this API.
+    #[serde(rename = "schemas")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub schemas: Option<HashMap<String, JsonSchema>>,
+    /// The resources in this API.
+    #[serde(rename = "resources")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub resources: Option<HashMap<String, RestResource>>,
+    /// Indicates how the API name should be capitalized and split into various parts. Useful for generating pretty class names.
+    #[serde(rename = "canonicalName")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub canonical_name: Option<String>,
+}
+
+/// Additional information about this property.
+#[derive(Serialize, Deserialize, Debug, Clone, Default)]
+pub struct JsonSchemaAnnotations {
+    /// A list of methods for which this property is required on requests.
+    #[serde(rename = "required")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub required: Option<Vec<String>>,
+}
+
+///
+#[derive(Serialize, Deserialize, Debug, Clone, Default)]
+pub struct JsonSchemaVariantMap {
+    #[serde(rename = "$ref")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub dollarref: Option<String>,
+    #[serde(rename = "type_value")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub type_value: Option<String>,
+}
+
+/// In a variant data type, the value of one property is used to determine how to interpret the entire entity. Its value must exist in a map of descriminant values to schema names.
+#[derive(Serialize, Deserialize, Debug, Clone, Default)]
+pub struct JsonSchemaVariant {
+    /// The name of the type discriminant property.
+    #[serde(rename = "discriminant")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub discriminant: Option<String>,
+    /// The map of discriminant value to schema to use for parsing..
+    #[serde(rename = "map")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub map: Option<Vec<JsonSchemaVariantMap>>,
+}
+
+///
+#[derive(Serialize, Deserialize, Debug, Clone, Default)]
+pub struct JsonSchema {
+    /// A description of this object.
+    #[serde(rename = "description")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub description: Option<String>,
+    /// The maximum value of this parameter.
+    #[serde(rename = "maximum")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub maximum: Option<String>,
+    /// Whether this parameter may appear multiple times.
+    #[serde(rename = "repeated")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub repeated: Option<bool>,
+    /// Values this parameter may take (if it is an enum).
+    #[serde(rename = "enum")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub enums: Option<Vec<String>>,
+    /// The descriptions for the enums. Each position maps to the corresponding value in the "enum" array.
+    #[serde(rename = "enumDescriptions")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub enum_descriptions: Option<Vec<String>>,
+    /// Unique identifier for this schema.
+    #[serde(rename = "id")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub id: Option<String>,
+    #[serde(rename = "additionalProperties")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub additional_properties: Option<Box<JsonSchema>>,
+    /// If this is a schema for an object, list the schema for each property of this object.
+    #[serde(rename = "properties")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub properties: Option<HashMap<String, Box<JsonSchema>>>,
+    /// Additional information about this property.
+    #[serde(rename = "annotations")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub annotations: Option<JsonSchemaAnnotations>,
+    #[serde(rename = "items")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub items: Option<Box<JsonSchema>>,
+    /// In a variant data type, the value of one property is used to determine how to interpret the entire entity. Its value must exist in a map of descriminant values to schema names.
+    #[serde(rename = "variant")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub variant: Option<JsonSchemaVariant>,
+    /// Whether this parameter goes in the query or the path for REST requests.
+    #[serde(rename = "location")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub location: Option<String>,
+    /// Whether the parameter is required.
+    #[serde(rename = "required")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub required: Option<bool>,
+    /// The value is read-only, generated by the service. The value cannot be modified by the client. If the value is included in a POST, PUT, or PATCH request, it is ignored by the service.
+    #[serde(rename = "readOnly")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub read_only: Option<bool>,
+    /// The default value of this property (if one exists).
+    #[serde(rename = "default")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub default: Option<String>,
+    /// The minimum value of this parameter.
+    #[serde(rename = "minimum")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub minimum: Option<String>,
+    /// An additional regular expression or key that helps constrain the value. For more details see: http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.23
+    #[serde(rename = "format")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub format: Option<String>,
+    /// The value type for this schema. A list of values can be found here: http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.1
+    #[serde(rename = "type")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub typ: Option<String>,
+    /// The regular expression this parameter must conform to. Uses Java 6 regex format: http://docs.oracle.com/javase/6/docs/api/java/util/regex/Pattern.html
+    #[serde(rename = "pattern")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub pattern: Option<String>,
+    /// A reference to another schema. The value of this property is the "id" of another schema.
+    #[serde(rename = "$ref")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub dollarref: Option<String>,
+}
+
+/// Links to 16x16 and 32x32 icons representing the API.
+#[derive(Serialize, Deserialize, Debug, Clone, Default)]
+pub struct DirectoryListItemsIcons {
+    /// The URL of the 16x16 icon.
+    #[serde(rename = "x16")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub x16: Option<String>,
+    /// The URL of the 32x32 icon.
+    #[serde(rename = "x32")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub x32: Option<String>,
+}
+
+///
+#[derive(Serialize, Deserialize, Debug, Clone, Default)]
+pub struct DirectoryListItems {
+    /// A link to human readable documentation for the API.
+    #[serde(rename = "documentationLink")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub documentation_link: Option<String>,
+    /// True if this version is the preferred version to use.
+    #[serde(rename = "preferred")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub preferred: Option<bool>,
+    /// The version of the API.
+    #[serde(rename = "version")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub version: Option<String>,
+    /// A link to the discovery document.
+    #[serde(rename = "discoveryLink")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub discovery_link: Option<String>,
+    /// The description of this API.
+    #[serde(rename = "description")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub description: Option<String>,
+    /// Labels for the status of this API, such as labs or deprecated.
+    #[serde(rename = "labels")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub labels: Option<Vec<String>>,
+    /// Links to 16x16 and 32x32 icons representing the API.
+    #[serde(rename = "icons")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub icons: Option<DirectoryListItemsIcons>,
+    /// The title of this API.
+    #[serde(rename = "title")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub title: Option<String>,
+    /// The id of this API.
+    #[serde(rename = "id")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub id: Option<String>,
+    /// The kind for this response.
+    #[serde(rename = "kind")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub kind: Option<String>,
+    /// The URL for the discovery REST document.
+    #[serde(rename = "discoveryRestUrl")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub discovery_rest_url: Option<String>,
+    /// The name of the API.
+    #[serde(rename = "name")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub name: Option<String>,
+}
+
+///
+#[derive(Serialize, Deserialize, Debug, Clone, Default)]
+pub struct DirectoryList {
+    /// The kind for this response.
+    #[serde(rename = "kind")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub kind: Option<String>,
+    /// The individual directory entries. One entry per api/version pair.
+    #[serde(rename = "items")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub items: Option<Vec<DirectoryListItems>>,
+    /// Indicate the version of the Discovery API used to generate this doc.
+    #[serde(rename = "discoveryVersion")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub discovery_version: Option<String>,
+}
+
+///
+#[derive(Serialize, Deserialize, Debug, Clone, Default)]
+pub struct DiscoveryParams {
+    /// Data format for the response.
+    #[serde(rename = "alt")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub alt: Option<String>,
+    /// OAuth 2.0 token for the current user.
+    #[serde(rename = "oauth_token")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub oauth_token: Option<String>,
+    /// Returns response with indentations and line breaks.
+    #[serde(rename = "prettyPrint")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub pretty_print: Option<bool>,
+    /// Deprecated. Please use quotaUser instead.
+    #[serde(rename = "userIp")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub user_ip: Option<String>,
+    /// API key. Your API key identifies your project and provides you with API access, quota, and reports. Required unless you provide an OAuth 2.0 token.
+    #[serde(rename = "key")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub key: Option<String>,
+    /// An opaque string that represents a user for quota purposes. Must not exceed 40 characters.
+    #[serde(rename = "quotaUser")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub quota_user: Option<String>,
+    /// Selector specifying which fields to include in a partial response.
+    #[serde(rename = "fields")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub fields: Option<String>,
+}
+
+/// Parameters for the `apis.list` method.
+#[derive(Serialize, Deserialize, Debug, Clone, Default)]
+pub struct ApisListParams {
+    /// General attributes applying to any API call
+    #[serde(flatten)]
+    pub discovery_params: Option<DiscoveryParams>,
+    /// Return only the preferred version of an API.
+    #[serde(rename = "preferred")]
+    pub preferred: Option<bool>,
+    /// Only include APIs with the given name.
+    #[serde(rename = "name")]
+    pub name: Option<String>,
+}
+
+impl std::fmt::Display for ApisListParams {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        if let Some(ref v) = self.preferred {
+            write!(
+                f,
+                "&preferred={}",
+                percent_encode(format!("{}", v).as_bytes(), NON_ALPHANUMERIC).to_string()
+            )?;
+        }
+        if let Some(ref v) = self.name {
+            write!(
+                f,
+                "&name={}",
+                percent_encode(format!("{}", v).as_bytes(), NON_ALPHANUMERIC).to_string()
+            )?;
+        }
+        Ok(())
+    }
+}
+
+/// Parameters for the `apis.getRest` method.
+#[derive(Serialize, Deserialize, Debug, Clone, Default)]
+pub struct ApisGetRestParams {
+    /// General attributes applying to any API call
+    #[serde(flatten)]
+    pub discovery_params: Option<DiscoveryParams>,
+    /// The version of the API.
+    #[serde(rename = "version")]
+    pub version: String,
+    /// The name of the API.
+    #[serde(rename = "api")]
+    pub api: String,
+}
+
+impl std::fmt::Display for ApisGetRestParams {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        Ok(())
+    }
+}
+
+impl std::fmt::Display for DiscoveryParams {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        if let Some(ref v) = self.alt {
+            write!(
+                f,
+                "&alt={}",
+                percent_encode(format!("{}", v).as_bytes(), NON_ALPHANUMERIC).to_string()
+            )?;
+        }
+        if let Some(ref v) = self.oauth_token {
+            write!(
+                f,
+                "&oauth_token={}",
+                percent_encode(format!("{}", v).as_bytes(), NON_ALPHANUMERIC).to_string()
+            )?;
+        }
+        if let Some(ref v) = self.pretty_print {
+            write!(
+                f,
+                "&prettyPrint={}",
+                percent_encode(format!("{}", v).as_bytes(), NON_ALPHANUMERIC).to_string()
+            )?;
+        }
+        if let Some(ref v) = self.user_ip {
+            write!(
+                f,
+                "&userIp={}",
+                percent_encode(format!("{}", v).as_bytes(), NON_ALPHANUMERIC).to_string()
+            )?;
+        }
+        if let Some(ref v) = self.key {
+            write!(
+                f,
+                "&key={}",
+                percent_encode(format!("{}", v).as_bytes(), NON_ALPHANUMERIC).to_string()
+            )?;
+        }
+        if let Some(ref v) = self.quota_user {
+            write!(
+                f,
+                "&quotaUser={}",
+                percent_encode(format!("{}", v).as_bytes(), NON_ALPHANUMERIC).to_string()
+            )?;
+        }
+        if let Some(ref v) = self.fields {
+            write!(
+                f,
+                "&fields={}",
+                percent_encode(format!("{}", v).as_bytes(), NON_ALPHANUMERIC).to_string()
+            )?;
+        }
+        Ok(())
+    }
+}
+
+/// The Discovery Apis service represents the Apis resource.
+pub struct ApisService {
+    client: TlsClient,
+
+    base_url: String,
+    root_url: String,
+}
+
+impl ApisService {
+    /// Create a new ApisService object. The easiest way to call this is wrapping the Authenticator
+    /// into an `Rc`: `new(client.clone(), Rc::new(authenticator))`.
+    /// This way, one authenticator can be shared among several services.
+    pub fn new(client: TlsClient) -> ApisService {
+        ApisService {
+            client: client,
+            base_url: "https://www.googleapis.com/discovery/v1/".into(),
+            root_url: "https://www.googleapis.com/".into(),
+        }
+    }
+
+    /// Provide the base URL of this API. The returned URL is guaranteed to end with a '/'.
+    fn base_url(&self) -> String {
+        if self.base_url.ends_with("/") {
+            return self.base_url.clone();
+        }
+        return self.base_url.clone() + "/";
+    }
+    /// Provide the root URL of this API. The returned URL is guaranteed to end with a '/'.
+    fn root_url(&self) -> String {
+        if self.root_url.ends_with("/") {
+            return self.root_url.clone();
+        }
+        return self.root_url.clone();
+    }
+    /// Returns appropriate URLs for relative and absolute paths.
+    fn format_path(&self, path: &str) -> String {
+        if path.starts_with("/") {
+            return self.root_url().trim_end_matches("/").to_string() + path;
+        } else {
+            return self.base_url() + path;
+        }
+    }
+
+    #[cfg(test)]
+    /// Override API URLs. `base` is the base path relative to which (relative) method paths are interpreted,
+    /// whereas `root` is the URL relative to which absolute paths are interpreted.
+    pub fn set_urls(&mut self, base: String, root: String) {
+        self.base_url = base;
+        self.root_url = root;
+    }
+
+    /// Retrieve the list of APIs supported at this endpoint.
+    pub async fn list(&mut self, params: &ApisListParams) -> Result<DirectoryList> {
+        let rel_path = format!("apis",);
+        let path = self.format_path(rel_path.as_str());
+
+        let mut headers = vec![];
+
+        let mut url_params = format!("?{params}", params = params);
+        if let Some(ref api_params) = &params.discovery_params {
+            url_params.push_str(&format!("{}", api_params));
+        }
+
+        let full_uri = path + &url_params;
+
+        let opt_request: Option<&EmptyRequest> = None;
+        do_request(&self.client, &full_uri, &headers, "GET", opt_request).await
+    }
+
+    /// Retrieve the description of a particular version of an api.
+    pub async fn get_rest(&mut self, params: &ApisGetRestParams) -> Result<RestDescription> {
+        let rel_path = format!(
+            "apis/{api}/{version}/rest",
+            api = percent_encode(params.api.as_bytes(), NON_ALPHANUMERIC),
+            version = percent_encode(params.version.as_bytes(), NON_ALPHANUMERIC)
+        );
+        let path = self.format_path(rel_path.as_str());
+
+        let mut headers = vec![];
+
+        let mut url_params = format!("?{params}", params = params);
+        if let Some(ref api_params) = &params.discovery_params {
+            url_params.push_str(&format!("{}", api_params));
+        }
+
+        let full_uri = path + &url_params;
+
+        let opt_request: Option<&EmptyRequest> = None;
+        do_request(&self.client, &full_uri, &headers, "GET", opt_request).await
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/generate_rs/src/main.rs	Sun Dec 13 23:40:03 2020 +0100
@@ -0,0 +1,98 @@
+mod discovery_v1_types;
+
+use anyhow::{Error, Result};
+use clap::{App, Arg, SubCommand};
+
+enum ApiSource {
+    /// API ID to fetch
+    ApiId(String),
+    /// URL of document to fetch
+    ApiDoc(String),
+}
+
+struct RunConfig {
+    api: ApiSource,
+    discovery_base: String,
+}
+
+fn argparse() -> Result<RunConfig> {
+    let app = App::new("generate_rs")
+        .about("Generate asynchronous Rust stubs for Google REST APIs")
+        .arg(
+            Arg::with_name("api")
+                .long("api")
+                .value_name("API-ID")
+                .help("Google API ID, e.g. drive:v3 or discovery:v1."),
+        )
+        .arg(
+            Arg::with_name("discovery_base")
+                .long("discovery_base")
+                .value_name("DISCOVERY-BASE")
+                .help("Base URL of Discovery service"),
+        )
+        .arg(
+            Arg::with_name("doc_url")
+                .long("doc_url")
+                .value_name("DOC-URL")
+                .help("URL of Discovery document to process directly (instead of --api)"),
+        );
+    let matches = app.get_matches();
+
+    let api = match (matches.value_of("api"), matches.value_of("doc_url")) {
+        (Some(id), None) => ApiSource::ApiId(id.into()),
+        (None, Some(url)) => ApiSource::ApiDoc(url.into()),
+        (None, None) => return Err(anyhow::anyhow!("Please specify either --api or --doc_url")),
+        (Some(_), Some(_)) => {
+            return Err(anyhow::anyhow!("Please specify either --api or --doc_url"))
+        }
+    };
+
+    let discovery_base = matches
+        .value_of("discovery_base")
+        .unwrap_or("https://www.googleapis.com/discovery/v1/apis")
+        .into();
+    Ok(RunConfig {
+        api: api,
+        discovery_base: discovery_base,
+    })
+}
+
+async fn fetch_doc_by_api_id(
+    base_url: &str,
+    id: &str,
+) -> Result<discovery_v1_types::RestDescription> {
+    let list: discovery_v1_types::DirectoryList = fetch_url(base_url).await?;
+
+    if let Some(items) = list.items {
+        if let Some(found) = items
+            .iter()
+            .find(|it| it.id.as_ref().map(|s| s.as_str()).unwrap_or("") == id)
+        {
+            let url = found.discovery_rest_url.clone().unwrap();
+            return fetch_doc_by_url(url.as_str()).await;
+        }
+    }
+    Err(anyhow::anyhow!("No API with this ID could be found"))
+}
+
+async fn fetch_doc_by_url(doc_url: &str) -> Result<discovery_v1_types::RestDescription> {
+    fetch_url(doc_url).await
+}
+
+async fn fetch_url<Out: serde::de::DeserializeOwned>(url: &str) -> Result<Out> {
+    let doc = reqwest::get(url).await?.text().await?;
+    serde_json::from_reader(doc.as_bytes()).map_err(|e| e.into())
+}
+
+#[tokio::main]
+async fn main() {
+    let cfg = argparse().unwrap();
+
+    let doc = match cfg.api {
+        ApiSource::ApiDoc(url) => fetch_doc_by_url(url.as_str()).await.unwrap(),
+        ApiSource::ApiId(id) => fetch_doc_by_api_id(cfg.discovery_base.as_str(), id.as_str())
+            .await
+            .unwrap(),
+    };
+    println!("{:?}", doc);
+}