sdl_render.ex (12858B)
1 defmodule Absinthe.Schema.Notation.SDL.Render do 2 @moduledoc false 3 import Inspect.Algebra 4 import Absinthe.Utils.Render 5 6 alias Absinthe.Blueprint 7 8 @line_width 120 9 10 def inspect(term, %{pretty: true}) do 11 term 12 |> render() 13 |> concat(line()) 14 |> format(@line_width) 15 |> to_string 16 end 17 18 def inspect(term, options) do 19 Inspect.Any.inspect(term, options) 20 end 21 22 @skip_modules [ 23 Absinthe.Phase.Schema.Introspection, 24 Absinthe.Type.BuiltIns.Directives, 25 Absinthe.Type.BuiltIns.Scalars, 26 Absinthe.Type.BuiltIns.Introspection 27 ] 28 defp render(bp, type_definitions \\ []) 29 30 defp render(%Blueprint{} = bp, _) do 31 %{ 32 schema_definitions: [ 33 %Blueprint.Schema.SchemaDefinition{ 34 type_definitions: type_definitions, 35 directive_definitions: directive_definitions, 36 schema_declaration: schema_declaration 37 } 38 ] 39 } = bp 40 41 schema_declaration = 42 schema_declaration || 43 %{ 44 query: Enum.find(type_definitions, &(&1.identifier == :query)), 45 mutation: Enum.find(type_definitions, &(&1.identifier == :mutation)), 46 subscription: Enum.find(type_definitions, &(&1.identifier == :subscription)), 47 description: Enum.find(type_definitions, &(&1.identifier == :__schema)).description 48 } 49 50 directive_definitions = 51 directive_definitions 52 |> Enum.reject(&(&1.module in @skip_modules)) 53 54 all_type_definitions = 55 type_definitions 56 |> Enum.reject(&(&1.__struct__ == Blueprint.Schema.SchemaDeclaration)) 57 58 types_to_render = 59 all_type_definitions 60 |> Enum.reject(&(&1.module in @skip_modules)) 61 |> Enum.filter(& &1.__private__[:__absinthe_referenced__]) 62 63 ([schema_declaration] ++ directive_definitions ++ types_to_render) 64 |> Enum.map(&render(&1, all_type_definitions)) 65 |> Enum.reject(&(&1 == empty())) 66 |> join([line(), line()]) 67 end 68 69 defp render(%Blueprint.Schema.SchemaDeclaration{} = schema, type_definitions) do 70 block( 71 concat([ 72 "schema", 73 directives(schema.directives, type_definitions) 74 ]), 75 render_list(schema.field_definitions, type_definitions) 76 ) 77 |> description(schema.description) 78 end 79 80 defp render( 81 %{ 82 query: query_type, 83 mutation: mutation_type, 84 subscription: subscription_type, 85 description: description 86 }, 87 _type_definitions 88 ) do 89 schema_type_docs = 90 [ 91 query_type && concat("query: ", string(query_type.name)), 92 mutation_type && concat("mutation: ", string(mutation_type.name)), 93 subscription_type && concat("subscription: ", string(subscription_type.name)) 94 ] 95 |> Enum.reject(&is_nil/1) 96 |> join([line()]) 97 98 block( 99 "schema", 100 schema_type_docs 101 ) 102 |> description(description) 103 end 104 105 @adapter Absinthe.Adapter.LanguageConventions 106 defp render(%Blueprint.Schema.InputValueDefinition{} = input_value, type_definitions) do 107 concat([ 108 string(@adapter.to_external_name(input_value.name, :argument)), 109 ": ", 110 render(input_value.type, type_definitions), 111 default(input_value.default_value_blueprint), 112 directives(input_value.directives, type_definitions) 113 ]) 114 |> description(input_value.description) 115 end 116 117 defp render(%Blueprint.Schema.FieldDefinition{} = field, type_definitions) do 118 concat([ 119 string(@adapter.to_external_name(field.name, :field)), 120 arguments(field.arguments, type_definitions), 121 ": ", 122 render(field.type, type_definitions), 123 directives(field.directives, type_definitions) 124 ]) 125 |> description(field.description) 126 end 127 128 defp render(%Blueprint.Schema.ObjectTypeDefinition{} = object_type, type_definitions) do 129 block( 130 "type", 131 concat([ 132 string(object_type.name), 133 implements(object_type, type_definitions), 134 directives(object_type.directives, type_definitions) 135 ]), 136 render_list(object_type.fields, type_definitions) 137 ) 138 |> description(object_type.description) 139 end 140 141 defp render(%Blueprint.Schema.InputObjectTypeDefinition{} = input_object_type, type_definitions) do 142 block( 143 concat([ 144 "input ", 145 string(input_object_type.name), 146 directives(input_object_type.directives, type_definitions) 147 ]), 148 render_list(input_object_type.fields, type_definitions) 149 ) 150 |> description(input_object_type.description) 151 end 152 153 defp render(%Blueprint.Schema.UnionTypeDefinition{} = union_type, type_definitions) do 154 Enum.map(union_type.types, fn 155 identifier when is_atom(identifier) -> 156 render(%Blueprint.TypeReference.Identifier{id: identifier}, type_definitions) 157 158 %Blueprint.TypeReference.Name{} = ref -> 159 render(ref, type_definitions) 160 161 %Blueprint.TypeReference.Identifier{} = ref -> 162 render(ref, type_definitions) 163 end) 164 |> case do 165 [] -> 166 concat([ 167 "union ", 168 string(union_type.name), 169 directives(union_type.directives, type_definitions) 170 ]) 171 172 types -> 173 concat([ 174 "union ", 175 string(union_type.name), 176 directives(union_type.directives, type_definitions), 177 " = ", 178 join(types, " | ") 179 ]) 180 end 181 |> description(union_type.description) 182 end 183 184 defp render(%Blueprint.Schema.InterfaceTypeDefinition{} = interface_type, type_definitions) do 185 block( 186 "interface", 187 concat([ 188 string(interface_type.name), 189 implements(interface_type, type_definitions), 190 directives(interface_type.directives, type_definitions) 191 ]), 192 render_list(interface_type.fields, type_definitions) 193 ) 194 |> description(interface_type.description) 195 end 196 197 defp render(%Blueprint.Schema.EnumTypeDefinition{} = enum_type, type_definitions) do 198 block( 199 concat([ 200 "enum ", 201 string(enum_type.name), 202 directives(enum_type.directives, type_definitions) 203 ]), 204 render_list(List.flatten(enum_type.values), type_definitions) 205 ) 206 |> description(enum_type.description) 207 end 208 209 defp render(%Blueprint.Schema.EnumValueDefinition{} = enum_value, type_definitions) do 210 concat([ 211 string(enum_value.name), 212 directives(enum_value.directives, type_definitions) 213 ]) 214 |> description(enum_value.description) 215 end 216 217 defp render(%Blueprint.Schema.ScalarTypeDefinition{} = scalar_type, type_definitions) do 218 concat([ 219 "scalar ", 220 string(scalar_type.name), 221 directives(scalar_type.directives, type_definitions) 222 ]) 223 |> description(scalar_type.description) 224 end 225 226 defp render(%Blueprint.Schema.DirectiveDefinition{} = directive, type_definitions) do 227 locations = directive.locations |> Enum.map(&String.upcase(to_string(&1))) 228 229 concat([ 230 "directive ", 231 "@", 232 string(directive.name), 233 arguments(directive.arguments, type_definitions), 234 repeatable(directive.repeatable), 235 " on ", 236 join(locations, " | ") 237 ]) 238 |> description(directive.description) 239 end 240 241 defp render(%Blueprint.Directive{} = directive, type_definitions) do 242 concat([ 243 " @", 244 directive.name, 245 directive_arguments(directive.arguments, type_definitions) 246 ]) 247 end 248 249 defp render(%Blueprint.Input.Argument{} = argument, _type_definitions) do 250 concat([ 251 argument.name, 252 ": ", 253 render_value(argument.input_value) 254 ]) 255 end 256 257 defp render(%Blueprint.TypeReference.Name{name: name}, _type_definitions) do 258 string(name) 259 end 260 261 defp render(%Blueprint.TypeReference.Identifier{id: id}, type_definitions) do 262 type = Enum.find(type_definitions, &(&1.identifier == id)) 263 264 if type do 265 string(type.name) 266 else 267 all_type_ids = Enum.map(type_definitions, & &1.identifier) 268 269 raise """ 270 No type found for identifier #{inspect(id)} in #{inspect(all_type_ids)} 271 """ 272 end 273 end 274 275 defp render(%Blueprint.TypeReference.List{of_type: of_type}, type_definitions) do 276 concat(["[", render(of_type, type_definitions), "]"]) 277 end 278 279 defp render(%Blueprint.TypeReference.NonNull{of_type: of_type}, type_definitions) do 280 concat([render(of_type, type_definitions), "!"]) 281 end 282 283 defp render(nil, _) do 284 raise "Unexpected nil" 285 end 286 287 defp render(identifier, type_definitions) when is_atom(identifier) do 288 render(%Blueprint.TypeReference.Identifier{id: identifier}, type_definitions) 289 end 290 291 # SDL Syntax Helpers 292 293 defp directives([], _) do 294 empty() 295 end 296 297 defp directives(directives, type_definitions) do 298 concat(Enum.map(directives, &render(&1, type_definitions))) 299 end 300 301 defp directive_arguments([], _) do 302 empty() 303 end 304 305 defp directive_arguments(arguments, type_definitions) do 306 args = Enum.map(arguments, &render(&1, type_definitions)) 307 308 concat([ 309 "(", 310 join(args, ", "), 311 ")" 312 ]) 313 end 314 315 defp arguments([], _) do 316 empty() 317 end 318 319 defp arguments(args, type_definitions) do 320 any_descriptions? = Enum.any?(args, & &1.description) 321 322 group( 323 glue( 324 nest( 325 multiline( 326 glue( 327 "(", 328 "", 329 render_list(args, type_definitions, ", ") 330 ), 331 any_descriptions? 332 ), 333 2, 334 :break 335 ), 336 "", 337 ")" 338 ) 339 ) 340 end 341 342 defp default(nil) do 343 empty() 344 end 345 346 defp default(default_value) do 347 concat([" = ", render_value(default_value)]) 348 end 349 350 defp description(docs, nil) do 351 docs 352 end 353 354 defp description(docs, description) do 355 concat([ 356 render_string_value(description, 0), 357 line(), 358 docs 359 ]) 360 end 361 362 defp implements(%{interface_blueprints: [], interfaces: []}, _) do 363 empty() 364 end 365 366 defp implements(interface, type_definitions) do 367 interface_names = 368 case interface do 369 %{interface_blueprints: [], interfaces: identifiers} -> 370 Enum.map(identifiers, fn identifier -> 371 Enum.find_value(type_definitions, fn 372 %{identifier: ^identifier, name: name} -> name 373 _ -> nil 374 end) 375 end) 376 377 %{interface_blueprints: blueprints} -> 378 Enum.map(blueprints, & &1.name) 379 end 380 381 concat([ 382 " implements ", 383 join(interface_names, " & ") 384 ]) 385 end 386 387 defp repeatable(true), do: " repeatable" 388 defp repeatable(_), do: empty() 389 390 # Render Helpers 391 392 defp render_list(items, type_definitions, seperator \\ line()) 393 394 # Workaround for `values` macro which temporarily defines 395 # values as raw atoms to support dynamic schemas 396 defp render_list([first | _] = items, type_definitions, seperator) when is_atom(first) do 397 items 398 |> Enum.map( 399 &%Blueprint.Schema.EnumValueDefinition{ 400 value: &1, 401 name: String.upcase(to_string(&1)) 402 } 403 ) 404 |> render_list(type_definitions, seperator) 405 end 406 407 defp render_list(items, type_definitions, seperator) do 408 items = Enum.reject(items, &(&1.module in @skip_modules)) 409 410 splitter = 411 items 412 |> Enum.any?(&(&1.description not in ["", nil])) 413 |> case do 414 true -> [nest(line(), :reset), line()] 415 false -> [seperator] 416 end 417 418 items 419 |> Enum.reverse() 420 |> Enum.reduce(:start, fn 421 item, :start -> render(item, type_definitions) 422 item, acc -> concat([render(item, type_definitions)] ++ splitter ++ [acc]) 423 end) 424 end 425 426 defp render_value(%Blueprint.Input.String{value: value}), 427 do: render_string_value(value) 428 429 defp render_value(%Blueprint.Input.RawValue{content: content}), 430 do: render_value(content) 431 432 defp render_value(%Blueprint.Input.Value{raw: raw}), 433 do: render_value(raw) 434 435 defp render_value(%Blueprint.Input.Null{}), 436 do: "null" 437 438 defp render_value(%Blueprint.Input.Object{fields: fields}) do 439 default_fields = Enum.map(fields, &render_value/1) 440 concat(["{", join(default_fields, ", "), "}"]) 441 end 442 443 defp render_value(%Blueprint.Input.List{items: items}) do 444 default_list = Enum.map(items, &render_value/1) 445 concat(["[", join(default_list, ", "), "]"]) 446 end 447 448 defp render_value(%Blueprint.Input.Field{name: name, input_value: value}), 449 do: concat([name, ": ", render_value(value)]) 450 451 defp render_value(%{value: value}), 452 do: to_string(value) 453 454 # Algebra Helpers 455 456 defp multiline(docs, true) do 457 force_unfit(docs) 458 end 459 460 defp multiline(docs, false) do 461 docs 462 end 463 464 defp block(kind, name, docs) do 465 glue( 466 kind, 467 block(name, docs) 468 ) 469 end 470 471 defp block(name, docs) do 472 glue( 473 name, 474 group( 475 glue( 476 nest( 477 force_unfit( 478 glue( 479 "{", 480 "", 481 docs 482 ) 483 ), 484 2, 485 :always 486 ), 487 "", 488 "}" 489 ) 490 ) 491 ) 492 end 493 end