Skip to content

Commit f07772d

Browse files
authored
Merge pull request #11 from sumo-mcp/workspace
Add GetBanzukeAPI
2 parents 22c51bc + afbe853 commit f07772d

6 files changed

Lines changed: 426 additions & 2 deletions

File tree

banzuke.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package sumoapi
2+
3+
// Banzuke represents the ranking list of rikishi in a basho division.
4+
type Banzuke struct {
5+
BashoID BashoID `json:"bashoId" jsonschema:"The unique identifier for the basho (sumo tournament)."`
6+
Division string `json:"division" jsonschema:"The division of the basho (sumo tournament). One of Makuuchi, Juryo, Makushita, Sandanme, Jonidan, Jonokuchi."`
7+
East []RikishiBanzuke `json:"east" jsonschema:"The banzuke (ranking list) for the east side of the division."`
8+
West []RikishiBanzuke `json:"west" jsonschema:"The banzuke (ranking list) for the west side of the division."`
9+
}
10+
11+
// RikishiBanzuke represents a rikishi's ranking information in a basho division.
12+
type RikishiBanzuke struct {
13+
Side string `json:"side" jsonschema:"The side of the rikishi in the banzuke (ranking list). Either East or West."`
14+
RikishiID int `json:"rikishiID" jsonschema:"The unique identifier for the rikishi (sumo wrestler)."`
15+
ShikonaEnglish string `json:"shikonaEn" jsonschema:"The shikona (ring name) in English of the rikishi."`
16+
ShikonaJapanese string `json:"shikonaJp" jsonschema:"The shikona (ring name) in Japanese of the rikishi."`
17+
HumanReadableRankName string `json:"rank" jsonschema:"The human-readable name of the rank (e.g., Maegashira 1 East, Ozeki 2 West) of the rikishi (sumo wrestler) in the specific basho (sumo tournament)."`
18+
NumericRankName int `json:"rankValue" jsonschema:"The numeric name of the rank of the rikishi (sumo wrestler) in the specific basho (sumo tournament)."`
19+
Wins int `json:"wins" jsonschema:"The number of wins the rikishi (sumo wrestler) achieved in the specific basho (sumo tournament)."`
20+
Losses int `json:"losses" jsonschema:"The number of losses the rikishi (sumo wrestler) had in the specific basho (sumo tournament)."`
21+
Absences int `json:"absences" jsonschema:"The number of absences the rikishi (sumo wrestler) had in the specific basho (sumo tournament)."`
22+
Matches []RikishiBanzukeMatch `json:"record" jsonschema:"The list of matches the rikishi (sumo wrestler) had or will have in the specific basho (sumo tournament)."`
23+
}
24+
25+
// RikishiBanzukeMatch represents a match against an opponent in the banzuke.
26+
type RikishiBanzukeMatch struct {
27+
OpponentShikonaEnglish string `json:"opponentShikonaEn" jsonschema:"The shikona (ring name) in English of the opponent rikishi (sumo wrestler)."`
28+
OpponentShikonaJapanese string `json:"opponentShikonaJp" jsonschema:"The shikona (ring name) in Japanese of the opponent rikishi (sumo wrestler)."`
29+
OpponentID int `json:"opponentID" jsonschema:"The unique identifier for the opponent rikishi (sumo wrestler)."`
30+
Result string `json:"result,omitempty" jsonschema:"The result of the match for the rikishi (sumo wrestler). One of win, loss, absent, fusen win (forfeit win), fusen loss (forfeit loss). This field may be omitted if the match has not yet occurred."`
31+
Kimarite string `json:"kimarite,omitempty" jsonschema:"The kimarite (technique) used in the match, if the match has already occurred."`
32+
}

