Add static OpenAPI spec and Stainless SDK config

Introduce SDK generation scaffolding: commit a static openapi.json
extracted from the Utoipa annotations via a golden-file test, add
Stainless workspace/config for TypeScript and Python SDKs, and clean
up operation IDs for ergonomic generated method names.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ragnor Comerford 2026-04-17 14:26:31 +02:00
parent 9ad9d1f71f
commit 228032a4ac
No known key found for this signature in database
6 changed files with 1887 additions and 0 deletions

1
.gitignore vendored
View file

@ -16,3 +16,4 @@ __pycache__/
*.pyc
demo/*.omni/
.omnigraph-rustfs-demo/
sdks/

60
.stainless/stainless.yml Normal file
View file

@ -0,0 +1,60 @@
edition: "2026-02-23"
organization:
name: omnigraph
docs: https://github.com/ModernRelay/omnigraph
github_org: ModernRelay
targets:
typescript:
package_name: omnigraph
production_repo: ModernRelay/omnigraph-typescript
publish:
npm: true
python:
package_name: omnigraph
production_repo: ModernRelay/omnigraph-python
publish:
pypi: true
client_settings:
opts:
api_key:
type: string
auth:
security_scheme: bearer_token
base_url:
type: string
resources:
$client:
methods:
read: post /read
change: post /change
export: post /export
ingest: post /ingest
schema:
methods:
apply: post /schema/apply
branches:
methods:
list: get /branches
create: post /branches
delete: delete /branches/{branch}
merge: post /branches/merge
runs:
methods:
list: get /runs
retrieve: get /runs/{run_id}
publish: post /runs/{run_id}/publish
abort: post /runs/{run_id}/abort
commits:
methods:
list: get /commits
retrieve: get /commits/{commit_id}
snapshots:
methods:
retrieve: get /snapshot
settings:
license: MIT

13
.stainless/workspace.json Normal file
View file

@ -0,0 +1,13 @@
{
"project": "omnigraph",
"openapi_spec": "../openapi.json",
"stainless_config": "./stainless.yml",
"targets": {
"typescript": {
"output_path": "../sdks/omnigraph-typescript"
},
"python": {
"output_path": "../sdks/omnigraph-python"
}
}
}

View file

@ -465,6 +465,7 @@ async fn shutdown_signal() {
get,
path = "/healthz",
tag = "health",
operation_id = "health",
responses(
(status = 200, description = "Server is healthy", body = HealthOutput),
),
@ -575,6 +576,7 @@ fn authorize_request(
get,
path = "/snapshot",
tag = "snapshots",
operation_id = "getSnapshot",
params(SnapshotQuery),
responses(
(status = 200, description = "Database snapshot", body = api::SnapshotOutput),
@ -615,6 +617,7 @@ async fn server_snapshot(
post,
path = "/read",
tag = "queries",
operation_id = "read",
request_body = ReadRequest,
responses(
(status = 200, description = "Query results", body = ReadOutput),
@ -684,6 +687,7 @@ async fn server_read(
post,
path = "/export",
tag = "queries",
operation_id = "export",
request_body = ExportRequest,
responses(
(status = 200, description = "Exported data as NDJSON", content_type = "application/x-ndjson"),
@ -742,6 +746,7 @@ async fn server_export(
post,
path = "/change",
tag = "mutations",
operation_id = "change",
request_body = ChangeRequest,
responses(
(status = 200, description = "Mutation results", body = ChangeOutput),
@ -800,6 +805,7 @@ async fn server_change(
post,
path = "/schema/apply",
tag = "mutations",
operation_id = "applySchema",
request_body = SchemaApplyRequest,
responses(
(status = 200, description = "Schema apply results", body = SchemaApplyOutput),
@ -838,6 +844,7 @@ async fn server_schema_apply(
post,
path = "/ingest",
tag = "mutations",
operation_id = "ingest",
request_body = IngestRequest,
responses(
(status = 200, description = "Ingest results", body = IngestOutput),
@ -907,6 +914,7 @@ async fn server_ingest(
get,
path = "/branches",
tag = "branches",
operation_id = "listBranches",
responses(
(status = 200, description = "List of branches", body = BranchListOutput),
(status = 401, description = "Unauthorized", body = ErrorOutput),
@ -943,6 +951,7 @@ async fn server_branch_list(
post,
path = "/branches",
tag = "branches",
operation_id = "createBranch",
request_body = BranchCreateRequest,
responses(
(status = 200, description = "Branch created", body = BranchCreateOutput),
@ -990,6 +999,7 @@ async fn server_branch_create(
delete,
path = "/branches/{branch}",
tag = "branches",
operation_id = "deleteBranch",
params(
("branch" = String, Path, description = "Branch name to delete"),
),
@ -1034,6 +1044,7 @@ async fn server_branch_delete(
post,
path = "/branches/merge",
tag = "branches",
operation_id = "mergeBranches",
request_body = BranchMergeRequest,
responses(
(status = 200, description = "Branches merged", body = BranchMergeOutput),
@ -1079,6 +1090,7 @@ async fn server_branch_merge(
get,
path = "/runs",
tag = "runs",
operation_id = "listRuns",
responses(
(status = 200, description = "List of runs", body = RunListOutput),
(status = 401, description = "Unauthorized", body = ErrorOutput),
@ -1116,6 +1128,7 @@ async fn server_run_list(
get,
path = "/runs/{run_id}",
tag = "runs",
operation_id = "getRun",
params(
("run_id" = String, Path, description = "Run identifier"),
),
@ -1158,6 +1171,7 @@ async fn server_run_show(
post,
path = "/runs/{run_id}/publish",
tag = "runs",
operation_id = "publishRun",
params(
("run_id" = String, Path, description = "Run identifier"),
),
@ -1207,6 +1221,7 @@ async fn server_run_publish(
post,
path = "/runs/{run_id}/abort",
tag = "runs",
operation_id = "abortRun",
params(
("run_id" = String, Path, description = "Run identifier"),
),
@ -1255,6 +1270,7 @@ async fn server_run_abort(
get,
path = "/commits",
tag = "commits",
operation_id = "listCommits",
params(CommitListQuery),
responses(
(status = 200, description = "List of commits", body = CommitListOutput),
@ -1296,6 +1312,7 @@ async fn server_commit_list(
get,
path = "/commits/{commit_id}",
tag = "commits",
operation_id = "getCommit",
params(
("commit_id" = String, Path, description = "Commit identifier"),
),

View file

@ -962,3 +962,28 @@ async fn auth_mode_healthz_still_has_no_security() {
"auth-mode: /healthz should still have no security"
);
}
#[test]
fn openapi_spec_is_up_to_date() {
let spec_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../../openapi.json");
let generated = serde_json::to_string_pretty(&openapi_doc()).unwrap() + "\n";
if env::var("OMNIGRAPH_UPDATE_OPENAPI").is_ok() {
fs::write(&spec_path, &generated).unwrap();
return;
}
let committed = fs::read_to_string(&spec_path).unwrap_or_else(|_| {
panic!(
"openapi.json not found at {}. Run: OMNIGRAPH_UPDATE_OPENAPI=1 cargo test -p omnigraph-server --test openapi openapi_spec_is_up_to_date",
spec_path.display()
)
});
assert_eq!(
committed, generated,
"openapi.json is out of date. Run: OMNIGRAPH_UPDATE_OPENAPI=1 cargo test -p omnigraph-server --test openapi openapi_spec_is_up_to_date"
);
}

1771
openapi.json Normal file

File diff suppressed because it is too large Load diff