Skip to content

Commit a081751

Browse files
hyperpolymathclaude
andcommitted
fix(rescript-openapi): code cleanup, attribution fixes, and documentation updates
Fix all clippy warnings (collapsible if-let, unused params, too-many-args), remove dead codegen::generate function and duplicate StringEnum branches, correct SPDX copyright headers and Cargo.toml author attribution, update README with missing CLI flags and fix module import path, and bring STATE.scm up to date with actual test counts and session history. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 1f44268 commit a081751

12 files changed

Lines changed: 112 additions & 125 deletions

File tree

rescript-ecosystem/packages/bindings/openapi/.machines_readable/6scm/STATE.scm

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
(version "0.1.0")
77
(schema-version "1.0")
88
(created "2025-01-05")
9-
(updated "2026-01-05")
9+
(updated "2026-03-03")
1010
(project "rescript-openapi")
1111
(repo "github.com/hyperpolymath/rescript-openapi"))
1212

@@ -37,7 +37,7 @@
3737
"Authentication (Bearer token, API key)"
3838
"File watching with --watch flag"
3939
"Dry run with --dry-run flag"
40-
"Snapshot tests with insta (6 passing)"))
40+
"Snapshot tests with insta (12 tests passing, 13 snapshots)"))
4141

4242
(route-to-mvp
4343
(milestone "m1-compiles" (status completed)
@@ -96,4 +96,24 @@
9696
"Security audit passed (0 vulnerabilities)"
9797
"Performance tested (<10ms generation)"
9898
"All 6 tests passing"
99-
"Ready for crates.io publish"))))
99+
"Ready for crates.io publish"))
100+
(session "2026-02-24-dual-track"
101+
(accomplishments
102+
"Implemented dual-track architecture (OOTB vs Streamlined)"
103+
"Added --client-mode flag (full, functor-only, none)"
104+
"Added --unified flag for single-module output"
105+
"Added --variant-mode flag (polymorphic, standard)"
106+
"Added rescript-openapi.toml project config support"
107+
"ADR-001: Dual-Track Generation Architecture documented"
108+
"All 12 tests passing, 13 snapshots"))
109+
(session "2026-03-03-cleanup"
110+
(accomplishments
111+
"Fixed 3 clippy warnings (collapsible if-let, unused param, too-many-args)"
112+
"Removed dead codegen::generate function"
113+
"Fixed duplicate StringEnum branches in RsType::to_rescript"
114+
"Fixed all SPDX copyright headers (2025 Hyperpolymath -> 2026 Jonathan D.A. Jewell)"
115+
"Fixed Cargo.toml author attribution"
116+
"Updated README with missing CLI flags"
117+
"Fixed README module import (RescriptSchema -> RescriptSchema.S)"
118+
"Added SPDX header to DECISIONS.md"
119+
"Updated STATE.scm with correct test counts and session history"))))

rescript-ecosystem/packages/bindings/openapi/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
# SPDX-License-Identifier: PMPL-1.0-or-later
2-
# SPDX-FileCopyrightText: 2025 Hyperpolymath
2+
# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell
33

44
[package]
55
name = "rescript-openapi"
66
version = "0.1.0"
77
edition = "2021"
8-
authors = ["Hyperpolymath <hello@hyperpolymath.com>"]
8+
authors = ["Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>"]
99
description = "Generate type-safe ReScript clients from OpenAPI specifications"
1010
readme = "README.adoc"
1111
license = "PMPL-1.0-or-later"

rescript-ecosystem/packages/bindings/openapi/DECISIONS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
<!-- SPDX-License-Identifier: PMPL-1.0-or-later -->
2+
<!-- SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell -->
3+
14
# Architectural Decisions - rescript-openapi
25

36
## Dual-Track Generation: OOTB vs. Streamlined

rescript-ecosystem/packages/bindings/openapi/README.adoc

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// SPDX-License-Identifier: PMPL-1.0-or-later
2-
// SPDX-FileCopyrightText: 2025 Hyperpolymath
2+
// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell
33

44
= rescript-openapi
55
:toc:
@@ -72,6 +72,30 @@ This generates:
7272
| `--with-client`
7373
| Generate HTTP client
7474
| `true`
75+
76+
| `--unified`
77+
| Generate single module with types + schemas
78+
| `false`
79+
80+
| `--client-mode`
81+
| Client generation: `full`, `functor-only`, `none`
82+
| `full`
83+
84+
| `--variant-mode`
85+
| Variant style: `polymorphic`, `standard`
86+
| `polymorphic`
87+
88+
| `-c, --config`
89+
| Path to `rescript-openapi.toml` config file
90+
| `rescript-openapi.toml`
91+
92+
| `-w, --watch`
93+
| Watch input file and regenerate on change
94+
| `false`
95+
96+
| `--dry-run`
97+
| Print generated code to stdout
98+
| `false`
7599
|===
76100

