Skip to content

libs: Ignore unknown keys #2002

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Aug 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions csharp/Svix.Tests/WiremockTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -361,5 +361,16 @@ public void ClientProvidedIdempotencyKeyIsNotOverridden()
);
Assert.Equal(1, stub.LogEntries.Count);
}

[Fact]
public void UnknownKeysAreIgnored()
{
var res =
"""{"data": [],"iterator": "iterator","prevIterator": "-iterator","done": true,"extra-field": "ignored"}""";
stub.Given(Request.Create().WithPath("/api/v1/app"))
.RespondWith(Response.Create().WithStatusCode(200).WithBody(res));

client.Application.List();
}
}
}
17 changes: 17 additions & 0 deletions go/svix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -749,3 +749,20 @@ func TestReadStructEnumField(t *testing.T) {
}

}

func TestUnknownKeysAreIgnored(t *testing.T) {
ctx := context.Background()
svx := newMockClient()
httpmock.Activate()
defer httpmock.DeactivateAndReset()
httpmock.RegisterResponder("GET", "http://testapi.test/api/v1/app",
func(r *http.Request) (*http.Response, error) {
return httpmock.NewStringResponse(200, `{"data":[],"done":true,"iterator":null,"prevIterator":null,"extra-key":"ignored"}`), nil
},
)
_, err := svx.Application.List(ctx, nil)
if err != nil {
t.Error(err)
}

}
2 changes: 2 additions & 0 deletions java/lib/src/main/java/com/svix/Utils.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.svix;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
Expand Down Expand Up @@ -53,6 +54,7 @@ public static ObjectMapper getObjectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
mapper.enable(JsonParser.Feature.INCLUDE_SOURCE_IN_LOCATION);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.registerModule(new JavaTimeModule());
mapper.registerModule(new Jdk8Module());
return mapper;
Expand Down
13 changes: 13 additions & 0 deletions java/lib/src/test/com/svix/test/WiremockTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -544,4 +544,17 @@ public void testClientProvidedIdempotencyKeyIsNotOverridden() throws Exception {
.withHeader("idempotency-key", equalTo(clientProvidedKey)));
}

@Test
public void testUnknownKeysAreIgnored() throws Exception {
String res = "{\"data\": [],\"iterator\": \"iterator\",\"prevIterator\": \"-iterator\",\"done\": true,\"extra-field\": \"ignored\"}";
Svix svx = testClient();
wireMockRule.stubFor(
WireMock.get(urlEqualTo("/api/v1/app"))
.willReturn(WireMock.ok().withBody(res)));

svx.getApplication().list();

wireMockRule.verify(1, getRequestedFor(urlEqualTo("/api/v1/app")));
}

}
15 changes: 15 additions & 0 deletions javascript/src/mockttp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -450,4 +450,19 @@ describe("mockttp tests", () => {
expect(requests.length).toBe(1);
expect(requests[0].headers["idempotency-key"]).toBe(clientProvidedKey);
});

