Skip to content

Commit ec31efa

Browse files
authored
initial work to support x-ms-pageable for generated specs (#742)
1 parent c95db55 commit ec31efa

File tree

3,111 files changed

+4242285
-3382557
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

3,111 files changed

+4242285
-3382557
lines changed

services/autorust/codegen/src/cargo_toml.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ futures = "0.3"
5353
[dev-dependencies]
5454
azure_identity = {{ path = "../../../sdk/identity" }}
5555
tokio = {{ version = "1.0", features = ["macros"] }}
56+
env_logger = "0.9"
5657
5758
[package.metadata.docs.rs]
5859
features = [{}]

services/autorust/codegen/src/codegen_models.rs

Lines changed: 105 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ use crate::{
44
spec::{self, get_schema_array_items, get_type_name_for_schema, get_type_name_for_schema_ref, TypeName},
55
CodeGen, PropertyName, ResolvedSchema, Spec,
66
};
7-
use autorust_openapi::{DataType, Reference, ReferenceOr, Schema};
7+
use autorust_openapi::{DataType, MsPageable, Reference, ReferenceOr, Schema};
88
use camino::{Utf8Path, Utf8PathBuf};
99
use indexmap::IndexMap;
1010
use proc_macro2::TokenStream;
1111
use quote::quote;
1212
use serde_json::Value;
1313
use spec::{get_schema_schema_references, openapi, RefKey};
14-
use std::collections::HashSet;
14+
use std::collections::{HashMap, HashSet};
1515

1616
#[derive(Clone)]
1717
struct PropertyGen {
@@ -300,6 +300,35 @@ pub fn create_models(cg: &CodeGen) -> Result<TokenStream, Error> {
300300
});
301301
}
302302

