Skip to content

Commit 3d0a565

Browse files
committed
feat: report runtime properties
Propagate more information about the SDK's runtime including the API version, package version, etc. to get more observability into the SDKs usage. Also removes the generation of the client/client.go file as it doesn't depend on the OpenAPI specs and can be maintained manually.
1 parent 1a676eb commit 3d0a565

8 files changed

Lines changed: 112 additions & 4 deletions

File tree

codegen/internal/generator/params.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
type Params struct {
1313
SpecPath string
1414
OutputDir string
15+
ResourceDir string
1516
BasePackage string
1617
}
1718

@@ -34,6 +35,12 @@ func (p *Params) normalize() error {
3435
if p.OutputDir, err = filepath.Abs(p.OutputDir); err != nil {
3536
return fmt.Errorf("resolve output directory: %w", err)
3637
}
38+
if p.ResourceDir == "" {
39+
p.ResourceDir = filepath.Join(filepath.Dir(p.OutputDir), "resources")
40+
}
41+
if p.ResourceDir, err = filepath.Abs(p.ResourceDir); err != nil {
42+
return fmt.Errorf("resolve resource directory: %w", err)
43+
}
3744
return nil
3845
}
3946

@@ -55,6 +62,18 @@ func (p *Params) validate() error {
5562
} else if !fi.IsDir() {
5663
return fmt.Errorf("output directory %s is not a directory", p.OutputDir)
5764
}
65+
fi, err = os.Stat(p.ResourceDir)
66+
if err != nil {
67+
if errors.Is(err, os.ErrNotExist) {
68+
if err := os.MkdirAll(p.ResourceDir, 0o755); err != nil {
69+
return fmt.Errorf("create resource directory: %w", err)
70+
}
71+
} else {
72+
return fmt.Errorf("resource directory: %w", err)
73+
}
74+
} else if !fi.IsDir() {
75+
return fmt.Errorf("resource directory %s is not a directory", p.ResourceDir)
76+
}
5877
return nil
5978
}
6079

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package generator
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
"strings"
8+
)
9+
10+
func renderApiVersionResource(apiVersion string, params Params) error {
11+
apiVersion = strings.TrimSpace(apiVersion)
12+
if apiVersion == "" {
13+
return fmt.Errorf("missing api version in spec info")
14+
}
15+
targetDir := filepath.Join(params.ResourceDir, params.basePackagePath())
16+
if err := os.MkdirAll(targetDir, 0o755); err != nil {
17+
return fmt.Errorf("create api version resource directory: %w", err)
18+
}
19+
target := filepath.Join(targetDir, "api-version.txt")
20+
if err := os.WriteFile(target, []byte(apiVersion), 0o644); err != nil {
21+
return fmt.Errorf("write api version resource: %w", err)
22+
}
23+
return nil
24+
}

codegen/internal/generator/run.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ func Run(ctx context.Context, params Params) error {
3232
return err
3333
}
3434

