Packages
oaspec
0.54.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/internal/openapi/operations.gleam
import gleam/dict
import gleam/list
import gleam/option.{None, Some}
import gleam/string
import oaspec/internal/openapi/spec.{
type HttpMethod, type OpenApiSpec, type Operation, type Resolved,
}
/// Collect all operations from the spec with their IDs, paths, and methods.
/// Handles path-level parameter inheritance, security inheritance, and
/// operationId generation from method + path when not specified.
///
/// Pure: depends only on the resolved spec, not on the codegen Context.
/// The Context now precomputes this list once at construction time and
/// every codegen pass reads it through `context.operations/1` instead of
/// re-running this traversal (issue #371).
pub fn collect_operations(
resolved_spec: OpenApiSpec(Resolved),
) -> List(#(String, Operation(Resolved), String, HttpMethod)) {
let paths =
list.sort(dict.to_list(resolved_spec.paths), fn(a, b) {
string.compare(a.0, b.0)
})
list.flat_map(paths, fn(entry) {
let #(path, ref_or) = entry
// All path-level refs are resolved by this point
let path_item = spec.unwrap_ref(ref_or)
{
let ops = [
#(path_item.get, spec.Get),
#(path_item.post, spec.Post),
#(path_item.put, spec.Put),
#(path_item.delete, spec.Delete),
#(path_item.patch, spec.Patch),
#(path_item.head, spec.Head),
#(path_item.options, spec.Options),
#(path_item.trace, spec.Trace),
]
list.filter_map(ops, fn(op_entry) {
let #(maybe_op, method) = op_entry
case maybe_op {
Some(operation) -> {
// Merge path-level parameters with operation parameters.
// Operation params take precedence by (name, in) key per OpenAPI spec.
let op_param_keys =
list.map(operation.parameters, fn(ref_p) {
let parameter = spec.unwrap_ref(ref_p)
#(parameter.name, parameter.in_)
})
let inherited_params =
list.filter(path_item.parameters, fn(ref_p) {
let parameter = spec.unwrap_ref(ref_p)
!list.contains(op_param_keys, #(parameter.name, parameter.in_))
})
let merged_params =
list.append(inherited_params, operation.parameters)
// Inherit top-level security if operation doesn't define its own.
// operation.security = None → inherit, Some([]) → no security,
// Some([...]) → use operation-level.
let effective_security = case operation.security {
Some(sec) -> sec
None -> resolved_spec.security
}
// Inherit path-level servers when operation doesn't define its own.
// OpenAPI precedence: operation.servers > path_item.servers > spec.servers
let effective_servers = case operation.servers {
[_, ..] -> operation.servers
[] -> path_item.servers
}
let operation =
spec.Operation(
..operation,
parameters: merged_params,
security: Some(effective_security),
servers: effective_servers,
)
let op_id = case operation.operation_id {
Some(id) -> id
None ->
spec.method_to_lower(method)
<> "_"
<> string.replace(path, "/", "_")
|> string.replace("{", "")
|> string.replace("}", "")
}
Ok(#(op_id, operation, path, method))
}
None -> Error(Nil)
}
})
}
})
}