client.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ type Client interface {
1818
ListRikishiMatchesAPI
1919
ListRikishiMatchesAgainstOpponentAPI
2020
GetBashoAPI
21+
GetBanzukeAPI
2122
GetBashoWithTorikumiAPI
2223
ListKimariteAPI
2324
ListKimariteMatchesAPI
@@ -70,6 +71,9 @@ func (c *client) doRequest(ctx context.Context, method, path string, query url.V
7071
if err != nil {
7172
return nil, fmt.Errorf("error creating http request: %w", err)
7273
}
74+
if obj != nil {
75+
req.Header.Set("Content-Type", "application/json")
76+
}
7377

7478
resp, err := c.httpClient.Do(req)
7579
if err != nil {

get_banzuke.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package sumoapi
2+
3+
import (
4+
"context"
5+
"fmt"
6+
)
7+
8+
// GetBanzukeAPI defines the methods available for retrieving a banzuke.
9+
type GetBanzukeAPI interface {
10+
// GetBanzuke calls the GET /api/basho/{bashoID}/banzuke/{division} endpoint.
11+
GetBanzuke(ctx context.Context, req GetBanzukeRequest) (*Banzuke, error)
12+
}
13+
14+
// GetBanzukeRequest represents the request parameters for the GetBanzuke method.
15+
type GetBanzukeRequest struct {
16+
BashoID BashoID `json:"bashoId" jsonschema:"The unique identifier of the basho (sumo tournament) to retrieve the banzuke (ranking list) for. Format: YYYYMM, e.g., 202401 for the January 2024 basho."`
17+
Division string `json:"division" jsonschema:"The division of the basho (sumo tournament) to retrieve the banzuke (ranking list) for. One of Makuuchi, Juryo, Makushita, Sandanme, Jonidan, Jonokuchi."`
18+
}
19+
20+
func (c *client) GetBanzuke(ctx context.Context, req GetBanzukeRequest) (*Banzuke, error) {
21+
path := fmt.Sprintf("/basho/%s/banzuke/%s", req.BashoID.String(), req.Division)
22+
return getObject[Banzuke](ctx, c, path, nil)
23+
}

get_banzuke_test.go

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
package sumoapi_test
2+
3+
import (
4+
"context"
5+
"io"
6+
"net/http"
7+
"strings"
8+
"testing"
9+
10+
. "github.com/onsi/gomega"
11+
12+
"github.com/sumo-mcp/sumoapi-go"
13+
)
14+
15+
func TestClient_GetBanzuke(t *testing.T) {
16+
t.Run("get banzuke for division", func(t *testing.T) {
17+
g := NewWithT(t)
18+
19+
mockResp := `{
20+
"bashoId": "202511",
21+
"division": "Makuuchi",
22+
"east": [
23+
{
24+
"side": "East",
25+
"rikishiID": 8850,
26+
"shikonaEn": "Onosato",
27+
"shikonaJp": "大の里",
28+
"rankValue": 101,
29+
"rank": "Yokozuna 1 East",
30+
"record": [
31+
{
32+
"result": "win",
33+
"opponentShikonaEn": "Takayasu",
34+
"opponentShikonaJp": "高安",
35+
"opponentID": 44,
36+
"kimarite": "yorikiri"
37+
}
38+
],
39+
"wins": 11,
40+
"losses": 4,
41+
"absences": 0
42+
}
43+
],
44+
"west": [
45+
{
46+
"side": "West",
47+
"rikishiID": 19,
48+
"shikonaEn": "Hoshoryu",
49+
"shikonaJp": "豊昇龍",
50+
"rankValue": 201,
51+
"rank": "Ozeki 1 West",
52+
"record": [],
53+
"wins": 12,
54+
"losses": 3,
55+
"absences": 0
56+
}
57+
]
58+
}`
59+
60+
transport := &mockTransport{
61+
validateRequest: func(req *http.Request) error {
62+
g.Expect(req.Method).To(Equal(http.MethodGet))
63+
g.Expect(req.URL.Scheme).To(Equal("https"))
64+
g.Expect(req.URL.Host).To(Equal("sumo-api.com"))
65+
g.Expect(req.URL.Path).To(Equal("/api/basho/202511/banzuke/Makuuchi"))
66+
return nil
67+
},
68+
response: &http.Response{
69+
StatusCode: http.StatusOK,
70+
Body: io.NopCloser(strings.NewReader(mockResp)),
71+
},
72+
}
73+
74+
client := sumoapi.New(sumoapi.WithHTTPClient(&http.Client{Transport: transport}))
75+
bashoID := sumoapi.BashoID{Year: 2025, Month: 11}
76+
resp, err := client.GetBanzuke(context.Background(), sumoapi.GetBanzukeRequest{
77+
BashoID: bashoID,
78+
Division: "Makuuchi",
79+
})
80+
81+
g.Expect(err).ToNot(HaveOccurred())
82+
g.Expect(resp).ToNot(BeNil())
83+
g.Expect(resp.BashoID).To(Equal(bashoID))
84+
g.Expect(resp.Division).To(Equal("Makuuchi"))
85+
86+
// Check east side
87+
g.Expect(resp.East).To(HaveLen(1))
88+
g.Expect(resp.East[0].Side).To(Equal("East"))
89+
g.Expect(resp.East[0].RikishiID).To(Equal(8850))
90+
g.Expect(resp.East[0].ShikonaEnglish).To(Equal("Onosato"))
91+
g.Expect(resp.East[0].ShikonaJapanese).To(Equal("大の里"))
92+
g.Expect(resp.East[0].HumanReadableRankName).To(Equal("Yokozuna 1 East"))
93+
g.Expect(resp.East[0].NumericRankName).To(Equal(101))
94+
g.Expect(resp.East[0].Wins).To(Equal(11))
95+
g.Expect(resp.East[0].Losses).To(Equal(4))
96+
g.Expect(resp.East[0].Absences).To(Equal(0))
97+
98+
// Check match record
99+
g.Expect(resp.East[0].Matches).To(HaveLen(1))
100+
g.Expect(resp.East[0].Matches[0].Result).To(Equal("win"))
101+
g.Expect(resp.East[0].Matches[0].OpponentShikonaEnglish).To(Equal("Takayasu"))
102+
g.Expect(resp.East[0].Matches[0].OpponentShikonaJapanese).To(Equal("高安"))
103+
g.Expect(resp.East[0].Matches[0].OpponentID).To(Equal(44))
104+
g.Expect(resp.East[0].Matches[0].Kimarite).To(Equal("yorikiri"))
105+
106+
// Check west side
107+
g.Expect(resp.West).To(HaveLen(1))
108+
g.Expect(resp.West[0].Side).To(Equal("West"))
109+
g.Expect(resp.West[0].RikishiID).To(Equal(19))
110+
g.Expect(resp.West[0].ShikonaEnglish).To(Equal("Hoshoryu"))
111+
})
112+
113+
t.Run("get banzuke for different division", func(t *testing.T) {
114+
g := NewWithT(t)
115+
116+
mockResp := `{
117+
"bashoId": "202501",
118+
"division": "Juryo",
119+
"east": [],
120+
"west": []
121+
}`
122+
123+
transport := &mockTransport{
124+
validateRequest: func(req *http.Request) error {
125+
g.Expect(req.URL.Path).To(Equal("/api/basho/202501/banzuke/Juryo"))
126+
return nil
127+
},
128+
response: &http.Response{
129+
StatusCode: http.StatusOK,
130+
Body: io.NopCloser(strings.NewReader(mockResp)),
131+
},
132+
}
133+
134+
client := sumoapi.New(sumoapi.WithHTTPClient(&http.Client{Transport: transport}))
135+
bashoID := sumoapi.BashoID{Year: 2025, Month: 1}
136+
resp, err := client.GetBanzuke(context.Background(), sumoapi.GetBanzukeRequest{
137+
BashoID: bashoID,
138+
Division: "Juryo",
139+
})
140+
141+
g.Expect(err).ToNot(HaveOccurred())
142+
g.Expect(resp).ToNot(BeNil())
143+
g.Expect(resp.BashoID).To(Equal(bashoID))
144+
g.Expect(resp.Division).To(Equal("Juryo"))
145+
})
146+
147+
t.Run("banzuke with all result types", func(t *testing.T) {
148+
g := NewWithT(t)
149+
150+
mockResp := `{
151+
"bashoId": "202511",
152+
"division": "Makuuchi",
153+
"east": [
154+
{
155+
"side": "East",
156+
"rikishiID": 1,
157+
"shikonaEn": "TestRikishi",
158+
"shikonaJp": "テスト力士",
159+
"rankValue": 101,
160+
"rank": "Yokozuna 1 East",
161+
"record": [
162+
{"result": "win", "opponentShikonaEn": "A", "opponentShikonaJp": "あ", "opponentID": 2, "kimarite": "yorikiri"},
163+
{"result": "loss", "opponentShikonaEn": "B", "opponentShikonaJp": "い", "opponentID": 3, "kimarite": "oshidashi"},
164+
{"result": "absent", "opponentShikonaEn": "C", "opponentShikonaJp": "う", "opponentID": 4},
165+
{"result": "fusen win", "opponentShikonaEn": "D", "opponentShikonaJp": "え", "opponentID": 5, "kimarite": "fusen"},
166+
{"result": "fusen loss", "opponentShikonaEn": "E", "opponentShikonaJp": "お", "opponentID": 6, "kimarite": "fusen"}
167+
],
168+
"wins": 2,
169+
"losses": 2,
170+
"absences": 1
171+
}
172+
],
173+
"west": []
174+
}`
175+
176+
transport := &mockTransport{
177+
response: &http.Response{
178+
StatusCode: http.StatusOK,
179+
Body: io.NopCloser(strings.NewReader(mockResp)),
180+
},
181+
}
182+
183+
client := sumoapi.New(sumoapi.WithHTTPClient(&http.Client{Transport: transport}))
184+
resp, err := client.GetBanzuke(context.Background(), sumoapi.GetBanzukeRequest{
185+
BashoID: sumoapi.BashoID{Year: 2025, Month: 11},
186+
Division: "Makuuchi",
187+
})
188+
189+
g.Expect(err).ToNot(HaveOccurred())
190+
g.Expect(resp).ToNot(BeNil())
191+
g.Expect(resp.East[0].Matches).To(HaveLen(5))
192+
g.Expect(resp.East[0].Matches[0].Result).To(Equal("win"))
193+
g.Expect(resp.East[0].Matches[1].Result).To(Equal("loss"))
194+
g.Expect(resp.East[0].Matches[2].Result).To(Equal("absent"))
195+
g.Expect(resp.East[0].Matches[3].Result).To(Equal("fusen win"))
196+
g.Expect(resp.East[0].Matches[4].Result).To(Equal("fusen loss"))
197+
})
198+
199+
t.Run("context is propagated", func(t *testing.T) {
200+
g := NewWithT(t)
201+
202+
mockResp := `{
203+
"bashoId": "202511",
204+
"division": "Makuuchi",
205+
"east": [],
206+
"west": []
207+
}`
208+
209+
type testKey struct{}
210+
ctx := context.WithValue(context.Background(), testKey{}, "test-value")
211+
212+
transport := &mockTransport{
213+
validateRequest: func(req *http.Request) error {
214+
g.Expect(req.Context().Value(testKey{})).To(Equal("test-value"))
215+
return nil
216+
},
217+
response: &http.Response{
218+
StatusCode: http.StatusOK,
219+
Body: io.NopCloser(strings.NewReader(mockResp)),
220+
},
221+
}
222+
223+
client := sumoapi.New(sumoapi.WithHTTPClient(&http.Client{Transport: transport}))
224+
_, err := client.GetBanzuke(ctx, sumoapi.GetBanzukeRequest{
225+
BashoID: sumoapi.BashoID{Year: 2025, Month: 11},
226+
Division: "Makuuchi",
227+
})
228+
229+
g.Expect(err).ToNot(HaveOccurred())
230+
})
231+
232+
t.Run("http request error", func(t *testing.T) {
233+
g := NewWithT(t)
234+
235+
transport := &mockTransport{
236+
validateRequest: func(req *http.Request) error {
237+
return http.ErrAbortHandler
238+
},
239+
}
240+
241+
client := sumoapi.New(sumoapi.WithHTTPClient(&http.Client{Transport: transport}))
242+
resp, err := client.GetBanzuke(context.Background(), sumoapi.GetBanzukeRequest{
243+
BashoID: sumoapi.BashoID{Year: 2025, Month: 11},
244+
Division: "Makuuchi",
245+
})
246+
247+
g.Expect(err).To(HaveOccurred())
248+
g.Expect(err.Error()).To(ContainSubstring("error making http request"))
249+
g.Expect(resp).To(BeNil())
250+
})
251+
252+
t.Run("invalid JSON response", func(t *testing.T) {
253+
g := NewWithT(t)
254+
255+
transport := &mockTransport{
256+
response: &http.Response{
257+
StatusCode: http.StatusOK,
258+
Body: io.NopCloser(strings.NewReader("not valid json")),
259+
},
260+
}
261+
262+
client := sumoapi.New(sumoapi.WithHTTPClient(&http.Client{Transport: transport}))
263+
resp, err := client.GetBanzuke(context.Background(), sumoapi.GetBanzukeRequest{
264+
BashoID: sumoapi.BashoID{Year: 2025, Month: 11},
265+
Division: "Makuuchi",
266+
})
267+
268+
g.Expect(err).To(HaveOccurred())
269+
g.Expect(err.Error()).To(ContainSubstring("error unmarshaling response body"))
270+
g.Expect(resp).To(BeNil())
271+
})
272+
}

rank.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ type Rank struct {
55
ID RikishiChangeID `json:"id" jsonschema:"The unique identifier for the rank in the format {bashoID}-{rikishiID} where {bashoID} is in the format YYYYMM and {rikishiID} is the unique identifier for the rikishi in the API."`
66
BashoID BashoID `json:"bashoId" jsonschema:"The ID of the basho (sumo tournament) in the format YYYYMM."`
77
RikishiID int `json:"rikishiId" jsonschema:"The unique identifier for the rikishi (sumo wrestler) in the API."`
8-
HumanReadableName string `json:"rank,omitempty" jsonschema:"The human-readable name of the rank (e.g., Maegashira 1 East, Ozeki 2 West) of the rikishi in the specific basho (sumo tournament)."`
9-
NumericName int `json:"rankValue,omitempty" jsonschema:"The numeric name of the rank of the rikishi in the specific basho (sumo tournament)."`
8+
HumanReadableName string `json:"rank,omitempty" jsonschema:"The human-readable name of the rank (e.g., Maegashira 1 East, Ozeki 2 West) of the rikishi (sumo wrestler) in the specific basho (sumo tournament)."`
9+
NumericName int `json:"rankValue,omitempty" jsonschema:"The numeric name of the rank of the rikishi (sumo wrestler) in the specific basho (sumo tournament)."`
1010
}

0 commit comments

Comments
 (0)