Skip to content

Commit 09781fc

Browse files
authored
Merge pull request #96 from svix/mendy/horrible-op-webhooks-hack
Add op webhooks body schema to generated models
2 parents 684c1d6 + 6532e23 commit 09781fc

File tree

4 files changed

+86
-34
lines changed

4 files changed

+86
-34
lines changed

Dockerfile

+3-3
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,6 @@ RUN echo "${BIOME_HASH} biome" > biome.sha256 && \
8686
mv biome /usr/bin/ && \
8787
chmod +x /usr/bin/biome
8888

89-
# openapi-codegen
90-
COPY --from=openapi-codegen-builder /app/target/${RUST_TARGET}/release/openapi-codegen /usr/bin/
91-
9289
# Ruby
9390
COPY --from=rubyfmt-builder /app/target/release/rubyfmt-main /usr/bin/rubyfmt
9491

@@ -117,3 +114,6 @@ RUN apk add --no-cache binutils && \
117114
rm -rf /root/.rustup/toolchains/nightly-*/share && \
118115
strip /root/.rustup/toolchains/nightly-*/lib/librustc_driver-*.so && \
119116
apk del binutils
117+
118+
# openapi-codegen
119+
COPY --from=openapi-codegen-builder /app/target/${RUST_TARGET}/release/openapi-codegen /usr/bin/

src/api.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,14 @@ impl Api {
5757
.flat_map(Resource::referenced_components)
5858
}
5959

60-
pub(crate) fn types(&self, schemas: &mut IndexMap<String, openapi::SchemaObject>) -> Types {
61-
Types::from_referenced_components(schemas, self.referenced_components())
60+
pub(crate) fn types(
61+
&self,
62+
schemas: &mut IndexMap<String, openapi::SchemaObject>,
63+
webhooks: Vec<String>,
64+
) -> Types {
65+
let mut referenced_components: Vec<&str> = webhooks.iter().map(|s| &**s).collect();
66+
referenced_components.extend(self.referenced_components());
67+
Types::from_referenced_components(schemas, referenced_components.into_iter())
6268
}
6369
}
6470

src/main.rs

+27-2
Original file line numberDiff line numberDiff line change
@@ -127,11 +127,11 @@ fn analyze_and_generate(
127127
path: &Utf8Path,
128128
flags: GenerateFlags,
129129
) -> anyhow::Result<()> {
130+
let webhooks = get_webhooks(&spec);
130131
let mut components = spec.components.unwrap_or_default();
131-
132132
if let Some(paths) = spec.paths {
133133
let api = Api::new(paths, &components.schemas, flags.include_hidden).unwrap();
134-
let types = api.types(&mut components.schemas);
134+
let types = api.types(&mut components.schemas, webhooks);
135135

136136
if flags.debug {
137137
let mut api_file = BufWriter::new(File::create("api.ron")?);
@@ -161,3 +161,28 @@ fn write_codegen_metadata(input_sha256sum: String, output_dir: &Utf8Path) -> any
161161
std::fs::write(metadata_path, &encoded_metadata)?;
162162
Ok(())
163163
}
164+
165+
fn get_webhooks(spec: &OpenApi) -> Vec<String> {
166+
let empty_obj = serde_json::json!({});
167+
let empty_obj = empty_obj.as_object().unwrap();
168+
let mut referenced_components = std::collections::BTreeSet::<String>::new();
169+
if let Some(webhooks) = spec.extensions.get("x-webhooks") {
170+
for req in webhooks.as_object().unwrap_or(empty_obj).values() {
171+
for method in req.as_object().unwrap_or(empty_obj).values() {
172+
if let Some(schema_ref) = method
173+
.get("requestBody")
174+
.and_then(|v| v.get("content"))
175+
.and_then(|v| v.get("application/json"))
176+
.and_then(|v| v.get("schema"))
177+
.and_then(|v| v.get("$ref"))
178+
.and_then(|v| v.as_str())
179+
{
180+
if let Some(schema_name) = schema_ref.split('/').next_back() {
181+
referenced_components.insert(schema_name.to_string());
182+
}
183+
}
184+
}
185+
}
186+
}
187+
referenced_components.into_iter().collect::<Vec<String>>()
188+
}

src/types.rs

+48-27
Original file line numberDiff line numberDiff line change
@@ -614,10 +614,8 @@ impl FieldType {
614614
Self::List(field_type) | Self::Set(field_type) => {
615615
format!("List<{}>", field_type.to_csharp_typename()).into()
616616
}
617-
Self::SchemaRef(name) => name.clone().into(),
618-
Self::StringConst(_) => {
619-
unreachable!("FieldType::const should never be exposed to template code")
620-
}
617+
Self::SchemaRef(name) => filter_schema_ref(name, "Object"),
618+
Self::StringConst(_) => "string".into(),
621619
}
622620
}
623621

@@ -636,10 +634,8 @@ impl FieldType {
636634
Self::List(field_type) | Self::Set(field_type) => {
637635
format!("[]{}", field_type.to_go_typename()).into()
638636
}
639-
Self::SchemaRef(name) => name.clone().into(),
640-
Self::StringConst(_) => {
641-
unreachable!("FieldType::const should never be exposed to template code")
642-
}
637+
Self::SchemaRef(name) => filter_schema_ref(name, "map[string]any"),
638+
Self::StringConst(_) => "string".into(),
643639
}
644640
}
645641

