Class: Tina4::GraphQL
- Inherits:
-
Object
- Object
- Tina4::GraphQL
- Defined in:
- lib/tina4/graphql.rb
Overview
─── Main GraphQL class ──────────────────────────────────────────────
Class Attribute Summary collapse
-
.default_instance ⇒ Object
Returns the value of attribute default_instance.
Instance Attribute Summary collapse
-
#max_depth ⇒ Object
Maximum selection-set nesting depth (DoS / stack-overflow guard).
-
#schema ⇒ Object
readonly
Returns the value of attribute schema.
Class Method Summary collapse
-
._clear_class_resolvers! ⇒ Object
Test-only — clear the class-level registry.
-
.auto_schema_enabled? ⇒ Boolean
Class-level toggle for ORM auto-schema generation.
- .class_resolvers ⇒ Object
-
.resolve(type_name, field_name, &block) ⇒ Object
Decorator-style resolver registration.
Instance Method Summary collapse
-
#execute(query, variables: {}, context: {}, operation_name: nil) ⇒ Object
Execute a query string directly.
-
#field_resolver(type_name, field_name) ⇒ Object
Get the field resolver registered for an object type, if any.
-
#handle_request(body, context: {}) ⇒ Object
Handle an HTTP request body (JSON string).
-
#initialize(schema = nil) ⇒ GraphQL
constructor
A new instance of GraphQL.
-
#introspect ⇒ Object
Return schema metadata for debugging.
-
#register_route(path = nil) ⇒ Object
── Route Registration ───────────────────────────────────────────── Register a POST /graphql route in the Tina4 router.
-
#schema_sdl ⇒ Object
Return schema as GraphQL SDL string.
Constructor Details
#initialize(schema = nil) ⇒ GraphQL
Returns a new instance of GraphQL.
964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 |
# File 'lib/tina4/graphql.rb', line 964 def initialize(schema = nil) @schema = schema || GraphQLSchema.new # Maximum selection-set nesting depth. A deeply nested query (or a # circular fragment) would otherwise recurse without bound — a classic # GraphQL DoS / stack-overflow vector. Default 50 is far beyond any # legitimate query; TINA4_GRAPHQL_MAX_DEPTH <= 0 disables the guard. @max_depth = Integer(ENV.fetch("TINA4_GRAPHQL_MAX_DEPTH", "50"), exception: false) || 50 @executor = GraphQLExecutor.new(@schema, max_depth: @max_depth) @field_resolvers = {} # Drain any resolvers registered via the class-level GraphQL.resolve() # BEFORE this instance was constructed. self.class.class_resolvers.each do |type_name, fields| fields.each do |field_name, resolver| attach_resolver(type_name, field_name, resolver) end end end |
Class Attribute Details
.default_instance ⇒ Object
Returns the value of attribute default_instance.
920 921 922 |
# File 'lib/tina4/graphql.rb', line 920 def default_instance @default_instance end |
Instance Attribute Details
#max_depth ⇒ Object
Maximum selection-set nesting depth (DoS / stack-overflow guard). Read from TINA4_GRAPHQL_MAX_DEPTH (default 50; <= 0 disables). Exposed so tests/app code can override it; the writer keeps the executor in sync.
892 893 894 |
# File 'lib/tina4/graphql.rb', line 892 def max_depth @max_depth end |
#schema ⇒ Object (readonly)
Returns the value of attribute schema.
887 888 889 |
# File 'lib/tina4/graphql.rb', line 887 def schema @schema end |
Class Method Details
._clear_class_resolvers! ⇒ Object
Test-only — clear the class-level registry. Used by parity tests to avoid bleed-over between cases.
958 959 960 961 |
# File 'lib/tina4/graphql.rb', line 958 def _clear_class_resolvers! @class_resolvers = {} @default_instance = nil end |
.auto_schema_enabled? ⇒ Boolean
Class-level toggle for ORM auto-schema generation. Defaults to true, can be disabled via TINA4_GRAPHQL_AUTO_SCHEMA=false. Initializers and user app code can branch on this before calling ‘schema.from_orm(…)`.
902 903 904 905 |
# File 'lib/tina4/graphql.rb', line 902 def self.auto_schema_enabled? val = ENV.fetch("TINA4_GRAPHQL_AUTO_SCHEMA", "true").to_s.strip.downcase !%w[false 0 no off].include?(val) end |
.class_resolvers ⇒ Object
922 923 924 |
# File 'lib/tina4/graphql.rb', line 922 def class_resolvers @class_resolvers ||= {} end |
.resolve(type_name, field_name, &block) ⇒ Object
Decorator-style resolver registration.
Tina4::GraphQL.resolve("Query", "products") do |root, args, ctx|
Product.all
end
Tina4::GraphQL.resolve("Mutation", "createProduct") do |root, args, ctx|
Product.create(args["input"])
end
Tina4::GraphQL.resolve("Product", "reviews") do |product, args, ctx|
Review.where("product_id = ?", [product["id"]])
end
Resolvers registered before any GraphQL instance accumulate in a class-level registry. new GraphQL drains them into its schema. Resolvers registered after .default_instance is set wire into the live schema immediately.
944 945 946 947 948 949 950 951 952 953 954 |
# File 'lib/tina4/graphql.rb', line 944 def resolve(type_name, field_name, &block) class_resolvers[type_name] ||= {} class_resolvers[type_name][field_name] = block # If a default instance is active, attach immediately so post-startup # registrations take effect without re-instantiation. if @default_instance @default_instance.send(:attach_resolver, type_name, field_name, block) end block end |
Instance Method Details
#execute(query, variables: {}, context: {}, operation_name: nil) ⇒ Object
Execute a query string directly
1012 1013 1014 1015 1016 1017 1018 1019 1020 |
# File 'lib/tina4/graphql.rb', line 1012 def execute(query, variables: {}, context: {}, operation_name: nil) parser = GraphQLParser.new(query) document = parser.parse @executor.execute(document, variables: variables, context: context, operation_name: operation_name) rescue GraphQLError => e { "data" => nil, "errors" => [{ "message" => e. }] } rescue => e { "data" => nil, "errors" => [{ "message" => "Internal error: #{e.}" }] } end |
#field_resolver(type_name, field_name) ⇒ Object
Get the field resolver registered for an object type, if any. Used by the executor during nested field resolution.
1007 1008 1009 |
# File 'lib/tina4/graphql.rb', line 1007 def field_resolver(type_name, field_name) @field_resolvers.dig(type_name, field_name) end |
#handle_request(body, context: {}) ⇒ Object
Handle an HTTP request body (JSON string)
1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 |
# File 'lib/tina4/graphql.rb', line 1059 def handle_request(body, context: {}) payload = JSON.parse(body) query = payload["query"] || "" variables = payload["variables"] || {} op_name = payload["operationName"] execute(query, variables: variables, context: context, operation_name: op_name) rescue JSON::ParserError { "data" => nil, "errors" => [{ "message" => "Invalid JSON in request body" }] } end |
#introspect ⇒ Object
Return schema metadata for debugging.
1052 1053 1054 1055 1056 |
# File 'lib/tina4/graphql.rb', line 1052 def introspect queries = @schema.queries.transform_values { |v| { type: v[:type], args: v[:args] || {} } } mutations = @schema.mutations.transform_values { |v| { type: v[:type], args: v[:args] || {} } } { types: @schema.types.keys, queries: queries, mutations: mutations } end |
#register_route(path = nil) ⇒ Object
1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 |
# File 'lib/tina4/graphql.rb', line 1077 def register_route(path = nil) # TINA4_GRAPHQL_ENDPOINT — defaults to /graphql when caller doesn't override. path ||= ENV.fetch("TINA4_GRAPHQL_ENDPOINT", "/graphql") path = path.to_s path = "/#{path}" unless path.start_with?("/") graphql = self Tina4.post path, auth: false do |request, response| # handle_request expects the raw JSON text (it JSON.parses internally), # so read body_raw — request.body now returns the PARSED payload. body = request.body_raw result = graphql.handle_request(body, context: { request: request }) response.json(result) end # Optional: GET for GraphiQL/introspection Tina4.get path, auth: false do |request, response| query = request.params["query"] if query variables = request.params["variables"] variables = JSON.parse(variables) if variables.is_a?(String) && !variables.empty? result = graphql.execute(query, variables: variables || {}, context: { request: request }) response.json(result) else response.html(graphiql_html(path)) end end end |
#schema_sdl ⇒ Object
Return schema as GraphQL SDL string.
1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 |
# File 'lib/tina4/graphql.rb', line 1023 def schema_sdl sdl = "" @schema.types.each do |name, type_obj| sdl += "type #{name} {\n" type_obj.fields.each { |f| sdl += " #{f[:name]}: #{f[:type]}\n" } sdl += "}\n\n" end unless @schema.queries.empty? sdl += "type Query {\n" @schema.queries.each do |name, config| args = (config[:args] || {}).map { |k, v| "#{k}: #{v}" }.join(", ") arg_str = args.empty? ? "" : "(#{args})" sdl += " #{name}#{arg_str}: #{config[:type]}\n" end sdl += "}\n\n" end unless @schema.mutations.empty? sdl += "type Mutation {\n" @schema.mutations.each do |name, config| args = (config[:args] || {}).map { |k, v| "#{k}: #{v}" }.join(", ") arg_str = args.empty? ? "" : "(#{args})" sdl += " #{name}#{arg_str}: #{config[:type]}\n" end sdl += "}\n\n" end sdl end |