graphql 2.0.32 → 2.5.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (308) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/detailed_trace_generator.rb +77 -0
  3. data/lib/generators/graphql/install/mutation_root_generator.rb +2 -2
  4. data/lib/generators/graphql/install/templates/base_mutation.erb +2 -0
  5. data/lib/generators/graphql/install/templates/mutation_type.erb +2 -0
  6. data/lib/generators/graphql/install_generator.rb +49 -0
  7. data/lib/generators/graphql/orm_mutations_base.rb +1 -1
  8. data/lib/generators/graphql/templates/base_argument.erb +2 -0
  9. data/lib/generators/graphql/templates/base_connection.erb +2 -0
  10. data/lib/generators/graphql/templates/base_edge.erb +2 -0
  11. data/lib/generators/graphql/templates/base_enum.erb +2 -0
  12. data/lib/generators/graphql/templates/base_field.erb +2 -0
  13. data/lib/generators/graphql/templates/base_input_object.erb +2 -0
  14. data/lib/generators/graphql/templates/base_interface.erb +2 -0
  15. data/lib/generators/graphql/templates/base_object.erb +2 -0
  16. data/lib/generators/graphql/templates/base_resolver.erb +8 -0
  17. data/lib/generators/graphql/templates/base_scalar.erb +2 -0
  18. data/lib/generators/graphql/templates/base_union.erb +2 -0
  19. data/lib/generators/graphql/templates/create_graphql_detailed_traces.erb +10 -0
  20. data/lib/generators/graphql/templates/graphql_controller.erb +2 -0
  21. data/lib/generators/graphql/templates/loader.erb +2 -0
  22. data/lib/generators/graphql/templates/mutation.erb +2 -0
  23. data/lib/generators/graphql/templates/node_type.erb +2 -0
  24. data/lib/generators/graphql/templates/query_type.erb +2 -0
  25. data/lib/generators/graphql/templates/schema.erb +5 -0
  26. data/lib/generators/graphql/type_generator.rb +1 -1
  27. data/lib/graphql/analysis/analyzer.rb +90 -0
  28. data/lib/graphql/analysis/field_usage.rb +82 -0
  29. data/lib/graphql/analysis/max_query_complexity.rb +20 -0
  30. data/lib/graphql/analysis/max_query_depth.rb +20 -0
  31. data/lib/graphql/analysis/query_complexity.rb +263 -0
  32. data/lib/graphql/analysis/query_depth.rb +58 -0
  33. data/lib/graphql/analysis/visitor.rb +280 -0
  34. data/lib/graphql/analysis.rb +95 -1
  35. data/lib/graphql/autoload.rb +38 -0
  36. data/lib/graphql/backtrace/table.rb +118 -55
  37. data/lib/graphql/backtrace.rb +1 -19
  38. data/lib/graphql/coercion_error.rb +1 -9
  39. data/lib/graphql/current.rb +57 -0
  40. data/lib/graphql/dashboard/application_controller.rb +41 -0
  41. data/lib/graphql/dashboard/detailed_traces.rb +47 -0
  42. data/lib/graphql/dashboard/installable.rb +22 -0
  43. data/lib/graphql/dashboard/landings_controller.rb +9 -0
  44. data/lib/graphql/dashboard/limiters.rb +93 -0
  45. data/lib/graphql/dashboard/operation_store.rb +199 -0
  46. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css +6 -0
  47. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js +7 -0
  48. data/lib/graphql/dashboard/statics/charts.min.css +1 -0
  49. data/lib/graphql/dashboard/statics/dashboard.css +30 -0
  50. data/lib/graphql/dashboard/statics/dashboard.js +143 -0
  51. data/lib/graphql/dashboard/statics/header-icon.png +0 -0
  52. data/lib/graphql/dashboard/statics/icon.png +0 -0
  53. data/lib/graphql/dashboard/statics_controller.rb +31 -0
  54. data/lib/graphql/dashboard/subscriptions.rb +97 -0
  55. data/lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/traces/index.html.erb +45 -0
  56. data/lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb +18 -0
  57. data/lib/graphql/dashboard/views/graphql/dashboard/limiters/limiters/show.html.erb +62 -0
  58. data/lib/graphql/dashboard/views/graphql/dashboard/not_installed.html.erb +18 -0
  59. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +24 -0
  60. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +21 -0
  61. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +69 -0
  62. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +7 -0
  63. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +39 -0
  64. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/show.html.erb +32 -0
  65. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/index.html.erb +81 -0
  66. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +71 -0
  67. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/show.html.erb +41 -0
  68. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/index.html.erb +55 -0
  69. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +40 -0
  70. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +108 -0
  71. data/lib/graphql/dashboard.rb +96 -0
  72. data/lib/graphql/dataloader/active_record_association_source.rb +84 -0
  73. data/lib/graphql/dataloader/active_record_source.rb +47 -0
  74. data/lib/graphql/dataloader/async_dataloader.rb +112 -0
  75. data/lib/graphql/dataloader/null_dataloader.rb +55 -10
  76. data/lib/graphql/dataloader/request.rb +5 -0
  77. data/lib/graphql/dataloader/source.rb +35 -12
  78. data/lib/graphql/dataloader.rb +224 -149
  79. data/lib/graphql/date_encoding_error.rb +1 -1
  80. data/lib/graphql/dig.rb +2 -1
  81. data/lib/graphql/duration_encoding_error.rb +16 -0
  82. data/lib/graphql/execution/interpreter/argument_value.rb +5 -1
  83. data/lib/graphql/execution/interpreter/arguments_cache.rb +5 -10
  84. data/lib/graphql/execution/interpreter/resolve.rb +23 -25
  85. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +228 -0
  86. data/lib/graphql/execution/interpreter/runtime.rb +363 -434
  87. data/lib/graphql/execution/interpreter.rb +91 -164
  88. data/lib/graphql/execution/lookahead.rb +105 -31
  89. data/lib/graphql/execution/multiplex.rb +7 -6
  90. data/lib/graphql/execution/next/field_resolve_step.rb +711 -0
  91. data/lib/graphql/execution/next/load_argument_step.rb +60 -0
  92. data/lib/graphql/execution/next/prepare_object_step.rb +129 -0
  93. data/lib/graphql/execution/next/runner.rb +389 -0
  94. data/lib/graphql/execution/next/selections_step.rb +37 -0
  95. data/lib/graphql/execution/next.rb +70 -0
  96. data/lib/graphql/execution.rb +1 -0
  97. data/lib/graphql/execution_error.rb +13 -10
  98. data/lib/graphql/introspection/directive_location_enum.rb +1 -1
  99. data/lib/graphql/introspection/directive_type.rb +7 -3
  100. data/lib/graphql/introspection/dynamic_fields.rb +5 -1
  101. data/lib/graphql/introspection/entry_points.rb +20 -6
  102. data/lib/graphql/introspection/enum_value_type.rb +5 -5
  103. data/lib/graphql/introspection/field_type.rb +13 -5
  104. data/lib/graphql/introspection/input_value_type.rb +21 -13
  105. data/lib/graphql/introspection/schema_type.rb +8 -11
  106. data/lib/graphql/introspection/type_type.rb +64 -28
  107. data/lib/graphql/invalid_name_error.rb +1 -1
  108. data/lib/graphql/invalid_null_error.rb +26 -17
  109. data/lib/graphql/language/block_string.rb +34 -18
  110. data/lib/graphql/language/cache.rb +13 -0
  111. data/lib/graphql/language/comment.rb +18 -0
  112. data/lib/graphql/language/definition_slice.rb +1 -1
  113. data/lib/graphql/language/document_from_schema_definition.rb +90 -61
  114. data/lib/graphql/language/lexer.rb +319 -193
  115. data/lib/graphql/language/nodes.rb +136 -77
  116. data/lib/graphql/language/parser.rb +807 -1985
  117. data/lib/graphql/language/printer.rb +324 -151
  118. data/lib/graphql/language/sanitized_printer.rb +21 -23
  119. data/lib/graphql/language/static_visitor.rb +171 -0
  120. data/lib/graphql/language/visitor.rb +23 -83
  121. data/lib/graphql/language.rb +71 -1
  122. data/lib/graphql/load_application_object_failed_error.rb +5 -1
  123. data/lib/graphql/pagination/array_connection.rb +6 -6
  124. data/lib/graphql/pagination/connection.rb +30 -1
  125. data/lib/graphql/pagination/connections.rb +32 -0
  126. data/lib/graphql/pagination/mongoid_relation_connection.rb +1 -2
  127. data/lib/graphql/query/context/scoped_context.rb +101 -0
  128. data/lib/graphql/query/context.rb +82 -144
  129. data/lib/graphql/query/null_context.rb +15 -18
  130. data/lib/graphql/query/partial.rb +179 -0
  131. data/lib/graphql/query/validation_pipeline.rb +4 -4
  132. data/lib/graphql/query/variable_validation_error.rb +1 -1
  133. data/lib/graphql/query/variables.rb +3 -3
  134. data/lib/graphql/query.rb +126 -81
  135. data/lib/graphql/railtie.rb +16 -6
  136. data/lib/graphql/rake_task.rb +3 -12
  137. data/lib/graphql/rubocop/graphql/base_cop.rb +1 -1
  138. data/lib/graphql/rubocop/graphql/field_type_in_block.rb +144 -0
  139. data/lib/graphql/rubocop/graphql/root_types_in_block.rb +38 -0
  140. data/lib/graphql/rubocop.rb +2 -0
  141. data/lib/graphql/schema/addition.rb +26 -13
  142. data/lib/graphql/schema/always_visible.rb +7 -2
  143. data/lib/graphql/schema/argument.rb +75 -9
  144. data/lib/graphql/schema/base_64_encoder.rb +3 -5
  145. data/lib/graphql/schema/build_from_definition.rb +123 -60
  146. data/lib/graphql/schema/directive/flagged.rb +4 -2
  147. data/lib/graphql/schema/directive/one_of.rb +12 -0
  148. data/lib/graphql/schema/directive/specified_by.rb +14 -0
  149. data/lib/graphql/schema/directive.rb +54 -2
  150. data/lib/graphql/schema/enum.rb +110 -27
  151. data/lib/graphql/schema/enum_value.rb +10 -2
  152. data/lib/graphql/schema/field/connection_extension.rb +15 -49
  153. data/lib/graphql/schema/field/scope_extension.rb +23 -7
  154. data/lib/graphql/schema/field.rb +245 -118
  155. data/lib/graphql/schema/field_extension.rb +34 -1
  156. data/lib/graphql/schema/has_single_input_argument.rb +160 -0
  157. data/lib/graphql/schema/input_object.rb +116 -60
  158. data/lib/graphql/schema/interface.rb +34 -16
  159. data/lib/graphql/schema/introspection_system.rb +8 -17
  160. data/lib/graphql/schema/late_bound_type.rb +4 -0
  161. data/lib/graphql/schema/list.rb +3 -3
  162. data/lib/graphql/schema/loader.rb +3 -4
  163. data/lib/graphql/schema/member/base_dsl_methods.rb +18 -2
  164. data/lib/graphql/schema/member/has_arguments.rb +132 -100
  165. data/lib/graphql/schema/member/has_authorization.rb +35 -0
  166. data/lib/graphql/schema/member/has_dataloader.rb +99 -0
  167. data/lib/graphql/schema/member/has_deprecation_reason.rb +15 -0
  168. data/lib/graphql/schema/member/has_directives.rb +4 -4
  169. data/lib/graphql/schema/member/has_fields.rb +115 -15
  170. data/lib/graphql/schema/member/has_interfaces.rb +26 -12
  171. data/lib/graphql/schema/member/has_unresolved_type_error.rb +5 -1
  172. data/lib/graphql/schema/member/has_validators.rb +1 -1
  173. data/lib/graphql/schema/member/relay_shortcuts.rb +1 -1
  174. data/lib/graphql/schema/member/scoped.rb +19 -0
  175. data/lib/graphql/schema/member/type_system_helpers.rb +17 -4
  176. data/lib/graphql/schema/member/validates_input.rb +3 -3
  177. data/lib/graphql/schema/member.rb +6 -0
  178. data/lib/graphql/schema/mutation.rb +7 -0
  179. data/lib/graphql/schema/object.rb +34 -8
  180. data/lib/graphql/schema/printer.rb +9 -7
  181. data/lib/graphql/schema/ractor_shareable.rb +79 -0
  182. data/lib/graphql/schema/relay_classic_mutation.rb +6 -129
  183. data/lib/graphql/schema/resolver.rb +90 -32
  184. data/lib/graphql/schema/scalar.rb +4 -9
  185. data/lib/graphql/schema/subscription.rb +63 -10
  186. data/lib/graphql/schema/timeout.rb +19 -2
  187. data/lib/graphql/schema/type_expression.rb +2 -2
  188. data/lib/graphql/schema/union.rb +2 -2
  189. data/lib/graphql/schema/unique_within_type.rb +1 -1
  190. data/lib/graphql/schema/validator/all_validator.rb +62 -0
  191. data/lib/graphql/schema/validator/required_validator.rb +92 -11
  192. data/lib/graphql/schema/validator.rb +3 -1
  193. data/lib/graphql/schema/visibility/migration.rb +188 -0
  194. data/lib/graphql/schema/visibility/profile.rb +445 -0
  195. data/lib/graphql/schema/visibility/visit.rb +190 -0
  196. data/lib/graphql/schema/visibility.rb +311 -0
  197. data/lib/graphql/schema/warden.rb +275 -103
  198. data/lib/graphql/schema.rb +950 -210
  199. data/lib/graphql/static_validation/all_rules.rb +3 -3
  200. data/lib/graphql/static_validation/base_visitor.rb +7 -6
  201. data/lib/graphql/static_validation/literal_validator.rb +6 -7
  202. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  203. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +1 -1
  204. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +3 -2
  205. data/lib/graphql/static_validation/rules/directives_are_defined.rb +3 -3
  206. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +2 -0
  207. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +12 -2
  208. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +47 -13
  209. data/lib/graphql/static_validation/rules/fields_will_merge.rb +88 -25
  210. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +10 -2
  211. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  212. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +12 -2
  213. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
  214. data/lib/graphql/static_validation/rules/mutation_root_exists.rb +1 -1
  215. data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +1 -1
  216. data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
  217. data/lib/graphql/static_validation/rules/query_root_exists.rb +1 -1
  218. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +5 -5
  219. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +5 -5
  220. data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
  221. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +7 -3
  222. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +18 -27
  223. data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +1 -1
  224. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +2 -2
  225. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +11 -2
  226. data/lib/graphql/static_validation/validation_context.rb +21 -5
  227. data/lib/graphql/static_validation/validator.rb +9 -1
  228. data/lib/graphql/static_validation.rb +0 -1
  229. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +8 -5
  230. data/lib/graphql/subscriptions/broadcast_analyzer.rb +11 -5
  231. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +12 -10
  232. data/lib/graphql/subscriptions/event.rb +21 -4
  233. data/lib/graphql/subscriptions/serialize.rb +3 -1
  234. data/lib/graphql/subscriptions.rb +21 -17
  235. data/lib/graphql/testing/helpers.rb +161 -0
  236. data/lib/graphql/testing/mock_action_cable.rb +111 -0
  237. data/lib/graphql/testing.rb +3 -0
  238. data/lib/graphql/tracing/active_support_notifications_trace.rb +14 -3
  239. data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
  240. data/lib/graphql/tracing/appoptics_trace.rb +7 -3
  241. data/lib/graphql/tracing/appoptics_tracing.rb +9 -2
  242. data/lib/graphql/tracing/appsignal_trace.rb +32 -59
  243. data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
  244. data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
  245. data/lib/graphql/tracing/data_dog_trace.rb +46 -162
  246. data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
  247. data/lib/graphql/tracing/detailed_trace/active_record_backend.rb +74 -0
  248. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
  249. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
  250. data/lib/graphql/tracing/detailed_trace.rb +156 -0
  251. data/lib/graphql/tracing/legacy_hooks_trace.rb +75 -0
  252. data/lib/graphql/tracing/legacy_trace.rb +4 -61
  253. data/lib/graphql/tracing/monitor_trace.rb +283 -0
  254. data/lib/graphql/tracing/new_relic_trace.rb +47 -54
  255. data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
  256. data/lib/graphql/tracing/notifications_trace.rb +183 -37
  257. data/lib/graphql/tracing/notifications_tracing.rb +2 -0
  258. data/lib/graphql/tracing/null_trace.rb +9 -0
  259. data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
  260. data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
  261. data/lib/graphql/tracing/perfetto_trace.rb +864 -0
  262. data/lib/graphql/tracing/platform_tracing.rb +3 -1
  263. data/lib/graphql/tracing/{prometheus_tracing → prometheus_trace}/graphql_collector.rb +5 -1
  264. data/lib/graphql/tracing/prometheus_trace.rb +73 -73
  265. data/lib/graphql/tracing/prometheus_tracing.rb +2 -0
  266. data/lib/graphql/tracing/scout_trace.rb +32 -58
  267. data/lib/graphql/tracing/scout_tracing.rb +2 -0
  268. data/lib/graphql/tracing/sentry_trace.rb +82 -0
  269. data/lib/graphql/tracing/statsd_trace.rb +33 -45
  270. data/lib/graphql/tracing/statsd_tracing.rb +2 -0
  271. data/lib/graphql/tracing/trace.rb +112 -1
  272. data/lib/graphql/tracing.rb +31 -28
  273. data/lib/graphql/type_kinds.rb +2 -1
  274. data/lib/graphql/types/iso_8601_duration.rb +77 -0
  275. data/lib/graphql/types/relay/connection_behaviors.rb +44 -2
  276. data/lib/graphql/types/relay/edge_behaviors.rb +18 -0
  277. data/lib/graphql/types/relay/has_node_field.rb +13 -8
  278. data/lib/graphql/types/relay/has_nodes_field.rb +13 -8
  279. data/lib/graphql/types/relay/node_behaviors.rb +13 -2
  280. data/lib/graphql/types/relay/page_info_behaviors.rb +4 -0
  281. data/lib/graphql/types.rb +18 -10
  282. data/lib/graphql/unauthorized_enum_value_error.rb +13 -0
  283. data/lib/graphql/unauthorized_error.rb +5 -1
  284. data/lib/graphql/version.rb +1 -1
  285. data/lib/graphql.rb +71 -54
  286. data/readme.md +12 -2
  287. metadata +233 -37
  288. data/lib/graphql/analysis/ast/analyzer.rb +0 -84
  289. data/lib/graphql/analysis/ast/field_usage.rb +0 -57
  290. data/lib/graphql/analysis/ast/max_query_complexity.rb +0 -22
  291. data/lib/graphql/analysis/ast/max_query_depth.rb +0 -22
  292. data/lib/graphql/analysis/ast/query_complexity.rb +0 -230
  293. data/lib/graphql/analysis/ast/query_depth.rb +0 -55
  294. data/lib/graphql/analysis/ast/visitor.rb +0 -276
  295. data/lib/graphql/analysis/ast.rb +0 -81
  296. data/lib/graphql/backtrace/inspect_result.rb +0 -50
  297. data/lib/graphql/backtrace/trace.rb +0 -96
  298. data/lib/graphql/backtrace/tracer.rb +0 -80
  299. data/lib/graphql/deprecation.rb +0 -9
  300. data/lib/graphql/filter.rb +0 -59
  301. data/lib/graphql/language/parser.y +0 -560
  302. data/lib/graphql/language/token.rb +0 -34
  303. data/lib/graphql/schema/base_64_bp.rb +0 -26
  304. data/lib/graphql/schema/invalid_type_error.rb +0 -7
  305. data/lib/graphql/schema/null_mask.rb +0 -11
  306. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +0 -17
  307. data/lib/graphql/static_validation/type_stack.rb +0 -216
  308. data/lib/graphql/subscriptions/instrumentation.rb +0 -28
