Skip to content

Commit 8a5ef7d

Browse files
author
jarrod
committed
fix: render default values in SDL output for schema.sdl task
Previously, the `mix absinthe.schema.sdl` task would not include default values for arguments and input object fields in the generated SDL output, even though the schema.json task included them in its output. Changes: - Modified SDL renderer to handle default values for InputValueDefinition (arguments) and FieldDefinition (input object fields) - Added support for DSL-defined schemas where default_value is an Elixir term that needs conversion to Blueprint.Input structures - Added support for SDL-parsed schemas where default_value_blueprint is already a Blueprint structure - Skip non-serializable values (refs, functions) Tests: - Added DefaultValueTestSchema with default value coverage to prevent regression while rendering boolean, string, and input object default values - All existing tests updated to expect default values in output
1 parent a035261 commit 8a5ef7d

4 files changed

Lines changed: 268 additions & 7 deletions

File tree

lib/absinthe/schema/notation/sdl_render.ex

Lines changed: 151 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ defmodule Absinthe.Schema.Notation.SDL.Render do
7070
string(@adapter.to_external_name(input_value.name, :argument)),
7171
": ",
7272
render(input_value.type, type_definitions),
73-
default(input_value.default_value_blueprint),
73+
default(input_value, type_definitions),
7474
directives(input_value.directives, type_definitions)
7575
])
7676
|> description(input_value.description)
@@ -107,7 +107,7 @@ defmodule Absinthe.Schema.Notation.SDL.Render do
107107
string(input_object_type.name),
108108
directives(input_object_type.directives, type_definitions)
109109
]),
110-
render_list(input_object_type.fields, type_definitions)
110+
render_input_fields(input_object_type.fields, type_definitions)
111111
)
112112
|> description(input_object_type.description)
113113
end
@@ -306,14 +306,119 @@ defmodule Absinthe.Schema.Notation.SDL.Render do
306306
)
307307
end
308308

309-
defp default(nil) do
309+
defp default(%Blueprint.Schema.FieldDefinition{default_value: nil}, _type_definitions) do
310310
empty()
311311
end
312312

313-
defp default(default_value) do
314-
concat([" = ", render_value(default_value)])
313+
defp default(%Blueprint.Schema.FieldDefinition{default_value: {:ref, _, _}}, _type_definitions) do
314+
empty()
315+
end
316+
317+
defp default(
318+
%Blueprint.Schema.FieldDefinition{type: type, default_value: default_value},
319+
type_definitions
320+
)
321+
when is_atom(default_value) do
322+
case find_enum_value_name(type, default_value, type_definitions) do
323+
{:ok, enum_name} ->
324+
concat([" = ", string(enum_name)])
325+
326+
:error ->
327+
try do
328+
blueprint_value = Blueprint.Input.parse(default_value)
329+
concat([" = ", render_value(blueprint_value)])
330+
rescue
331+
_ -> empty()
332+
end
333+
end
334+
end
335+
336+
defp default(
337+
%Blueprint.Schema.FieldDefinition{default_value: default_value},
338+
_type_definitions
339+
)
340+
when is_binary(default_value) or is_number(default_value) or is_boolean(default_value) or
341+
is_list(default_value) or is_map(default_value) do
342+
try do
343+
blueprint_value = Blueprint.Input.parse(default_value)
344+
concat([" = ", render_value(blueprint_value)])
345+
rescue
346+
_ -> empty()
347+
end
348+
end
349+
350+
defp default(%Blueprint.Schema.FieldDefinition{}, _type_definitions) do
351+
empty()
352+
end
353+
354+
defp default(
355+
%Blueprint.Schema.InputValueDefinition{
356+
default_value_blueprint: nil,
357+
default_value: nil
358+
},
359+
_type_definitions
360+
) do
361+
empty()
362+
end
363+
364+
defp default(
365+
%Blueprint.Schema.InputValueDefinition{
366+
default_value_blueprint: nil,
367+
default_value: default_value
368+
},
369+
_type_definitions
370+
)
371+
when not is_nil(default_value) do
372+
blueprint_value = Blueprint.Input.parse(default_value)
373+
concat([" = ", render_value(blueprint_value)])
315374
end
316375

