mirror of
https://github.com/OMGeeky/google-apis-rs.git
synced 2026-01-05 19:16:24 +01:00
Merge branch 'refactor'
This commit is contained in:
12
.github/workflows/rust.yml
vendored
12
.github/workflows/rust.yml
vendored
@@ -7,6 +7,14 @@ on:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
clippy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Run clippy
|
||||
run: |
|
||||
cargo clippy -- -D warnings
|
||||
build-and-test:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
@@ -17,9 +25,11 @@ jobs:
|
||||
- name: Run tests
|
||||
run: |
|
||||
source ~/.profile
|
||||
cargo test
|
||||
make test-gen
|
||||
make gen-all-cli cargo-api ARGS=test
|
||||
make cargo-api ARGS='check --no-default-features'
|
||||
make cargo-api ARGS=check
|
||||
make cargo-api ARGS=doc
|
||||
make cargo-cli ARGS=check
|
||||
make docs-all
|
||||
cargo test
|
||||
|
||||
@@ -23,6 +23,7 @@ serde_json = "^ 1.0"
|
||||
|
||||
base64 = "0.13.0"
|
||||
chrono = { version = "0.4.22", features = ["serde"] }
|
||||
url = "= 1.7"
|
||||
|
||||
yup-oauth2 = { version = "^ 7.0", optional = true }
|
||||
itertools = "^ 0.10"
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::str::FromStr;
|
||||
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
fn titlecase(source: &str, dest: &mut String) {
|
||||
@@ -28,7 +31,7 @@ fn snakecase(source: &str) -> String {
|
||||
}
|
||||
|
||||
/// A `FieldMask` as defined in `https://github.com/protocolbuffers/protobuf/blob/ec1a70913e5793a7d0a7b5fbf7e0e4f75409dd41/src/google/protobuf/field_mask.proto#L180`
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct FieldMask(Vec<String>);
|
||||
|
||||
impl Serialize for FieldMask {
|
||||
@@ -46,12 +49,13 @@ impl<'de> Deserialize<'de> for FieldMask {
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s: &str = Deserialize::deserialize(deserializer)?;
|
||||
Ok(FieldMask::from_str(s))
|
||||
Ok(FieldMask::from_str(s).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl FieldMask {
|
||||
fn from_str(s: &str) -> FieldMask {
|
||||
impl FromStr for FieldMask {
|
||||
type Err = std::convert::Infallible;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut in_quotes = false;
|
||||
let mut prev_ind = 0;
|
||||
let mut paths = Vec::new();
|
||||
@@ -66,17 +70,19 @@ impl FieldMask {
|
||||
}
|
||||
}
|
||||
paths.push(snakecase(&s[prev_ind..]));
|
||||
FieldMask(paths)
|
||||
Ok(FieldMask(paths))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_string(&self) -> String {
|
||||
impl Display for FieldMask {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let mut repr = String::new();
|
||||
for path in &self.0 {
|
||||
titlecase(path, &mut repr);
|
||||
repr.push(',');
|
||||
}
|
||||
repr.pop();
|
||||
repr
|
||||
f.write_str(&repr)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
pub mod auth;
|
||||
pub mod field_mask;
|
||||
pub mod serde;
|
||||
pub mod url;
|
||||
|
||||
use std::error;
|
||||
use std::error::Error as StdError;
|
||||
@@ -23,7 +24,6 @@ use serde_json as json;
|
||||
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use tokio::time::sleep;
|
||||
use tower_service;
|
||||
|
||||
pub use auth::{GetToken, NoToken};
|
||||
pub use chrono;
|
||||
@@ -41,6 +41,12 @@ pub enum Retry {
|
||||
After(Duration),
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
pub enum UploadProtocol {
|
||||
Simple,
|
||||
Resumable,
|
||||
}
|
||||
|
||||
/// Identifies the Hub. There is only one per library, this trait is supposed
|
||||
/// to make intended use more explicit.
|
||||
/// The hub allows to access all resource methods more easily.
|
||||
@@ -365,7 +371,7 @@ impl<'a> MultiPartReader<'a> {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
CONTENT_TYPE,
|
||||
hyper::header::HeaderValue::from_str(&mime_type.to_string()).unwrap(),
|
||||
hyper::header::HeaderValue::from_str(mime_type.as_ref()).unwrap(),
|
||||
);
|
||||
headers.insert(CONTENT_LENGTH, size.into());
|
||||
self.raw_parts.push((headers, reader));
|
||||
@@ -474,7 +480,7 @@ impl<'a> Read for MultiPartReader<'a> {
|
||||
///
|
||||
/// Generated via rustc --pretty expanded -Z unstable-options, and manually
|
||||
/// processed to be more readable.
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
pub struct XUploadContentType(pub Mime);
|
||||
|
||||
impl ::std::ops::Deref for XUploadContentType {
|
||||
@@ -494,7 +500,7 @@ impl Display for XUploadContentType {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct Chunk {
|
||||
pub first: u64,
|
||||
pub last: u64,
|
||||
@@ -530,7 +536,7 @@ impl FromStr for Chunk {
|
||||
}
|
||||
|
||||
/// Implements the Content-Range header, for serialization only
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct ContentRange {
|
||||
pub range: Option<Chunk>,
|
||||
pub total_length: u64,
|
||||
@@ -549,7 +555,7 @@ impl ContentRange {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct RangeResponseHeader(pub Chunk);
|
||||
|
||||
impl RangeResponseHeader {
|
||||
@@ -558,7 +564,7 @@ impl RangeResponseHeader {
|
||||
if let Ok(s) = std::str::from_utf8(raw) {
|
||||
const PREFIX: &str = "bytes ";
|
||||
if let Some(stripped) = s.strip_prefix(PREFIX) {
|
||||
if let Ok(c) = <Chunk as FromStr>::from_str(&stripped) {
|
||||
if let Ok(c) = <Chunk as FromStr>::from_str(stripped) {
|
||||
return RangeResponseHeader(c);
|
||||
}
|
||||
}
|
||||
@@ -771,7 +777,7 @@ mod test_api {
|
||||
use std::str::FromStr;
|
||||
|
||||
use ::serde::{Deserialize, Serialize};
|
||||
use mime;
|
||||
|
||||
use serde_json as json;
|
||||
|
||||
#[test]
|
||||
@@ -802,7 +808,7 @@ mod test_api {
|
||||
json::to_string(&<Bar as Default>::default()).unwrap();
|
||||
|
||||
let j = "{\"snooSnoo\":\"foo\"}";
|
||||
let b: Bar = json::from_str(&j).unwrap();
|
||||
let b: Bar = json::from_str(j).unwrap();
|
||||
assert_eq!(b.snoo_snoo, "foo");
|
||||
|
||||
// We can't have unknown fields with structs.
|
||||
|
||||
@@ -61,9 +61,9 @@ pub mod duration {
|
||||
};
|
||||
|
||||
let (seconds, nanoseconds) = if let Some((seconds, nanos)) = value.split_once('.') {
|
||||
let is_neg = seconds.starts_with("-");
|
||||
let is_neg = seconds.starts_with('-');
|
||||
let seconds = i64::from_str(seconds)?;
|
||||
let nano_magnitude = nanos.chars().filter(|c| c.is_digit(10)).count() as u32;
|
||||
let nano_magnitude = nanos.chars().filter(|c| c.is_ascii_digit()).count() as u32;
|
||||
if nano_magnitude > 9 {
|
||||
// not enough precision to model the remaining digits
|
||||
return Err(ParseDurationError::NanosTooSmall);
|
||||
@@ -261,10 +261,7 @@ mod test {
|
||||
fn urlsafe_base64_de_success_cases() {
|
||||
let wrapper: Base64Wrapper =
|
||||
serde_json::from_str(r#"{"bytes": "aGVsbG8gd29ybGQ="}"#).unwrap();
|
||||
assert_eq!(
|
||||
Some(b"hello world".as_slice()),
|
||||
wrapper.bytes.as_ref().map(Vec::as_slice)
|
||||
);
|
||||
assert_eq!(Some(b"hello world".as_slice()), wrapper.bytes.as_deref());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
71
google-apis-common/src/url.rs
Normal file
71
google-apis-common/src/url.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use ::url::percent_encoding::{percent_encode, DEFAULT_ENCODE_SET};
|
||||
use ::url::Url;
|
||||
|
||||
pub struct Params<'a> {
|
||||
params: Vec<(&'a str, Cow<'a, str>)>,
|
||||
}
|
||||
|
||||
impl<'a> Params<'a> {
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
Self {
|
||||
params: Vec::with_capacity(capacity),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push<I: Into<Cow<'a, str>>>(&mut self, param: &'a str, value: I) {
|
||||
self.params.push((param, value.into()))
|
||||
}
|
||||
|
||||
pub fn extend<I: Iterator<Item = (&'a String, IC)>, IC: Into<Cow<'a, str>>>(
|
||||
&mut self,
|
||||
params: I,
|
||||
) {
|
||||
self.params
|
||||
.extend(params.map(|(k, v)| (k.as_str(), v.into())))
|
||||
}
|
||||
|
||||
pub fn get(&self, param_name: &str) -> Option<&str> {
|
||||
self.params
|
||||
.iter()
|
||||
.find(|(name, _)| name == ¶m_name)
|
||||
.map(|(_, param)| param.as_ref())
|
||||
}
|
||||
|
||||
pub fn uri_replacement(
|
||||
&self,
|
||||
url: String,
|
||||
param: &str,
|
||||
from: &str,
|
||||
url_encode: bool,
|
||||
) -> String {
|
||||
if url_encode {
|
||||
let mut replace_with: Cow<str> = self.get(param).unwrap_or_default().into();
|
||||
if from.as_bytes()[1] == b'+' {
|
||||
replace_with = percent_encode(replace_with.as_bytes(), DEFAULT_ENCODE_SET)
|
||||
.to_string()
|
||||
.into();
|
||||
}
|
||||
url.replace(from, &replace_with)
|
||||
} else {
|
||||
let replace_with = self
|
||||
.get(param)
|
||||
.expect("to find substitution value in params");
|
||||
|
||||
url.replace(from, replace_with)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_params(&mut self, to_remove: &[&str]) {
|
||||
self.params.retain(|(n, _)| !to_remove.contains(n))
|
||||
}
|
||||
|
||||
pub fn inner_mut(&mut self) -> &mut Vec<(&'a str, Cow<'a, str>)> {
|
||||
self.params.as_mut()
|
||||
}
|
||||
|
||||
pub fn parse_with_url(&self, url: &str) -> Url {
|
||||
Url::parse_with_params(url, &self.params).unwrap()
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ edition = "2021"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
mime = "0.2"
|
||||
mime = "^ 0.3"
|
||||
yup-oauth2 = "^ 7.0"
|
||||
serde = "1"
|
||||
serde_json = "1"
|
||||
|
||||
@@ -50,11 +50,11 @@ pub fn remove_json_null_values(value: &mut Value) {
|
||||
Value::Object(ref mut map) => {
|
||||
let mut for_removal = Vec::new();
|
||||
|
||||
for (key, mut value) in map.iter_mut() {
|
||||
for (key, value) in map.iter_mut() {
|
||||
if value.is_null() {
|
||||
for_removal.push(key.clone());
|
||||
} else {
|
||||
remove_json_null_values(&mut value);
|
||||
remove_json_null_values(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,9 +221,9 @@ impl FieldCursor {
|
||||
|
||||
let push_field = |fs: &mut String, f: &mut String| {
|
||||
if !f.is_empty() {
|
||||
fs.push_str(match did_you_mean(&f, possible_values) {
|
||||
fs.push_str(match did_you_mean(f, possible_values) {
|
||||
Some(candidate) => candidate,
|
||||
None => &f,
|
||||
None => f,
|
||||
});
|
||||
f.truncate(0);
|
||||
}
|
||||
@@ -264,7 +264,7 @@ impl FieldCursor {
|
||||
for field in &self.0[..self.0.len() - 1] {
|
||||
let tmp = object;
|
||||
object = match *tmp {
|
||||
Value::Object(ref mut mapping) => assure_entry(mapping, &field),
|
||||
Value::Object(ref mut mapping) => assure_entry(mapping, field),
|
||||
_ => panic!("We don't expect non-object Values here ..."),
|
||||
};
|
||||
}
|
||||
@@ -276,18 +276,18 @@ impl FieldCursor {
|
||||
|value: &str, jtype: JsonType, err: &mut InvalidOptionsError| -> Value {
|
||||
match jtype {
|
||||
JsonType::Boolean => {
|
||||
Value::Bool(arg_from_str(value, err, &field, "boolean"))
|
||||
Value::Bool(arg_from_str(value, err, field, "boolean"))
|
||||
}
|
||||
JsonType::Int => Value::Number(
|
||||
json::Number::from_f64(arg_from_str(value, err, &field, "int"))
|
||||
json::Number::from_f64(arg_from_str(value, err, field, "int"))
|
||||
.expect("valid f64"),
|
||||
),
|
||||
JsonType::Uint => Value::Number(
|
||||
json::Number::from_f64(arg_from_str(value, err, &field, "uint"))
|
||||
json::Number::from_f64(arg_from_str(value, err, field, "uint"))
|
||||
.expect("valid f64"),
|
||||
),
|
||||
JsonType::Float => Value::Number(
|
||||
json::Number::from_f64(arg_from_str(value, err, &field, "float"))
|
||||
json::Number::from_f64(arg_from_str(value, err, field, "float"))
|
||||
.expect("valid f64"),
|
||||
),
|
||||
JsonType::String => Value::String(value.to_owned()),
|
||||
@@ -315,7 +315,7 @@ impl FieldCursor {
|
||||
let (key, value) = parse_kv_arg(value, err, true);
|
||||
let jval = to_jval(value.unwrap_or(""), type_info.jtype, err);
|
||||
|
||||
match *assure_entry(mapping, &field) {
|
||||
match *assure_entry(mapping, field) {
|
||||
Value::Object(ref mut value_map) => {
|
||||
if value_map.insert(key.to_owned(), jval).is_some() {
|
||||
err.issues.push(CLIError::Field(FieldError::Duplicate(
|
||||
@@ -590,7 +590,7 @@ impl fmt::Display for CLIError {
|
||||
arg_name, value, type_name, err_desc
|
||||
),
|
||||
CLIError::UnknownParameter(ref param_name, ref possible_values) => {
|
||||
let suffix = match did_you_mean(param_name, &possible_values) {
|
||||
let suffix = match did_you_mean(param_name, possible_values) {
|
||||
Some(v) => format!(" Did you mean '{}' ?", v),
|
||||
None => String::new(),
|
||||
};
|
||||
@@ -620,6 +620,15 @@ pub struct InvalidOptionsError {
|
||||
pub exit_code: i32,
|
||||
}
|
||||
|
||||
impl Default for InvalidOptionsError {
|
||||
fn default() -> Self {
|
||||
InvalidOptionsError {
|
||||
issues: Vec::new(),
|
||||
exit_code: 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for InvalidOptionsError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
for issue in &self.issues {
|
||||
@@ -638,10 +647,7 @@ impl InvalidOptionsError {
|
||||
}
|
||||
|
||||
pub fn new() -> InvalidOptionsError {
|
||||
InvalidOptionsError {
|
||||
issues: Vec::new(),
|
||||
exit_code: 1,
|
||||
}
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ import collections
|
||||
from copy import deepcopy
|
||||
from random import (randint, random, choice)
|
||||
|
||||
from generator.lib import types
|
||||
|
||||
SPLIT_START = '>>>>>>>'
|
||||
SPLIT_END = '<<<<<<<'
|
||||
|
||||
@@ -56,7 +58,7 @@ JSON_TYPE_RND_MAP = {'boolean': lambda: str(bool(randint(0, 1))).lower(),
|
||||
'number' : lambda: random(),
|
||||
'int32' : lambda: randint(-101, -1),
|
||||
'int64' : lambda: randint(-101, -1),
|
||||
'string': lambda: '%s' % choice(util.words).lower()}
|
||||
'string': lambda: '%s' % choice(types.WORDS).lower()}
|
||||
|
||||
JSON_TYPE_TO_ENUM_MAP = {'boolean' : 'Boolean',
|
||||
'integer' : 'Int',
|
||||
@@ -74,17 +76,6 @@ CTYPE_TO_ENUM_MAP = {CTYPE_POD: 'Pod',
|
||||
CTYPE_ARRAY: 'Vec',
|
||||
CTYPE_MAP: 'Map'}
|
||||
|
||||
JSON_TYPE_VALUE_MAP = {'boolean': 'false',
|
||||
'integer' : '-0',
|
||||
'uint32' : '0',
|
||||
'uint64' : '0',
|
||||
'float' : '0.0',
|
||||
'double' : '0.0',
|
||||
'number' : '0.0',
|
||||
'int32' : '-0',
|
||||
'int64' : '-0',
|
||||
'string': ''}
|
||||
|
||||
assert len(set(JSON_TYPE_RND_MAP.keys()) ^ POD_TYPES) == 0
|
||||
|
||||
def new_method_context(resource, method, c):
|
||||
|
||||
97
src/generator/lib/types.py
Normal file
97
src/generator/lib/types.py
Normal file
@@ -0,0 +1,97 @@
|
||||
from .rust_type import Base, HashMap, Vec
|
||||
from random import randint, random, choice, seed
|
||||
|
||||
seed(1337)
|
||||
|
||||
WORDS = [
|
||||
w.strip(',') for w in
|
||||
"Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.".split(
|
||||
' ')]
|
||||
|
||||
|
||||
def chrono_date(y=None, m=None, d=None):
|
||||
y = randint(1, 9999) if y is None else y
|
||||
m = randint(1, 12) if m is None else m
|
||||
d = randint(1, 31) if d is None else d
|
||||
return f"chrono::NaiveDate::from_ymd({y}, {m}, {d})"
|
||||
|
||||
|
||||
CHRONO_PATH = "client::chrono"
|
||||
CHRONO_DATETIME = f"{CHRONO_PATH}::DateTime<{CHRONO_PATH}::offset::Utc>"
|
||||
CHRONO_DATE = f"{CHRONO_PATH}::NaiveDate"
|
||||
USE_FORMAT = 'use_format_field'
|
||||
CHRONO_UTC_NOW = "chrono::Utc::now()"
|
||||
|
||||
RUST_TYPE_MAP = {
|
||||
'boolean': Base("bool"),
|
||||
'integer': USE_FORMAT,
|
||||
'number': USE_FORMAT,
|
||||
'uint32': Base("u32"),
|
||||
'double': Base("f64"),
|
||||
'float': Base("f32"),
|
||||
'int32': Base("i32"),
|
||||
'any': Base("String"), # TODO: Figure out how to handle it. It's 'interface' in Go ...
|
||||
'int64': Base("i64"),
|
||||
'uint64': Base("u64"),
|
||||
'array': Vec(None),
|
||||
'string': Base("String"),
|
||||
'object': HashMap(None, None),
|
||||
# https://github.com/protocolbuffers/protobuf/blob/ec1a70913e5793a7d0a7b5fbf7e0e4f75409dd41/src/google/protobuf/timestamp.proto
|
||||
# In JSON format, the Timestamp type is encoded as a string in the [RFC 3339] format
|
||||
'google-datetime': Base(CHRONO_DATETIME),
|
||||
# Per .json files: RFC 3339 timestamp
|
||||
'date-time': Base(CHRONO_DATETIME),
|
||||
# Per .json files: A date in RFC 3339 format with only the date part
|
||||
# e.g. "2013-01-15"
|
||||
'date': Base(CHRONO_DATE),
|
||||
# https://github.com/protocolbuffers/protobuf/blob/ec1a70913e5793a7d0a7b5fbf7e0e4f75409dd41/src/google/protobuf/duration.proto
|
||||
'google-duration': Base(f"{CHRONO_PATH}::Duration"),
|
||||
# guessing bytes is universally url-safe b64
|
||||
"byte": Vec(Base("u8")),
|
||||
# https://github.com/protocolbuffers/protobuf/blob/ec1a70913e5793a7d0a7b5fbf7e0e4f75409dd41/src/google/protobuf/field_mask.proto
|
||||
"google-fieldmask": Base("client::FieldMask")
|
||||
}
|
||||
|
||||
RUST_TYPE_RND_MAP = {
|
||||
'bool': lambda: str(bool(randint(0, 1))).lower(),
|
||||
'u32': lambda: randint(0, 100),
|
||||
'u64': lambda: randint(0, 100),
|
||||
'f64': lambda: random(),
|
||||
'f32': lambda: random(),
|
||||
'i32': lambda: randint(-101, -1),
|
||||
'i64': lambda: randint(-101, -1),
|
||||
'String': lambda: '"%s"' % choice(WORDS),
|
||||
'&str': lambda: '"%s"' % choice(WORDS),
|
||||
'&Vec<String>': lambda: '&vec!["%s".into()]' % choice(WORDS),
|
||||
"Vec<u8>": lambda: f"vec![0, 1, 2, 3]",
|
||||
# why a reference to Vec? Because it works. Should be slice, but who knows how typing works here.
|
||||
"&Vec<u8>": lambda: f"&vec![0, 1, 2, 3]",
|
||||
# TODO: styling this
|
||||
f"{CHRONO_PATH}::Duration": lambda: f"chrono::Duration::seconds({randint(0, 9999999)})",
|
||||
CHRONO_DATE: chrono_date,
|
||||
CHRONO_DATETIME: lambda: CHRONO_UTC_NOW,
|
||||
"FieldMask": lambda: f"FieldMask(vec![{choice(WORDS)}])",
|
||||
}
|
||||
|
||||
JSON_TO_RUST_DEFAULT = {
|
||||
'boolean': 'false',
|
||||
'uint32': '0',
|
||||
'uint64': '0',
|
||||
'int32': "-0",
|
||||
'int64': "-0",
|
||||
'float': '0.0',
|
||||
'double': '0.0',
|
||||
'string': "\"\"",
|
||||
'google-datetime': CHRONO_UTC_NOW,
|
||||
'date-time': CHRONO_UTC_NOW,
|
||||
'date': chrono_date(2000, 1, 1),
|
||||
# https://github.com/protocolbuffers/protobuf/blob/ec1a70913e5793a7d0a7b5fbf7e0e4f75409dd41/src/google/protobuf/duration.proto
|
||||
'google-duration': "chrono::Duration::seconds(0)",
|
||||
# guessing bytes is universally url-safe b64
|
||||
"byte": "b\"hello world\"",
|
||||
# https://github.com/protocolbuffers/protobuf/blob/ec1a70913e5793a7d0a7b5fbf7e0e4f75409dd41/src/google/protobuf/field_mask.proto
|
||||
"google-fieldmask": "FieldMask::default()"
|
||||
}
|
||||
|
||||
|
||||
assert set(JSON_TO_RUST_DEFAULT.keys()).issubset(set(RUST_TYPE_MAP.keys()))
|
||||
@@ -3,12 +3,10 @@ import re
|
||||
import subprocess
|
||||
|
||||
from dataclasses import dataclass
|
||||
from random import (randint, random, choice, seed)
|
||||
from typing import Any, Dict, List, Mapping, Tuple
|
||||
from copy import deepcopy
|
||||
from .rust_type import Base, Box, HashMap, Vec, Option, RustType
|
||||
|
||||
seed(1337)
|
||||
from .types import RUST_TYPE_MAP, RUST_TYPE_RND_MAP
|
||||
|
||||
re_linestart = re.compile('^', flags=re.MULTILINE)
|
||||
re_spaces_after_newline = re.compile('^ {4}', flags=re.MULTILINE)
|
||||
@@ -20,40 +18,7 @@ re_desc_parts = re.compile(
|
||||
re_find_replacements = re.compile(r"\{[/\+]?\w+\*?\}")
|
||||
|
||||
HTTP_METHODS = set(("OPTIONS", "GET", "POST", "PUT", "DELETE", "HEAD", "TRACE", "CONNECT", "PATCH"))
|
||||
CHRONO_PATH = "client::chrono"
|
||||
CHRONO_DATETIME = f"{CHRONO_PATH}::DateTime<{CHRONO_PATH}::offset::Utc>"
|
||||
CHRONO_DATE = f"{CHRONO_PATH}::NaiveDate"
|
||||
USE_FORMAT = 'use_format_field'
|
||||
|
||||
RUST_TYPE_MAP = {
|
||||
'boolean': Base("bool"),
|
||||
'integer': USE_FORMAT,
|
||||
'number': USE_FORMAT,
|
||||
'uint32': Base("u32"),
|
||||
'double': Base("f64"),
|
||||
'float': Base("f32"),
|
||||
'int32': Base("i32"),
|
||||
'any': Base("String"), # TODO: Figure out how to handle it. It's 'interface' in Go ...
|
||||
'int64': Base("i64"),
|
||||
'uint64': Base("u64"),
|
||||
'array': Vec(None),
|
||||
'string': Base("String"),
|
||||
'object': HashMap(None, None),
|
||||
# https://github.com/protocolbuffers/protobuf/blob/ec1a70913e5793a7d0a7b5fbf7e0e4f75409dd41/src/google/protobuf/timestamp.proto
|
||||
# In JSON format, the Timestamp type is encoded as a string in the [RFC 3339] format
|
||||
'google-datetime': Base(CHRONO_DATETIME),
|
||||
# Per .json files: RFC 3339 timestamp
|
||||
'date-time': Base(CHRONO_DATETIME),
|
||||
# Per .json files: A date in RFC 3339 format with only the date part
|
||||
# e.g. "2013-01-15"
|
||||
'date': Base(CHRONO_DATE),
|
||||
# https://github.com/protocolbuffers/protobuf/blob/ec1a70913e5793a7d0a7b5fbf7e0e4f75409dd41/src/google/protobuf/duration.proto
|
||||
'google-duration': Base(f"{CHRONO_PATH}::Duration"),
|
||||
# guessing bytes is universally url-safe b64
|
||||
"byte": Vec(Base("u8")),
|
||||
# https://github.com/protocolbuffers/protobuf/blob/ec1a70913e5793a7d0a7b5fbf7e0e4f75409dd41/src/google/protobuf/field_mask.proto
|
||||
"google-fieldmask": Base("client::FieldMask")
|
||||
}
|
||||
|
||||
RESERVED_WORDS = set(('abstract', 'alignof', 'as', 'become', 'box', 'break', 'const', 'continue', 'crate', 'do',
|
||||
'else', 'enum', 'extern', 'false', 'final', 'fn', 'for', 'if', 'impl', 'in', 'let', 'loop',
|
||||
@@ -61,35 +26,8 @@ RESERVED_WORDS = set(('abstract', 'alignof', 'as', 'become', 'box', 'break', 'co
|
||||
'return', 'sizeof', 'static', 'self', 'struct', 'super', 'true', 'trait', 'type', 'typeof',
|
||||
'unsafe', 'unsized', 'use', 'virtual', 'where', 'while', 'yield'))
|
||||
|
||||
words = [w.strip(',') for w in
|
||||
"Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.".split(
|
||||
' ')]
|
||||
|
||||
|
||||
def chrono_date():
|
||||
return f"chrono::NaiveDate::from_ymd({randint(1, 9999)}, {randint(1, 12)}, {randint(1, 31)})"
|
||||
|
||||
|
||||
RUST_TYPE_RND_MAP = {
|
||||
'bool': lambda: str(bool(randint(0, 1))).lower(),
|
||||
'u32': lambda: randint(0, 100),
|
||||
'u64': lambda: randint(0, 100),
|
||||
'f64': lambda: random(),
|
||||
'f32': lambda: random(),
|
||||
'i32': lambda: randint(-101, -1),
|
||||
'i64': lambda: randint(-101, -1),
|
||||
'String': lambda: '"%s"' % choice(words),
|
||||
'&str': lambda: '"%s"' % choice(words),
|
||||
'&Vec<String>': lambda: '&vec!["%s".into()]' % choice(words),
|
||||
"Vec<u8>": lambda: f"vec![0, 1, 2, 3]",
|
||||
# why a reference to Vec? Because it works. Should be slice, but who knows how typing works here.
|
||||
"&Vec<u8>": lambda: f"&vec![0, 1, 2, 3]",
|
||||
# TODO: styling this
|
||||
f"{CHRONO_PATH}::Duration": lambda: f"chrono::Duration::seconds({randint(0, 9999999)})",
|
||||
CHRONO_DATE: chrono_date,
|
||||
CHRONO_DATETIME: lambda: f"chrono::Utc::now()",
|
||||
"FieldMask": lambda: f"FieldMask(vec![{choice(words)}])",
|
||||
}
|
||||
TREF = '$ref'
|
||||
IO_RESPONSE = 'response'
|
||||
IO_REQUEST = 'request'
|
||||
@@ -1222,8 +1160,9 @@ def string_impl(p):
|
||||
"byte": lambda x: f"::client::serde::urlsafe_base64::to_string(&{x})",
|
||||
"google-datetime": lambda x: f"::client::serde::datetime_to_string(&{x})",
|
||||
"date-time": lambda x: f"::client::serde::datetime_to_string(&{x})",
|
||||
"google-fieldmask": lambda x: f"{x}.to_string()"
|
||||
}.get(p.get("format"), lambda x: f"{x}.to_string()")
|
||||
"google-fieldmask": lambda x: f"{x}.to_string()",
|
||||
"string": lambda x: x
|
||||
}.get(p.get("format", p["type"]), lambda x: f"{x}.to_string()")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -14,6 +14,13 @@
|
||||
is_repeated_property, setter_fn_name, ADD_SCOPE_FN, ADD_SCOPES_FN, rust_doc_sanitize,
|
||||
CLEAR_SCOPES_FN, items, string_impl)
|
||||
|
||||
SIMPLE = "simple"
|
||||
RESUMABLE = "resumable"
|
||||
PROTOCOL_TYPE_MAP = {
|
||||
SIMPLE: "client::UploadProtocol::Simple",
|
||||
RESUMABLE: "client::UploadProtocol::Resumable"
|
||||
}
|
||||
|
||||
def get_parts(part_prop):
|
||||
if not part_prop:
|
||||
return list()
|
||||
@@ -450,11 +457,11 @@ match result {
|
||||
type_params = '<%s>' % mtype_param
|
||||
qualifier = ''
|
||||
where = '\n\t\twhere ' + mtype_param + ': client::ReadSeek'
|
||||
add_args = (', mut reader: %s, reader_mime_type: mime::Mime' % mtype_param) + ", protocol: &'static str"
|
||||
add_args = (', mut reader: %s, reader_mime_type: mime::Mime' % mtype_param) + ", protocol: client::UploadProtocol"
|
||||
for p in media_params:
|
||||
if p.protocol == 'simple':
|
||||
if p.protocol == SIMPLE:
|
||||
simple_media_param = p
|
||||
elif p.protocol == 'resumable':
|
||||
elif p.protocol == RESUMABLE:
|
||||
resumable_media_param = p
|
||||
# end handle media params
|
||||
|
||||
@@ -519,28 +526,34 @@ match result {
|
||||
/// Perform the operation you have build so far.
|
||||
% endif
|
||||
${action_fn} {
|
||||
% if URL_ENCODE in special_cases:
|
||||
use url::percent_encoding::{percent_encode, DEFAULT_ENCODE_SET};
|
||||
% endif
|
||||
use std::io::{Read, Seek};
|
||||
use hyper::header::{CONTENT_TYPE, CONTENT_LENGTH, AUTHORIZATION, USER_AGENT, LOCATION};
|
||||
use client::ToParts;
|
||||
use client::{ToParts, url::Params};
|
||||
use std::borrow::Cow;
|
||||
|
||||
let mut dd = client::DefaultDelegate;
|
||||
let mut dlg: &mut dyn client::Delegate = match ${delegate} {
|
||||
Some(d) => d,
|
||||
None => &mut dd
|
||||
};
|
||||
let mut dlg: &mut dyn client::Delegate = ${delegate}.unwrap_or(&mut dd);
|
||||
dlg.begin(client::MethodInfo { id: "${m.id}",
|
||||
http_method: ${method_name_to_variant(m.httpMethod)} });
|
||||
let mut params: Vec<(&str, String)> = Vec::with_capacity(${len(params) + len(reserved_params)} + ${paddfields}.len());
|
||||
|
||||
## TODO: Should go into validation function?
|
||||
## Additional params - may not overlap with optional params
|
||||
for &field in [${', '.join(enclose_in('"', reserved_params + [p.name for p in field_params]))}].iter() {
|
||||
if ${paddfields}.contains_key(field) {
|
||||
${delegate_finish}(false);
|
||||
return Err(client::Error::FieldClash(field));
|
||||
}
|
||||
}
|
||||
|
||||
let mut params = Params::with_capacity(${len(params) + len(reserved_params)} + ${paddfields}.len());
|
||||
<%
|
||||
if media_params and 'mediaUpload' in m:
|
||||
upload_type_map = dict()
|
||||
for mp in media_params:
|
||||
if mp.protocol == 'simple':
|
||||
if mp.protocol == SIMPLE:
|
||||
upload_type_map[mp.protocol] = m.mediaUpload.protocols.simple.multipart and 'multipart' or 'media'
|
||||
break
|
||||
# for each meadia param
|
||||
# for each media param
|
||||
# end build media param map
|
||||
%>\
|
||||
% for p in field_params:
|
||||
@@ -569,50 +582,36 @@ match result {
|
||||
% if p.get('repeated', False):
|
||||
if ${pname}.len() > 0 {
|
||||
for f in ${pname}.iter() {
|
||||
params.push(("${p.name}", ${to_string_impl("f")}));
|
||||
params.push("${p.name}", ${to_string_impl("f")});
|
||||
}
|
||||
}
|
||||
% elif not is_required_property(p):
|
||||
if let Some(value) = ${pname}.as_ref() {
|
||||
params.push(("${p.name}", ${to_string_impl("value")}));
|
||||
params.push("${p.name}", ${to_string_impl("value")});
|
||||
}
|
||||
% else:
|
||||
params.push(("${p.name}", ${to_string_impl(pname)}));
|
||||
params.push("${p.name}", ${to_string_impl(pname)});
|
||||
% endif
|
||||
% endfor
|
||||
## Additional params - may not overlap with optional params
|
||||
for &field in [${', '.join(enclose_in('"', reserved_params + [p.name for p in field_params]))}].iter() {
|
||||
if ${paddfields}.contains_key(field) {
|
||||
${delegate_finish}(false);
|
||||
return Err(client::Error::FieldClash(field));
|
||||
}
|
||||
}
|
||||
for (name, value) in ${paddfields}.iter() {
|
||||
params.push((&name, value.clone()));
|
||||
}
|
||||
|
||||
params.extend(${paddfields}.iter());
|
||||
|
||||
% if response_schema:
|
||||
% if supports_download:
|
||||
let (json_field_missing, enable_resource_parsing) = {
|
||||
let mut enable = true;
|
||||
let mut field_missing = true;
|
||||
for &(name, ref value) in params.iter() {
|
||||
if name == "alt" {
|
||||
field_missing = false;
|
||||
enable = value == "json";
|
||||
break;
|
||||
}
|
||||
let (alt_field_missing, enable_resource_parsing) = {
|
||||
if let Some(value) = params.get("alt") {
|
||||
(false, value == "json")
|
||||
} else {
|
||||
(true, true)
|
||||
}
|
||||
(field_missing, enable)
|
||||
};
|
||||
if json_field_missing {
|
||||
params.push(("alt", "json".to_string()));
|
||||
if alt_field_missing {
|
||||
params.push("alt", "json");
|
||||
}
|
||||
% else:
|
||||
params.push(("alt", "json".to_string()));
|
||||
params.push("alt", "json");
|
||||
% endif ## supportsMediaDownload
|
||||
% endif ## response schema
|
||||
|
||||
% if media_params:
|
||||
let (mut url, upload_type) =
|
||||
% for mp in media_params:
|
||||
@@ -621,14 +620,14 @@ match result {
|
||||
% else:
|
||||
else if \
|
||||
% endif
|
||||
protocol == "${mp.protocol}" {
|
||||
protocol == ${PROTOCOL_TYPE_MAP[mp.protocol]} {
|
||||
(self.hub._root_url.clone() + "${mp.path.lstrip('/')}", "${upload_type_map.get(mp.protocol, mp.protocol)}")
|
||||
} \
|
||||
% endfor
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
params.push(("uploadType", upload_type.to_string()));
|
||||
params.push("uploadType", upload_type);
|
||||
% else:
|
||||
let mut url = self.hub._base_url.clone() + "${m.path}";
|
||||
% endif
|
||||
@@ -637,9 +636,8 @@ else {
|
||||
<%
|
||||
assert 'key' in parameters, "Expected 'key' parameter if there are no scopes"
|
||||
%>
|
||||
let key = dlg.api_key();
|
||||
match key {
|
||||
Some(value) => params.push(("key", value)),
|
||||
match dlg.api_key() {
|
||||
Some(value) => params.push("key", value),
|
||||
None => {
|
||||
${delegate_finish}(false);
|
||||
return Err(client::Error::MissingAPIKey)
|
||||
@@ -652,41 +650,19 @@ else {
|
||||
}
|
||||
% endif
|
||||
|
||||
## Hanlde URI Tempates
|
||||
## Handle URI Templates
|
||||
% if replacements:
|
||||
for &(find_this, param_name) in [${', '.join('("%s", "%s")' % r for r in replacements)}].iter() {
|
||||
<%
|
||||
replace_init = ': Option<&str> = None'
|
||||
replace_assign = 'Some(value)'
|
||||
url_replace_arg = 'replace_with.expect("to find substitution value in params")'
|
||||
if URL_ENCODE in special_cases:
|
||||
replace_init = ' = String::new()'
|
||||
replace_assign = 'value.to_string()'
|
||||
url_replace_arg = '&replace_with'
|
||||
# end handle url encoding
|
||||
%>\
|
||||
let mut replace_with${replace_init};
|
||||
for &(name, ref value) in params.iter() {
|
||||
if name == param_name {
|
||||
replace_with = ${replace_assign};
|
||||
break;
|
||||
}
|
||||
}
|
||||
% if URL_ENCODE in special_cases:
|
||||
if find_this.as_bytes()[1] == '+' as u8 {
|
||||
replace_with = percent_encode(replace_with.as_bytes(), DEFAULT_ENCODE_SET).to_string();
|
||||
}
|
||||
% endif
|
||||
url = url.replace(find_this, ${url_replace_arg});
|
||||
url = params.uri_replacement(url, param_name, find_this, ${"true" if URL_ENCODE in special_cases else "false"});
|
||||
}
|
||||
## Remove all used parameters
|
||||
{
|
||||
let to_remove = [${', '.join(reversed(['"%s"' % r[1] for r in replacements]))}];
|
||||
params.retain(|(n, _)| !to_remove.contains(n));
|
||||
params.remove_params(&to_remove);
|
||||
}
|
||||
% endif
|
||||
|
||||
let url = url::Url::parse_with_params(&url, params).unwrap();
|
||||
let url = params.parse_with_url(&url);
|
||||
|
||||
% if request_value:
|
||||
let mut json_mime_type = mime::APPLICATION_JSON;
|
||||
@@ -742,7 +718,7 @@ else {
|
||||
% if request_value and simple_media_param:
|
||||
let mut mp_reader: client::MultiPartReader = Default::default();
|
||||
let (mut body_reader, content_type) = match protocol {
|
||||
"${simple_media_param.protocol}" => {
|
||||
${PROTOCOL_TYPE_MAP[simple_media_param.protocol]} => {
|
||||
mp_reader.reserve_exact(2);
|
||||
${READER_SEEK | indent_all_but_first_by(5)}
|
||||
mp_reader.add_part(&mut request_value_reader, request_size, json_mime_type.clone())
|
||||
@@ -754,8 +730,10 @@ else {
|
||||
% endif
|
||||
let client = &self.hub.client;
|
||||
dlg.pre_request();
|
||||
let mut req_builder = hyper::Request::builder().method(${method_name_to_variant(m.httpMethod)}).uri(url.clone().into_string())
|
||||
.header(USER_AGENT, self.hub._user_agent.clone());
|
||||
let mut req_builder = hyper::Request::builder()
|
||||
.method(${method_name_to_variant(m.httpMethod)})
|
||||
.uri(url.as_str())
|
||||
.header(USER_AGENT, self.hub._user_agent.clone());
|
||||
|
||||
% if default_scope:
|
||||
if let Some(token) = token.as_ref() {
|
||||
@@ -765,7 +743,7 @@ else {
|
||||
|
||||
% if resumable_media_param:
|
||||
upload_url_from_server = true;
|
||||
if protocol == "${resumable_media_param.protocol}" {
|
||||
if protocol == ${PROTOCOL_TYPE_MAP[resumable_media_param.protocol]} {
|
||||
req_builder = req_builder.header("X-Upload-Content-Type", format!("{}", reader_mime_type));
|
||||
}
|
||||
% endif
|
||||
@@ -785,7 +763,7 @@ else {
|
||||
% endif ## not simple_media_param
|
||||
% else:
|
||||
% if simple_media_param:
|
||||
let request = if protocol == "${simple_media_param.protocol}" {
|
||||
let request = if protocol == ${PROTOCOL_TYPE_MAP[simple_media_param.protocol]} {
|
||||
${READER_SEEK | indent_all_but_first_by(4)}
|
||||
let mut bytes = Vec::with_capacity(size as usize);
|
||||
reader.read_to_end(&mut bytes)?;
|
||||
@@ -841,7 +819,7 @@ else {
|
||||
}
|
||||
}
|
||||
% if resumable_media_param:
|
||||
if protocol == "${resumable_media_param.protocol}" {
|
||||
if protocol == ${PROTOCOL_TYPE_MAP[resumable_media_param.protocol]} {
|
||||
${READER_SEEK | indent_all_but_first_by(6)}
|
||||
let upload_result = {
|
||||
let url_str = &res.headers().get("Location").expect("LOCATION header is part of protocol").to_str().unwrap();
|
||||
@@ -927,7 +905,7 @@ if enable_resource_parsing \
|
||||
% endfor
|
||||
pub async fn ${upload_action_fn(api.terms.upload_action, p.type.suffix)}<${mtype_param}>(self, ${p.type.arg_name}: ${mtype_param}, mime_type: mime::Mime) -> ${rtype}
|
||||
where ${mtype_param}: client::ReadSeek {
|
||||
self.${api.terms.action}(${p.type.arg_name}, mime_type, "${p.protocol}").await
|
||||
self.${api.terms.action}(${p.type.arg_name}, mime_type, ${PROTOCOL_TYPE_MAP[p.protocol]}).await
|
||||
}
|
||||
% endfor
|
||||
</%def>
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
ADD_SCOPE_FN, TREF, enclose_in)
|
||||
from generator.lib.cli import (mangle_subcommand, new_method_context, PARAM_FLAG, STRUCT_FLAG, OUTPUT_FLAG, VALUE_ARG,
|
||||
CONFIG_DIR, SCOPE_FLAG, is_request_value_property, FIELD_SEP, docopt_mode, FILE_ARG, MIME_ARG, OUT_ARG,
|
||||
call_method_ident, POD_TYPES, opt_value, ident, JSON_TYPE_VALUE_MAP,
|
||||
call_method_ident, POD_TYPES, opt_value, ident,
|
||||
KEY_VALUE_ARG, to_cli_schema, SchemaEntry, CTYPE_POD, actual_json_type, CTYPE_MAP, CTYPE_ARRAY,
|
||||
application_secret_path, CONFIG_DIR_FLAG, req_value, MODE_ARG,
|
||||
opt_values, SCOPE_ARG, CONFIG_DIR_ARG, DEFAULT_MIME, field_vec, comma_sep_fields, JSON_TYPE_TO_ENUM_MAP,
|
||||
CTYPE_TO_ENUM_MAP)
|
||||
|
||||
from generator.lib.types import JSON_TO_RUST_DEFAULT
|
||||
v_arg = '<%s>' % VALUE_ARG
|
||||
SOPT = 'self.opt'
|
||||
|
||||
@@ -226,8 +226,9 @@ for parg in ${opt_values(VALUE_ARG)} {
|
||||
match key {
|
||||
% for p in optional_props:
|
||||
<%
|
||||
ptype = actual_json_type(p.name, p.type)
|
||||
value_unwrap = 'value.unwrap_or("%s")' % JSON_TYPE_VALUE_MAP[ptype]
|
||||
ptype = actual_json_type(p.name, p.get("format", p.type))
|
||||
default_value = JSON_TO_RUST_DEFAULT[ptype]
|
||||
value_unwrap = f"value.unwrap_or({default_value})"
|
||||
%>\
|
||||
"${mangle_subcommand(p.name)}" => {
|
||||
% if p.name == 'alt':
|
||||
@@ -237,7 +238,7 @@ for parg in ${opt_values(VALUE_ARG)} {
|
||||
% endif
|
||||
call = call.${mangle_ident(setter_fn_name(p))}(\
|
||||
% if ptype != 'string':
|
||||
arg_from_str(${value_unwrap}, err, "${mangle_subcommand(p.name)}", "${ptype}")\
|
||||
value.map(|v| arg_from_str(v, err, "${mangle_subcommand(p.name)}", "${ptype}")).unwrap_or(${default_value})\
|
||||
% else:
|
||||
${value_unwrap}\
|
||||
% endif # handle conversion
|
||||
|
||||
@@ -21,7 +21,8 @@ use std::env;
|
||||
use std::io::{self, Write};
|
||||
use clap::{App, SubCommand, Arg};
|
||||
|
||||
use ${to_extern_crate_name(library_to_crate_name(library_name(name, version), make.depends_on_suffix))}::{api, Error, oauth2};
|
||||
use ${to_extern_crate_name(library_to_crate_name(library_name(name, version), make.depends_on_suffix))}::{api, Error, oauth2, client::chrono, FieldMask};
|
||||
|
||||
|
||||
use google_clis_common as client;
|
||||
|
||||
|
||||
@@ -31,5 +31,5 @@ fn main() {
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
io::stdout().write_all(&output.as_bytes()).unwrap();
|
||||
io::stdout().write_all(output.as_bytes()).unwrap();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user