@@ -1,39 +1,261 @@
1
1
  # frozen_string_literal: true
2
-
3
- require "strscan"
4
-
5
2
  module GraphQL
6
3
  module Language
4
+
7
5
  class Lexer
8
- IDENTIFIER = /[_A-Za-z][_0-9A-Za-z]*/
9
- NEWLINE = /[\c\r\n]/
10
- BLANK = /[, \t]+/
11
- COMMENT = /#[^\n\r]*/
12
- INT = /[-]?(?:[0]|[1-9][0-9]*)/
13
- FLOAT_DECIMAL = /[.][0-9]+/
14
- FLOAT_EXP = /[eE][+-]?[0-9]+/
15
- FLOAT = /#{INT}(#{FLOAT_DECIMAL}#{FLOAT_EXP}|#{FLOAT_DECIMAL}|#{FLOAT_EXP})/
16
-
17
- module Literals
18
- ON = /on\b/
19
- FRAGMENT = /fragment\b/
20
- TRUE = /true\b/
21
- FALSE = /false\b/
22
- NULL = /null\b/
23
- QUERY = /query\b/
24
- MUTATION = /mutation\b/
25
- SUBSCRIPTION = /subscription\b/
26
- SCHEMA = /schema\b/
27
- SCALAR = /scalar\b/
28
- TYPE = /type\b/
29
- EXTEND = /extend\b/
30
- IMPLEMENTS = /implements\b/
31
- INTERFACE = /interface\b/
32
- UNION = /union\b/
33
- ENUM = /enum\b/
34
- INPUT = /input\b/
35
- DIRECTIVE = /directive\b/
36
- REPEATABLE = /repeatable\b/
6
+ def initialize(graphql_str, filename: nil, max_tokens: nil)
7
+ if !(graphql_str.encoding == Encoding::UTF_8 || graphql_str.ascii_only?)
8
+ graphql_str = graphql_str.dup.force_encoding(Encoding::UTF_8)
9
+ end
10
+ @string = graphql_str
11
+ @filename = filename
12
+ @scanner = StringScanner.new(graphql_str)
13
+ @pos = nil
14
+ @max_tokens = max_tokens || Float::INFINITY
15
+ @tokens_count = 0
16
+ @finished = false
17
+ end
18
+
19
+ def finished?
20
+ @finished
21
+ end
22
+
23
+ def freeze
24
+ @scanner = nil
25
+ super
26
+ end
27
+
28
+ attr_reader :pos, :tokens_count
29
+
30
+ def advance
31
+ @scanner.skip(IGNORE_REGEXP)
32
+ if @scanner.eos?
33
+ @finished = true
34
+ return false
35
+ end
36
+ @tokens_count += 1
37
+ if @tokens_count > @max_tokens
38
+ raise_parse_error("This query is too large to execute.")
39
+ end
40
+ @pos = @scanner.pos
41
+ next_byte = @string.getbyte(@pos)
42
+ next_byte_is_for = FIRST_BYTES[next_byte]
43
+ case next_byte_is_for
44
+ when ByteFor::PUNCTUATION
45
+ @scanner.pos += 1
46
+ PUNCTUATION_NAME_FOR_BYTE[next_byte]
47
+ when ByteFor::NAME
48
+ if len = @scanner.skip(KEYWORD_REGEXP)
49
+ case len
50
+ when 2
51
+ :ON
52
+ when 12
53
+ :SUBSCRIPTION
54
+ else
55
+ pos = @pos
56
+
57
+ # Use bytes 2 and 3 as a unique identifier for this keyword
58
+ bytes = (@string.getbyte(pos + 2) << 8) | @string.getbyte(pos + 1)
59
+ KEYWORD_BY_TWO_BYTES[_hash(bytes)]
60
+ end
61
+ else
62
+ @scanner.skip(IDENTIFIER_REGEXP)
63
+ :IDENTIFIER
64
+ end
65
+ when ByteFor::IDENTIFIER
66
+ @scanner.skip(IDENTIFIER_REGEXP)
67
+ :IDENTIFIER
68
+ when ByteFor::NUMBER
69
+ if len = @scanner.skip(NUMERIC_REGEXP)
70
+
71
+ if GraphQL.reject_numbers_followed_by_names
72
+ new_pos = @scanner.pos
73
+ peek_byte = @string.getbyte(new_pos)
74
+ next_first_byte = FIRST_BYTES[peek_byte]
75
+ if next_first_byte == ByteFor::NAME || next_first_byte == ByteFor::IDENTIFIER
76
+ number_part = token_value
77
+ name_part = @scanner.scan(IDENTIFIER_REGEXP)
78
+ raise_parse_error("Name after number is not allowed (in `#{number_part}#{name_part}`)")
79
+ end
80
+ end
81
+ # Check for a matched decimal:
82
+ @scanner[1] ? :FLOAT : :INT
83
+ else
84
+ # Attempt to find the part after the `-`
85
+ value = @scanner.scan(/-\s?[a-z0-9]*/i)
86
+ invalid_byte_for_number_error_message = "Expected type 'number', but it was malformed#{value.nil? ? "" : ": #{value.inspect}"}."
87
+ raise_parse_error(invalid_byte_for_number_error_message)
88
+ end
89
+ when ByteFor::ELLIPSIS
90
+ if @string.getbyte(@pos + 1) != 46 || @string.getbyte(@pos + 2) != 46
91
+ raise_parse_error("Expected `...`, actual: #{@string[@pos..@pos + 2].inspect}")
92
+ end
93
+ @scanner.pos += 3
94
+ :ELLIPSIS
95
+ when ByteFor::STRING
96
+ if @scanner.skip(BLOCK_STRING_REGEXP) || @scanner.skip(QUOTED_STRING_REGEXP)
97
+ :STRING
98
+ else
99
+ raise_parse_error("Expected string or block string, but it was malformed")
100
+ end
101
+ else
102
+ @scanner.pos += 1
103
+ :UNKNOWN_CHAR
104
+ end
105
+ rescue ArgumentError => err
106
+ if err.message == "invalid byte sequence in UTF-8"
107
+ raise_parse_error("Parse error on bad Unicode escape sequence", nil, nil)
108
+ end
109
+ end
110
+
111
+ def token_value
112
+ @string.byteslice(@scanner.pos - @scanner.matched_size, @scanner.matched_size)
113
+ rescue StandardError => err
114
+ raise GraphQL::Error, "(token_value failed: #{err.class}: #{err.message})"
115
+ end
116
+
117
+ def debug_token_value(token_name)
118
+ if token_name && Lexer::Punctuation.const_defined?(token_name)
119
+ Lexer::Punctuation.const_get(token_name)
120
+ elsif token_name == :ELLIPSIS
121
+ "..."
122
+ elsif token_name == :STRING
123
+ string_value
124
+ elsif @scanner.matched_size.nil?
125
+ @scanner.peek(1)
126
+ else
127
+ token_value
128
+ end
129
+ end
130
+
131
+ ESCAPES = /\\["\\\/bfnrt]/
132
+ ESCAPES_REPLACE = {
133
+ '\\"' => '"',
134
+ "\\\\" => "\\",
135
+ "\\/" => '/',
136
+ "\\b" => "\b",
137
+ "\\f" => "\f",
138
+ "\\n" => "\n",
139
+ "\\r" => "\r",
140
+ "\\t" => "\t",
141
+ }
142
+ UTF_8 = /\\u(?:([\dAa-f]{4})|\{([\da-f]{4,})\})(?:\\u([\dAa-f]{4}))?/i
143
+ VALID_STRING = /\A(?:[^\\]|#{ESCAPES}|#{UTF_8})*\z/o
144
+ ESCAPED = /(?:#{ESCAPES}|#{UTF_8})/o
145
+
146
+ def string_value
147
+ str = token_value
148
+ is_block = str.start_with?('"""')
149
+ if is_block
150
+ str.gsub!(/\A"""|"""\z/, '')
151
+ return Language::BlockString.trim_whitespace(str)
152
+ else
153
+ str.gsub!(/\A"|"\z/, '')
154
+
155
+ if !str.valid_encoding? || !str.match?(VALID_STRING)
156
+ raise_parse_error("Bad unicode escape in #{str.inspect}")
157
+ else
158
+ Lexer.replace_escaped_characters_in_place(str)
159
+
160
+ if !str.valid_encoding?
161
+ raise_parse_error("Bad unicode escape in #{str.inspect}")
162
+ else
163
+ str
164
+ end
165
+ end
166
+ end
167
+ end
168
+
169
+ def line_number
170
+ @scanner.string[0..@pos].count("\n") + 1
171
+ end
172
+
173
+ def column_number
174
+ @scanner.string[0..@pos].split("\n").last.length
175
+ end
176
+
177
+ def raise_parse_error(message, line = line_number, col = column_number)
178
+ raise GraphQL::ParseError.new(message, line, col, @string, filename: @filename)
179
+ end
180
+
181
+ IGNORE_REGEXP = %r{
182
+ (?:
183
+ [, \c\r\n\t]+ |
184
+ \#.*$
185
+ )*
186
+ }x
187
+ IDENTIFIER_REGEXP = /[_A-Za-z][_0-9A-Za-z]*/
188
+ INT_REGEXP = /-?(?:[0]|[1-9][0-9]*)/
189
+ FLOAT_DECIMAL_REGEXP = /[.][0-9]+/
190
+ FLOAT_EXP_REGEXP = /[eE][+-]?[0-9]+/
191
+ # TODO: FLOAT_EXP_REGEXP should not be allowed to follow INT_REGEXP, integers are not allowed to have exponent parts.
192
+ NUMERIC_REGEXP = /#{INT_REGEXP}(#{FLOAT_DECIMAL_REGEXP}#{FLOAT_EXP_REGEXP}|#{FLOAT_DECIMAL_REGEXP}|#{FLOAT_EXP_REGEXP})?/
193
+
194
+ KEYWORDS = [
195
+ "on",
196
+ "fragment",
197
+ "true",
198
+ "false",
199
+ "null",
200
+ "query",
201
+ "mutation",
202
+ "subscription",
203
+ "schema",
204
+ "scalar",
205
+ "type",
206
+ "extend",
207
+ "implements",
208
+ "interface",
209
+ "union",
210
+ "enum",
211
+ "input",
212
+ "directive",
213
+ "repeatable"
214
+ ].freeze
215
+
216
+ KEYWORD_REGEXP = /#{Regexp.union(KEYWORDS.sort)}\b/
217
+ KEYWORD_BY_TWO_BYTES = [
218
+ :INTERFACE,
219
+ :MUTATION,
220
+ :EXTEND,
221
+ :FALSE,
222
+ :ENUM,
223
+ :TRUE,
224
+ :NULL,
225
+ nil,
226
+ nil,
227
+ nil,
228
+ nil,
229
+ nil,
230
+ nil,
231
+ nil,
232
+ :QUERY,
233
+ nil,
234
+ nil,
235
+ :REPEATABLE,
236
+ :IMPLEMENTS,
237
+ :INPUT,
238
+ :TYPE,
239
+ :SCHEMA,
240
+ nil,
241
+ nil,
242
+ nil,
243
+ :DIRECTIVE,
244
+ :UNION,
245
+ nil,
246
+ nil,
247
+ :SCALAR,
248
+ nil,
249
+ :FRAGMENT
250
+ ].freeze
251
+
252
+ # This produces a unique integer for bytes 2 and 3 of each keyword string
253
+ # See https://tenderlovemaking.com/2023/09/02/fast-tokenizers-with-stringscanner.html
254
+ def _hash key
255
+ (key * 18592990) >> 27 & 0x1f
256
+ end
257
+
258
+ module Punctuation
37
259
  LCURLY = '{'
38
260
  RCURLY = '}'
39
261
  LPAREN = '('
@@ -43,36 +265,31 @@ module GraphQL
43
265
  COLON = ':'
44
266
  VAR_SIGN = '$'
45
267
  DIR_SIGN = '@'
46
- ELLIPSIS = '...'
47
268
  EQUALS = '='
48
269
  BANG = '!'
49
270
  PIPE = '|'
50
271
  AMP = '&'
51
272
  end
52
273
 
53
- include Literals
274
+ # A sparse array mapping the bytes for each punctuation
275
+ # to a symbol name for that punctuation
276
+ PUNCTUATION_NAME_FOR_BYTE = Punctuation.constants.each_with_object([]) { |name, arr|
277
+ punct = Punctuation.const_get(name)
278
+ arr[punct.ord] = name
279
+ }.freeze
280
+
54
281
 
55
282
  QUOTE = '"'
56
283
  UNICODE_DIGIT = /[0-9A-Za-z]/
57
284
  FOUR_DIGIT_UNICODE = /#{UNICODE_DIGIT}{4}/
58
- N_DIGIT_UNICODE = %r{#{LCURLY}#{UNICODE_DIGIT}{4,}#{RCURLY}}x
285
+ N_DIGIT_UNICODE = %r{#{Punctuation::LCURLY}#{UNICODE_DIGIT}{4,}#{Punctuation::RCURLY}}x
59
286
  UNICODE_ESCAPE = %r{\\u(?:#{FOUR_DIGIT_UNICODE}|#{N_DIGIT_UNICODE})}
60
- # # https://graphql.github.io/graphql-spec/June2018/#sec-String-Value
61
287
  STRING_ESCAPE = %r{[\\][\\/bfnrt]}
62
288
  BLOCK_QUOTE = '"""'
63
289
  ESCAPED_QUOTE = /\\"/;
64
- STRING_CHAR = /#{ESCAPED_QUOTE}|[^"\\]|#{UNICODE_ESCAPE}|#{STRING_ESCAPE}/
65
-
66
- LIT_NAME_LUT = Literals.constants.each_with_object({}) { |n, o|
67
- key = Literals.const_get(n)
68
- key = key.is_a?(Regexp) ? key.source.gsub(/(\\b|\\)/, '') : key
69
- o[key] = n
70
- }
71
-
72
- LIT = Regexp.union(Literals.constants.map { |n| Literals.const_get(n) })
73
-
74
- QUOTED_STRING = %r{#{QUOTE} (?:#{STRING_CHAR})* #{QUOTE}}x
75
- BLOCK_STRING = %r{
290
+ STRING_CHAR = /#{ESCAPED_QUOTE}|[^"\\\n\r]|#{UNICODE_ESCAPE}|#{STRING_ESCAPE}/
291
+ QUOTED_STRING_REGEXP = %r{#{QUOTE} (?:#{STRING_CHAR})* #{QUOTE}}x
292
+ BLOCK_STRING_REGEXP = %r{
76
293
  #{BLOCK_QUOTE}
77
294
  (?: [^"\\] | # Any characters that aren't a quote or slash
78
295
  (?<!") ["]{1,2} (?!") | # Any quotes that don't have quotes next to them
@@ -84,169 +301,78 @@ module GraphQL
84
301
  #{BLOCK_QUOTE}
85
302
  }xm
86
303
 
87
- # # catch-all for anything else. must be at the bottom for precedence.
88
- UNKNOWN_CHAR = /./
89
-
90
- def initialize(value)
91
- @line = 1
92
- @col = 1
93
- @previous_token = nil
94
-
95
- @scan = scanner value
96
- end
97
-
98
- class BadEncoding < Lexer # :nodoc:
99
- def scanner(value)
100
- [emit(:BAD_UNICODE_ESCAPE, 0, 0, value)]
101
- end
102
-
103
- def next_token
104
- @scan.pop
105
- end
304
+ # Use this array to check, for a given byte that will start a token,
305
+ # what kind of token might it start?
306
+ FIRST_BYTES = Array.new(255)
307
+
308
+ module ByteFor
309
+ NUMBER = 0 # int or float
310
+ NAME = 1 # identifier or keyword
311
+ STRING = 2
312
+ ELLIPSIS = 3
313
+ IDENTIFIER = 4 # identifier, *not* a keyword
314
+ PUNCTUATION = 5
106
315
  end
107
316
 
108
- def self.tokenize(string)
109
- value = string.dup.force_encoding(Encoding::UTF_8)
110
-
111
- scanner = if value.valid_encoding?
112
- new value
113
- else
114
- BadEncoding.new value
115
- end
116
-
117
- toks = []
118
-
119
- while tok = scanner.next_token
120
- toks << tok
121
- end
122
-
123
- toks
124
- end
125
-
126
- def next_token
127
- return if @scan.eos?
128
-
129
- pos = @scan.pos
130
-
131
- case
132
- when str = @scan.scan(FLOAT) then emit(:FLOAT, pos, @scan.pos, str)
133
- when str = @scan.scan(INT) then emit(:INT, pos, @scan.pos, str)
134
- when str = @scan.scan(LIT) then emit(LIT_NAME_LUT[str], pos, @scan.pos, -str)
135
- when str = @scan.scan(IDENTIFIER) then emit(:IDENTIFIER, pos, @scan.pos, str)
136
- when str = @scan.scan(BLOCK_STRING) then emit_block(pos, @scan.pos, str.gsub(/\A#{BLOCK_QUOTE}|#{BLOCK_QUOTE}\z/, ''))
137
- when str = @scan.scan(QUOTED_STRING) then emit_string(pos, @scan.pos, str.gsub(/^"|"$/, ''))
138
- when str = @scan.scan(COMMENT) then record_comment(pos, @scan.pos, str)
139
- when str = @scan.scan(NEWLINE)
140
- @line += 1
141
- @col = 1
142
- next_token
143
- when @scan.scan(BLANK)
144
- @col += @scan.pos - pos
145
- next_token
146
- when str = @scan.scan(UNKNOWN_CHAR) then emit(:UNKNOWN_CHAR, pos, @scan.pos, str)
147
- else
148
- # This should never happen since `UNKNOWN_CHAR` ensures we make progress
149
- raise "Unknown string?"
150
- end
317
+ (0..9).each { |i| FIRST_BYTES[i.to_s.ord] = ByteFor::NUMBER }
318
+ FIRST_BYTES["-".ord] = ByteFor::NUMBER
319
+ # Some of these may be overwritten below, if keywords start with the same character
320
+ ("A".."Z").each { |char| FIRST_BYTES[char.ord] = ByteFor::IDENTIFIER }
321
+ ("a".."z").each { |char| FIRST_BYTES[char.ord] = ByteFor::IDENTIFIER }
322
+ FIRST_BYTES['_'.ord] = ByteFor::IDENTIFIER
323
+ FIRST_BYTES['.'.ord] = ByteFor::ELLIPSIS
324
+ FIRST_BYTES['"'.ord] = ByteFor::STRING
325
+ KEYWORDS.each { |kw| FIRST_BYTES[kw.getbyte(0)] = ByteFor::NAME }
326
+ Punctuation.constants.each do |punct_name|
327
+ punct = Punctuation.const_get(punct_name)
328
+ FIRST_BYTES[punct.ord] = ByteFor::PUNCTUATION
151
329
  end
330
+ FIRST_BYTES.freeze
152
331
 
153
- def emit(token_name, ts, te, token_value)
154
- token = [
155
- token_name,
156
- @line,
157
- @col,
158
- token_value,
159
- @previous_token,
160
- ]
161
- @previous_token = token
162
- # Bump the column counter for the next token
163
- @col += te - ts
164
- token
165
- end
166
332
 
167
333
  # Replace any escaped unicode or whitespace with the _actual_ characters
168
334
  # To avoid allocating more strings, this modifies the string passed into it
169
335
  def self.replace_escaped_characters_in_place(raw_string)
170
- raw_string.gsub!(ESCAPES, ESCAPES_REPLACE)
171
- raw_string.gsub!(UTF_8) do |_matched_str|
172
- codepoint_1 = ($1 || $2).to_i(16)
173
- codepoint_2 = $3
174
-
175
- if codepoint_2
176
- codepoint_2 = codepoint_2.to_i(16)
177
- if (codepoint_1 >= 0xD800 && codepoint_1 <= 0xDBFF) && # leading surrogate
178
- (codepoint_2 >= 0xDC00 && codepoint_2 <= 0xDFFF) # trailing surrogate
179
- # A surrogate pair
180
- combined = ((codepoint_1 - 0xD800) * 0x400) + (codepoint_2 - 0xDC00) + 0x10000
181
- [combined].pack('U'.freeze)
336
+ raw_string.gsub!(ESCAPED) do |matched_str|
337
+ if (point_str_1 = $1 || $2)
338
+ codepoint_1 = point_str_1.to_i(16)
339
+ if (codepoint_2 = $3)
340
+ codepoint_2 = codepoint_2.to_i(16)
341
+ if (codepoint_1 >= 0xD800 && codepoint_1 <= 0xDBFF) && # leading surrogate
342
+ (codepoint_2 >= 0xDC00 && codepoint_2 <= 0xDFFF) # trailing surrogate
343
+ # A surrogate pair
344
+ combined = ((codepoint_1 - 0xD800) * 0x400) + (codepoint_2 - 0xDC00) + 0x10000
345
+ [combined].pack('U'.freeze)
346
+ else
347
+ # Two separate code points
348
+ [codepoint_1].pack('U'.freeze) + [codepoint_2].pack('U'.freeze)
349
+ end
182
350
  else
183
- # Two separate code points
184
- [codepoint_1].pack('U'.freeze) + [codepoint_2].pack('U'.freeze)
351
+ [codepoint_1].pack('U'.freeze)
185
352
  end
186
353
  else
187
- [codepoint_1].pack('U'.freeze)
354
+ ESCAPES_REPLACE[matched_str]
188
355
  end
189
356
  end
190
357
  nil
191
358
  end
192
359
 
193
- def record_comment(ts, te, str)
194
- token = [
195
- :COMMENT,
196
- @line,
197
- @col,
198
- str,
199
- @previous_token,
200
- ]
201
-
202
- @previous_token = token
203
-
204
- @col += te - ts
205
- next_token
206
- end
207
-
208
- ESCAPES = /\\["\\\/bfnrt]/
209
- ESCAPES_REPLACE = {
210
- '\\"' => '"',
211
- "\\\\" => "\\",
212
- "\\/" => '/',
213
- "\\b" => "\b",
214
- "\\f" => "\f",
215
- "\\n" => "\n",
216
- "\\r" => "\r",
217
- "\\t" => "\t",
218
- }
219
- UTF_8 = /\\u(?:([\dAa-f]{4})|\{([\da-f]{4,})\})(?:\\u([\dAa-f]{4}))?/i
220
- VALID_STRING = /\A(?:[^\\]|#{ESCAPES}|#{UTF_8})*\z/o
221
-
222
- def emit_block(ts, te, value)
223
- line_incr = value.count("\n")
224
- value = GraphQL::Language::BlockString.trim_whitespace(value)
225
- tok = emit_string(ts, te, value)
226
- @line += line_incr
227
- tok
228
- end
229
-
230
- def emit_string(ts, te, value)
231
- if !value.valid_encoding? || !value.match?(VALID_STRING)
232
- emit(:BAD_UNICODE_ESCAPE, ts, te, value)
233
- else
234
- self.class.replace_escaped_characters_in_place(value)
235
-
236
- if !value.valid_encoding?
237
- emit(:BAD_UNICODE_ESCAPE, ts, te, value)
238
- else
239
- emit(:STRING, ts, te, value)
240
- end
360
+ # This is not used during parsing because the parser
361
+ # doesn't actually need tokens.
362
+ def self.tokenize(string)
363
+ lexer = GraphQL::Language::Lexer.new(string)
364
+ tokens = []
365
+ while (token_name = lexer.advance)
366
+ new_token = [
367
+ token_name,
368
+ lexer.line_number,
369
+ lexer.column_number,
370
+ lexer.debug_token_value(token_name),
371
+ ]
372
+ tokens << new_token
241
373
  end
374
+ tokens
242
375
  end
243
-
244
- private
245
-
246
- def scanner(value)
247
- StringScanner.new value
248
- end
249
-
250
376
  end
251
377
  end
252
378
  end