Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ After updating your OpenAPI spec, regenerate the typed scaffolding without touch
mapi generate --spec openapi.json --project ./my-api
```

`generate` treats `--project` as an existing MoonBit package root: it reads the package name from `moon.mod.json`, regenerates `src/generated/*`, and leaves your existing module manifest untouched.
Only files with the `// Code generated by mapi. DO NOT EDIT.` header are modified. Your handlers are safe.

---
Expand Down
10 changes: 8 additions & 2 deletions main/generator.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,15 @@ let generated_header = "// Code generated by mapi. DO NOT EDIT.\n"
let user_header = "// User-owned file. Safe from regeneration.\n"

///|
fn generate_project(ir : ApiIR, module_name : String) -> Map[String, String] {
fn generate_project(
ir : ApiIR,
module_name : String,
include_module_manifest : Bool,
) -> Map[String, String] {
let files = {}
files["moon.mod.json"] = generate_moon_mod(module_name)
if include_module_manifest {
files["moon.mod.json"] = generate_moon_mod(module_name)
}
files["src/generated/moon.pkg"] = generate_generated_pkg(module_name)
files["src/generated/schemas.mbt"] = generate_schemas_file(ir)
files["src/generated/operations.mbt"] = generate_operations_file(ir)
Expand Down
76 changes: 72 additions & 4 deletions main/main.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,17 @@ fn run_command(args : Array[String], mode : CommandMode) -> Unit {
{ title: "", version: "", schemas: [], operations: [] }
}
}
let module_name = infer_module_name(config.output_dir)
let files = generate_project(ir, module_name)
write_project(files, config.output_dir, mode == Generate) catch {
err => fail_with_message("Failed to write project: ", debug_string(err))
let include_module_manifest = mode == Init
match resolve_module_name(config.output_dir, include_module_manifest) {
Ok(module_name) => {
let files = generate_project(ir, module_name, include_module_manifest)
write_project(files, config.output_dir, mode == Generate) catch {
err =>
fail_with_message("Failed to write project: ", debug_string(err))
}
}
Err(message) =>
fail_with_message("Failed to resolve module name: ", message)
}
write_state(ir, config.spec_path, config.output_dir) catch {
err => fail_with_message("Failed to write state: ", debug_string(err))
Expand All @@ -67,6 +74,11 @@ struct CliConfig {
output_dir : String
}

///|
struct MoonModConfig {
name : String
} derive(FromJson)

///|
fn parse_cli_options(
args : Array[String],
Expand Down Expand Up @@ -155,6 +167,62 @@ fn infer_module_name(output_dir : String) -> String {
}
}

///|
fn resolve_module_name(
output_dir : String,
include_module_manifest : Bool,
) -> Result[String, String] {
if include_module_manifest {
Ok(infer_module_name(output_dir))
} else {
read_module_name_from_moon_mod(output_dir)
}
}

///|
fn read_module_name_from_moon_mod(
project_dir : String,
) -> Result[String, String] {
let mod_path = project_dir + "/moon.mod.json"
if !@fs.path_exists(mod_path) {
return Err("moon.mod.json not found in " + project_dir)
}
let raw_result = Ok(@fs.read_file_to_string(mod_path)) catch {
err => Err("cannot read moon.mod.json: " + debug_string(err))
}
let raw = match raw_result {
Ok(value) => value
Err(message) => return Err(message)
}
parse_module_name_from_moon_mod(raw)
}

///|
fn parse_module_name_from_moon_mod(raw : String) -> Result[String, String] {
let parsed = @json.parse(raw) catch {
_ => return Err("moon.mod.json is not valid JSON")
}
let mod_config : MoonModConfig = @json.from_json(parsed) catch {
_ => return Err("missing or invalid \"name\" field in moon.mod.json")
}
let module_name = mod_config.name.trim().to_string()
if module_name.is_empty() {
Err("empty \"name\" value in moon.mod.json")
} else if contains_whitespace(module_name) {
Err("invalid \"name\" value in moon.mod.json: contains whitespace")
} else {
Ok(module_name)
}
}

///|
fn contains_whitespace(value : String) -> Bool {
value.find(" ") is Some(_) ||
value.find("\t") is Some(_) ||
value.find("\n") is Some(_) ||
value.find("\r") is Some(_)
}

///|
fn success_message(mode : CommandMode, output_dir : String) -> String {
match mode {
Expand Down
1 change: 1 addition & 0 deletions main/moon.pkg
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
"moonbitlang/core/debug" @debug,
"moonbitlang/core/json" @json,
"moonbitlang/x/fs" @fs,
"moonbitlang/x/sys" @sys,
"cogna-dev/x/openapi" @openapi,
Expand Down
Loading