Skip to content
Merged
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
52 changes: 37 additions & 15 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ edition = "2024"
license = "MIT"

[dependencies]
aide = "0.14.1"
aide = "=0.16.0-alpha.4"
anyhow = "1.0.94"
camino = "1.1.9"
clap = { version = "4.5.23", features = ["derive"] }
Expand All @@ -14,8 +14,8 @@ heck = "0.5.0"
indexmap = "2.7.0"
itertools = "0.14.0"
minijinja = { version = "2.8.0", features = ["json", "loader"] }
ron = "0.10.1"
schemars = "0.8.21"
ron = "0.12.1"
schemars = "1.2.1"
serde = { version = "1.0.215", features = ["derive", "rc"] }
serde_json = "1.0.133"
tempfile = "3.14.0"
Expand Down
4 changes: 2 additions & 2 deletions src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,8 @@ fn merge_types(dst: &mut Types, src: Types) -> anyhow::Result<()> {
Ok(())
}

pub(crate) fn get_schema_name(maybe_ref: Option<&str>) -> Option<String> {
let r = maybe_ref?;
pub(crate) fn get_schema_name<'a>(maybe_ref: impl Into<Option<&'a str>>) -> Option<String> {
let r = maybe_ref.into()?;
let schema_name = r.strip_prefix("#/components/schemas/");
if schema_name.is_none() {
tracing::warn!(
Expand Down
57 changes: 27 additions & 30 deletions src/api/resources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use std::{

use aide::openapi::{self, ReferenceOr};
use anyhow::{Context as _, bail};
use schemars::schema::{InstanceType, Schema};
use serde::{Deserialize, Serialize};

use crate::cli_v1::IncludeMode;
Expand Down Expand Up @@ -315,18 +314,7 @@ impl Operation {
.swap_remove("application/json")
.expect("should have JSON body");
assert!(json_body.extensions.is_empty());
match json_body.schema.expect("no json body schema?!").json_schema {
Schema::Bool(_) => {
tracing::error!("unexpected bool schema");
None
}
Schema::Object(obj) => {
if !obj.is_ref() {
tracing::error!(?obj, "unexpected non-$ref json body schema");
}
get_schema_name(obj.reference.as_deref())
}
}
get_body_schema_name(json_body)
}
ReferenceOr::Reference { .. } => {
tracing::error!("$ref request bodies are not currently supported");
Expand Down Expand Up @@ -387,15 +375,35 @@ impl Operation {
}
}

fn get_body_schema_name(json_body: openapi::MediaType) -> Option<String> {
let Some(schema_object) = json_body.schema else {
tracing::error!("missing json body schema");
return None;
};
let Some(obj) = schema_object.json_schema.as_object() else {
tracing::error!("unexpected bool schema");
return None;
};

match obj.get("$ref") {
Some(reference) => get_schema_name(reference.as_str()),
None => {
tracing::error!(?obj, "unexpected non-$ref json body schema");
None
}
}
}

fn enforce_string_parameter(parameter_data: &openapi::ParameterData) -> anyhow::Result<()> {
let openapi::ParameterSchemaOrContent::Schema(s) = &parameter_data.format else {
bail!("found unexpected 'content' data format");
};
let Schema::Object(obj) = &s.json_schema else {
bail!("found unexpected `true` schema");
};
if obj.instance_type != Some(InstanceType::String.into()) {
bail!("unsupported path parameter type `{:?}`", obj.instance_type);
let ty = s
.json_schema
.get("type")
.context("missing path parameter type")?;
if ty != "string" {
bail!("unsupported path parameter type `{ty:?}`");
}

Ok(())
Expand All @@ -415,18 +423,7 @@ fn response_body_schema_name(resp: ReferenceOr<openapi::Response>) -> Option<Str
.swap_remove("application/json")
.expect("should have JSON body");
assert!(json_body.extensions.is_empty());
match json_body.schema.expect("no json body schema?!").json_schema {
Schema::Bool(_) => {
tracing::error!("unexpected bool schema");
None
}
Schema::Object(obj) => {
if !obj.is_ref() {
tracing::error!(?obj, "unexpected non-$ref json body schema");
}
get_schema_name(obj.reference.as_deref())
}
}
get_body_schema_name(json_body)
}
ReferenceOr::Reference { .. } => {
tracing::error!("$ref response bodies are not currently supported");
Expand Down
86 changes: 37 additions & 49 deletions src/api/struct_enum.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
use anyhow::{Context as _, bail, ensure};
use schemars::schema::{ObjectValidation, Schema, SchemaObject};

use crate::api::{
get_schema_name,
types::{EnumVariantType, Field, SimpleVariant, StructEnumRepr, TypeData},
use crate::{
JsonValue,
api::{
get_schema_name,
types::{EnumVariantType, Field, SimpleVariant, StructEnumRepr, TypeData},
},
utils::get_properties,
};

/// A wrapper around a Option<String>
Expand All @@ -25,23 +28,25 @@ impl SameString {
}

impl TypeData {
pub(super) fn inline_struct_enum(one_of: &[Schema], fields: &[Field]) -> anyhow::Result<Self> {
pub(super) fn inline_struct_enum(one_of: &JsonValue, fields: &[Field]) -> anyhow::Result<Self> {
let one_of = one_of.as_array().context("oneOf must be an array")?;

let mut discriminator_field = SameString(None);
let mut content_field = SameString(None);
let mut variants = vec![];

let mut process_one_of = |s: &Schema| {
let variant = get_obj_validation(s)?;

let (variant_discriminator_name, discriminator) = get_discriminator(variant)?;
let mut process_one_of = |variant: &JsonValue| {
let (variant_discriminator_name, discriminator) =
get_discriminator(variant).context("get struct-enum discriminator")?;
discriminator_field.update(variant_discriminator_name)?;

let len = variant.properties.len();
let properties = get_properties(variant).context("get struct-enum properties")?;
let len = properties.len();
ensure!(
(1..=2).contains(&len),
"Found struct enum variant with {len} properties, expected 1 or 2"
);
if variant.properties.len() == 1 {
if properties.len() == 1 {
variants.push(SimpleVariant {
name: discriminator,
content: EnumVariantType::Ref {
Expand All @@ -50,7 +55,8 @@ impl TypeData {
},
});
} else {
let (variant_content_field, content) = get_content(variant)?;
let (variant_content_field, content) =
get_content(variant).context("get struct-enum content")?;
content_field.update(variant_content_field)?;

variants.push(SimpleVariant {
Expand Down Expand Up @@ -81,23 +87,20 @@ impl TypeData {
}
}

fn get_content(variant: &ObjectValidation) -> anyhow::Result<(String, EnumVariantType)> {
for (p_name, p) in &variant.properties {
let schema_obj = get_schema_obj(p)?;
if let Some(obj) = &schema_obj.object {
let ty = TypeData::from_object_schema(*obj.clone(), schemars::Map::new(), None)?;
fn get_content(variant: &JsonValue) -> anyhow::Result<(String, EnumVariantType)> {
for (prop_name, prop_schema) in get_properties(variant)? {
if prop_schema["type"] == "object" {
let ty = TypeData::from_object_schema(prop_schema)?;
let TypeData::Struct { fields } = ty else {
bail!("Expected obj to be a struct");
};

return Ok((p_name.to_owned(), EnumVariantType::Struct { fields }));
}

if let Some(schema_ref) = &schema_obj.reference {
return Ok((prop_name.to_owned(), EnumVariantType::Struct { fields }));
} else if let Some(reference) = prop_schema["$ref"].as_str() {
return Ok((
p_name.to_owned(),
prop_name.to_owned(),
EnumVariantType::Ref {
schema_ref: Some(get_schema_name(Some(schema_ref.as_str())).unwrap()),
schema_ref: Some(get_schema_name(reference).unwrap()),
inner: None,
},
));
Expand All @@ -107,22 +110,21 @@ fn get_content(variant: &ObjectValidation) -> anyhow::Result<(String, EnumVarian
bail!("Failed to find content on struct enum")
}

fn get_discriminator(obj: &ObjectValidation) -> anyhow::Result<(String, String)> {
fn get_discriminator(obj: &JsonValue) -> anyhow::Result<(String, String)> {
let mut discriminator_field_name = None;
let mut discriminator = None;

for (p_name, p) in &obj.properties {
let schema_obj = get_schema_obj(p).with_context(|| p_name.to_owned())?;
if let Some(enum_vals) = &schema_obj.enum_values
&& enum_vals.len() == 1
for (prop_name, prop_schema) in get_properties(obj)? {
if let Some(enum_value) = &prop_schema.get("enum")
&& let Some(enum_list) = enum_value.as_array()
&& let [value] = enum_list.as_slice()
{
match &enum_vals[0].as_str() {
Some(v) => {
discriminator_field_name = Some(p_name.clone());
discriminator = Some((*v).to_owned());
}
None => bail!("Expected discriminator field name to be a string"),
}
let value = value
.as_str()
.context("Expected discriminator field name to be a string")?;

discriminator_field_name = Some(prop_name.clone());
discriminator = Some(value.to_owned());
}
}

Expand All @@ -135,17 +137,3 @@ fn get_discriminator(obj: &ObjectValidation) -> anyhow::Result<(String, String)>

Ok((discriminator_field_name, discriminator))
}

fn get_schema_obj(s: &Schema) -> anyhow::Result<&SchemaObject> {
match s {
Schema::Bool(_) => bail!("unsupported bool schema"),
Schema::Object(o) => Ok(o),
}
}

fn get_obj_validation(s: &Schema) -> anyhow::Result<&ObjectValidation> {
let Some(obj) = get_schema_obj(s)?.object.as_ref() else {
bail!("unsupported: object type without further validation");
};
Ok(obj)
}
Loading
Loading