Skip to content

Commit ce4c65d

Browse files
authored
Automated Updates: add a simple Maven registry API client (#837)
This PR adds a simple Maven registry API client which sends requests to Maven Central Repository. For now, this client can only be used to fetch a Maven project. We are able to extend the client to fetch other data (versions, dependencies, etc) in the future.
1 parent 18a675a commit ce4c65d

File tree

2 files changed

+129
-0
lines changed

2 files changed

+129
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package datasource
2+
3+
import (
4+
"context"
5+
"encoding/xml"
6+
"errors"
7+
"fmt"
8+
"net/http"
9+
"net/url"
10+
"strings"
11+
12+
"deps.dev/util/maven"
13+
)
14+
15+
const MavenCentral = "https://repo.maven.apache.org/maven2"
16+
17+
type MavenRegistryAPIClient struct {
18+
Registry string // Base URL of the registry that we are making requests
19+
}
20+
21+
func NewMavenRegistryAPIClient() (*MavenRegistryAPIClient, error) {
22+
return &MavenRegistryAPIClient{
23+
Registry: MavenCentral,
24+
}, nil
25+
}
26+
27+
func (m *MavenRegistryAPIClient) GetProject(ctx context.Context, groupID, artifactID, version string) (maven.Project, error) {
28+
url, err := url.JoinPath(m.Registry, strings.ReplaceAll(groupID, ".", "/"), artifactID, version, fmt.Sprintf("%s-%s.pom", artifactID, version))
29+
if err != nil {
30+
return maven.Project{}, err
31+
}
32+
33+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
34+
if err != nil {
35+
return maven.Project{}, err
36+
}
37+
38+
resp, err := http.DefaultClient.Do(req)
39+
if err != nil {
40+
return maven.Project{}, err
41+
}
42+
defer resp.Body.Close()
43+
44+
if resp.StatusCode != http.StatusOK {
45+
return maven.Project{}, errors.New(resp.Status)
46+
}
47+
48+
var proj maven.Project
49+
if err := xml.NewDecoder(resp.Body).Decode(&proj); err != nil {
50+
return maven.Project{}, err
51+
}
52+
53+
return proj, nil
54+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package datasource
2+
3+
import (
4+
"context"
5+
"io"
6+
"log"
7+
"net/http"
8+
"net/http/httptest"
9+
"reflect"
10+
"strings"
11+
"sync"
12+
"testing"
13+
14+
"deps.dev/util/maven"
15+
)
16+
17+
type fakeMavenRegistry struct {
18+
mu sync.Mutex
19+
repository map[string]string // path -> response
20+
}
21+
22+
func (f *fakeMavenRegistry) setResponse(path, response string) {
23+
f.mu.Lock()
24+
defer f.mu.Unlock()
25+
if f.repository == nil {
26+
f.repository = make(map[string]string)
27+
}
28+
f.repository[path] = response
29+
}
30+
31+
func (f *fakeMavenRegistry) ServeHTTP(w http.ResponseWriter, r *http.Request) {
32+
f.mu.Lock()
33+
resp, ok := f.repository[strings.TrimPrefix(r.URL.Path, "/")]
34+
f.mu.Unlock()
35+
if !ok {
36+
w.WriteHeader(http.StatusNotFound)
37+
resp = "not found"
38+
}
39+
if _, err := io.WriteString(w, resp); err != nil {
40+
log.Fatalf("WriteString: %v", err)
41+
}
42+
}
43+
44+
func TestGetProject(t *testing.T) {
45+
t.Parallel()
46+
47+
fakeMaven := &fakeMavenRegistry{}
48+
srv := httptest.NewServer(fakeMaven)
49+
defer srv.Close()
50+
client := &MavenRegistryAPIClient{
51+
Registry: srv.URL,
52+
}
53+
54+
fakeMaven.setResponse("org/example/x.y.z/1.0.0/x.y.z-1.0.0.pom", `
55+
<project>
56+
<groupId>org.example</groupId>
57+
<artifactId>x.y.z</artifactId>
58+
<version>1.0.0</version>
59+
</project>
60+
`)
61+
got, err := client.GetProject(context.Background(), "org.example", "x.y.z", "1.0.0")
62+
if err != nil {
63+
t.Fatalf("failed to get Maven project %s:%s verion %s: %v", "org.example", "x.y.z", "1.0.0", err)
64+
}
65+
want := maven.Project{
66+
ProjectKey: maven.ProjectKey{
67+
GroupID: "org.example",
68+
ArtifactID: "x.y.z",
69+
Version: "1.0.0",
70+
},
71+
}
72+
if !reflect.DeepEqual(got, want) {
73+
t.Errorf("GetProject(%s, %s, %s):\ngot %v\nwant %v\n", "org.example", "x.y.z", "1.0.0", got, want)
74+
}
75+
}

0 commit comments

Comments
 (0)