77101
=== Validate Spec
@@ -113,7 +137,7 @@ type userRole = [
113137

114138
[source,rescript]
115139
----
116-
module S = RescriptSchema
140+
module S = RescriptSchema.S
117141
118142
let userSchema: S.t<user> = S.object(s => ({
119143
id: s.field("id", S.string),

rescript-ecosystem/packages/bindings/openapi/src/codegen/client.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// SPDX-License-Identifier: PMPL-1.0-or-later
2-
// SPDX-FileCopyrightText: 2025 Hyperpolymath
2+
// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell
33

44
//! HTTP client generation with pluggable HTTP backend
55
@@ -179,7 +179,7 @@ module Make = (Http: HttpClient) => {
179179

180180
// Generate endpoint functions inside the functor
181181
for endpoint in &spec.endpoints {
182-
output.push_str(&generate_endpoint(endpoint, config));
182+
output.push_str(&generate_endpoint(endpoint));
183183
output.push('\n');
184184
}
185185

@@ -205,7 +205,7 @@ module Make = (Http: HttpClient) => {
205205
Ok(output)
206206
}
207207

208-
fn generate_endpoint(endpoint: &Endpoint, config: &Config) -> String {
208+
fn generate_endpoint(endpoint: &Endpoint) -> String {
209209
let mut output = String::new();
210210

211211
// Documentation
@@ -230,17 +230,17 @@ fn generate_endpoint(endpoint: &Endpoint, config: &Config) -> String {
230230
let mut params = vec!["config: config".to_string()];
231231

232232
for p in &path_params {
233-
params.push(format!("~{}: {}", p.name, p.ty.to_rescript(config)));
233+
params.push(format!("~{}: {}", p.name, p.ty.to_rescript()));
234234
}
235235

236236
if let Some(body) = &endpoint.request_body {
237-
params.push(format!("~body: {}", body.ty.to_rescript(config)));
237+
params.push(format!("~body: {}", body.ty.to_rescript()));
238238
}
239239

240240
// Optional query parameters
241241
for p in &query_params {
242242
if p.required {
243-
params.push(format!("~{}: {}", p.name, p.ty.to_rescript(config)));
243+
params.push(format!("~{}: {}", p.name, p.ty.to_rescript()));
244244
} else {
245245
params.push(format!("~{}=?", p.name));
246246
}
@@ -249,7 +249,7 @@ fn generate_endpoint(endpoint: &Endpoint, config: &Config) -> String {
249249
// Optional header parameters
250250
for p in &header_params {
251251
if p.required {
252-
params.push(format!("~{}: {}", p.name, p.ty.to_rescript(config)));
252+
params.push(format!("~{}: {}", p.name, p.ty.to_rescript()));
253253
} else {
254254
params.push(format!("~{}=?", p.name));
255255
}
@@ -261,7 +261,7 @@ fn generate_endpoint(endpoint: &Endpoint, config: &Config) -> String {
261261

262262
let return_type = success_response
263263
.and_then(|r| r.ty.as_ref())
264-
.map(|t| t.to_rescript(config))
264+
.map(|t| t.to_rescript())
265265
.unwrap_or_else(|| "unit".to_string());
266266

267267
output.push_str(&format!(

rescript-ecosystem/packages/bindings/openapi/src/codegen/mod.rs

Lines changed: 1 addition & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// SPDX-License-Identifier: PMPL-1.0-or-later
2-
// SPDX-FileCopyrightText: 2025 Hyperpolymath
2+
// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell
33

44
//! ReScript code generation from IR
55
//!
@@ -12,7 +12,6 @@ pub mod client;
1212
pub mod schema;
1313
pub mod types;
1414

15-
use crate::ir::ApiSpec;
1615
use anyhow::Result;
1716
use serde::{Deserialize, Serialize};
1817
use std::fs;
@@ -68,52 +67,3 @@ impl ProjectConfig {
6867
}
6968
}
7069

71-
/// Generate ReScript code from IR
72-
pub fn generate(spec: &ApiSpec, config: &Config) -> Result<()> {
73-
fs::create_dir_all(&config.output_dir)?;
74-
75-
if config.unified_module {
76-
// Generate Unified Module (Types + Schemas)
77-
let mut output = String::new();
78-
output.push_str("// SPDX-License-Identifier: PMPL-1.0-or-later\n");
79-
output.push_str("// Generated by rescript-openapi - DO NOT EDIT\n");
80-
output.push_str(&format!("// Source: {} v{}\n\n", spec.title, spec.version));
81-
output.push_str("open RescriptCore\n");
82-
output.push_str("module S = RescriptSchema.S\n\n");
83-
84-
let sccs = schema::topological_sort_scc(&spec.types);
85-
for scc in sccs {
86-
output.push_str(&types::generate_scc(&scc, config));
87-
for type_def in scc {
88-
output.push_str(&schema::generate_schema_only(type_def, config));
89-
output.push('\n');
90-
}
91-
output.push('\n');
92-
}
93-
94-
let path = config.output_dir.join(format!("{}.res", config.module_prefix));
95-
fs::write(&path, output)?;
96-
} else {
97-
// Generate separate files (Legacy mode)
98-
// Generate Types.res - all type definitions
99-
let types_code = types::generate(spec, config)?;
100-
let types_path = config.output_dir.join(format!("{}Types.res", config.module_prefix));
101-
fs::write(&types_path, types_code)?;
102-
103-
// Generate Schema.res - rescript-schema validators
104-
if config.generate_schema {
105-
let schema_code = schema::generate(spec, config)?;
106-
let schema_path = config.output_dir.join(format!("{}Schema.res", config.module_prefix));
107-
fs::write(&schema_path, schema_code)?;
108-
}
109-
}
110-
111-
// Generate Client.res - HTTP client functions
112-
if config.generate_client {
113-
let client_code = client::generate(spec, config)?;
114-
let client_path = config.output_dir.join(format!("{}Client.res", config.module_prefix));
115-
fs::write(&client_path, client_code)?;
116-
}
117-
118-
Ok(())
119-
}

rescript-ecosystem/packages/bindings/openapi/src/codegen/schema.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// SPDX-License-Identifier: PMPL-1.0-or-later
2-
// SPDX-FileCopyrightText: 2025 Hyperpolymath
2+
// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell
33

44
//! rescript-schema validator generation with topological sorting
55
@@ -272,6 +272,7 @@ pub fn topological_sort_scc(types: &[TypeDef]) -> Vec<Vec<&TypeDef>> {
272272
result
273273
}
274274

275+
#[allow(clippy::too_many_arguments)]
275276
fn tarjan_dfs(
276277
at: usize,
277278
adj: &Vec<Vec<usize>>,
@@ -330,7 +331,7 @@ fn generate_schema(type_def: &TypeDef, config: &Config) -> String {
330331
output.push_str(&format!("let {}: S.t<{}> = S.object(s => ({{\n", schema_name, type_name));
331332

332333
for field in fields {
333-
output.push_str(&generate_field_schema(field, config));
334+
output.push_str(&generate_field_schema(field));
334335
}
335336

336337
output.push_str(&format!("}}: {}))\n", type_name));
@@ -376,7 +377,7 @@ fn generate_schema(type_def: &TypeDef, config: &Config) -> String {
376377
// Wrap the inner schema to transform to variant constructor
377378
output.push_str(&format!(
378379
" {}->S.transform(s => {{\n parser: v => {}(v),\n serializer: v => switch v {{ | {}(x) => x | _ => S.fail(\"Expected {}\") }}\n }}),\n",
379-
ty.to_schema(config),
380+
ty.to_schema(),
380381
case.name,
381382
case.name,
382383
case.name
@@ -405,22 +406,22 @@ fn generate_schema(type_def: &TypeDef, config: &Config) -> String {
405406
output.push_str(&format!("/** Schema for {} */\n", doc));
406407
}
407408

408-
output.push_str(&format!("let {} = {}\n", schema_name, target.to_schema(config)));
409+
output.push_str(&format!("let {} = {}\n", schema_name, target.to_schema()));
409410
}
410411
}
411412

412413
output
413414
}
414415

415-
fn generate_field_schema(field: &Field, config: &Config) -> String {
416+
fn generate_field_schema(field: &Field) -> String {
416417
let method = if field.optional { "fieldOr" } else { "field" };
417418
let default = if field.optional {
418419
", None"
419420
} else {
420421
""
421422
};
422423

423-
let schema = field.ty.to_schema(config);
424+
let schema = field.ty.to_schema();
424425

425426
if field.name != field.original_name {
426427
format!(

rescript-ecosystem/packages/bindings/openapi/src/codegen/types.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// SPDX-License-Identifier: PMPL-1.0-or-later
2-
// SPDX-FileCopyrightText: 2025 Hyperpolymath
2+
// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell
33

44
//! ReScript type generation
55
@@ -89,7 +89,7 @@ pub fn generate_type(type_def: &TypeDef, is_rec: bool, config: &Config) -> Strin
8989
output.push_str(" ");
9090
}
9191

92-
output.push_str(&format!("{}: {},\n", field.name, field.ty.to_rescript(config)));
92+
output.push_str(&format!("{}: {},\n", field.name, field.ty.to_rescript()));
9393
}
9494

9595
output.push_str("}\n");
@@ -111,7 +111,7 @@ pub fn generate_type(type_def: &TypeDef, is_rec: bool, config: &Config) -> Strin
111111
for case in cases {
112112
match &case.payload {
113113
Some(ty) => {
114-
output.push_str(&format!(" | {}({})\n", case.name, ty.to_rescript(config)));
114+
output.push_str(&format!(" | {}({})\n", case.name, ty.to_rescript()));
115115
}
116116
None => {
117117
output.push_str(&format!(" | {}\n", case.name));
@@ -143,7 +143,7 @@ pub fn generate_type(type_def: &TypeDef, is_rec: bool, config: &Config) -> Strin
143143
}
144144

145145
let type_name = name.to_lower_camel_case();
146-
output.push_str(&format!("{} {} = {}\n", keyword, type_name, target.to_rescript(config)));
146+
output.push_str(&format!("{} {} = {}\n", keyword, type_name, target.to_rescript()));
147147
}
148148
}
149149

0 commit comments

Comments
 (0)