Skip to content
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

Support YAML::Value with the right content header #24

Closed
wants to merge 2 commits into from
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
167 changes: 95 additions & 72 deletions src/rust/client_gen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ fn match_tag(tag: &Option<Tag>, path_item: &ReferenceOr<PathItem>) -> bool {
fn param_data_to_type(data: &ParameterData, ref_cache: &mut RefCache) -> Result<DataType> {
match &data.format {
ParameterSchemaOrContent::Schema(ref_or_schema) => {
ref_or_schema_type(ref_or_schema, ref_cache)
ref_or_schema_type(ref_or_schema, ref_cache, None)
}
ParameterSchemaOrContent::Content(_) => {
Err(Error::unimplemented("Content parameter is not supported."))
Expand Down Expand Up @@ -301,79 +301,95 @@ fn request_body_params(
"Unexpected ref request body: '{reference}'."
))),
ReferenceOr::Item(body) => {
if body.content.len() != 1 {
Err(Error::unimplemented("Content with not exactly 1 option."))
} else {
let (content_type, media_type) = body.content.first().unwrap();

if content_type.starts_with("application/json") {
let schema = match &media_type.schema {
None => Err(Error::unimplemented("JSON content without schema.")),
Some(schema) => Ok(schema),
};
let (content_type, media_type) =
// TODO: It is in standards that a REST API may support multiple content types
// and we shouldn't fail. Handle this to complete the PR
body.content.first().unwrap();

if content_type.starts_with("application/json") {
let schema = match &media_type.schema {
None => Err(Error::unimplemented("JSON content without schema.")),
Some(schema) => Ok(schema),
};

let param = Param {
original_name: "".to_string(),
name: "value".to_string(),
tpe: ref_or_schema_type(schema?, ref_cache, Some(content_type))?,
required: body.required,
kind: ParamKind::Body,
};

Ok(vec![param])
} else if content_type == "application/octet-stream" {
Ok(vec![Param {
original_name: "".to_string(),
name: "value".to_string(),
tpe: DataType::Binary,
required: body.required,
kind: ParamKind::Body,
}])
} else if content_type == "application/x-yaml" {
let schema = match &media_type.schema {
None => Err(Error::unimplemented("YAML content without schema.")),
Some(schema) => Ok(schema),
};

let param = Param {
original_name: "".to_string(),
name: "value".to_string(),
tpe: ref_or_schema_type(schema?, ref_cache, Some(content_type))?,
required: body.required,
kind: ParamKind::Body,
};

Ok(vec![param])
} else if content_type == "multipart/form-data" {
match &media_type.schema {
None => Err(Error::unimplemented("Multipart content without schema.")),
Some(schema) => match schema {
ReferenceOr::Reference { reference } => Err(Error::unimplemented(format!(
"Unexpected ref multipart schema: '{reference}'."
))),
ReferenceOr::Item(schema) => match &schema.schema_kind {
SchemaKind::Type(Type::Object(obj)) => {
fn multipart_param(
name: &str,
required: bool,
schema: &ReferenceOr<Box<Schema>>,
ref_cache: &mut RefCache,
) -> Result<Param> {
Ok(Param {
original_name: name.to_string(),
name: name.to_case(Case::Snake),
tpe: ref_or_box_schema_type(schema, ref_cache)?,
required,
kind: ParamKind::Multipart,
})
}

Ok(vec![Param {
original_name: "".to_string(),
name: "value".to_string(),
tpe: ref_or_schema_type(schema?, ref_cache)?,
required: body.required,
kind: ParamKind::Body,
}])
} else if content_type == "application/octet-stream" {
Ok(vec![Param {
original_name: "".to_string(),
name: "value".to_string(),
tpe: DataType::Binary,
required: body.required,
kind: ParamKind::Body,
}])
} else if content_type == "multipart/form-data" {
match &media_type.schema {
None => Err(Error::unimplemented("Multipart content without schema.")),
Some(schema) => match schema {
ReferenceOr::Reference { reference } => Err(Error::unimplemented(
format!("Unexpected ref multipart schema: '{reference}'."),
obj.properties
.iter()
.map(|(name, schema)| {
multipart_param(
name,
body.required && obj.required.contains(name),
schema,
ref_cache,
)
})
.collect()
}
_ => Err(Error::unimplemented(
"Object schema expected for multipart request body.",
)),
ReferenceOr::Item(schema) => match &schema.schema_kind {
SchemaKind::Type(Type::Object(obj)) => {
fn multipart_param(
name: &str,
required: bool,
schema: &ReferenceOr<Box<Schema>>,
ref_cache: &mut RefCache,
) -> Result<Param> {
Ok(Param {
original_name: name.to_string(),
name: name.to_case(Case::Snake),
tpe: ref_or_box_schema_type(schema, ref_cache)?,
required,
kind: ParamKind::Multipart,
})
}

obj.properties
.iter()
.map(|(name, schema)| {
multipart_param(
name,
body.required && obj.required.contains(name),
schema,
ref_cache,
)
})
.collect()
}
_ => Err(Error::unimplemented(
"Object schema expected for multipart request body.",
)),
},
},
}
} else {
Err(Error::unimplemented(format!(
"Request body content type: '{content_type}'."
)))
},
}
} else {
Err(Error::unimplemented(format!(
"Request body content type: '{content_type}'."
)))
}
}
}
Expand Down Expand Up @@ -449,7 +465,7 @@ fn response_type(response: &ReferenceOr<Response>, ref_cache: &mut RefCache) ->
Some(schema) => Ok(schema),
};

Ok(ref_or_schema_type(schema?, ref_cache)?)
Ok(ref_or_schema_type(schema?, ref_cache, Some(content_type))?)
} else if content_type == "application/octet-stream" {
Ok(DataType::Binary)
} else {
Expand Down Expand Up @@ -837,7 +853,14 @@ fn render_method_implementation(method: &Method, error_kind: &ErrorKind) -> Rust
r#"request = request.header(reqwest::header::CONTENT_TYPE, "application/octet-stream");"#,
)
+ NewLine
} else {
} else if param.tpe == DataType::Yaml {
line(unit() + "request = request.body(serde_yaml::to_string(" + &param.name + ").unwrap_or_default().into_bytes());") +
line(
r#"request = request.header(reqwest::header::CONTENT_TYPE, "application/x-yaml");"#,
) + NewLine
}
// Not sure why everything else is assumed to be json (previously)
else {
line(unit() + "request = request.json(" + &param.name + ");") + NewLine
}
}
Expand Down
29 changes: 24 additions & 5 deletions src/rust/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ pub enum DataType {
Array(Box<DataType>),
MapOf(Box<DataType>),
Json,
Yaml
}

pub fn escape_keywords(name: &str) -> String {
Expand Down Expand Up @@ -143,6 +144,11 @@ impl DataType {
let res = rust_name("serde_json::value", "Value");
to_ref(res, top_param)
}

DataType::Yaml => {
let res = rust_name("serde_yaml::value", "Value");
to_ref(res, top_param)
}
}
}
}
Expand All @@ -159,7 +165,7 @@ pub fn ref_type_name(reference: &str, ref_cache: &mut RefCache) -> Result<DataTy
}))
}