@@ -659,10 +655,8 @@ impl FieldType {
659655
Self::JsonObject => "Map<String,Any>".into(),
660656
Self::List(field_type) => format!("List<{}>", field_type.to_kotlin_typename()).into(),
661657
Self::Set(field_type) => format!("Set<{}>", field_type.to_kotlin_typename()).into(),
662-
Self::SchemaRef(name) => name.clone().into(),
663-
Self::StringConst(_) => {
664-
unreachable!("FieldType::const should never be exposed to template code")
665-
}
658+
Self::SchemaRef(name) => filter_schema_ref(name, "Map<String,Any>"),
659+
Self::StringConst(_) => "String".into(),
666660
}
667661
}
668662

@@ -681,10 +675,8 @@ impl FieldType {
681675
Self::Map { value_ty } => {
682676
format!("{{ [key: string]: {} }}", value_ty.to_js_typename()).into()
683677
}
684-
Self::SchemaRef(name) => name.clone().into(),
685-
Self::StringConst(_) => {
686-
unreachable!("FieldType::const should never be exposed to template code")
687-
}
678+
Self::SchemaRef(name) => filter_schema_ref(name, "any"),
679+
Self::StringConst(_) => "string".into(),
688680
}
689681
}
690682

@@ -710,14 +702,23 @@ impl FieldType {
710702
value_ty.to_rust_typename(),
711703
)
712704
.into(),
713-
Self::SchemaRef(name) => name.clone().into(),
714-
Self::StringConst(_) => unreachable!("FieldType::const should never be exposed to template code"),
705+
Self::SchemaRef(name) => filter_schema_ref(name, "serde_json::Value"),
706+
Self::StringConst(_) => "String".into()
715707
}
716708
}
717709

718710
pub(crate) fn referenced_schema(&self) -> Option<&str> {
719711
match self {
720-
Self::SchemaRef(v) => Some(v),
712+
Self::SchemaRef(v) => {
713+
// TODO(10055): the `BackgroundTaskFinishedEvent2` struct has a field with type of `Data`
714+
// this corresponds to a `#[serde(untagged)]` enum `svix_server::v1::endpoints::background_tasks::Data`
715+
// we should change this server side, but for now I am changing it here
716+
if v == "Data" {
717+
None
718+
} else {
719+
Some(v)
720+
}
721+
}
721722
Self::List(ty) | Self::Set(ty) | Self::Map { value_ty: ty } => ty.referenced_schema(),
722723
_ => None,
723724
}
@@ -729,7 +730,7 @@ impl FieldType {
729730
Self::Int16 | Self::UInt16 | Self::Int32 | Self::Int64 | Self::UInt64 => "int".into(),
730731
Self::String => "str".into(),
731732
Self::DateTime => "datetime".into(),
732-
Self::SchemaRef(name) => name.clone().into(),
733+
Self::SchemaRef(name) => filter_schema_ref(name, "t.Dict[str, t.Any]"),
733734
Self::Uri => "str".into(),
734735
Self::JsonObject => "t.Dict[str, t.Any]".into(),
735736
Self::Set(field_type) | Self::List(field_type) => {
@@ -738,9 +739,7 @@ impl FieldType {
738739
Self::Map { value_ty } => {
739740
format!("t.Dict[str, {}]", value_ty.to_python_typename()).into()
740741
}
741-
Self::StringConst(_) => {
742-
unreachable!("FieldType::const should never be exposed to template code")
743-
}
742+
Self::StringConst(_) => "str".into(),
744743
}
745744
}
746745

@@ -762,10 +761,9 @@ impl FieldType {
762761
FieldType::Map { value_ty } => {
763762
format!("Map<String,{}>", value_ty.to_java_typename()).into()
764763
}
765-
FieldType::SchemaRef(name) => name.clone().into(),
766-
FieldType::StringConst(_) => {
767-
unreachable!("FieldType::const should never be exposed to template code")
768-
}
764+
FieldType::SchemaRef(name) => filter_schema_ref(name, "Object"),
765+
// backwards compat
766+
FieldType::StringConst(_) => "TypeEnum".into(),
769767
}
770768
}
771769

@@ -853,6 +851,10 @@ impl minijinja::value::Object for FieldType {
853851
ensure_no_args(args, "is_json_object")?;
854852
Ok(matches!(**self, Self::JsonObject).into())
855853
}
854+
"is_string_const" => {
855+
ensure_no_args(args, "is_string_const")?;
856+
Ok(matches!(**self, Self::StringConst(_)).into())
857+
}
856858

857859
// Returns the inner type of a list or set
858860
"inner_type" => {
@@ -878,6 +880,14 @@ impl minijinja::value::Object for FieldType {
878880
};
879881
Ok(ty.into())
880882
}
883+
"string_const_val" => {
884+
ensure_no_args(args, "string_const_val")?;
885+
let val = match &**self {
886+
Self::StringConst(val) => Some(minijinja::Value::from_safe_string(val.clone())),
887+
_ => None,
888+
};
889+
Ok(val.into())
890+
}
881891
_ => Err(minijinja::Error::from(minijinja::ErrorKind::UnknownMethod)),
882892
}
883893
}
@@ -901,3 +911,14 @@ impl serde::Serialize for FieldType {
901911
minijinja::Value::from_object(self.clone()).serialize(serializer)
902912
}
903913
}
914+
915+
fn filter_schema_ref<'a>(name: &'a String, json_obj_typename: &'a str) -> Cow<'a, str> {
916+
// TODO(10055): the `BackgroundTaskFinishedEvent2` struct has a field with type of `Data`
917+
// this corresponds to a `#[serde(untagged)]` enum `svix_server::v1::endpoints::background_tasks::Data`
918+
// we should change this server side, but for now I am changing it here
919+
if name == "Data" {
920+
json_obj_typename.into()
921+
} else {
922+
name.clone().into()
923+
}
924+
}

0 commit comments

Comments
 (0)