test("test unknown keys are ignored", async () => {
const endpointMock = await mockServer
.forGet("/api/v1/app")
.thenReply(
200,
`{"data":[],"done":true,"iterator":null,"prevIterator":null,"extra-key":"ignored"}`
);
const svx = new Svix("token", { serverUrl: mockServer.url });

await svx.application.list();

const requests = await endpointMock.getSeenRequests();
expect(requests.length).toBe(1);
});
});
9 changes: 5 additions & 4 deletions kotlin/lib/src/main/kotlin/SvixHttpClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ internal constructor(
private val baseUrl: HttpUrl,
private val defaultHeaders: Map<String, String>,
private val retrySchedule: List<Long>,
private val jsonDeserializer: Json = Json { ignoreUnknownKeys = true }
) {
private val client: OkHttpClient = OkHttpClient()

Expand Down Expand Up @@ -44,8 +45,8 @@ internal constructor(
}

if (headers?.get("idempotency-key") == null && method == "POST") {
val uuid = UUID.randomUUID().toString()
reqBuilder.addHeader("idempotency-key", "auto_" + uuid)
val uuid = UUID.randomUUID().toString()
reqBuilder.addHeader("idempotency-key", "auto_" + uuid)
}

reqBuilder.addHeader("svix-req-id", Random.nextULong().toString())
Expand All @@ -59,10 +60,10 @@ internal constructor(
}
val bodyString = res.body!!.string()
if (res.code == 204) {
return Json.decodeFromString<Res>("true")
return jsonDeserializer.decodeFromString<Res>("true")
}
if (res.code in 200..299) {
return Json.decodeFromString<Res>(bodyString)
return jsonDeserializer.decodeFromString<Res>(bodyString)
}
throw ApiException("Non 200 status code ${res.code}", res.code, bodyString)
}
Expand Down
15 changes: 15 additions & 0 deletions kotlin/lib/src/test/com/svix/kotlin/WiremockTests.kt
Original file line number Diff line number Diff line change
Expand Up @@ -481,4 +481,19 @@ class WiremockTests {
.withHeader("idempotency-key", equalTo(clientProvidedKey)),
)
}

@Test
fun testUnknownKeysAreIgnored() {
val res =
"""{"data": [],"iterator": "iterator","prevIterator": "-iterator","done": true,"extra-field": "ignored"}"""
val svx = testClient()
wireMockServer.stubFor(get(urlEqualTo("/api/v1/app")).willReturn(ok().withBody(res)))

runBlocking { svx.application.list() }

wireMockServer.verify(
1,
getRequestedFor(urlEqualTo("/api/v1/app")),
)
}
}
14 changes: 14 additions & 0 deletions python/tests/test_httpretty.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,17 @@ def test_client_provided_idempotency_key_is_not_overridden():
request = httpretty.latest_requests()[0]
assert "idempotency-key" in request.headers
assert request.headers["idempotency-key"] == client_provided_key



@httpretty.activate(verbose=True, allow_net_connect=False)
def test_unknown_keys_are_ignored():
svx = svix.Svix("token", svix.SvixOptions(server_url="http://test.example"))
httpretty.register_uri(
httpretty.GET,
"http://test.example/api/v1/app",
body='{"data":[],"done":true,"iterator":null,"prevIterator":null,"extra-key":"ignored"}'
)


svx.application.list()
14 changes: 14 additions & 0 deletions ruby/spec/webmock_tests_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -433,4 +433,18 @@
request = WebMock::RequestRegistry.instance.requested_signatures.hash.first[0]
expect(request.headers["Idempotency-Key"]).to(eq(client_provided_key))
end

it "test unknown keys are ignored" do
stub_request(:get, "#{host}/api/v1/app")
.to_return(
status: 200,
body: '{"data":[],"done":true,"iterator":null,"prevIterator":null,"extra-key":"ignored"}'
)

svx.application.list()

expect(WebMock).to(
have_requested(:get, "#{host}/api/v1/app")
)
end
end
26 changes: 26 additions & 0 deletions rust/tests/it/wiremock_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,29 @@ async fn test_client_provided_idempotency_key_is_not_overridden() {
"client provided idempotency key should not be overridden"
);
}

#[tokio::test]
async fn test_unknown_keys_are_ignored() {
let mock_server = MockServer::start().await;

let json_body =
r#"{"data":[],"done":true,"iterator":null,"prevIterator":null,"extra-key":"ignored"}"#;
Mock::given(method("GET"))
.and(path("/api/v1/app"))
.respond_with(ResponseTemplate::new(200).set_body_string(json_body))
.expect(1)
.mount(&mock_server)
.await;

let svx = Svix::new(
"token".to_string(),
Some(SvixOptions {
server_url: Some(mock_server.uri()),
..Default::default()
}),
);

svx.application().list(None).await.unwrap();

mock_server.verify().await;
}