tina4ruby 3.11.1 → 3.11.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aac5b02fd85a6eb7582cf1eddd1575323513bb4c26e1ddca556bd75fd54e4b35
4
- data.tar.gz: 161ef65b78f9f7007815a76494f1f24919dfc7811bf9817d398fde61ad5e4a4a
3
+ metadata.gz: ef9466f8965eb7728b8aadecd725236011298269966d3775c9f11dc1aa9b0c62
4
+ data.tar.gz: 11f5cc6ac83ad16b63a23068476517e0babe7db60f793942e2fa4b7048f12894
5
5
  SHA512:
6
- metadata.gz: c7b42bbe344e93288c3d05798039b6660650ccd5fdeac07e8b1e38b014a7d91e2975bfba34564753c2c4815c0c0851aed51421d39ab4bdce11f72a09dda2482c
7
- data.tar.gz: 051321fea2a3a94e54415a3d1a8d1a0e95d1a85617aae8ff64f22b94e54d0a2938c4a7c01231b6a4de1a63003d431c1160ec25432cc52db68c4e5146f05fdb1a
6
+ metadata.gz: d214fe2b2e79c28232fec6ed03af2d352b70854e66a1611535ad49bd6c79059110579a6d85ab2c8fdc0de8db215abcfae8fe1332d4398985a53c9a3bb4cc5ede
7
+ data.tar.gz: 5095de7d8683d27ef009859f3e9272e50be8eece9eeea94a7abf62332028cbf9a4aaabf1d3fd6468c57bfbfeca13d2b276703ea55d89d3190343686fd46d5765
data/lib/tina4/graphql.rb CHANGED
@@ -651,14 +651,29 @@ module Tina4
651
651
  field_name = selection[:name]
652
652
  output_name = selection[:alias] || field_name
653
653
 
654
+ # Check directives (@skip, @include, @auth, @role, @guest)
655
+ return unless check_directives(selection[:directives] || [], variables, context)
656
+
654
657
  # Resolve arguments (substitute variables)
655
658
  args = resolve_args(selection[:arguments], variables)
656
659
 
657
660
  field_def = fields[field_name]
658
661
 
662
+ # Input validation
663
+ if field_def && field_def[:args]
664
+ validation_errors = validate_args(args, field_def[:args], field_name)
665
+ if validation_errors.any?
666
+ errors.concat(validation_errors)
667
+ data[output_name] = nil
668
+ return
669
+ end
670
+ end
671
+
659
672
  begin
660
673
  if field_def && field_def[:resolve]
661
- value = field_def[:resolve].call(parent, args, context)
674
+ # Inject sub-selections into context for DataLoader/eager-loading
675
+ ctx = context.merge("__selections" => (selection[:selection_set] || []))
676
+ value = field_def[:resolve].call(parent, args, ctx)
662
677
  elsif parent.is_a?(Hash)
663
678
  value = parent[field_name] || parent[field_name.to_sym]
664
679
  else
@@ -705,6 +720,81 @@ module Tina4
705
720
  result
706
721
  end
707
722
 
723
+ # Check directives: @skip, @include, @auth, @role, @guest.
724
+ # Returns true if the field should be included.
725
+ def check_directives(directives, variables, context = {})
726
+ directives.each do |d|
727
+ val = d[:arguments]&.dig("if")
728
+ val = variables[val[:name]] if val.is_a?(Hash) && val[:kind] == :variable
729
+
730
+ return false if d[:name] == "skip" && val
731
+ return false if d[:name] == "include" && !val
732
+
733
+ # Auth: @auth — requires authenticated user
734
+ return false if d[:name] == "auth" && !context["user"]
735
+
736
+ # Auth: @role(role: "admin") — requires specific role
737
+ if d[:name] == "role"
738
+ required = d[:arguments]&.dig("role")
739
+ user = context["user"]
740
+ actual = user.is_a?(Hash) ? (user["role"] || user[:role]) : nil
741
+ actual ||= context["role"]
742
+ return false if required.nil? || actual != required
743
+ end
744
+
745
+ # Auth: @guest — only for unauthenticated
746
+ return false if d[:name] == "guest" && context["user"]
747
+ end
748
+ true
749
+ end
750
+
751
+ # Validate resolved args against declared types.
752
+ def validate_args(args, arg_configs, field_name)
753
+ errors = []
754
+ arg_configs.each do |arg_name, declared_type|
755
+ value = args[arg_name]
756
+ is_non_null = declared_type.to_s.end_with?("!")
757
+ base_type = declared_type.to_s.gsub(/[!\[\]]/, "").strip
758
+
759
+ if is_non_null && (value.nil? || value == "")
760
+ errors << {
761
+ "message" => "Argument '#{arg_name}' on field '#{field_name}' is required (type: #{declared_type})",
762
+ "path" => [field_name]
763
+ }
764
+ next
765
+ end
766
+
767
+ next if value.nil?
768
+
769
+ if %w[Int Float Boolean String ID].include?(base_type)
770
+ unless coerce_check(value, base_type)
771
+ errors << {
772
+ "message" => "Argument '#{arg_name}' on field '#{field_name}' expected type #{base_type}, got #{value.class}",
773
+ "path" => [field_name]
774
+ }
775
+ end
776
+ end
777
+ end
778
+ errors
779
+ end
780
+
781
+ def coerce_check(value, type_name)
782
+ case type_name
783
+ when "String", "ID"
784
+ value.is_a?(String) || value.is_a?(Numeric) || value.is_a?(Symbol)
785
+ when "Int"
786
+ return false if value.is_a?(TrueClass) || value.is_a?(FalseClass)
787
+ value.is_a?(Integer) || (value.is_a?(String) && value.match?(/\A-?\d+\z/))
788
+ when "Float"
789
+ return false if value.is_a?(TrueClass) || value.is_a?(FalseClass)
790
+ value.is_a?(Numeric) || (value.is_a?(String) && value.match?(/\A-?\d+(\.\d+)?\z/))
791
+ when "Boolean"
792
+ value.is_a?(TrueClass) || value.is_a?(FalseClass) || [0, 1, "true", "false"].include?(value)
793
+ else
794
+ true
795
+ end
796
+ end
797
+
708
798
  def resolve_args(args, variables)
709
799
  return {} unless args
710
800
  resolved = {}
data/lib/tina4/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tina4
4
- VERSION = "3.11.1"
4
+ VERSION = "3.11.2"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tina4ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.11.1
4
+ version: 3.11.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tina4 Team