376+
defp default(
377+
%Blueprint.Schema.InputValueDefinition{
378+
default_value_blueprint: default_value_blueprint
379+
},
380+
_type_definitions
381+
)
382+
when not is_nil(default_value_blueprint) do
383+
concat([" = ", render_value(default_value_blueprint)])
384+
end
385+
386+
defp default(_, _) do
387+
empty()
388+
end
389+
390+
defp find_enum_value_name(type, internal_value, type_definitions) do
391+
enum_identifier = unwrap_type_identifier(type)
392+
393+
with enum_def when not is_nil(enum_def) <-
394+
Enum.find(type_definitions, &(&1.identifier == enum_identifier)),
395+
%Blueprint.Schema.EnumTypeDefinition{} <- enum_def,
396+
enum_value when not is_nil(enum_value) <-
397+
Enum.find(List.flatten(enum_def.values), fn
398+
%Blueprint.Schema.EnumValueDefinition{value: ^internal_value} -> true
399+
atom when is_atom(atom) and atom == internal_value -> true
400+
_ -> false
401+
end) do
402+
case enum_value do
403+
%Blueprint.Schema.EnumValueDefinition{name: name} -> {:ok, name}
404+
atom when is_atom(atom) -> {:ok, atom |> to_string() |> String.upcase()}
405+
end
406+
else
407+
_ -> :error
408+
end
409+
end
410+
411+
defp unwrap_type_identifier(%Blueprint.TypeReference.NonNull{of_type: inner}),
412+
do: unwrap_type_identifier(inner)
413+
414+
defp unwrap_type_identifier(%Blueprint.TypeReference.List{of_type: inner}),
415+
do: unwrap_type_identifier(inner)
416+
417+
defp unwrap_type_identifier(%Blueprint.TypeReference.Identifier{id: id}), do: id
418+
defp unwrap_type_identifier(%Blueprint.TypeReference.Name{name: name}), do: name
419+
defp unwrap_type_identifier(atom) when is_atom(atom), do: atom
420+
defp unwrap_type_identifier(_), do: nil
421+
317422
defp description(docs, nil) do
318423
docs
319424
end
@@ -356,6 +461,47 @@ defmodule Absinthe.Schema.Notation.SDL.Render do
356461

357462
# Render Helpers
358463

464+
defp render_input_fields(fields, type_definitions) do
465+
items = Enum.reject(fields, &(&1.module in @skip_modules))
466+
467+
splitter =
468+
items
469+
|> Enum.any?(&(&1.description not in ["", nil]))
470+
|> case do
471+
true -> [nest(line(), :reset), line()]
472+
false -> [line()]
473+
end
474+
475+
items
476+
|> Enum.reverse()
477+
|> Enum.reduce(:start, fn
478+
item, :start -> render_input_field(item, type_definitions)
479+
item, acc -> concat([render_input_field(item, type_definitions)] ++ splitter ++ [acc])
480+
end)
481+
end
482+
483+
defp render_input_field(%Blueprint.Schema.FieldDefinition{} = field, type_definitions) do
484+
concat([
485+
string(@adapter.to_external_name(field.name, :field)),
486+
": ",
487+
render(field.type, type_definitions),
488+
default(field, type_definitions),
489+
directives(field.directives, type_definitions)
490+
])
491+
|> description(field.description)
492+
end
493+
494+
defp render_input_field(%Blueprint.Schema.InputValueDefinition{} = field, type_definitions) do
495+
concat([
496+
string(@adapter.to_external_name(field.name, :argument)),
497+
": ",
498+
render(field.type, type_definitions),
499+
default(field, type_definitions),
500+
directives(field.directives, type_definitions)
501+
])
502+
|> description(field.description)
503+
end
504+
359505
defp render_list(items, type_definitions, separator \\ line())
360506

361507
# Workaround for `values` macro which temporarily defines

test/absinthe/schema/sdl_render_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ defmodule Absinthe.Schema.SdlRenderTest do
275275
type RootQueryType {
276276
echo(
277277
\"The number of times\"
278-
times: Int
278+
times: Int = 10
279279
280280
timeInterval: Int
281281
): String

test/absinthe/schema/type_system_directive_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ defmodule Absinthe.Schema.TypeSystemDirectiveTest do
225225
}
226226
227227
input SearchFilter @feature(name: \":input_object\") {
228-
query: String @feature(name: \":input_field_definition\")
228+
query: String = "default" @feature(name: \":input_field_definition\")
229229
}
230230
231231
union SearchResult @feature(name: \":union\") = Dog | Post

test/mix/tasks/absinthe.schema.sdl_test.exs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,96 @@ defmodule Mix.Tasks.Absinthe.Schema.SdlTest do
123123
end
124124
end
125125

126+
defmodule DefaultValueTestSchema do
127+
use Absinthe.Schema
128+
129+
enum :color do
130+
value :red
131+
value :green
132+
value :blue
133+
end
134+
135+
enum :priority do
136+
value :high
137+
value :medium
138+
value :low
139+
end
140+
141+
enum :status do
142+
value :active
143+
value :inactive
144+
end
145+
146+
query do
147+
field :item, :string do
148+
arg :show, :boolean, default_value: true
149+
end
150+
151+
field :search, :string do
152+
arg :filters, :filters, default_value: %{query: "mystring"}
153+
end
154+
155+
field :enum_field, :string do
156+
arg :input, :enum_input
157+
end
158+
159+
field :sorted_items, :string do
160+
arg :input, :sort_input
161+
end
162+
163+
field :nested_field, :string do
164+
arg :input, :nested_input
165+
end
166+
167+
field :list_field, :string do
168+
arg :input, :list_input
169+
end
170+
171+
field :optional_field, :string do
172+
arg :input, :optional_input
173+
end
174+
end
175+
176+
mutation do
177+
field :create_item, :string do
178+
arg :input, non_null(:test_input)
179+
arg :other, :string, default_value: "other"
180+
end
181+
end
182+
183+
input_object :filters do
184+
field :query, :string
185+
end
186+
187+
input_object :test_input do
188+
field :value, non_null(:string), default_value: "test"
189+
end
190+
191+
input_object :enum_input do
192+
field :name, non_null(:string)
193+
field :color, :color, default_value: :red
194+
end
195+
196+
input_object :sort_input do
197+
field :priority, :priority, default_value: :high
198+
field :include_archived, :boolean, default_value: false
199+
end
200+
201+
input_object :nested_input do
202+
field :status, :status, default_value: :active
203+
end
204+
205+
input_object :list_input do
206+
field :items, list_of(:string), default_value: ["test"]
207+
field :default_status, :status, default_value: :inactive
208+
end
209+
210+
input_object :optional_input do
211+
field :optional_status, :status
212+
field :required_status, non_null(:status), default_value: :active
213+
end
214+
end
215+
126216
@test_mod_schema "Mix.Tasks.Absinthe.Schema.SdlTest.TestSchemaWithMods"
127217

128218
setup_all do
@@ -193,5 +283,30 @@ defmodule Mix.Tasks.Absinthe.Schema.SdlTest do
193283

194284
assert File.exists?(path)
195285
end
286+
287+
test "with default values including enums" do
288+
argv = ["output.graphql", "--schema", "#{DefaultValueTestSchema}"]
289+
opts = Task.parse_options(argv)
290+
291+
{:ok, schema} = Task.generate_schema(opts)
292+
293+
assert schema =~ "value: String! = \"test\""
294+
assert schema =~ "other: String = \"other\""
295+
assert schema =~ "show: Boolean = true"
296+
assert schema =~ "filters: Filters = {query: \"mystring\"}"
297+
assert schema =~ "input EnumInput {"
298+
assert schema =~ "color: Color = RED"
299+
assert schema =~ "input SortInput {"
300+
assert schema =~ "priority: Priority = HIGH"
301+
assert schema =~ "includeArchived: Boolean = false"
302+
assert schema =~ "input NestedInput {"
303+
assert schema =~ "status: Status = ACTIVE"
304+
assert schema =~ "input ListInput {"
305+
assert schema =~ ~s(items: [String] = ["test"])
306+
assert schema =~ "defaultStatus: Status = INACTIVE"
307+
assert schema =~ "input OptionalInput {"
308+
assert schema =~ "optionalStatus: Status"
309+
assert schema =~ "requiredStatus: Status! = ACTIVE"
310+
end
196311
end
197312
end

0 commit comments

Comments
 (0)