Skip to content
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
19 changes: 19 additions & 0 deletions codegen/internal/generator/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
type Params struct {
SpecPath string
OutputDir string
ResourceDir string
BasePackage string
}

Expand All @@ -34,6 +35,12 @@ func (p *Params) normalize() error {
if p.OutputDir, err = filepath.Abs(p.OutputDir); err != nil {
return fmt.Errorf("resolve output directory: %w", err)
}
if p.ResourceDir == "" {
p.ResourceDir = filepath.Join(filepath.Dir(p.OutputDir), "resources")
}
if p.ResourceDir, err = filepath.Abs(p.ResourceDir); err != nil {
return fmt.Errorf("resolve resource directory: %w", err)
}
return nil
}

Expand All @@ -55,6 +62,18 @@ func (p *Params) validate() error {
} else if !fi.IsDir() {
return fmt.Errorf("output directory %s is not a directory", p.OutputDir)
}
fi, err = os.Stat(p.ResourceDir)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
if err := os.MkdirAll(p.ResourceDir, 0o755); err != nil {
return fmt.Errorf("create resource directory: %w", err)
}
} else {
return fmt.Errorf("resource directory: %w", err)
}
} else if !fi.IsDir() {
return fmt.Errorf("resource directory %s is not a directory", p.ResourceDir)
}
return nil
}

Expand Down
24 changes: 24 additions & 0 deletions codegen/internal/generator/resources.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package generator

import (
"fmt"
"os"
"path/filepath"
"strings"
)

func renderApiVersionResource(apiVersion string, params Params) error {
apiVersion = strings.TrimSpace(apiVersion)
if apiVersion == "" {
return fmt.Errorf("missing api version in spec info")
}
targetDir := filepath.Join(params.ResourceDir, params.basePackagePath())
if err := os.MkdirAll(targetDir, 0o755); err != nil {
return fmt.Errorf("create api version resource directory: %w", err)
}
target := filepath.Join(targetDir, "api-version.txt")
if err := os.WriteFile(target, []byte(apiVersion), 0o644); err != nil {
return fmt.Errorf("write api version resource: %w", err)
}
return nil
}
8 changes: 8 additions & 0 deletions codegen/internal/generator/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ func Run(ctx context.Context, params Params) error {
return err
}

apiVersion := ""
if doc.Info != nil {
apiVersion = doc.Info.Version
}

slog.Info("Generating SDK", "spec", params.SpecPath)
if err := renderClients(model, params); err != nil {
return err
Expand All @@ -42,6 +47,9 @@ func Run(ctx context.Context, params Params) error {
if err := renderSumUpClient(model, params); err != nil {
return err
}
if err := renderApiVersionResource(apiVersion, params); err != nil {
return err
}

return nil
}
Expand Down
2 changes: 1 addition & 1 deletion src/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ sourceSets {
def emptyDirs = []
main {
java.srcDirs = ['main/java']
resources.srcDirs = emptyDirs
resources.srcDirs = ['main/resources']
}
test {
java.srcDirs = ['test/java']
Expand Down
1 change: 1 addition & 0 deletions src/main/java/com/sumup/sdk/core/ApiClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ private void applyHeaders(
RequestOptions requestOptions) {
Map<String, String> merged = new LinkedHashMap<>();
merged.put("User-Agent", SdkMetadata.userAgent());
merged.putAll(SdkMetadata.runtimeHeaders());
if (headerParams != null) {
headerParams.forEach(
(name, value) -> {
Expand Down
44 changes: 41 additions & 3 deletions src/main/java/com/sumup/sdk/core/SdkMetadata.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,38 @@
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Map;

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

private static final String VERSION = loadVersion();
private static final String API_VERSION = loadResource(API_VERSION_RESOURCE);
private static final String VERSION = loadResource(VERSION_RESOURCE);
private static final String USER_AGENT = USER_AGENT_PREFIX + "/v" + VERSION;
private static final Map<String, String> RUNTIME_HEADERS =
Map.of(
"X-Sumup-Api-Version", API_VERSION,
"X-Sumup-Lang", LANGUAGE,
"X-Sumup-Package-Version", VERSION,
"X-Sumup-OS", System.getProperty("os.name", "unknown"),
"X-Sumup-Arch", runtimeArch(),
"X-Sumup-Runtime", runtimeIdentifier(),
"X-Sumup-Runtime-Version", Runtime.version().toString());

private SdkMetadata() {}

/** Returns the API version declared by the OpenAPI specification. */
public static String apiVersion() {
return API_VERSION;
}

/** Returns the SDK version read from the generated version resource. */
public static String version() {
return VERSION;
Expand All @@ -27,8 +45,28 @@ public static String userAgent() {
return USER_AGENT;
}

private static String loadVersion() {
try (InputStream stream = SdkMetadata.class.getResourceAsStream(VERSION_RESOURCE)) {
/** Returns the runtime headers that should be sent with each request. */
public static Map<String, String> runtimeHeaders() {
return RUNTIME_HEADERS;
}

static String runtimeArch() {
String arch = System.getProperty("os.arch", "unknown").toLowerCase();
return switch (arch) {
case "amd64", "x86_64" -> "x86_64";
case "x86", "i386", "i486", "i586", "i686" -> "x86";
case "aarch64", "arm64" -> "arm64";
case "arm", "armv7", "armv7l" -> "arm";
default -> arch;
};
}

private static String runtimeIdentifier() {
return LANGUAGE + Runtime.version();
}

private static String loadResource(String path) {
try (InputStream stream = SdkMetadata.class.getResourceAsStream(path)) {
if (stream == null) {
return "unknown";
}
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/com/sumup/sdk/api-version.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.0.0
21 changes: 21 additions & 0 deletions src/test/java/com/sumup/sdk/core/ApiClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,27 @@ void requestOptionsCanOverrideUserAgent() {
"custom/agent", httpClient.lastRequest().headers().firstValue("User-Agent").orElse(null));
}

@Test
void defaultRuntimeHeadersAreIncluded() {
CapturingHttpClient httpClient = new CapturingHttpClient();
ApiClient client = ApiClient.builder().httpClient(httpClient).build();

client.send(HttpMethod.GET, "/v1/test", null, null, null, null, null);

HttpHeaders headers = httpClient.lastRequest().headers();
assertEquals(SdkMetadata.apiVersion(), headers.firstValue("X-Sumup-Api-Version").orElse(null));
assertEquals("java", headers.firstValue("X-Sumup-Lang").orElse(null));
assertEquals(SdkMetadata.version(), headers.firstValue("X-Sumup-Package-Version").orElse(null));
assertEquals(
System.getProperty("os.name", "unknown"), headers.firstValue("X-Sumup-OS").orElse(null));
assertEquals(
SdkMetadata.runtimeHeaders().get("X-Sumup-Arch"),
headers.firstValue("X-Sumup-Arch").orElse(null));
assertEquals("java" + Runtime.version(), headers.firstValue("X-Sumup-Runtime").orElse(null));
assertEquals(
Runtime.version().toString(), headers.firstValue("X-Sumup-Runtime-Version").orElse(null));
}

@Test
void requestOptionsCanOverrideTimeout() {
CapturingHttpClient httpClient = new CapturingHttpClient();
Expand Down
Loading