Skip to content
This repository was archived by the owner on Apr 10, 2021. It is now read-only.

Commit

Permalink
add codegen support for other http verbs (#20)
Browse files Browse the repository at this point in the history
  • Loading branch information
ctaggart authored Sep 30, 2020
1 parent bcb48eb commit 68b0b60
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 36 deletions.
6 changes: 6 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"recommendations": [
"matklad.rust-analyzer",
"GitHub.vscode-pull-request-github"
]
}
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ The command line args are a subset of those supported by `autorest`.
``` sh
cargo run -- --help
cargo run -- --input-file=../azure-rest-api-specs/specification/vmware/resource-manager/Microsoft.AVS/stable/2020-03-20/vmware.json
cargo +nightly run --features fmt -- --input-file ../OpenAPI-Specification/examples/v2.0/json/petstore.json
```

## Status

It is early days. The generated code is not finished. No binaries have been published. You will probably get panics trying out other specs.
It is early days. The generated code is not finished. No binaries have been published. You will probably get panics trying out other specs. I've posted some status videos to an [AutoRust YouTube playlist](https://www.youtube.com/playlist?list=PL6MfGfZ-qCMq1mYjzTdGhKOHfrMFZjjW_). TODO items are starting to be annotated with the GitHub issue numbers using [GitHub Pull Requests and Issues](https://marketplace.visualstudio.com/items?itemName=GitHub.vscode-pull-request-github) extension for VS Code.
96 changes: 72 additions & 24 deletions src/codegen.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#![allow(unused_variables, dead_code)]
use crate::{format_code, pathitem_operations, Reference, Result, Spec};
use autorust_openapi::{DataType, Operation, Parameter, ReferenceOr, Schema};
use crate::{format_code, pathitem_operations, OperationVerb, Reference, Result, Spec};
use autorust_openapi::{DataType, Operation, Parameter, PathItem, ReferenceOr, Schema};
use heck::{CamelCase, SnakeCase};
use proc_macro2::TokenStream;
use quote::{format_ident, quote, ToTokens};
Expand Down Expand Up @@ -115,7 +115,7 @@ fn create_struct_field_type(
let vec_items_typ = get_type_for_schema(&items)?;
quote! {Vec<#vec_items_typ>}
}
DataType::Integer => quote! {i32},
DataType::Integer => quote! {i64},
DataType::Number => quote! {f64},
DataType::String => quote! {String},
DataType::Boolean => quote! {bool},
Expand Down Expand Up @@ -202,7 +202,7 @@ fn create_struct(
}

let st = quote! {
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct #nm {
#props
}
Expand All @@ -226,7 +226,7 @@ fn trim_ref(path: &str) -> String {
fn map_type(param_type: &DataType) -> TokenStream {
match param_type {
DataType::String => quote! { &str },
DataType::Integer => quote! { i32 },
DataType::Integer => quote! { i64 },
_ => {
quote! {map_type} // TODO may be Err instead
}
Expand Down Expand Up @@ -272,7 +272,7 @@ fn create_function_params(cg: &CodeGen, op: &Operation) -> Result<TokenStream> {
for param in &parameters {
params.push(get_param_name_and_type(param)?);
}
let slf = quote! { &self };
let slf = quote! { configuration: &Configuration };
params.insert(0, slf);
Ok(quote! { #(#params),* })
}
Expand All @@ -287,7 +287,27 @@ fn get_type_for_schema(schema: &ReferenceOr<Schema>) -> Result<TokenStream> {
);
Ok(quote! { #idt })
}
ReferenceOr::Item(_) => {
ReferenceOr::Item(schema) => {
if let Some(schema_type) = &schema.type_ {
let ts = match schema_type {
DataType::Array => {
let items = schema
.items
.as_ref()
.as_ref()
.ok_or_else(|| format!("array expected to have items"))?;
let vec_items_typ = get_type_for_schema(&items)?;
quote! {Vec<#vec_items_typ>}
}
DataType::Integer => quote! {i64},
DataType::Number => quote! {f64},
DataType::String => quote! {String},
DataType::Boolean => quote! {bool},
DataType::Object => quote! {serde::Value},
};
return Ok(ts);
}

// TODO probably need to create a struct
// and have a way to name it
let idt = ident("NoParamType2");
Expand All @@ -297,10 +317,10 @@ fn get_type_for_schema(schema: &ReferenceOr<Schema>) -> Result<TokenStream> {
}

// TODO is _ref_param not needed for a return
fn create_function_return(op: &Operation) -> Result<TokenStream> {
fn create_function_return(verb: &OperationVerb) -> Result<TokenStream> {
// TODO error responses
// TODO union of responses
for (_http_code, rsp) in op.responses.iter() {
for (_http_code, rsp) in verb.operation().responses.iter() {
// println!("response key {:#?} {:#?}", key, rsp);
if let Some(schema) = &rsp.schema {
let tp = get_type_for_schema(schema)?;
Expand All @@ -310,18 +330,32 @@ fn create_function_return(op: &Operation) -> Result<TokenStream> {
Ok(quote! { Result<()> })
}

/// Creating a function name from the path and verb when an operationId is not specified.
/// All azure-rest-api-specs operations should have an operationId.
fn create_function_name(path: &str, verb_name: &str) -> String {
let mut path = path
.split('/')
.filter(|&x| !x.is_empty())
.collect::<Vec<_>>();
path.push(verb_name);
path.join("_")
}

fn create_function(
cg: &CodeGen,
path: &str,
op: &Operation,
item: &PathItem,
operation_verb: &OperationVerb,
param_re: &Regex,
) -> Result<TokenStream> {
let name_default = "operation_id_missing";
let name = ident(
op.operation_id
let fname = ident(
operation_verb
.operation()
.operation_id
.as_ref()
.map(String::as_ref)
.unwrap_or(name_default),
.unwrap_or(&create_function_name(path, operation_verb.verb_name()))
.to_snake_case()
.as_ref(),
);

let params = parse_params(param_re, path);
Expand All @@ -333,19 +367,28 @@ fn create_function(

// get path parameters
// Option if not required
let fparams = create_function_params(cg, op)?;
let fparams = create_function_params(cg, operation_verb.operation())?;

// see if there is a body parameter
let fresponse = create_function_return(op)?;
let fresponse = create_function_return(operation_verb)?;

let client_verb = match operation_verb {
OperationVerb::Get(_) => quote! { client.get(uri_str) },
OperationVerb::Post(_) => quote! { client.post(uri_str) },
OperationVerb::Put(_) => quote! { client.put(uri_str) },
OperationVerb::Patch(_) => quote! { client.patch(uri_str) },
OperationVerb::Delete(_) => quote! { client.delete(uri_str) },
OperationVerb::Options(_) => quote! { client.options(uri_str) },
OperationVerb::Head(_) => quote! { client.head(uri_str) },
};

// TODO #17 decode the different errors depending on http status
// TODO #18 other callbacks like auth
let func = quote! {
pub async fn #name(#fparams) -> #fresponse {
let configuration = self.configuration;
pub async fn #fname(#fparams) -> #fresponse {
let client = &configuration.client;
let uri_str = format!(#fpath, configuration.base_path, #uri_str_args);
// TODO client.get, put, post, delete
let mut req_builder = client.get(uri_str.as_str());
// TODO other callbacks like auth
let uri_str = &format!(#fpath, &configuration.base_path, #uri_str_args);
let mut req_builder = #client_verb;
let req = req_builder.build()?;
let res = client.execute(req).await?;
match res.error_for_status_ref() {
Expand All @@ -370,7 +413,7 @@ pub fn create_client(cg: &CodeGen) -> Result<TokenStream> {
// println!("{}", path);
for op in pathitem_operations(item) {
// println!("{:?}", op.operation_id);
file.extend(create_function(cg, &path, &op, &param_re))
file.extend(create_function(cg, path, item, &op, &param_re))
}
}
Ok(file)
Expand All @@ -393,4 +436,9 @@ mod tests {
let idt = ident("3.2");
assert_eq!(idt.to_string(), "_3_2");
}

#[test]
fn test_create_function_name() {
assert_eq!(create_function_name("/pets", "get"), "pets_get");
}
}
63 changes: 52 additions & 11 deletions src/spec.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::{path_join, Reference, Result};
use autorust_openapi::{AdditionalProperties, OpenAPI, Operation, Parameter, PathItem, ReferenceOr, Schema};
use autorust_openapi::{
AdditionalProperties, OpenAPI, Operation, Parameter, PathItem, ReferenceOr, Schema,
};
use indexmap::{IndexMap, IndexSet};
use std::{fs::File, io::prelude::*};

Expand Down Expand Up @@ -195,20 +197,58 @@ pub fn read_api_file(path: &str) -> Result<OpenAPI> {
Ok(api)
}

pub fn pathitem_operations(item: &PathItem) -> impl Iterator<Item = &Operation> {
pub enum OperationVerb<'a> {
Get(&'a Operation),
Post(&'a Operation),
Put(&'a Operation),
Patch(&'a Operation),
Delete(&'a Operation),
Options(&'a Operation),
Head(&'a Operation),
}

// Hold an operation and remembers the operation verb.
impl<'a> OperationVerb<'a> {
pub fn operation(&self) -> &'a Operation {
match self {
OperationVerb::Get(op) => op,
OperationVerb::Post(op) => op,
OperationVerb::Put(op) => op,
OperationVerb::Patch(op) => op,
OperationVerb::Delete(op) => op,
OperationVerb::Options(op) => op,
OperationVerb::Head(op) => op,
}
}

pub fn verb_name(&self) -> &'static str {
match self {
OperationVerb::Get(_) => "get",
OperationVerb::Post(_) => "post",
OperationVerb::Put(_) => "put",
OperationVerb::Patch(_) => "patch",
OperationVerb::Delete(_) => "delete",
OperationVerb::Options(_) => "options",
OperationVerb::Head(_) => "head",
}
}
}

pub fn pathitem_operations(item: &PathItem) -> impl Iterator<Item = OperationVerb> {
vec![
item.get.as_ref(),
item.post.as_ref(),
item.put.as_ref(),
item.patch.as_ref(),
item.delete.as_ref(),
item.options.as_ref(),
item.head.as_ref(),
item.get.as_ref().map(OperationVerb::Get),
item.post.as_ref().map(OperationVerb::Post),
item.put.as_ref().map(OperationVerb::Put),
item.patch.as_ref().map(OperationVerb::Patch),
item.delete.as_ref().map(OperationVerb::Delete),
item.options.as_ref().map(OperationVerb::Options),
item.head.as_ref().map(OperationVerb::Head),
]
.into_iter()
.filter_map(|x| x)
}

/// Holds a $ref string and remembers the type.
#[derive(Clone, Debug, PartialEq)]
pub enum RefString {
PathItem(String),
Expand Down Expand Up @@ -245,7 +285,7 @@ fn add_refs_for_schema(list: &mut Vec<RefString>, schema: &Schema) {
list.push(RefString::Schema(reference.to_owned()))
}
ReferenceOr::Item(schema) => add_refs_for_schema(list, schema),
}
},
},
_ => {}
}
Expand Down Expand Up @@ -279,7 +319,8 @@ pub fn get_refs(api: &OpenAPI) -> Vec<RefString> {
list.push(RefString::PathItem(reference.to_owned()))
}
ReferenceOr::Item(item) => {
for op in pathitem_operations(&item) {
for verb in pathitem_operations(&item) {
let op = verb.operation();
// parameters
for prm in &op.parameters {
match prm {
Expand Down

0 comments on commit 68b0b60

Please sign in to comment.