From 3fb8bbef1d84a288830afbbfd0cad9a8017f46f3 Mon Sep 17 00:00:00 2001 From: Adam Basfop Cavendish Date: Fri, 23 Oct 2026 21:02:36 +0800 Subject: [PATCH] refactor: structure TypeScript response dispatch - Replace formatted status branches, lazy error payloads, and JSON conversion callbacks with structured sigil-stitch blocks and semantic types and imports. - Preserve response precedence, decoding behavior, generated APIs, wire payloads, and contextual emission errors while pinning response combinations and renders. - Synchronize Java and Kotlin closed-sum snapshots and exact adapter expectations with normalized composition, changing only whitespace and keeping assertions. - Require the upstream composition fixes without committing machine-local Cargo configuration or local-only dependency lockfile changes. --- docs/src/golden_testing.md | 4 + src/generators/java/okhttp/sigil_emit.rs | 11 +- src/generators/kotlin/okhttp/sigil_emit.rs | 9 +- .../typescript/fetch/sigil_emit_api.rs | 501 +++++++++++------- .../valid/typescript-response-dispatch.yaml | 136 +++++ .../models/AnyOfTypes.java.golden | 19 +- .../models/OneOfObjects.java.golden | 23 +- .../models/OneOfTypes.java.golden | 27 +- .../models/OneOfWithNull.java.golden | 24 +- .../models/AdjacentlyTaggedEnum.java.golden | 11 +- .../models/ExternallyTaggedEnum.java.golden | 11 +- .../models/InternallyTaggedEnum.java.golden | 11 +- .../enum-repr/models/MixedEnum.java.golden | 41 +- .../enum-repr/models/UntaggedEnum.java.golden | 27 +- .../models/Choice.java.golden | 29 +- .../models/Foo.java.golden | 43 +- .../models/Event.java.golden | 13 +- .../models/Resource.java.golden | 16 +- ...nionTypeNameForTesting20260630.java.golden | 16 +- .../models/Setup.java.golden | 13 +- .../models/ContainerKind.java.golden | 11 +- .../models/VolumeKind.java.golden | 11 +- .../models/ContainerImage.java.golden | 11 +- .../models/Foo.java.golden | 37 +- .../models/Qux.java.golden | 27 +- .../models/Foo.java.golden | 31 +- .../models/Config.java.golden | 23 +- .../models/Foo.java.golden | 33 +- .../models/Foo.java.golden | 43 +- .../models/Foo.java.golden | 28 +- .../models/AnyOfTypes.kt.golden | 15 +- .../models/OneOfObjects.kt.golden | 19 +- .../models/OneOfTypes.kt.golden | 21 +- .../models/OneOfWithNull.kt.golden | 20 +- .../models/AdjacentlyTaggedEnum.kt.golden | 11 +- .../models/ExternallyTaggedEnum.kt.golden | 11 +- .../models/InternallyTaggedEnum.kt.golden | 11 +- .../enum-repr/models/MixedEnum.kt.golden | 33 +- .../enum-repr/models/UntaggedEnum.kt.golden | 23 +- .../models/Choice.kt.golden | 23 +- .../models/Foo.kt.golden | 37 +- .../models/Event.kt.golden | 13 +- .../models/Resource.kt.golden | 16 +- ...dUnionTypeNameForTesting20260630.kt.golden | 16 +- .../models/Setup.kt.golden | 13 +- .../models/ContainerKind.kt.golden | 11 +- .../models/VolumeKind.kt.golden | 11 +- .../models/ContainerImage.kt.golden | 11 +- .../models/Foo.kt.golden | 31 +- .../models/Qux.kt.golden | 23 +- .../models/Foo.kt.golden | 25 +- .../models/Config.kt.golden | 20 +- .../models/Foo.kt.golden | 27 +- .../models/Foo.kt.golden | 37 +- .../models/Foo.kt.golden | 22 +- .../nested-nullable/apis/ValuesApi.ts.golden | 10 +- .../petstore/apis/StoreApi.ts.golden | 2 +- .../response-dispatch/README.md.golden | 248 +++++++++ .../apis/ResponsesApi.ts.golden | 367 +++++++++++++ .../response-dispatch/apis/index.ts.golden | 26 + .../response-dispatch/index.ts.golden | 8 + .../response-dispatch/models/Detail.ts.golden | 8 + .../response-dispatch/models/index.ts.golden | 6 + .../response-dispatch/package.json.golden | 26 + .../runtime/runtime.ts.golden | 490 +++++++++++++++++ .../tsconfig.esm.json.golden | 7 + .../response-dispatch/tsconfig.json.golden | 31 ++ .../README.md.golden | 248 +++++++++ .../apis/ResponsesApi.ts.golden | 368 +++++++++++++ .../apis/index.ts.golden | 26 + .../index.ts.golden | 8 + .../models/Detail.ts.golden | 24 + .../models/index.ts.golden | 7 + .../package.json.golden | 26 + .../runtime/runtime.ts.golden | 490 +++++++++++++++++ .../tsconfig.esm.json.golden | 7 + .../tsconfig.json.golden | 31 ++ .../apis/StoreApi.ts.golden | 2 +- tests/golden_tests_typescript_fetch.rs | 66 +++ 79 files changed, 3146 insertions(+), 1126 deletions(-) create mode 100644 tests/fixtures/valid/typescript-response-dispatch.yaml create mode 100644 tests/golden/typescript/typescript-fetch/response-dispatch/README.md.golden create mode 100644 tests/golden/typescript/typescript-fetch/response-dispatch/apis/ResponsesApi.ts.golden create mode 100644 tests/golden/typescript/typescript-fetch/response-dispatch/apis/index.ts.golden create mode 100644 tests/golden/typescript/typescript-fetch/response-dispatch/index.ts.golden create mode 100644 tests/golden/typescript/typescript-fetch/response-dispatch/models/Detail.ts.golden create mode 100644 tests/golden/typescript/typescript-fetch/response-dispatch/models/index.ts.golden create mode 100644 tests/golden/typescript/typescript-fetch/response-dispatch/package.json.golden create mode 100644 tests/golden/typescript/typescript-fetch/response-dispatch/runtime/runtime.ts.golden create mode 100644 tests/golden/typescript/typescript-fetch/response-dispatch/tsconfig.esm.json.golden create mode 100644 tests/golden/typescript/typescript-fetch/response-dispatch/tsconfig.json.golden create mode 100644 tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-response-dispatch/README.md.golden create mode 100644 tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-response-dispatch/apis/ResponsesApi.ts.golden create mode 100644 tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-response-dispatch/apis/index.ts.golden create mode 100644 tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-response-dispatch/index.ts.golden create mode 100644 tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-response-dispatch/models/Detail.ts.golden create mode 100644 tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-response-dispatch/models/index.ts.golden create mode 100644 tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-response-dispatch/package.json.golden create mode 100644 tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-response-dispatch/runtime/runtime.ts.golden create mode 100644 tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-response-dispatch/tsconfig.esm.json.golden create mode 100644 tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-response-dispatch/tsconfig.json.golden diff --git a/docs/src/golden_testing.md b/docs/src/golden_testing.md index 76771bde2..f698ef0c2 100644 --- a/docs/src/golden_testing.md +++ b/docs/src/golden_testing.md @@ -169,6 +169,10 @@ suite. The independent JVM Client Tests CI matrix and `just ci` run these checks - Development and ordinary CI validation select current Rust `stable` (locally validated with Rust 1.99.0). MSRV checks explicitly select Rust 1.90.0; they do not substitute for stable validation. +- Local TypeScript validation uses TypeScript 7.0.2 and Vite+ 1.3.0 on Node + 24.22.1. The native validators use the active `tsc` and `vp` on `PATH`; when + `tsc` is not globally installed, `npm exec --package typescript -- just + golden-typescript::build-tsc` supplies it without changing generated packages. - Current local JVM validation uses Homebrew JDK 27 with Gradle 9.8.1, respecting `JAVA_HOME` or the active JVM. CI validates on JDK 27. Generated libraries may still declare a Java 17 compile target, independently of the consumer test JVM. diff --git a/src/generators/java/okhttp/sigil_emit.rs b/src/generators/java/okhttp/sigil_emit.rs index de2967dae..1c55859b2 100644 --- a/src/generators/java/okhttp/sigil_emit.rs +++ b/src/generators/java/okhttp/sigil_emit.rs @@ -1173,17 +1173,11 @@ mod tests { JsonElement element = JsonParser.parseReader(reader); if (element.isJsonNull()) { return null; - } - if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString() - ) { - return new Choice.VariantString(element - .getAsString()); - + if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString()) { + return new Choice.VariantString(element.getAsString()); } throw new JsonParseException("unknown Choice union value"); - - } @Override @@ -1194,7 +1188,6 @@ mod tests { } if (value instanceof Choice.VariantString) { gson.toJson(gson.toJsonTree(((Choice.VariantString) value).value0()), writer); - return; } } diff --git a/src/generators/kotlin/okhttp/sigil_emit.rs b/src/generators/kotlin/okhttp/sigil_emit.rs index 8fc9b0a7d..6173dcb2f 100644 --- a/src/generators/kotlin/okhttp/sigil_emit.rs +++ b/src/generators/kotlin/okhttp/sigil_emit.rs @@ -933,15 +933,9 @@ mod tests { val element = JsonParser.parseReader(reader) return when { element.isJsonNull -> null - - element.isJsonPrimitive && element.asJsonPrimitive.isString - -> Choice.VariantString(element - .asString) - + element.isJsonPrimitive && element.asJsonPrimitive.isString -> Choice.VariantString(element.asString) else -> throw JsonParseException("unknown Choice union value") - } - } override fun write(writer: JsonWriter, value: Choice?) { @@ -952,7 +946,6 @@ mod tests { when (value) { is Choice.VariantString -> { gson.toJson(gson.toJsonTree(value.value0), writer) - } } } diff --git a/src/generators/typescript/fetch/sigil_emit_api.rs b/src/generators/typescript/fetch/sigil_emit_api.rs index 367c13725..95566457d 100644 --- a/src/generators/typescript/fetch/sigil_emit_api.rs +++ b/src/generators/typescript/fetch/sigil_emit_api.rs @@ -1178,8 +1178,14 @@ fn emit_response_handler( property_naming_camel_case: bool, convertible: &HashSet, ) -> Result<(), SigilStitchError> { - cb.add("// Handle responses\n", vec![]); - emit_error_throw(cb, op, property_naming_camel_case, convertible); + cb.add_code(sigil_quote!(TypeScript { + $comment("Handle responses") + })?); + cb.add_code(build_error_dispatch( + op, + property_naming_camel_case, + convertible, + )?); let mut numeric: Vec<(u16, &IrResponse)> = Vec::new(); let mut wildcards: Vec<(&str, &IrResponse)> = Vec::new(); @@ -1205,51 +1211,76 @@ fn emit_response_handler( .filter(|resp| is_success_status(&resp.status)) .any(|r| !r.content.is_empty()); - if numeric.is_empty() && wildcards.is_empty() { - if let Some(d) = default { - emit_response_return(cb, d, false, property_naming_camel_case, convertible)?; - } else { - emit_fallback_return(cb, fallback_has_body, false)?; - } + let fallback = if let Some(response) = default { + build_response_return(response, property_naming_camel_case, convertible)? } else { - for (i, (code, resp)) in numeric.iter().enumerate() { - let keyword = if i == 0 { "if" } else { "else if" }; - cb.add( - &format!("{keyword} (response.status === {code}) {{\n "), - vec![], - ); - emit_response_return(cb, resp, true, property_naming_camel_case, convertible)?; - cb.add("}\n", vec![]); - } + build_fallback_return(fallback_has_body)? + }; + if numeric.is_empty() && wildcards.is_empty() { + cb.add_code(fallback); + return Ok(()); + } - // Wildcard range checks (4XX, 5XX, etc.) go in the else arm - let else_keyword = if numeric.is_empty() { "" } else { "else " }; - if !wildcards.is_empty() { - for (i, (status, resp)) in wildcards.iter().enumerate() { - let (low, high) = wildcard_status_range(status); - let kw = if i == 0 { else_keyword } else { "else " }; - cb.add( - &format!( - "{kw}if (response.status >= {low} && response.status < {high}) {{\n " - ), - vec![], - ); - emit_response_return(cb, resp, true, property_naming_camel_case, convertible)?; - cb.add("}\n", vec![]); - } - cb.add("else {\n ", vec![]); - } else { - cb.add(&format!("{else_keyword}{{\n "), vec![]); - } + let mut branches = Vec::new(); + for response in numeric + .iter() + .map(|(_, response)| *response) + .chain(wildcards.iter().map(|(_, response)| *response)) + { + branches.push(( + response_status_condition(&response.status)?, + build_response_return(response, property_naming_camel_case, convertible)?, + )); + } + cb.add_code(build_status_dispatch(branches, fallback)?); + Ok(()) +} - if let Some(d) = default { - emit_response_return(cb, d, true, property_naming_camel_case, convertible)?; +fn response_status_condition(status: &str) -> Result { + if let Ok(code) = status.parse::() { + sigil_quote!(TypeScript { response.status === $L(code.to_string()) }) + } else { + let (low, high) = wildcard_status_range(status); + sigil_quote!(TypeScript { response.status >= $L(low.to_string()) && response.status < $L(high.to_string()) }) + } +} + +fn build_status_dispatch( + branches: Vec<(CodeBlock, CodeBlock)>, + fallback: CodeBlock, +) -> Result { + let mut cb = CodeBlock::builder(); + let has_branches = !branches.is_empty(); + for (index, (condition, body)) in branches.into_iter().enumerate() { + let branch = if index == 0 { + sigil_quote!(TypeScript { + if ($C(condition)) { + $C(body) + } + }) } else { - emit_fallback_return(cb, fallback_has_body, true)?; - } - cb.add("}\n", vec![]); + sigil_quote!(TypeScript { + else if ($C(condition)) { + $C(body) + } + }) + }?; + cb.add_code(branch); } - Ok(()) + if has_branches { + cb.add_code(sigil_quote!(TypeScript { + else { + $C(fallback) + } + })?); + } else { + // A bare scope is ambiguous with an object literal in sigil_quote!; + // the structural block API expresses that intent without raw braces. + cb.begin_control_flow_with_intent(sigil_stitch::code_node::BlockIntent::Generic, "", ()); + cb.add_code(fallback); + cb.end_control_flow(); + } + cb.build() } fn wildcard_status_range(status: &str) -> (u16, u16) { @@ -1270,18 +1301,11 @@ fn is_success_status(status: &str) -> bool { || status.eq_ignore_ascii_case("2XX") } -fn emit_error_throw( - cb: &mut sigil_stitch::code_block::CodeBlockBuilder, +fn build_error_dispatch( op: &IrOperation, property_naming_camel_case: bool, convertible: &HashSet, -) { - cb.add( - "if (!(response.status >= 200 && response.status < 300)) {\n", - vec![], - ); - cb.add(" const errorRaw = response.clone();\n", vec![]); - +) -> Result { let mut numeric = Vec::new(); let mut wildcards = Vec::new(); let mut default = None; @@ -1301,132 +1325,121 @@ fn emit_error_throw( numeric.sort_by_key(|(code, _)| *code); let mut used = HashSet::new(); - let mut emitted_any = false; - for (code, resp) in numeric { - let keyword = if emitted_any { "else if" } else { "if" }; - let kind = unique_ts_kind(&response_detail_kind(&resp.status), &mut used); - cb.add( - &format!(" {keyword} (response.status === {code}) {{\n "), - vec![], - ); - emit_error_throw_for_response(cb, op, resp, &kind, property_naming_camel_case, convertible); - cb.add(" }\n", vec![]); - emitted_any = true; - } - for (status, resp) in wildcards { - let (low, high) = wildcard_status_range(status); - let keyword = if emitted_any { "else if" } else { "if" }; + let mut branches = Vec::new(); + for resp in numeric + .iter() + .map(|(_, response)| *response) + .chain(wildcards.iter().map(|(_, response)| *response)) + { let kind = unique_ts_kind(&response_detail_kind(&resp.status), &mut used); - cb.add( - &format!(" {keyword} (response.status >= {low} && response.status < {high}) {{\n "), - vec![], - ); - emit_error_throw_for_response(cb, op, resp, &kind, property_naming_camel_case, convertible); - cb.add(" }\n", vec![]); - emitted_any = true; + branches.push(( + response_status_condition(&resp.status)?, + build_error_throw_for_response( + op, + resp, + &kind, + property_naming_camel_case, + convertible, + )?, + )); } - let keyword = if emitted_any { "else" } else { "" }; - cb.add(&format!(" {keyword} {{\n "), vec![]); - if let Some(resp) = default { + let fallback = if let Some(resp) = default { let kind = unique_ts_kind(&response_detail_kind(&resp.status), &mut used); - emit_error_throw_for_response(cb, op, resp, &kind, property_naming_camel_case, convertible); + build_error_throw_for_response(op, resp, &kind, property_naming_camel_case, convertible)? } else { - let class_name = error_class_name(op); - cb.add( - &format!( - "let errorValue: Promise | undefined;\n throw new {class_name}(response, {{ kind: 'unexpected', status: response.status, raw: response, value: () => errorValue ??= errorRaw.clone().blob() }});\n" - ), - vec![], - ); - } - cb.add(" }\n", vec![]); - cb.add("}\n", vec![]); + build_error_throw( + op, + "unexpected", + sigil_quote!(TypeScript { response.status })?, + TypeName::primitive("Blob"), + sigil_quote!(TypeScript { errorRaw.clone().blob() })?, + )? + }; + let dispatch = build_status_dispatch(branches, fallback)?; + sigil_quote!(TypeScript { + if (!(response.status >= 200 && response.status < 300)) { + const errorRaw = response.clone(); + $C(dispatch) + } + }) } -fn emit_error_throw_for_response( - cb: &mut sigil_stitch::code_block::CodeBlockBuilder, +fn build_error_throw_for_response( op: &IrOperation, resp: &IrResponse, kind: &str, property_naming_camel_case: bool, convertible: &HashSet, -) { +) -> Result { + let status = if let Ok(code) = resp.status.parse::() { + sigil_quote!(TypeScript { response.status as $L(code.to_string()) }) + } else { + sigil_quote!(TypeScript { response.status }) + }?; + build_error_throw( + op, + kind, + status, + response_value_type(resp), + error_value_expr(resp, property_naming_camel_case, convertible)?, + ) +} + +fn build_error_throw( + op: &IrOperation, + kind: &str, + status: CodeBlock, + value_type: TypeName, + value: CodeBlock, +) -> Result { let class_name = error_class_name(op); - let status_expr = resp - .status - .parse::() - .ok() - .map(|code| format!("response.status as {code}")) - .unwrap_or_else(|| "response.status".to_string()); - let value_expr = error_value_expr(resp, property_naming_camel_case, convertible); - let value_ty = response_value_type(resp); - cb.add( - &format!( - "let errorValue: Promise<%T> | undefined;\n throw new {class_name}(response, {{ kind: '{}', status: {}, raw: response, value: () => errorValue ??= {} }});\n", - kind, status_expr, value_expr.0 - ), - std::iter::once(Arg::TypeName(value_ty)) - .chain(value_expr.1) - .collect::>(), - ); + let cached_type = TypeName::union(vec![ + type_application(TypeName::primitive("Promise"), vec![value_type]), + TypeName::primitive("undefined"), + ]); + // sigil_quote! normalizes inline object literals without brace padding. + // Keep the existing brace padding with static spaces; values stay structured. + sigil_quote!(TypeScript { + let errorValue: $T(cached_type); + throw new $N(class_name)(response, {$L(" ")$N("kind"): $S(kind), status: $C(status), raw: response, value: () => errorValue ??= $C(value)$L(" ")}); + }) } fn error_value_expr( resp: &IrResponse, property_naming_camel_case: bool, convertible: &HashSet, -) -> (String, Vec) { +) -> Result { let kind = classify_response(resp); if property_naming_camel_case && let ResponseKind::Json(Some(_)) = &kind - && let Some(from_json) = response_from_json_transformer(resp, convertible) + && let Some(from_json) = response_from_json_transformer(resp, convertible)? { let wrapper_type = type_application(rt_value("JSONApiResponse"), vec![response_value_type(resp)]); - return ( - format!("new %T(errorRaw.clone(), {from_json}).value()"), - vec![Arg::TypeName(wrapper_type)], - ); + return sigil_quote!(TypeScript { + new $T(wrapper_type)(errorRaw.clone(), $C(from_json)).value() + }); } - match kind { - ResponseKind::Json(Some(body_ty)) => ( - "new %T(errorRaw.clone()).value()".to_string(), - vec![Arg::TypeName(type_application( - rt_value("JSONApiResponse"), - vec![body_ty], - ))], - ), - ResponseKind::Json(None) => ( - "new %T(errorRaw.clone()).value()".to_string(), - vec![Arg::TypeName(type_application( - rt_value("JSONApiResponse"), - vec![TypeName::primitive("unknown")], - ))], - ), - ResponseKind::Text => ( - "new %T(errorRaw.clone()).value()".to_string(), - vec![Arg::TypeName(rt_value("TextApiResponse"))], - ), - ResponseKind::Blob => ( - "new %T(errorRaw.clone()).value()".to_string(), - vec![Arg::TypeName(rt_value("BlobApiResponse"))], - ), - ResponseKind::None => ( - "new %T(errorRaw.clone()).value()".to_string(), - vec![Arg::TypeName(rt_value("VoidApiResponse"))], + let wrapper_type = match kind { + ResponseKind::Json(body_ty) => type_application( + rt_value("JSONApiResponse"), + vec![body_ty.unwrap_or_else(|| TypeName::primitive("unknown"))], ), - } + ResponseKind::Text => rt_value("TextApiResponse"), + ResponseKind::Blob => rt_value("BlobApiResponse"), + ResponseKind::None => rt_value("VoidApiResponse"), + }; + sigil_quote!(TypeScript { new $T(wrapper_type)(errorRaw.clone()).value() }) } /// `return new Wrapper(response) as Wrapper & { status: X };` -fn emit_response_return( - cb: &mut sigil_stitch::code_block::CodeBlockBuilder, +fn build_response_return( resp: &IrResponse, - _inside_block: bool, property_naming_camel_case: bool, convertible: &HashSet, -) -> Result<(), SigilStitchError> { +) -> Result { let kind = classify_response(resp); let status_ty = match resp.status.parse::() { Ok(n) => n.to_string(), @@ -1437,7 +1450,7 @@ fn emit_response_return( // inject a fromJSON transformer: `new JSONApiResponse(response, (json) => petFromJSON(json as Pet$Wire))` if property_naming_camel_case && let ResponseKind::Json(Some(_)) = &kind - && let Some(from_json) = response_from_json_transformer(resp, convertible) + && let Some(from_json) = response_from_json_transformer(resp, convertible)? { let wrapper_type = match kind { ResponseKind::Json(Some(body_ty)) => { @@ -1445,15 +1458,10 @@ fn emit_response_return( } _ => unreachable!(), }; - // The transformer is a complete target-language callback expression. let status_type = response_status_type(&status_ty); - cb.add_code( - sigil_quote!(TypeScript { - return new $T(rt_value("JSONApiResponse"))(response, $L(from_json.as_str())) as $T(wrapper_type)$L(" & ")$T(status_type); - }) - ?, - ); - return Ok(()); + return sigil_quote!(TypeScript { + return new $T(rt_value("JSONApiResponse"))(response, $C(from_json)) as $T(wrapper_type)$L(" & ")$T(status_type); + }); } let wrapper_value = match kind { @@ -1473,10 +1481,9 @@ fn emit_response_return( }; let status_type = response_status_type(&status_ty); - cb.add_code(sigil_quote!(TypeScript { + sigil_quote!(TypeScript { return new $T(wrapper_value)(response) as $T(wrapper_type)$L(" & ")$T(status_type); - })?); - Ok(()) + }) } fn response_status_type(status_ty: &str) -> TypeName { @@ -1487,26 +1494,18 @@ fn response_status_type(status_ty: &str) -> TypeName { /// `return new JSONApiResponse(response) as JSONApiResponse & { status: 2xx };` /// (or VoidApiResponse equivalent when no body appears anywhere). -fn emit_fallback_return( - cb: &mut sigil_stitch::code_block::CodeBlockBuilder, - any_body: bool, - _inside_block: bool, -) -> Result<(), SigilStitchError> { +fn build_fallback_return(any_body: bool) -> Result { let json_response = rt_value("JSONApiResponse"); let void_response = rt_value("VoidApiResponse"); let unknown = TypeName::primitive("unknown"); let success_response = rt_type("HttpSuccessResponse"); - cb.add_code( - sigil_quote!(TypeScript { - $if(any_body) { - return new $T(json_response.clone())(response) as $T(json_response)<$T(unknown)> & $T(success_response.clone()); - } $else { - return new $T(void_response.clone())(response) as $T(void_response) & $T(success_response); - } - }) - ?, - ); - Ok(()) + sigil_quote!(TypeScript { + $if(any_body) { + return new $T(json_response.clone())(response) as $T(json_response)<$T(unknown)> & $T(success_response.clone()); + } $else { + return new $T(void_response.clone())(response) as $T(void_response) & $T(success_response); + } + }) } // ============================================================================ @@ -1935,29 +1934,35 @@ fn primitive_to_ts(p: &IrPrimitive) -> &'static str { fn response_from_json_transformer( resp: &IrResponse, convertible: &HashSet, -) -> Option { - let (media_type, type_expr) = pick_response_content(resp)?; +) -> Result, SigilStitchError> { + let Some((media_type, type_expr)) = pick_response_content(resp) else { + return Ok(None); + }; if !is_json_media_type(&media_type) { - return None; + return Ok(None); } - match type_expr { - IrTypeExpr::Named(name) if convertible.contains(name) => { - let pascal = name.to_pascal_case(); - let from_fn = format!("{}FromJSON", fn_base_name(&pascal)); - let wire = format!("{}$Wire", pascal); - Some(format!("(json) => {from_fn}(json as {wire})")) - } + let (name, is_array) = match type_expr { + IrTypeExpr::Named(name) if convertible.contains(name) => (name, false), IrTypeExpr::Array(inner) => match inner.as_ref() { - IrTypeExpr::Named(name) if convertible.contains(name) => { - let pascal = name.to_pascal_case(); - let from_fn = format!("{}FromJSON", fn_base_name(&pascal)); - let wire = format!("{}$Wire", pascal); - Some(format!("(json) => (json as {wire}[]).map({from_fn})")) - } - _ => None, + IrTypeExpr::Named(name) if convertible.contains(name) => (name, true), + _ => return Ok(None), }, - _ => None, - } + _ => return Ok(None), + }; + let pascal = name.to_pascal_case(); + let module = format!("../models/{pascal}"); + let from_fn = TypeName::importable(&module, &format!("{}FromJSON", fn_base_name(&pascal))); + let wire = TypeName::importable_type(&module, &format!("{pascal}$Wire")); + let transformer = if is_array { + sigil_quote!(TypeScript { + (json) => (json as $T(TypeName::array(wire))).map($T(from_fn)) + }) + } else { + sigil_quote!(TypeScript { + (json) => $T(from_fn)(json as $T(wire)) + }) + }?; + Ok(Some(transformer)) } /// For a request body with a Named type, produce the toJSON call: @@ -2285,4 +2290,118 @@ mod tests { ); Ok(()) } + + fn response_dispatch_ir() -> IrSpec { + let parsed = crate::parser::parse_content_yaml(include_str!( + "../../../../tests/fixtures/valid/typescript-response-dispatch.yaml" + )) + .expect("response dispatch fixture parses"); + crate::ir::lower::lower(parsed).expect("response dispatch fixture lowers") + } + + #[test] + fn error_throw_composes_lazy_array_conversion_and_imports() -> Result<(), SigilStitchError> { + let ir = response_dispatch_ir(); + let op = ir + .operations + .iter() + .find(|op| op.operation_id == "readMixed") + .expect("mixed response operation exists"); + let response = op + .responses + .iter() + .find(|response| response.status == "401") + .expect("array error exists"); + let convertible = HashSet::from(["Detail".to_string()]); + let block = + build_error_throw_for_response(op, response, "unauthorized", true, &convertible)?; + + assert_rendered!( + TypeScript::new(), + width = 100, + block, + r#"import type { Detail, Detail$Wire } from '../models/Detail'; +import { detailFromJSON } from '../models/Detail'; +import { JSONApiResponse } from '../runtime/runtime'; + +let errorValue: Promise | undefined; +throw new ReadMixedError(response, { kind: 'unauthorized', status: response.status as 401, raw: response, value: () => errorValue ??= new JSONApiResponse(errorRaw.clone(), (json) => (json as Detail$Wire[]).map(detailFromJSON)).value() }); +"# + ); + Ok(()) + } + + #[test] + fn status_dispatch_composes_conditions_and_returns_without_extra_newlines() + -> Result<(), SigilStitchError> { + let ir = response_dispatch_ir(); + let op = ir + .operations + .iter() + .find(|op| op.operation_id == "readMixed") + .expect("mixed response operation exists"); + let mut branches = Vec::new(); + for status in ["201", "2XX"] { + let response = op + .responses + .iter() + .find(|response| response.status == status) + .expect("success response exists"); + branches.push(( + response_status_condition(status)?, + build_response_return(response, false, &HashSet::new())?, + )); + } + let block = build_status_dispatch(branches, build_fallback_return(true)?)?; + assert_rendered!( + TypeScript::new(), + width = 100, + block, + r#"import type { Detail } from '../models/Detail'; +import type { HttpSuccessResponse } from '../runtime/runtime'; +import { JSONApiResponse } from '../runtime/runtime'; + +if (response.status === 201) { + return new JSONApiResponse(response) as JSONApiResponse & { status: 201 }; +} +else if (response.status >= 200 && response.status < 300) { + return new JSONApiResponse(response) as JSONApiResponse & { status: number }; +} +else { + return new JSONApiResponse(response) as JSONApiResponse &HttpSuccessResponse; +} +"# + ); + Ok(()) + } + + #[test] + fn default_only_error_keeps_its_scope_and_lazy_named_conversion() -> Result<(), SigilStitchError> + { + let ir = response_dispatch_ir(); + let op = ir + .operations + .iter() + .find(|op| op.operation_id == "readDefault") + .expect("default-only operation exists"); + let block = build_error_dispatch(op, true, &HashSet::from(["Detail".to_string()]))?; + assert_rendered!( + TypeScript::new(), + width = 100, + block, + r#"import type { Detail, Detail$Wire } from '../models/Detail'; +import { detailFromJSON } from '../models/Detail'; +import { JSONApiResponse } from '../runtime/runtime'; + +if (!(response.status >= 200 && response.status < 300)) { + const errorRaw = response.clone(); + { + let errorValue: Promise | undefined; + throw new ReadDefaultError(response, { kind: 'default', status: response.status, raw: response, value: () => errorValue ??= new JSONApiResponse(errorRaw.clone(), (json) => detailFromJSON(json as Detail$Wire)).value() }); + } +} +"# + ); + Ok(()) + } } diff --git a/tests/fixtures/valid/typescript-response-dispatch.yaml b/tests/fixtures/valid/typescript-response-dispatch.yaml new file mode 100644 index 000000000..8f28b6d92 --- /dev/null +++ b/tests/fixtures/valid/typescript-response-dispatch.yaml @@ -0,0 +1,136 @@ +openapi: 3.1.0 +info: + title: TypeScript Response Dispatch + version: 1.0.0 +paths: + /mixed: + get: + tags: [Responses] + operationId: readMixed + responses: + 5XX: + description: Wildcard errors precede exact errors in the document. + content: + application/json: + schema: + $ref: '#/components/schemas/Detail' + '409': + description: Text error. + content: + text/plain: + schema: + type: string + 2XX: + description: Wildcard success precedes exact successes in the document. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Detail' + '201': + description: Named JSON success. + content: + application/json: + schema: + $ref: '#/components/schemas/Detail' + '202': + description: Text success. + content: + text/plain: + schema: + type: string + '204': + description: Bodyless success. + '206': + description: Binary success. + content: + application/octet-stream: + schema: + type: string + format: binary + '400': + description: Named JSON error. + content: + application/json: + schema: + $ref: '#/components/schemas/Detail' + '401': + description: Array JSON error. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Detail' + '410': + description: Bodyless error. + '415': + description: Binary error. + content: + application/octet-stream: + schema: + type: string + format: binary + default: + description: Unknown JSON fallback. + content: + application/json: + schema: {} + /wildcards: + get: + tags: [Responses] + operationId: readWildcards + responses: + 2XX: + description: Bodyless wildcard success. + 4XX: + description: Text wildcard error without exact errors or default. + content: + text/plain: + schema: + type: string + 5XX: + description: Binary wildcard error. + content: + application/octet-stream: + schema: + type: string + format: binary + /default: + get: + tags: [Responses] + operationId: readDefault + responses: + default: + description: Default only, with no exact or wildcard branches. + content: + application/json: + schema: + $ref: '#/components/schemas/Detail' + /success: + get: + tags: [Responses] + operationId: readSuccessOnly + responses: + '204': + description: No documented errors. + /error: + get: + tags: [Responses] + operationId: readErrorOnly + responses: + '400': + description: No documented successes. + content: + text/plain: + schema: + type: string +components: + schemas: + Detail: + type: object + required: [error_message] + properties: + error_message: + type: string diff --git a/tests/golden/java/java-okhttp/comprehensive-schemas/models/AnyOfTypes.java.golden b/tests/golden/java/java-okhttp/comprehensive-schemas/models/AnyOfTypes.java.golden index c97882461..7fa87afe6 100644 --- a/tests/golden/java/java-okhttp/comprehensive-schemas/models/AnyOfTypes.java.golden +++ b/tests/golden/java/java-okhttp/comprehensive-schemas/models/AnyOfTypes.java.golden @@ -35,23 +35,14 @@ final class AnyOfTypesJsonAdapter extends TypeAdapter { JsonElement element = JsonParser.parseReader(reader); if (element.isJsonNull()) { return null; - } - if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString() - ) { - return new AnyOfTypes.VariantString(element - .getAsString()); - + if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString()) { + return new AnyOfTypes.VariantString(element.getAsString()); } - if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isNumber() - ) { - return new AnyOfTypes.VariantNumber(gson.fromJson(element - , Double.class)); - + if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isNumber()) { + return new AnyOfTypes.VariantNumber(gson.fromJson(element, Double.class)); } throw new JsonParseException("unknown AnyOfTypes union value"); - - } @Override @@ -62,12 +53,10 @@ final class AnyOfTypesJsonAdapter extends TypeAdapter { } if (value instanceof AnyOfTypes.VariantString) { gson.toJson(gson.toJsonTree(((AnyOfTypes.VariantString) value).value0()), writer); - return; } if (value instanceof AnyOfTypes.VariantNumber) { gson.toJson(gson.toJsonTree(((AnyOfTypes.VariantNumber) value).value0()), writer); - return; } } diff --git a/tests/golden/java/java-okhttp/comprehensive-schemas/models/OneOfObjects.java.golden b/tests/golden/java/java-okhttp/comprehensive-schemas/models/OneOfObjects.java.golden index 12733262c..3ae650f22 100644 --- a/tests/golden/java/java-okhttp/comprehensive-schemas/models/OneOfObjects.java.golden +++ b/tests/golden/java/java-okhttp/comprehensive-schemas/models/OneOfObjects.java.golden @@ -35,27 +35,14 @@ final class OneOfObjectsJsonAdapter extends TypeAdapter { JsonElement element = JsonParser.parseReader(reader); if (element.isJsonNull()) { return null; - } - if (((element.isJsonObject() - && element.getAsJsonObject().has("id") - ) - && element.getAsJsonObject().has("name") - ) - ) { - return new OneOfObjects.VariantBasicObject(gson.fromJson(element - , new TypeToken() {}.getType())); - + if (((element.isJsonObject() && element.getAsJsonObject().has("id")) && element.getAsJsonObject().has("name"))) { + return new OneOfObjects.VariantBasicObject(gson.fromJson(element, new TypeToken() {}.getType())); } - if (element.isJsonObject() - ) { - return new OneOfObjects.VariantNestedObject(gson.fromJson(element - , new TypeToken() {}.getType())); - + if (element.isJsonObject()) { + return new OneOfObjects.VariantNestedObject(gson.fromJson(element, new TypeToken() {}.getType())); } throw new JsonParseException("unknown OneOfObjects union value"); - - } @Override @@ -66,12 +53,10 @@ final class OneOfObjectsJsonAdapter extends TypeAdapter { } if (value instanceof OneOfObjects.VariantBasicObject) { gson.toJson(gson.toJsonTree(((OneOfObjects.VariantBasicObject) value).value0()), writer); - return; } if (value instanceof OneOfObjects.VariantNestedObject) { gson.toJson(gson.toJsonTree(((OneOfObjects.VariantNestedObject) value).value0()), writer); - return; } } diff --git a/tests/golden/java/java-okhttp/comprehensive-schemas/models/OneOfTypes.java.golden b/tests/golden/java/java-okhttp/comprehensive-schemas/models/OneOfTypes.java.golden index 8f97dd2f3..6b368d8e9 100644 --- a/tests/golden/java/java-okhttp/comprehensive-schemas/models/OneOfTypes.java.golden +++ b/tests/golden/java/java-okhttp/comprehensive-schemas/models/OneOfTypes.java.golden @@ -37,29 +37,17 @@ final class OneOfTypesJsonAdapter extends TypeAdapter { JsonElement element = JsonParser.parseReader(reader); if (element.isJsonNull()) { return null; - } - if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString() - ) { - return new OneOfTypes.VariantString(element - .getAsString()); - + if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString()) { + return new OneOfTypes.VariantString(element.getAsString()); } - if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isNumber() - ) { - return new OneOfTypes.VariantNumber(gson.fromJson(element - , Double.class)); - + if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isNumber()) { + return new OneOfTypes.VariantNumber(gson.fromJson(element, Double.class)); } - if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isBoolean() - ) { - return new OneOfTypes.VariantBoolean(element - .getAsBoolean()); - + if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isBoolean()) { + return new OneOfTypes.VariantBoolean(element.getAsBoolean()); } throw new JsonParseException("unknown OneOfTypes union value"); - - } @Override @@ -70,17 +58,14 @@ final class OneOfTypesJsonAdapter extends TypeAdapter { } if (value instanceof OneOfTypes.VariantString) { gson.toJson(gson.toJsonTree(((OneOfTypes.VariantString) value).value0()), writer); - return; } if (value instanceof OneOfTypes.VariantNumber) { gson.toJson(gson.toJsonTree(((OneOfTypes.VariantNumber) value).value0()), writer); - return; } if (value instanceof OneOfTypes.VariantBoolean) { gson.toJson(gson.toJsonTree(((OneOfTypes.VariantBoolean) value).value0()), writer); - return; } } diff --git a/tests/golden/java/java-okhttp/comprehensive-schemas/models/OneOfWithNull.java.golden b/tests/golden/java/java-okhttp/comprehensive-schemas/models/OneOfWithNull.java.golden index a44839358..3c05f4aed 100644 --- a/tests/golden/java/java-okhttp/comprehensive-schemas/models/OneOfWithNull.java.golden +++ b/tests/golden/java/java-okhttp/comprehensive-schemas/models/OneOfWithNull.java.golden @@ -40,27 +40,14 @@ final class OneOfWithNullJsonAdapter extends TypeAdapter { JsonElement element = JsonParser.parseReader(reader); if (element.isJsonNull()) { return OneOfWithNull.VariantNull.INSTANCE; - } - if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString() - ) { - return new OneOfWithNull.VariantString(element - .getAsString()); - + if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString()) { + return new OneOfWithNull.VariantString(element.getAsString()); } - if (((element.isJsonObject() - && element.getAsJsonObject().has("id") - ) - && element.getAsJsonObject().has("name") - ) - ) { - return new OneOfWithNull.VariantBasicObject(gson.fromJson(element - , new TypeToken() {}.getType())); - + if (((element.isJsonObject() && element.getAsJsonObject().has("id")) && element.getAsJsonObject().has("name"))) { + return new OneOfWithNull.VariantBasicObject(gson.fromJson(element, new TypeToken() {}.getType())); } throw new JsonParseException("unknown OneOfWithNull union value"); - - } @Override @@ -71,17 +58,14 @@ final class OneOfWithNullJsonAdapter extends TypeAdapter { } if (value instanceof OneOfWithNull.VariantString) { gson.toJson(gson.toJsonTree(((OneOfWithNull.VariantString) value).value0()), writer); - return; } if (value instanceof OneOfWithNull.VariantBasicObject) { gson.toJson(gson.toJsonTree(((OneOfWithNull.VariantBasicObject) value).value0()), writer); - return; } if (value instanceof OneOfWithNull.VariantNull) { writer.nullValue(); - return; } } diff --git a/tests/golden/java/java-okhttp/enum-repr/models/AdjacentlyTaggedEnum.java.golden b/tests/golden/java/java-okhttp/enum-repr/models/AdjacentlyTaggedEnum.java.golden index fc5a6acdf..6354c4917 100644 --- a/tests/golden/java/java-okhttp/enum-repr/models/AdjacentlyTaggedEnum.java.golden +++ b/tests/golden/java/java-okhttp/enum-repr/models/AdjacentlyTaggedEnum.java.golden @@ -47,17 +47,12 @@ final class AdjacentlyTaggedEnumJsonAdapter extends TypeAdapter() {}.getType())); - + return new AdjacentlyTaggedEnum.VariantAdjacentlyTaggedEnumVariantA(gson.fromJson(object.get("data"), new TypeToken() {}.getType())); } if ("ADJACENTLY_TAGGED_ENUM_VARIANT_B".equals(discriminator)) { - return new AdjacentlyTaggedEnum.VariantAdjacentlyTaggedEnumVariantB(gson.fromJson(object.get("data") - , new TypeToken() {}.getType())); - + return new AdjacentlyTaggedEnum.VariantAdjacentlyTaggedEnumVariantB(gson.fromJson(object.get("data"), new TypeToken() {}.getType())); } throw new JsonParseException("unknown AdjacentlyTaggedEnum discriminator"); - } @Override @@ -71,7 +66,6 @@ final class AdjacentlyTaggedEnumJsonAdapter extends TypeAdapter() {}.getType())); - + return new ExternallyTaggedEnum.VariantExternallyTaggedEnumVariantA(gson.fromJson(object.get("EXTERNALLY_TAGGED_ENUM_VARIANT_A"), new TypeToken() {}.getType())); } if (object.has("EXTERNALLY_TAGGED_ENUM_VARIANT_B")) { - return new ExternallyTaggedEnum.VariantExternallyTaggedEnumVariantB(gson.fromJson(object.get("EXTERNALLY_TAGGED_ENUM_VARIANT_B") - , new TypeToken() {}.getType())); - + return new ExternallyTaggedEnum.VariantExternallyTaggedEnumVariantB(gson.fromJson(object.get("EXTERNALLY_TAGGED_ENUM_VARIANT_B"), new TypeToken() {}.getType())); } throw new JsonParseException("unknown ExternallyTaggedEnum variant"); - } @Override @@ -69,14 +64,12 @@ final class ExternallyTaggedEnumJsonAdapter extends TypeAdapter() {}.getType())); - + return new InternallyTaggedEnum.VariantInternallyTaggedEnumVariantA(gson.fromJson(object, new TypeToken() {}.getType())); } if ("INTERNALLY_TAGGED_ENUM_VARIANT_B".equals(discriminator)) { - return new InternallyTaggedEnum.VariantInternallyTaggedEnumVariantB(gson.fromJson(object - , new TypeToken() {}.getType())); - + return new InternallyTaggedEnum.VariantInternallyTaggedEnumVariantB(gson.fromJson(object, new TypeToken() {}.getType())); } throw new JsonParseException("unknown InternallyTaggedEnum discriminator"); - } @Override @@ -70,14 +65,12 @@ final class InternallyTaggedEnumJsonAdapter extends TypeAdapter { JsonElement element = JsonParser.parseReader(reader); if (element.isJsonNull()) { return null; - } - if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString() - && element.getAsString().equals("SimpleA") - ) { - return new MixedEnum.VariantSimpleA(element - .getAsString()); - + if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString() && element.getAsString().equals("SimpleA")) { + return new MixedEnum.VariantSimpleA(element.getAsString()); } - if ((element.isJsonObject() - && element.getAsJsonObject().has("VariantA") - ) - ) { - return new MixedEnum.VariantMixedEnumMember2(gson.fromJson(element - , new TypeToken() {}.getType())); - + if ((element.isJsonObject() && element.getAsJsonObject().has("VariantA"))) { + return new MixedEnum.VariantMixedEnumMember2(gson.fromJson(element, new TypeToken() {}.getType())); } - if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString() - && element.getAsString().equals("SimpleB") - ) { - return new MixedEnum.VariantSimpleB(element - .getAsString()); - + if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString() && element.getAsString().equals("SimpleB")) { + return new MixedEnum.VariantSimpleB(element.getAsString()); } - if ((element.isJsonObject() - && element.getAsJsonObject().has("VariantB") - ) - ) { - return new MixedEnum.VariantMixedEnumMember4(gson.fromJson(element - , new TypeToken() {}.getType())); - + if ((element.isJsonObject() && element.getAsJsonObject().has("VariantB"))) { + return new MixedEnum.VariantMixedEnumMember4(gson.fromJson(element, new TypeToken() {}.getType())); } throw new JsonParseException("unknown MixedEnum union value"); - - } @Override @@ -87,22 +66,18 @@ final class MixedEnumJsonAdapter extends TypeAdapter { } if (value instanceof MixedEnum.VariantSimpleA) { gson.toJson(gson.toJsonTree(((MixedEnum.VariantSimpleA) value).value0()), writer); - return; } if (value instanceof MixedEnum.VariantMixedEnumMember2) { gson.toJson(gson.toJsonTree(((MixedEnum.VariantMixedEnumMember2) value).value0()), writer); - return; } if (value instanceof MixedEnum.VariantSimpleB) { gson.toJson(gson.toJsonTree(((MixedEnum.VariantSimpleB) value).value0()), writer); - return; } if (value instanceof MixedEnum.VariantMixedEnumMember4) { gson.toJson(gson.toJsonTree(((MixedEnum.VariantMixedEnumMember4) value).value0()), writer); - return; } } diff --git a/tests/golden/java/java-okhttp/enum-repr/models/UntaggedEnum.java.golden b/tests/golden/java/java-okhttp/enum-repr/models/UntaggedEnum.java.golden index 86eaa1a74..baf5ccb37 100644 --- a/tests/golden/java/java-okhttp/enum-repr/models/UntaggedEnum.java.golden +++ b/tests/golden/java/java-okhttp/enum-repr/models/UntaggedEnum.java.golden @@ -38,31 +38,14 @@ final class UntaggedEnumJsonAdapter extends TypeAdapter { JsonElement element = JsonParser.parseReader(reader); if (element.isJsonNull()) { return null; - } - if (((element.isJsonObject() - && element.getAsJsonObject().has("field1") - ) - && element.getAsJsonObject().has("field2") - ) - ) { - return new UntaggedEnum.VariantVariantA(gson.fromJson(element - , new TypeToken() {}.getType())); - + if (((element.isJsonObject() && element.getAsJsonObject().has("field1")) && element.getAsJsonObject().has("field2"))) { + return new UntaggedEnum.VariantVariantA(gson.fromJson(element, new TypeToken() {}.getType())); } - if (((element.isJsonObject() - && element.getAsJsonObject().has("field3") - ) - && element.getAsJsonObject().has("field4") - ) - ) { - return new UntaggedEnum.VariantVariantB(gson.fromJson(element - , new TypeToken() {}.getType())); - + if (((element.isJsonObject() && element.getAsJsonObject().has("field3")) && element.getAsJsonObject().has("field4"))) { + return new UntaggedEnum.VariantVariantB(gson.fromJson(element, new TypeToken() {}.getType())); } throw new JsonParseException("unknown UntaggedEnum union value"); - - } @Override @@ -73,12 +56,10 @@ final class UntaggedEnumJsonAdapter extends TypeAdapter { } if (value instanceof UntaggedEnum.VariantVariantA) { gson.toJson(gson.toJsonTree(((UntaggedEnum.VariantVariantA) value).value0()), writer); - return; } if (value instanceof UntaggedEnum.VariantVariantB) { gson.toJson(gson.toJsonTree(((UntaggedEnum.VariantVariantB) value).value0()), writer); - return; } } diff --git a/tests/golden/java/java-okhttp/type-aliases-closed-sum-payload-shapes/models/Choice.java.golden b/tests/golden/java/java-okhttp/type-aliases-closed-sum-payload-shapes/models/Choice.java.golden index e5822ada3..e52641ff7 100644 --- a/tests/golden/java/java-okhttp/type-aliases-closed-sum-payload-shapes/models/Choice.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-closed-sum-payload-shapes/models/Choice.java.golden @@ -42,29 +42,18 @@ final class ChoiceJsonAdapter extends TypeAdapter { JsonElement element = JsonParser.parseReader(reader); if (element.isJsonNull()) { return Choice.VariantNull.INSTANCE; - } - if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString() - ) { - return new Choice.VariantString(element - .getAsString()); - + if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString()) { + return new Choice.VariantString(element.getAsString()); } - if (element.isJsonArray() - ) { - return new Choice.VariantArray(gson.fromJson(element - , new TypeToken>() {}.getType())); - + if (element.isJsonArray()) { + return new Choice.VariantArray(gson.fromJson(element, new TypeToken>() {}.getType())); } - if (element.isJsonObject() - ) { - return new Choice.VariantMap(gson.fromJson(element - , new TypeToken>() {}.getType())); - + if (element.isJsonObject()) { + return new Choice.VariantMap(gson.fromJson(element, new TypeToken>() {}.getType())); } throw new JsonParseException("unknown Choice union value"); - - } @Override @@ -75,22 +64,18 @@ final class ChoiceJsonAdapter extends TypeAdapter { } if (value instanceof Choice.VariantString) { gson.toJson(gson.toJsonTree(((Choice.VariantString) value).value0()), writer); - return; } if (value instanceof Choice.VariantArray) { gson.toJson(gson.toJsonTree(((Choice.VariantArray) value).value0()), writer); - return; } if (value instanceof Choice.VariantMap) { gson.toJson(gson.toJsonTree(((Choice.VariantMap) value).value0()), writer); - return; } if (value instanceof Choice.VariantNull) { writer.nullValue(); - return; } } diff --git a/tests/golden/java/java-okhttp/type-aliases-complex-union/models/Foo.java.golden b/tests/golden/java/java-okhttp/type-aliases-complex-union/models/Foo.java.golden index b49f7056f..c2716ba7b 100644 --- a/tests/golden/java/java-okhttp/type-aliases-complex-union/models/Foo.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-complex-union/models/Foo.java.golden @@ -34,45 +34,17 @@ final class FooJsonAdapter extends TypeAdapter { JsonElement element = JsonParser.parseReader(reader); if (element.isJsonNull()) { return null; - } - if ((((element.isJsonObject() - && element.getAsJsonObject().has("bucket") - ) - && element.getAsJsonObject().has("endpoint") - ) - && element.getAsJsonObject().has("type") - ) - ) { - return new Foo.VariantBar(gson.fromJson(element - , new TypeToken() {}.getType())); - + if ((((element.isJsonObject() && element.getAsJsonObject().has("bucket")) && element.getAsJsonObject().has("endpoint")) && element.getAsJsonObject().has("type"))) { + return new Foo.VariantBar(gson.fromJson(element, new TypeToken() {}.getType())); } - if (((element.isJsonObject() - && element.getAsJsonObject().has("path") - ) - && element.getAsJsonObject().has("type") - ) - ) { - return new Foo.VariantBaz(gson.fromJson(element - , new TypeToken() {}.getType())); - + if (((element.isJsonObject() && element.getAsJsonObject().has("path")) && element.getAsJsonObject().has("type"))) { + return new Foo.VariantBaz(gson.fromJson(element, new TypeToken() {}.getType())); } - if ((((element.isJsonObject() - && element.getAsJsonObject().has("namespace") - ) - && element.getAsJsonObject().has("type") - ) - && element.getAsJsonObject().has("url") - ) - ) { - return new Foo.VariantQux(gson.fromJson(element - , new TypeToken() {}.getType())); - + if ((((element.isJsonObject() && element.getAsJsonObject().has("namespace")) && element.getAsJsonObject().has("type")) && element.getAsJsonObject().has("url"))) { + return new Foo.VariantQux(gson.fromJson(element, new TypeToken() {}.getType())); } throw new JsonParseException("unknown Foo union value"); - - } @Override @@ -83,17 +55,14 @@ final class FooJsonAdapter extends TypeAdapter { } if (value instanceof Foo.VariantBar) { gson.toJson(gson.toJsonTree(((Foo.VariantBar) value).value0()), writer); - return; } if (value instanceof Foo.VariantBaz) { gson.toJson(gson.toJsonTree(((Foo.VariantBaz) value).value0()), writer); - return; } if (value instanceof Foo.VariantQux) { gson.toJson(gson.toJsonTree(((Foo.VariantQux) value).value0()), writer); - return; } } diff --git a/tests/golden/java/java-okhttp/type-aliases-discriminated-union-inline-discriminator-only/models/Event.java.golden b/tests/golden/java/java-okhttp/type-aliases-discriminated-union-inline-discriminator-only/models/Event.java.golden index a508db2ef..0846e3dc5 100644 --- a/tests/golden/java/java-okhttp/type-aliases-discriminated-union-inline-discriminator-only/models/Event.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-discriminated-union-inline-discriminator-only/models/Event.java.golden @@ -57,20 +57,14 @@ final class EventJsonAdapter extends TypeAdapter { String discriminator = object.has("kind") ? object.get("kind").getAsString() : null; if ("EVENT_UNSPECIFIED".equals(discriminator)) { return Event.VariantEventUnspecified.INSTANCE; - } if ("EVENT_CREATED".equals(discriminator)) { - return new Event.VariantEventCreated(gson.fromJson(object - , new TypeToken() {}.getType())); - + return new Event.VariantEventCreated(gson.fromJson(object, new TypeToken() {}.getType())); } if ("EVENT_UPDATED".equals(discriminator)) { - return new Event.VariantEventUpdated(gson.fromJson(object - , new TypeToken() {}.getType())); - + return new Event.VariantEventUpdated(gson.fromJson(object, new TypeToken() {}.getType())); } throw new JsonParseException("unknown Event discriminator"); - } @Override @@ -83,21 +77,18 @@ final class EventJsonAdapter extends TypeAdapter { JsonObject object = new JsonObject(); object.addProperty("kind", "EVENT_UNSPECIFIED"); gson.toJson(object, writer); - return; } if (value instanceof Event.VariantEventCreated) { JsonObject object = gson.toJsonTree(((Event.VariantEventCreated) value).value0()).getAsJsonObject(); object.addProperty("kind", "EVENT_CREATED"); gson.toJson(object, writer); - return; } if (value instanceof Event.VariantEventUpdated) { JsonObject object = gson.toJsonTree(((Event.VariantEventUpdated) value).value0()).getAsJsonObject(); object.addProperty("kind", "EVENT_UPDATED"); gson.toJson(object, writer); - return; } } diff --git a/tests/golden/java/java-okhttp/type-aliases-discriminated-union-internally-tagged/models/Resource.java.golden b/tests/golden/java/java-okhttp/type-aliases-discriminated-union-internally-tagged/models/Resource.java.golden index 7a451b2ba..2fefd1980 100644 --- a/tests/golden/java/java-okhttp/type-aliases-discriminated-union-internally-tagged/models/Resource.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-discriminated-union-internally-tagged/models/Resource.java.golden @@ -40,22 +40,15 @@ final class ResourceJsonAdapter extends TypeAdapter { JsonObject object = element.getAsJsonObject(); String discriminator = object.has("kind") ? object.get("kind").getAsString() : null; if ("RESOURCE_UNSPECIFIED".equals(discriminator)) { - return new Resource.VariantResourceUnspecified(gson.fromJson(object - , new TypeToken() {}.getType())); - + return new Resource.VariantResourceUnspecified(gson.fromJson(object, new TypeToken() {}.getType())); } if ("RESOURCE_QUICK".equals(discriminator)) { - return new Resource.VariantResourceQuick(gson.fromJson(object - , new TypeToken() {}.getType())); - + return new Resource.VariantResourceQuick(gson.fromJson(object, new TypeToken() {}.getType())); } if ("RESOURCE_CUSTOM".equals(discriminator)) { - return new Resource.VariantResourceCustom(gson.fromJson(object - , new TypeToken() {}.getType())); - + return new Resource.VariantResourceCustom(gson.fromJson(object, new TypeToken() {}.getType())); } throw new JsonParseException("unknown Resource discriminator"); - } @Override @@ -68,21 +61,18 @@ final class ResourceJsonAdapter extends TypeAdapter { JsonObject object = gson.toJsonTree(((Resource.VariantResourceUnspecified) value).value0()).getAsJsonObject(); object.addProperty("kind", "RESOURCE_UNSPECIFIED"); gson.toJson(object, writer); - return; } if (value instanceof Resource.VariantResourceQuick) { JsonObject object = gson.toJsonTree(((Resource.VariantResourceQuick) value).value0()).getAsJsonObject(); object.addProperty("kind", "RESOURCE_QUICK"); gson.toJson(object, writer); - return; } if (value instanceof Resource.VariantResourceCustom) { JsonObject object = gson.toJsonTree(((Resource.VariantResourceCustom) value).value0()).getAsJsonObject(); object.addProperty("kind", "RESOURCE_CUSTOM"); gson.toJson(object, writer); - return; } } diff --git a/tests/golden/java/java-okhttp/type-aliases-discriminated-union-long-names/models/VeryLongDiscriminatedUnionTypeNameForTesting20260630.java.golden b/tests/golden/java/java-okhttp/type-aliases-discriminated-union-long-names/models/VeryLongDiscriminatedUnionTypeNameForTesting20260630.java.golden index e62efa41f..313976c5c 100644 --- a/tests/golden/java/java-okhttp/type-aliases-discriminated-union-long-names/models/VeryLongDiscriminatedUnionTypeNameForTesting20260630.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-discriminated-union-long-names/models/VeryLongDiscriminatedUnionTypeNameForTesting20260630.java.golden @@ -40,22 +40,15 @@ final class VeryLongDiscriminatedUnionTypeNameForTesting20260630JsonAdapter exte JsonObject object = element.getAsJsonObject(); String discriminator = object.has("kind") ? object.get("kind").getAsString() : null; if ("ALPHA".equals(discriminator)) { - return new VeryLongDiscriminatedUnionTypeNameForTesting20260630.VariantAlpha(gson.fromJson(object - , new TypeToken() {}.getType())); - + return new VeryLongDiscriminatedUnionTypeNameForTesting20260630.VariantAlpha(gson.fromJson(object, new TypeToken() {}.getType())); } if ("BETA".equals(discriminator)) { - return new VeryLongDiscriminatedUnionTypeNameForTesting20260630.VariantBeta(gson.fromJson(object - , new TypeToken() {}.getType())); - + return new VeryLongDiscriminatedUnionTypeNameForTesting20260630.VariantBeta(gson.fromJson(object, new TypeToken() {}.getType())); } if ("GAMMA".equals(discriminator)) { - return new VeryLongDiscriminatedUnionTypeNameForTesting20260630.VariantGamma(gson.fromJson(object - , new TypeToken() {}.getType())); - + return new VeryLongDiscriminatedUnionTypeNameForTesting20260630.VariantGamma(gson.fromJson(object, new TypeToken() {}.getType())); } throw new JsonParseException("unknown VeryLongDiscriminatedUnionTypeNameForTesting20260630 discriminator"); - } @Override @@ -68,21 +61,18 @@ final class VeryLongDiscriminatedUnionTypeNameForTesting20260630JsonAdapter exte JsonObject object = gson.toJsonTree(((VeryLongDiscriminatedUnionTypeNameForTesting20260630.VariantAlpha) value).value0()).getAsJsonObject(); object.addProperty("kind", "ALPHA"); gson.toJson(object, writer); - return; } if (value instanceof VeryLongDiscriminatedUnionTypeNameForTesting20260630.VariantBeta) { JsonObject object = gson.toJsonTree(((VeryLongDiscriminatedUnionTypeNameForTesting20260630.VariantBeta) value).value0()).getAsJsonObject(); object.addProperty("kind", "BETA"); gson.toJson(object, writer); - return; } if (value instanceof VeryLongDiscriminatedUnionTypeNameForTesting20260630.VariantGamma) { JsonObject object = gson.toJsonTree(((VeryLongDiscriminatedUnionTypeNameForTesting20260630.VariantGamma) value).value0()).getAsJsonObject(); object.addProperty("kind", "GAMMA"); gson.toJson(object, writer); - return; } } diff --git a/tests/golden/java/java-okhttp/type-aliases-discriminated-union-mixed-unit-and-allof/models/Setup.java.golden b/tests/golden/java/java-okhttp/type-aliases-discriminated-union-mixed-unit-and-allof/models/Setup.java.golden index bdee5dc99..f563c4559 100644 --- a/tests/golden/java/java-okhttp/type-aliases-discriminated-union-mixed-unit-and-allof/models/Setup.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-discriminated-union-mixed-unit-and-allof/models/Setup.java.golden @@ -46,20 +46,14 @@ final class SetupJsonAdapter extends TypeAdapter { String discriminator = object.has("kind") ? object.get("kind").getAsString() : null; if ("SETUP_UNSPECIFIED".equals(discriminator)) { return Setup.VariantSetupUnspecified.INSTANCE; - } if ("SETUP_QUICK".equals(discriminator)) { - return new Setup.VariantSetupQuick(gson.fromJson(object - , new TypeToken() {}.getType())); - + return new Setup.VariantSetupQuick(gson.fromJson(object, new TypeToken() {}.getType())); } if ("SETUP_CUSTOM".equals(discriminator)) { - return new Setup.VariantSetupCustom(gson.fromJson(object - , new TypeToken() {}.getType())); - + return new Setup.VariantSetupCustom(gson.fromJson(object, new TypeToken() {}.getType())); } throw new JsonParseException("unknown Setup discriminator"); - } @Override @@ -72,21 +66,18 @@ final class SetupJsonAdapter extends TypeAdapter { JsonObject object = new JsonObject(); object.addProperty("kind", "SETUP_UNSPECIFIED"); gson.toJson(object, writer); - return; } if (value instanceof Setup.VariantSetupQuick) { JsonObject object = gson.toJsonTree(((Setup.VariantSetupQuick) value).value0()).getAsJsonObject(); object.addProperty("kind", "SETUP_QUICK"); gson.toJson(object, writer); - return; } if (value instanceof Setup.VariantSetupCustom) { JsonObject object = gson.toJsonTree(((Setup.VariantSetupCustom) value).value0()).getAsJsonObject(); object.addProperty("kind", "SETUP_CUSTOM"); gson.toJson(object, writer); - return; } } diff --git a/tests/golden/java/java-okhttp/type-aliases-discriminated-union-multiple/models/ContainerKind.java.golden b/tests/golden/java/java-okhttp/type-aliases-discriminated-union-multiple/models/ContainerKind.java.golden index 4c921107b..45d498187 100644 --- a/tests/golden/java/java-okhttp/type-aliases-discriminated-union-multiple/models/ContainerKind.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-discriminated-union-multiple/models/ContainerKind.java.golden @@ -39,17 +39,12 @@ final class ContainerKindJsonAdapter extends TypeAdapter { JsonObject object = element.getAsJsonObject(); String discriminator = object.has("kind") ? object.get("kind").getAsString() : null; if ("CONTAINER_KIND_MANAGED".equals(discriminator)) { - return new ContainerKind.VariantContainerKindManaged(gson.fromJson(object - , new TypeToken() {}.getType())); - + return new ContainerKind.VariantContainerKindManaged(gson.fromJson(object, new TypeToken() {}.getType())); } if ("CONTAINER_KIND_CUSTOM".equals(discriminator)) { - return new ContainerKind.VariantContainerKindCustom(gson.fromJson(object - , new TypeToken() {}.getType())); - + return new ContainerKind.VariantContainerKindCustom(gson.fromJson(object, new TypeToken() {}.getType())); } throw new JsonParseException("unknown ContainerKind discriminator"); - } @Override @@ -62,14 +57,12 @@ final class ContainerKindJsonAdapter extends TypeAdapter { JsonObject object = gson.toJsonTree(((ContainerKind.VariantContainerKindManaged) value).value0()).getAsJsonObject(); object.addProperty("kind", "CONTAINER_KIND_MANAGED"); gson.toJson(object, writer); - return; } if (value instanceof ContainerKind.VariantContainerKindCustom) { JsonObject object = gson.toJsonTree(((ContainerKind.VariantContainerKindCustom) value).value0()).getAsJsonObject(); object.addProperty("kind", "CONTAINER_KIND_CUSTOM"); gson.toJson(object, writer); - return; } } diff --git a/tests/golden/java/java-okhttp/type-aliases-discriminated-union-multiple/models/VolumeKind.java.golden b/tests/golden/java/java-okhttp/type-aliases-discriminated-union-multiple/models/VolumeKind.java.golden index fcb3a9e54..5070eb2dc 100644 --- a/tests/golden/java/java-okhttp/type-aliases-discriminated-union-multiple/models/VolumeKind.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-discriminated-union-multiple/models/VolumeKind.java.golden @@ -39,17 +39,12 @@ final class VolumeKindJsonAdapter extends TypeAdapter { JsonObject object = element.getAsJsonObject(); String discriminator = object.has("kind") ? object.get("kind").getAsString() : null; if ("VOLUME_KIND_LOCAL".equals(discriminator)) { - return new VolumeKind.VariantVolumeKindLocal(gson.fromJson(object - , new TypeToken() {}.getType())); - + return new VolumeKind.VariantVolumeKindLocal(gson.fromJson(object, new TypeToken() {}.getType())); } if ("VOLUME_KIND_REMOTE".equals(discriminator)) { - return new VolumeKind.VariantVolumeKindRemote(gson.fromJson(object - , new TypeToken() {}.getType())); - + return new VolumeKind.VariantVolumeKindRemote(gson.fromJson(object, new TypeToken() {}.getType())); } throw new JsonParseException("unknown VolumeKind discriminator"); - } @Override @@ -62,14 +57,12 @@ final class VolumeKindJsonAdapter extends TypeAdapter { JsonObject object = gson.toJsonTree(((VolumeKind.VariantVolumeKindLocal) value).value0()).getAsJsonObject(); object.addProperty("kind", "VOLUME_KIND_LOCAL"); gson.toJson(object, writer); - return; } if (value instanceof VolumeKind.VariantVolumeKindRemote) { JsonObject object = gson.toJsonTree(((VolumeKind.VariantVolumeKindRemote) value).value0()).getAsJsonObject(); object.addProperty("kind", "VOLUME_KIND_REMOTE"); gson.toJson(object, writer); - return; } } diff --git a/tests/golden/java/java-okhttp/type-aliases-discriminated-union-with-refs/models/ContainerImage.java.golden b/tests/golden/java/java-okhttp/type-aliases-discriminated-union-with-refs/models/ContainerImage.java.golden index 2fe2c737e..1258e53af 100644 --- a/tests/golden/java/java-okhttp/type-aliases-discriminated-union-with-refs/models/ContainerImage.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-discriminated-union-with-refs/models/ContainerImage.java.golden @@ -37,17 +37,12 @@ final class ContainerImageJsonAdapter extends TypeAdapter { JsonObject object = element.getAsJsonObject(); String discriminator = object.has("kind") ? object.get("kind").getAsString() : null; if ("CONTAINER_IMAGE_MANAGED".equals(discriminator)) { - return new ContainerImage.VariantContainerImageManaged(gson.fromJson(object - , new TypeToken() {}.getType())); - + return new ContainerImage.VariantContainerImageManaged(gson.fromJson(object, new TypeToken() {}.getType())); } if ("CONTAINER_IMAGE_CUSTOM".equals(discriminator)) { - return new ContainerImage.VariantContainerImageCustom(gson.fromJson(object - , new TypeToken() {}.getType())); - + return new ContainerImage.VariantContainerImageCustom(gson.fromJson(object, new TypeToken() {}.getType())); } throw new JsonParseException("unknown ContainerImage discriminator"); - } @Override @@ -60,14 +55,12 @@ final class ContainerImageJsonAdapter extends TypeAdapter { JsonObject object = gson.toJsonTree(((ContainerImage.VariantContainerImageManaged) value).value0()).getAsJsonObject(); object.addProperty("kind", "CONTAINER_IMAGE_MANAGED"); gson.toJson(object, writer); - return; } if (value instanceof ContainerImage.VariantContainerImageCustom) { JsonObject object = gson.toJsonTree(((ContainerImage.VariantContainerImageCustom) value).value0()).getAsJsonObject(); object.addProperty("kind", "CONTAINER_IMAGE_CUSTOM"); gson.toJson(object, writer); - return; } } diff --git a/tests/golden/java/java-okhttp/type-aliases-nested-union/models/Foo.java.golden b/tests/golden/java/java-okhttp/type-aliases-nested-union/models/Foo.java.golden index 30cc91972..e5c8c4e2b 100644 --- a/tests/golden/java/java-okhttp/type-aliases-nested-union/models/Foo.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-nested-union/models/Foo.java.golden @@ -34,39 +34,17 @@ final class FooJsonAdapter extends TypeAdapter { JsonElement element = JsonParser.parseReader(reader); if (element.isJsonNull()) { return null; - } - if ((((element.isJsonObject() - && element.getAsJsonObject().has("endpoint") - ) - && element.getAsJsonObject().has("type") - ) - || ((element.isJsonObject() - && element.getAsJsonObject().has("path") - ) - && element.getAsJsonObject().has("type") - ) - ) - ) { - return new Foo.VariantQux(gson.fromJson(element - , new TypeToken() {}.getType())); - + if ((((element.isJsonObject() && element.getAsJsonObject().has("endpoint")) && element.getAsJsonObject().has("type")) || ((element.isJsonObject() && element.getAsJsonObject().has("path")) && element.getAsJsonObject().has("type")))) { + return new Foo.VariantQux(gson.fromJson(element, new TypeToken() {}.getType())); } - if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString() - ) { - return new Foo.VariantString(element - .getAsString()); - + if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString()) { + return new Foo.VariantString(element.getAsString()); } - if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isNumber() - ) { - return new Foo.VariantNumber(gson.fromJson(element - , Double.class)); - + if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isNumber()) { + return new Foo.VariantNumber(gson.fromJson(element, Double.class)); } throw new JsonParseException("unknown Foo union value"); - - } @Override @@ -77,17 +55,14 @@ final class FooJsonAdapter extends TypeAdapter { } if (value instanceof Foo.VariantQux) { gson.toJson(gson.toJsonTree(((Foo.VariantQux) value).value0()), writer); - return; } if (value instanceof Foo.VariantString) { gson.toJson(gson.toJsonTree(((Foo.VariantString) value).value0()), writer); - return; } if (value instanceof Foo.VariantNumber) { gson.toJson(gson.toJsonTree(((Foo.VariantNumber) value).value0()), writer); - return; } } diff --git a/tests/golden/java/java-okhttp/type-aliases-nested-union/models/Qux.java.golden b/tests/golden/java/java-okhttp/type-aliases-nested-union/models/Qux.java.golden index 18fa76366..c9f9be92d 100644 --- a/tests/golden/java/java-okhttp/type-aliases-nested-union/models/Qux.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-nested-union/models/Qux.java.golden @@ -32,31 +32,14 @@ final class QuxJsonAdapter extends TypeAdapter { JsonElement element = JsonParser.parseReader(reader); if (element.isJsonNull()) { return null; - } - if (((element.isJsonObject() - && element.getAsJsonObject().has("endpoint") - ) - && element.getAsJsonObject().has("type") - ) - ) { - return new Qux.VariantBar(gson.fromJson(element - , new TypeToken() {}.getType())); - + if (((element.isJsonObject() && element.getAsJsonObject().has("endpoint")) && element.getAsJsonObject().has("type"))) { + return new Qux.VariantBar(gson.fromJson(element, new TypeToken() {}.getType())); } - if (((element.isJsonObject() - && element.getAsJsonObject().has("path") - ) - && element.getAsJsonObject().has("type") - ) - ) { - return new Qux.VariantBaz(gson.fromJson(element - , new TypeToken() {}.getType())); - + if (((element.isJsonObject() && element.getAsJsonObject().has("path")) && element.getAsJsonObject().has("type"))) { + return new Qux.VariantBaz(gson.fromJson(element, new TypeToken() {}.getType())); } throw new JsonParseException("unknown Qux union value"); - - } @Override @@ -67,12 +50,10 @@ final class QuxJsonAdapter extends TypeAdapter { } if (value instanceof Qux.VariantBar) { gson.toJson(gson.toJsonTree(((Qux.VariantBar) value).value0()), writer); - return; } if (value instanceof Qux.VariantBaz) { gson.toJson(gson.toJsonTree(((Qux.VariantBaz) value).value0()), writer); - return; } } diff --git a/tests/golden/java/java-okhttp/type-aliases-union-mixed/models/Foo.java.golden b/tests/golden/java/java-okhttp/type-aliases-union-mixed/models/Foo.java.golden index f11e722bc..7c1c2ae15 100644 --- a/tests/golden/java/java-okhttp/type-aliases-union-mixed/models/Foo.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-union-mixed/models/Foo.java.golden @@ -34,33 +34,17 @@ final class FooJsonAdapter extends TypeAdapter { JsonElement element = JsonParser.parseReader(reader); if (element.isJsonNull()) { return null; - } - if (((element.isJsonObject() - && element.getAsJsonObject().has("id") - ) - && element.getAsJsonObject().has("name") - ) - ) { - return new Foo.VariantBar(gson.fromJson(element - , new TypeToken() {}.getType())); - + if (((element.isJsonObject() && element.getAsJsonObject().has("id")) && element.getAsJsonObject().has("name"))) { + return new Foo.VariantBar(gson.fromJson(element, new TypeToken() {}.getType())); } - if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString() - ) { - return new Foo.VariantString(element - .getAsString()); - + if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString()) { + return new Foo.VariantString(element.getAsString()); } - if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isNumber() - ) { - return new Foo.VariantNumber(gson.fromJson(element - , Double.class)); - + if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isNumber()) { + return new Foo.VariantNumber(gson.fromJson(element, Double.class)); } throw new JsonParseException("unknown Foo union value"); - - } @Override @@ -71,17 +55,14 @@ final class FooJsonAdapter extends TypeAdapter { } if (value instanceof Foo.VariantBar) { gson.toJson(gson.toJsonTree(((Foo.VariantBar) value).value0()), writer); - return; } if (value instanceof Foo.VariantString) { gson.toJson(gson.toJsonTree(((Foo.VariantString) value).value0()), writer); - return; } if (value instanceof Foo.VariantNumber) { gson.toJson(gson.toJsonTree(((Foo.VariantNumber) value).value0()), writer); - return; } } diff --git a/tests/golden/java/java-okhttp/type-aliases-union-with-any/models/Config.java.golden b/tests/golden/java/java-okhttp/type-aliases-union-with-any/models/Config.java.golden index 9c6fa72f3..cd387381f 100644 --- a/tests/golden/java/java-okhttp/type-aliases-union-with-any/models/Config.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-union-with-any/models/Config.java.golden @@ -34,24 +34,14 @@ final class ConfigJsonAdapter extends TypeAdapter { JsonElement element = JsonParser.parseReader(reader); if (element.isJsonNull()) { return null; - } - if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString() - ) { - return new Config.VariantString(element - .getAsString()); - + if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString()) { + return new Config.VariantString(element.getAsString()); } - if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isNumber() - ) { - return new Config.VariantNumber(gson.fromJson(element - , Double.class)); - + if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isNumber()) { + return new Config.VariantNumber(gson.fromJson(element, Double.class)); } - return new Config.VariantAny(gson.fromJson(element - , new TypeToken() {}.getType())); - - + return new Config.VariantAny(gson.fromJson(element, new TypeToken() {}.getType())); } @Override @@ -62,17 +52,14 @@ final class ConfigJsonAdapter extends TypeAdapter { } if (value instanceof Config.VariantString) { gson.toJson(gson.toJsonTree(((Config.VariantString) value).value0()), writer); - return; } if (value instanceof Config.VariantNumber) { gson.toJson(gson.toJsonTree(((Config.VariantNumber) value).value0()), writer); - return; } if (value instanceof Config.VariantAny) { gson.toJson(gson.toJsonTree(((Config.VariantAny) value).value0()), writer); - return; } } diff --git a/tests/golden/java/java-okhttp/type-aliases-union-with-inline-objects/models/Foo.java.golden b/tests/golden/java/java-okhttp/type-aliases-union-with-inline-objects/models/Foo.java.golden index 2f77a23b2..c1a74dd6a 100644 --- a/tests/golden/java/java-okhttp/type-aliases-union-with-inline-objects/models/Foo.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-union-with-inline-objects/models/Foo.java.golden @@ -37,35 +37,17 @@ final class FooJsonAdapter extends TypeAdapter { JsonElement element = JsonParser.parseReader(reader); if (element.isJsonNull()) { return null; - } - if ((element.isJsonObject() - && element.getAsJsonObject().has("bar") - ) - ) { - return new Foo.VariantFooMember1(gson.fromJson(element - , new TypeToken() {}.getType())); - + if ((element.isJsonObject() && element.getAsJsonObject().has("bar"))) { + return new Foo.VariantFooMember1(gson.fromJson(element, new TypeToken() {}.getType())); } - if ((element.isJsonObject() - && element.getAsJsonObject().has("quux") - ) - ) { - return new Foo.VariantFooMember2(gson.fromJson(element - , new TypeToken() {}.getType())); - + if ((element.isJsonObject() && element.getAsJsonObject().has("quux"))) { + return new Foo.VariantFooMember2(gson.fromJson(element, new TypeToken() {}.getType())); } - if ((element.isJsonObject() - && element.getAsJsonObject().has("grault") - ) - ) { - return new Foo.VariantFooMember3(gson.fromJson(element - , new TypeToken() {}.getType())); - + if ((element.isJsonObject() && element.getAsJsonObject().has("grault"))) { + return new Foo.VariantFooMember3(gson.fromJson(element, new TypeToken() {}.getType())); } throw new JsonParseException("unknown Foo union value"); - - } @Override @@ -76,17 +58,14 @@ final class FooJsonAdapter extends TypeAdapter { } if (value instanceof Foo.VariantFooMember1) { gson.toJson(gson.toJsonTree(((Foo.VariantFooMember1) value).value0()), writer); - return; } if (value instanceof Foo.VariantFooMember2) { gson.toJson(gson.toJsonTree(((Foo.VariantFooMember2) value).value0()), writer); - return; } if (value instanceof Foo.VariantFooMember3) { gson.toJson(gson.toJsonTree(((Foo.VariantFooMember3) value).value0()), writer); - return; } } diff --git a/tests/golden/java/java-okhttp/type-aliases-union-with-interfaces/models/Foo.java.golden b/tests/golden/java/java-okhttp/type-aliases-union-with-interfaces/models/Foo.java.golden index bffcece90..ea718e521 100644 --- a/tests/golden/java/java-okhttp/type-aliases-union-with-interfaces/models/Foo.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-union-with-interfaces/models/Foo.java.golden @@ -34,45 +34,17 @@ final class FooJsonAdapter extends TypeAdapter { JsonElement element = JsonParser.parseReader(reader); if (element.isJsonNull()) { return null; - } - if ((((element.isJsonObject() - && element.getAsJsonObject().has("bucket") - ) - && element.getAsJsonObject().has("endpoint") - ) - && element.getAsJsonObject().has("type") - ) - ) { - return new Foo.VariantBar(gson.fromJson(element - , new TypeToken() {}.getType())); - + if ((((element.isJsonObject() && element.getAsJsonObject().has("bucket")) && element.getAsJsonObject().has("endpoint")) && element.getAsJsonObject().has("type"))) { + return new Foo.VariantBar(gson.fromJson(element, new TypeToken() {}.getType())); } - if (((element.isJsonObject() - && element.getAsJsonObject().has("path") - ) - && element.getAsJsonObject().has("type") - ) - ) { - return new Foo.VariantBaz(gson.fromJson(element - , new TypeToken() {}.getType())); - + if (((element.isJsonObject() && element.getAsJsonObject().has("path")) && element.getAsJsonObject().has("type"))) { + return new Foo.VariantBaz(gson.fromJson(element, new TypeToken() {}.getType())); } - if ((((element.isJsonObject() - && element.getAsJsonObject().has("namespace") - ) - && element.getAsJsonObject().has("type") - ) - && element.getAsJsonObject().has("url") - ) - ) { - return new Foo.VariantQux(gson.fromJson(element - , new TypeToken() {}.getType())); - + if ((((element.isJsonObject() && element.getAsJsonObject().has("namespace")) && element.getAsJsonObject().has("type")) && element.getAsJsonObject().has("url"))) { + return new Foo.VariantQux(gson.fromJson(element, new TypeToken() {}.getType())); } throw new JsonParseException("unknown Foo union value"); - - } @Override @@ -83,17 +55,14 @@ final class FooJsonAdapter extends TypeAdapter { } if (value instanceof Foo.VariantBar) { gson.toJson(gson.toJsonTree(((Foo.VariantBar) value).value0()), writer); - return; } if (value instanceof Foo.VariantBaz) { gson.toJson(gson.toJsonTree(((Foo.VariantBaz) value).value0()), writer); - return; } if (value instanceof Foo.VariantQux) { gson.toJson(gson.toJsonTree(((Foo.VariantQux) value).value0()), writer); - return; } } diff --git a/tests/golden/java/java-okhttp/type-aliases-union-with-primitives/models/Foo.java.golden b/tests/golden/java/java-okhttp/type-aliases-union-with-primitives/models/Foo.java.golden index 44138eb61..32c0ace65 100644 --- a/tests/golden/java/java-okhttp/type-aliases-union-with-primitives/models/Foo.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-union-with-primitives/models/Foo.java.golden @@ -39,29 +39,17 @@ final class FooJsonAdapter extends TypeAdapter { JsonElement element = JsonParser.parseReader(reader); if (element.isJsonNull()) { return Foo.VariantNull.INSTANCE; - } - if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString() - ) { - return new Foo.VariantString(element - .getAsString()); - + if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString()) { + return new Foo.VariantString(element.getAsString()); } - if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isNumber() - ) { - return new Foo.VariantNumber(gson.fromJson(element - , Double.class)); - + if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isNumber()) { + return new Foo.VariantNumber(gson.fromJson(element, Double.class)); } - if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isBoolean() - ) { - return new Foo.VariantBoolean(element - .getAsBoolean()); - + if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isBoolean()) { + return new Foo.VariantBoolean(element.getAsBoolean()); } throw new JsonParseException("unknown Foo union value"); - - } @Override @@ -72,22 +60,18 @@ final class FooJsonAdapter extends TypeAdapter { } if (value instanceof Foo.VariantString) { gson.toJson(gson.toJsonTree(((Foo.VariantString) value).value0()), writer); - return; } if (value instanceof Foo.VariantNumber) { gson.toJson(gson.toJsonTree(((Foo.VariantNumber) value).value0()), writer); - return; } if (value instanceof Foo.VariantBoolean) { gson.toJson(gson.toJsonTree(((Foo.VariantBoolean) value).value0()), writer); - return; } if (value instanceof Foo.VariantNull) { writer.nullValue(); - return; } } diff --git a/tests/golden/kotlin/kotlin-okhttp/comprehensive-schemas/models/AnyOfTypes.kt.golden b/tests/golden/kotlin/kotlin-okhttp/comprehensive-schemas/models/AnyOfTypes.kt.golden index 1940bb4bd..dca34862f 100644 --- a/tests/golden/kotlin/kotlin-okhttp/comprehensive-schemas/models/AnyOfTypes.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/comprehensive-schemas/models/AnyOfTypes.kt.golden @@ -35,19 +35,10 @@ private class AnyOfTypesJsonAdapter: TypeAdapter() { val element = JsonParser.parseReader(reader) return when { element.isJsonNull -> null - - element.isJsonPrimitive && element.asJsonPrimitive.isString - -> AnyOfTypes.VariantString(element - .asString) - - element.isJsonPrimitive && element.asJsonPrimitive.isNumber - -> AnyOfTypes.VariantNumber(element - .asDouble) - + element.isJsonPrimitive && element.asJsonPrimitive.isString -> AnyOfTypes.VariantString(element.asString) + element.isJsonPrimitive && element.asJsonPrimitive.isNumber -> AnyOfTypes.VariantNumber(element.asDouble) else -> throw JsonParseException("unknown AnyOfTypes union value") - } - } override fun write(writer: JsonWriter, value: AnyOfTypes?) { @@ -58,11 +49,9 @@ private class AnyOfTypesJsonAdapter: TypeAdapter() { when (value) { is AnyOfTypes.VariantString -> { gson.toJson(gson.toJsonTree(value.value0), writer) - } is AnyOfTypes.VariantNumber -> { gson.toJson(gson.toJsonTree(value.value0), writer) - } } } diff --git a/tests/golden/kotlin/kotlin-okhttp/comprehensive-schemas/models/OneOfObjects.kt.golden b/tests/golden/kotlin/kotlin-okhttp/comprehensive-schemas/models/OneOfObjects.kt.golden index 61c33d700..ea049f53c 100644 --- a/tests/golden/kotlin/kotlin-okhttp/comprehensive-schemas/models/OneOfObjects.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/comprehensive-schemas/models/OneOfObjects.kt.golden @@ -35,23 +35,10 @@ private class OneOfObjectsJsonAdapter: TypeAdapter() { val element = JsonParser.parseReader(reader) return when { element.isJsonNull -> null - - ((element.isJsonObject - && element.asJsonObject.has("id") - ) - && element.asJsonObject.has("name") - ) - -> OneOfObjects.VariantBasicObject(gson.fromJson(element - , object: TypeToken() {}.type)) - - element.isJsonObject - -> OneOfObjects.VariantNestedObject(gson.fromJson(element - , object: TypeToken() {}.type)) - + ((element.isJsonObject && element.asJsonObject.has("id")) && element.asJsonObject.has("name")) -> OneOfObjects.VariantBasicObject(gson.fromJson(element, object: TypeToken() {}.type)) + element.isJsonObject -> OneOfObjects.VariantNestedObject(gson.fromJson(element, object: TypeToken() {}.type)) else -> throw JsonParseException("unknown OneOfObjects union value") - } - } override fun write(writer: JsonWriter, value: OneOfObjects?) { @@ -62,11 +49,9 @@ private class OneOfObjectsJsonAdapter: TypeAdapter() { when (value) { is OneOfObjects.VariantBasicObject -> { gson.toJson(gson.toJsonTree(value.value0), writer) - } is OneOfObjects.VariantNestedObject -> { gson.toJson(gson.toJsonTree(value.value0), writer) - } } } diff --git a/tests/golden/kotlin/kotlin-okhttp/comprehensive-schemas/models/OneOfTypes.kt.golden b/tests/golden/kotlin/kotlin-okhttp/comprehensive-schemas/models/OneOfTypes.kt.golden index f842ba8aa..51ffe40c0 100644 --- a/tests/golden/kotlin/kotlin-okhttp/comprehensive-schemas/models/OneOfTypes.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/comprehensive-schemas/models/OneOfTypes.kt.golden @@ -37,23 +37,11 @@ private class OneOfTypesJsonAdapter: TypeAdapter() { val element = JsonParser.parseReader(reader) return when { element.isJsonNull -> null - - element.isJsonPrimitive && element.asJsonPrimitive.isString - -> OneOfTypes.VariantString(element - .asString) - - element.isJsonPrimitive && element.asJsonPrimitive.isNumber - -> OneOfTypes.VariantNumber(element - .asDouble) - - element.isJsonPrimitive && element.asJsonPrimitive.isBoolean - -> OneOfTypes.VariantBoolean(element - .asBoolean) - + element.isJsonPrimitive && element.asJsonPrimitive.isString -> OneOfTypes.VariantString(element.asString) + element.isJsonPrimitive && element.asJsonPrimitive.isNumber -> OneOfTypes.VariantNumber(element.asDouble) + element.isJsonPrimitive && element.asJsonPrimitive.isBoolean -> OneOfTypes.VariantBoolean(element.asBoolean) else -> throw JsonParseException("unknown OneOfTypes union value") - } - } override fun write(writer: JsonWriter, value: OneOfTypes?) { @@ -64,15 +52,12 @@ private class OneOfTypesJsonAdapter: TypeAdapter() { when (value) { is OneOfTypes.VariantString -> { gson.toJson(gson.toJsonTree(value.value0), writer) - } is OneOfTypes.VariantNumber -> { gson.toJson(gson.toJsonTree(value.value0), writer) - } is OneOfTypes.VariantBoolean -> { gson.toJson(gson.toJsonTree(value.value0), writer) - } } } diff --git a/tests/golden/kotlin/kotlin-okhttp/comprehensive-schemas/models/OneOfWithNull.kt.golden b/tests/golden/kotlin/kotlin-okhttp/comprehensive-schemas/models/OneOfWithNull.kt.golden index 64280a579..d04150e45 100644 --- a/tests/golden/kotlin/kotlin-okhttp/comprehensive-schemas/models/OneOfWithNull.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/comprehensive-schemas/models/OneOfWithNull.kt.golden @@ -40,23 +40,10 @@ private class OneOfWithNullJsonAdapter: TypeAdapter() { val element = JsonParser.parseReader(reader) return when { element.isJsonNull -> OneOfWithNull.VariantNull - - element.isJsonPrimitive && element.asJsonPrimitive.isString - -> OneOfWithNull.VariantString(element - .asString) - - ((element.isJsonObject - && element.asJsonObject.has("id") - ) - && element.asJsonObject.has("name") - ) - -> OneOfWithNull.VariantBasicObject(gson.fromJson(element - , object: TypeToken() {}.type)) - + element.isJsonPrimitive && element.asJsonPrimitive.isString -> OneOfWithNull.VariantString(element.asString) + ((element.isJsonObject && element.asJsonObject.has("id")) && element.asJsonObject.has("name")) -> OneOfWithNull.VariantBasicObject(gson.fromJson(element, object: TypeToken() {}.type)) else -> throw JsonParseException("unknown OneOfWithNull union value") - } - } override fun write(writer: JsonWriter, value: OneOfWithNull?) { @@ -67,15 +54,12 @@ private class OneOfWithNullJsonAdapter: TypeAdapter() { when (value) { is OneOfWithNull.VariantString -> { gson.toJson(gson.toJsonTree(value.value0), writer) - } is OneOfWithNull.VariantBasicObject -> { gson.toJson(gson.toJsonTree(value.value0), writer) - } is OneOfWithNull.VariantNull -> { writer.nullValue() - } } } diff --git a/tests/golden/kotlin/kotlin-okhttp/enum-repr/models/AdjacentlyTaggedEnum.kt.golden b/tests/golden/kotlin/kotlin-okhttp/enum-repr/models/AdjacentlyTaggedEnum.kt.golden index c4645cfac..527bd77b2 100644 --- a/tests/golden/kotlin/kotlin-okhttp/enum-repr/models/AdjacentlyTaggedEnum.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/enum-repr/models/AdjacentlyTaggedEnum.kt.golden @@ -46,15 +46,10 @@ private class AdjacentlyTaggedEnumJsonAdapter: TypeAdapter val element = JsonParser.parseReader(reader) val jsonObject = element.asJsonObject return when (jsonObject["ty"]?.asString) { - "ADJACENTLY_TAGGED_ENUM_VARIANT_A" -> AdjacentlyTaggedEnum.VariantAdjacentlyTaggedEnumVariantA(gson.fromJson(jsonObject["data"] - , object: TypeToken() {}.type)) - - "ADJACENTLY_TAGGED_ENUM_VARIANT_B" -> AdjacentlyTaggedEnum.VariantAdjacentlyTaggedEnumVariantB(gson.fromJson(jsonObject["data"] - , object: TypeToken() {}.type)) - + "ADJACENTLY_TAGGED_ENUM_VARIANT_A" -> AdjacentlyTaggedEnum.VariantAdjacentlyTaggedEnumVariantA(gson.fromJson(jsonObject["data"], object: TypeToken() {}.type)) + "ADJACENTLY_TAGGED_ENUM_VARIANT_B" -> AdjacentlyTaggedEnum.VariantAdjacentlyTaggedEnumVariantB(gson.fromJson(jsonObject["data"], object: TypeToken() {}.type)) else -> throw JsonParseException("unknown AdjacentlyTaggedEnum discriminator") } - } override fun write(writer: JsonWriter, value: AdjacentlyTaggedEnum?) { @@ -68,14 +63,12 @@ private class AdjacentlyTaggedEnumJsonAdapter: TypeAdapter jsonObject.addProperty("ty", "ADJACENTLY_TAGGED_ENUM_VARIANT_A") jsonObject.add("data", gson.toJsonTree(value.value0)) gson.toJson(jsonObject, writer) - } is AdjacentlyTaggedEnum.VariantAdjacentlyTaggedEnumVariantB -> { val jsonObject = JsonObject() jsonObject.addProperty("ty", "ADJACENTLY_TAGGED_ENUM_VARIANT_B") jsonObject.add("data", gson.toJsonTree(value.value0)) gson.toJson(jsonObject, writer) - } } } diff --git a/tests/golden/kotlin/kotlin-okhttp/enum-repr/models/ExternallyTaggedEnum.kt.golden b/tests/golden/kotlin/kotlin-okhttp/enum-repr/models/ExternallyTaggedEnum.kt.golden index 2116deac9..dca320608 100644 --- a/tests/golden/kotlin/kotlin-okhttp/enum-repr/models/ExternallyTaggedEnum.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/enum-repr/models/ExternallyTaggedEnum.kt.golden @@ -46,15 +46,10 @@ private class ExternallyTaggedEnumJsonAdapter: TypeAdapter val element = JsonParser.parseReader(reader) val jsonObject = element.asJsonObject return when { - jsonObject.has("EXTERNALLY_TAGGED_ENUM_VARIANT_A") -> ExternallyTaggedEnum.VariantExternallyTaggedEnumVariantA(gson.fromJson(jsonObject["EXTERNALLY_TAGGED_ENUM_VARIANT_A"] - , object: TypeToken() {}.type)) - - jsonObject.has("EXTERNALLY_TAGGED_ENUM_VARIANT_B") -> ExternallyTaggedEnum.VariantExternallyTaggedEnumVariantB(gson.fromJson(jsonObject["EXTERNALLY_TAGGED_ENUM_VARIANT_B"] - , object: TypeToken() {}.type)) - + jsonObject.has("EXTERNALLY_TAGGED_ENUM_VARIANT_A") -> ExternallyTaggedEnum.VariantExternallyTaggedEnumVariantA(gson.fromJson(jsonObject["EXTERNALLY_TAGGED_ENUM_VARIANT_A"], object: TypeToken() {}.type)) + jsonObject.has("EXTERNALLY_TAGGED_ENUM_VARIANT_B") -> ExternallyTaggedEnum.VariantExternallyTaggedEnumVariantB(gson.fromJson(jsonObject["EXTERNALLY_TAGGED_ENUM_VARIANT_B"], object: TypeToken() {}.type)) else -> throw JsonParseException("unknown ExternallyTaggedEnum variant") } - } override fun write(writer: JsonWriter, value: ExternallyTaggedEnum?) { @@ -67,13 +62,11 @@ private class ExternallyTaggedEnumJsonAdapter: TypeAdapter val jsonObject = JsonObject() jsonObject.add("EXTERNALLY_TAGGED_ENUM_VARIANT_A", gson.toJsonTree(value.value0)) gson.toJson(jsonObject, writer) - } is ExternallyTaggedEnum.VariantExternallyTaggedEnumVariantB -> { val jsonObject = JsonObject() jsonObject.add("EXTERNALLY_TAGGED_ENUM_VARIANT_B", gson.toJsonTree(value.value0)) gson.toJson(jsonObject, writer) - } } } diff --git a/tests/golden/kotlin/kotlin-okhttp/enum-repr/models/InternallyTaggedEnum.kt.golden b/tests/golden/kotlin/kotlin-okhttp/enum-repr/models/InternallyTaggedEnum.kt.golden index ebbf007e9..d29cbd538 100644 --- a/tests/golden/kotlin/kotlin-okhttp/enum-repr/models/InternallyTaggedEnum.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/enum-repr/models/InternallyTaggedEnum.kt.golden @@ -46,15 +46,10 @@ private class InternallyTaggedEnumJsonAdapter: TypeAdapter val element = JsonParser.parseReader(reader) val jsonObject = element.asJsonObject return when (jsonObject["ty"]?.asString) { - "INTERNALLY_TAGGED_ENUM_VARIANT_A" -> InternallyTaggedEnum.VariantInternallyTaggedEnumVariantA(gson.fromJson(jsonObject - , object: TypeToken() {}.type)) - - "INTERNALLY_TAGGED_ENUM_VARIANT_B" -> InternallyTaggedEnum.VariantInternallyTaggedEnumVariantB(gson.fromJson(jsonObject - , object: TypeToken() {}.type)) - + "INTERNALLY_TAGGED_ENUM_VARIANT_A" -> InternallyTaggedEnum.VariantInternallyTaggedEnumVariantA(gson.fromJson(jsonObject, object: TypeToken() {}.type)) + "INTERNALLY_TAGGED_ENUM_VARIANT_B" -> InternallyTaggedEnum.VariantInternallyTaggedEnumVariantB(gson.fromJson(jsonObject, object: TypeToken() {}.type)) else -> throw JsonParseException("unknown InternallyTaggedEnum discriminator") } - } override fun write(writer: JsonWriter, value: InternallyTaggedEnum?) { @@ -67,13 +62,11 @@ private class InternallyTaggedEnumJsonAdapter: TypeAdapter val jsonObject = gson.toJsonTree(value.value0).asJsonObject jsonObject.addProperty("ty", "INTERNALLY_TAGGED_ENUM_VARIANT_A") gson.toJson(jsonObject, writer) - } is InternallyTaggedEnum.VariantInternallyTaggedEnumVariantB -> { val jsonObject = gson.toJsonTree(value.value0).asJsonObject jsonObject.addProperty("ty", "INTERNALLY_TAGGED_ENUM_VARIANT_B") gson.toJson(jsonObject, writer) - } } } diff --git a/tests/golden/kotlin/kotlin-okhttp/enum-repr/models/MixedEnum.kt.golden b/tests/golden/kotlin/kotlin-okhttp/enum-repr/models/MixedEnum.kt.golden index 14a4efacb..0bb23b763 100644 --- a/tests/golden/kotlin/kotlin-okhttp/enum-repr/models/MixedEnum.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/enum-repr/models/MixedEnum.kt.golden @@ -42,33 +42,12 @@ private class MixedEnumJsonAdapter: TypeAdapter() { val element = JsonParser.parseReader(reader) return when { element.isJsonNull -> null - - element.isJsonPrimitive && element.asJsonPrimitive.isString - && element.asString == "SimpleA" - -> MixedEnum.VariantSimpleA(element - .asString) - - (element.isJsonObject - && element.asJsonObject.has("VariantA") - ) - -> MixedEnum.VariantMixedEnumMember2(gson.fromJson(element - , object: TypeToken() {}.type)) - - element.isJsonPrimitive && element.asJsonPrimitive.isString - && element.asString == "SimpleB" - -> MixedEnum.VariantSimpleB(element - .asString) - - (element.isJsonObject - && element.asJsonObject.has("VariantB") - ) - -> MixedEnum.VariantMixedEnumMember4(gson.fromJson(element - , object: TypeToken() {}.type)) - + element.isJsonPrimitive && element.asJsonPrimitive.isString && element.asString == "SimpleA" -> MixedEnum.VariantSimpleA(element.asString) + (element.isJsonObject && element.asJsonObject.has("VariantA")) -> MixedEnum.VariantMixedEnumMember2(gson.fromJson(element, object: TypeToken() {}.type)) + element.isJsonPrimitive && element.asJsonPrimitive.isString && element.asString == "SimpleB" -> MixedEnum.VariantSimpleB(element.asString) + (element.isJsonObject && element.asJsonObject.has("VariantB")) -> MixedEnum.VariantMixedEnumMember4(gson.fromJson(element, object: TypeToken() {}.type)) else -> throw JsonParseException("unknown MixedEnum union value") - } - } override fun write(writer: JsonWriter, value: MixedEnum?) { @@ -79,19 +58,15 @@ private class MixedEnumJsonAdapter: TypeAdapter() { when (value) { is MixedEnum.VariantSimpleA -> { gson.toJson(gson.toJsonTree(value.value0), writer) - } is MixedEnum.VariantMixedEnumMember2 -> { gson.toJson(gson.toJsonTree(value.value0), writer) - } is MixedEnum.VariantSimpleB -> { gson.toJson(gson.toJsonTree(value.value0), writer) - } is MixedEnum.VariantMixedEnumMember4 -> { gson.toJson(gson.toJsonTree(value.value0), writer) - } } } diff --git a/tests/golden/kotlin/kotlin-okhttp/enum-repr/models/UntaggedEnum.kt.golden b/tests/golden/kotlin/kotlin-okhttp/enum-repr/models/UntaggedEnum.kt.golden index 602596fe8..792b3d0cb 100644 --- a/tests/golden/kotlin/kotlin-okhttp/enum-repr/models/UntaggedEnum.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/enum-repr/models/UntaggedEnum.kt.golden @@ -38,27 +38,10 @@ private class UntaggedEnumJsonAdapter: TypeAdapter() { val element = JsonParser.parseReader(reader) return when { element.isJsonNull -> null - - ((element.isJsonObject - && element.asJsonObject.has("field1") - ) - && element.asJsonObject.has("field2") - ) - -> UntaggedEnum.VariantVariantA(gson.fromJson(element - , object: TypeToken() {}.type)) - - ((element.isJsonObject - && element.asJsonObject.has("field3") - ) - && element.asJsonObject.has("field4") - ) - -> UntaggedEnum.VariantVariantB(gson.fromJson(element - , object: TypeToken() {}.type)) - + ((element.isJsonObject && element.asJsonObject.has("field1")) && element.asJsonObject.has("field2")) -> UntaggedEnum.VariantVariantA(gson.fromJson(element, object: TypeToken() {}.type)) + ((element.isJsonObject && element.asJsonObject.has("field3")) && element.asJsonObject.has("field4")) -> UntaggedEnum.VariantVariantB(gson.fromJson(element, object: TypeToken() {}.type)) else -> throw JsonParseException("unknown UntaggedEnum union value") - } - } override fun write(writer: JsonWriter, value: UntaggedEnum?) { @@ -69,11 +52,9 @@ private class UntaggedEnumJsonAdapter: TypeAdapter() { when (value) { is UntaggedEnum.VariantVariantA -> { gson.toJson(gson.toJsonTree(value.value0), writer) - } is UntaggedEnum.VariantVariantB -> { gson.toJson(gson.toJsonTree(value.value0), writer) - } } } diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-closed-sum-payload-shapes/models/Choice.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-closed-sum-payload-shapes/models/Choice.kt.golden index 56a41b64d..6f979aaaa 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-closed-sum-payload-shapes/models/Choice.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-closed-sum-payload-shapes/models/Choice.kt.golden @@ -39,23 +39,12 @@ private class ChoiceJsonAdapter: TypeAdapter() { val element = JsonParser.parseReader(reader) return when { element.isJsonNull -> Choice.VariantNull - - element.isJsonPrimitive && element.asJsonPrimitive.isString - -> Choice.VariantString(element - .asString) - - element.isJsonArray - -> Choice.VariantArray(gson.fromJson(element - , object: TypeToken>() {}.type)) - - element.isJsonObject - -> Choice.VariantMap(gson.fromJson(element - , object: TypeToken>() {}.type)) - + element.isJsonPrimitive && element.asJsonPrimitive.isString -> Choice.VariantString(element.asString) + element.isJsonArray -> Choice.VariantArray(gson.fromJson(element, object: TypeToken>() {}.type)) + element.isJsonObject -> Choice.VariantMap(gson.fromJson(element, object: TypeToken>() {}.type)) else -> throw JsonParseException("unknown Choice union value") - } - } override fun write(writer: JsonWriter, value: Choice?) { @@ -66,19 +55,15 @@ private class ChoiceJsonAdapter: TypeAdapter() { when (value) { is Choice.VariantString -> { gson.toJson(gson.toJsonTree(value.value0), writer) - } is Choice.VariantArray -> { gson.toJson(gson.toJsonTree(value.value0), writer) - } is Choice.VariantMap -> { gson.toJson(gson.toJsonTree(value.value0), writer) - } is Choice.VariantNull -> { writer.nullValue() - } } } diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-complex-union/models/Foo.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-complex-union/models/Foo.kt.golden index e1a4363c3..cc6bb4366 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-complex-union/models/Foo.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-complex-union/models/Foo.kt.golden @@ -34,39 +34,11 @@ private class FooJsonAdapter: TypeAdapter() { val element = JsonParser.parseReader(reader) return when { element.isJsonNull -> null - - (((element.isJsonObject - && element.asJsonObject.has("bucket") - ) - && element.asJsonObject.has("endpoint") - ) - && element.asJsonObject.has("type") - ) - -> Foo.VariantBar(gson.fromJson(element - , object: TypeToken() {}.type)) - - ((element.isJsonObject - && element.asJsonObject.has("path") - ) - && element.asJsonObject.has("type") - ) - -> Foo.VariantBaz(gson.fromJson(element - , object: TypeToken() {}.type)) - - (((element.isJsonObject - && element.asJsonObject.has("namespace") - ) - && element.asJsonObject.has("type") - ) - && element.asJsonObject.has("url") - ) - -> Foo.VariantQux(gson.fromJson(element - , object: TypeToken() {}.type)) - + (((element.isJsonObject && element.asJsonObject.has("bucket")) && element.asJsonObject.has("endpoint")) && element.asJsonObject.has("type")) -> Foo.VariantBar(gson.fromJson(element, object: TypeToken() {}.type)) + ((element.isJsonObject && element.asJsonObject.has("path")) && element.asJsonObject.has("type")) -> Foo.VariantBaz(gson.fromJson(element, object: TypeToken() {}.type)) + (((element.isJsonObject && element.asJsonObject.has("namespace")) && element.asJsonObject.has("type")) && element.asJsonObject.has("url")) -> Foo.VariantQux(gson.fromJson(element, object: TypeToken() {}.type)) else -> throw JsonParseException("unknown Foo union value") - } - } override fun write(writer: JsonWriter, value: Foo?) { @@ -77,15 +49,12 @@ private class FooJsonAdapter: TypeAdapter() { when (value) { is Foo.VariantBar -> { gson.toJson(gson.toJsonTree(value.value0), writer) - } is Foo.VariantBaz -> { gson.toJson(gson.toJsonTree(value.value0), writer) - } is Foo.VariantQux -> { gson.toJson(gson.toJsonTree(value.value0), writer) - } } } diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-inline-discriminator-only/models/Event.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-inline-discriminator-only/models/Event.kt.golden index f20059550..e538f98da 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-inline-discriminator-only/models/Event.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-inline-discriminator-only/models/Event.kt.golden @@ -56,16 +56,10 @@ private class EventJsonAdapter: TypeAdapter() { val jsonObject = element.asJsonObject return when (jsonObject["kind"]?.asString) { "EVENT_UNSPECIFIED" -> Event.VariantEventUnspecified - - "EVENT_CREATED" -> Event.VariantEventCreated(gson.fromJson(jsonObject - , object: TypeToken() {}.type)) - - "EVENT_UPDATED" -> Event.VariantEventUpdated(gson.fromJson(jsonObject - , object: TypeToken() {}.type)) - + "EVENT_CREATED" -> Event.VariantEventCreated(gson.fromJson(jsonObject, object: TypeToken() {}.type)) + "EVENT_UPDATED" -> Event.VariantEventUpdated(gson.fromJson(jsonObject, object: TypeToken() {}.type)) else -> throw JsonParseException("unknown Event discriminator") } - } override fun write(writer: JsonWriter, value: Event?) { @@ -78,19 +72,16 @@ private class EventJsonAdapter: TypeAdapter() { val jsonObject = JsonObject() jsonObject.addProperty("kind", "EVENT_UNSPECIFIED") gson.toJson(jsonObject, writer) - } is Event.VariantEventCreated -> { val jsonObject = gson.toJsonTree(value.value0).asJsonObject jsonObject.addProperty("kind", "EVENT_CREATED") gson.toJson(jsonObject, writer) - } is Event.VariantEventUpdated -> { val jsonObject = gson.toJsonTree(value.value0).asJsonObject jsonObject.addProperty("kind", "EVENT_UPDATED") gson.toJson(jsonObject, writer) - } } } diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-internally-tagged/models/Resource.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-internally-tagged/models/Resource.kt.golden index f59e2b550..a61e8f08f 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-internally-tagged/models/Resource.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-internally-tagged/models/Resource.kt.golden @@ -39,18 +39,11 @@ private class ResourceJsonAdapter: TypeAdapter() { val element = JsonParser.parseReader(reader) val jsonObject = element.asJsonObject return when (jsonObject["kind"]?.asString) { - "RESOURCE_UNSPECIFIED" -> Resource.VariantResourceUnspecified(gson.fromJson(jsonObject - , object: TypeToken() {}.type)) - - "RESOURCE_QUICK" -> Resource.VariantResourceQuick(gson.fromJson(jsonObject - , object: TypeToken() {}.type)) - - "RESOURCE_CUSTOM" -> Resource.VariantResourceCustom(gson.fromJson(jsonObject - , object: TypeToken() {}.type)) - + "RESOURCE_UNSPECIFIED" -> Resource.VariantResourceUnspecified(gson.fromJson(jsonObject, object: TypeToken() {}.type)) + "RESOURCE_QUICK" -> Resource.VariantResourceQuick(gson.fromJson(jsonObject, object: TypeToken() {}.type)) + "RESOURCE_CUSTOM" -> Resource.VariantResourceCustom(gson.fromJson(jsonObject, object: TypeToken() {}.type)) else -> throw JsonParseException("unknown Resource discriminator") } - } override fun write(writer: JsonWriter, value: Resource?) { @@ -63,19 +56,16 @@ private class ResourceJsonAdapter: TypeAdapter() { val jsonObject = gson.toJsonTree(value.value0).asJsonObject jsonObject.addProperty("kind", "RESOURCE_UNSPECIFIED") gson.toJson(jsonObject, writer) - } is Resource.VariantResourceQuick -> { val jsonObject = gson.toJsonTree(value.value0).asJsonObject jsonObject.addProperty("kind", "RESOURCE_QUICK") gson.toJson(jsonObject, writer) - } is Resource.VariantResourceCustom -> { val jsonObject = gson.toJsonTree(value.value0).asJsonObject jsonObject.addProperty("kind", "RESOURCE_CUSTOM") gson.toJson(jsonObject, writer) - } } } diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-long-names/models/VeryLongDiscriminatedUnionTypeNameForTesting20260630.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-long-names/models/VeryLongDiscriminatedUnionTypeNameForTesting20260630.kt.golden index ef1da36c2..64ba0389f 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-long-names/models/VeryLongDiscriminatedUnionTypeNameForTesting20260630.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-long-names/models/VeryLongDiscriminatedUnionTypeNameForTesting20260630.kt.golden @@ -39,18 +39,11 @@ private class VeryLongDiscriminatedUnionTypeNameForTesting20260630JsonAdapter: T val element = JsonParser.parseReader(reader) val jsonObject = element.asJsonObject return when (jsonObject["kind"]?.asString) { - "ALPHA" -> VeryLongDiscriminatedUnionTypeNameForTesting20260630.VariantAlpha(gson.fromJson(jsonObject - , object: TypeToken() {}.type)) - - "BETA" -> VeryLongDiscriminatedUnionTypeNameForTesting20260630.VariantBeta(gson.fromJson(jsonObject - , object: TypeToken() {}.type)) - - "GAMMA" -> VeryLongDiscriminatedUnionTypeNameForTesting20260630.VariantGamma(gson.fromJson(jsonObject - , object: TypeToken() {}.type)) - + "ALPHA" -> VeryLongDiscriminatedUnionTypeNameForTesting20260630.VariantAlpha(gson.fromJson(jsonObject, object: TypeToken() {}.type)) + "BETA" -> VeryLongDiscriminatedUnionTypeNameForTesting20260630.VariantBeta(gson.fromJson(jsonObject, object: TypeToken() {}.type)) + "GAMMA" -> VeryLongDiscriminatedUnionTypeNameForTesting20260630.VariantGamma(gson.fromJson(jsonObject, object: TypeToken() {}.type)) else -> throw JsonParseException("unknown VeryLongDiscriminatedUnionTypeNameForTesting20260630 discriminator") } - } override fun write(writer: JsonWriter, value: VeryLongDiscriminatedUnionTypeNameForTesting20260630?) { @@ -63,19 +56,16 @@ private class VeryLongDiscriminatedUnionTypeNameForTesting20260630JsonAdapter: T val jsonObject = gson.toJsonTree(value.value0).asJsonObject jsonObject.addProperty("kind", "ALPHA") gson.toJson(jsonObject, writer) - } is VeryLongDiscriminatedUnionTypeNameForTesting20260630.VariantBeta -> { val jsonObject = gson.toJsonTree(value.value0).asJsonObject jsonObject.addProperty("kind", "BETA") gson.toJson(jsonObject, writer) - } is VeryLongDiscriminatedUnionTypeNameForTesting20260630.VariantGamma -> { val jsonObject = gson.toJsonTree(value.value0).asJsonObject jsonObject.addProperty("kind", "GAMMA") gson.toJson(jsonObject, writer) - } } } diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-mixed-unit-and-allof/models/Setup.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-mixed-unit-and-allof/models/Setup.kt.golden index ccaab95ed..d35a7f093 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-mixed-unit-and-allof/models/Setup.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-mixed-unit-and-allof/models/Setup.kt.golden @@ -45,16 +45,10 @@ private class SetupJsonAdapter: TypeAdapter() { val jsonObject = element.asJsonObject return when (jsonObject["kind"]?.asString) { "SETUP_UNSPECIFIED" -> Setup.VariantSetupUnspecified - - "SETUP_QUICK" -> Setup.VariantSetupQuick(gson.fromJson(jsonObject - , object: TypeToken() {}.type)) - - "SETUP_CUSTOM" -> Setup.VariantSetupCustom(gson.fromJson(jsonObject - , object: TypeToken() {}.type)) - + "SETUP_QUICK" -> Setup.VariantSetupQuick(gson.fromJson(jsonObject, object: TypeToken() {}.type)) + "SETUP_CUSTOM" -> Setup.VariantSetupCustom(gson.fromJson(jsonObject, object: TypeToken() {}.type)) else -> throw JsonParseException("unknown Setup discriminator") } - } override fun write(writer: JsonWriter, value: Setup?) { @@ -67,19 +61,16 @@ private class SetupJsonAdapter: TypeAdapter() { val jsonObject = JsonObject() jsonObject.addProperty("kind", "SETUP_UNSPECIFIED") gson.toJson(jsonObject, writer) - } is Setup.VariantSetupQuick -> { val jsonObject = gson.toJsonTree(value.value0).asJsonObject jsonObject.addProperty("kind", "SETUP_QUICK") gson.toJson(jsonObject, writer) - } is Setup.VariantSetupCustom -> { val jsonObject = gson.toJsonTree(value.value0).asJsonObject jsonObject.addProperty("kind", "SETUP_CUSTOM") gson.toJson(jsonObject, writer) - } } } diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-multiple/models/ContainerKind.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-multiple/models/ContainerKind.kt.golden index f5c68e046..56bda1900 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-multiple/models/ContainerKind.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-multiple/models/ContainerKind.kt.golden @@ -38,15 +38,10 @@ private class ContainerKindJsonAdapter: TypeAdapter() { val element = JsonParser.parseReader(reader) val jsonObject = element.asJsonObject return when (jsonObject["kind"]?.asString) { - "CONTAINER_KIND_MANAGED" -> ContainerKind.VariantContainerKindManaged(gson.fromJson(jsonObject - , object: TypeToken() {}.type)) - - "CONTAINER_KIND_CUSTOM" -> ContainerKind.VariantContainerKindCustom(gson.fromJson(jsonObject - , object: TypeToken() {}.type)) - + "CONTAINER_KIND_MANAGED" -> ContainerKind.VariantContainerKindManaged(gson.fromJson(jsonObject, object: TypeToken() {}.type)) + "CONTAINER_KIND_CUSTOM" -> ContainerKind.VariantContainerKindCustom(gson.fromJson(jsonObject, object: TypeToken() {}.type)) else -> throw JsonParseException("unknown ContainerKind discriminator") } - } override fun write(writer: JsonWriter, value: ContainerKind?) { @@ -59,13 +54,11 @@ private class ContainerKindJsonAdapter: TypeAdapter() { val jsonObject = gson.toJsonTree(value.value0).asJsonObject jsonObject.addProperty("kind", "CONTAINER_KIND_MANAGED") gson.toJson(jsonObject, writer) - } is ContainerKind.VariantContainerKindCustom -> { val jsonObject = gson.toJsonTree(value.value0).asJsonObject jsonObject.addProperty("kind", "CONTAINER_KIND_CUSTOM") gson.toJson(jsonObject, writer) - } } } diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-multiple/models/VolumeKind.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-multiple/models/VolumeKind.kt.golden index bd504b95d..7d2aee069 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-multiple/models/VolumeKind.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-multiple/models/VolumeKind.kt.golden @@ -38,15 +38,10 @@ private class VolumeKindJsonAdapter: TypeAdapter() { val element = JsonParser.parseReader(reader) val jsonObject = element.asJsonObject return when (jsonObject["kind"]?.asString) { - "VOLUME_KIND_LOCAL" -> VolumeKind.VariantVolumeKindLocal(gson.fromJson(jsonObject - , object: TypeToken() {}.type)) - - "VOLUME_KIND_REMOTE" -> VolumeKind.VariantVolumeKindRemote(gson.fromJson(jsonObject - , object: TypeToken() {}.type)) - + "VOLUME_KIND_LOCAL" -> VolumeKind.VariantVolumeKindLocal(gson.fromJson(jsonObject, object: TypeToken() {}.type)) + "VOLUME_KIND_REMOTE" -> VolumeKind.VariantVolumeKindRemote(gson.fromJson(jsonObject, object: TypeToken() {}.type)) else -> throw JsonParseException("unknown VolumeKind discriminator") } - } override fun write(writer: JsonWriter, value: VolumeKind?) { @@ -59,13 +54,11 @@ private class VolumeKindJsonAdapter: TypeAdapter() { val jsonObject = gson.toJsonTree(value.value0).asJsonObject jsonObject.addProperty("kind", "VOLUME_KIND_LOCAL") gson.toJson(jsonObject, writer) - } is VolumeKind.VariantVolumeKindRemote -> { val jsonObject = gson.toJsonTree(value.value0).asJsonObject jsonObject.addProperty("kind", "VOLUME_KIND_REMOTE") gson.toJson(jsonObject, writer) - } } } diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-with-refs/models/ContainerImage.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-with-refs/models/ContainerImage.kt.golden index 07df45704..bcf18241c 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-with-refs/models/ContainerImage.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-discriminated-union-with-refs/models/ContainerImage.kt.golden @@ -36,15 +36,10 @@ private class ContainerImageJsonAdapter: TypeAdapter() { val element = JsonParser.parseReader(reader) val jsonObject = element.asJsonObject return when (jsonObject["kind"]?.asString) { - "CONTAINER_IMAGE_MANAGED" -> ContainerImage.VariantContainerImageManaged(gson.fromJson(jsonObject - , object: TypeToken() {}.type)) - - "CONTAINER_IMAGE_CUSTOM" -> ContainerImage.VariantContainerImageCustom(gson.fromJson(jsonObject - , object: TypeToken() {}.type)) - + "CONTAINER_IMAGE_MANAGED" -> ContainerImage.VariantContainerImageManaged(gson.fromJson(jsonObject, object: TypeToken() {}.type)) + "CONTAINER_IMAGE_CUSTOM" -> ContainerImage.VariantContainerImageCustom(gson.fromJson(jsonObject, object: TypeToken() {}.type)) else -> throw JsonParseException("unknown ContainerImage discriminator") } - } override fun write(writer: JsonWriter, value: ContainerImage?) { @@ -57,13 +52,11 @@ private class ContainerImageJsonAdapter: TypeAdapter() { val jsonObject = gson.toJsonTree(value.value0).asJsonObject jsonObject.addProperty("kind", "CONTAINER_IMAGE_MANAGED") gson.toJson(jsonObject, writer) - } is ContainerImage.VariantContainerImageCustom -> { val jsonObject = gson.toJsonTree(value.value0).asJsonObject jsonObject.addProperty("kind", "CONTAINER_IMAGE_CUSTOM") gson.toJson(jsonObject, writer) - } } } diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-nested-union/models/Foo.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-nested-union/models/Foo.kt.golden index f654f32de..ec6c64bdb 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-nested-union/models/Foo.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-nested-union/models/Foo.kt.golden @@ -34,33 +34,11 @@ private class FooJsonAdapter: TypeAdapter() { val element = JsonParser.parseReader(reader) return when { element.isJsonNull -> null - - (((element.isJsonObject - && element.asJsonObject.has("endpoint") - ) - && element.asJsonObject.has("type") - ) - || ((element.isJsonObject - && element.asJsonObject.has("path") - ) - && element.asJsonObject.has("type") - ) - ) - -> Foo.VariantQux(gson.fromJson(element - , object: TypeToken() {}.type)) - - element.isJsonPrimitive && element.asJsonPrimitive.isString - -> Foo.VariantString(element - .asString) - - element.isJsonPrimitive && element.asJsonPrimitive.isNumber - -> Foo.VariantNumber(element - .asDouble) - + (((element.isJsonObject && element.asJsonObject.has("endpoint")) && element.asJsonObject.has("type")) || ((element.isJsonObject && element.asJsonObject.has("path")) && element.asJsonObject.has("type"))) -> Foo.VariantQux(gson.fromJson(element, object: TypeToken() {}.type)) + element.isJsonPrimitive && element.asJsonPrimitive.isString -> Foo.VariantString(element.asString) + element.isJsonPrimitive && element.asJsonPrimitive.isNumber -> Foo.VariantNumber(element.asDouble) else -> throw JsonParseException("unknown Foo union value") - } - } override fun write(writer: JsonWriter, value: Foo?) { @@ -71,15 +49,12 @@ private class FooJsonAdapter: TypeAdapter() { when (value) { is Foo.VariantQux -> { gson.toJson(gson.toJsonTree(value.value0), writer) - } is Foo.VariantString -> { gson.toJson(gson.toJsonTree(value.value0), writer) - } is Foo.VariantNumber -> { gson.toJson(gson.toJsonTree(value.value0), writer) - } } } diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-nested-union/models/Qux.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-nested-union/models/Qux.kt.golden index 8b19e1feb..275ceb71a 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-nested-union/models/Qux.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-nested-union/models/Qux.kt.golden @@ -32,27 +32,10 @@ private class QuxJsonAdapter: TypeAdapter() { val element = JsonParser.parseReader(reader) return when { element.isJsonNull -> null - - ((element.isJsonObject - && element.asJsonObject.has("endpoint") - ) - && element.asJsonObject.has("type") - ) - -> Qux.VariantBar(gson.fromJson(element - , object: TypeToken() {}.type)) - - ((element.isJsonObject - && element.asJsonObject.has("path") - ) - && element.asJsonObject.has("type") - ) - -> Qux.VariantBaz(gson.fromJson(element - , object: TypeToken() {}.type)) - + ((element.isJsonObject && element.asJsonObject.has("endpoint")) && element.asJsonObject.has("type")) -> Qux.VariantBar(gson.fromJson(element, object: TypeToken() {}.type)) + ((element.isJsonObject && element.asJsonObject.has("path")) && element.asJsonObject.has("type")) -> Qux.VariantBaz(gson.fromJson(element, object: TypeToken() {}.type)) else -> throw JsonParseException("unknown Qux union value") - } - } override fun write(writer: JsonWriter, value: Qux?) { @@ -63,11 +46,9 @@ private class QuxJsonAdapter: TypeAdapter() { when (value) { is Qux.VariantBar -> { gson.toJson(gson.toJsonTree(value.value0), writer) - } is Qux.VariantBaz -> { gson.toJson(gson.toJsonTree(value.value0), writer) - } } } diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-mixed/models/Foo.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-mixed/models/Foo.kt.golden index b0f550238..2bbb7bef6 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-mixed/models/Foo.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-mixed/models/Foo.kt.golden @@ -34,27 +34,11 @@ private class FooJsonAdapter: TypeAdapter() { val element = JsonParser.parseReader(reader) return when { element.isJsonNull -> null - - ((element.isJsonObject - && element.asJsonObject.has("id") - ) - && element.asJsonObject.has("name") - ) - -> Foo.VariantBar(gson.fromJson(element - , object: TypeToken() {}.type)) - - element.isJsonPrimitive && element.asJsonPrimitive.isString - -> Foo.VariantString(element - .asString) - - element.isJsonPrimitive && element.asJsonPrimitive.isNumber - -> Foo.VariantNumber(element - .asDouble) - + ((element.isJsonObject && element.asJsonObject.has("id")) && element.asJsonObject.has("name")) -> Foo.VariantBar(gson.fromJson(element, object: TypeToken() {}.type)) + element.isJsonPrimitive && element.asJsonPrimitive.isString -> Foo.VariantString(element.asString) + element.isJsonPrimitive && element.asJsonPrimitive.isNumber -> Foo.VariantNumber(element.asDouble) else -> throw JsonParseException("unknown Foo union value") - } - } override fun write(writer: JsonWriter, value: Foo?) { @@ -65,15 +49,12 @@ private class FooJsonAdapter: TypeAdapter() { when (value) { is Foo.VariantBar -> { gson.toJson(gson.toJsonTree(value.value0), writer) - } is Foo.VariantString -> { gson.toJson(gson.toJsonTree(value.value0), writer) - } is Foo.VariantNumber -> { gson.toJson(gson.toJsonTree(value.value0), writer) - } } } diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-any/models/Config.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-any/models/Config.kt.golden index adb98ace2..36aa45693 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-any/models/Config.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-any/models/Config.kt.golden @@ -34,21 +34,10 @@ private class ConfigJsonAdapter: TypeAdapter() { val element = JsonParser.parseReader(reader) return when { element.isJsonNull -> null - - element.isJsonPrimitive && element.asJsonPrimitive.isString - -> Config.VariantString(element - .asString) - - element.isJsonPrimitive && element.asJsonPrimitive.isNumber - -> Config.VariantNumber(element - .asDouble) - - else -> Config.VariantAny(gson.fromJson(element - , object: TypeToken() {}.type)) - - + element.isJsonPrimitive && element.asJsonPrimitive.isString -> Config.VariantString(element.asString) + element.isJsonPrimitive && element.asJsonPrimitive.isNumber -> Config.VariantNumber(element.asDouble) + else -> Config.VariantAny(gson.fromJson(element, object: TypeToken() {}.type)) } - } override fun write(writer: JsonWriter, value: Config?) { @@ -59,15 +48,12 @@ private class ConfigJsonAdapter: TypeAdapter() { when (value) { is Config.VariantString -> { gson.toJson(gson.toJsonTree(value.value0), writer) - } is Config.VariantNumber -> { gson.toJson(gson.toJsonTree(value.value0), writer) - } is Config.VariantAny -> { gson.toJson(gson.toJsonTree(value.value0), writer) - } } } diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-inline-objects/models/Foo.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-inline-objects/models/Foo.kt.golden index ebf92268c..62d259fb0 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-inline-objects/models/Foo.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-inline-objects/models/Foo.kt.golden @@ -37,29 +37,11 @@ private class FooJsonAdapter: TypeAdapter() { val element = JsonParser.parseReader(reader) return when { element.isJsonNull -> null - - (element.isJsonObject - && element.asJsonObject.has("bar") - ) - -> Foo.VariantFooMember1(gson.fromJson(element - , object: TypeToken() {}.type)) - - (element.isJsonObject - && element.asJsonObject.has("quux") - ) - -> Foo.VariantFooMember2(gson.fromJson(element - , object: TypeToken() {}.type)) - - (element.isJsonObject - && element.asJsonObject.has("grault") - ) - -> Foo.VariantFooMember3(gson.fromJson(element - , object: TypeToken() {}.type)) - + (element.isJsonObject && element.asJsonObject.has("bar")) -> Foo.VariantFooMember1(gson.fromJson(element, object: TypeToken() {}.type)) + (element.isJsonObject && element.asJsonObject.has("quux")) -> Foo.VariantFooMember2(gson.fromJson(element, object: TypeToken() {}.type)) + (element.isJsonObject && element.asJsonObject.has("grault")) -> Foo.VariantFooMember3(gson.fromJson(element, object: TypeToken() {}.type)) else -> throw JsonParseException("unknown Foo union value") - } - } override fun write(writer: JsonWriter, value: Foo?) { @@ -70,15 +52,12 @@ private class FooJsonAdapter: TypeAdapter() { when (value) { is Foo.VariantFooMember1 -> { gson.toJson(gson.toJsonTree(value.value0), writer) - } is Foo.VariantFooMember2 -> { gson.toJson(gson.toJsonTree(value.value0), writer) - } is Foo.VariantFooMember3 -> { gson.toJson(gson.toJsonTree(value.value0), writer) - } } } diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-interfaces/models/Foo.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-interfaces/models/Foo.kt.golden index 13f5901f8..af67f9c0e 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-interfaces/models/Foo.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-interfaces/models/Foo.kt.golden @@ -34,39 +34,11 @@ private class FooJsonAdapter: TypeAdapter() { val element = JsonParser.parseReader(reader) return when { element.isJsonNull -> null - - (((element.isJsonObject - && element.asJsonObject.has("bucket") - ) - && element.asJsonObject.has("endpoint") - ) - && element.asJsonObject.has("type") - ) - -> Foo.VariantBar(gson.fromJson(element - , object: TypeToken() {}.type)) - - ((element.isJsonObject - && element.asJsonObject.has("path") - ) - && element.asJsonObject.has("type") - ) - -> Foo.VariantBaz(gson.fromJson(element - , object: TypeToken() {}.type)) - - (((element.isJsonObject - && element.asJsonObject.has("namespace") - ) - && element.asJsonObject.has("type") - ) - && element.asJsonObject.has("url") - ) - -> Foo.VariantQux(gson.fromJson(element - , object: TypeToken() {}.type)) - + (((element.isJsonObject && element.asJsonObject.has("bucket")) && element.asJsonObject.has("endpoint")) && element.asJsonObject.has("type")) -> Foo.VariantBar(gson.fromJson(element, object: TypeToken() {}.type)) + ((element.isJsonObject && element.asJsonObject.has("path")) && element.asJsonObject.has("type")) -> Foo.VariantBaz(gson.fromJson(element, object: TypeToken() {}.type)) + (((element.isJsonObject && element.asJsonObject.has("namespace")) && element.asJsonObject.has("type")) && element.asJsonObject.has("url")) -> Foo.VariantQux(gson.fromJson(element, object: TypeToken() {}.type)) else -> throw JsonParseException("unknown Foo union value") - } - } override fun write(writer: JsonWriter, value: Foo?) { @@ -77,15 +49,12 @@ private class FooJsonAdapter: TypeAdapter() { when (value) { is Foo.VariantBar -> { gson.toJson(gson.toJsonTree(value.value0), writer) - } is Foo.VariantBaz -> { gson.toJson(gson.toJsonTree(value.value0), writer) - } is Foo.VariantQux -> { gson.toJson(gson.toJsonTree(value.value0), writer) - } } } diff --git a/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-primitives/models/Foo.kt.golden b/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-primitives/models/Foo.kt.golden index 6c8bdb3cd..eda0ec595 100644 --- a/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-primitives/models/Foo.kt.golden +++ b/tests/golden/kotlin/kotlin-okhttp/type-aliases-union-with-primitives/models/Foo.kt.golden @@ -39,23 +39,11 @@ private class FooJsonAdapter: TypeAdapter() { val element = JsonParser.parseReader(reader) return when { element.isJsonNull -> Foo.VariantNull - - element.isJsonPrimitive && element.asJsonPrimitive.isString - -> Foo.VariantString(element - .asString) - - element.isJsonPrimitive && element.asJsonPrimitive.isNumber - -> Foo.VariantNumber(element - .asDouble) - - element.isJsonPrimitive && element.asJsonPrimitive.isBoolean - -> Foo.VariantBoolean(element - .asBoolean) - + element.isJsonPrimitive && element.asJsonPrimitive.isString -> Foo.VariantString(element.asString) + element.isJsonPrimitive && element.asJsonPrimitive.isNumber -> Foo.VariantNumber(element.asDouble) + element.isJsonPrimitive && element.asJsonPrimitive.isBoolean -> Foo.VariantBoolean(element.asBoolean) else -> throw JsonParseException("unknown Foo union value") - } - } override fun write(writer: JsonWriter, value: Foo?) { @@ -66,19 +54,15 @@ private class FooJsonAdapter: TypeAdapter() { when (value) { is Foo.VariantString -> { gson.toJson(gson.toJsonTree(value.value0), writer) - } is Foo.VariantNumber -> { gson.toJson(gson.toJsonTree(value.value0), writer) - } is Foo.VariantBoolean -> { gson.toJson(gson.toJsonTree(value.value0), writer) - } is Foo.VariantNull -> { writer.nullValue() - } } } diff --git a/tests/golden/typescript/typescript-fetch/nested-nullable/apis/ValuesApi.ts.golden b/tests/golden/typescript/typescript-fetch/nested-nullable/apis/ValuesApi.ts.golden index d67d5e7e4..5587ccac5 100644 --- a/tests/golden/typescript/typescript-fetch/nested-nullable/apis/ValuesApi.ts.golden +++ b/tests/golden/typescript/typescript-fetch/nested-nullable/apis/ValuesApi.ts.golden @@ -72,23 +72,23 @@ export class ValuesApi extends BaseAPI implements ValuesApiInterface { if (response.status === 400) { let errorValue: Promise> | undefined; throw new GetValuesError(response, { kind: 'badRequest', status: response.status as 400, raw: response, value: () => errorValue ??= new JSONApiResponse>(errorRaw.clone()).value() }); + string[] | null>>(errorRaw.clone()).value() }); } else if (response.status === 401) { let errorValue: Promise<(Record | null)[]> | undefined; throw new GetValuesError(response, { kind: 'unauthorized', status: response.status as 401, raw: response, value: () => errorValue ??= new JSONApiResponse<(Record - | null)[]>(errorRaw.clone()).value() }); + string> + | null)[]>(errorRaw.clone()).value() }); } else if (response.status === 402) { let errorValue: Promise | undefined; throw new GetValuesError(response, { kind: 'status402', status: response.status as 402, raw: response, value: () => errorValue ??= new JSONApiResponse(errorRaw.clone()).value() }); + | null>(errorRaw.clone()).value() }); } else if (response.status === 403) { let errorValue: Promise> | undefined; throw new GetValuesError(response, { kind: 'forbidden', status: response.status as 403, raw: response, value: () => errorValue ??= new JSONApiResponse>(errorRaw.clone()).value() }); + string[]>>(errorRaw.clone()).value() }); } else if (response.status === 404) { let errorValue: Promise | undefined; diff --git a/tests/golden/typescript/typescript-fetch/petstore/apis/StoreApi.ts.golden b/tests/golden/typescript/typescript-fetch/petstore/apis/StoreApi.ts.golden index 8ce5248e7..fab9712e8 100644 --- a/tests/golden/typescript/typescript-fetch/petstore/apis/StoreApi.ts.golden +++ b/tests/golden/typescript/typescript-fetch/petstore/apis/StoreApi.ts.golden @@ -146,7 +146,7 @@ export class StoreApi extends BaseAPI implements StoreApiInterface { } if (response.status === 200) { return new JSONApiResponse(response) as JSONApiResponse> & { status: 200 }; + number>> & { status: 200 }; } else { return new JSONApiResponse(response) as JSONApiResponse &HttpSuccessResponse; diff --git a/tests/golden/typescript/typescript-fetch/response-dispatch/README.md.golden b/tests/golden/typescript/typescript-fetch/response-dispatch/README.md.golden new file mode 100644 index 000000000..3e33d7b90 --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/response-dispatch/README.md.golden @@ -0,0 +1,248 @@ +# type-script-response-dispatch + +Generated API client + +**Version:** 1.0.0 + +## Overview + +This package provides a TypeScript/JavaScript client for the TypeScript Response Dispatch API. It uses the native [Fetch API](https://fetch.spec.whatwg.org/) for HTTP requests and works in both Node.js and browser environments. + +## Features + +- ✨ **Type-safe** - Full TypeScript support with generated types +- 🚀 **Modern** - Uses native Fetch API, no external HTTP dependencies +- 🔧 **Configurable** - Flexible configuration options +- 🎯 **Middleware** - Support for request/response interceptors +- 📦 **Tree-shakeable** - Import only what you need +- 🌐 **Universal** - Works in Node.js and browsers + +## Installation + +### From npm (published package) + +```bash +npm install type-script-response-dispatch +``` + +### From local path (development) + +Add the package to your `package.json` using the `file:` protocol: + +```json +{ + "dependencies": { + "type-script-response-dispatch": "file:../../path/to/generated/package" + } +} +``` + +Then run: + +```bash +npm install +``` + +## Quick Start + +```typescript +import { Configuration, ResponsesApi } from 'type-script-response-dispatch'; + +// Create a configuration +const config = new Configuration({ + basePath: 'https://api.example.com', + headers: { + 'Authorization': 'Bearer YOUR_TOKEN' + } +}); + +// Initialize the API client +const api = new ResponsesApi(config); + +// Make API calls +try { + const result = await api.someMethod(); + console.log(result); +} catch (error) { + console.error('API Error:', error); +} +``` + +## Configuration + +The `Configuration` class accepts the following options: + +```typescript +interface ConfigurationParameters { + /** Base URL for API requests */ + basePath?: string; + + /** Custom fetch implementation */ + fetchApi?: typeof fetch; + + /** Request/response middleware */ + middleware?: Middleware[]; + + /** Custom query string serializer */ + queryParamsStringify?: (params: HTTPQuery) => string; + + /** Default headers for all requests */ + headers?: Record; + + /** Credentials mode for requests */ + credentials?: RequestCredentials; +} +``` + +### Example with custom configuration + +```typescript +const config = new Configuration({ + basePath: 'https://api.example.com', + headers: { + 'X-API-Key': 'your-api-key', + 'Content-Type': 'application/json' + }, + credentials: 'include' +}); +``` + +## Middleware + +Add custom middleware to intercept requests and responses: + +```typescript +import { Configuration, Middleware } from 'type-script-response-dispatch'; + +const loggingMiddleware: Middleware = { + pre: async (context) => { + console.log('Request:', context.url); + return context; + }, + post: async (context) => { + console.log('Response:', context.response.status); + return context.response; + }, + onError: async (context) => { + console.error('Error:', context.error); + return undefined; + } +}; + +const config = new Configuration({ + basePath: 'https://api.example.com', + middleware: [loggingMiddleware] +}); +``` + +## Error Handling + +The client throws typed errors for different failure scenarios: + +```typescript +import { ResponseError, FetchError, RequiredError } from 'type-script-response-dispatch'; + +try { + const result = await api.someMethod(); +} catch (error) { + if (error instanceof ResponseError) { + // HTTP error response (4xx, 5xx) + console.error('HTTP Error:', error.response.status); + } else if (error instanceof FetchError) { + // Network or fetch error + console.error('Network Error:', error.cause); + } else if (error instanceof RequiredError) { + // Missing required parameter + console.error('Missing field:', error.field); + } +} +``` + +## API Reference + +This package exports the following: + +- **Configuration** - Client configuration class +- **BaseAPI** - Base class for all API clients +- **API Classes** - Generated API client classes (e.g., `UserApi`, `PostApi`) +- **Models** - Generated TypeScript interfaces for request/response types +- **Errors** - `ResponseError`, `FetchError`, `RequiredError` +- **Types** - TypeScript type definitions + +## Development + +### Building + +To build the package: + +```bash +npm install +npm run build +``` + +This will compile TypeScript to JavaScript in the `dist/` directory. + +### Building for ESM + +To build ES modules: + +```bash +npm run build +``` + +## TypeScript Support + +This package includes TypeScript type definitions. No additional `@types` package is needed. + +### TypeScript Configuration + +This package works with standard TypeScript configurations. If you're using a bundler-based setup, you may want to configure: + +```json +{ + "compilerOptions": { + "moduleResolution": "bundler" + } +} +``` + +### Type Imports + +```typescript +import type { User, CreateUserRequest } from 'type-script-response-dispatch'; + +const user: User = { + id: 1, + name: 'John Doe', + email: 'john@example.com' +}; +``` + +## Browser Support + +This package uses the native Fetch API, which is supported in: + +- Chrome 42+ +- Firefox 39+ +- Safari 10.1+ +- Edge 14+ +- Node.js 18+ (native fetch) +- Node.js <18 (with `node-fetch` polyfill) + +For older browsers, you may need to include a fetch polyfill. + +## License + +This is an auto-generated API client. Please refer to your API documentation for license information. + +## Support + +For issues related to the API itself, please contact the API provider. + +For issues with this generated client, please check the OpenAPI specification used to generate it. + +--- + +**Generated by OpenAPI Generator** + +API Version: 1.0.0 diff --git a/tests/golden/typescript/typescript-fetch/response-dispatch/apis/ResponsesApi.ts.golden b/tests/golden/typescript/typescript-fetch/response-dispatch/apis/ResponsesApi.ts.golden new file mode 100644 index 000000000..175cf1ba4 --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/response-dispatch/apis/ResponsesApi.ts.golden @@ -0,0 +1,367 @@ +/** + * @generated by openapi-nexus. Do not edit. + * + * TypeScript Response Dispatch — 1.0.0 + */ +import type { Detail } from '../models/Detail'; +import type { Configuration, HTTPQuery, HttpSuccessResponse, InitOverrideFunction } from '../runtime/runtime'; +import { BaseAPI, BlobApiResponse, DefaultConfig, JSONApiResponse, ResponseError, TextApiResponse, VoidApiResponse } from '../runtime/runtime'; + +export type ReadDefaultRawResponse = VoidApiResponse & HttpSuccessResponse; + +export type ReadErrorOnlyRawResponse = VoidApiResponse & HttpSuccessResponse; + +export type ReadMixedRawResponse = + | JSONApiResponse & { status: 201 } + | TextApiResponse & { status: 202 } + | VoidApiResponse & { status: 204 } + | BlobApiResponse & { status: 206 } + | JSONApiResponse & { status: number } + | JSONApiResponse & HttpSuccessResponse; + +export type ReadSuccessOnlyRawResponse = + | VoidApiResponse & { status: 204 } + | VoidApiResponse & HttpSuccessResponse; + +export type ReadWildcardsRawResponse = + | VoidApiResponse & { status: number } + | VoidApiResponse & HttpSuccessResponse; + + +export type ReadDefaultErrorDetail = + { kind: 'default'; status: number; raw: Response; value(): Promise } + | { kind: 'unexpected'; status: number; raw: Response; value(): Promise }; + +export class ReadDefaultError extends ResponseError { + readonly detail: ReadDefaultErrorDetail; + + constructor(response: Response, detail: ReadDefaultErrorDetail, msg = 'Response returned an error code') { + super(response, msg); + this.detail = detail; + } +} + +export type ReadErrorOnlyErrorDetail = + { kind: 'badRequest'; status: 400; raw: Response; value(): Promise } + | { kind: 'unexpected'; status: number; raw: Response; value(): Promise }; + +export class ReadErrorOnlyError extends ResponseError { + readonly detail: ReadErrorOnlyErrorDetail; + + constructor(response: Response, detail: ReadErrorOnlyErrorDetail, msg = 'Response returned an error code') { + super(response, msg); + this.detail = detail; + } +} + +export type ReadMixedErrorDetail = + { kind: 'badRequest'; status: 400; raw: Response; value(): Promise } + | { kind: 'unauthorized'; status: 401; raw: Response; value(): Promise } + | { kind: 'conflict'; status: 409; raw: Response; value(): Promise } + | { kind: 'status410'; status: 410; raw: Response; value(): Promise } + | { kind: 'status415'; status: 415; raw: Response; value(): Promise } + | { kind: 'serverError'; status: number; raw: Response; value(): Promise } + | { kind: 'default'; status: number; raw: Response; value(): Promise } + | { kind: 'unexpected'; status: number; raw: Response; value(): Promise }; + +export class ReadMixedError extends ResponseError { + readonly detail: ReadMixedErrorDetail; + + constructor(response: Response, detail: ReadMixedErrorDetail, msg = 'Response returned an error code') { + super(response, msg); + this.detail = detail; + } +} + +export type ReadSuccessOnlyErrorDetail = + { kind: 'unexpected'; status: number; raw: Response; value(): Promise }; + +export class ReadSuccessOnlyError extends ResponseError { + readonly detail: ReadSuccessOnlyErrorDetail; + + constructor(response: Response, detail: ReadSuccessOnlyErrorDetail, msg = 'Response returned an error code') { + super(response, msg); + this.detail = detail; + } +} + +export type ReadWildcardsErrorDetail = + { kind: 'clientError'; status: number; raw: Response; value(): Promise } + | { kind: 'serverError'; status: number; raw: Response; value(): Promise } + | { kind: 'unexpected'; status: number; raw: Response; value(): Promise }; + +export class ReadWildcardsError extends ResponseError { + readonly detail: ReadWildcardsErrorDetail; + + constructor(response: Response, detail: ReadWildcardsErrorDetail, msg = 'Response returned an error code') { + super(response, msg); + this.detail = detail; + } +} + + +export interface ResponsesApiInterface { + readDefaultRaw: (initOverrides?: RequestInit | InitOverrideFunction) => Promise; + readDefault: (initOverrides?: RequestInit | InitOverrideFunction) => Promise; + readErrorOnlyRaw: (initOverrides?: RequestInit | InitOverrideFunction) => Promise; + readErrorOnly: (initOverrides?: RequestInit | InitOverrideFunction) => Promise; + readMixedRaw: (initOverrides?: RequestInit | InitOverrideFunction) => Promise; + readMixed: (initOverrides?: RequestInit | InitOverrideFunction) => Promise; + readSuccessOnlyRaw: (initOverrides?: RequestInit | InitOverrideFunction) => Promise; + readSuccessOnly: (initOverrides?: RequestInit | InitOverrideFunction) => Promise; + readWildcardsRaw: (initOverrides?: RequestInit | InitOverrideFunction) => Promise; + readWildcards: (initOverrides?: RequestInit | InitOverrideFunction) => Promise; +} + +export class ResponsesApi extends BaseAPI implements ResponsesApiInterface { + /** + * Initialize the API client + */ + constructor(configuration?: Configuration) { + super(configuration ?? DefaultConfig); + } + + async readDefaultRaw(initOverrides?: RequestInit + | InitOverrideFunction): Promise { + // Build path with path parameters + const urlPath = `/default`; + // Build query parameters + const queryParameters: HTTPQuery = {}; + // Build headers + const headerParameters: Record = { + }; + + // Make request + const response = await this.request({ + path: urlPath, + method: 'GET', + headers: headerParameters, + query: queryParameters, + body: undefined, + }, initOverrides); + + // Handle responses + if (!(response.status >= 200 && response.status < 300)) { + const errorRaw = response.clone(); + { + let errorValue: Promise | undefined; + throw new ReadDefaultError(response, { kind: 'default', status: response.status, raw: response, value: () => errorValue ??= new JSONApiResponse(errorRaw.clone()).value() }); + } + } + return new VoidApiResponse(response) as VoidApiResponse & HttpSuccessResponse; + } + + async readDefault(initOverrides?: RequestInit | InitOverrideFunction): Promise { + const response = await this.readDefaultRaw(initOverrides); + return await response.value(); + } + + async readErrorOnlyRaw(initOverrides?: RequestInit + | InitOverrideFunction): Promise { + // Build path with path parameters + const urlPath = `/error`; + // Build query parameters + const queryParameters: HTTPQuery = {}; + // Build headers + const headerParameters: Record = { + }; + + // Make request + const response = await this.request({ + path: urlPath, + method: 'GET', + headers: headerParameters, + query: queryParameters, + body: undefined, + }, initOverrides); + + // Handle responses + if (!(response.status >= 200 && response.status < 300)) { + const errorRaw = response.clone(); + if (response.status === 400) { + let errorValue: Promise | undefined; + throw new ReadErrorOnlyError(response, { kind: 'badRequest', status: response.status as 400, raw: response, value: () => errorValue ??= new TextApiResponse(errorRaw.clone()).value() }); + } + else { + let errorValue: Promise | undefined; + throw new ReadErrorOnlyError(response, { kind: 'unexpected', status: response.status, raw: response, value: () => errorValue ??= errorRaw.clone().blob() }); + } + } + return new VoidApiResponse(response) as VoidApiResponse & HttpSuccessResponse; + } + + async readErrorOnly(initOverrides?: RequestInit | InitOverrideFunction): Promise { + const response = await this.readErrorOnlyRaw(initOverrides); + return await response.value(); + } + + async readMixedRaw(initOverrides?: RequestInit + | InitOverrideFunction): Promise { + // Build path with path parameters + const urlPath = `/mixed`; + // Build query parameters + const queryParameters: HTTPQuery = {}; + // Build headers + const headerParameters: Record = { + }; + + // Make request + const response = await this.request({ + path: urlPath, + method: 'GET', + headers: headerParameters, + query: queryParameters, + body: undefined, + }, initOverrides); + + // Handle responses + if (!(response.status >= 200 && response.status < 300)) { + const errorRaw = response.clone(); + if (response.status === 400) { + let errorValue: Promise | undefined; + throw new ReadMixedError(response, { kind: 'badRequest', status: response.status as 400, raw: response, value: () => errorValue ??= new JSONApiResponse(errorRaw.clone()).value() }); + } + else if (response.status === 401) { + let errorValue: Promise | undefined; + throw new ReadMixedError(response, { kind: 'unauthorized', status: response.status as 401, raw: response, value: () => errorValue ??= new JSONApiResponse(errorRaw.clone()).value() }); + } + else if (response.status === 409) { + let errorValue: Promise | undefined; + throw new ReadMixedError(response, { kind: 'conflict', status: response.status as 409, raw: response, value: () => errorValue ??= new TextApiResponse(errorRaw.clone()).value() }); + } + else if (response.status === 410) { + let errorValue: Promise | undefined; + throw new ReadMixedError(response, { kind: 'status410', status: response.status as 410, raw: response, value: () => errorValue ??= new VoidApiResponse(errorRaw.clone()).value() }); + } + else if (response.status === 415) { + let errorValue: Promise | undefined; + throw new ReadMixedError(response, { kind: 'status415', status: response.status as 415, raw: response, value: () => errorValue ??= new BlobApiResponse(errorRaw.clone()).value() }); + } + else if (response.status >= 500 && response.status < 600) { + let errorValue: Promise | undefined; + throw new ReadMixedError(response, { kind: 'serverError', status: response.status, raw: response, value: () => errorValue ??= new JSONApiResponse(errorRaw.clone()).value() }); + } + else { + let errorValue: Promise | undefined; + throw new ReadMixedError(response, { kind: 'default', status: response.status, raw: response, value: () => errorValue ??= new JSONApiResponse(errorRaw.clone()).value() }); + } + } + if (response.status === 201) { + return new JSONApiResponse(response) as JSONApiResponse & { status: 201 }; + } + else if (response.status === 202) { + return new TextApiResponse(response) as TextApiResponse & { status: 202 }; + } + else if (response.status === 204) { + return new VoidApiResponse(response) as VoidApiResponse & { status: 204 }; + } + else if (response.status === 206) { + return new BlobApiResponse(response) as BlobApiResponse & { status: 206 }; + } + else if (response.status >= 200 && response.status < 300) { + return new JSONApiResponse(response) as JSONApiResponse & { status: number }; + } + else { + return new JSONApiResponse(response) as JSONApiResponse &HttpSuccessResponse; + } + } + + async readMixed(initOverrides?: RequestInit | InitOverrideFunction): Promise { + const response = await this.readMixedRaw(initOverrides); + return await response.value() as Detail | string | Blob | Detail[]; + } + + async readSuccessOnlyRaw(initOverrides?: RequestInit + | InitOverrideFunction): Promise { + // Build path with path parameters + const urlPath = `/success`; + // Build query parameters + const queryParameters: HTTPQuery = {}; + // Build headers + const headerParameters: Record = { + }; + + // Make request + const response = await this.request({ + path: urlPath, + method: 'GET', + headers: headerParameters, + query: queryParameters, + body: undefined, + }, initOverrides); + + // Handle responses + if (!(response.status >= 200 && response.status < 300)) { + const errorRaw = response.clone(); + { + let errorValue: Promise | undefined; + throw new ReadSuccessOnlyError(response, { kind: 'unexpected', status: response.status, raw: response, value: () => errorValue ??= errorRaw.clone().blob() }); + } + } + if (response.status === 204) { + return new VoidApiResponse(response) as VoidApiResponse & { status: 204 }; + } + else { + return new VoidApiResponse(response) as VoidApiResponse & HttpSuccessResponse; + } + } + + async readSuccessOnly(initOverrides?: RequestInit | InitOverrideFunction): Promise { + const response = await this.readSuccessOnlyRaw(initOverrides); + return await response.value(); + } + + async readWildcardsRaw(initOverrides?: RequestInit + | InitOverrideFunction): Promise { + // Build path with path parameters + const urlPath = `/wildcards`; + // Build query parameters + const queryParameters: HTTPQuery = {}; + // Build headers + const headerParameters: Record = { + }; + + // Make request + const response = await this.request({ + path: urlPath, + method: 'GET', + headers: headerParameters, + query: queryParameters, + body: undefined, + }, initOverrides); + + // Handle responses + if (!(response.status >= 200 && response.status < 300)) { + const errorRaw = response.clone(); + if (response.status >= 400 && response.status < 500) { + let errorValue: Promise | undefined; + throw new ReadWildcardsError(response, { kind: 'clientError', status: response.status, raw: response, value: () => errorValue ??= new TextApiResponse(errorRaw.clone()).value() }); + } + else if (response.status >= 500 && response.status < 600) { + let errorValue: Promise | undefined; + throw new ReadWildcardsError(response, { kind: 'serverError', status: response.status, raw: response, value: () => errorValue ??= new BlobApiResponse(errorRaw.clone()).value() }); + } + else { + let errorValue: Promise | undefined; + throw new ReadWildcardsError(response, { kind: 'unexpected', status: response.status, raw: response, value: () => errorValue ??= errorRaw.clone().blob() }); + } + } + if (response.status >= 200 && response.status < 300) { + return new VoidApiResponse(response) as VoidApiResponse & { status: number }; + } + else { + return new VoidApiResponse(response) as VoidApiResponse & HttpSuccessResponse; + } + } + + async readWildcards(initOverrides?: RequestInit | InitOverrideFunction): Promise { + const response = await this.readWildcardsRaw(initOverrides); + return await response.value(); + } +} diff --git a/tests/golden/typescript/typescript-fetch/response-dispatch/apis/index.ts.golden b/tests/golden/typescript/typescript-fetch/response-dispatch/apis/index.ts.golden new file mode 100644 index 000000000..04c4a9c05 --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/response-dispatch/apis/index.ts.golden @@ -0,0 +1,26 @@ +/** + * @generated by openapi-nexus. Do not edit. + * + * TypeScript Response Dispatch — 1.0.0 + */ +export type { + ReadDefaultRawResponse, + ReadDefaultErrorDetail, + ReadErrorOnlyRawResponse, + ReadErrorOnlyErrorDetail, + ReadMixedRawResponse, + ReadMixedErrorDetail, + ReadSuccessOnlyRawResponse, + ReadSuccessOnlyErrorDetail, + ReadWildcardsRawResponse, + ReadWildcardsErrorDetail, + ResponsesApiInterface, +} from './ResponsesApi'; +export { + ResponsesApi, + ReadDefaultError, + ReadErrorOnlyError, + ReadMixedError, + ReadSuccessOnlyError, + ReadWildcardsError, +} from './ResponsesApi'; diff --git a/tests/golden/typescript/typescript-fetch/response-dispatch/index.ts.golden b/tests/golden/typescript/typescript-fetch/response-dispatch/index.ts.golden new file mode 100644 index 000000000..1200d8618 --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/response-dispatch/index.ts.golden @@ -0,0 +1,8 @@ +/** + * @generated by openapi-nexus. Do not edit. + * + * TypeScript Response Dispatch — 1.0.0 + */ +export * from './runtime/runtime'; +export * from './apis'; +export * from './models'; diff --git a/tests/golden/typescript/typescript-fetch/response-dispatch/models/Detail.ts.golden b/tests/golden/typescript/typescript-fetch/response-dispatch/models/Detail.ts.golden new file mode 100644 index 000000000..e64dbc17d --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/response-dispatch/models/Detail.ts.golden @@ -0,0 +1,8 @@ +/** + * @generated by openapi-nexus. Do not edit. + * + * TypeScript Response Dispatch — 1.0.0 + */ +export interface Detail { + readonly error_message: string; +} diff --git a/tests/golden/typescript/typescript-fetch/response-dispatch/models/index.ts.golden b/tests/golden/typescript/typescript-fetch/response-dispatch/models/index.ts.golden new file mode 100644 index 000000000..bf03fe275 --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/response-dispatch/models/index.ts.golden @@ -0,0 +1,6 @@ +/** + * @generated by openapi-nexus. Do not edit. + * + * TypeScript Response Dispatch — 1.0.0 + */ +export type { Detail } from './Detail'; diff --git a/tests/golden/typescript/typescript-fetch/response-dispatch/package.json.golden b/tests/golden/typescript/typescript-fetch/response-dispatch/package.json.golden new file mode 100644 index 000000000..59d6da387 --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/response-dispatch/package.json.golden @@ -0,0 +1,26 @@ +{ + "description": "TypeScript client for TypeScript Response Dispatch", + "exports": { + ".": { + "default": "./dist/index.js", + "types": "./dist/index.d.ts" + } + }, + "files": [ + "dist" + ], + "keywords": [ + "openapi", + "api-client", + "typescript", + "generated" + ], + "main": "./dist/index.js", + "name": "type-script-response-dispatch", + "scripts": { + "build": "tsc" + }, + "type": "module", + "types": "./dist/index.d.ts", + "version": "1.0.0" +} \ No newline at end of file diff --git a/tests/golden/typescript/typescript-fetch/response-dispatch/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/response-dispatch/runtime/runtime.ts.golden new file mode 100644 index 000000000..62758288d --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/response-dispatch/runtime/runtime.ts.golden @@ -0,0 +1,490 @@ +/** + * @generated by openapi-nexus. Do not edit. + * + * TypeScript Response Dispatch — 1.0.0 + */ +export const BASE_PATH = "http://localhost".replace(/\/+$/, ""); + +export interface ConfigurationParameters { + basePath?: string; // override base path + fetchApi?: FetchAPI; // override for fetch implementation + middleware?: Middleware[]; // middleware to apply before/after fetch requests + queryParamsStringify?: (params: HTTPQuery) => string; // stringify function for query strings + username?: string | (() => string | Promise); // parameter for basic security + password?: string | (() => string | Promise); // parameter for basic security + apiKey?: string | Promise | ((name: string) => string | Promise); // parameter for apiKey security + accessToken?: string | Promise | ((name?: string, scopes?: string[]) => string | Promise); // parameter for oauth2 security + headers?: HTTPHeaders; //header params we want to use on every request + credentials?: RequestCredentials; //value for the credentials param we want to use on each request +} + +export class Configuration { + private configuration: ConfigurationParameters; + + constructor(configuration: ConfigurationParameters = {}) { + this.configuration = configuration; + } + + set config(configuration: ConfigurationParameters) { + this.configuration = configuration; + } + + get basePath(): string { + return this.configuration.basePath != null ? this.configuration.basePath : BASE_PATH; + } + + get fetchApi(): FetchAPI | undefined { + return this.configuration.fetchApi; + } + + get middleware(): Middleware[] { + return this.configuration.middleware || []; + } + + get queryParamsStringify(): (params: HTTPQuery) => string { + return this.configuration.queryParamsStringify || querystring; + } + + get username(): (() => string | Promise) | undefined { + const username = this.configuration.username; + if (username) { + return typeof username === 'function' ? username : async () => username; + } + return undefined; + } + + get password(): (() => string | Promise) | undefined { + const password = this.configuration.password; + if (password) { + return typeof password === 'function' ? password : async () => password; + } + return undefined; + } + + get apiKey(): ((name: string) => string | Promise) | undefined { + const apiKey = this.configuration.apiKey; + if (apiKey) { + return typeof apiKey === 'function' ? apiKey : () => apiKey; + } + return undefined; + } + + get accessToken(): ((name?: string, scopes?: string[]) => string | Promise) | undefined { + const accessToken = this.configuration.accessToken; + if (accessToken) { + return typeof accessToken === 'function' ? accessToken : async () => accessToken; + } + return undefined; + } + + get headers(): HTTPHeaders | undefined { + return this.configuration.headers; + } + + get credentials(): RequestCredentials | undefined { + return this.configuration.credentials; + } +} + +export const DefaultConfig = new Configuration(); + +/** + * This is the base class for all generated API classes. + */ +export class BaseAPI { + + private static readonly jsonRegex = new RegExp('^(:?application/json|[^;/ \t]+/[^;/ \t]+[+]json)[ \t]*(:?;.*)?$', 'i'); + protected configuration: Configuration; + private middleware: Middleware[]; + + constructor(configuration: Configuration = DefaultConfig) { + this.configuration = configuration; + this.middleware = configuration.middleware; + } + + withMiddleware(this: T, ...middlewares: Middleware[]) { + const next = this.clone(); + next.middleware = next.middleware.concat(...middlewares); + return next; + } + + withPreMiddleware(this: T, ...preMiddlewares: Array) { + const middlewares = preMiddlewares.map((pre) => ({ pre })); + return this.withMiddleware(...middlewares); + } + + withPostMiddleware(this: T, ...postMiddlewares: Array) { + const middlewares = postMiddlewares.map((post) => ({ post })); + return this.withMiddleware(...middlewares); + } + + /** + * Check if the given MIME is a JSON MIME. + * JSON MIME examples: + * application/json + * application/json; charset=UTF8 + * APPLICATION/JSON + * application/vnd.company+json + * @param mime - MIME (Multipurpose Internet Mail Extensions) + * @return True if the given MIME is JSON, false otherwise. + */ + protected isJsonMime(mime: string | null | undefined): boolean { + if (!mime) { + return false; + } + return BaseAPI.jsonRegex.test(mime); + } + + protected async request(context: RequestOpts, initOverrides?: RequestInit | InitOverrideFunction): Promise { + const { url, init } = await this.createFetchParams(context, initOverrides); + return await this.fetchApi(url, init); + } + + private async createFetchParams(context: RequestOpts, initOverrides?: RequestInit | InitOverrideFunction) { + let url = this.configuration.basePath + context.path; + if (context.query !== undefined && Object.keys(context.query).length !== 0) { + // only add the querystring to the URL if there are query parameters. + // this is done to avoid urls ending with a "?" character which buggy webservers + // do not handle correctly sometimes. + url += '?' + this.configuration.queryParamsStringify(context.query); + } + + const headers = Object.assign({}, this.configuration.headers, context.headers); + Object.keys(headers).forEach(key => headers[key] === undefined ? delete headers[key] : {}); + + const token = await this.configuration.accessToken?.(); + if (token) { + headers['Authorization'] = `Bearer ${token}`; + } + + if (!headers['Authorization']) { + const username = await this.configuration.username?.(); + const password = await this.configuration.password?.(); + if (username && password) { + headers['Authorization'] = `Basic ${btoa(`${username}:${password}`)}`; + } + } + + const initOverrideFn = + typeof initOverrides === "function" + ? initOverrides + : async () => initOverrides; + + const initParams: HTTPRequestInit = { + method: context.method, + headers, + body: context.body, + credentials: this.configuration.credentials, + signal: context.signal, + }; + + const overriddenInit = { + ...initParams, + ...(await initOverrideFn({ + init: initParams, + context, + })) + }; + + let body: BodyInit | null | undefined; + if (isFormData(overriddenInit.body) + || (overriddenInit.body instanceof URLSearchParams) + || isBlob(overriddenInit.body)) { + body = overriddenInit.body; + } else if (this.isJsonMime(headers['Content-Type'])) { + body = JSON.stringify(overriddenInit.body); + } else { + body = overriddenInit.body as BodyInit | null | undefined; + } + + const init: RequestInit = { + ...overriddenInit, + body + }; + + return { url, init }; + } + + private fetchApi = async (url: string, init: RequestInit) => { + let fetchParams = { url, init }; + for (const middleware of this.middleware) { + if (middleware.pre) { + fetchParams = await middleware.pre({ + fetch: this.fetchApi, + ...fetchParams, + }) || fetchParams; + } + } + let response: Response | undefined = undefined; + try { + response = await (this.configuration.fetchApi || fetch)(fetchParams.url, fetchParams.init); + } catch (e) { + for (const middleware of this.middleware) { + if (middleware.onError) { + response = await middleware.onError({ + fetch: this.fetchApi, + url: fetchParams.url, + init: fetchParams.init, + error: e, + response: response ? response.clone() : undefined, + }) || response; + } + } + if (response === undefined) { + if (e instanceof Error) { + throw new FetchError(e, 'The request failed and the interceptors did not return an alternative response'); + } else { + throw e; + } + } + } + for (const middleware of this.middleware) { + if (middleware.post) { + response = await middleware.post({ + fetch: this.fetchApi, + url: fetchParams.url, + init: fetchParams.init, + response: response.clone(), + }) || response; + } + } + return response; + } + + /** + * Create a shallow clone of `this` by constructing a new instance + * and then shallow cloning data members. + */ + private clone(this: T): T { + const constructor = this.constructor as new (configuration: Configuration) => T; + const next = new constructor(this.configuration); + next.middleware = this.middleware.slice(); + return next; + } +} + +function isBlob(value: unknown): value is Blob { + return typeof Blob !== 'undefined' && value instanceof Blob; +} + +function isFormData(value: unknown): value is FormData { + return typeof FormData !== "undefined" && value instanceof FormData; +} + +export class ResponseError extends Error { + override name = "ResponseError" as const; + public response: Response; + + constructor(response: Response, msg?: string) { + super(msg); + this.response = response; + } +} + +export class FetchError extends Error { + override name = "FetchError" as const; + public cause: Error; + + constructor(cause: Error, msg?: string) { + super(msg); + this.cause = cause; + } +} + +export class RequiredError extends Error { + override name = "RequiredError" as const; + public field: string; + + constructor(field: string, msg?: string) { + super(msg); + this.field = field; + } +} + +export type FetchAPI = WindowOrWorkerGlobalScope['fetch']; + +export type Json = unknown; +export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; +export type HTTPHeaders = { [key: string]: string }; +export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; +export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; + +export type InitOverrideFunction = (requestContext: { init: HTTPRequestInit, context: RequestOpts }) => Promise + +export interface FetchParams { + url: string; + init: RequestInit; +} + +export interface RequestOpts { + path: string; + method: HTTPMethod; + headers: HTTPHeaders; + query?: HTTPQuery; + body?: HTTPBody; + signal?: AbortSignal; +} + +export function querystring(params: HTTPQuery, prefix: string = ''): string { + return Object.keys(params) + .map(key => querystringSingleKey(key, params[key], prefix)) + .filter(part => part.length > 0) + .join('&'); +} + +function querystringSingleKey(key: string, value: string | number | null | undefined | boolean | Array | Set | HTTPQuery, keyPrefix: string = ''): string { + const fullKey = keyPrefix + (keyPrefix.length ? `[${key}]` : key); + if (value instanceof Array) { + const multiValue = value.map(singleValue => encodeURIComponent(String(singleValue))) + .join(`&${encodeURIComponent(fullKey)}=`); + return `${encodeURIComponent(fullKey)}=${multiValue}`; + } + if (value instanceof Set) { + const valueAsArray = Array.from(value); + return querystringSingleKey(key, valueAsArray, keyPrefix); + } + if (value instanceof Date) { + return `${encodeURIComponent(fullKey)}=${encodeURIComponent(value.toISOString())}`; + } + if (value instanceof Object) { + return querystring(value as HTTPQuery, fullKey); + } + return `${encodeURIComponent(fullKey)}=${encodeURIComponent(String(value))}`; +} + +export function exists(json: Record, key: string): boolean { + const value = json[key]; + return value !== null && value !== undefined; +} + +export function mapValues(data: Record, fn: (item: T) => U): Record { + const result: Record = {}; + for (const key of Object.keys(data)) { + result[key] = fn(data[key]); + } + return result; +} + +export function canConsumeForm(consumes: Consume[]): boolean { + for (const consume of consumes) { + if ('multipart/form-data' === consume.contentType) { + return true; + } + } + return false; +} + +export interface Consume { + contentType: string; +} + +export interface RequestContext { + fetch: FetchAPI; + url: string; + init: RequestInit; +} + +export interface ResponseContext { + fetch: FetchAPI; + url: string; + init: RequestInit; + response: Response; +} + +export interface ErrorContext { + fetch: FetchAPI; + url: string; + init: RequestInit; + error: unknown; + response?: Response; +} + +export interface Middleware { + pre?(context: RequestContext): Promise; + post?(context: ResponseContext): Promise; + onError?(context: ErrorContext): Promise; +} + +export interface ApiResponse { + raw: Response; + value(): Promise; +} + +export type HttpSuccessStatus = + | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 + | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 + | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 + | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 + | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 + | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 + | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 + | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 + | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 + | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299; + +export type HttpSuccessResponse = { status: HttpSuccessStatus }; + +export interface ResponseTransformer { + (json: unknown): T; +} + +class ApiResponseBase { + public readonly status: number; + public readonly ok: boolean; + public readonly statusText: string; + public readonly headers: Headers; + public readonly raw: Response; + + constructor(raw: Response) { + this.raw = raw; + this.status = raw.status; + this.ok = raw.ok; + this.statusText = raw.statusText; + this.headers = raw.headers; + } +} + +export class JSONApiResponse extends ApiResponseBase { + private transformer: ResponseTransformer; + + constructor(raw: Response, transformer: ResponseTransformer = (jsonValue: unknown) => jsonValue as T) { + super(raw); + this.transformer = transformer; + } + + async value(): Promise { + return this.transformer(await this.raw.json()); + } +} + +export class VoidApiResponse extends ApiResponseBase { + constructor(raw: Response) { + super(raw); + } + + async value(): Promise { + return undefined; + } +} + +export class BlobApiResponse extends ApiResponseBase { + constructor(raw: Response) { + super(raw); + } + + async value(): Promise { + return await this.raw.blob(); + }; +} + +export class TextApiResponse extends ApiResponseBase { + constructor(raw: Response) { + super(raw); + } + + async value(): Promise { + return await this.raw.text(); + }; +} diff --git a/tests/golden/typescript/typescript-fetch/response-dispatch/tsconfig.esm.json.golden b/tests/golden/typescript/typescript-fetch/response-dispatch/tsconfig.esm.json.golden new file mode 100644 index 000000000..bb8350cb6 --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/response-dispatch/tsconfig.esm.json.golden @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "module": "ES2020", + "outDir": "dist/esm" + }, + "extends": "./tsconfig.json" +} \ No newline at end of file diff --git a/tests/golden/typescript/typescript-fetch/response-dispatch/tsconfig.json.golden b/tests/golden/typescript/typescript-fetch/response-dispatch/tsconfig.json.golden new file mode 100644 index 000000000..4b3a8cf7f --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/response-dispatch/tsconfig.json.golden @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "lib": [ + "ES2020", + "DOM" + ], + "module": "ES2020", + "moduleResolution": "bundler", + "outDir": "./dist", + "resolveJsonModule": true, + "rootDir": "./", + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "target": "ES2020", + "typeRoots": [ + "node_modules/@types" + ] + }, + "exclude": [ + "dist", + "node_modules" + ], + "include": [ + "**/*.ts" + ] +} \ No newline at end of file diff --git a/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-response-dispatch/README.md.golden b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-response-dispatch/README.md.golden new file mode 100644 index 000000000..3e33d7b90 --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-response-dispatch/README.md.golden @@ -0,0 +1,248 @@ +# type-script-response-dispatch + +Generated API client + +**Version:** 1.0.0 + +## Overview + +This package provides a TypeScript/JavaScript client for the TypeScript Response Dispatch API. It uses the native [Fetch API](https://fetch.spec.whatwg.org/) for HTTP requests and works in both Node.js and browser environments. + +## Features + +- ✨ **Type-safe** - Full TypeScript support with generated types +- 🚀 **Modern** - Uses native Fetch API, no external HTTP dependencies +- 🔧 **Configurable** - Flexible configuration options +- 🎯 **Middleware** - Support for request/response interceptors +- 📦 **Tree-shakeable** - Import only what you need +- 🌐 **Universal** - Works in Node.js and browsers + +## Installation + +### From npm (published package) + +```bash +npm install type-script-response-dispatch +``` + +### From local path (development) + +Add the package to your `package.json` using the `file:` protocol: + +```json +{ + "dependencies": { + "type-script-response-dispatch": "file:../../path/to/generated/package" + } +} +``` + +Then run: + +```bash +npm install +``` + +## Quick Start + +```typescript +import { Configuration, ResponsesApi } from 'type-script-response-dispatch'; + +// Create a configuration +const config = new Configuration({ + basePath: 'https://api.example.com', + headers: { + 'Authorization': 'Bearer YOUR_TOKEN' + } +}); + +// Initialize the API client +const api = new ResponsesApi(config); + +// Make API calls +try { + const result = await api.someMethod(); + console.log(result); +} catch (error) { + console.error('API Error:', error); +} +``` + +## Configuration + +The `Configuration` class accepts the following options: + +```typescript +interface ConfigurationParameters { + /** Base URL for API requests */ + basePath?: string; + + /** Custom fetch implementation */ + fetchApi?: typeof fetch; + + /** Request/response middleware */ + middleware?: Middleware[]; + + /** Custom query string serializer */ + queryParamsStringify?: (params: HTTPQuery) => string; + + /** Default headers for all requests */ + headers?: Record; + + /** Credentials mode for requests */ + credentials?: RequestCredentials; +} +``` + +### Example with custom configuration + +```typescript +const config = new Configuration({ + basePath: 'https://api.example.com', + headers: { + 'X-API-Key': 'your-api-key', + 'Content-Type': 'application/json' + }, + credentials: 'include' +}); +``` + +## Middleware + +Add custom middleware to intercept requests and responses: + +```typescript +import { Configuration, Middleware } from 'type-script-response-dispatch'; + +const loggingMiddleware: Middleware = { + pre: async (context) => { + console.log('Request:', context.url); + return context; + }, + post: async (context) => { + console.log('Response:', context.response.status); + return context.response; + }, + onError: async (context) => { + console.error('Error:', context.error); + return undefined; + } +}; + +const config = new Configuration({ + basePath: 'https://api.example.com', + middleware: [loggingMiddleware] +}); +``` + +## Error Handling + +The client throws typed errors for different failure scenarios: + +```typescript +import { ResponseError, FetchError, RequiredError } from 'type-script-response-dispatch'; + +try { + const result = await api.someMethod(); +} catch (error) { + if (error instanceof ResponseError) { + // HTTP error response (4xx, 5xx) + console.error('HTTP Error:', error.response.status); + } else if (error instanceof FetchError) { + // Network or fetch error + console.error('Network Error:', error.cause); + } else if (error instanceof RequiredError) { + // Missing required parameter + console.error('Missing field:', error.field); + } +} +``` + +## API Reference + +This package exports the following: + +- **Configuration** - Client configuration class +- **BaseAPI** - Base class for all API clients +- **API Classes** - Generated API client classes (e.g., `UserApi`, `PostApi`) +- **Models** - Generated TypeScript interfaces for request/response types +- **Errors** - `ResponseError`, `FetchError`, `RequiredError` +- **Types** - TypeScript type definitions + +## Development + +### Building + +To build the package: + +```bash +npm install +npm run build +``` + +This will compile TypeScript to JavaScript in the `dist/` directory. + +### Building for ESM + +To build ES modules: + +```bash +npm run build +``` + +## TypeScript Support + +This package includes TypeScript type definitions. No additional `@types` package is needed. + +### TypeScript Configuration + +This package works with standard TypeScript configurations. If you're using a bundler-based setup, you may want to configure: + +```json +{ + "compilerOptions": { + "moduleResolution": "bundler" + } +} +``` + +### Type Imports + +```typescript +import type { User, CreateUserRequest } from 'type-script-response-dispatch'; + +const user: User = { + id: 1, + name: 'John Doe', + email: 'john@example.com' +}; +``` + +## Browser Support + +This package uses the native Fetch API, which is supported in: + +- Chrome 42+ +- Firefox 39+ +- Safari 10.1+ +- Edge 14+ +- Node.js 18+ (native fetch) +- Node.js <18 (with `node-fetch` polyfill) + +For older browsers, you may need to include a fetch polyfill. + +## License + +This is an auto-generated API client. Please refer to your API documentation for license information. + +## Support + +For issues related to the API itself, please contact the API provider. + +For issues with this generated client, please check the OpenAPI specification used to generate it. + +--- + +**Generated by OpenAPI Generator** + +API Version: 1.0.0 diff --git a/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-response-dispatch/apis/ResponsesApi.ts.golden b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-response-dispatch/apis/ResponsesApi.ts.golden new file mode 100644 index 000000000..a2e7f711a --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-response-dispatch/apis/ResponsesApi.ts.golden @@ -0,0 +1,368 @@ +/** + * @generated by openapi-nexus. Do not edit. + * + * TypeScript Response Dispatch — 1.0.0 + */ +import type { Detail, Detail$Wire } from '../models/Detail'; +import { detailFromJSON } from '../models/Detail'; +import type { Configuration, HTTPQuery, HttpSuccessResponse, InitOverrideFunction } from '../runtime/runtime'; +import { BaseAPI, BlobApiResponse, DefaultConfig, JSONApiResponse, ResponseError, TextApiResponse, VoidApiResponse } from '../runtime/runtime'; + +export type ReadDefaultRawResponse = VoidApiResponse & HttpSuccessResponse; + +export type ReadErrorOnlyRawResponse = VoidApiResponse & HttpSuccessResponse; + +export type ReadMixedRawResponse = + | JSONApiResponse & { status: 201 } + | TextApiResponse & { status: 202 } + | VoidApiResponse & { status: 204 } + | BlobApiResponse & { status: 206 } + | JSONApiResponse & { status: number } + | JSONApiResponse & HttpSuccessResponse; + +export type ReadSuccessOnlyRawResponse = + | VoidApiResponse & { status: 204 } + | VoidApiResponse & HttpSuccessResponse; + +export type ReadWildcardsRawResponse = + | VoidApiResponse & { status: number } + | VoidApiResponse & HttpSuccessResponse; + + +export type ReadDefaultErrorDetail = + { kind: 'default'; status: number; raw: Response; value(): Promise } + | { kind: 'unexpected'; status: number; raw: Response; value(): Promise }; + +export class ReadDefaultError extends ResponseError { + readonly detail: ReadDefaultErrorDetail; + + constructor(response: Response, detail: ReadDefaultErrorDetail, msg = 'Response returned an error code') { + super(response, msg); + this.detail = detail; + } +} + +export type ReadErrorOnlyErrorDetail = + { kind: 'badRequest'; status: 400; raw: Response; value(): Promise } + | { kind: 'unexpected'; status: number; raw: Response; value(): Promise }; + +export class ReadErrorOnlyError extends ResponseError { + readonly detail: ReadErrorOnlyErrorDetail; + + constructor(response: Response, detail: ReadErrorOnlyErrorDetail, msg = 'Response returned an error code') { + super(response, msg); + this.detail = detail; + } +} + +export type ReadMixedErrorDetail = + { kind: 'badRequest'; status: 400; raw: Response; value(): Promise } + | { kind: 'unauthorized'; status: 401; raw: Response; value(): Promise } + | { kind: 'conflict'; status: 409; raw: Response; value(): Promise } + | { kind: 'status410'; status: 410; raw: Response; value(): Promise } + | { kind: 'status415'; status: 415; raw: Response; value(): Promise } + | { kind: 'serverError'; status: number; raw: Response; value(): Promise } + | { kind: 'default'; status: number; raw: Response; value(): Promise } + | { kind: 'unexpected'; status: number; raw: Response; value(): Promise }; + +export class ReadMixedError extends ResponseError { + readonly detail: ReadMixedErrorDetail; + + constructor(response: Response, detail: ReadMixedErrorDetail, msg = 'Response returned an error code') { + super(response, msg); + this.detail = detail; + } +} + +export type ReadSuccessOnlyErrorDetail = + { kind: 'unexpected'; status: number; raw: Response; value(): Promise }; + +export class ReadSuccessOnlyError extends ResponseError { + readonly detail: ReadSuccessOnlyErrorDetail; + + constructor(response: Response, detail: ReadSuccessOnlyErrorDetail, msg = 'Response returned an error code') { + super(response, msg); + this.detail = detail; + } +} + +export type ReadWildcardsErrorDetail = + { kind: 'clientError'; status: number; raw: Response; value(): Promise } + | { kind: 'serverError'; status: number; raw: Response; value(): Promise } + | { kind: 'unexpected'; status: number; raw: Response; value(): Promise }; + +export class ReadWildcardsError extends ResponseError { + readonly detail: ReadWildcardsErrorDetail; + + constructor(response: Response, detail: ReadWildcardsErrorDetail, msg = 'Response returned an error code') { + super(response, msg); + this.detail = detail; + } +} + + +export interface ResponsesApiInterface { + readDefaultRaw: (initOverrides?: RequestInit | InitOverrideFunction) => Promise; + readDefault: (initOverrides?: RequestInit | InitOverrideFunction) => Promise; + readErrorOnlyRaw: (initOverrides?: RequestInit | InitOverrideFunction) => Promise; + readErrorOnly: (initOverrides?: RequestInit | InitOverrideFunction) => Promise; + readMixedRaw: (initOverrides?: RequestInit | InitOverrideFunction) => Promise; + readMixed: (initOverrides?: RequestInit | InitOverrideFunction) => Promise; + readSuccessOnlyRaw: (initOverrides?: RequestInit | InitOverrideFunction) => Promise; + readSuccessOnly: (initOverrides?: RequestInit | InitOverrideFunction) => Promise; + readWildcardsRaw: (initOverrides?: RequestInit | InitOverrideFunction) => Promise; + readWildcards: (initOverrides?: RequestInit | InitOverrideFunction) => Promise; +} + +export class ResponsesApi extends BaseAPI implements ResponsesApiInterface { + /** + * Initialize the API client + */ + constructor(configuration?: Configuration) { + super(configuration ?? DefaultConfig); + } + + async readDefaultRaw(initOverrides?: RequestInit + | InitOverrideFunction): Promise { + // Build path with path parameters + const urlPath = `/default`; + // Build query parameters + const queryParameters: HTTPQuery = {}; + // Build headers + const headerParameters: Record = { + }; + + // Make request + const response = await this.request({ + path: urlPath, + method: 'GET', + headers: headerParameters, + query: queryParameters, + body: undefined, + }, initOverrides); + + // Handle responses + if (!(response.status >= 200 && response.status < 300)) { + const errorRaw = response.clone(); + { + let errorValue: Promise | undefined; + throw new ReadDefaultError(response, { kind: 'default', status: response.status, raw: response, value: () => errorValue ??= new JSONApiResponse(errorRaw.clone(), (json) => detailFromJSON(json as Detail$Wire)).value() }); + } + } + return new VoidApiResponse(response) as VoidApiResponse & HttpSuccessResponse; + } + + async readDefault(initOverrides?: RequestInit | InitOverrideFunction): Promise { + const response = await this.readDefaultRaw(initOverrides); + return await response.value(); + } + + async readErrorOnlyRaw(initOverrides?: RequestInit + | InitOverrideFunction): Promise { + // Build path with path parameters + const urlPath = `/error`; + // Build query parameters + const queryParameters: HTTPQuery = {}; + // Build headers + const headerParameters: Record = { + }; + + // Make request + const response = await this.request({ + path: urlPath, + method: 'GET', + headers: headerParameters, + query: queryParameters, + body: undefined, + }, initOverrides); + + // Handle responses + if (!(response.status >= 200 && response.status < 300)) { + const errorRaw = response.clone(); + if (response.status === 400) { + let errorValue: Promise | undefined; + throw new ReadErrorOnlyError(response, { kind: 'badRequest', status: response.status as 400, raw: response, value: () => errorValue ??= new TextApiResponse(errorRaw.clone()).value() }); + } + else { + let errorValue: Promise | undefined; + throw new ReadErrorOnlyError(response, { kind: 'unexpected', status: response.status, raw: response, value: () => errorValue ??= errorRaw.clone().blob() }); + } + } + return new VoidApiResponse(response) as VoidApiResponse & HttpSuccessResponse; + } + + async readErrorOnly(initOverrides?: RequestInit | InitOverrideFunction): Promise { + const response = await this.readErrorOnlyRaw(initOverrides); + return await response.value(); + } + + async readMixedRaw(initOverrides?: RequestInit + | InitOverrideFunction): Promise { + // Build path with path parameters + const urlPath = `/mixed`; + // Build query parameters + const queryParameters: HTTPQuery = {}; + // Build headers + const headerParameters: Record = { + }; + + // Make request + const response = await this.request({ + path: urlPath, + method: 'GET', + headers: headerParameters, + query: queryParameters, + body: undefined, + }, initOverrides); + + // Handle responses + if (!(response.status >= 200 && response.status < 300)) { + const errorRaw = response.clone(); + if (response.status === 400) { + let errorValue: Promise | undefined; + throw new ReadMixedError(response, { kind: 'badRequest', status: response.status as 400, raw: response, value: () => errorValue ??= new JSONApiResponse(errorRaw.clone(), (json) => detailFromJSON(json as Detail$Wire)).value() }); + } + else if (response.status === 401) { + let errorValue: Promise | undefined; + throw new ReadMixedError(response, { kind: 'unauthorized', status: response.status as 401, raw: response, value: () => errorValue ??= new JSONApiResponse(errorRaw.clone(), (json) => (json as Detail$Wire[]).map(detailFromJSON)).value() }); + } + else if (response.status === 409) { + let errorValue: Promise | undefined; + throw new ReadMixedError(response, { kind: 'conflict', status: response.status as 409, raw: response, value: () => errorValue ??= new TextApiResponse(errorRaw.clone()).value() }); + } + else if (response.status === 410) { + let errorValue: Promise | undefined; + throw new ReadMixedError(response, { kind: 'status410', status: response.status as 410, raw: response, value: () => errorValue ??= new VoidApiResponse(errorRaw.clone()).value() }); + } + else if (response.status === 415) { + let errorValue: Promise | undefined; + throw new ReadMixedError(response, { kind: 'status415', status: response.status as 415, raw: response, value: () => errorValue ??= new BlobApiResponse(errorRaw.clone()).value() }); + } + else if (response.status >= 500 && response.status < 600) { + let errorValue: Promise | undefined; + throw new ReadMixedError(response, { kind: 'serverError', status: response.status, raw: response, value: () => errorValue ??= new JSONApiResponse(errorRaw.clone(), (json) => detailFromJSON(json as Detail$Wire)).value() }); + } + else { + let errorValue: Promise | undefined; + throw new ReadMixedError(response, { kind: 'default', status: response.status, raw: response, value: () => errorValue ??= new JSONApiResponse(errorRaw.clone()).value() }); + } + } + if (response.status === 201) { + return new JSONApiResponse(response, (json) => detailFromJSON(json as Detail$Wire)) as JSONApiResponse & { status: 201 }; + } + else if (response.status === 202) { + return new TextApiResponse(response) as TextApiResponse & { status: 202 }; + } + else if (response.status === 204) { + return new VoidApiResponse(response) as VoidApiResponse & { status: 204 }; + } + else if (response.status === 206) { + return new BlobApiResponse(response) as BlobApiResponse & { status: 206 }; + } + else if (response.status >= 200 && response.status < 300) { + return new JSONApiResponse(response, (json) => (json as Detail$Wire[]).map(detailFromJSON)) as JSONApiResponse & { status: number }; + } + else { + return new JSONApiResponse(response) as JSONApiResponse &HttpSuccessResponse; + } + } + + async readMixed(initOverrides?: RequestInit | InitOverrideFunction): Promise { + const response = await this.readMixedRaw(initOverrides); + return await response.value() as Detail | string | Blob | Detail[]; + } + + async readSuccessOnlyRaw(initOverrides?: RequestInit + | InitOverrideFunction): Promise { + // Build path with path parameters + const urlPath = `/success`; + // Build query parameters + const queryParameters: HTTPQuery = {}; + // Build headers + const headerParameters: Record = { + }; + + // Make request + const response = await this.request({ + path: urlPath, + method: 'GET', + headers: headerParameters, + query: queryParameters, + body: undefined, + }, initOverrides); + + // Handle responses + if (!(response.status >= 200 && response.status < 300)) { + const errorRaw = response.clone(); + { + let errorValue: Promise | undefined; + throw new ReadSuccessOnlyError(response, { kind: 'unexpected', status: response.status, raw: response, value: () => errorValue ??= errorRaw.clone().blob() }); + } + } + if (response.status === 204) { + return new VoidApiResponse(response) as VoidApiResponse & { status: 204 }; + } + else { + return new VoidApiResponse(response) as VoidApiResponse & HttpSuccessResponse; + } + } + + async readSuccessOnly(initOverrides?: RequestInit | InitOverrideFunction): Promise { + const response = await this.readSuccessOnlyRaw(initOverrides); + return await response.value(); + } + + async readWildcardsRaw(initOverrides?: RequestInit + | InitOverrideFunction): Promise { + // Build path with path parameters + const urlPath = `/wildcards`; + // Build query parameters + const queryParameters: HTTPQuery = {}; + // Build headers + const headerParameters: Record = { + }; + + // Make request + const response = await this.request({ + path: urlPath, + method: 'GET', + headers: headerParameters, + query: queryParameters, + body: undefined, + }, initOverrides); + + // Handle responses + if (!(response.status >= 200 && response.status < 300)) { + const errorRaw = response.clone(); + if (response.status >= 400 && response.status < 500) { + let errorValue: Promise | undefined; + throw new ReadWildcardsError(response, { kind: 'clientError', status: response.status, raw: response, value: () => errorValue ??= new TextApiResponse(errorRaw.clone()).value() }); + } + else if (response.status >= 500 && response.status < 600) { + let errorValue: Promise | undefined; + throw new ReadWildcardsError(response, { kind: 'serverError', status: response.status, raw: response, value: () => errorValue ??= new BlobApiResponse(errorRaw.clone()).value() }); + } + else { + let errorValue: Promise | undefined; + throw new ReadWildcardsError(response, { kind: 'unexpected', status: response.status, raw: response, value: () => errorValue ??= errorRaw.clone().blob() }); + } + } + if (response.status >= 200 && response.status < 300) { + return new VoidApiResponse(response) as VoidApiResponse & { status: number }; + } + else { + return new VoidApiResponse(response) as VoidApiResponse & HttpSuccessResponse; + } + } + + async readWildcards(initOverrides?: RequestInit | InitOverrideFunction): Promise { + const response = await this.readWildcardsRaw(initOverrides); + return await response.value(); + } +} diff --git a/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-response-dispatch/apis/index.ts.golden b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-response-dispatch/apis/index.ts.golden new file mode 100644 index 000000000..04c4a9c05 --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-response-dispatch/apis/index.ts.golden @@ -0,0 +1,26 @@ +/** + * @generated by openapi-nexus. Do not edit. + * + * TypeScript Response Dispatch — 1.0.0 + */ +export type { + ReadDefaultRawResponse, + ReadDefaultErrorDetail, + ReadErrorOnlyRawResponse, + ReadErrorOnlyErrorDetail, + ReadMixedRawResponse, + ReadMixedErrorDetail, + ReadSuccessOnlyRawResponse, + ReadSuccessOnlyErrorDetail, + ReadWildcardsRawResponse, + ReadWildcardsErrorDetail, + ResponsesApiInterface, +} from './ResponsesApi'; +export { + ResponsesApi, + ReadDefaultError, + ReadErrorOnlyError, + ReadMixedError, + ReadSuccessOnlyError, + ReadWildcardsError, +} from './ResponsesApi'; diff --git a/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-response-dispatch/index.ts.golden b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-response-dispatch/index.ts.golden new file mode 100644 index 000000000..1200d8618 --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-response-dispatch/index.ts.golden @@ -0,0 +1,8 @@ +/** + * @generated by openapi-nexus. Do not edit. + * + * TypeScript Response Dispatch — 1.0.0 + */ +export * from './runtime/runtime'; +export * from './apis'; +export * from './models'; diff --git a/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-response-dispatch/models/Detail.ts.golden b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-response-dispatch/models/Detail.ts.golden new file mode 100644 index 000000000..a78f972c5 --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-response-dispatch/models/Detail.ts.golden @@ -0,0 +1,24 @@ +/** + * @generated by openapi-nexus. Do not edit. + * + * TypeScript Response Dispatch — 1.0.0 + */ +export interface Detail$Wire { + readonly error_message: string; +} + +export interface Detail { + readonly errorMessage: string; +} + +export function detailFromJSON(json: Detail$Wire): Detail { + return { + errorMessage: json.error_message, + }; +} + +export function detailToJSON(value: Detail): Detail$Wire { + return { + error_message: value.errorMessage, + }; +} diff --git a/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-response-dispatch/models/index.ts.golden b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-response-dispatch/models/index.ts.golden new file mode 100644 index 000000000..0e6ddafa1 --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-response-dispatch/models/index.ts.golden @@ -0,0 +1,7 @@ +/** + * @generated by openapi-nexus. Do not edit. + * + * TypeScript Response Dispatch — 1.0.0 + */ +export type { Detail, Detail$Wire } from './Detail'; +export { detailFromJSON, detailToJSON } from './Detail'; diff --git a/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-response-dispatch/package.json.golden b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-response-dispatch/package.json.golden new file mode 100644 index 000000000..59d6da387 --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-response-dispatch/package.json.golden @@ -0,0 +1,26 @@ +{ + "description": "TypeScript client for TypeScript Response Dispatch", + "exports": { + ".": { + "default": "./dist/index.js", + "types": "./dist/index.d.ts" + } + }, + "files": [ + "dist" + ], + "keywords": [ + "openapi", + "api-client", + "typescript", + "generated" + ], + "main": "./dist/index.js", + "name": "type-script-response-dispatch", + "scripts": { + "build": "tsc" + }, + "type": "module", + "types": "./dist/index.d.ts", + "version": "1.0.0" +} \ No newline at end of file diff --git a/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-response-dispatch/runtime/runtime.ts.golden b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-response-dispatch/runtime/runtime.ts.golden new file mode 100644 index 000000000..62758288d --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-response-dispatch/runtime/runtime.ts.golden @@ -0,0 +1,490 @@ +/** + * @generated by openapi-nexus. Do not edit. + * + * TypeScript Response Dispatch — 1.0.0 + */ +export const BASE_PATH = "http://localhost".replace(/\/+$/, ""); + +export interface ConfigurationParameters { + basePath?: string; // override base path + fetchApi?: FetchAPI; // override for fetch implementation + middleware?: Middleware[]; // middleware to apply before/after fetch requests + queryParamsStringify?: (params: HTTPQuery) => string; // stringify function for query strings + username?: string | (() => string | Promise); // parameter for basic security + password?: string | (() => string | Promise); // parameter for basic security + apiKey?: string | Promise | ((name: string) => string | Promise); // parameter for apiKey security + accessToken?: string | Promise | ((name?: string, scopes?: string[]) => string | Promise); // parameter for oauth2 security + headers?: HTTPHeaders; //header params we want to use on every request + credentials?: RequestCredentials; //value for the credentials param we want to use on each request +} + +export class Configuration { + private configuration: ConfigurationParameters; + + constructor(configuration: ConfigurationParameters = {}) { + this.configuration = configuration; + } + + set config(configuration: ConfigurationParameters) { + this.configuration = configuration; + } + + get basePath(): string { + return this.configuration.basePath != null ? this.configuration.basePath : BASE_PATH; + } + + get fetchApi(): FetchAPI | undefined { + return this.configuration.fetchApi; + } + + get middleware(): Middleware[] { + return this.configuration.middleware || []; + } + + get queryParamsStringify(): (params: HTTPQuery) => string { + return this.configuration.queryParamsStringify || querystring; + } + + get username(): (() => string | Promise) | undefined { + const username = this.configuration.username; + if (username) { + return typeof username === 'function' ? username : async () => username; + } + return undefined; + } + + get password(): (() => string | Promise) | undefined { + const password = this.configuration.password; + if (password) { + return typeof password === 'function' ? password : async () => password; + } + return undefined; + } + + get apiKey(): ((name: string) => string | Promise) | undefined { + const apiKey = this.configuration.apiKey; + if (apiKey) { + return typeof apiKey === 'function' ? apiKey : () => apiKey; + } + return undefined; + } + + get accessToken(): ((name?: string, scopes?: string[]) => string | Promise) | undefined { + const accessToken = this.configuration.accessToken; + if (accessToken) { + return typeof accessToken === 'function' ? accessToken : async () => accessToken; + } + return undefined; + } + + get headers(): HTTPHeaders | undefined { + return this.configuration.headers; + } + + get credentials(): RequestCredentials | undefined { + return this.configuration.credentials; + } +} + +export const DefaultConfig = new Configuration(); + +/** + * This is the base class for all generated API classes. + */ +export class BaseAPI { + + private static readonly jsonRegex = new RegExp('^(:?application/json|[^;/ \t]+/[^;/ \t]+[+]json)[ \t]*(:?;.*)?$', 'i'); + protected configuration: Configuration; + private middleware: Middleware[]; + + constructor(configuration: Configuration = DefaultConfig) { + this.configuration = configuration; + this.middleware = configuration.middleware; + } + + withMiddleware(this: T, ...middlewares: Middleware[]) { + const next = this.clone(); + next.middleware = next.middleware.concat(...middlewares); + return next; + } + + withPreMiddleware(this: T, ...preMiddlewares: Array) { + const middlewares = preMiddlewares.map((pre) => ({ pre })); + return this.withMiddleware(...middlewares); + } + + withPostMiddleware(this: T, ...postMiddlewares: Array) { + const middlewares = postMiddlewares.map((post) => ({ post })); + return this.withMiddleware(...middlewares); + } + + /** + * Check if the given MIME is a JSON MIME. + * JSON MIME examples: + * application/json + * application/json; charset=UTF8 + * APPLICATION/JSON + * application/vnd.company+json + * @param mime - MIME (Multipurpose Internet Mail Extensions) + * @return True if the given MIME is JSON, false otherwise. + */ + protected isJsonMime(mime: string | null | undefined): boolean { + if (!mime) { + return false; + } + return BaseAPI.jsonRegex.test(mime); + } + + protected async request(context: RequestOpts, initOverrides?: RequestInit | InitOverrideFunction): Promise { + const { url, init } = await this.createFetchParams(context, initOverrides); + return await this.fetchApi(url, init); + } + + private async createFetchParams(context: RequestOpts, initOverrides?: RequestInit | InitOverrideFunction) { + let url = this.configuration.basePath + context.path; + if (context.query !== undefined && Object.keys(context.query).length !== 0) { + // only add the querystring to the URL if there are query parameters. + // this is done to avoid urls ending with a "?" character which buggy webservers + // do not handle correctly sometimes. + url += '?' + this.configuration.queryParamsStringify(context.query); + } + + const headers = Object.assign({}, this.configuration.headers, context.headers); + Object.keys(headers).forEach(key => headers[key] === undefined ? delete headers[key] : {}); + + const token = await this.configuration.accessToken?.(); + if (token) { + headers['Authorization'] = `Bearer ${token}`; + } + + if (!headers['Authorization']) { + const username = await this.configuration.username?.(); + const password = await this.configuration.password?.(); + if (username && password) { + headers['Authorization'] = `Basic ${btoa(`${username}:${password}`)}`; + } + } + + const initOverrideFn = + typeof initOverrides === "function" + ? initOverrides + : async () => initOverrides; + + const initParams: HTTPRequestInit = { + method: context.method, + headers, + body: context.body, + credentials: this.configuration.credentials, + signal: context.signal, + }; + + const overriddenInit = { + ...initParams, + ...(await initOverrideFn({ + init: initParams, + context, + })) + }; + + let body: BodyInit | null | undefined; + if (isFormData(overriddenInit.body) + || (overriddenInit.body instanceof URLSearchParams) + || isBlob(overriddenInit.body)) { + body = overriddenInit.body; + } else if (this.isJsonMime(headers['Content-Type'])) { + body = JSON.stringify(overriddenInit.body); + } else { + body = overriddenInit.body as BodyInit | null | undefined; + } + + const init: RequestInit = { + ...overriddenInit, + body + }; + + return { url, init }; + } + + private fetchApi = async (url: string, init: RequestInit) => { + let fetchParams = { url, init }; + for (const middleware of this.middleware) { + if (middleware.pre) { + fetchParams = await middleware.pre({ + fetch: this.fetchApi, + ...fetchParams, + }) || fetchParams; + } + } + let response: Response | undefined = undefined; + try { + response = await (this.configuration.fetchApi || fetch)(fetchParams.url, fetchParams.init); + } catch (e) { + for (const middleware of this.middleware) { + if (middleware.onError) { + response = await middleware.onError({ + fetch: this.fetchApi, + url: fetchParams.url, + init: fetchParams.init, + error: e, + response: response ? response.clone() : undefined, + }) || response; + } + } + if (response === undefined) { + if (e instanceof Error) { + throw new FetchError(e, 'The request failed and the interceptors did not return an alternative response'); + } else { + throw e; + } + } + } + for (const middleware of this.middleware) { + if (middleware.post) { + response = await middleware.post({ + fetch: this.fetchApi, + url: fetchParams.url, + init: fetchParams.init, + response: response.clone(), + }) || response; + } + } + return response; + } + + /** + * Create a shallow clone of `this` by constructing a new instance + * and then shallow cloning data members. + */ + private clone(this: T): T { + const constructor = this.constructor as new (configuration: Configuration) => T; + const next = new constructor(this.configuration); + next.middleware = this.middleware.slice(); + return next; + } +} + +function isBlob(value: unknown): value is Blob { + return typeof Blob !== 'undefined' && value instanceof Blob; +} + +function isFormData(value: unknown): value is FormData { + return typeof FormData !== "undefined" && value instanceof FormData; +} + +export class ResponseError extends Error { + override name = "ResponseError" as const; + public response: Response; + + constructor(response: Response, msg?: string) { + super(msg); + this.response = response; + } +} + +export class FetchError extends Error { + override name = "FetchError" as const; + public cause: Error; + + constructor(cause: Error, msg?: string) { + super(msg); + this.cause = cause; + } +} + +export class RequiredError extends Error { + override name = "RequiredError" as const; + public field: string; + + constructor(field: string, msg?: string) { + super(msg); + this.field = field; + } +} + +export type FetchAPI = WindowOrWorkerGlobalScope['fetch']; + +export type Json = unknown; +export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; +export type HTTPHeaders = { [key: string]: string }; +export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; +export type HTTPBody = Json | Blob | FormData | URLSearchParams; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents +export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody; signal?: AbortSignal }; +export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; + +export type InitOverrideFunction = (requestContext: { init: HTTPRequestInit, context: RequestOpts }) => Promise + +export interface FetchParams { + url: string; + init: RequestInit; +} + +export interface RequestOpts { + path: string; + method: HTTPMethod; + headers: HTTPHeaders; + query?: HTTPQuery; + body?: HTTPBody; + signal?: AbortSignal; +} + +export function querystring(params: HTTPQuery, prefix: string = ''): string { + return Object.keys(params) + .map(key => querystringSingleKey(key, params[key], prefix)) + .filter(part => part.length > 0) + .join('&'); +} + +function querystringSingleKey(key: string, value: string | number | null | undefined | boolean | Array | Set | HTTPQuery, keyPrefix: string = ''): string { + const fullKey = keyPrefix + (keyPrefix.length ? `[${key}]` : key); + if (value instanceof Array) { + const multiValue = value.map(singleValue => encodeURIComponent(String(singleValue))) + .join(`&${encodeURIComponent(fullKey)}=`); + return `${encodeURIComponent(fullKey)}=${multiValue}`; + } + if (value instanceof Set) { + const valueAsArray = Array.from(value); + return querystringSingleKey(key, valueAsArray, keyPrefix); + } + if (value instanceof Date) { + return `${encodeURIComponent(fullKey)}=${encodeURIComponent(value.toISOString())}`; + } + if (value instanceof Object) { + return querystring(value as HTTPQuery, fullKey); + } + return `${encodeURIComponent(fullKey)}=${encodeURIComponent(String(value))}`; +} + +export function exists(json: Record, key: string): boolean { + const value = json[key]; + return value !== null && value !== undefined; +} + +export function mapValues(data: Record, fn: (item: T) => U): Record { + const result: Record = {}; + for (const key of Object.keys(data)) { + result[key] = fn(data[key]); + } + return result; +} + +export function canConsumeForm(consumes: Consume[]): boolean { + for (const consume of consumes) { + if ('multipart/form-data' === consume.contentType) { + return true; + } + } + return false; +} + +export interface Consume { + contentType: string; +} + +export interface RequestContext { + fetch: FetchAPI; + url: string; + init: RequestInit; +} + +export interface ResponseContext { + fetch: FetchAPI; + url: string; + init: RequestInit; + response: Response; +} + +export interface ErrorContext { + fetch: FetchAPI; + url: string; + init: RequestInit; + error: unknown; + response?: Response; +} + +export interface Middleware { + pre?(context: RequestContext): Promise; + post?(context: ResponseContext): Promise; + onError?(context: ErrorContext): Promise; +} + +export interface ApiResponse { + raw: Response; + value(): Promise; +} + +export type HttpSuccessStatus = + | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 + | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 + | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 + | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 + | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 + | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 + | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 + | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 + | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 + | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299; + +export type HttpSuccessResponse = { status: HttpSuccessStatus }; + +export interface ResponseTransformer { + (json: unknown): T; +} + +class ApiResponseBase { + public readonly status: number; + public readonly ok: boolean; + public readonly statusText: string; + public readonly headers: Headers; + public readonly raw: Response; + + constructor(raw: Response) { + this.raw = raw; + this.status = raw.status; + this.ok = raw.ok; + this.statusText = raw.statusText; + this.headers = raw.headers; + } +} + +export class JSONApiResponse extends ApiResponseBase { + private transformer: ResponseTransformer; + + constructor(raw: Response, transformer: ResponseTransformer = (jsonValue: unknown) => jsonValue as T) { + super(raw); + this.transformer = transformer; + } + + async value(): Promise { + return this.transformer(await this.raw.json()); + } +} + +export class VoidApiResponse extends ApiResponseBase { + constructor(raw: Response) { + super(raw); + } + + async value(): Promise { + return undefined; + } +} + +export class BlobApiResponse extends ApiResponseBase { + constructor(raw: Response) { + super(raw); + } + + async value(): Promise { + return await this.raw.blob(); + }; +} + +export class TextApiResponse extends ApiResponseBase { + constructor(raw: Response) { + super(raw); + } + + async value(): Promise { + return await this.raw.text(); + }; +} diff --git a/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-response-dispatch/tsconfig.esm.json.golden b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-response-dispatch/tsconfig.esm.json.golden new file mode 100644 index 000000000..bb8350cb6 --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-response-dispatch/tsconfig.esm.json.golden @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "module": "ES2020", + "outDir": "dist/esm" + }, + "extends": "./tsconfig.json" +} \ No newline at end of file diff --git a/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-response-dispatch/tsconfig.json.golden b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-response-dispatch/tsconfig.json.golden new file mode 100644 index 000000000..4b3a8cf7f --- /dev/null +++ b/tests/golden/typescript/typescript-fetch/ts-property-naming-camel-case-response-dispatch/tsconfig.json.golden @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "lib": [ + "ES2020", + "DOM" + ], + "module": "ES2020", + "moduleResolution": "bundler", + "outDir": "./dist", + "resolveJsonModule": true, + "rootDir": "./", + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "target": "ES2020", + "typeRoots": [ + "node_modules/@types" + ] + }, + "exclude": [ + "dist", + "node_modules" + ], + "include": [ + "**/*.ts" + ] +} \ No newline at end of file diff --git a/tests/golden/typescript/typescript-fetch/ts-toolchain-vp-petstore/apis/StoreApi.ts.golden b/tests/golden/typescript/typescript-fetch/ts-toolchain-vp-petstore/apis/StoreApi.ts.golden index 8ce5248e7..fab9712e8 100644 --- a/tests/golden/typescript/typescript-fetch/ts-toolchain-vp-petstore/apis/StoreApi.ts.golden +++ b/tests/golden/typescript/typescript-fetch/ts-toolchain-vp-petstore/apis/StoreApi.ts.golden @@ -146,7 +146,7 @@ export class StoreApi extends BaseAPI implements StoreApiInterface { } if (response.status === 200) { return new JSONApiResponse(response) as JSONApiResponse> & { status: 200 }; + number>> & { status: 200 }; } else { return new JSONApiResponse(response) as JSONApiResponse &HttpSuccessResponse; diff --git a/tests/golden_tests_typescript_fetch.rs b/tests/golden_tests_typescript_fetch.rs index 638f665be..51b8ac1dc 100644 --- a/tests/golden_tests_typescript_fetch.rs +++ b/tests/golden_tests_typescript_fetch.rs @@ -81,6 +81,7 @@ fn get_golden_test_cases() -> HashMap<&'static str, &'static str> { ("response-body-multi-status-responses", "valid/response-body/multi-status-responses.yaml"), ("response-body-no-response-body", "valid/response-body/no-response-body.yaml"), ("typed-error-responses", "valid/typed-error-responses.yaml"), + ("response-dispatch", "valid/typescript-response-dispatch.yaml"), ("nested-nullable", "valid/nested-nullable.yaml"), ("additional-properties", "valid/additional-properties/additional-properties.yaml"), @@ -172,6 +173,7 @@ generate_golden_tests! { test_response_body_multi_status_responses_golden: "response-body-multi-status-responses", test_response_body_no_response_body_golden: "response-body-no-response-body", test_typed_error_responses_golden: "typed-error-responses", + test_response_dispatch_golden: "response-dispatch", test_enum_repr_golden: "enum-repr", test_additional_properties_golden: "additional-properties", @@ -279,6 +281,20 @@ property_naming = "camelCase" ); } +#[test] +#[traced_test] +fn test_property_naming_camel_case_response_dispatch_golden() { + let config = toml::from_str("property_naming = 'camelCase'").unwrap(); + let generator = TypeScriptFetchCodeGenerator::new(config); + run_golden_test( + &generator, + golden_dir(), + "ts-property-naming-camel-case-response-dispatch", + "valid/typescript-response-dispatch.yaml", + UPDATE_HINT, + ); +} + #[test] #[traced_test] fn test_property_naming_camel_case_tagged_union_golden() { @@ -658,6 +674,56 @@ fn test_self_referential_object_keeps_self_references_local() { } } +// Deliberately invalid lowered IR, not a valid OpenAPI input or a production hook. +#[test] +fn test_invalid_error_payload_retains_api_emission_context() { + use openapi_nexus::codegen::traits::code_generator::CodeGenerator; + use openapi_nexus::generators::{ArtifactKind, GeneratorError}; + use openapi_nexus::ir::types::IrTypeExpr; + use sigil_stitch::error::SigilStitchError; + + let parsed = openapi_nexus::parser::parse_content_yaml(include_str!( + "fixtures/valid/typescript-response-dispatch.yaml" + )) + .expect("response dispatch fixture parses"); + let mut ir = openapi_nexus::ir::lower::lower(parsed).expect("response dispatch fixture lowers"); + let response = ir + .operations + .iter_mut() + .find(|op| op.operation_id == "readMixed") + .expect("mixed response operation exists") + .responses + .iter_mut() + .find(|response| response.status == "400") + .expect("named error response exists"); + response.content.insert( + "application/json".to_string(), + IrTypeExpr::Named(String::new()), + ); + let generator = TypeScriptFetchCodeGenerator::new(toml::value::Table::new()); + let error = generator + .generate(&ir) + .expect_err("invalid error payload must return an error, not panic"); + match error { + GeneratorError::Emission { + artifact, + name, + source, + } => { + assert!(matches!(artifact, ArtifactKind::Api)); + assert_eq!(name, "Responses API"); + match *source { + SigilStitchError::InvalidTypeName { context, reason } => { + assert!(!context.is_empty(), "original render context is retained"); + assert_eq!(reason, "imported name must not be blank"); + } + other => panic!("original type diagnostic was lost: {other:?}"), + } + } + other => panic!("expected a contextual API emission error, got {other:?}"), + } +} + // =========================================================================== // Toolchain = "vp" golden tests // Same fixtures re-generated with vp toolchain to verify both paths compile.