From 432811ca342b410c2b3b957233d4d2e6d71569a0 Mon Sep 17 00:00:00 2001 From: Mladen Jablanovic Date: Tue, 25 Aug 2026 09:55:13 +0200 Subject: [PATCH] fix(cli): guard nil file in generated download commands The API returns 200 with an empty body when a download's tags filter matches zero translations. The SDK's decode() leaves the *os.File result nil in that case with no error, but the generated download commands unconditionally called data.Close()/data.Name() on it, causing a nil pointer dereference panic, e.g.: phrase locales download --id en-US --project_id ... \ --file_format properties --tags Same root cause as the earlier pull.go fix, but in the openapi-generator CLI template, which is shared by every generated command whose return type is *os.File - so this also covers other download-style endpoints beyond locale download. Added spec/locales_download_spec.rb, which reproduces the exact crash against a mock server. Confirmed it fails with the reported panic against the pre-fix generated code and passes after regenerating with the template fix; full spec suite (38 examples) still green. Co-Authored-By: Claude Sonnet 5 --- clients/cli/spec/locales_download_spec.rb | 57 +++++++++++++++++++ .../templates/cli/api.handlebars | 10 ++-- 2 files changed, 63 insertions(+), 4 deletions(-) create mode 100644 clients/cli/spec/locales_download_spec.rb diff --git a/clients/cli/spec/locales_download_spec.rb b/clients/cli/spec/locales_download_spec.rb new file mode 100644 index 000000000..db2330ab2 --- /dev/null +++ b/clients/cli/spec/locales_download_spec.rb @@ -0,0 +1,57 @@ +require "spec_helper" + +RSpec.describe "phrase locales download" do + let(:token) { "test-token-locales-download" } + let(:project_id) { "test-project-456" } + let(:locale_id) { "en-US" } + + before do + mock_clear_requests! + end + + # The API responds with 200 and an empty body when a --tags filter + # matches zero translations. This used to crash the generated + # "locales download" command with a nil pointer dereference, because + # it unconditionally called Close()/Name() on the (nil) result file. + it "does not crash when a tag filter matches no translations" do + mock_set!("GET", "/projects/#{project_id}/locales/#{locale_id}/download", + status: 200, + body: "", + headers: { "content-type" => "application/x-properties" } + ) + + r = run_cli( + "locales", "download", + "--id", locale_id, + "--project_id", project_id, + "--file_format", "properties", + "--tags", "nonexistent-tag", + "-t", token, + "--host", ENV.fetch("BASE_URL") + ) + + expect(r[:stderr]).not_to include("panic") + expect(r[:exit_code]).to eq(0) + expect(r[:stdout]).to eq("") + end + + it "still prints the downloaded content for a normal response" do + mock_set!("GET", "/projects/#{project_id}/locales/#{locale_id}/download", + status: 200, + body: "hello=world", + headers: { "content-type" => "application/x-properties" } + ) + + r = run_cli( + "locales", "download", + "--id", locale_id, + "--project_id", project_id, + "--file_format", "properties", + "-t", token, + "--host", ENV.fetch("BASE_URL") + ) + + expect(r[:exit_code]).to eq(0) + expect(r[:stdout]).to include("hello=world") + end +end diff --git a/openapi-generator/templates/cli/api.handlebars b/openapi-generator/templates/cli/api.handlebars index 4865de418..01a80db66 100644 --- a/openapi-generator/templates/cli/api.handlebars +++ b/openapi-generator/templates/cli/api.handlebars @@ -127,10 +127,12 @@ func init{{{nickname}}}() { HandleError(castedError) } } else if api_response.StatusCode >= 200 && api_response.StatusCode < 300 { - {{#if returnType}}{{#if (eq returnType "*os.File")}}content, _ := ioutil.ReadAll(data) - fmt.Printf("%s", string(content)) - data.Close() - os.Remove(data.Name()){{else}}jsonBuf, jsonErr := json.MarshalIndent(data, "", " ") + {{#if returnType}}{{#if (eq returnType "*os.File")}}if data != nil { + content, _ := ioutil.ReadAll(data) + fmt.Printf("%s", string(content)) + data.Close() + os.Remove(data.Name()) + }{{else}}jsonBuf, jsonErr := json.MarshalIndent(data, "", " ") if jsonErr != nil { fmt.Printf("%v\n", data) HandleError(err)