Skip to content

Commit bfa530e

Browse files
committed
Add TypeSpec example for iRacing data API
1 parent 3172590 commit bfa530e

4 files changed

Lines changed: 184 additions & 0 deletions

File tree

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# iRacing `/data` API (TypeSpec)
2+
3+
This example ports a small slice of the `/data` API schema into [TypeSpec](https://microsoft.github.io/typespec/). It mirrors
4+
structures from `@iracing-data/api-schema` and the OpenAPI generator in `@iracing-data/api-schema-to-openapi`, but uses TypeSpec's
5+
native emitters to produce OpenAPI definitions.
6+
7+
## Getting started
8+
9+
Install dependencies and compile the TypeSpec project:
10+
11+
```bash
12+
pnpm install
13+
pnpm --filter @examples/iracing-typespec build
14+
```
15+
16+
The included `tspconfig.yaml` is configured to emit OpenAPI v3.1 JSON into `./dist/iracing.typespec.openapi.json`.
17+
18+
## Project layout
19+
20+
- `main.tsp` — The TypeSpec surface area that maps `/data` endpoints to operations.
21+
- `tspconfig.yaml` — Configures the OpenAPI emitter and lints with the standard HTTP/REST rules.
22+
- `package.json` — Declares the TypeSpec toolchain dependencies.

examples/iracing-typespec/main.tsp

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import "@typespec/http";
2+
import "@typespec/openapi3";
3+
import "@typespec/rest";
4+
5+
using TypeSpec.Http;
6+
using TypeSpec.Rest;
7+
8+
@service({
9+
title: "iRacing `/data` API (TypeSpec)",
10+
version: "0.0.1",
11+
})
12+
@server("https://members-ng.iracing.com", "iRacing members data service")
13+
@route("/data")
14+
namespace IRacingDataApi {
15+
@doc("Headers included with every successful or rate-limited response.")
16+
model RateLimitHeaders {
17+
@header("x-ratelimit-limit")
18+
rateLimit?: int64;
19+
20+
@header("x-ratelimit-remaining")
21+
remaining?: int64;
22+
23+
@header("x-ratelimit-reset")
24+
resetAt?: int64;
25+
}
26+
27+
@doc("Response returned for successful `/data` calls, pointing to cached data.")
28+
model DataTicket {
29+
@doc("A link to the cached data")
30+
link: url;
31+
32+
@doc("When the cached data expires")
33+
expires: utcDateTime;
34+
}
35+
36+
@doc("Errors returned by the `/data` endpoints.")
37+
model ErrorResponse {
38+
error: string;
39+
message?: string;
40+
note?: string;
41+
}
42+
43+
model SuccessResponse extends RateLimitHeaders {
44+
@statusCode status: 200;
45+
@body body: DataTicket;
46+
}
47+
48+
model UnauthorizedResponse {
49+
@statusCode status: 401;
50+
@body body: ErrorResponse;
51+
}
52+
53+
model RateLimitedResponse extends RateLimitHeaders {
54+
@statusCode status: 429;
55+
@body body: ErrorResponse;
56+
}
57+
58+
model MaintenanceResponse {
59+
@statusCode status: 503;
60+
@body body: ErrorResponse;
61+
}
62+
63+
alias ApiResponses =
64+
| SuccessResponse
65+
| UnauthorizedResponse
66+
| RateLimitedResponse
67+
| MaintenanceResponse;
68+
69+
@doc("iRacing Event Type")
70+
enum EventType {
71+
@doc("Practice")
72+
practice: 2,
73+
@doc("Qualifying")
74+
qualifying: 3,
75+
@doc("Time trial")
76+
timeTrial: 4,
77+
@doc("Race")
78+
race: 5,
79+
}
80+
81+
@doc("Racing category.")
82+
enum Category {
83+
@doc("Oval discipline")
84+
oval: "oval",
85+
@doc("Road discipline. Legacy, use `sports_car` or `formula_car` instead.")
86+
road: "road",
87+
@doc("Dirt road discipline.")
88+
dirtRoad: "dirt_road",
89+
@doc("Dirt oval discipline.")
90+
dirtOval: "dirt_oval",
91+
@doc("Sports car discipline.")
92+
sportsCar: "sports_car",
93+
@doc("Formula car discipline.")
94+
formulaCar: "formula_car",
95+
}
96+
97+
@tag("constants")
98+
@route("/constants")
99+
interface Constants {
100+
@get("/event_types")
101+
@doc("Constant; returned directly as an array of objects")
102+
getEventTypes(): ApiResponses;
103+
104+
@get("/categories")
105+
@doc("List the current iRacing racing categories.")
106+
getCategories(): ApiResponses;
107+
108+
@get("/divisions")
109+
@doc("List license divisions for the active season.")
110+
getDivisions(): ApiResponses;
111+
}
112+
113+
@tag("car")
114+
@route("/car")
115+
interface Cars {
116+
@get("/get")
117+
@doc("Retrieve the cached payload for an iRacing car.")
118+
getCar(): ApiResponses;
119+
120+
@get("/assets")
121+
@doc("Return a link to the media assets for cars.")
122+
getCarAssets(): ApiResponses;
123+
}
124+
125+
@tag("doc")
126+
@route("/doc")
127+
interface Documentation {
128+
@get
129+
@doc("List `/data` services, mirroring the behaviour described by the Zod schema.")
130+
getServices(): ApiResponses;
131+
132+
@get("/car")
133+
@doc("Describe car-related `/data` endpoints.")
134+
getCarDocs(): ApiResponses;
135+
}
136+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "@examples/iracing-typespec",
3+
"version": "0.0.1",
4+
"private": true,
5+
"description": "TypeSpec sketch of the iRacing /data API emitting OpenAPI definitions.",
6+
"scripts": {
7+
"build": "tsp compile ."
8+
},
9+
"devDependencies": {
10+
"@typespec/compiler": "^0.65.0",
11+
"@typespec/http": "^0.65.0",
12+
"@typespec/openapi3": "^0.65.0",
13+
"@typespec/rest": "^0.65.0"
14+
}
15+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
emit:
2+
- "@typespec/openapi3"
3+
4+
options:
5+
output-dir: "./dist"
6+
"@typespec/openapi3":
7+
output-file: "iracing.typespec.openapi.json"
8+
new-line: lf
9+
10+
linter:
11+
extends: ["@typespec/rest", "@typespec/http"]

0 commit comments

Comments
 (0)