35+
apiVersion := ""
36+
if doc.Info != nil {
37+
apiVersion = doc.Info.Version
38+
}
39+
3540
slog.Info("Generating SDK", "spec", params.SpecPath)
3641
if err := renderClients(model, params); err != nil {
3742
return err
@@ -42,6 +47,9 @@ func Run(ctx context.Context, params Params) error {
4247
if err := renderSumUpClient(model, params); err != nil {
4348
return err
4449
}
50+
if err := renderApiVersionResource(apiVersion, params); err != nil {
51+
return err
52+
}
4553

4654
return nil
4755
}

src/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ sourceSets {
2121
def emptyDirs = []
2222
main {
2323
java.srcDirs = ['main/java']
24-
resources.srcDirs = emptyDirs
24+
resources.srcDirs = ['main/resources']
2525
}
2626
test {
2727
java.srcDirs = ['test/java']

src/main/java/com/sumup/sdk/core/ApiClient.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ private void applyHeaders(
186186
RequestOptions requestOptions) {
187187
Map<String, String> merged = new LinkedHashMap<>();
188188
merged.put("User-Agent", SdkMetadata.userAgent());
189+
merged.putAll(SdkMetadata.runtimeHeaders());
189190
if (headerParams != null) {
190191
headerParams.forEach(
191192
(name, value) -> {

src/main/java/com/sumup/sdk/core/SdkMetadata.java

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,38 @@
33
import java.io.IOException;
44
import java.io.InputStream;
55
import java.nio.charset.StandardCharsets;
6+
import java.util.Map;
67

78
/**
89
* Provides metadata about the SDK that can be attached to outgoing requests, such as the current
910
* version and the default {@code User-Agent} header value.
1011
*/
1112
public final class SdkMetadata {
13+
private static final String API_VERSION_RESOURCE = "/com/sumup/sdk/api-version.txt";
1214
private static final String VERSION_RESOURCE = "/com/sumup/sdk/sdk-version.txt";
1315
private static final String USER_AGENT_PREFIX = "sumup-java";
16+
private static final String LANGUAGE = "java";
1417

15-
private static final String VERSION = loadVersion();
18+
private static final String API_VERSION = loadResource(API_VERSION_RESOURCE);
19+
private static final String VERSION = loadResource(VERSION_RESOURCE);
1620
private static final String USER_AGENT = USER_AGENT_PREFIX + "/v" + VERSION;
21+
private static final Map<String, String> RUNTIME_HEADERS =
22+
Map.of(
23+
"X-Sumup-Api-Version", API_VERSION,
24+
"X-Sumup-Lang", LANGUAGE,
25+
"X-Sumup-Package-Version", VERSION,
26+
"X-Sumup-OS", System.getProperty("os.name", "unknown"),
27+
"X-Sumup-Arch", runtimeArch(),
28+
"X-Sumup-Runtime", LANGUAGE,
29+
"X-Sumup-Runtime-Version", Runtime.version().toString());
1730

1831
private SdkMetadata() {}
1932

33+
/** Returns the API version declared by the OpenAPI specification. */
34+
public static String apiVersion() {
35+
return API_VERSION;
36+
}
37+
2038
/** Returns the SDK version read from the generated version resource. */
2139
public static String version() {
2240
return VERSION;
@@ -27,8 +45,24 @@ public static String userAgent() {
2745
return USER_AGENT;
2846
}
2947

30-
private static String loadVersion() {
31-
try (InputStream stream = SdkMetadata.class.getResourceAsStream(VERSION_RESOURCE)) {
48+
/** Returns the runtime headers that should be sent with each request. */
49+
public static Map<String, String> runtimeHeaders() {
50+
return RUNTIME_HEADERS;
51+
}
52+
53+
static String runtimeArch() {
54+
String arch = System.getProperty("os.arch", "unknown").toLowerCase();
55+
return switch (arch) {
56+
case "amd64", "x86_64" -> "x86_64";
57+
case "x86", "i386", "i486", "i586", "i686" -> "x86";
58+
case "aarch64", "arm64" -> "arm64";
59+
case "arm", "armv7", "armv7l" -> "arm";
60+
default -> arch;
61+
};
62+
}
63+
64+
private static String loadResource(String path) {
65+
try (InputStream stream = SdkMetadata.class.getResourceAsStream(path)) {
3266
if (stream == null) {
3367
return "unknown";
3468
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1.0.0

src/test/java/com/sumup/sdk/core/ApiClientTest.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,27 @@ void requestOptionsCanOverrideUserAgent() {
7474
"custom/agent", httpClient.lastRequest().headers().firstValue("User-Agent").orElse(null));
7575
}
7676

77+
@Test
78+
void defaultRuntimeHeadersAreIncluded() {
79+
CapturingHttpClient httpClient = new CapturingHttpClient();
80+
ApiClient client = ApiClient.builder().httpClient(httpClient).build();
81+
82+
client.send(HttpMethod.GET, "/v1/test", null, null, null, null, null);
83+
84+
HttpHeaders headers = httpClient.lastRequest().headers();
85+
assertEquals(SdkMetadata.apiVersion(), headers.firstValue("X-Sumup-Api-Version").orElse(null));
86+
assertEquals("java", headers.firstValue("X-Sumup-Lang").orElse(null));
87+
assertEquals(SdkMetadata.version(), headers.firstValue("X-Sumup-Package-Version").orElse(null));
88+
assertEquals(
89+
System.getProperty("os.name", "unknown"), headers.firstValue("X-Sumup-OS").orElse(null));
90+
assertEquals(
91+
SdkMetadata.runtimeHeaders().get("X-Sumup-Arch"),
92+
headers.firstValue("X-Sumup-Arch").orElse(null));
93+
assertEquals("java", headers.firstValue("X-Sumup-Runtime").orElse(null));
94+
assertEquals(
95+
Runtime.version().toString(), headers.firstValue("X-Sumup-Runtime-Version").orElse(null));
96+
}
97+
7798
@Test
7899
void requestOptionsCanOverrideTimeout() {
79100
CapturingHttpClient httpClient = new CapturingHttpClient();

0 commit comments

Comments
 (0)