graphb

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 29, 2022 License: MIT Imports: 4 Imported by: 0

README

graphb: A GraphQL query builder

This is a fork of github.com/udacity/graphb. Upstream has unfortunately been abandoned. See the bottom of the README for a list of differences.

Focus on the query, not string manipulation

Motivation

As Go developers, when there is a GraphQL server, we often build our query string like this:

// Define this in some file x.go
const queryTemplate = `
    "query": "
        query an_operation_name { 
            a_field_name (
                an_argument_name_if_any: \"%s\"
            ) {
                a_sub_field,
                another_sub_field
            }
        }"`
    
// DO this in some other file y.go
func buildQuery(value string) string {
    return fmt.Sprintf(queryTemplate, value)
}

This approach is verbose, inflexible and error prone.

It is verbose and inflexible because every time you want to query a different structure, you need to rewrite another template and another string interpolation function. Not to mention when you have argument types such lists or enums or when you want to use fragments, directives and other syntax of GraphQL.

It is error prone because there is no way to check the correctness of your syntax until the query string is send to the server.

It also wastes extra spaces and looks not beautiful.

Therefore, when it comes to GraphQL client in Go, a developer spends much of her time to fight the string manipulation war (that's easy to lose), rather than focusing on the query (aka the business logic).

This library solves the string building problem so that you focus on business logic.

Example

All code are well documented. See example dir for more examples.

The lib provides 3 ways of constructing a query.

  1. Method Chaining
  2. Functional Options
  3. Struct Literal
1. Method Chaining

See example/three_ways_to_construct_query_test.go#L10-L43

2. Functional Options

See example/three_ways_to_construct_query_test.go#L45-L74

3. Struct Literal

See example/three_ways_to_construct_query_test.go#L76-L100

Words from the author

The library catches cycles. That is, if you have a Field whose sub Fields can reach the Field itself, the library reports an error.

I hesitate to make all fields private and only allow constructing a query through NewQuery and MakeQuery. I also don't know if MakeQuery is better than NewQuery or the contrary. Please use it and give feedback.

Error Handling

All graphb errors are wrapped by pkg/errors.
All error types are defined in error.go

Test

graphb uses testify/assert.

go test

Todos

The library does not currently support:

  1. Directive
  2. Variables
  3. Fragments

I do not know how useful would them be for a user of this library. Since the library builds the string for you, you sort of get the functionality of Variable and Fragment for free. You can just reuse a Field or the values of Fields and Arguments as normal Go code. Directive might be the most useful one for this library.

Differences to abandoned upstream

  • ArgumentEnum enables adding string arguments that aren't wrapped in quotes, serving as enums. Values are checked against the regex EnumValuePattern. If the value doesn't match, the function panics.
  • Query.GraphString returns GraphQL query string without wrapping it in JSON.

Documentation

Index

Constants

View Source
const (
	TypeQuery        operationType = "query"
	TypeMutation     operationType = "mutation"
	TypeSubscription operationType = "subscription"
)

3 types of operation.

View Source
const (
	EnumValuePattern = "^[A-Z_][A-Z0-9_]*$"
)

Variables

This section is empty.

Functions

func StringFromChan

func StringFromChan(c <-chan string) string

StringFromChan builds a string from a channel, assuming the channel has been closed.

Types

type Argument

type Argument struct {
	Name  string
	Value argumentValue
}

func ArgumentAny

func ArgumentAny(name string, value interface{}) (Argument, error)

func ArgumentBool

func ArgumentBool(name string, value bool) Argument

func ArgumentBoolSlice

func ArgumentBoolSlice(name string, values ...bool) Argument

func ArgumentCustomType

func ArgumentCustomType(name string, values ...Argument) Argument

ArgumentCustomType returns a custom GraphQL type's argument representation, which could be a recursive structure.

func ArgumentCustomTypeSlice

func ArgumentCustomTypeSlice(name string, values ...[]Argument) Argument

func ArgumentCustomTypeSliceElem

func ArgumentCustomTypeSliceElem(values ...Argument) []Argument

func ArgumentEnum

func ArgumentEnum(name string, value string) Argument

func ArgumentInt

func ArgumentInt(name string, value int) Argument

func ArgumentIntSlice

func ArgumentIntSlice(name string, values ...int) Argument

func ArgumentString

func ArgumentString(name string, value string) Argument

func ArgumentStringSlice

func ArgumentStringSlice(name string, values ...string) Argument

type ArgumentTypeNotSupportedErr

type ArgumentTypeNotSupportedErr struct {
	Value interface{}
}

ArgumentTypeNotSupportedErr is returned when user tries to pass an unsupported type to ArgumentAny.

func (ArgumentTypeNotSupportedErr) Error

type CyclicFieldErr

type CyclicFieldErr struct {
	Field Field
}

CyclicFieldErr is returned when any field contains a loop which goes back to itself.

func (CyclicFieldErr) Error

func (e CyclicFieldErr) Error() string

type Field

type Field struct {
	Name      string
	Alias     string
	Arguments []Argument
	Fields    []*Field
	E         error
}

Field is a recursive data struct which represents a GraphQL query field.

func Fields

func Fields(args ...string) []*Field

Fields takes a list of strings and make them a slice of *Field. This is useful when you want fields with no sub fields. For example:

query { courses { id, key } }

can be written as:

Query{
	Type: "query",
	Fields: []*Field{
		{
			Name:      "courses",
			Fields:    Fields("id", "key"),
		},
	},
}

func MakeField

func MakeField(name string) *Field

MakeField constructs a Field of given name and return the pointer to this Field.

func NewField

func NewField(name string, options ...FieldOptionInterface) *Field

NewField uses functional options to construct a new Field and returns the pointer to it. On error, the pointer is nil. To know more about this design pattern, see https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis

func (*Field) AddArguments

func (f *Field) AddArguments(argument ...Argument) *Field

func (*Field) SetAlias

func (f *Field) SetAlias(alias string) *Field

SetAlias sets the alias of a Field and return the pointer to this Field.

func (*Field) SetArguments

func (f *Field) SetArguments(arguments ...Argument) *Field

SetArguments sets the arguments of a Field and return the pointer to this Field.

func (*Field) SetFields

func (f *Field) SetFields(fs ...*Field) *Field

SetFields sets the sub fields of a Field and return the pointer to this Field.

func (*Field) StringChan

func (f *Field) StringChan() (<-chan string, error)

StringChan returns read only string token channel or an error. It checks if there is a circle.

type FieldContainerOption

type FieldContainerOption func(fc fieldContainer) error

FieldContainerOption implements FieldOptionInterface and QueryOptionInterface, which means, it can be used as the functional option for both NewQuery() and NewField(). FieldContainerOption is a function which takes in a fieldContainer and config it. Both Query and Field are fieldContainer.

func OfField

func OfField(name string, options ...FieldOptionInterface) FieldContainerOption

OfField returns a FieldContainerOption and has the same parameter signature of NewField(name string, options ...FieldOptionInterface) (*Field, error)

type FieldOption

type FieldOption func(field *Field) error

FieldOption implements FieldOptionInterface

func OfAlias

func OfAlias(alias string) FieldOption

func OfArguments

func OfArguments(arguments ...Argument) FieldOption

OfArguments returns a FieldOption which sets the arguments of the targeting field.

func OfFields

func OfFields(name ...string) FieldOption

OfFields returns a FieldOption which sets a list of sub fields of given names of the targeting field. All the sub fields only have one level which is their names. That is, no sub fields have sub fields.

type FieldOptionInterface

type FieldOptionInterface interface {
	// contains filtered or unexported methods
}

FieldOptionInterface implements functional options for NewField().

type InvalidNameErr

type InvalidNameErr struct {
	Type nameType
	Name string
}

InvalidNameErr is returned when an invalid name is used. In GraphQL, operation, alias, field and argument all have names. A valid name matches ^[_A-Za-z][_0-9A-Za-z]*$ exactly.

func (InvalidNameErr) Error

func (e InvalidNameErr) Error() string

type InvalidOperationTypeErr

type InvalidOperationTypeErr struct {
	Type operationType
}

InvalidOperationTypeErr is returned when the operation is not one of query, mutation and subscription.

func (InvalidOperationTypeErr) Error

func (e InvalidOperationTypeErr) Error() string

type NilFieldErr

type NilFieldErr struct{}

NilFieldErr is returned when any field is nil. Of course the author could choose to ignore nil fields. But, author chose a stricter construct.

func (NilFieldErr) Error

func (e NilFieldErr) Error() string

type Query

type Query struct {
	Type   operationType // The operation type is either query, mutation, or subscription.
	Name   string        // The operation name is a meaningful and explicit name for your operation.
	Fields []*Field
	E      error
}

Query represents a GraphQL query. Though all fields (Go struct field, not GraphQL field) of this struct is public, the author recommends you to use functions in public.go.

func MakeQuery

func MakeQuery(Type operationType) *Query

MakeQuery constructs a Query of the given type and returns a pointer of it.

func NewQuery

func NewQuery(Type operationType, options ...QueryOptionInterface) *Query

NewQuery uses functional options to construct a new Query and returns the pointer to it. On error, the pointer is nil. Type is required. Other options such as operation name and alias are optional.

func (*Query) AddFields

func (q *Query) AddFields(fields ...*Field) *Query

AddFields adds to the Fields field of this Query.

func (*Query) GetField

func (q *Query) GetField(name string) *Field

GetField return the field identified by the name. Nil if not exist.

func (*Query) GraphString

func (q *Query) GraphString() (string, error)

func (*Query) JSON

func (q *Query) JSON() (string, error)

JSON returns a json string with "query" field.

func (*Query) SetFields

func (q *Query) SetFields(fields ...*Field) *Query

SetFields sets the Fields field of this Query. If q.Fields already contains data, they will be replaced.

func (*Query) SetName

func (q *Query) SetName(name string) *Query

SetName sets the Name field of this Query.

func (*Query) StringChan

func (q *Query) StringChan() (<-chan string, error)

StringChan returns a string channel and an error. When error is not nil, the channel is nil. When error is nil, the channel is guaranteed to be closed. Warning: One should never receive from a nil channel for eternity awaits by a nil channel.

type QueryOption

type QueryOption func(query *Query) error

QueryOption implements QueryOptionInterface

func OfName

func OfName(name string) QueryOption

OfName returns a QueryOption which validates and sets the operation name of a query.

type QueryOptionInterface

type QueryOptionInterface interface {
	// contains filtered or unexported methods
}

QueryOptionInterface implements functional options for NewQuery().

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL