Packages
oaspec
0.17.0
0.68.0
0.67.0
0.66.0
0.65.0
0.64.0
0.63.0
0.62.0
0.61.0
0.60.0
0.59.0
0.58.1
0.58.0
0.57.0
0.56.0
0.55.0
0.54.0
0.53.0
0.52.0
0.51.0
0.50.0
0.49.0
0.48.0
0.47.0
0.46.0
0.45.0
0.44.0
0.43.0
0.42.0
0.41.0
0.40.0
0.39.0
0.38.0
0.37.0
0.36.0
0.35.0
0.34.0
0.33.0
0.32.0
0.31.0
0.30.0
0.29.0
0.28.0
0.27.0
0.26.0
0.25.0
0.24.0
0.23.0
0.22.0
0.21.0
0.20.0
0.19.0
0.18.0
0.17.0
0.16.0
0.15.0
0.14.0
0.13.0
0.12.0
0.11.0
0.10.0
0.9.0
0.8.0
0.7.0
0.6.3
0.6.1
0.6.0
0.5.0
0.4.0
0.3.0
0.1.3
Generate Gleam code from OpenAPI 3.x specifications
Current section
Files
Jump to
Current section
Files
src/oaspec/generate.gleam
import gleam/list
import gleam/result
import oaspec/codegen/client
import oaspec/codegen/context.{type Context, type GeneratedFile}
import oaspec/codegen/decoders
import oaspec/codegen/encoders
import oaspec/codegen/guards
import oaspec/codegen/server
import oaspec/codegen/types
import oaspec/codegen/validate
import oaspec/config.{type Config, Both, Client, Server}
import oaspec/openapi/capability_check
import oaspec/openapi/dedup
import oaspec/openapi/diagnostic.{type Diagnostic}
import oaspec/openapi/hoist
import oaspec/openapi/normalize
import oaspec/openapi/resolve
import oaspec/openapi/spec.{type OpenApiSpec, type Unresolved}
/// Result of a successful code generation run.
pub type GenerationSummary {
GenerationSummary(
files: List(GeneratedFile),
spec_title: String,
warnings: List(Diagnostic),
)
}
/// Result of a successful validation-only run.
pub type ValidationSummary {
ValidationSummary(spec_title: String, warnings: List(Diagnostic))
}
/// Errors from the pure generation pipeline.
pub type GenerateError {
ValidationErrors(errors: List(Diagnostic))
}
/// Intermediate result from the shared pipeline.
/// Contains the validated context and accumulated warnings.
type PreparedContext {
PreparedContext(ctx: Context, spec_title: String, warnings: List(Diagnostic))
}
/// Shared pipeline: normalize → resolve → capability_check → hoist → dedup → validate.
/// Returns a validated context with accumulated warnings, or errors.
fn prepare_context(
spec: OpenApiSpec(Unresolved),
cfg: Config,
) -> Result(PreparedContext, GenerateError) {
let spec_title = spec.info.title <> " v" <> spec.info.version
// Normalize OAS 3.1 patterns to 3.0-compatible form
let spec = normalize.normalize(spec)
// Resolve component entry aliases ($ref within components)
use spec <- result.try(
resolve.resolve(spec)
|> result.map_error(fn(errors) { ValidationErrors(errors:) }),
)
// Check for unsupported features using capability registry
let capability_issues =
capability_check.check(spec)
|> validate.filter_by_mode(config.mode(cfg))
let capability_errors = validate.errors_only(capability_issues)
let capability_warnings = validate.warnings_only(capability_issues)
use _ <- result.try(case list.is_empty(capability_errors) {
False -> Error(ValidationErrors(errors: capability_errors))
True -> Ok(Nil)
})
// Hoist inline complex schemas into components.schemas
let spec = hoist.hoist(spec)
// Deduplicate names to avoid collisions in generated code
let spec = dedup.dedup(spec)
// Create generation context
let ctx = context.new(spec, cfg)
// Check for parsed-but-unused features (capability warnings)
let preserved_warnings =
capability_check.check_preserved(ctx)
|> diagnostic.filter_by_mode(config.mode(cfg))
// Validate spec for unsupported features
let validation_issues =
validate.validate(ctx)
|> validate.filter_by_mode(config.mode(cfg))
let blocking_errors = validate.errors_only(validation_issues)
let validation_warnings = validate.warnings_only(validation_issues)
use _ <- result.try(case list.is_empty(blocking_errors) {
False -> Error(ValidationErrors(errors: blocking_errors))
True -> Ok(Nil)
})
let warnings =
list.flatten([capability_warnings, preserved_warnings, validation_warnings])
Ok(PreparedContext(ctx:, spec_title:, warnings:))
}
/// Pure generation pipeline: parse → normalize → resolve → capability_check → hoist → dedup → validate → codegen.
/// Takes an already-parsed spec and config; returns generated files or errors.
/// Does not perform IO — callers handle writing files and printing output.
pub fn generate(
spec: OpenApiSpec(Unresolved),
cfg: Config,
) -> Result(GenerationSummary, GenerateError) {
use prepared <- result.try(prepare_context(spec, cfg))
let files = generate_all_files(prepared.ctx)
Ok(GenerationSummary(
files:,
spec_title: prepared.spec_title,
warnings: prepared.warnings,
))
}
/// Validation-only pipeline: parse → normalize → resolve → capability_check → hoist → dedup → validate.
/// Runs the same checks as generate() but skips code generation and file writing.
pub fn validate_only(
spec: OpenApiSpec(Unresolved),
cfg: Config,
) -> Result(ValidationSummary, GenerateError) {
use prepared <- result.try(prepare_context(spec, cfg))
Ok(ValidationSummary(
spec_title: prepared.spec_title,
warnings: prepared.warnings,
))
}
/// Pure file generation: produce all GeneratedFile values without any IO.
pub fn generate_all_files(ctx: Context) -> List(GeneratedFile) {
let shared = generate_shared(ctx)
let server_files = case config.mode(context.config(ctx)) {
Server | Both -> server.generate(ctx)
Client -> []
}
let client_files = case config.mode(context.config(ctx)) {
Client | Both -> client.generate(ctx)
Server -> []
}
list.flatten([shared, server_files, client_files])
}
/// Generate shared files (types, decoders, encoders, guards).
///
/// `middleware.gleam` used to be emitted here too, but its `Handler` shape
/// did not actually compose with the generated client or server APIs (see
/// issue #116). It is no longer part of the default generated surface;
/// the `oaspec/codegen/middleware` module is kept only as a library-level
/// helper for consumers who want to assemble their own middleware chain.
fn generate_shared(ctx: Context) -> List(GeneratedFile) {
let type_files = types.generate(ctx)
let decoder_files = decoders.generate(ctx)
let encoder_files = encoders.generate(ctx)
let guard_files = guards.generate(ctx)
list.flatten([type_files, decoder_files, encoder_files, guard_files])
}