Skip to content

1105 progenitor requires operationid to be set #1110

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion progenitor-impl/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ struct CliOperation {
impl Generator {
/// Generate a `clap`-based CLI.
pub fn cli(&mut self, spec: &OpenAPI, crate_name: &str) -> Result<TokenStream> {
validate_openapi(spec)?;
self.operation_ids = validate_openapi(spec)?;

// Convert our components dictionary to schemars
let schemas = spec.components.iter().flat_map(|components| {
Expand Down
2 changes: 1 addition & 1 deletion progenitor-impl/src/httpmock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ impl Generator {
/// the SDK. This can include `::` and instances of `-` in the crate name
/// should be converted to `_`.
pub fn httpmock(&mut self, spec: &OpenAPI, crate_path: &str) -> Result<TokenStream> {
validate_openapi(spec)?;
self.operation_ids = validate_openapi(spec)?;

// Convert our components dictionary to schemars
let schemas = spec.components.iter().flat_map(|components| {
Expand Down
48 changes: 36 additions & 12 deletions progenitor-impl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@

#![deny(missing_docs)]

use std::collections::{BTreeMap, HashMap, HashSet};
use std::collections::{BTreeMap, HashMap};

use openapiv3::OpenAPI;
use opid::OperationIds;
use proc_macro2::TokenStream;
use quote::quote;
use serde::Deserialize;
Expand All @@ -23,6 +24,7 @@ pub use typify::UnknownPolicy;
mod cli;
mod httpmock;
mod method;
mod opid;
mod template;
mod to_schema;
mod util;
Expand All @@ -49,6 +51,7 @@ pub type Result<T> = std::result::Result<T, Error>;

/// OpenAPI generator.
pub struct Generator {
operation_ids: OperationIds,
type_space: TypeSpace,
settings: GenerationSettings,
uses_futures: bool,
Expand Down Expand Up @@ -247,6 +250,7 @@ impl Default for Generator {
fn default() -> Self {
Self {
type_space: TypeSpace::new(TypeSpaceSettings::default().with_type_mod("types")),
operation_ids: Default::default(),
settings: Default::default(),
uses_futures: Default::default(),
uses_websockets: Default::default(),
Expand Down Expand Up @@ -298,6 +302,7 @@ impl Generator {

Self {
type_space: TypeSpace::new(&type_settings),
operation_ids: OperationIds::default(),
settings: settings.clone(),
uses_futures: false,
uses_websockets: false,
Expand All @@ -306,7 +311,7 @@ impl Generator {

/// Emit a [TokenStream] containing the generated client code.
pub fn generate_tokens(&mut self, spec: &OpenAPI) -> Result<TokenStream> {
validate_openapi(spec)?;
self.operation_ids = validate_openapi(spec)?;

// Convert our components dictionary to schemars
let schemas = spec.components.iter().flat_map(|components| {
Expand Down Expand Up @@ -666,31 +671,50 @@ fn validate_openapi_spec_version(spec_version: &str) -> Result<()> {
}

/// Do some very basic checks of the OpenAPI documents.
pub fn validate_openapi(spec: &OpenAPI) -> Result<()> {
pub fn validate_openapi(spec: &OpenAPI) -> Result<OperationIds> {
validate_openapi_spec_version(spec.openapi.as_str())?;

let mut opids = HashSet::new();
// populate OperationIds in two passes:
// first, add the IDs that are defined explicitely
// second, create synthetic operation IDs where they are not defined
let mut opids = OperationIds::default();
populate_operation_ids(spec, &mut opids, true)?;
populate_operation_ids(spec, &mut opids, false)?;

Ok(opids)
}

fn populate_operation_ids(
spec: &OpenAPI,
opids: &mut OperationIds,
process_present: bool,
) -> Result<()> {
spec.paths.paths.iter().try_for_each(|p| {
match p.1 {
openapiv3::ReferenceOr::Reference { reference: _ } => Err(Error::UnexpectedFormat(
format!("path {} uses reference, unsupported", p.0,),
)),
openapiv3::ReferenceOr::Item(item) => {
// Make sure every operation has an operation ID, and that each
// operation ID is only used once in the document.
item.iter().try_for_each(|(_, o)| {
// Make sure that each operation ID is only used once in the document.
// Where operation IDs are missing, create synthetic ones.
item.iter().try_for_each(|(method, o)| {
if let Some(oid) = o.operation_id.as_ref() {
if !opids.insert(oid.to_string()) {
if !process_present {
return Ok(());
}
if let Err(_e) =
opids.insert_opid_with_path_method(&oid.to_string(), p.0, method)
{
return Err(Error::UnexpectedFormat(format!(
"duplicate operation ID: {}",
oid,
)));
}
} else {
return Err(Error::UnexpectedFormat(format!(
"path {} is missing operation ID",
p.0,
)));
if process_present {
return Ok(());
}
opids.insert_synthetic_opid_for_path_method(p.0, method)?
}
Ok(())
})
Expand Down
42 changes: 15 additions & 27 deletions progenitor-impl/src/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,11 @@ impl Generator {
method: &str,
path_parameters: &[ReferenceOr<Parameter>],
) -> Result<OperationMethod> {
let operation_id = operation.operation_id.as_ref().unwrap();
let operation_id = self
.operation_ids
.opid_for_path_method(path, method)
.unwrap()
.to_string();

let mut combined_path_parameters = parameter_map(path_parameters, components)?;
for operation_param in items(&operation.parameters, components) {
Expand Down Expand Up @@ -336,11 +340,7 @@ impl Generator {
} => {
let schema = parameter_data.schema()?.to_schema();
let name = sanitize(
&format!(
"{}-{}",
operation.operation_id.as_ref().unwrap(),
&parameter_data.name,
),
&format!("{}-{}", operation_id, &parameter_data.name,),
Case::Pascal,
);

Expand Down Expand Up @@ -373,11 +373,7 @@ impl Generator {
} => {
let schema = parameter_data.schema()?.to_schema();
let name = sanitize(
&format!(
"{}-{}",
operation.operation_id.as_ref().unwrap(),
&parameter_data.name,
),
&format!("{}-{}", operation_id, &parameter_data.name,),
Case::Pascal,
);

Expand Down Expand Up @@ -409,7 +405,7 @@ impl Generator {
self.uses_websockets = true;
}

if let Some(body_param) = self.get_body_param(operation, components)? {
if let Some(body_param) = self.get_body_param(operation, &operation_id, components)? {
params.push(body_param);
}

Expand Down Expand Up @@ -462,10 +458,8 @@ impl Generator {

let typ = if let Some(schema) = &mt.schema {
let schema = schema.to_schema();
let name = sanitize(
&format!("{}-response", operation.operation_id.as_ref().unwrap(),),
Case::Pascal,
);
let name =
sanitize(&format!("{}-response", operation_id,), Case::Pascal);
self.type_space.add_type_with_name(&schema, Some(name))?
} else {
todo!("media type encoding, no schema: {:#?}", mt);
Expand Down Expand Up @@ -535,7 +529,7 @@ impl Generator {
}

Ok(OperationMethod {
operation_id: sanitize(operation_id, Case::Snake),
operation_id: sanitize(&operation_id, Case::Snake),
tags: operation.tags.clone(),
method: HttpMethod::from_str(method)?,
path: tmp,
Expand Down Expand Up @@ -1385,7 +1379,7 @@ impl Generator {
/// param_1,
/// param_2,
/// } = self;
///
///
/// let param_1 = param_1.map_err(Error::InvalidRequest)?;
/// let param_2 = param_1.map_err(Error::InvalidRequest)?;
///
Expand Down Expand Up @@ -2016,6 +2010,7 @@ impl Generator {
fn get_body_param(
&mut self,
operation: &openapiv3::Operation,
operation_id: &str,
components: &Option<Components>,
) -> Result<Option<OperationParameter>> {
let body = match &operation.request_body {
Expand All @@ -2026,11 +2021,7 @@ impl Generator {
let (content_str, media_type) = match (body.content.first(), body.content.len()) {
(None, _) => return Ok(None),
(Some(first), 1) => first,
(_, n) => todo!(
"more media types than expected for {}: {}",
operation.operation_id.as_ref().unwrap(),
n,
),
(_, n) => todo!("more media types than expected for {}: {}", operation_id, n,),
};

let schema = media_type.schema.as_ref().ok_or_else(|| {
Expand Down Expand Up @@ -2119,10 +2110,7 @@ impl Generator {
if !media_type.encoding.is_empty() {
todo!("media type encoding not empty: {:#?}", media_type);
}
let name = sanitize(
&format!("{}-body", operation.operation_id.as_ref().unwrap(),),
Case::Pascal,
);
let name = sanitize(&format!("{}-body", operation_id), Case::Pascal);
let typ = self
.type_space
.add_type_with_name(&schema.to_schema(), Some(name))?;
Expand Down
Loading