Skip to content

Commit b90ccf1

Browse files
feat: add runnable examples, httptest-based tests, GitHub Actions CI
- examples/natal, examples/transits, examples/positions · self-contained, read TUFFYS_API_KEY - tuffys/client_test.go · 7 tests via httptest.Server, no real API calls covers: happy path, structured API error, raw error fallback, no-key omits header, custom http.Client transport, context cancellation, trailing-slash baseURL - .github/workflows/ci.yml · go vet/build/test -race on Go 1.21, 1.22, 1.23 - README: CI status badge, Examples section, swap private-repo link for case-study, soften 'no interface{}' claim (responses are map[string]any today), drop nonexistent WithUserAgent from design principles
1 parent c55d27f commit b90ccf1

7 files changed

Lines changed: 422 additions & 3 deletions

File tree

.github/workflows/ci.yml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
name: ci
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
8+
permissions:
9+
contents: read
10+
11+
jobs:
12+
test:
13+
runs-on: ubuntu-latest
14+
strategy:
15+
fail-fast: false
16+
matrix:
17+
go: ["1.21", "1.22", "1.23"]
18+
steps:
19+
- uses: actions/checkout@v4
20+
- uses: actions/setup-go@v5
21+
with:
22+
go-version: ${{ matrix.go }}
23+
cache: true
24+
- name: go vet
25+
run: go vet ./...
26+
- name: go build
27+
run: go build ./...
28+
- name: go test
29+
run: go test -race -count=1 ./...

