Skip to content

Commit

Permalink
feat: response_data (#8)
Browse files Browse the repository at this point in the history
* wip: start response_data

* test: add compact layers

- Add superdiff for better diff

* update: specs for new proposals

* working on new version of response_data to support digging through hash and array for values

* core of digging through hash and array are working. still need to get digging specific array index, and shaping from hash keys

* digging through an array by index works now

* digging by array index and field now work at a basic level

* failing spec that shows it can't yet do full nesting of field, hash, array, etc

* begin extracting dig_dug to encapsulate the response digging features

* first passing spec of first level dig with a hash

* core specs to outline the feature set of dig_dug

* dig through array to nested fields

* dig through an array to return an item by index from the array

* handling multiple hash key value sets

* additional spec to show indexed item from array that exists within item that came from an array

* corrections to hand data as an array or hash

* corrected the final spec to show digging through an array, an array by index, then grabbing the name of the items in the final array

* using DigDug in response_data

* docs for response_data

* adding an intro section for syntax, to the response_data docs

* bump version to 0.4.1 to prep for release

* adjustments to the docs

* a missing space

* log: add warning of soon to come deprecation

Co-authored-by: River Lynn Bailey <riverlynnbailey@gmail.com>
  • Loading branch information
dflynn15 and mxriverlynn authored Mar 16, 2021
1 parent 0ef7c7d commit d4c4736
Show file tree
Hide file tree
Showing 17 changed files with 536 additions and 47 deletions.
13 changes: 11 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
PATH
remote: .
specs:
rspec-graphql_response (0.4.0)
rspec-graphql_response (0.4.1)
graphql (>= 1.0)
rspec (>= 3.0)

GEM
remote: https://rubygems.org/
specs:
attr_extras (6.2.4)
byebug (11.1.3)
coderay (1.1.3)
diff-lcs (1.4.4)
graphql (1.12.5)
graphql (1.12.6)
method_source (1.0.0)
optimist (3.0.1)
patience_diff (1.2.0)
optimist (~> 3.0)
pry (0.14.0)
coderay (~> 1.1)
method_source (~> 1.0)
Expand All @@ -33,6 +37,10 @@ GEM
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.10.0)
rspec-support (3.10.2)
super_diff (0.6.1)
attr_extras (>= 6.2.4)
diff-lcs
patience_diff

PLATFORMS
ruby
Expand All @@ -43,6 +51,7 @@ DEPENDENCIES
pry-byebug (~> 3.8)
rake (>= 12.0)
rspec-graphql_response!
super_diff (~> 0.6)

BUNDLED WITH
1.17.2
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ Spec Helper Methods:

- [execute_graphql](/docs/execute_graphql.md) - executes a graphql call with the registered schema, query, variables and context
- [response](/docs/response.md) - the response, as JSON, of the executed graphql query
- [operation](/docs/operation.md) - retrieves the results of a named operation from the GraphQL response
- [response_data](/docs/response_data.md) - digs through the graphql response to return data from the specified node(s)

API / Development

Expand Down
37 changes: 1 addition & 36 deletions docs/operation.md
Original file line number Diff line number Diff line change
@@ -1,38 +1,3 @@
# Using the `operation` helper

The `operation` helper will dig through a response to find a data
structure that looks like,

```ruby
{
"data" => {
operation_name
}
}
```

## Basic Use

```ruby
it "has characters" do
characters = operation(:characters)

expect(character).to include(
{ id: 1, name: "Jam" },
# ...
)
end
```

## Handling Nil

If there is no `"data"` or no named operation for the name supplied, the
`operation` helper will return `nil`

```ruby
it "returns nil if operation doesn't exist" do
character = operation(:something_that_does_not_exist)

expect(operation).to be_nil
end
```
Deprecated. See [response_data](response_data.md) instead.
2 changes: 1 addition & 1 deletion docs/response.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
# Check the GraphQL Response with Helper `response`
# The GraphQL Response, via Helper `response`
146 changes: 146 additions & 0 deletions docs/response_data.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# Using the `response_data` Helper

The `response_data` helper will dig through a graphql response, through
the outer hash, into the response data for an operation, and through any
and all layers of hash and array.

## Syntax

```ruby
response_data *[dig_pattern]
```

Data returned via this helper will assume a `"data" => ` key at the root of
the `response` object. This root does not need to be specified in the list
of attributes for the `dig_pattern`.

### Params

* `*[dig_pattern]` - an array of attributes (`:symbol`, `"string"`, or `key: :value` pair) that describes
the data structure to dig through, and the final data set to retrieve from the graphql response.

#### dig_pattern

Each attribute added to the `dig_pattern` represents an attribute at the given level of the
data structure, in numeric order from left to right. The first attribute provides will dig into
that attribute at the first level of data (just below the `"data" =>` key). The second attribute
will dig through data just below that first level, etc. etc. etc.

For example, with a data structure as shown below, in "Basic Use", you could specifiy these
attributes for the dig pattern:

* :characters
* :name

Like this:

```ruby
response_data :characters, :name
```

This dig pattern will find the `"characters"` key just below `"data"`, then iterate through
the array of characters and retrieve the `"name"` of each character.

For more details and options for the dig pattern, see the examples below.

## Basic Use

A `response` data structure may look something like the following.

```ruby
{
"data" => {
"characters" => [
{ "id" => "1", "name" => "Jam" },
{ "id" => "2", "name" => "Redemption" },
{ "id" => "3", "name" => "Pet" }
]
}
}
```

The `response_data` helper will dig through to give you simplified
results that are easier to verify.

For example, if only the names of the characters need to be checked:

```ruby
response_data :characters, :name

# => ["Jam", "Redemption", "Pet"]
```