fn schema_type(schema: &Schema, ref_cache: &mut RefCache) -> Result<DataType> {
fn schema_type(schema: &Schema, ref_cache: &mut RefCache, content_type: Option<&str>) -> Result<DataType> {
match &schema.schema_kind {
SchemaKind::Type(tpe) => match tpe {
Type::String(string_type) => {
Expand Down Expand Up @@ -213,7 +219,7 @@ fn schema_type(schema: &Schema, ref_cache: &mut RefCache) -> Result<DataType> {
"Object parameter with Any additional_properties is not supported.",
)),
AdditionalProperties::Schema(element_schema) => Ok(DataType::MapOf(
Box::new(ref_or_schema_type(element_schema, ref_cache)?),
Box::new(ref_or_schema_type(element_schema, ref_cache, None)?),
)),
}
} else {
Expand All @@ -238,17 +244,30 @@ fn schema_type(schema: &Schema, ref_cache: &mut RefCache) -> Result<DataType> {
SchemaKind::AllOf { .. } => Err(Error::unimplemented("AllOf parameter is not supported.")),
SchemaKind::AnyOf { .. } => Err(Error::unimplemented("AnyOf parameter is not supported.")),
SchemaKind::Not { .. } => Err(Error::unimplemented("Not parameter is not supported.")),
SchemaKind::Any(_) => Ok(DataType::Json),
SchemaKind::Any(_) => {
if let Some(content_type) = content_type {
if content_type == "application/json" {
Ok(DataType::Json)
} else if content_type == "application/x-yaml" {
Ok(DataType::Yaml)
} else {
Err(Error::unexpected(format!("Cannot resolve the data type for content_type {} with `any` schema-kind", content_type)))
}
} else {
Err(Error::unexpected("Cannot resolve the data type for any schema-kind with no details on content_type"))
}
},
}
}

pub fn ref_or_schema_type(
ref_or_schema: &ReferenceOr<Schema>,
ref_cache: &mut RefCache,
content_type: Option<&str>
) -> Result<DataType> {
match ref_or_schema {
ReferenceOr::Reference { reference } => ref_type_name(reference, ref_cache),
ReferenceOr::Item(schema) => schema_type(schema, ref_cache),
ReferenceOr::Item(schema) => schema_type(schema, ref_cache, content_type),
}
}

Expand All @@ -258,6 +277,6 @@ pub fn ref_or_box_schema_type(
) -> Result<DataType> {
match ref_or_schema {
ReferenceOr::Reference { reference } => ref_type_name(reference, ref_cache),
ReferenceOr::Item(schema) => schema_type(schema, ref_cache),
ReferenceOr::Item(schema) => schema_type(schema, ref_cache, None),
}
}
Loading