Packages
oaspec
0.4.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@openapi@hoist.erl
-module(oaspec@openapi@hoist).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/oaspec/openapi/hoist.gleam").
-export([hoist/1]).
-export_type([hoist_state/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-type hoist_state() :: {hoist_state,
gleam@dict:dict(binary(), oaspec@openapi@schema:schema_ref()),
gleam@dict:dict(binary(), nil),
gleam@dict:dict(binary(), nil)}.
-file("src/oaspec/openapi/hoist.gleam", 80).
?DOC(" Determine if a SchemaObject is complex and should be hoisted.\n").
-spec needs_hoisting(oaspec@openapi@schema:schema_object()) -> boolean().
needs_hoisting(Schema_obj) ->
case Schema_obj of
{object_schema, _, _, _, _, _, _} ->
true;
{all_of_schema, _, _} ->
true;
{one_of_schema, _, _, _} ->
true;
{any_of_schema, _, _} ->
true;
{array_schema, _, {inline, Inner}, _, _, _} ->
needs_hoisting(Inner);
_ ->
false
end.
-file("src/oaspec/openapi/hoist.gleam", 119).
?DOC(" Try incrementing suffixes until a unique name is found.\n").
-spec find_unique_name(binary(), integer(), hoist_state()) -> {binary(),
hoist_state()}.
find_unique_name(Base_name, Suffix, State) ->
Candidate = <<Base_name/binary, (erlang:integer_to_binary(Suffix))/binary>>,
Type_name = oaspec@util@naming:schema_to_type_name(Candidate),
case gleam@dict:has_key(erlang:element(3, State), Candidate) orelse gleam@dict:has_key(
erlang:element(4, State),
Type_name
) of
false ->
State@1 = {hoist_state,
erlang:element(2, State),
gleam@dict:insert(erlang:element(3, State), Candidate, nil),
gleam@dict:insert(erlang:element(4, State), Type_name, nil)},
{Candidate, State@1};
true ->
find_unique_name(Base_name, Suffix + 1, State)
end.
-file("src/oaspec/openapi/hoist.gleam", 92).
?DOC(" Generate a unique schema name, handling collisions by appending 2, 3, etc.\n").
-spec make_unique_name(binary(), hoist_state()) -> {binary(), hoist_state()}.
make_unique_name(Base_name, State) ->
Type_name = oaspec@util@naming:schema_to_type_name(Base_name),
case gleam@dict:has_key(erlang:element(3, State), Base_name) orelse gleam@dict:has_key(
erlang:element(4, State),
Type_name
) of
false ->
State@1 = {hoist_state,
erlang:element(2, State),
gleam@dict:insert(erlang:element(3, State), Base_name, nil),
gleam@dict:insert(erlang:element(4, State), Type_name, nil)},
{Base_name, State@1};
true ->
find_unique_name(Base_name, 2, State)
end.
-file("src/oaspec/openapi/hoist.gleam", 149).
?DOC(
" Hoist a single SchemaRef if it is inline and complex.\n"
" Returns the (possibly replaced) SchemaRef and updated state.\n"
).
-spec hoist_schema_ref(
oaspec@openapi@schema:schema_ref(),
binary(),
binary(),
hoist_state()
) -> {oaspec@openapi@schema:schema_ref(), hoist_state()}.
hoist_schema_ref(Schema_ref, Name_prefix, Name_suffix, State) ->
case Schema_ref of
{reference, _} ->
{Schema_ref, State};
{inline, Schema_obj} ->
case needs_hoisting(Schema_obj) of
false ->
{Schema_ref, State};
true ->
Base_name = <<(oaspec@util@naming:to_pascal_case(
Name_prefix
))/binary,
(oaspec@util@naming:to_pascal_case(Name_suffix))/binary>>,
{Hoisted_obj, State@1} = hoist_within_schema(
Schema_obj,
Base_name,
State
),
{Unique_name, State@2} = make_unique_name(
Base_name,
State@1
),
State@3 = {hoist_state,
gleam@dict:insert(
erlang:element(2, State@2),
Unique_name,
{inline, Hoisted_obj}
),
erlang:element(3, State@2),
erlang:element(4, State@2)},
Ref = <<"#/components/schemas/"/utf8, Unique_name/binary>>,
{{reference, Ref}, State@3}
end
end.
-file("src/oaspec/openapi/hoist.gleam", 189).
?DOC(" Recursively hoist within a SchemaObject's children.\n").
-spec hoist_within_schema(
oaspec@openapi@schema:schema_object(),
binary(),
hoist_state()
) -> {oaspec@openapi@schema:schema_object(), hoist_state()}.
hoist_within_schema(Schema_obj, Name_prefix, State) ->
case Schema_obj of
{object_schema,
Description,
Properties,
Required,
Additional_properties,
Additional_properties_untyped,
Nullable} ->
{New_props, State@3} = begin
_pipe = maps:to_list(Properties),
gleam@list:fold(
_pipe,
{maps:new(), State},
fun(Acc, Entry) ->
{Props_acc, State@1} = Acc,
{Prop_name, Prop_ref} = Entry,
{Hoisted_ref, State@2} = hoist_schema_ref(
Prop_ref,
Name_prefix,
Prop_name,
State@1
),
{gleam@dict:insert(Props_acc, Prop_name, Hoisted_ref),
State@2}
end
)
end,
{New_ap, State@5} = case Additional_properties of
{some, Ap_ref} ->
{Hoisted, State@4} = hoist_schema_ref(
Ap_ref,
Name_prefix,
<<"Value"/utf8>>,
State@3
),
{{some, Hoisted}, State@4};
none ->
{none, State@3}
end,
Result = {object_schema,
Description,
New_props,
Required,
New_ap,
Additional_properties_untyped,
Nullable},
{Result, State@5};
{array_schema, Description@1, Items, Min_items, Max_items, Nullable@1} ->
{Hoisted_items, State@6} = hoist_schema_ref(
Items,
Name_prefix,
<<"Item"/utf8>>,
State
),
{{array_schema,
Description@1,
Hoisted_items,
Min_items,
Max_items,
Nullable@1},
State@6};
{one_of_schema, Description@2, Schemas, Discriminator} ->
{Hoisted_schemas_rev, State@9} = gleam@list:index_fold(
Schemas,
{[], State},
fun(Acc@1, S_ref, Idx) ->
{Schemas_acc, State@7} = Acc@1,
Suffix = <<"Variant"/utf8,
(erlang:integer_to_binary(Idx))/binary>>,
{Hoisted@1, State@8} = hoist_schema_ref(
S_ref,
Name_prefix,
Suffix,
State@7
),
{[Hoisted@1 | Schemas_acc], State@8}
end
),
{{one_of_schema,
Description@2,
lists:reverse(Hoisted_schemas_rev),
Discriminator},
State@9};
{any_of_schema, Description@3, Schemas@1} ->
{Hoisted_schemas_rev@1, State@12} = gleam@list:index_fold(
Schemas@1,
{[], State},
fun(Acc@2, S_ref@1, Idx@1) ->
{Schemas_acc@1, State@10} = Acc@2,
Suffix@1 = <<"Variant"/utf8,
(erlang:integer_to_binary(Idx@1))/binary>>,
{Hoisted@2, State@11} = hoist_schema_ref(
S_ref@1,
Name_prefix,
Suffix@1,
State@10
),
{[Hoisted@2 | Schemas_acc@1], State@11}
end
),
{{any_of_schema,
Description@3,
lists:reverse(Hoisted_schemas_rev@1)},
State@12};
{all_of_schema, Description@4, Schemas@2} ->
{Hoisted_schemas_rev@2, State@15} = gleam@list:index_fold(
Schemas@2,
{[], State},
fun(Acc@3, S_ref@2, Idx@2) ->
{Schemas_acc@2, State@13} = Acc@3,
Suffix@2 = <<"Part"/utf8,
(erlang:integer_to_binary(Idx@2))/binary>>,
{Hoisted@3, State@14} = hoist_schema_ref(
S_ref@2,
Name_prefix,
Suffix@2,
State@13
),
{[Hoisted@3 | Schemas_acc@2], State@14}
end
),
{{all_of_schema,
Description@4,
lists:reverse(Hoisted_schemas_rev@2)},
State@15};
_ ->
{Schema_obj, State}
end.
-file("src/oaspec/openapi/hoist.gleam", 305).
?DOC(" Hoist schemas within component schemas.\n").
-spec hoist_component_schemas(
gleam@dict:dict(binary(), oaspec@openapi@schema:schema_ref()),
hoist_state()
) -> {gleam@dict:dict(binary(), oaspec@openapi@schema:schema_ref()),
hoist_state()}.
hoist_component_schemas(Schemas, State) ->
_pipe = maps:to_list(Schemas),
gleam@list:fold(
_pipe,
{maps:new(), State},
fun(Acc, Entry) ->
{Result, State@1} = Acc,
{Name, Schema_ref} = Entry,
case Schema_ref of
{inline, Schema_obj} ->
{Hoisted_obj, State@2} = hoist_within_schema(
Schema_obj,
Name,
State@1
),
{gleam@dict:insert(Result, Name, {inline, Hoisted_obj}),
State@2};
{reference, _} ->
{gleam@dict:insert(Result, Name, Schema_ref), State@1}
end
end
).
-file("src/oaspec/openapi/hoist.gleam", 404).
?DOC(" Hoist schemas within a RequestBody.\n").
-spec hoist_request_body(
oaspec@openapi@spec:request_body(),
binary(),
hoist_state()
) -> {oaspec@openapi@spec:request_body(), hoist_state()}.
hoist_request_body(Rb, Op_id, State) ->
{New_content, State@3} = begin
_pipe = maps:to_list(erlang:element(3, Rb)),
gleam@list:fold(
_pipe,
{maps:new(), State},
fun(Acc, Entry) ->
{Result, State@1} = Acc,
{Media_type_name, Media_type} = Entry,
case erlang:element(2, Media_type) of
{some, Schema_ref} ->
{Hoisted, State@2} = hoist_schema_ref(
Schema_ref,
Op_id,
<<"Request"/utf8>>,
State@1
),
Mt = {media_type, {some, Hoisted}},
{gleam@dict:insert(Result, Media_type_name, Mt),
State@2};
none ->
{gleam@dict:insert(Result, Media_type_name, Media_type),
State@1}
end
end
)
end,
{{request_body, erlang:element(2, Rb), New_content, erlang:element(4, Rb)},
State@3}.
-file("src/oaspec/openapi/hoist.gleam", 428).
?DOC(" Hoist schemas within response definitions.\n").
-spec hoist_responses(
gleam@dict:dict(binary(), oaspec@openapi@spec:response()),
binary(),
hoist_state()
) -> {gleam@dict:dict(binary(), oaspec@openapi@spec:response()), hoist_state()}.
hoist_responses(Responses, Op_id, State) ->
_pipe = maps:to_list(Responses),
gleam@list:fold(
_pipe,
{maps:new(), State},
fun(Acc, Entry) ->
{Result, State@1} = Acc,
{Status_code, Response} = Entry,
{New_content, State@4} = begin
_pipe@1 = maps:to_list(erlang:element(3, Response)),
gleam@list:fold(
_pipe@1,
{maps:new(), State@1},
fun(Ct_acc, Ct_entry) ->
{Ct_result, State@2} = Ct_acc,
{Media_type_name, Media_type} = Ct_entry,
case erlang:element(2, Media_type) of
{some, Schema_ref} ->
Suffix = <<"Response"/utf8,
(oaspec@util@naming:to_pascal_case(
Status_code
))/binary>>,
{Hoisted, State@3} = hoist_schema_ref(
Schema_ref,
Op_id,
Suffix,
State@2
),
Mt = {media_type, {some, Hoisted}},
{gleam@dict:insert(
Ct_result,
Media_type_name,
Mt
),
State@3};
none ->
{gleam@dict:insert(
Ct_result,
Media_type_name,
Media_type
),
State@2}
end
end
)
end,
Hoisted_response = {response,
erlang:element(2, Response),
New_content},
{gleam@dict:insert(Result, Status_code, Hoisted_response), State@4}
end
).
-file("src/oaspec/openapi/hoist.gleam", 382).
?DOC(" Hoist schemas within a single Operation.\n").
-spec hoist_operation(oaspec@openapi@spec:operation(), binary(), hoist_state()) -> {oaspec@openapi@spec:operation(),
hoist_state()}.
hoist_operation(Operation, Op_id, State) ->
{Request_body, State@2} = case erlang:element(7, Operation) of
none ->
{none, State};
{some, Rb} ->
{Hoisted_rb, State@1} = hoist_request_body(Rb, Op_id, State),
{{some, Hoisted_rb}, State@1}
end,
{Responses, State@3} = hoist_responses(
erlang:element(8, Operation),
Op_id,
State@2
),
Result = {operation,
erlang:element(2, Operation),
erlang:element(3, Operation),
erlang:element(4, Operation),
erlang:element(5, Operation),
erlang:element(6, Operation),
Request_body,
Responses,
erlang:element(9, Operation),
erlang:element(10, Operation)},
{Result, State@3}.
-file("src/oaspec/openapi/hoist.gleam", 357).
?DOC(" Hoist schemas in an optional operation.\n").
-spec hoist_maybe_operation(
gleam@option:option(oaspec@openapi@spec:operation()),
binary(),
binary(),
hoist_state()
) -> {gleam@option:option(oaspec@openapi@spec:operation()), hoist_state()}.
hoist_maybe_operation(Maybe_op, Method, Path, State) ->
case Maybe_op of
none ->
{none, State};
{some, Operation} ->
Op_id = case erlang:element(2, Operation) of
{some, Id} ->
Id;
none ->
<<<<Method/binary, "_"/utf8>>/binary,
(begin
_pipe = gleam@string:replace(
Path,
<<"/"/utf8>>,
<<"_"/utf8>>
),
_pipe@1 = gleam@string:replace(
_pipe,
<<"{"/utf8>>,
<<""/utf8>>
),
gleam@string:replace(
_pipe@1,
<<"}"/utf8>>,
<<""/utf8>>
)
end)/binary>>
end,
{Hoisted_op, State@1} = hoist_operation(Operation, Op_id, State),
{{some, Hoisted_op}, State@1}
end.
-file("src/oaspec/openapi/hoist.gleam", 338).
?DOC(" Hoist schemas within a single PathItem.\n").
-spec hoist_path_item(oaspec@openapi@spec:path_item(), binary(), hoist_state()) -> {oaspec@openapi@spec:path_item(),
hoist_state()}.
hoist_path_item(Path_item, Path, State) ->
{Get, State@1} = hoist_maybe_operation(
erlang:element(4, Path_item),
<<"get"/utf8>>,
Path,
State
),
{Post, State@2} = hoist_maybe_operation(
erlang:element(5, Path_item),
<<"post"/utf8>>,
Path,
State@1
),
{Put, State@3} = hoist_maybe_operation(
erlang:element(6, Path_item),
<<"put"/utf8>>,
Path,
State@2
),
{Delete, State@4} = hoist_maybe_operation(
erlang:element(7, Path_item),
<<"delete"/utf8>>,
Path,
State@3
),
{Patch, State@5} = hoist_maybe_operation(
erlang:element(8, Path_item),
<<"patch"/utf8>>,
Path,
State@4
),
Result = {path_item,
erlang:element(2, Path_item),
erlang:element(3, Path_item),
Get,
Post,
Put,
Delete,
Patch,
erlang:element(9, Path_item)},
{Result, State@5}.
-file("src/oaspec/openapi/hoist.gleam", 324).
?DOC(" Walk all paths and operations, hoisting inline schemas.\n").
-spec hoist_paths(
gleam@dict:dict(binary(), oaspec@openapi@spec:path_item()),
hoist_state()
) -> {gleam@dict:dict(binary(), oaspec@openapi@spec:path_item()), hoist_state()}.
hoist_paths(Paths, State) ->
_pipe = maps:to_list(Paths),
gleam@list:fold(
_pipe,
{maps:new(), State},
fun(Acc, Entry) ->
{Result, State@1} = Acc,
{Path, Path_item} = Entry,
{Hoisted_item, State@2} = hoist_path_item(Path_item, Path, State@1),
{gleam@dict:insert(Result, Path, Hoisted_item), State@2}
end
).
-file("src/oaspec/openapi/hoist.gleam", 30).
?DOC(
" Hoist inline complex schemas into components.schemas, replacing them with $ref.\n"
" This is a pre-processing pass that runs after parsing and before validation.\n"
" The function is idempotent: running it twice produces the same result.\n"
).
-spec hoist(oaspec@openapi@spec:open_api_spec()) -> oaspec@openapi@spec:open_api_spec().
hoist(Spec) ->
Existing_schemas = case erlang:element(5, Spec) of
{some, Components} ->
erlang:element(2, Components);
none ->
maps:new()
end,
Existing_names = begin
_pipe = maps:keys(Existing_schemas),
_pipe@1 = gleam@list:map(_pipe, fun(K) -> {K, nil} end),
maps:from_list(_pipe@1)
end,
Existing_type_names = begin
_pipe@2 = maps:keys(Existing_schemas),
_pipe@3 = gleam@list:map(
_pipe@2,
fun(K@1) -> {oaspec@util@naming:schema_to_type_name(K@1), nil} end
),
maps:from_list(_pipe@3)
end,
State = {hoist_state, maps:new(), Existing_names, Existing_type_names},
{Hoisted_component_schemas, State@1} = hoist_component_schemas(
Existing_schemas,
State
),
{Hoisted_paths, State@2} = hoist_paths(erlang:element(4, Spec), State@1),
Final_schemas = maps:merge(
Hoisted_component_schemas,
erlang:element(2, State@2)
),
Components@1 = case erlang:element(5, Spec) of
{some, C} ->
{some,
{components,
Final_schemas,
erlang:element(3, C),
erlang:element(4, C),
erlang:element(5, C),
erlang:element(6, C)}};
none ->
case gleam@dict:is_empty(erlang:element(2, State@2)) of
true ->
none;
false ->
{some,
{components,
Final_schemas,
maps:new(),
maps:new(),
maps:new(),
maps:new()}}
end
end,
{open_api_spec,
erlang:element(2, Spec),
erlang:element(3, Spec),
Hoisted_paths,
Components@1,
erlang:element(6, Spec),
erlang:element(7, Spec)}.