Or perhaps only the name for 2nd character is needed:

```ruby
response_data {characters: [1]}, :name

# => "Redemption"
```

## List Every Item in an Array

Many responses from a graphql call will include an array of data somewhere
in the data structure. If you need to return all of the items in an array,
you only need to specify that array's key:

```ruby
it "has characters" do
characters = response_data(:characters)

expect(character).to include(
{ id: 1, name: "Jam" },
# ...
)
end
```

## Dig a Field From Every Item in an Array

When validation only needs to occur on a specific field for items found in
an array, there are two options.

1. Specify a list of fields as already shown
2. change the array's key to a hash and provide a `:symbol` wrapped in an array as the value

The first option was already shown in the Basic Use section above.

```ruby
response_data :characters, :name

# => ["Jam", "Redemption", "Pet"]
```

For the second option, the code would look like this:

```ruby
response_data characters: [:name]

# => ["Jam", "Redemption", "Pet"]
```

Both of these options are functionaly the same. The primary difference will be
how you wish to express the data structure in your code. Changing the list of
attributes to a hash with an array wrapping the value will provide a better
indication that an array is expected at that point in the data structure.

## Dig Out an Item By Index, From an Array

There may be times when only a single piece of a returned array needs to be
validated. To handle this, switch the key of the array to a hash, as in the
previous example. Rather than specifying a child node's key in the value, though,
specify the index of the item you wish to extract.

```ruby
response_data characters: [1]
```

This will return the character at index 1, from the array of characters.

## Handling Nil

If there is no data the key supplied, the helper will return `nil`

```ruby
response_data(:something_that_does_not_exist) #=> nil
```
1 change: 1 addition & 0 deletions lib/rspec/graphql_response.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require "rspec"

require_relative "graphql_response/dig_dug/dig_dug"
require_relative "graphql_response/version"
require_relative "graphql_response/configuration"
require_relative "graphql_response/validators"
Expand Down
83 changes: 83 additions & 0 deletions lib/rspec/graphql_response/dig_dug/dig_dug.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
module RSpec
module GraphQLResponse
class DigDug
attr_reader :dig_pattern

def initialize(*dig_pattern)
@dig_pattern = parse_dig_pattern(*dig_pattern)
end

def dig(data)
dig_data(data, dig_pattern)
end

private

def dig_data(data, patterns)
return data if patterns.nil?
return data if patterns.empty?

node = patterns[0]
node_key = node[:key]
node_key = node_key.to_s if node_key.is_a? Symbol
node_value = node[:value]

if node[:type] == :symbol
result = dig_symbol(data, node_key)
elsif node[:type] == :array
if data.is_a? Hash
child_data = data[node_key]
result = dig_symbol(child_data, node_value)
elsif data.is_a? Array
result = data.map { |value|
child_data = value[node_key]
dig_symbol(child_data, node_value)
}.compact
else
result = data
end
end

dig_data(result, patterns.drop(1))
end

def parse_dig_pattern(*pattern)
pattern_config = pattern.map do |pattern_item|
if pattern_item.is_a? Symbol
{
type: :symbol,
key: pattern_item
}
elsif pattern_item.is_a? Hash
pattern_item.map do |key, value|
{
type: :array,
key: key,
value: value[0]
}
end
end
end

pattern_config.flatten
end

def dig_symbol(data, key)
key = key.to_s if key.is_a? Symbol
return data[key] if data.is_a? Hash

if data.is_a? Array
if key.is_a? Numeric
mapped_data = data[key]
else
mapped_data = data.map { |value| value[key] }.flatten
end

return mapped_data
end

return data
end
end
end
end
5 changes: 3 additions & 2 deletions lib/rspec/graphql_response/helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,12 @@ def self.add_helper(name, scope: :spec, &helper)
end

# describe level helpers
require_relative "helpers/graphql_context"
require_relative "helpers/graphql_operation"
require_relative "helpers/graphql_variables"
require_relative "helpers/graphql_context"

# spec level helpers
require_relative "helpers/execute_graphql"
require_relative "helpers/operation"
require_relative "helpers/response"
require_relative "helpers/execute_graphql"
require_relative "helpers/response_data"
1 change: 1 addition & 0 deletions lib/rspec/graphql_response/helpers/operation.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
RSpec::GraphQLResponse.add_helper :operation do |name|
warn 'WARNING: operation has been deprecated in favor of response_data. This helper will be removed in v0.5'
return nil unless response.is_a? Hash

response.dig("data", name.to_s)
Expand Down
13 changes: 13 additions & 0 deletions lib/rspec/graphql_response/helpers/response_data.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
RSpec::GraphQLResponse.add_helper :response_data do |*fields|
next nil unless response.is_a? Hash

response_data = response["data"]
next nil if response_data.nil?
next nil if response_data.empty?

fields = fields.compact
next response_data if fields.empty?

dig_dug = RSpec::GraphQLResponse::DigDug.new(*fields)
dig_dug.dig(response_data)
end
2 changes: 1 addition & 1 deletion lib/rspec/graphql_response/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module RSpec
module GraphQLResponse
VERSION = "0.4.0"
VERSION = "0.4.1"
end
end
1 change: 1 addition & 0 deletions rspec-graphql_response.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Gem::Specification.new do |spec|
spec.add_development_dependency "rake", ">= 12.0"
spec.add_development_dependency "pry", "~> 0.14"
spec.add_development_dependency "pry-byebug", "~> 3.8"
spec.add_development_dependency "super_diff", "~> 0.6"

spec.add_runtime_dependency "rspec", ">= 3.0"
spec.add_runtime_dependency "graphql", ">= 1.0"
Expand Down
Loading

0 comments on commit d4c4736

Please sign in to comment.