diff --git a/client.go b/client.go index 24ec77e..8b7cd06 100644 --- a/client.go +++ b/client.go @@ -10,6 +10,8 @@ import ( "net/http" "strconv" "strings" + + coreerr "forge.lthn.ai/core/go-log" ) // APIError represents an error response from the Forgejo API. @@ -139,14 +141,14 @@ func (c *Client) PostRaw(ctx context.Context, path string, body any) ([]byte, er if body != nil { data, err := json.Marshal(body) if err != nil { - return nil, fmt.Errorf("forge: marshal body: %w", err) + return nil, coreerr.E("Client.PostRaw", "forge: marshal body", err) } bodyReader = bytes.NewReader(data) } req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bodyReader) if err != nil { - return nil, fmt.Errorf("forge: create request: %w", err) + return nil, coreerr.E("Client.PostRaw", "forge: create request", err) } req.Header.Set("Authorization", "token "+c.token) @@ -157,7 +159,7 @@ func (c *Client) PostRaw(ctx context.Context, path string, body any) ([]byte, er resp, err := c.httpClient.Do(req) if err != nil { - return nil, fmt.Errorf("forge: request POST %s: %w", path, err) + return nil, coreerr.E("Client.PostRaw", "forge: request POST "+path, err) } defer resp.Body.Close() @@ -167,7 +169,7 @@ func (c *Client) PostRaw(ctx context.Context, path string, body any) ([]byte, er data, err := io.ReadAll(resp.Body) if err != nil { - return nil, fmt.Errorf("forge: read response body: %w", err) + return nil, coreerr.E("Client.PostRaw", "forge: read response body", err) } return data, nil @@ -180,7 +182,7 @@ func (c *Client) GetRaw(ctx context.Context, path string) ([]byte, error) { req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { - return nil, fmt.Errorf("forge: create request: %w", err) + return nil, coreerr.E("Client.GetRaw", "forge: create request", err) } req.Header.Set("Authorization", "token "+c.token) @@ -190,7 +192,7 @@ func (c *Client) GetRaw(ctx context.Context, path string) ([]byte, error) { resp, err := c.httpClient.Do(req) if err != nil { - return nil, fmt.Errorf("forge: request GET %s: %w", path, err) + return nil, coreerr.E("Client.GetRaw", "forge: request GET "+path, err) } defer resp.Body.Close() @@ -200,7 +202,7 @@ func (c *Client) GetRaw(ctx context.Context, path string) ([]byte, error) { data, err := io.ReadAll(resp.Body) if err != nil { - return nil, fmt.Errorf("forge: read response body: %w", err) + return nil, coreerr.E("Client.GetRaw", "forge: read response body", err) } return data, nil @@ -218,14 +220,14 @@ func (c *Client) doJSON(ctx context.Context, method, path string, body, out any) if body != nil { data, err := json.Marshal(body) if err != nil { - return nil, fmt.Errorf("forge: marshal body: %w", err) + return nil, coreerr.E("Client.doJSON", "forge: marshal body", err) } bodyReader = bytes.NewReader(data) } req, err := http.NewRequestWithContext(ctx, method, url, bodyReader) if err != nil { - return nil, fmt.Errorf("forge: create request: %w", err) + return nil, coreerr.E("Client.doJSON", "forge: create request", err) } req.Header.Set("Authorization", "token "+c.token) @@ -239,7 +241,7 @@ func (c *Client) doJSON(ctx context.Context, method, path string, body, out any) resp, err := c.httpClient.Do(req) if err != nil { - return nil, fmt.Errorf("forge: request %s %s: %w", method, path, err) + return nil, coreerr.E("Client.doJSON", "forge: request "+method+" "+path, err) } defer resp.Body.Close() @@ -251,7 +253,7 @@ func (c *Client) doJSON(ctx context.Context, method, path string, body, out any) if out != nil && resp.StatusCode != http.StatusNoContent { if err := json.NewDecoder(resp.Body).Decode(out); err != nil { - return nil, fmt.Errorf("forge: decode response: %w", err) + return nil, coreerr.E("Client.doJSON", "forge: decode response", err) } } diff --git a/cmd/forgegen/generator.go b/cmd/forgegen/generator.go index e8aa786..e1beb25 100644 --- a/cmd/forgegen/generator.go +++ b/cmd/forgegen/generator.go @@ -2,13 +2,14 @@ package main import ( "bytes" - "fmt" "maps" - "os" "path/filepath" "slices" "strings" "text/template" + + coreio "forge.lthn.ai/core/go-io" + coreerr "forge.lthn.ai/core/go-log" ) // typeGrouping maps type name prefixes to output file names. @@ -204,8 +205,8 @@ type templateData struct { // Generate writes Go source files for the extracted types, grouped by logical domain. func Generate(types map[string]*GoType, pairs []CRUDPair, outDir string) error { - if err := os.MkdirAll(outDir, 0o755); err != nil { - return fmt.Errorf("create output directory: %w", err) + if err := coreio.Local.EnsureDir(outDir); err != nil { + return coreerr.E("Generate", "create output directory", err) } // Group types by output file. @@ -229,7 +230,7 @@ func Generate(types map[string]*GoType, pairs []CRUDPair, outDir string) error { for _, file := range fileNames { outPath := filepath.Join(outDir, file+".go") if err := writeFile(outPath, groups[file]); err != nil { - return fmt.Errorf("write %s: %w", outPath, err) + return coreerr.E("Generate", "write "+outPath, err) } } @@ -251,11 +252,11 @@ func writeFile(path string, types []*GoType) error { var buf bytes.Buffer if err := fileHeader.Execute(&buf, data); err != nil { - return fmt.Errorf("execute template: %w", err) + return coreerr.E("writeFile", "execute template", err) } - if err := os.WriteFile(path, buf.Bytes(), 0o644); err != nil { - return fmt.Errorf("write file: %w", err) + if err := coreio.Local.Write(path, buf.String()); err != nil { + return coreerr.E("writeFile", "write file", err) } return nil diff --git a/cmd/forgegen/generator_test.go b/cmd/forgegen/generator_test.go index de803ba..fcad069 100644 --- a/cmd/forgegen/generator_test.go +++ b/cmd/forgegen/generator_test.go @@ -5,6 +5,8 @@ import ( "path/filepath" "strings" "testing" + + coreio "forge.lthn.ai/core/go-io" ) func TestGenerate_Good_CreatesFiles(t *testing.T) { @@ -48,20 +50,18 @@ func TestGenerate_Good_ValidGoSyntax(t *testing.T) { } entries, _ := os.ReadDir(outDir) - var data []byte + var content string for _, e := range entries { if strings.HasSuffix(e.Name(), ".go") { - data, err = os.ReadFile(filepath.Join(outDir, e.Name())) + content, err = coreio.Local.Read(filepath.Join(outDir, e.Name())) if err == nil { break } } } - if err != nil || data == nil { + if err != nil || content == "" { t.Fatal("could not read any generated file") } - - content := string(data) if !strings.Contains(content, "package types") { t.Error("missing package declaration") } @@ -87,9 +87,9 @@ func TestGenerate_Good_RepositoryType(t *testing.T) { var content string entries, _ := os.ReadDir(outDir) for _, e := range entries { - data, _ := os.ReadFile(filepath.Join(outDir, e.Name())) - if strings.Contains(string(data), "type Repository struct") { - content = string(data) + data, _ := coreio.Local.Read(filepath.Join(outDir, e.Name())) + if strings.Contains(data, "type Repository struct") { + content = data break } } @@ -129,8 +129,7 @@ func TestGenerate_Good_TimeImport(t *testing.T) { entries, _ := os.ReadDir(outDir) for _, e := range entries { - data, _ := os.ReadFile(filepath.Join(outDir, e.Name())) - content := string(data) + content, _ := coreio.Local.Read(filepath.Join(outDir, e.Name())) if strings.Contains(content, "time.Time") && !strings.Contains(content, "\"time\"") { t.Errorf("file %s uses time.Time but doesn't import time", e.Name()) } diff --git a/cmd/forgegen/parser.go b/cmd/forgegen/parser.go index 37e512e..a667fb1 100644 --- a/cmd/forgegen/parser.go +++ b/cmd/forgegen/parser.go @@ -3,9 +3,11 @@ package main import ( "encoding/json" "fmt" - "os" "slices" "strings" + + coreio "forge.lthn.ai/core/go-io" + coreerr "forge.lthn.ai/core/go-log" ) // Spec represents a Swagger 2.0 specification document. @@ -70,13 +72,13 @@ type CRUDPair struct { // LoadSpec reads and parses a Swagger 2.0 JSON file from the given path. func LoadSpec(path string) (*Spec, error) { - data, err := os.ReadFile(path) + content, err := coreio.Local.Read(path) if err != nil { - return nil, fmt.Errorf("read spec: %w", err) + return nil, coreerr.E("LoadSpec", "read spec", err) } var spec Spec - if err := json.Unmarshal(data, &spec); err != nil { - return nil, fmt.Errorf("parse spec: %w", err) + if err := json.Unmarshal([]byte(content), &spec); err != nil { + return nil, coreerr.E("LoadSpec", "parse spec", err) } return &spec, nil } diff --git a/config.go b/config.go index fe22c0a..afa48c5 100644 --- a/config.go +++ b/config.go @@ -1,8 +1,9 @@ package forge import ( - "errors" "os" + + coreerr "forge.lthn.ai/core/go-log" ) const ( @@ -41,7 +42,7 @@ func NewForgeFromConfig(flagURL, flagToken string, opts ...Option) (*Forge, erro return nil, err } if token == "" { - return nil, errors.New("forge: no API token configured (set FORGE_TOKEN or pass --token)") + return nil, coreerr.E("NewForgeFromConfig", "forge: no API token configured (set FORGE_TOKEN or pass --token)", nil) } return NewForge(url, token, opts...), nil } diff --git a/go.mod b/go.mod index f16172c..04868bf 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,8 @@ module forge.lthn.ai/core/go-forge go 1.26.0 + +require ( + forge.lthn.ai/core/go-io v0.1.2 + forge.lthn.ai/core/go-log v0.0.3 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..052d9b0 --- /dev/null +++ b/go.sum @@ -0,0 +1,12 @@ +forge.lthn.ai/core/go-io v0.1.2 h1:q8hj2jtOFqAgHlBr5wsUAOXtaFkxy9gqGrQT/il0WYA= +forge.lthn.ai/core/go-io v0.1.2/go.mod h1:PbNKW1Q25ywSOoQXeGdQHbV5aiIrTXvHIQ5uhplA//g= +forge.lthn.ai/core/go-log v0.0.3 h1:Ip//c94QzvSCeFWI7WVDLBRxd1CmqLgs/UZ5iIAqnBc= +forge.lthn.ai/core/go-log v0.0.3/go.mod h1:r14MXKOD3LF/sI8XUJQhRk/SZHBE7jAFVuCfgkXoZPw= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pagination.go b/pagination.go index b978694..d72d43b 100644 --- a/pagination.go +++ b/pagination.go @@ -2,11 +2,12 @@ package forge import ( "context" - "fmt" "iter" "net/http" "net/url" "strconv" + + coreerr "forge.lthn.ai/core/go-log" ) // ListOptions controls pagination. @@ -38,7 +39,7 @@ func ListPage[T any](ctx context.Context, c *Client, path string, query map[stri u, err := url.Parse(path) if err != nil { - return nil, fmt.Errorf("forge: parse path: %w", err) + return nil, coreerr.E("ListPage", "forge: parse path", err) } q := u.Query()