303+
let mut pageable_response_names: HashMap<String, MsPageable> = HashMap::new();
304+
for operation in cg.spec.operations()? {
305+
if let Some(pageable) = operation.pageable.as_ref() {
306+
for response in operation.responses.values() {
307+
if let Some(schema) = &response.schema {
308+
let pageable_name = format!("{}", type_name_gen(&get_type_name_for_schema_ref(schema)?, false, false)?);
309+
// in some cases, the same struct is used multiple times for
310+
// responses (such as a get and list for a given object
311+
// type). In these cases, what we see is a next_link_name
312+
// of null in one response, and a valid next_link_name in
313+
// another. so, only keep the one that has a next_link_name.
314+
//
315+
// operations that are not pageable won't call the
316+
// Continuable trait, which should mean this is workaround
317+
// is functional.
318+
if let Some(entry) = pageable_response_names.get(&pageable_name) {
319+
if entry.next_link_name.is_some() && pageable.next_link_name.is_none() {
320+
continue;
321+
}
322+
}
323+
324+
pageable_response_names.insert(pageable_name.clone(), pageable.clone());
325+
}
326+
}
327+
}
328+
}
329+
330+
// println!("response_names: {:?}", pageable_response_names);
331+
303332
let mut schema_names = IndexMap::new();
304333
let schemas = all_schemas(&cg.spec)?;
305334
let schemas = resolve_all_schema_properties(&schemas, &cg.spec)?;
@@ -310,6 +339,11 @@ pub fn create_models(cg: &CodeGen) -> Result<TokenStream, Error> {
310339
for (ref_key, schema) in &schemas {
311340
let doc_file = &ref_key.file_path;
312341
let schema_name = &ref_key.name;
342+
343+
// println!("schema_name: {}", schema_name);
344+
345+
// create_response_type()
346+
313347
if let Some(_first_doc_file) = schema_names.insert(schema_name, doc_file) {
314348
// eprintln!(
315349
// "WARN schema {} already created from {:?}, duplicate from {:?}",
@@ -325,7 +359,8 @@ pub fn create_models(cg: &CodeGen) -> Result<TokenStream, Error> {
325359
let (id, value) = create_basic_type_alias(schema_name, schema)?;
326360
file.extend(quote! { pub type #id = #value;});
327361
} else {
328-
file.extend(create_struct(cg, schema, schema_name)?);
362+
let pageable_name = format!("{}", schema_name.to_camel_case_ident().map_err(Error::StructName)?);
363+
file.extend(create_struct(cg, schema, schema_name, pageable_response_names.get(&pageable_name))?);
329364
}
330365
}
331366
Ok(file)
@@ -437,7 +472,7 @@ fn create_vec_alias(schema: &SchemaGen) -> Result<TokenStream, Error> {
437472
Ok(quote! { pub type #typ = Vec<#items_typ>; })
438473
}
439474

440-
fn create_struct(cg: &CodeGen, schema: &SchemaGen, struct_name: &str) -> Result<TokenStream, Error> {
475+
fn create_struct(cg: &CodeGen, schema: &SchemaGen, struct_name: &str, pageable: Option<&MsPageable>) -> Result<TokenStream, Error> {
441476
let mut code = TokenStream::new();
442477
let mut mod_code = TokenStream::new();
443478
let mut props = TokenStream::new();
@@ -447,6 +482,8 @@ fn create_struct(cg: &CodeGen, schema: &SchemaGen, struct_name: &str) -> Result<
447482
let struct_name_code = struct_name.to_camel_case_ident().map_err(Error::StructName)?;
448483
let required = schema.required();
449484

485+
// println!("struct: {} {:?}", struct_name_code, pageable);
486+
450487
for schema in schema.all_of() {
451488
let schema_name = schema.name()?;
452489
let type_name = schema_name.to_camel_case_ident().map_err(Error::StructFieldName)?;
@@ -463,6 +500,8 @@ fn create_struct(cg: &CodeGen, schema: &SchemaGen, struct_name: &str) -> Result<
463500
}
464501
}
465502

503+
let mut field_names = HashMap::new();
504+
466505
for property in schema.properties() {
467506
let property_name = property.name.as_str();
468507
let field_name = property_name.to_snake_case_ident().map_err(Error::StructName)?;
@@ -489,6 +528,8 @@ fn create_struct(cg: &CodeGen, schema: &SchemaGen, struct_name: &str) -> Result<
489528

490529
let is_required = required.contains(property_name) && !cg.should_force_optional(prop_nm);
491530

531+
field_names.insert(format!("{}", field_name), is_required);
532+
492533
let is_vec = is_vec(&type_name);
493534
if !is_vec {
494535
type_name = add_option(!is_required, type_name);
@@ -561,13 +602,72 @@ fn create_struct(cg: &CodeGen, schema: &SchemaGen, struct_name: &str) -> Result<
561602
None => quote! {},
562603
};
563604

605+
let mut continuable = quote! {};
606+
if let Some(pageable) = pageable {
607+
if let Some(name) = &pageable.next_link_name {
608+
let field_name = name.to_snake_case_ident().map_err(Error::StructName)?;
609+
// when there are multiple responses, we only add the Continuable
610+
// for the cases that have the field we care about.
611+
// println!("checking {} {} {}", struct_name_code, field_name, field_names.contains(&format!("{}", field_name)));
612+
if let Some(is_required) = field_names.get(&format!("{}", field_name)) {
613+
if *is_required {
614+
continuable = quote! {
615+
impl azure_core::Continuable for #struct_name_code {
616+
fn continuation(&self) -> Option<String> {
617+
if self.#field_name.is_empty() {
618+
None
619+
} else {
620+
Some(self.#field_name.clone())
621+
}
622+
}
623+
}
624+
};
625+
} else {
626+
continuable = quote! {
627+
impl azure_core::Continuable for #struct_name_code {
628+
fn continuation(&self) -> Option<String> {
629+
self.#field_name.clone()
630+
}
631+
}
632+
};
633+
}
634+
} else {
635+
// In a number of cases, such as USqlAssemblyList used in
636+
// datalake-analytics, the next link name is provided, but the
637+
// field doesn't exist in the response schema. Handle that by
638+
// adding a Continuable that always returns None.
639+
continuable = quote! {
640+
impl azure_core::Continuable for #struct_name_code {
641+
fn continuation(&self) -> Option<String> {
642+
None
643+
}
644+
}
645+
};
646+
}
647+
} else {
648+
// In a number of cases, such as DimensionsListResult used in
649+
// costmanagement, the next link name is null, and it's not provided
650+
// via a header or sometimes used in other responses.
651+
//
652+
// Handle that by // adding a Continuable that always returns None.
653+
continuable = quote! {
654+
impl azure_core::Continuable for #struct_name_code {
655+
fn continuation(&self) -> Option<String> {
656+
None
657+
}
658+
}
659+
};
660+
}
661+
}
662+
564663
let struct_code = quote! {
565664
#doc_comment
566665
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
567666
#default_code
568667
pub struct #struct_name_code {
569668
#props
570669
}
670+
#continuable
571671
};
572672
code.extend(struct_code);
573673

@@ -631,7 +731,7 @@ fn create_struct_field_code(
631731
} else if property.is_local_struct() {
632732
let id = property_name.to_camel_case_ident().map_err(Error::PropertyName)?;
633733
let type_name = quote! {#namespace::#id};
634-
let code = create_struct(cg, property, property_name)?;
734+
let code = create_struct(cg, property, property_name, None)?;
635735
Ok(TypeCode { type_name, code })
636736
} else {
637737
Ok(TypeCode {

0 commit comments

Comments
 (0)