README.md

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Official Go client for the [Insights Astrology API](https://tuffys-ai-astrology.
55
**Zero runtime dependencies.** Only `net/http` and `encoding/json` from the standard library.
66

77
<p align="center">
8+
<a href="https://github.com/omkarjaliparthi/tuffys-astrology-go/actions/workflows/ci.yml"><img src="https://github.com/omkarjaliparthi/tuffys-astrology-go/actions/workflows/ci.yml/badge.svg" alt="CI" /></a>
89
<a href="https://pkg.go.dev/github.com/omkarjaliparthi/tuffys-astrology-go/tuffys"><img src="https://img.shields.io/badge/pkg.go.dev-docs-007d9c?style=flat-square" /></a>
910
<a href="https://tuffys-ai-astrology.vercel.app/docs/api"><img src="https://img.shields.io/badge/API_docs-Scalar-6E56CF?style=flat-square" /></a>
1011
<a href="https://tuffys-ai-astrology.vercel.app/pricing"><img src="https://img.shields.io/badge/Pricing-tiered-success?style=flat-square" /></a>
@@ -90,23 +91,34 @@ All 43 v1 endpoints, grouped by domain:
9091
| **Aggregators** | `/daily` · `/compatibility` · `/events` |
9192
| **Utility** | `/geocode` · `/openapi.json` |
9293

93-
Every method is typed end-to-end. No `interface{}`, no magic strings.
94+
Inputs are typed (`Person`, `NatalChartOpts`, `AspectPoint`); responses currently decode as `map[string]any` to match the API's rich, endpoint-specific shapes. Typed response structs are on the roadmap as the OpenAPI 3.1 spec stabilizes.
9495

9596
---
9697

98+
## Examples
99+
100+
Runnable examples live in [`examples/`](./examples):
101+
102+
```bash
103+
export TUFFYS_API_KEY="eyJ..."
104+
go run ./examples/natal # natal chart with typed error handling
105+
go run ./examples/transits # transits from a natal chart
106+
go run ./examples/positions # stateless body positions
107+
```
108+
97109
## Design principles
98110

99111
- **Zero runtime dependencies.** Embed this in anything — serverless, edge, CLI, bot — without dragging a graph.
100112
- **Context-first.** Every method takes `context.Context`. Cancel anywhere in the tree.
101113
- **Errors are data.** `*tuffys.APIError` carries the HTTP status, the machine-readable code, and a human message.
102-
- **Idiomatic options.** Functional options (`WithAPIKey`, `WithHTTPClient`, `WithUserAgent`) — swap out the transport for tracing, retries, or testing.
114+
- **Idiomatic options.** Functional options (`WithAPIKey`, `WithHTTPClient`) — swap out the transport for tracing, retries, or testing.
103115
- **Strict input parsing on the server.** ISO-8601 datetimes required — no ambiguous local times. Pair with Go's `time.Time.Format(time.RFC3339)` and you're set.
104116

105117
---
106118

107119
## Related
108120

109-
- **[Main repository](https://github.com/omkarjaliparthi/tuffys-ai-astrology)**engine, API, frontend, case study
121+
- **[Case study](https://github.com/omkarjaliparthi/insights-astrology-api-case-study)**architecture, decisions, accuracy, sources
110122
- **[TypeScript SDK](https://www.npmjs.com/package/tuffys-astrology)** — published on npm
111123
- **[Python SDK](https://pypi.org/project/tuffys-astrology/)** — published on PyPI
112124
- **[API docs](https://tuffys-ai-astrology.vercel.app/docs/api)** — interactive Scalar explorer

examples/README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Examples
2+
3+
Runnable Go examples for the Tuffy's Astrology API client.
4+
5+
Each example is self-contained — `go run ./examples/<name>` from the repo root.
6+
7+
| Example | What it shows |
8+
|---|---|
9+
| [`natal/`](./natal) | Quickstart · `NatalChart` with typed error handling |
10+
| [`transits/`](./transits) | Relational endpoint · `Transits` of a natal chart to a given datetime |
11+
| [`positions/`](./positions) | Stateless body positions — no birth data needed |
12+
13+
## Auth
14+
15+
All examples read the API key from `TUFFYS_API_KEY`:
16+
17+
```bash
18+
export TUFFYS_API_KEY="eyJ..." # HS256 JWT issued by the API
19+
go run ./examples/natal
20+
```
21+
22+
For the free Developer tier, [mint a key](https://tuffys-ai-astrology.vercel.app/pricing). No credit card.
23+
24+
## Base URL
25+
26+
Examples default to the hosted API at `https://tuffys-ai-astrology.vercel.app`. Override with `TUFFYS_BASE_URL` to point at a local or custom deployment.

examples/natal/main.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Example: compute a natal chart with typed error handling.
2+
//
3+
// TUFFYS_API_KEY=eyJ... go run ./examples/natal
4+
package main
5+
6+
import (
7+
"context"
8+
"encoding/json"
9+
"errors"
10+
"fmt"
11+
"os"
12+
"time"
13+
14+
"github.com/omkarjaliparthi/tuffys-astrology-go/tuffys"
15+
)
16+
17+
func main() {
18+
apiKey := os.Getenv("TUFFYS_API_KEY")
19+
if apiKey == "" {
20+
fmt.Fprintln(os.Stderr, "set TUFFYS_API_KEY — mint one at https://tuffys-ai-astrology.vercel.app/pricing")
21+
os.Exit(1)
22+
}
23+
24+
baseURL := os.Getenv("TUFFYS_BASE_URL")
25+
if baseURL == "" {
26+
baseURL = "https://tuffys-ai-astrology.vercel.app"
27+
}
28+
29+
client := tuffys.New(baseURL, tuffys.WithAPIKey(apiKey))
30+
31+
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
32+
defer cancel()
33+
34+
chart, err := client.NatalChart(ctx, tuffys.Person{
35+
Datetime: "1990-06-15T12:00:00Z",
36+
Latitude: 51.5074,
37+
Longitude: -0.1278,
38+
}, tuffys.NatalChartOpts{HouseSystem: "placidus"})
39+
40+
if err != nil {
41+
var apiErr *tuffys.APIError
42+
if errors.As(err, &apiErr) {
43+
fmt.Fprintf(os.Stderr, "API %d %s: %s\n", apiErr.Status, apiErr.Code, apiErr.Message)
44+
os.Exit(1)
45+
}
46+
fmt.Fprintln(os.Stderr, "transport error:", err)
47+
os.Exit(1)
48+
}
49+
50+
pretty, _ := json.MarshalIndent(chart, "", " ")
51+
fmt.Println(string(pretty))
52+
}

examples/positions/main.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Example: geocentric ecliptic positions of selected bodies at a given UTC instant.
2+
//
3+
// TUFFYS_API_KEY=eyJ... go run ./examples/positions
4+
package main
5+
6+
import (
7+
"context"
8+
"encoding/json"
9+
"errors"
10+
"fmt"
11+
"os"
12+
"time"
13+
14+
"github.com/omkarjaliparthi/tuffys-astrology-go/tuffys"
15+
)
16+
17+
func main() {
18+
apiKey := os.Getenv("TUFFYS_API_KEY")
19+
if apiKey == "" {
20+
fmt.Fprintln(os.Stderr, "set TUFFYS_API_KEY")
21+
os.Exit(1)
22+
}
23+
24+
baseURL := os.Getenv("TUFFYS_BASE_URL")
25+
if baseURL == "" {
26+
baseURL = "https://tuffys-ai-astrology.vercel.app"
27+
}
28+
29+
client := tuffys.New(baseURL, tuffys.WithAPIKey(apiKey))
30+
31+
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
32+
defer cancel()
33+
34+
now := time.Now().UTC().Format(time.RFC3339)
35+
36+
// Omit the bodies slice to get all ten.
37+
result, err := client.Positions(ctx, now, "sun", "moon", "mercury", "venus", "mars")
38+
if err != nil {
39+
var apiErr *tuffys.APIError
40+
if errors.As(err, &apiErr) {
41+
fmt.Fprintf(os.Stderr, "API %d %s: %s\n", apiErr.Status, apiErr.Code, apiErr.Message)
42+
os.Exit(1)
43+
}
44+
fmt.Fprintln(os.Stderr, "transport error:", err)
45+
os.Exit(1)
46+
}
47+
48+
pretty, _ := json.MarshalIndent(result, "", " ")
49+
fmt.Println(string(pretty))
50+
}

examples/transits/main.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Example: transits from a natal chart to a given datetime.
2+
//
3+
// TUFFYS_API_KEY=eyJ... go run ./examples/transits
4+
package main
5+
6+
import (
7+
"context"
8+
"encoding/json"
9+
"errors"
10+
"fmt"
11+
"os"
12+
"time"
13+
14+
"github.com/omkarjaliparthi/tuffys-astrology-go/tuffys"
15+
)
16+
17+
func main() {
18+
apiKey := os.Getenv("TUFFYS_API_KEY")
19+
if apiKey == "" {
20+
fmt.Fprintln(os.Stderr, "set TUFFYS_API_KEY")
21+
os.Exit(1)
22+
}
23+
24+
baseURL := os.Getenv("TUFFYS_BASE_URL")
25+
if baseURL == "" {
26+
baseURL = "https://tuffys-ai-astrology.vercel.app"
27+
}
28+
29+
client := tuffys.New(baseURL, tuffys.WithAPIKey(apiKey))
30+
31+
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
32+
defer cancel()
33+
34+
natal := tuffys.Person{
35+
Datetime: "1990-06-15T12:00:00Z",
36+
Latitude: 51.5074,
37+
Longitude: -0.1278,
38+
}
39+
transitAt := time.Now().UTC().Format(time.RFC3339)
40+
41+
result, err := client.Transits(ctx, natal, transitAt)
42+
if err != nil {
43+
var apiErr *tuffys.APIError
44+
if errors.As(err, &apiErr) {
45+
fmt.Fprintf(os.Stderr, "API %d %s: %s\n", apiErr.Status, apiErr.Code, apiErr.Message)
46+
os.Exit(1)
47+
}
48+
fmt.Fprintln(os.Stderr, "transport error:", err)
49+
os.Exit(1)
50+
}
51+
52+
pretty, _ := json.MarshalIndent(result, "", " ")
53+
fmt.Println(string(pretty))
54+
}

0 commit comments

Comments
 (0)