├── version ├── concourse ├── scripts │ ├── integration-tests.sh │ └── publish.sh ├── tasks │ ├── publish.yml │ ├── integration-tests-1-14.yml │ ├── integration-tests-1-15.yml │ ├── integration-tests-1-16.yml │ └── integration.yml └── pipeline.yml ├── .gitignore ├── go.mod ├── .dockerignore ├── LICENSE ├── Makefile ├── faunadb ├── doc.go ├── example_test.go ├── stream.go ├── expr.go ├── field.go ├── tags.go ├── stream_subscription.go ├── reflect.go ├── faunadb_test.go ├── encode.go ├── functions_auth.go ├── field_test.go ├── encode_test.go ├── functions_read.go ├── path.go ├── errors_test.go ├── decode.go ├── benchmark_test.go ├── errors.go ├── functions_logic.go ├── stream_events.go ├── functions_sets.go ├── functions_basic.go ├── json.go ├── functions_datetime.go ├── functions_write.go ├── stream_test.go ├── functions_collections.go ├── values.go ├── functions_types.go ├── query.go ├── functions_strings.go └── deserialization_test.go ├── .travis.yml ├── go.sum ├── CHANGELOG.md ├── .circleci └── config.yml └── README.md /version: -------------------------------------------------------------------------------- 1 | v4.3.1 2 | -------------------------------------------------------------------------------- /concourse/scripts/integration-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eou 4 | 5 | apk add --update make gcc musl-dev curl 6 | 7 | while ! $(curl --output /dev/null --silent --fail --max-time 1 http://faunadb:8443/ping); do sleep 1; done 8 | 9 | make test 10 | make coverage 11 | -------------------------------------------------------------------------------- /concourse/tasks/publish.yml: -------------------------------------------------------------------------------- 1 | --- 2 | platform: linux 3 | image_resource: 4 | type: registry-image 5 | source: 6 | repository: alpine/git 7 | tag: v2.30.2 8 | 9 | inputs: 10 | - name: fauna-go-repository 11 | 12 | outputs: 13 | - name: fauna-go-repository-updated 14 | - name: slack-message 15 | 16 | run: 17 | path: ./fauna-go-repository/concourse/scripts/publish.sh 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.prof 24 | *.swp 25 | 26 | # Jenkins/Docker 27 | results/ 28 | 29 | .idea/ 30 | *.iml 31 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | // Deprecated: Fauna is decommissioning FQL v4 on June 30, 2025. This driver is not compatible with FQL v10. Fauna accounts created after August 21, 2024 must use FQL v10. Ensure you migrate existing projects to the official v10 driver by the v4 EOL date: https://github.com/fauna/fauna-go. 2 | 3 | module github.com/fauna/faunadb-go/v4 4 | 5 | require ( 6 | github.com/stretchr/testify v1.6.1 7 | golang.org/x/net v0.0.0-20201224014010-6772e930b67b 8 | ) 9 | 10 | go 1.16 -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.prof 24 | *.swp 25 | 26 | # Git 27 | .git/ 28 | .gitignore 29 | 30 | # Jenkins/Docker 31 | results/ 32 | Dockerfile.test 33 | -------------------------------------------------------------------------------- /concourse/scripts/publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eou 4 | 5 | git clone fauna-go-repository fauna-go-repository-updated 6 | 7 | cd fauna-go-repository-updated 8 | 9 | CURRENT_VERSION=$(cat version) 10 | 11 | echo "Current version: $CURRENT_VERSION" 12 | 13 | echo "Publishing a new $CURRENT_VERSION version..." 14 | git config --global user.email "nobody@concourse-ci.org" 15 | git config --global user.name "Fauna, Inc" 16 | 17 | git tag "$CURRENT_VERSION" 18 | 19 | echo "faunadb-go@$CURRENT_VERSION has been released [fyi <@ewan.edwards>]" > ../slack-message/publish 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 Fauna, Inc. 2 | 3 | Licensed under the Mozilla Public License, Version 2.0 (the "License"); you may 4 | not use this software except in compliance with the License. You may obtain a 5 | copy of the License at 6 | 7 | http://mozilla.org/MPL/2.0/ 8 | 9 | Unless required by applicable law or agreed to in writing, software distributed 10 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 11 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 12 | specific language governing permissions and limitations under the License. 13 | -------------------------------------------------------------------------------- /concourse/tasks/integration-tests-1-14.yml: -------------------------------------------------------------------------------- 1 | --- 2 | platform: linux 3 | image_resource: 4 | type: registry-image 5 | source: 6 | repository: shared-concourse-dind 7 | aws_access_key_id: ((prod-images-aws-access-key-id)) 8 | aws_secret_access_key: ((prod-images-aws-secret-key)) 9 | aws_region: us-east-2 10 | 11 | params: 12 | FAUNA_ROOT_KEY: 13 | FAUNA_ENDPOINT: 14 | 15 | inputs: 16 | - name: fauna-go-repository 17 | 18 | run: 19 | path: entrypoint.sh 20 | args: 21 | - bash 22 | - -ceu 23 | - | 24 | # start containers 25 | docker-compose -f fauna-go-repository/concourse/tasks/integration.yml run tests-14 26 | # stop and remove containers 27 | docker-compose -f fauna-go-repository/concourse/tasks/integration.yml down 28 | # remove volumes 29 | docker volume rm $(docker volume ls -q) 30 | -------------------------------------------------------------------------------- /concourse/tasks/integration-tests-1-15.yml: -------------------------------------------------------------------------------- 1 | --- 2 | platform: linux 3 | image_resource: 4 | type: registry-image 5 | source: 6 | repository: shared-concourse-dind 7 | aws_access_key_id: ((prod-images-aws-access-key-id)) 8 | aws_secret_access_key: ((prod-images-aws-secret-key)) 9 | aws_region: us-east-2 10 | 11 | params: 12 | FAUNA_ROOT_KEY: 13 | FAUNA_ENDPOINT: 14 | 15 | inputs: 16 | - name: fauna-go-repository 17 | 18 | run: 19 | path: entrypoint.sh 20 | args: 21 | - bash 22 | - -ceu 23 | - | 24 | # start containers 25 | docker-compose -f fauna-go-repository/concourse/tasks/integration.yml run tests-15 26 | # stop and remove containers 27 | docker-compose -f fauna-go-repository/concourse/tasks/integration.yml down 28 | # remove volumes 29 | docker volume rm $(docker volume ls -q) 30 | -------------------------------------------------------------------------------- /concourse/tasks/integration-tests-1-16.yml: -------------------------------------------------------------------------------- 1 | --- 2 | platform: linux 3 | image_resource: 4 | type: registry-image 5 | source: 6 | repository: shared-concourse-dind 7 | aws_access_key_id: ((prod-images-aws-access-key-id)) 8 | aws_secret_access_key: ((prod-images-aws-secret-key)) 9 | aws_region: us-east-2 10 | 11 | params: 12 | FAUNA_ROOT_KEY: 13 | FAUNA_ENDPOINT: 14 | 15 | inputs: 16 | - name: fauna-go-repository 17 | 18 | run: 19 | path: entrypoint.sh 20 | args: 21 | - bash 22 | - -ceu 23 | - | 24 | # start containers 25 | docker-compose -f fauna-go-repository/concourse/tasks/integration.yml run tests-16 26 | # stop and remove containers 27 | docker-compose -f fauna-go-repository/concourse/tasks/integration.yml down 28 | # remove volumes 29 | docker volume rm $(docker volume ls -q) 30 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | RUNTIME_IMAGE ?= golang:1.9 2 | DOCKER_RUN_FLAGS = -it --rm 3 | 4 | ifdef FAUNA_ROOT_KEY 5 | DOCKER_RUN_FLAGS += -e FAUNA_ROOT_KEY=$(FAUNA_ROOT_KEY) 6 | endif 7 | 8 | ifdef FAUNA_ENDPOINT 9 | DOCKER_RUN_FLAGS += -e FAUNA_ENDPOINT=$(FAUNA_ENDPOINT) 10 | endif 11 | 12 | ifdef FAUNA_TIMEOUT 13 | DOCKER_RUN_FLAGS += -e FAUNA_TIMEOUT=$(FAUNA_TIMEOUT) 14 | endif 15 | 16 | ifdef FAUNA_QUERY_TIMEOUT_MS 17 | DOCKER_RUN_FLAGS += -e FAUNA_QUERY_TIMEOUT_MS=$(FAUNA_QUERY_TIMEOUT_MS) 18 | endif 19 | 20 | test: 21 | go test -v ./... 22 | 23 | coverage: 24 | go test -v -race -coverprofile=coverage.txt -covermode=atomic ./... 25 | 26 | jenkins-test: 27 | go test -v -race -coverprofile=/fauna/faunadb-go/results/coverage.txt -covermode=atomic ./... 2>&1 | tee log.txt 28 | go-junit-report -package-name faunadb -set-exit-code < log.txt > /fauna/faunadb-go/results/report.xml 29 | 30 | docker-wait: 31 | dockerize -wait $(FAUNA_ENDPOINT)/ping -timeout $(FAUNA_TIMEOUT) 32 | -------------------------------------------------------------------------------- /faunadb/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package faunadb implements the FaunaDB query language for Golang applications. 3 | 4 | FaunaClient is the main client structure, containing methods to communicate with a FaunaDB Cluster. 5 | This structure is designed to be reused, so avoid making copies of it. 6 | 7 | FaunaDB's query language is composed of expressions that implement the Expr interface. 8 | Expressions are created using the query language functions found in query.go. 9 | 10 | Responses returned by FaunaDB are wrapped into types that implement the Value interface. This interface provides 11 | methods for transversing and decoding FaunaDB values into native Go types. 12 | 13 | The driver allows for the user to encode custom data structures. You can create your own struct 14 | and encode it as a valid FaunaDB object. 15 | 16 | type User struct { 17 | Name string 18 | Age int 19 | } 20 | 21 | user := User{"John", 24} // Encodes as: {"Name": "John", "Age": 24} 22 | 23 | If you wish to control the property names, you can tag them with the "fauna" tag: 24 | 25 | type User struct { 26 | Name string `fauna:"displayName"` 27 | Age int `fauna:"age"` 28 | } 29 | 30 | user := User{"John", 24} // Encodes as: {"displayName": "John", "age": 24} 31 | 32 | For more information about FaunaDB, check https://fauna.com/. 33 | */ 34 | package faunadb 35 | -------------------------------------------------------------------------------- /faunadb/example_test.go: -------------------------------------------------------------------------------- 1 | package faunadb_test 2 | 3 | import f "github.com/fauna/faunadb-go/v4/faunadb" 4 | 5 | var ( 6 | data = f.ObjKey("data") 7 | ref = f.ObjKey("ref") 8 | ) 9 | 10 | type Profile struct { 11 | Name string `fauna:"name"` 12 | Verified bool `fauna:"verified"` 13 | } 14 | 15 | func Example() { 16 | var profileId f.RefV 17 | 18 | // Crate a new client 19 | client := f.NewFaunaClient("your-secret-here") 20 | 21 | // Create a collection to store profiles 22 | _, _ = client.Query(f.CreateCollection(f.Obj{"name": "profiles"})) 23 | 24 | // Create a new profile entry 25 | profile := Profile{ 26 | Name: "Jhon", 27 | Verified: false, 28 | } 29 | 30 | // Save profile at FaunaDB 31 | newProfile, _ := client.Query( 32 | f.Create( 33 | f.Collection("profiles"), 34 | f.Obj{"data": profile}, 35 | ), 36 | ) 37 | 38 | // Get generated profile ID 39 | _ = newProfile.At(ref).Get(&profileId) 40 | 41 | // Update existing profile entry 42 | _, _ = client.Query( 43 | f.Update( 44 | profileId, 45 | f.Obj{"data": f.Obj{ 46 | "verified": true, 47 | }}, 48 | ), 49 | ) 50 | 51 | // Retrieve profile by its ID 52 | value, _ := client.Query(f.Get(profileId)) 53 | _ = value.At(data).Get(&profile) 54 | 55 | // Delete profile using its ID 56 | _, _ = client.Query(f.Delete(profileId)) 57 | } 58 | -------------------------------------------------------------------------------- /faunadb/stream.go: -------------------------------------------------------------------------------- 1 | package faunadb 2 | 3 | // StreamField represents a stream field 4 | type StreamField string 5 | 6 | const ( 7 | DiffField StreamField = "diff" 8 | PrevField StreamField = "prev" 9 | DocumentField StreamField = "document" 10 | ActionField StreamField = "action" 11 | IndexField StreamField = "index" 12 | ) 13 | 14 | type streamConfig struct { 15 | Fields []StreamField 16 | } 17 | 18 | // StreamConfig describes optional parameters for a stream subscription 19 | type StreamConfig func(*StreamSubscription) 20 | 21 | // StreamConnectionStatus is a expression shortcut to represent 22 | // the stream connection status 23 | type StreamConnectionStatus int 24 | 25 | const ( 26 | // StreamConnIdle represents an idle stream subscription 27 | StreamConnIdle StreamConnectionStatus = iota 28 | // StreamConnActive describes an active/established stream subscription 29 | StreamConnActive 30 | // StreamConnClosed describes a closed stream subscription 31 | StreamConnClosed 32 | // StreamConnError describes a stream subscription error 33 | StreamConnError 34 | ) 35 | 36 | // Fields is optional stream parameter that describes the fields received on version and history_rewrite stream events. 37 | func Fields(fields ...StreamField) StreamConfig { 38 | return func(sub *StreamSubscription) { 39 | sub.config.Fields = []StreamField(fields) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /faunadb/expr.go: -------------------------------------------------------------------------------- 1 | package faunadb 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | /* 8 | Expr is the base type for FaunaDB query language expressions. 9 | 10 | Expressions are created by using the query language functions in query.go. Query functions are designed to compose with each other, as well as with 11 | custom data structures. For example: 12 | 13 | type User struct { 14 | Name string 15 | } 16 | 17 | _, _ := client.Query( 18 | Create( 19 | Collection("users"), 20 | Obj{"data": User{"John"}}, 21 | ), 22 | ) 23 | 24 | */ 25 | type Expr interface { 26 | expr() // Make sure only internal structures can be marked as valid expressions 27 | } 28 | 29 | type unescapedObj map[string]Expr 30 | type unescapedArr []Expr 31 | type invalidExpr struct{ err error } 32 | 33 | func (obj unescapedObj) expr() {} 34 | 35 | func (arr unescapedArr) expr() {} 36 | 37 | func (inv invalidExpr) expr() {} 38 | 39 | func (inv invalidExpr) MarshalJSON() ([]byte, error) { 40 | return nil, inv.err 41 | } 42 | 43 | // Obj is a expression shortcut to represent any valid JSON object 44 | type Obj map[string]interface{} 45 | 46 | func (obj Obj) expr() {} 47 | 48 | // Arr is a expression shortcut to represent any valid JSON array 49 | type Arr []interface{} 50 | 51 | func (arr Arr) expr() {} 52 | 53 | // MarshalJSON implements json.Marshaler for Obj expression 54 | func (obj Obj) MarshalJSON() ([]byte, error) { return json.Marshal(wrap(obj)) } 55 | 56 | // MarshalJSON implements json.Marshaler for Arr expression 57 | func (arr Arr) MarshalJSON() ([]byte, error) { return json.Marshal(wrap(arr)) } 58 | -------------------------------------------------------------------------------- /concourse/tasks/integration.yml: -------------------------------------------------------------------------------- 1 | version: "3.3" 2 | 3 | services: 4 | faunadb: 5 | image: fauna/faunadb 6 | container_name: faunadb 7 | healthcheck: 8 | test: ["CMD", "curl" ,"http://faunadb:8443/ping"] 9 | interval: 1s 10 | timeout: 3s 11 | retries: 30 12 | 13 | tests-17: 14 | environment: 15 | - FAUNA_ROOT_KEY 16 | - FAUNA_ENDPOINT 17 | image: golang:1.17-alpine3.13 18 | container_name: mytests 19 | depends_on: 20 | - faunadb 21 | volumes: 22 | - "../../:/tmp/app" 23 | working_dir: "/tmp/app" 24 | command: 25 | - concourse/scripts/integration-tests.sh 26 | 27 | tests-16: 28 | environment: 29 | - FAUNA_ROOT_KEY 30 | - FAUNA_ENDPOINT 31 | image: golang:1.16.7-alpine3.13 32 | container_name: mytests 33 | depends_on: 34 | - faunadb 35 | volumes: 36 | - "../../:/tmp/app" 37 | working_dir: "/tmp/app" 38 | command: 39 | - concourse/scripts/integration-tests.sh 40 | 41 | tests-15: 42 | environment: 43 | - FAUNA_ROOT_KEY 44 | - FAUNA_ENDPOINT 45 | image: golang:1.15.15-alpine3.13 46 | container_name: mytests 47 | depends_on: 48 | - faunadb 49 | volumes: 50 | - "../../:/tmp/app" 51 | working_dir: "/tmp/app" 52 | command: 53 | - concourse/scripts/integration-tests.sh 54 | 55 | tests-14: 56 | environment: 57 | - FAUNA_ROOT_KEY 58 | - FAUNA_ENDPOINT 59 | image: golang:1.14.15-alpine3.13 60 | container_name: mytests 61 | depends_on: 62 | - faunadb 63 | volumes: 64 | - "../../:/tmp/app" 65 | working_dir: "/tmp/app" 66 | command: 67 | - concourse/scripts/integration-tests.sh 68 | -------------------------------------------------------------------------------- /concourse/pipeline.yml: -------------------------------------------------------------------------------- 1 | --- 2 | resource_types: 3 | - name: slack-notification 4 | type: docker-image 5 | source: 6 | repository: cfcommunity/slack-notification-resource 7 | 8 | resources: 9 | - name: notify 10 | type: slack-notification 11 | source: 12 | url: ((slack-webhook)) 13 | 14 | - name: fauna-go-repository 15 | type: git 16 | icon: github 17 | source: 18 | uri: git@github.com:fauna/faunadb-go.git 19 | branch: v4 20 | private_key: ((github_repo_key)) 21 | 22 | jobs: 23 | - name: release 24 | serial: true 25 | public: false 26 | plan: 27 | - get: fauna-go-repository 28 | 29 | - task: integration-tests-go-1-14 30 | file: fauna-go-repository/concourse/tasks/integration-tests-1-14.yml 31 | privileged: true 32 | params: 33 | FAUNA_ROOT_KEY: ((db_secret)) 34 | FAUNA_ENDPOINT: ((db_endpoint)) 35 | 36 | - task: integration-tests-go-1-15 37 | file: fauna-go-repository/concourse/tasks/integration-tests-1-15.yml 38 | privileged: true 39 | params: 40 | FAUNA_ROOT_KEY: ((db_secret)) 41 | FAUNA_ENDPOINT: ((db_endpoint)) 42 | 43 | - task: integration-tests-go-1-16 44 | file: fauna-go-repository/concourse/tasks/integration-tests-1-16.yml 45 | privileged: true 46 | params: 47 | FAUNA_ROOT_KEY: ((db_secret)) 48 | FAUNA_ENDPOINT: ((db_endpoint)) 49 | 50 | - task: publish 51 | file: fauna-go-repository/concourse/tasks/publish.yml 52 | on_success: 53 | put: notify 54 | params: 55 | text_file: slack-message/publish 56 | 57 | - put: fauna-go-repository 58 | params: 59 | repository: fauna-go-repository-updated 60 | -------------------------------------------------------------------------------- /faunadb/field.go: -------------------------------------------------------------------------------- 1 | package faunadb 2 | 3 | // Field is a field extractor for FaunaDB values. 4 | type Field struct{ path path } 5 | 6 | // FieldValue describes an extracted field value. 7 | type FieldValue interface { 8 | GetValue() (Value, error) // GetValue returns the extracted FaunaDB value. 9 | Get(i interface{}) error // Get decodes a FaunaDB value to a native Go type. 10 | } 11 | 12 | // ObjKey creates a field extractor for a JSON object based on the provided keys. 13 | func ObjKey(keys ...string) Field { return Field{pathFromKeys(keys...)} } 14 | 15 | // ArrIndex creates a field extractor for a JSON array based on the provided indexes. 16 | func ArrIndex(indexes ...int) Field { return Field{pathFromIndexes(indexes...)} } 17 | 18 | // At creates a new field extractor based on the provided path. 19 | func (f Field) At(other Field) Field { return Field{f.path.subPath(other.path)} } 20 | 21 | // AtKey creates a new field extractor based on the provided key. 22 | func (f Field) AtKey(keys ...string) Field { return f.At(ObjKey(keys...)) } 23 | 24 | // AtIndex creates a new field extractor based on the provided index. 25 | func (f Field) AtIndex(indexes ...int) Field { return f.At(ArrIndex(indexes...)) } 26 | 27 | func (f *Field) get(value Value) FieldValue { 28 | value, err := f.path.get(value) 29 | 30 | if err != nil { 31 | return invalidField{err} 32 | } 33 | 34 | return validField{value} 35 | } 36 | 37 | type validField struct{ value Value } 38 | 39 | func (v validField) GetValue() (Value, error) { return v.value, nil } 40 | func (v validField) Get(i interface{}) error { return v.value.Get(i) } 41 | 42 | type invalidField struct{ err error } 43 | 44 | func (v invalidField) GetValue() (Value, error) { return nil, v.err } 45 | func (v invalidField) Get(i interface{}) error { return v.err } 46 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: go 3 | go: 4 | - 1.11 5 | - 1.12 6 | - 1.13 7 | - 1.14 8 | script: 9 | - go test -v -race -coverprofile=coverage.txt -covermode=atomic ./... 10 | after_success: 11 | - bash <(curl -s https://codecov.io/bash) 12 | env: 13 | global: 14 | - secure: Yx4Ka18FmyuZ9wuKMZaekfyyMNkvAQaK8UvzvsNzdYmx+psr3pGSK2w1tfu2OD+t8W7SHM2vlk6VXaNFOR5/kQMT5kQMh9QEQ8t9K2Sp5h6NYnQTiF8sHH58wpWgfEBTLi0itKqj4BNTgxahhLahdreEZQDHH0Mi711SU/rcZu5MqdVbjFsvNy/Qh39Z87LT8MsDcZ4LE0cvZ10DrOexCkVEwZXBS4SGqq9eGneA00zQnKECCecTKygTQfYdyCUCQhiGzvTdEXkS1NJqANDPXeRSggqRuTtOywq25k6QbUOl+AeuLWzs87ykKgm9SPtYF2yInKbj/D8E0AhawpzTZCwMHQcN4teNI2P+nm0TTnlER+X0ziplg5MMx81n06ZSh9Nq1Y/xc+2lgoTm9vzmcTry+RztuS6IQiTOZ3KEmzGcfkV6Xztb3m7vBBImU3s37moI6WUOfNfw+pTbWyrm3MBJ1mNK3yN8nc/uH3OB8e5PmssHKjprvnlxMa4KIR3uTOM9dbKSEHnkhmcJ/jrI/B5CbtbuOMc1gSqf82140MezuNYR/DVBTge4OtXDh0zCC3Pml3RlHB8CPiVkOX3Pv0heB54JcpoB/uXjtdOrJnB2j6POVkSw6M+OWNx0ZJC0uPnSev0vPko9CdLNqtk95EZAOXDo4PMURkv70zbd2xI= 15 | - FAUNA_DOMAIN=db.fauna.com 16 | - FAUNA_SCHEME=https 17 | - FAUNA_PORT=443 18 | notifications: 19 | email: false 20 | slack: 21 | secure: HWnjZ03WETkOQj2fzzTDYhAq3FGx2WLhwakRbYbc0t/GiE2pHOXN2Sx9Cbw83Ni8Tjw34qXhxd3sI3rnSkHPJh0yCLH9w2FA1+oO5NgfnZ1uzAWYcQv3Q9iFDCRrH8jr8Uf5TLtQY+7AuTfsgtkK7VCXhn4mtRwmzZ1OLJmClIZeOQM6qN4uxeQO1M60Vcy4johYNktkac7om49aIPy29rmNe3hx8exoRBKfPpaAoxpW0Bx4eLKoV3SKXdOu5rEyr9T47N64GMWLlQ0/RR9amOVS4AdZaM9TNqCnoT5uCfhJ2KRKwrV/3D7Qv9QEG1KKgIKRbgGEoyAH00Ud2nH+bxVZNDaHfF00KMIg+lVPyGlFtAQOD9r2s/4uMAU0O7FhQFaHnz1UjQ5NVXik2DcNF7VeBhG1R7AnH2GTbHoMuvhMOSITduxaO1MWo2OmqNbnSBucsREBORiTP38u0oTC8WmOL2Dai2XVLGSbyUO3IrPVljm5Rz2YxMMq/vAWcdoc8cbXBnVE5LH+dNxaNHk8X2XvXC8jr2wJ9pQycEFTZ+QjIRIovo3L6olRSps7Y2wsM8jErPU78CmAVeZMZ3u2zpYRab/DbySiKoMJT69wBtlgm900z6USH5zhH1bUTEh3hNnjL19elrZpS+eDLk/DF9gq9QL6H2RWund2Pzedmos= 22 | branches: 23 | only: 24 | - master 25 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= 6 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 7 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 8 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 9 | golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw= 10 | golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 11 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 12 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 13 | golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= 14 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 15 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 16 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 17 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 18 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 19 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 20 | -------------------------------------------------------------------------------- /faunadb/tags.go: -------------------------------------------------------------------------------- 1 | package faunadb 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | "time" 8 | ) 9 | 10 | const faunaTag = "fauna" 11 | 12 | func fieldName(field reflect.StructField) string { 13 | name := field.Tag.Get(faunaTag) 14 | 15 | if name == "" { 16 | name = field.Name 17 | } 18 | 19 | return name 20 | } 21 | 22 | // parseTag interprets fauna struct field tags 23 | func parseTag(field reflect.StructField) (name string, ignore, omitempty bool, err error) { 24 | s := field.Tag.Get(faunaTag) 25 | parts := strings.Split(s, ",") 26 | if s == "" { 27 | return field.Name, false, false, nil 28 | } 29 | 30 | if parts[0] == "-" { 31 | return "", true, false, nil 32 | } 33 | 34 | if len(parts) > 1 { 35 | for _, p := range parts[1:] { 36 | switch p { 37 | case "omitempty": 38 | omitempty = true 39 | default: 40 | err = fmt.Errorf("fauna: struct tag has invalid option: %q", p) 41 | return "", false, false, err 42 | } 43 | } 44 | } 45 | if parts[0] != "" { 46 | name = parts[0] 47 | } else { 48 | name = field.Name 49 | } 50 | 51 | return 52 | } 53 | 54 | func isEmptyValue(v reflect.Value) bool { 55 | switch v.Kind() { 56 | case reflect.Array, reflect.Map, reflect.Slice, reflect.String: 57 | return v.Len() == 0 58 | case reflect.Bool: 59 | return !v.Bool() 60 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 61 | return v.Int() == 0 62 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 63 | return v.Uint() == 0 64 | case reflect.Float32, reflect.Float64: 65 | return v.Float() == 0 66 | case reflect.Interface, reflect.Ptr: 67 | return v.IsNil() 68 | case reflect.Struct: 69 | if t, ok := v.Interface().(time.Time); ok { 70 | return t.IsZero() 71 | } 72 | } 73 | return false 74 | } 75 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 4.3.1 (September, 2024) [current] 2 | - Bump patch version to publish latest README with FQL v4 deprecation notice. 3 | 4 | # 4.3.0 (February, 2023) 5 | 6 | - Adds support for `tags` and `traceparent` headers 7 | 8 | # v4.2.0 (February, 2022) 9 | 10 | - Adds support for [set streaming](https://docs.fauna.com/fauna/current/drivers/streaming). 11 | 12 | # v4.1.0 (May, 2021) 13 | 14 | - Adds support for custom headers 15 | - Adds support for paginate cursor object 16 | 17 | # v4.0.0 (April, 2021) 18 | 19 | - Add document streaming. 20 | - Add third-party authentication functions: AccessProvider, AccessProviders, CreateAccessProvider, 21 | CurrentIdentity, CurrentToken, HasCurrentIdentity, HasCurrentToken. 22 | - Add `omitempty` support for JSON encodings. 23 | - Add support for partners info via request headers. 24 | 25 | # v3.0.0 (August, 2020) 26 | 27 | - Added Reverse() 28 | - Add ContainsPath(), ContainsField(), ContainsValue() 29 | - Add ToArray(), ToObject(), ToInteger(), ToDouble() functions 30 | - Deprecate Contains() 31 | - Add tests for versioned queries 32 | - Bump apiVersion to 3 33 | - Fix DoubleV string formatting 34 | 35 | # v2.12.1 (May, 2020) 36 | 37 | - Make base64 encoding of secret match other drivers. 38 | 39 | # v2.12.0 (May, 2020) 40 | 41 | - Add client specified query timeout 42 | - Update Ref() to take two arguments 43 | - Handle nil as NullV{} 44 | - Add type check functions: 45 | IsEmpty(), IsNonEmpty(), IsNumber(), IsDouble(), IsInteger() 46 | IsBoolean(), IsNull(), IsBytes(), IsTimestamp(), IsDate() 47 | IsString(), IsArray(), IsObject(), IsRef(), IsSet(), IsDoc() 48 | IsLambda(), IsCollection(), IsDatabase(), IsIndex(), IsFunction() 49 | IsKey(), IsToken(), IsCredentials(), IsRole() 50 | 51 | # v2.11.0 (February, 2020) 52 | 53 | - Add StartsWith(), EndsWith(), ContainsStr(), ContainsStrRegex(), RegexEscape() 54 | - Add TimeAdd(), TimeSubtract(), TimeDiff() 55 | - Add Count(), Sum(), Mean() 56 | - Add Documents(), Now() 57 | - Add Any(), All() 58 | 59 | # v2.10.0 (November, 2019) 60 | 61 | # v2.9.0 (October, 2019) 62 | 63 | - Add CHANGELOG.md 64 | - Add Range(), Reduce(), Merge() functions 65 | - Add MoveDatabase() 66 | - Add Format() function 67 | - Send "X-Fauna-Driver: Go" header with http requests 68 | -------------------------------------------------------------------------------- /faunadb/stream_subscription.go: -------------------------------------------------------------------------------- 1 | package faunadb 2 | 3 | import ( 4 | "errors" 5 | "sync" 6 | ) 7 | 8 | // StreamSubscription dispatches events received to the registered listener functions. 9 | // New subscriptions must be constructed via the FaunaClient stream method. 10 | type StreamSubscription struct { 11 | mu sync.Mutex 12 | query Expr 13 | config streamConfig 14 | client *FaunaClient 15 | status StreamConnectionStatus 16 | events chan StreamEvent 17 | closed chan bool 18 | } 19 | 20 | func newSubscription(client *FaunaClient, query Expr, config ...StreamConfig) StreamSubscription { 21 | sub := StreamSubscription{ 22 | query: query, 23 | config: streamConfig{ 24 | []StreamField{}, 25 | }, 26 | client: client, 27 | status: StreamConnIdle, 28 | events: make(chan StreamEvent), 29 | closed: make(chan bool), 30 | } 31 | for _, fn := range config { 32 | fn(&sub) 33 | } 34 | return sub 35 | } 36 | 37 | type streamConnectionStatus struct { 38 | mu sync.Mutex 39 | status StreamConnectionStatus 40 | } 41 | 42 | func (s *streamConnectionStatus) Set(status StreamConnectionStatus) { 43 | s.mu.Lock() 44 | defer s.mu.Unlock() 45 | s.status = status 46 | } 47 | 48 | func (s *streamConnectionStatus) Get() StreamConnectionStatus { 49 | s.mu.Lock() 50 | defer s.mu.Unlock() 51 | return s.status 52 | } 53 | 54 | // Query returns the query used to initiate the stream 55 | func (sub *StreamSubscription) Query() Expr { 56 | return sub.query 57 | } 58 | 59 | // Status returns the current stream status 60 | func (sub *StreamSubscription) Status() StreamConnectionStatus { 61 | sub.mu.Lock() 62 | defer sub.mu.Unlock() 63 | return sub.status 64 | } 65 | 66 | func (sub *StreamSubscription) Start() (err error) { 67 | sub.mu.Lock() 68 | defer sub.mu.Unlock() 69 | 70 | if sub.status != StreamConnIdle { 71 | err = errors.New("stream subscription already started") 72 | } else { 73 | sub.status = StreamConnActive 74 | if err = sub.client.startStream(sub); err != nil { 75 | sub.status = StreamConnError 76 | } 77 | } 78 | return 79 | } 80 | 81 | 82 | func (sub *StreamSubscription) Close() { 83 | sub.mu.Lock() 84 | defer sub.mu.Unlock() 85 | if sub.status == StreamConnActive { 86 | sub.status = StreamConnClosed 87 | close(sub.closed) 88 | close(sub.events) 89 | } 90 | } 91 | 92 | func (sub *StreamSubscription) StreamEvents() <-chan StreamEvent { 93 | return sub.events 94 | } -------------------------------------------------------------------------------- /faunadb/reflect.go: -------------------------------------------------------------------------------- 1 | package faunadb 2 | 3 | import "reflect" 4 | 5 | func structToMap(aStruct reflect.Value) map[string]interface{} { 6 | res := make(map[string]interface{}, aStruct.NumField()) 7 | 8 | if expStructFields, err := exportedStructFields(aStruct); err == nil { 9 | for key, value := range expStructFields { 10 | res[key] = value.Interface() 11 | } 12 | } 13 | return res 14 | } 15 | 16 | func exportedStructFields(aStruct reflect.Value) (map[string]reflect.Value, error) { 17 | fields := make(map[string]reflect.Value) 18 | aStructType := aStruct.Type() 19 | 20 | for i, size := 0, aStruct.NumField(); i < size; i++ { 21 | field := aStruct.Field(i) 22 | 23 | if !field.CanInterface() { 24 | continue 25 | } 26 | 27 | fieldName, ignore, omitempty, err := parseTag(aStructType.Field(i)) 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | if omitempty && isEmptyValue(field) { 33 | continue 34 | } 35 | 36 | if !ignore && fieldName != "" { 37 | fields[fieldName] = field 38 | } 39 | } 40 | 41 | return fields, nil 42 | } 43 | 44 | func indirectValue(i interface{}) (reflect.Value, reflect.Type) { 45 | var value reflect.Value 46 | 47 | if reflected, ok := i.(reflect.Value); ok { 48 | value = reflected 49 | } else { 50 | value = reflect.ValueOf(i) 51 | } 52 | 53 | for { 54 | if value.Kind() == reflect.Interface && !value.IsNil() { 55 | elem := value.Elem() 56 | 57 | if elem.IsValid() { 58 | value = elem 59 | continue 60 | } 61 | } 62 | 63 | if value.Kind() != reflect.Ptr { 64 | break 65 | } 66 | 67 | if value.IsNil() { 68 | if value.CanSet() { 69 | value.Set(reflect.New(value.Type().Elem())) 70 | } else { 71 | break 72 | } 73 | } 74 | 75 | value = value.Elem() 76 | } 77 | 78 | return value, value.Type() 79 | } 80 | 81 | func allStructFields(aStruct reflect.Value) map[string]reflect.Value { 82 | fields := make(map[string]reflect.Value) 83 | aStructType := aStruct.Type() 84 | 85 | for i, size := 0, aStruct.NumField(); i < size; i++ { 86 | field := aStruct.Field(i) 87 | structTypeField := aStructType.Field(i) 88 | 89 | if !field.CanInterface() { 90 | continue 91 | } 92 | 93 | fieldName, ignore, _, _ := parseTag(aStructType.Field(i)) 94 | 95 | if !ignore && fieldName != "" { 96 | fields[fieldName] = field 97 | } 98 | fields[structTypeField.Name] = field 99 | } 100 | 101 | return fields 102 | } 103 | -------------------------------------------------------------------------------- /faunadb/faunadb_test.go: -------------------------------------------------------------------------------- 1 | package faunadb 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "os" 7 | "strconv" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | var ( 13 | faunaSecret = os.Getenv("FAUNA_ROOT_KEY") 14 | faunaEndpoint = os.Getenv("FAUNA_ENDPOINT") 15 | 16 | allQueriesTimeout = os.Getenv("FAUNA_QUERY_TIMEOUT_MS") 17 | 18 | dbName string 19 | dbRef Expr 20 | 21 | adminClient *FaunaClient 22 | ) 23 | 24 | func init() { 25 | rand.Seed(time.Now().UTC().UnixNano()) // By default, the seed is always 1 26 | 27 | if faunaSecret == "" { 28 | panic("FAUNA_ROOT_KEY environment variable must be specified") 29 | } 30 | 31 | if faunaEndpoint == "" { 32 | faunaEndpoint = defaultEndpoint 33 | } 34 | 35 | dbName = RandomStartingWith("faunadb-go-test-") 36 | dbRef = Database(dbName) 37 | } 38 | 39 | func RandomStartingWith(parts ...string) string { 40 | return fmt.Sprintf("%s%v", strings.Join(parts, ""), rand.Uint32()) 41 | } 42 | 43 | func SetupTestDB() (client *FaunaClient, err error) { 44 | var key Value 45 | 46 | adminClient = NewFaunaClient( 47 | faunaSecret, 48 | Endpoint(faunaEndpoint), 49 | ) 50 | if allQueriesTimeout != "" { 51 | if millis, err := strconv.ParseUint(allQueriesTimeout, 10, 64); err == nil { 52 | adminClient = NewFaunaClient( 53 | faunaSecret, 54 | Endpoint(faunaEndpoint), 55 | QueryTimeoutMS(millis), 56 | ) 57 | } else { 58 | panic("FAUNA_QUERY_TIMEOUT_MS environment variable must be an integer.") 59 | } 60 | } 61 | 62 | DeleteTestDB() 63 | 64 | if err = createTestDatabase(); err == nil { 65 | if key, err = CreateKeyWithRole("server"); err == nil { 66 | client = adminClient.NewSessionClient(GetSecret(key)) 67 | } 68 | } 69 | 70 | return 71 | } 72 | 73 | func DeleteTestDB() { 74 | _, _ = adminClient.Query(Delete(dbRef)) // Ignore error because db may not exist 75 | } 76 | 77 | func createTestDatabase() (err error) { 78 | _, err = adminClient.Query( 79 | CreateDatabase(Obj{"name": dbName}), 80 | ) 81 | 82 | return 83 | } 84 | 85 | func AdminQuery(expr Expr) (Value, error) { 86 | return adminClient.Query(expr) 87 | } 88 | 89 | func CreateKeyWithRole(role string) (key Value, err error) { 90 | key, err = adminClient.Query( 91 | CreateKey(Obj{ 92 | "database": dbRef, 93 | "role": role, 94 | }), 95 | ) 96 | 97 | return 98 | } 99 | 100 | func GetSecret(key Value) (secret string) { 101 | key.At(ObjKey("secret")).Get(&secret) 102 | 103 | return 104 | } 105 | 106 | func DbRef() *RefV { 107 | return &RefV{dbName, NativeDatabases(), NativeDatabases(), nil} 108 | } 109 | -------------------------------------------------------------------------------- /faunadb/encode.go: -------------------------------------------------------------------------------- 1 | package faunadb 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "math" 7 | "reflect" 8 | "time" 9 | ) 10 | 11 | var ( 12 | exprType = reflect.TypeOf((*Expr)(nil)).Elem() 13 | objType = reflect.TypeOf((*Obj)(nil)).Elem() 14 | arrType = reflect.TypeOf((*Arr)(nil)).Elem() 15 | timeType = reflect.TypeOf((*time.Time)(nil)).Elem() 16 | 17 | maxSupportedUint = uint64(math.MaxInt64) 18 | 19 | errMapKeyMustBeString = invalidExpr{errors.New("Error while encoding map to json: All map keys must be of type string")} 20 | errMaxSupportedUintExceeded = invalidExpr{errors.New("Error while encoding number to json: Uint value exceeds maximum int64")} 21 | ) 22 | 23 | func wrap(i interface{}) Expr { 24 | if i == nil { 25 | return NullV{} 26 | } 27 | 28 | value, valueType := indirectValue(i) 29 | kind := value.Kind() 30 | 31 | if (kind == reflect.Ptr || kind == reflect.Interface) && value.IsNil() { 32 | return NullV{} 33 | } 34 | 35 | // Is an expression but not a syntax sugar 36 | if valueType.Implements(exprType) && valueType != objType && valueType != arrType { 37 | return value.Interface().(Expr) 38 | } 39 | 40 | switch kind { 41 | case reflect.String: 42 | return StringV(value.String()) 43 | 44 | case reflect.Bool: 45 | return BooleanV(value.Bool()) 46 | 47 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 48 | return LongV(value.Int()) 49 | 50 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 51 | num := value.Uint() 52 | 53 | if num > maxSupportedUint { 54 | return errMaxSupportedUintExceeded 55 | } 56 | 57 | return LongV(num) 58 | 59 | case reflect.Float32, reflect.Float64: 60 | return DoubleV(value.Float()) 61 | 62 | case reflect.Map: 63 | if valueType.Key().Kind() != reflect.String { 64 | return errMapKeyMustBeString 65 | } 66 | 67 | return wrapMap(value) 68 | 69 | case reflect.Struct: 70 | if valueType == timeType { 71 | return TimeV(value.Interface().(time.Time)) 72 | } 73 | 74 | value, _ = indirectValue(structToMap(value)) 75 | return wrapMap(value) 76 | 77 | case reflect.Slice, reflect.Array: 78 | return wrapArray(value) 79 | 80 | default: 81 | return invalidExpr{fmt.Errorf("Error while converting Expr to JSON: Non supported type %v", kind)} 82 | } 83 | } 84 | 85 | func wrapMap(value reflect.Value) Expr { 86 | obj := make(unescapedObj, value.Len()) 87 | 88 | for _, key := range value.MapKeys() { 89 | obj[key.String()] = wrap(value.MapIndex(key)) 90 | } 91 | 92 | return unescapedObj{"object": obj} 93 | } 94 | 95 | func wrapArray(value reflect.Value) Expr { 96 | arr := make(unescapedArr, value.Len()) 97 | 98 | for i, size := 0, value.Len(); i < size; i++ { 99 | arr[i] = wrap(value.Index(i)) 100 | } 101 | 102 | return arr 103 | } 104 | -------------------------------------------------------------------------------- /faunadb/functions_auth.go: -------------------------------------------------------------------------------- 1 | package faunadb 2 | 3 | // Authentication 4 | 5 | // Login creates a token for the provided ref. 6 | // 7 | // Parameters: 8 | // ref Ref - A reference with credentials to authenticate against. 9 | // params Object - An object of parameters to pass to the login function 10 | // - password: The password used to login 11 | // 12 | // Returns: 13 | // Key - a key with the secret to login. 14 | // 15 | // See: https://app.fauna.com/documentation/reference/queryapi#authentication 16 | func Login(ref, params interface{}) Expr { 17 | return loginFn{Login: wrap(ref), Params: wrap(params)} 18 | } 19 | 20 | type loginFn struct { 21 | fnApply 22 | Login Expr `json:"login"` 23 | Params Expr `json:"params"` 24 | } 25 | 26 | // Logout deletes the current session token. If invalidateAll is true, logout will delete all tokens associated with the current session. 27 | // 28 | // Parameters: 29 | // invalidateAll bool - If true, log out all tokens associated with the current session. 30 | // 31 | // See: https://app.fauna.com/documentation/reference/queryapi#authentication 32 | func Logout(invalidateAll interface{}) Expr { return logoutFn{Logout: wrap(invalidateAll)} } 33 | 34 | type logoutFn struct { 35 | fnApply 36 | Logout Expr `json:"logout"` 37 | } 38 | 39 | // Identify checks the given password against the provided ref's credentials. 40 | // 41 | // Parameters: 42 | // ref Ref - The reference to check the password against. 43 | // password string - The credentials password to check. 44 | // 45 | // Returns: 46 | // bool - true if the password is correct, false otherwise. 47 | // 48 | // See: https://app.fauna.com/documentation/reference/queryapi#authentication 49 | func Identify(ref, password interface{}) Expr { 50 | return identifyFn{Identify: wrap(ref), Password: wrap(password)} 51 | } 52 | 53 | type identifyFn struct { 54 | fnApply 55 | Identify Expr `json:"identify"` 56 | Password Expr `json:"password"` 57 | } 58 | 59 | // Identity returns the document reference associated with the current key. 60 | // 61 | // For example, the current key token created using: 62 | // Create(Tokens(), Obj{"document": someRef}) 63 | // or via: 64 | // Login(someRef, Obj{"password":"sekrit"}) 65 | // will return "someRef" as the result of this function. 66 | // 67 | // Returns: 68 | // Ref - The reference associated with the current key. 69 | // 70 | // See: https://app.fauna.com/documentation/reference/queryapi#authentication 71 | func Identity() Expr { return identityFn{Identity: NullV{}} } 72 | 73 | type identityFn struct { 74 | fnApply 75 | Identity Expr `json:"identity" faunarepr:"noargs"` 76 | } 77 | 78 | // HasIdentity checks if the current key has an identity associated to it. 79 | // 80 | // Returns: 81 | // bool - true if the current key has an identity, false otherwise. 82 | // 83 | // See: https://app.fauna.com/documentation/reference/queryapi#authentication 84 | func HasIdentity() Expr { return hasIdentityFn{HasIdentity: NullV{}} } 85 | 86 | type hasIdentityFn struct { 87 | fnApply 88 | HasIdentity Expr `json:"has_identity" faunarepr:"noargs"` 89 | } 90 | -------------------------------------------------------------------------------- /faunadb/field_test.go: -------------------------------------------------------------------------------- 1 | package faunadb 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestExtractValueFromObject(t *testing.T) { 12 | var str string 13 | 14 | value := ObjectV{ 15 | "data": ObjectV{ 16 | "testField": StringV("A"), 17 | }, 18 | } 19 | err := value.At(ObjKey("data", "testField")).Get(&str) 20 | 21 | require.NoError(t, err) 22 | require.Equal(t, "A", str) 23 | } 24 | 25 | func TestExtractValueFromArray(t *testing.T) { 26 | var num int 27 | 28 | value := ArrayV{ 29 | ArrayV{ 30 | LongV(1), 31 | LongV(2), 32 | LongV(3), 33 | }, 34 | } 35 | err := value.At(ArrIndex(0, 2)).Get(&num) 36 | 37 | require.NoError(t, err) 38 | require.Equal(t, 3, num) 39 | } 40 | 41 | func TestFailToExtractFieldForNonTransversableValues(t *testing.T) { 42 | assertFailToExtractAnyFieldFor(t, 43 | StringV("test"), 44 | LongV(0), 45 | DoubleV(0), 46 | BooleanV(false), 47 | DateV(time.Now()), 48 | TimeV(time.Now()), 49 | RefV{"collections/spells", nil, nil, nil}, 50 | SetRefV{map[string]Value{"any": StringV("set")}}, 51 | NullV{}, 52 | ) 53 | } 54 | 55 | func TestReportKeyNotFound(t *testing.T) { 56 | assertFailToExtractField(t, ObjectV{"data": ObjectV{}}, ObjKey("data", "testField", "ref"), 57 | "Error while extracting path: data / testField / ref. Object key testField not found") 58 | } 59 | 60 | func TestReportIndexNotFound(t *testing.T) { 61 | assertFailToExtractField(t, ArrayV{}, ArrIndex(0).AtKey("ref"), 62 | "Error while extracting path: 0 / ref. Array index 0 not found") 63 | } 64 | 65 | func TestReportErrorPathWhenValueIsNotAnArray(t *testing.T) { 66 | value := ObjectV{ 67 | "data": ObjectV{ 68 | "testField": StringV("A"), 69 | }, 70 | } 71 | 72 | assertFailToExtractField(t, value, ObjKey("data", "testField").AtIndex(1), 73 | "Error while extracting path: data / testField / 1. Expected value to be an array but was a faunadb.StringV") 74 | } 75 | 76 | func TestReportErrorPathWhenValueIsNotAnObject(t *testing.T) { 77 | assertFailToExtractField(t, ArrayV{ArrayV{}}, ArrIndex(0).AtKey("testField"), 78 | "Error while extracting path: 0 / testField. Expected value to be an object but was a faunadb.ArrayV") 79 | } 80 | 81 | func assertFailToExtractField(t *testing.T, value Value, field Field, message string) { 82 | _, err := value.At(field).GetValue() 83 | require.EqualError(t, err, message) 84 | 85 | var res Value 86 | require.EqualError(t, value.At(field).Get(&res), message) 87 | } 88 | 89 | func assertFailToExtractAnyFieldFor(t *testing.T, values ...Value) { 90 | key := ObjKey("anyField") 91 | index := ArrIndex(0) 92 | 93 | for _, value := range values { 94 | _, err := value.At(key).GetValue() 95 | assert.Contains(t, err.Error(), "Error while extracting path: anyField. Expected value to be an object but was a") 96 | 97 | _, err = value.At(index).GetValue() 98 | assert.Contains(t, err.Error(), "Error while extracting path: 0. Expected value to be an array but was a") 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /faunadb/encode_test.go: -------------------------------------------------------------------------------- 1 | package faunadb 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestWrapNil(t *testing.T) { 9 | var in *string 10 | 11 | actual := wrap(in) 12 | expected := NullV{} 13 | if actual != expected { 14 | t.Errorf("Nil test failed: wrap(%#v) == %#v, expected %#v", in, actual, expected) 15 | } 16 | } 17 | 18 | func TestWrapPODs(t *testing.T) { 19 | tests := []struct { 20 | name string 21 | in interface{} 22 | expected Expr 23 | }{ 24 | {"String", "abc", StringV("abc")}, 25 | {"Boolean true", true, BooleanV(true)}, 26 | {"Boolean false", false, BooleanV(false)}, 27 | {"Int", 42, LongV(42)}, 28 | {"Int8", int8(42), LongV(42)}, 29 | {"Int16", int16(42), LongV(42)}, 30 | {"Int32", int32(42), LongV(42)}, 31 | {"Int64", int64(42), LongV(42)}, 32 | {"Uint", 42, LongV(42)}, 33 | {"Uint8", uint8(42), LongV(42)}, 34 | {"Uint16", uint16(42), LongV(42)}, 35 | {"Uint32", uint32(42), LongV(42)}, 36 | {"Uint64", uint64(42), LongV(42)}, 37 | {"Float32", float32(1.0), DoubleV(1.0)}, 38 | {"Float64", float64(1.0), DoubleV(1.0)}, 39 | } 40 | 41 | for _, test := range tests { 42 | actual := wrap(test.in) 43 | if actual != test.expected { 44 | t.Errorf("POD test %s failed: wrap(%#v) in %#v, expected %#v", test.name, test.in, actual, test.expected) 45 | } 46 | } 47 | } 48 | 49 | func TestWrapMap(t *testing.T) { 50 | tests := []struct { 51 | name string 52 | in interface{} 53 | expected Expr 54 | }{ 55 | {"non-string key", map[int]string{4: "h"}, errMapKeyMustBeString}, 56 | {"string key", map[string]int{"h": 4}, 57 | unescapedObj{ 58 | "object": unescapedObj{ 59 | "h": LongV(4), 60 | }, 61 | }, 62 | }, 63 | } 64 | 65 | for _, test := range tests { 66 | actual := wrap(test.in) 67 | if !reflect.DeepEqual(actual, test.expected) { 68 | t.Errorf("map test %s failed: wrap(%#v) in %#v, expected %#v", test.name, test.in, actual, test.expected) 69 | } 70 | } 71 | } 72 | 73 | func TestWrapStruct(t *testing.T) { 74 | in := struct { 75 | A string 76 | B int 77 | }{ 78 | "hello", 79 | 42, 80 | } 81 | 82 | actual := wrap(in) 83 | expected := unescapedObj{ 84 | "object": unescapedObj{ 85 | "A": StringV("hello"), 86 | "B": LongV(42), 87 | }, 88 | } 89 | if !reflect.DeepEqual(actual, expected) { 90 | t.Errorf("Struct test failed: wrap(%#v) == %#v, expected %#v", in, actual, expected) 91 | } 92 | } 93 | 94 | func TestWrapSliceAndArrays(t *testing.T) { 95 | tests := []struct { 96 | name string 97 | in interface{} 98 | expected Expr 99 | }{ 100 | {"empty slice", []Expr{}, unescapedArr{}}, 101 | {"One element slice", []int{1}, unescapedArr{LongV(1)}}, 102 | {"Nested slice", [][]int{[]int{1}}, unescapedArr{unescapedArr{LongV(1)}}}, 103 | 104 | {"empty array", [0]Expr{}, unescapedArr{}}, 105 | {"One element array", [1]int{1}, unescapedArr{LongV(1)}}, 106 | } 107 | 108 | for _, test := range tests { 109 | actual := wrap(test.in) 110 | if !reflect.DeepEqual(actual, test.expected) { 111 | t.Errorf("POD test %s failed: wrap(%#v) in %#v, expected %#v", test.name, test.in, actual, test.expected) 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | description: FaunaDB Go Driver Tests 3 | 4 | executors: 5 | core: 6 | parameters: 7 | go_version: 8 | type: string 9 | version: 10 | type: enum 11 | enum: ["stable", "nightly"] 12 | resource_class: large 13 | docker: 14 | - image: circleci/golang:<> 15 | 16 | - image: fauna/faunadb 17 | name: core 18 | 19 | environment: 20 | FAUNA_ROOT_KEY: secret 21 | FAUNA_ENDPOINT: http://core:8443 22 | 23 | commands: 24 | build_and_test: 25 | description: "Run Go tests" 26 | steps: 27 | - checkout 28 | 29 | - setup_remote_docker: 30 | version: 24.0 31 | docker_layer_caching: true 32 | 33 | - restore_cache: 34 | keys: 35 | - v1-deps-{{ checksum "go.sum" }} 36 | 37 | - run: 38 | name: Install dependencies 39 | command: go mod download 40 | 41 | - run: go get github.com/jstemmer/go-junit-report 42 | 43 | - save_cache: 44 | paths: 45 | - /go/pkg 46 | key: v1-deps-{{ checksum "go.sum" }} 47 | 48 | - run: 49 | name: Wait FaunaDB init 50 | command: | 51 | while ! $(curl --output /dev/null --silent --fail --max-time 1 http://core:8443/ping); do sleep 1; done 52 | 53 | - run: 54 | name: Run Tests 55 | command: | 56 | mkdir results 57 | go test -v -race -coverprofile=results/coverage.txt -covermode=atomic ./... 2>&1 | tee log.txt 58 | 59 | - run: 60 | name: Gather Results 61 | when: always 62 | command: | 63 | mkdir -p results/junit 64 | go-junit-report -package-name faunadb -set-exit-code < log.txt > results/junit/report.xml 65 | 66 | - store_test_results: 67 | path: results/ 68 | 69 | jobs: 70 | core-stable-1-16-7: 71 | executor: 72 | name: core 73 | go_version: "1.16.7" 74 | version: stable 75 | steps: 76 | - build_and_test 77 | 78 | core-nightly-1-16-7: 79 | executor: 80 | name: core 81 | go_version: "1.16.7" 82 | version: nightly 83 | steps: 84 | - build_and_test 85 | 86 | core-stable-1-15-15: 87 | executor: 88 | name: core 89 | go_version: "1.15.15" 90 | version: stable 91 | steps: 92 | - build_and_test 93 | 94 | core-nightly-1-15-15: 95 | executor: 96 | name: core 97 | go_version: "1.15.15" 98 | version: nightly 99 | steps: 100 | - build_and_test 101 | 102 | core-stable-1-14-15: 103 | executor: 104 | name: core 105 | go_version: "1.14.15" 106 | version: stable 107 | steps: 108 | - build_and_test 109 | 110 | core-nightly-1-14-15: 111 | executor: 112 | name: core 113 | go_version: "1.14.15" 114 | version: nightly 115 | steps: 116 | - build_and_test 117 | 118 | workflows: 119 | version: 2 120 | build_and_test: 121 | jobs: 122 | - core-stable-1-16-7: 123 | context: faunadb-drivers 124 | - core-nightly-1-16-7: 125 | context: faunadb-drivers 126 | - core-stable-1-15-15: 127 | context: faunadb-drivers 128 | - core-nightly-1-15-15: 129 | context: faunadb-drivers 130 | - core-stable-1-14-15: 131 | context: faunadb-drivers 132 | - core-nightly-1-14-15: 133 | context: faunadb-drivers 134 | -------------------------------------------------------------------------------- /faunadb/functions_read.go: -------------------------------------------------------------------------------- 1 | package faunadb 2 | 3 | // Read 4 | 5 | // Get retrieves the document identified by the provided ref. Optional parameters: TS. 6 | // 7 | // Parameters: 8 | // ref Ref|SetRef - The reference to the object or a set reference. 9 | // 10 | // Optional parameters: 11 | // ts time - The snapshot time at which to get the document. See TS() function. 12 | // 13 | // Returns: 14 | // Object - The object requested. 15 | // 16 | // See: https://app.fauna.com/documentation/reference/queryapi#read-functions 17 | func Get(ref interface{}, options ...OptionalParameter) Expr { 18 | fn := getFn{Get: wrap(ref)} 19 | return applyOptionals(fn, options) 20 | } 21 | 22 | type getFn struct { 23 | fnApply 24 | Get Expr `json:"get"` 25 | TS Expr `json:"ts,omitempty" faunarepr:"optfn"` 26 | } 27 | 28 | // KeyFromSecret retrieves the key object from the given secret. 29 | // 30 | // Parameters: 31 | // secret string - The token secret. 32 | // 33 | // Returns: 34 | // Key - The key object related to the token secret. 35 | // 36 | // See: https://app.fauna.com/documentation/reference/queryapi#read-functions 37 | func KeyFromSecret(secret interface{}) Expr { return keyFromSecretFn{KeyFromSecret: wrap(secret)} } 38 | 39 | type keyFromSecretFn struct { 40 | fnApply 41 | KeyFromSecret Expr `json:"key_from_secret"` 42 | } 43 | 44 | // Exists returns boolean true if the provided ref exists (in the case of an document), 45 | // or is non-empty (in the case of a set), and false otherwise. Optional parameters: TS. 46 | // 47 | // Parameters: 48 | // ref Ref - The reference to the object. It could be a document reference of a object reference like a collection. 49 | // 50 | // Optional parameters: 51 | // ts time - The snapshot time at which to check for the document's existence. See TS() function. 52 | // 53 | // Returns: 54 | // bool - true if the reference exists, false otherwise. 55 | // 56 | // See: https://app.fauna.com/documentation/reference/queryapi#read-functions 57 | func Exists(ref interface{}, options ...OptionalParameter) Expr { 58 | fn := existsFn{Exists: wrap(ref)} 59 | return applyOptionals(fn, options) 60 | } 61 | 62 | type existsFn struct { 63 | fnApply 64 | Exists Expr `json:"exists"` 65 | TS Expr `json:"ts,omitempty" faunarepr:"optfn"` 66 | } 67 | 68 | // Paginate retrieves a page from the provided set. 69 | // 70 | // Parameters: 71 | // set SetRef - A set reference to paginate over. See Match() or MatchTerm() functions. 72 | // 73 | // Optional parameters: 74 | // after Cursor - Return the next page of results after this cursor (inclusive). See After() function. 75 | // before Cursor - Return the previous page of results before this cursor (exclusive). See Before() function. 76 | // sources bool - If true, include the source sets along with each element. See Sources() function. 77 | // ts time - The snapshot time at which to get the document. See TS() function. 78 | // 79 | // Returns: 80 | // Page - A page of elements. 81 | // 82 | // See: https://app.fauna.com/documentation/reference/queryapi#read-functions 83 | func Paginate(set interface{}, options ...OptionalParameter) Expr { 84 | fn := paginateFn{Paginate: wrap(set)} 85 | return applyOptionals(fn, options) 86 | } 87 | 88 | type paginateFn struct { 89 | fnApply 90 | Paginate Expr `json:"paginate"` 91 | Cursor Expr `json:"cursor,omitempty" faunarepr:"optfn"` 92 | After Expr `json:"after,omitempty" faunarepr:"optfn"` 93 | Before Expr `json:"before,omitempty" faunarepr:"optfn"` 94 | Events Expr `json:"events,omitempty" faunarepr:"fn=optfn,name=EventsOpt"` 95 | Size Expr `json:"size,omitempty" faunarepr:"optfn"` 96 | Sources Expr `json:"sources,omitempty" faunarepr:"optfn"` 97 | TS Expr `json:"ts,omitempty" faunarepr:"optfn"` 98 | } 99 | -------------------------------------------------------------------------------- /faunadb/path.go: -------------------------------------------------------------------------------- 1 | package faunadb 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // InvalidFieldType describes an error that may occurs when extracting a field. InvalidFieldType will occur 9 | // in the following cases: 10 | // * When trying to extract a field by key from a something that is not an object, or 11 | // * When trying to extract a field by index from something that is not an array. 12 | type InvalidFieldType struct { 13 | path path 14 | segment invalidSegmentType 15 | } 16 | 17 | func (i InvalidFieldType) Error() string { 18 | return fmt.Sprintf("Error while extracting path: %s. %s", i.path, i.segment) 19 | } 20 | 21 | type invalidSegmentType struct { 22 | desired string 23 | actual interface{} 24 | } 25 | 26 | func (i invalidSegmentType) Error() string { 27 | return fmt.Sprintf("Expected value to be %s but was a %T", i.desired, i.actual) 28 | } 29 | 30 | // ValueNotFound describes an error can occur when trying to extract a field, but that field could not be found. 31 | type ValueNotFound struct { 32 | path path 33 | segment segmentNotFound 34 | } 35 | 36 | func (v ValueNotFound) Error() string { 37 | return fmt.Sprintf("Error while extracting path: %s. %s", v.path, v.segment) 38 | } 39 | 40 | type segmentNotFound struct { 41 | desired string 42 | segment segment 43 | } 44 | 45 | func (s segmentNotFound) Error() string { 46 | return fmt.Sprintf("%s %v not found", s.desired, s.segment) 47 | } 48 | 49 | type segment interface { 50 | get(Value) (Value, error) 51 | } 52 | 53 | type path []segment 54 | 55 | func pathFromKeys(keys ...string) path { 56 | p := make(path, len(keys)) 57 | 58 | for i, key := range keys { 59 | p[i] = objectSegment(key) 60 | } 61 | 62 | return p 63 | } 64 | 65 | func pathFromIndexes(indexes ...int) path { 66 | p := make(path, len(indexes)) 67 | 68 | for i, index := range indexes { 69 | p[i] = arraySegment(index) 70 | } 71 | 72 | return p 73 | } 74 | 75 | func (p path) subPath(other path) path { 76 | return append(p, other...) 77 | } 78 | 79 | func (p path) get(value Value) (Value, error) { 80 | var err error 81 | 82 | next := value 83 | 84 | for _, seg := range p { 85 | if next, err = seg.get(next); err != nil { 86 | switch segErr := err.(type) { 87 | case segmentNotFound: 88 | return nil, ValueNotFound{p, segErr} 89 | case invalidSegmentType: 90 | return nil, InvalidFieldType{p, segErr} 91 | default: 92 | return nil, err 93 | } 94 | } 95 | } 96 | 97 | return next, nil 98 | } 99 | 100 | func (p path) String() (str string) { 101 | segments := make([]string, len(p)) 102 | 103 | for i, seg := range p { 104 | segments[i] = fmt.Sprintf("%v", seg) 105 | } 106 | 107 | str = strings.Join(segments, " / ") 108 | 109 | if str == "" { 110 | str = "" 111 | } 112 | 113 | return 114 | } 115 | 116 | type objectSegment string 117 | 118 | func (seg objectSegment) get(value Value) (res Value, err error) { 119 | key := string(seg) 120 | 121 | switch obj := value.(type) { 122 | case ObjectV: 123 | if value, ok := obj[key]; ok { 124 | res = value 125 | } else { 126 | err = segmentNotFound{"Object key", seg} 127 | } 128 | default: 129 | err = invalidSegmentType{"an object", value} 130 | } 131 | 132 | return 133 | } 134 | 135 | type arraySegment int 136 | 137 | func (seg arraySegment) get(value Value) (res Value, err error) { 138 | index := int(seg) 139 | 140 | switch arr := value.(type) { 141 | case ArrayV: 142 | if index >= 0 && index < len(arr) { 143 | res = arr[index] 144 | } else { 145 | err = segmentNotFound{"Array index", seg} 146 | } 147 | default: 148 | err = invalidSegmentType{"an array", value} 149 | } 150 | 151 | return 152 | } 153 | -------------------------------------------------------------------------------- /faunadb/errors_test.go: -------------------------------------------------------------------------------- 1 | package faunadb 2 | 3 | import ( 4 | "bytes" 5 | "io/ioutil" 6 | "net/http" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | var ( 13 | emptyErrorBody = `{ "errors": [] }` 14 | noErrors = []QueryError{} 15 | ) 16 | 17 | func TestReturnBadRequestOn400(t *testing.T) { 18 | err := checkForResponseErrors(httpErrorResponseWith(400, emptyErrorBody)) 19 | require.Equal(t, BadRequest{errorResponseWith(400, noErrors)}, err) 20 | } 21 | 22 | func TestReturnUnauthorizedOn401(t *testing.T) { 23 | err := checkForResponseErrors(httpErrorResponseWith(401, emptyErrorBody)) 24 | require.Equal(t, Unauthorized{errorResponseWith(401, noErrors)}, err) 25 | } 26 | 27 | func TestReturnPermissionDeniedon403(t *testing.T) { 28 | err := checkForResponseErrors(httpErrorResponseWith(403, emptyErrorBody)) 29 | require.Equal(t, PermissionDenied{errorResponseWith(403, noErrors)}, err) 30 | } 31 | 32 | func TestReturnNotFoundOn404(t *testing.T) { 33 | err := checkForResponseErrors(httpErrorResponseWith(404, emptyErrorBody)) 34 | require.Equal(t, NotFound{errorResponseWith(404, noErrors)}, err) 35 | } 36 | 37 | func TestReturnInternalErrorOn500(t *testing.T) { 38 | err := checkForResponseErrors(httpErrorResponseWith(500, emptyErrorBody)) 39 | require.Equal(t, InternalError{errorResponseWith(500, noErrors)}, err) 40 | } 41 | 42 | func TestReturnUnavailableOn503(t *testing.T) { 43 | err := checkForResponseErrors(httpErrorResponseWith(503, emptyErrorBody)) 44 | require.Equal(t, Unavailable{errorResponseWith(503, noErrors)}, err) 45 | } 46 | 47 | func TestReturnUnknownErrorByDefault(t *testing.T) { 48 | err := checkForResponseErrors(httpErrorResponseWith(1001, emptyErrorBody)) 49 | require.Equal(t, UnknownError{errorResponseWith(1001, noErrors)}, err) 50 | } 51 | 52 | func TestParseErrorResponse(t *testing.T) { 53 | json := ` 54 | { 55 | "errors": [ 56 | { 57 | "position": [ "data", "token" ], 58 | "code": "invalid token", 59 | "description": "Invalid token.", 60 | "cause": [ 61 | { 62 | "position": [ "data", "token" ], 63 | "code": "invalid token", 64 | "description": "invalid token" 65 | } 66 | ] 67 | } 68 | ] 69 | } 70 | ` 71 | 72 | err := checkForResponseErrors(httpErrorResponseWith(401, json)) 73 | 74 | expectedError := Unauthorized{ 75 | errorResponseWith(401, 76 | []QueryError{ 77 | { 78 | Position: []string{"data", "token"}, 79 | Code: "invalid token", 80 | Description: "Invalid token.", 81 | Cause: []ValidationFailure{ 82 | { 83 | Position: []string{"data", "token"}, 84 | Code: "invalid token", 85 | Description: "invalid token", 86 | }, 87 | }, 88 | }, 89 | }, 90 | ), 91 | } 92 | 93 | require.Equal(t, expectedError, err) 94 | require.EqualError(t, err, "Response error 401. Errors: [data/token](invalid token): Invalid token., details: [{[data token] invalid token invalid token}]") 95 | } 96 | 97 | func TestUnparseableResponse(t *testing.T) { 98 | json := "can't parse this as an error" 99 | err := checkForResponseErrors(httpErrorResponseWith(503, json)) 100 | 101 | require.Equal(t, Unavailable{errorResponse{status: 503}}, err) 102 | require.EqualError(t, err, "Response error 503. Unparseable server response.") 103 | } 104 | 105 | func httpErrorResponseWith(status int, errorBody string) *http.Response { 106 | return &http.Response{ 107 | StatusCode: status, 108 | Body: ioutil.NopCloser(bytes.NewBufferString(errorBody)), 109 | } 110 | } 111 | 112 | func errorResponseWith(status int, errors []QueryError) errorResponse { 113 | return errorResponse{ 114 | parseable: true, 115 | status: status, 116 | errors: errors, 117 | } 118 | } 119 | 120 | -------------------------------------------------------------------------------- /faunadb/decode.go: -------------------------------------------------------------------------------- 1 | package faunadb 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // A DecodeError describes an error when decoding a Fauna Value to a native Golang type 9 | type DecodeError struct { 10 | path path 11 | err error 12 | } 13 | 14 | func (d DecodeError) Error() string { 15 | path := d.path 16 | err := d.err 17 | 18 | for { 19 | if decodeErr, ok := err.(DecodeError); ok { 20 | path = path.subPath(decodeErr.path) 21 | err = decodeErr.err 22 | } else { 23 | break 24 | } 25 | } 26 | 27 | return fmt.Sprintf("Error while decoding fauna value at: %s. %s", path, err) 28 | } 29 | 30 | type valueDecoder struct { 31 | target reflect.Value 32 | targetType reflect.Type 33 | } 34 | 35 | func newValueDecoder(source interface{}) *valueDecoder { 36 | target, targetType := indirectValue(source) 37 | 38 | return &valueDecoder{ 39 | target: target, 40 | targetType: targetType, 41 | } 42 | } 43 | 44 | func (c *valueDecoder) assign(value interface{}) error { 45 | source, sourceType := indirectValue(value) 46 | 47 | if sourceType.AssignableTo(c.targetType) { 48 | c.target.Set(source) 49 | return nil 50 | } 51 | 52 | if sourceType.ConvertibleTo(c.targetType) { 53 | c.target.Set(source.Convert(c.targetType)) 54 | return nil 55 | } 56 | 57 | return DecodeError{ 58 | err: fmt.Errorf("Can not assign value of type \"%s\" to a value of type \"%s\"", sourceType, c.targetType), 59 | } 60 | } 61 | 62 | func (c *valueDecoder) decodeArray(arr ArrayV) error { 63 | if err := c.assign(arr); err == nil { 64 | return nil 65 | } 66 | 67 | if c.target.Kind() != reflect.Slice { 68 | return DecodeError{err: fmt.Errorf("Can not decode array into a value of type \"%s\"", c.targetType)} 69 | } 70 | 71 | return c.makeNewSlice(arr) 72 | } 73 | 74 | func (c *valueDecoder) makeNewSlice(arr []Value) error { 75 | newArray := reflect.MakeSlice(c.targetType, len(arr), len(arr)) 76 | 77 | for index, value := range arr { 78 | if err := value.Get(newArray.Index(index)); err != nil { 79 | return DecodeError{path: pathFromIndexes(index), err: err} 80 | } 81 | } 82 | 83 | return c.assign(newArray) 84 | } 85 | 86 | func (c *valueDecoder) decodeMap(obj ObjectV) error { 87 | if err := c.assign(obj); err == nil { 88 | return nil 89 | } 90 | 91 | switch c.target.Kind() { 92 | case reflect.Map: 93 | return c.makeNewMap(obj) 94 | case reflect.Struct: 95 | return c.fillStructFields(obj) 96 | default: 97 | return DecodeError{err: fmt.Errorf("Can not decode map into a value of type \"%s\"", c.targetType)} 98 | } 99 | } 100 | 101 | func (c *valueDecoder) makeNewMap(obj map[string]Value) error { 102 | newMap := reflect.MakeMap(c.targetType) 103 | elemType := c.targetType.Elem() 104 | 105 | for key, value := range obj { 106 | newElem := reflect.New(elemType).Elem() 107 | 108 | if err := value.Get(newElem); err != nil { 109 | return DecodeError{path: pathFromKeys(key), err: err} 110 | } 111 | 112 | newMap.SetMapIndex(reflect.ValueOf(key), newElem) 113 | } 114 | 115 | return c.assign(newMap) 116 | } 117 | 118 | func (c *valueDecoder) fillStructFields(obj map[string]Value) (err error) { 119 | newStruct := reflect.New(c.targetType).Elem() 120 | aStructType := newStruct.Type() 121 | 122 | //need to get all StructFields and unpack json values into the fields to check if they are empty - use allStructFields rather than 123 | for key, field := range allStructFields(newStruct) { 124 | f, _ := aStructType.FieldByName(key) 125 | if !field.CanInterface() { 126 | continue 127 | } 128 | fieldName, ignored, _, _ := parseTag(f) //need to parse tags 129 | 130 | value, found := obj[fieldName] 131 | 132 | if !found { 133 | continue 134 | } 135 | 136 | if ignored { 137 | continue 138 | } 139 | 140 | if err = value.Get(field); err != nil { 141 | return DecodeError{path: pathFromKeys(key), err: err} 142 | } 143 | } 144 | return c.assign(newStruct) 145 | } 146 | -------------------------------------------------------------------------------- /faunadb/benchmark_test.go: -------------------------------------------------------------------------------- 1 | package faunadb 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | // Current results: 11 | // BenchmarkParseJSON-8 50000 38307 ns/op 12 | // BenchmarkDecodeValue-8 50000 24235 ns/op 13 | // BenchmarkEncodeValue-8 100000 22126 ns/op 14 | // BenchmarkWriteJSON-8 50000 28964 ns/op 15 | // BenchmarkExtactValue-8 20000000 97.4 ns/op 16 | 17 | type benchmarkStruct struct { 18 | NonExistingField int 19 | nonPublicField int 20 | TaggedString string `fauna:"tagged"` 21 | Any Value 22 | Ref RefV 23 | Date time.Time 24 | Time time.Time 25 | LiteralObj map[string]string 26 | Str string 27 | Num int 28 | Float float64 29 | Boolean bool 30 | IntArr []int 31 | ObjArr []benchmarkNestedStruct 32 | Matrix [][]int 33 | Map map[string]string 34 | Object benchmarkNestedStruct 35 | Null *benchmarkNestedStruct 36 | } 37 | 38 | type benchmarkNestedStruct struct { 39 | Nested string 40 | } 41 | 42 | var ( 43 | benckmarkJSON = []byte(` 44 | { 45 | "Ref": { 46 | "@ref": "classes/spells/42" 47 | }, 48 | "Any": "any value", 49 | "Date": { "@date": "1970-01-03" }, 50 | "Time": { "@ts": "1970-01-01T00:00:00.000000005Z" }, 51 | "LiteralObj": { "@obj": {"@name": "@Jhon" } }, 52 | "tagged": "TaggedString", 53 | "Str": "Jhon Knows", 54 | "Num": 31, 55 | "Float": 31.1, 56 | "Boolean": true, 57 | "IntArr": [1, 2, 3], 58 | "ObjArr": [{"Nested": "object1"}, {"Nested": "object2"}], 59 | "Matrix": [[1, 2], [3, 4]], 60 | "Map": { 61 | "key": "value" 62 | }, 63 | "Object": { 64 | "Nested": "object" 65 | }, 66 | "Null": null 67 | } 68 | `) 69 | 70 | benchmarkData = benchmarkStruct{ 71 | TaggedString: "TaggedString", 72 | Ref: RefV{"classes/spells/42", nil, nil, nil}, 73 | Any: StringV("any value"), 74 | Date: time.Date(1970, time.January, 3, 0, 0, 0, 0, time.UTC), 75 | Time: time.Date(1970, time.January, 1, 0, 0, 0, 5, time.UTC), 76 | LiteralObj: map[string]string{"@name": "@Jhon"}, 77 | Str: "Jhon Knows", 78 | Num: 31, 79 | Float: 31.1, 80 | Boolean: true, 81 | IntArr: []int{ 82 | 1, 2, 3, 83 | }, 84 | ObjArr: []benchmarkNestedStruct{ 85 | {"object1"}, 86 | {"object2"}, 87 | }, 88 | Matrix: [][]int{ 89 | {1, 2}, 90 | {3, 4}, 91 | }, 92 | Map: map[string]string{"key": "value"}, 93 | Object: benchmarkNestedStruct{"object"}, 94 | } 95 | ) 96 | 97 | func BenchmarkParseJSON(b *testing.B) { 98 | for i := 0; i < b.N; i++ { 99 | if _, err := parseJSON(bytes.NewReader(benckmarkJSON)); err != nil { 100 | panic(err) 101 | } 102 | } 103 | } 104 | 105 | func BenchmarkDecodeValue(b *testing.B) { 106 | value, err := parseJSON(bytes.NewReader(benckmarkJSON)) 107 | if err != nil { 108 | panic(err) 109 | } 110 | 111 | for i := 0; i < b.N; i++ { 112 | var obj benchmarkStruct 113 | 114 | if err := value.Get(&obj); err != nil { 115 | panic(err) 116 | } 117 | } 118 | } 119 | 120 | func BenchmarkEncodeValue(b *testing.B) { 121 | expr := Obj{"data": benchmarkData} 122 | 123 | for i := 0; i < b.N; i++ { 124 | wrap(expr) 125 | } 126 | } 127 | 128 | func BenchmarkWriteJSON(b *testing.B) { 129 | escaped := wrap(benchmarkData) 130 | expr := Obj{"data": escaped} 131 | 132 | for i := 0; i < b.N; i++ { 133 | if _, err := json.Marshal(expr); err != nil { 134 | panic(err) 135 | } 136 | } 137 | } 138 | 139 | func BenchmarkExtactValue(b *testing.B) { 140 | field := ObjKey("ObjArr").AtIndex(1).AtKey("Nested") 141 | 142 | value, err := parseJSON(bytes.NewReader(benckmarkJSON)) 143 | if err != nil { 144 | panic(err) 145 | } 146 | 147 | for i := 0; i < b.N; i++ { 148 | if _, err := value.At(field).GetValue(); err != nil { 149 | panic(err) 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /faunadb/errors.go: -------------------------------------------------------------------------------- 1 | package faunadb 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net/http" 7 | "sort" 8 | "strings" 9 | ) 10 | 11 | var errorsField = ObjKey("errors") 12 | 13 | // A FaunaError wraps HTTP errors when sending queries to a FaunaDB cluster. 14 | type FaunaError interface { 15 | error 16 | Status() int // HTTP status code 17 | Errors() []QueryError // Errors returned by the server 18 | } 19 | 20 | // A BadRequest wraps an HTTP 400 error response. 21 | type BadRequest struct{ FaunaError } 22 | 23 | // A Unauthorized wraps an HTTP 401 error response. 24 | type Unauthorized struct{ FaunaError } 25 | 26 | // A PermissionDenied wraps an HTTP 403 error response. 27 | type PermissionDenied struct{ FaunaError } 28 | 29 | // A NotFound wraps an HTTP 404 error response. 30 | type NotFound struct{ FaunaError } 31 | 32 | // A InternalError wraps an HTTP 500 error response. 33 | type InternalError struct{ FaunaError } 34 | 35 | // A Unavailable wraps an HTTP 503 error response. 36 | type Unavailable struct{ FaunaError } 37 | 38 | // A UnknownError wraps any unknown http error response. 39 | type UnknownError struct{ FaunaError } 40 | 41 | // QueryError describes query errors returned by the server. 42 | type QueryError struct { 43 | Position []string `fauna:"position"` 44 | Code string `fauna:"code"` 45 | Description string `fauna:"description"` 46 | Cause []ValidationFailure `fauna:"cause"` 47 | } 48 | 49 | // ValidationFailure describes validation errors on a submitted query. 50 | type ValidationFailure struct { 51 | Position []string `fauna:"position"` 52 | Code string `fauna:"code"` 53 | Description string `fauna:"description"` 54 | } 55 | 56 | type errorResponse struct { 57 | parseable bool 58 | status int 59 | errors []QueryError 60 | } 61 | 62 | func (err errorResponse) Status() int { return err.status } 63 | func (err errorResponse) Errors() []QueryError { return err.errors } 64 | 65 | func (err errorResponse) Error() string { 66 | return fmt.Sprintf("Response error %d. %s", err.status, err.queryErrors()) 67 | } 68 | 69 | func (err *errorResponse) queryErrors() string { 70 | if !err.parseable { 71 | return "Unparseable server response." 72 | } 73 | 74 | errs := make([]string, len(err.errors)) 75 | 76 | for i, queryError := range err.errors { 77 | 78 | errs[i] = 79 | fmt.Sprintf("[%s](%s): %s, details: %s", strings.Join(queryError.Position, "/"), queryError.Code, queryError.Description, queryError.Cause) 80 | } 81 | 82 | return fmt.Sprintf("Errors: %s", strings.Join(errs, ", ")) 83 | } 84 | 85 | func checkForResponseErrors(response *http.Response) error { 86 | if response.StatusCode >= 200 && response.StatusCode < 300 { 87 | return nil 88 | } 89 | 90 | err := parseErrorResponse(response) 91 | 92 | switch response.StatusCode { 93 | case 400: 94 | return BadRequest{err} 95 | case 401: 96 | return Unauthorized{err} 97 | case 403: 98 | return PermissionDenied{err} 99 | case 404: 100 | return NotFound{err} 101 | case 500: 102 | return InternalError{err} 103 | case 503: 104 | return Unavailable{err} 105 | default: 106 | return UnknownError{err} 107 | } 108 | } 109 | 110 | func parseErrorResponse(response *http.Response) FaunaError { 111 | var errors []QueryError 112 | 113 | if response.Body != nil { 114 | if value, err := parseJSON(response.Body); err == nil { 115 | if err := value.At(errorsField).Get(&errors); err == nil { 116 | return errorResponse{true, response.StatusCode, errors} 117 | } 118 | } 119 | } 120 | 121 | return errorResponse{false, response.StatusCode, errors} 122 | } 123 | 124 | func errorFromStreamError(obj ObjectV) (err error) { 125 | var sb strings.Builder 126 | sb.WriteString("stream_error:") 127 | keys := make([]string, len(obj)) 128 | for k := range obj { 129 | keys = append(keys, k) 130 | } 131 | sort.Strings(keys) 132 | for _, k := range keys { 133 | if _, ok := obj[k]; ok { 134 | sb.WriteString(" ") 135 | sb.WriteString(k) 136 | sb.WriteString("=") 137 | sb.WriteString(fmt.Sprintf("'%s'", obj[k])) 138 | } 139 | 140 | } 141 | err = errors.New(sb.String()) 142 | return 143 | } 144 | -------------------------------------------------------------------------------- /faunadb/functions_logic.go: -------------------------------------------------------------------------------- 1 | package faunadb 2 | 3 | // Equals checks if all args are equivalents. 4 | // 5 | // Parameters: 6 | // args []Value - A collection of expressions to check for equivalence. 7 | // 8 | // Returns: 9 | // bool - true if all elements are equals, false otherwise. 10 | // 11 | // See: https://app.fauna.com/documentation/reference/queryapi#miscellaneous-functions 12 | func Equals(args ...interface{}) Expr { return equalsFn{Equals: wrap(varargs(args...))} } 13 | 14 | type equalsFn struct { 15 | fnApply 16 | Equals Expr `json:"equals" faunarepr:"varargs"` 17 | } 18 | 19 | // Any evaluates to true if any element of the collection is true. 20 | // 21 | // Parameters: 22 | // collection - the collection 23 | // 24 | // Returns: 25 | // Expr 26 | // 27 | // See: https://docs.fauna.com/fauna/current/api/fql/functions/any 28 | func Any(collection interface{}) Expr { 29 | return anyFn{Any: wrap(collection)} 30 | } 31 | 32 | type anyFn struct { 33 | fnApply 34 | Any Expr `json:"any"` 35 | } 36 | 37 | // All evaluates to true if all elements of the collection are true. 38 | // 39 | // Parameters: 40 | // collection - the collection 41 | // 42 | // Returns: 43 | // Expr 44 | // 45 | // See: https://docs.fauna.com/fauna/current/api/fql/functions/all 46 | func All(collection interface{}) Expr { 47 | return allFn{All: wrap(collection)} 48 | } 49 | 50 | type allFn struct { 51 | fnApply 52 | All Expr `json:"all"` 53 | } 54 | 55 | // LT returns true if each specified value is less than all the subsequent values. Otherwise LT returns false. 56 | // 57 | // Parameters: 58 | // args []number - A collection of terms to compare. 59 | // 60 | // Returns: 61 | // bool - true if all elements are less than each other from left to right. 62 | // 63 | // See: https://app.fauna.com/documentation/reference/queryapi#miscellaneous-functions 64 | func LT(args ...interface{}) Expr { return ltFn{LT: wrap(varargs(args...))} } 65 | 66 | type ltFn struct { 67 | fnApply 68 | LT Expr `json:"lt" faunarepr:"varargs"` 69 | } 70 | 71 | // LTE returns true if each specified value is less than or equal to all subsequent values. Otherwise LTE returns false. 72 | // 73 | // Parameters: 74 | // args []number - A collection of terms to compare. 75 | // 76 | // Returns: 77 | // bool - true if all elements are less than of equals each other from left to right. 78 | // 79 | // See: https://app.fauna.com/documentation/reference/queryapi#miscellaneous-functions 80 | func LTE(args ...interface{}) Expr { return lteFn{LTE: wrap(varargs(args...))} } 81 | 82 | type lteFn struct { 83 | fnApply 84 | LTE Expr `json:"lte" faunarepr:"varargs"` 85 | } 86 | 87 | // GT returns true if each specified value is greater than all subsequent values. Otherwise GT returns false. 88 | // and false otherwise. 89 | // 90 | // Parameters: 91 | // args []number - A collection of terms to compare. 92 | // 93 | // Returns: 94 | // bool - true if all elements are greather than to each other from left to right. 95 | // 96 | // See: https://app.fauna.com/documentation/reference/queryapi#miscellaneous-functions 97 | func GT(args ...interface{}) Expr { return gtFn{GT: wrap(varargs(args...))} } 98 | 99 | type gtFn struct { 100 | fnApply 101 | GT Expr `json:"gt" faunarepr:"varargs"` 102 | } 103 | 104 | // GTE returns true if each specified value is greater than or equal to all subsequent values. Otherwise GTE returns false. 105 | // 106 | // Parameters: 107 | // args []number - A collection of terms to compare. 108 | // 109 | // Returns: 110 | // bool - true if all elements are greather than or equals to each other from left to right. 111 | // 112 | // See: https://app.fauna.com/documentation/reference/queryapi#miscellaneous-functions 113 | func GTE(args ...interface{}) Expr { return gteFn{GTE: wrap(varargs(args...))} } 114 | 115 | type gteFn struct { 116 | fnApply 117 | GTE Expr `json:"gte" faunarepr:"varargs"` 118 | } 119 | 120 | // And returns the conjunction of a list of boolean values. 121 | // 122 | // Parameters: 123 | // args []bool - A collection to compute the conjunction of. 124 | // 125 | // Returns: 126 | // bool - true if all elements are true, false otherwise. 127 | // 128 | // See: https://app.fauna.com/documentation/reference/queryapi#miscellaneous-functions 129 | func And(args ...interface{}) Expr { return andFn{And: wrap(varargs(args...))} } 130 | 131 | type andFn struct { 132 | fnApply 133 | And Expr `json:"and" faunarepr:"varargs"` 134 | } 135 | 136 | // Or returns the disjunction of a list of boolean values. 137 | // 138 | // Parameters: 139 | // args []bool - A collection to compute the disjunction of. 140 | // 141 | // Returns: 142 | // bool - true if at least one element is true, false otherwise. 143 | // 144 | // See: https://app.fauna.com/documentation/reference/queryapi#miscellaneous-functions 145 | func Or(args ...interface{}) Expr { return orFn{Or: wrap(varargs(args...))} } 146 | 147 | type orFn struct { 148 | fnApply 149 | Or Expr `json:"or" faunarepr:"varargs"` 150 | } 151 | 152 | // Not returns the negation of a boolean value. 153 | // 154 | // Parameters: 155 | // boolean bool - A boolean to produce the negation of. 156 | // 157 | // Returns: 158 | // bool - The value negated. 159 | // 160 | // See: https://app.fauna.com/documentation/reference/queryapi#miscellaneous-functions 161 | func Not(boolean interface{}) Expr { return notFn{Not: wrap(boolean)} } 162 | 163 | type notFn struct { 164 | fnApply 165 | Not Expr `json:"not"` 166 | } 167 | -------------------------------------------------------------------------------- /faunadb/stream_events.go: -------------------------------------------------------------------------------- 1 | package faunadb 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | // StreamEventType is a stream eveny type 9 | type StreamEventType = string 10 | 11 | const ( 12 | // ErrorEventT is the stream error event type 13 | ErrorEventT StreamEventType = "error" 14 | 15 | // HistoryRewriteEventT is the stream history rewrite event type 16 | HistoryRewriteEventT StreamEventType = "history_rewrite" 17 | 18 | // StartEventT is the stream start event type 19 | StartEventT StreamEventType = "start" 20 | 21 | // VersionEventT is the stream version event type 22 | VersionEventT StreamEventType = "version" 23 | 24 | // SetT is the stream set event type 25 | SetEventT StreamEventType = "set" 26 | ) 27 | 28 | // StreamEvent represents a stream event with a `type` and `txn` 29 | type StreamEvent interface { 30 | Type() StreamEventType 31 | Txn() int64 32 | String() string 33 | } 34 | 35 | // StartEvent emitted when a valid stream subscription begins. 36 | // Upcoming events are guaranteed to have transaction timestamps equal to or greater than 37 | // the stream's start timestamp. 38 | type StartEvent struct { 39 | StreamEvent 40 | txn int64 41 | event Value 42 | } 43 | 44 | // Type returns the stream event type 45 | func (event StartEvent) Type() StreamEventType { 46 | return StartEventT 47 | } 48 | 49 | // Txn returns the stream event timestamp 50 | func (event StartEvent) Txn() int64 { 51 | return event.txn 52 | } 53 | 54 | // Event returns the stream event as a `f.Value` 55 | func (event StartEvent) Event() Value { 56 | return event.event 57 | } 58 | 59 | func (event StartEvent) String() string { 60 | return fmt.Sprintf("StartEvent{event=%d, txn=%d} ", event.Event(), event.Txn()) 61 | } 62 | 63 | // VersionEvent represents a version event that occurs upon any 64 | // modifications to the current state of the subscribed document. 65 | type VersionEvent struct { 66 | StreamEvent 67 | txn int64 68 | event Value 69 | } 70 | 71 | // Txn returns the stream event timestamp 72 | func (event VersionEvent) Txn() int64 { 73 | return event.txn 74 | } 75 | 76 | // Event returns the stream event as a `Value` 77 | func (event VersionEvent) Event() Value { 78 | return event.event 79 | } 80 | 81 | func (event VersionEvent) String() string { 82 | return fmt.Sprintf("VersionEvent{txn=%d, event=%s}", event.Txn(), event.Event()) 83 | } 84 | 85 | // Type returns the stream event type 86 | func (event VersionEvent) Type() StreamEventType { 87 | return VersionEventT 88 | } 89 | 90 | // VersionEvent represents a version event that occurs upon any 91 | // modifications to the current state of the subscribed document. 92 | type SetEvent struct { 93 | StreamEvent 94 | txn int64 95 | event Value 96 | } 97 | 98 | // Txn returns the stream event timestamp 99 | func (event SetEvent) Txn() int64 { 100 | return event.txn 101 | } 102 | 103 | // Event returns the stream event as a `Value` 104 | func (event SetEvent) Event() Value { 105 | return event.event 106 | } 107 | 108 | func (event SetEvent) String() string { 109 | return fmt.Sprintf("SetEvent{txn=%d, event=%s}", event.Txn(), event.Event()) 110 | } 111 | 112 | // Type returns the stream event type 113 | func (event SetEvent) Type() StreamEventType { 114 | return SetEventT 115 | } 116 | 117 | // HistoryRewriteEvent represents a history rewrite event which occurs upon any modifications 118 | // to the history of the subscribed document. 119 | type HistoryRewriteEvent struct { 120 | StreamEvent 121 | txn int64 122 | event Value 123 | } 124 | 125 | // Txn returns the stream event timestamp 126 | func (event HistoryRewriteEvent) Txn() int64 { 127 | return event.txn 128 | } 129 | 130 | // Event returns the stream event as a `Value` 131 | func (event HistoryRewriteEvent) Event() Value { 132 | return event.event 133 | } 134 | 135 | func (event HistoryRewriteEvent) String() string { 136 | return fmt.Sprintf("HistoryRewriteEvent{txn=%d, event=%s}", event.Txn(), event.Event()) 137 | } 138 | 139 | // Type returns the stream event type 140 | func (event HistoryRewriteEvent) Type() StreamEventType { 141 | return HistoryRewriteEventT 142 | } 143 | 144 | // ErrorEvent represents an error event fired both for client and server errors 145 | // that may occur as a result of a subscription. 146 | type ErrorEvent struct { 147 | StreamEvent 148 | txn int64 149 | err error 150 | } 151 | 152 | // Type returns the stream event type 153 | func (event ErrorEvent) Type() StreamEventType { 154 | return ErrorEventT 155 | } 156 | 157 | // Txn returns the stream event timestamp 158 | func (event ErrorEvent) Txn() int64 { 159 | return event.txn 160 | } 161 | 162 | // Error returns the event error 163 | func (event ErrorEvent) Error() error { 164 | return event.err 165 | } 166 | 167 | func (event ErrorEvent) String() string { 168 | return fmt.Sprintf("ErrorEvent{error=%s}", event.err) 169 | } 170 | 171 | func unMarshalStreamEvent(data Obj) (evt StreamEvent, err error) { 172 | if tpe, ok := data["type"]; ok { 173 | switch StreamEventType(tpe.(StringV)) { 174 | case StartEventT: 175 | evt = StartEvent{ 176 | txn: int64(data["txn"].(LongV)), 177 | event: data["event"].(LongV), 178 | } 179 | case VersionEventT: 180 | evt = VersionEvent{ 181 | txn: int64(data["txn"].(LongV)), 182 | event: data["event"].(ObjectV), 183 | } 184 | case SetEventT: 185 | evt = SetEvent{ 186 | txn: int64(data["txn"].(LongV)), 187 | event: data["event"].(ObjectV), 188 | } 189 | case ErrorEventT: 190 | evt = ErrorEvent{ 191 | txn: int64(data["txn"].(LongV)), 192 | err: errorFromStreamError(data["event"].(ObjectV)), 193 | } 194 | case HistoryRewriteEventT: 195 | evt = HistoryRewriteEvent{ 196 | txn: int64(data["txn"].(LongV)), 197 | event: data["event"].(ObjectV), 198 | } 199 | } 200 | } else { 201 | err = errors.New("unparseable event type") 202 | } 203 | return 204 | } 205 | -------------------------------------------------------------------------------- /faunadb/functions_sets.go: -------------------------------------------------------------------------------- 1 | package faunadb 2 | 3 | // Set 4 | 5 | // Singleton returns the history of the document's presence of the provided ref. 6 | // 7 | // Parameters: 8 | // ref Ref - The reference of the document for which to retrieve the singleton set. 9 | // 10 | // Returns: 11 | // SetRef - The singleton SetRef. 12 | // 13 | // See: https://app.fauna.com/documentation/reference/queryapi#sets 14 | func Singleton(ref interface{}) Expr { return singletonFn{Singleton: wrap(ref)} } 15 | 16 | type singletonFn struct { 17 | fnApply 18 | Singleton Expr `json:"singleton"` 19 | } 20 | 21 | // Events returns the history of document's data of the provided ref. 22 | // 23 | // Parameters: 24 | // refSet Ref|SetRef - A reference or set reference to retrieve an event set from. 25 | // 26 | // Returns: 27 | // SetRef - The events SetRef. 28 | // 29 | // See: https://app.fauna.com/documentation/reference/queryapi#sets 30 | func Events(refSet interface{}) Expr { return eventsFn{Events: wrap(refSet)} } 31 | 32 | type eventsFn struct { 33 | fnApply 34 | Events Expr `json:"events"` 35 | } 36 | 37 | // Match returns the set of documents for the specified index. 38 | // 39 | // Parameters: 40 | // ref Ref - The reference of the index to match against. 41 | // 42 | // Returns: 43 | // SetRef 44 | // 45 | // See: https://app.fauna.com/documentation/reference/queryapi#sets 46 | func Match(ref interface{}) Expr { return matchFn{Match: wrap(ref), Terms: nil} } 47 | 48 | type matchFn struct { 49 | fnApply 50 | Match Expr `json:"match"` 51 | Terms Expr `json:"terms,omitempty"` 52 | } 53 | 54 | // MatchTerm returns th set of documents that match the terms in an index. 55 | // 56 | // Parameters: 57 | // ref Ref - The reference of the index to match against. 58 | // terms []Value - A list of terms used in the match. 59 | // 60 | // Returns: 61 | // SetRef 62 | // 63 | // See: https://app.fauna.com/documentation/reference/queryapi#sets 64 | func MatchTerm(ref, terms interface{}) Expr { return matchFn{Match: wrap(ref), Terms: wrap(terms)} } 65 | 66 | // Union returns the set of documents that are present in at least one of the specified sets. 67 | // 68 | // Parameters: 69 | // sets []SetRef - A list of SetRef to union together. 70 | // 71 | // Returns: 72 | // SetRef 73 | // 74 | // See: https://app.fauna.com/documentation/reference/queryapi#sets 75 | func Union(sets ...interface{}) Expr { return unionFn{Union: wrap(varargs(sets...))} } 76 | 77 | type unionFn struct { 78 | fnApply 79 | Union Expr `json:"union" faunarepr:"varargs"` 80 | } 81 | 82 | // Merge two or more objects.. 83 | // 84 | // Parameters: 85 | // merge merge the first object. 86 | // with the second object or a list of objects 87 | // lambda a lambda to resolve possible conflicts 88 | // 89 | // Returns: 90 | // merged object 91 | // 92 | func Merge(merge interface{}, with interface{}, lambda ...OptionalParameter) Expr { 93 | fn := mergeFn{Merge: wrap(merge), With: wrap(with)} 94 | return applyOptionals(fn, lambda) 95 | } 96 | 97 | type mergeFn struct { 98 | fnApply 99 | Merge Expr `json:"merge"` 100 | With Expr `json:"with"` 101 | Lambda Expr `json:"lambda,omitempty" faunarepr:"optfn,name=ConflictResolver"` 102 | } 103 | 104 | // Reduce function applies a reducer Lambda function serially to each member of the collection to produce a single value. 105 | // 106 | // Parameters: 107 | // lambda Expr - The accumulator function 108 | // initial Expr - The initial value 109 | // collection Expr - The collection to be reduced 110 | // 111 | // Returns: 112 | // Expr 113 | // 114 | // See: https://docs.fauna.com/fauna/current/api/fql/functions/reduce 115 | func Reduce(lambda, initial interface{}, collection interface{}) Expr { 116 | return reduceFn{ 117 | Reduce: wrap(lambda), 118 | Initial: wrap(initial), 119 | Collection: wrap(collection), 120 | } 121 | } 122 | 123 | type reduceFn struct { 124 | fnApply 125 | Reduce Expr `json:"reduce"` 126 | Initial Expr `json:"initial"` 127 | Collection Expr `json:"collection"` 128 | } 129 | 130 | // Intersection returns the set of documents that are present in all of the specified sets. 131 | // 132 | // Parameters: 133 | // sets []SetRef - A list of SetRef to intersect. 134 | // 135 | // Returns: 136 | // SetRef 137 | // 138 | // See: https://app.fauna.com/documentation/reference/queryapi#sets 139 | func Intersection(sets ...interface{}) Expr { 140 | return intersectionFn{Intersection: wrap(varargs(sets...))} 141 | } 142 | 143 | type intersectionFn struct { 144 | fnApply 145 | Intersection Expr `json:"intersection" faunarepr:"varargs"` 146 | } 147 | 148 | // Difference returns the set of documents that are present in the first set but not in 149 | // any of the other specified sets. 150 | // 151 | // Parameters: 152 | // sets []SetRef - A list of SetRef to diff. 153 | // 154 | // Returns: 155 | // SetRef 156 | // 157 | // See: https://app.fauna.com/documentation/reference/queryapi#sets 158 | func Difference(sets ...interface{}) Expr { return differenceFn{Difference: wrap(varargs(sets...))} } 159 | 160 | type differenceFn struct { 161 | fnApply 162 | Difference Expr `json:"difference" faunarepr:"varargs"` 163 | } 164 | 165 | // Distinct returns the set of documents with duplicates removed. 166 | // 167 | // Parameters: 168 | // set []SetRef - A list of SetRef to remove duplicates from. 169 | // 170 | // Returns: 171 | // SetRef 172 | // 173 | // See: https://app.fauna.com/documentation/reference/queryapi#sets 174 | func Distinct(set interface{}) Expr { return distinctFn{Distinct: wrap(set)} } 175 | 176 | type distinctFn struct { 177 | fnApply 178 | Distinct Expr `json:"distinct"` 179 | } 180 | 181 | // Join derives a set of resources by applying each document in the source set to the target set. 182 | // 183 | // Parameters: 184 | // source SetRef - A SetRef of the source set. 185 | // target Lambda - A Lambda that will accept each element of the source Set and return a Set. 186 | // 187 | // Returns: 188 | // SetRef 189 | // 190 | // See: https://app.fauna.com/documentation/reference/queryapi#sets 191 | func Join(source, target interface{}) Expr { return joinFn{Join: wrap(source), With: wrap(target)} } 192 | 193 | type joinFn struct { 194 | fnApply 195 | Join Expr `json:"join"` 196 | With Expr `json:"with"` 197 | } 198 | 199 | // Range filters the set based on the lower/upper bounds (inclusive). 200 | // 201 | // Parameters: 202 | // set SetRef - Set to be filtered. 203 | // from - lower bound. 204 | // to - upper bound 205 | // 206 | // Returns: 207 | // SetRef 208 | // 209 | // See: https://app.fauna.com/documentation/reference/queryapi#sets 210 | func Range(set interface{}, from interface{}, to interface{}) Expr { 211 | return rangeFn{Range: wrap(set), From: wrap(from), To: wrap(to)} 212 | } 213 | 214 | type rangeFn struct { 215 | fnApply 216 | Range Expr `json:"range"` 217 | From Expr `json:"from"` 218 | To Expr `json:"to"` 219 | } 220 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Golang driver for Fauna v4 (deprecated) 2 | 3 | [![Go Report Card](https://goreportcard.com/badge/github.com/fauna/faunadb-go)](https://goreportcard.com/report/github.com/fauna/faunadb-go) 4 | [![GoDoc](https://godoc.org/github.com/fauna/faunadb-go/faunadb?status.svg)](https://pkg.go.dev/github.com/fauna/faunadb-go/v4) 5 | [![License](https://img.shields.io/badge/license-MPL_2.0-blue.svg?maxAge=2592000)](https://raw.githubusercontent.com/fauna/faunadb-go/main/LICENSE) 6 | 7 | > [!WARNING] 8 | > Fauna is decommissioning FQL v4 on June 30, 2025. 9 | > 10 | > This driver is not compatible with FQL v10, the latest version. Fauna accounts 11 | > created after August 21, 2024 must use FQL v10. Ensure you migrate existing 12 | > projects to the official v10 driver by the v4 EOL date: 13 | > https://github.com/fauna/fauna-go. 14 | > 15 | > For more information, see the [v4 end of life (EOL) 16 | > announcement](https://docs.fauna.com/fauna/v4/#fql-v4-end-of-life) and 17 | > [related FAQ](https://docs.fauna.com/fauna/v4/migration/faq). 18 | 19 | The official Golang driver for [Fauna v4](https://docs.fauna.com/fauna/v4/). 20 | 21 | See the [Fauna v4 documentation](https://docs.fauna.com/fauna/v4) and 22 | [Tttorials](https://docs.fauna.com/fauna/v4/learn/tutorials/fql/crud?lang=javascript) 23 | for guides and a complete database [API 24 | reference](https://docs.fauna.com/fauna/v4/api/fql/). 25 | 26 | ## Supported Go Versions 27 | 28 | Currently, the driver is tested on: 29 | - 1.13 30 | - 1.14 31 | - 1.15 32 | - 1.16 33 | 34 | ## Using the Driver 35 | 36 | ### Installing 37 | 38 | To get the latest version run: 39 | 40 | ```bash 41 | go get github.com/fauna/faunadb-go/v4/faunadb 42 | ``` 43 | 44 | Please note that our driver undergoes breaking changes from time to time, so depending on our `main` branch is not recommended. 45 | It is recommended to use one of the following methods instead: 46 | 47 | ### Importing 48 | 49 | For better usage, we recommend that you import this driver with an alias import. 50 | 51 | #### Using `dep` or `go get` 52 | 53 | To import a specific version when using `go get`, use: 54 | 55 | ```go 56 | import f "github.com/fauna/faunadb-go/v4/faunadb" 57 | ``` 58 | 59 | ### Basic Usage 60 | 61 | ```go 62 | package main 63 | 64 | import ( 65 | "fmt" 66 | 67 | f "github.com/fauna/faunadb-go/v4/faunadb" 68 | ) 69 | 70 | type User struct { 71 | Name string `fauna:"name"` 72 | } 73 | 74 | func main() { 75 | client := f.NewFaunaClient("your-secret-here") 76 | 77 | res, err := client.Query(f.Get(f.Ref(f.Collection("user"), "42"))) 78 | if err != nil { 79 | panic(err) 80 | } 81 | 82 | var user User 83 | 84 | if err := res.At(f.ObjKey("data")).Get(&user); err != nil { 85 | panic(err) 86 | } 87 | 88 | fmt.Println(user) 89 | } 90 | ``` 91 | 92 | ### Streaming feature usage 93 | ```go 94 | package main 95 | 96 | import f "github.com/fauna/faunadb-go/v4/faunadb" 97 | 98 | func main() { 99 | secret := "" 100 | dbClient = f.NewFaunaClient(secret) 101 | var ref f.RefV 102 | value, err := dbClient.Query(f.Get(&ref)) 103 | if err != nil { 104 | panic(err) 105 | } 106 | err = value.At(f.ObjKey("ref")).Get(&docRef) 107 | var subscription f.StreamSubscription 108 | subscription = dbClient.Stream(docRef) 109 | err = subscription.Start() 110 | if err != nil { 111 | panic("Panic") 112 | } 113 | for a := range subscription.StreamEvents() { 114 | switch a.Type() { 115 | 116 | case f.StartEventT: 117 | // do smth on start event 118 | 119 | case f.HistoryRewriteEventT: 120 | // do smth on historyRewrite event 121 | 122 | case f.VersionEventT: 123 | // do smth on version event 124 | 125 | case f.ErrorEventT: 126 | // do smth on error event 127 | subscription.Close() // if you want to close streaming on errors 128 | } 129 | } 130 | } 131 | ``` 132 | 133 | ### Omitempty usage 134 | ```go 135 | package main 136 | 137 | import f "github.com/fauna/faunadb-go/v4/faunadb" 138 | 139 | func main() { 140 | secret := "" 141 | dbClient = f.NewFaunaClient(secret) 142 | var ref f.RefV 143 | value, err := dbClient.Query(f.Get(&ref)) 144 | if err != nil { 145 | panic(err) 146 | } 147 | type OmitStruct struct { 148 | Name string `fauna:"name,omitempty"` 149 | Age int `fauna:"age,omitempty"` 150 | Payment float64 `fauna:"payment,omitempty"` 151 | AgePointer *int `fauna:"agePointer,omitempty"` 152 | PaymentPointer *float64 `fauna:"paymentPointer,omitempty"` 153 | } 154 | _, err := dbClient.Query( 155 | f.Create(f.Collection("categories"), f.Obj{"data": OmitStruct{Name: "John", Age: 0}})) 156 | if err != nil { 157 | panic(err) 158 | } 159 | } 160 | ``` 161 | **Result (empty/zero fields will be ignored):** 162 | ```text 163 | { 164 | "ref": Ref(Collection("categories"), "295143889346494983"), 165 | "ts": 1617729997710000, 166 | "data": { 167 | "name": "John" 168 | } 169 | } 170 | ``` 171 | ### Http2 support 172 | Driver uses http2 by default. To use http 1.x provide custom http client to `FaunaClient` 173 | ```go 174 | package main 175 | 176 | import f "github.com/fauna/faunadb-go/v4/faunadb" 177 | 178 | func main() { 179 | secret := "" 180 | customHttpClient := http.Client{} 181 | dbClient = f.NewFaunaClient(secret, f.HTTP(&customHttpClient)) 182 | } 183 | ``` 184 |
185 | For more information about Fauna Query Language (FQL), consult our query language 186 | [reference documentation](https://docs.fauna.com/fauna/v4/api/fql/). 187 | 188 | Specific reference documentation for the driver is hosted at 189 | [GoDoc](https://pkg.go.dev/github.com/fauna/faunadb-go/v4). 190 | 191 | 192 | Most users found tests for the driver as a very useful form of documentation 193 | [Check it out here](https://github.com/fauna/faunadb-go/tree/main/faunadb). 194 | 195 | 196 | ## Contributing 197 | 198 | GitHub pull requests are very welcome. 199 | 200 | ### Driver Development 201 | 202 | Run `go get -t ./...` in order to install project's dependencies. 203 | 204 | Run tests against FaunaDB Cloud by passing your root database key to the 205 | test suite, as follows: `FAUNA_ROOT_KEY="your-cloud-secret" go test ./...`. 206 | 207 | If you have access to another running FaunaDB database, use the 208 | `FAUNA_ENDPOINT` environment variable to specify its URI. 209 | 210 | Alternatively, tests can be run via a Docker container with 211 | `FAUNA_ROOT_KEY="your-cloud-secret" make docker-test` (an alternate 212 | Debian-based Go image can be provided via `RUNTIME_IMAGE`). 213 | 214 | Tip: Setting the `FAUNA_QUERY_TIMEOUT_MS` environment variable will 215 | set a timeout in milliseconds for all queries. 216 | 217 | ## LICENSE 218 | 219 | Copyright 2020 [Fauna, Inc.](https://fauna.com/) 220 | 221 | Licensed under the Mozilla Public License, Version 2.0 (the 222 | "License"); you may not use this software except in compliance with 223 | the License. You may obtain a copy of the License at 224 | 225 | [http://mozilla.org/MPL/2.0/](http://mozilla.org/MPL/2.0/) 226 | 227 | Unless required by applicable law or agreed to in writing, software 228 | distributed under the License is distributed on an "AS IS" BASIS, 229 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 230 | implied. See the License for the specific language governing 231 | permissions and limitations under the License. 232 | -------------------------------------------------------------------------------- /faunadb/functions_basic.go: -------------------------------------------------------------------------------- 1 | package faunadb 2 | 3 | // Basic forms 4 | 5 | // Abort aborts the execution of the query 6 | // 7 | // Parameters: 8 | // msg string - An error message. 9 | // 10 | // Returns: 11 | // Error 12 | // 13 | // See: https://app.fauna.com/documentation/reference/queryapi#basic-forms 14 | func Abort(msg interface{}) Expr { return abortFn{Abort: wrap(msg)} } 15 | 16 | type abortFn struct { 17 | fnApply 18 | Abort Expr `json:"abort"` 19 | } 20 | 21 | // Do sequentially evaluates its arguments, and returns the last expression. 22 | // If no expressions are provided, do returns an error. 23 | // 24 | // Parameters: 25 | // exprs []Expr - A variable number of expressions to be evaluated. 26 | // 27 | // Returns: 28 | // Value - The result of the last expression in the list. 29 | // 30 | // See: https://app.fauna.com/documentation/reference/queryapi#basic-forms 31 | func Do(exprs ...interface{}) Expr { return doFn{Do: wrap(varargs(exprs))} } 32 | 33 | type doFn struct { 34 | fnApply 35 | Do Expr `json:"do" faunarepr:"varargs"` 36 | } 37 | 38 | // If evaluates and returns then or elze depending on the value of cond. 39 | // If cond evaluates to anything other than a boolean, if returns an “invalid argument” error 40 | // 41 | // Parameters: 42 | // cond bool - A boolean expression. 43 | // then Expr - The expression to run if condition is true. 44 | // elze Expr - The expression to run if condition is false. 45 | // 46 | // Returns: 47 | // Value - The result of either then or elze expression. 48 | // 49 | // See: https://app.fauna.com/documentation/reference/queryapi#basic-forms 50 | func If(cond, then, elze interface{}) Expr { 51 | return ifFn{ 52 | If: wrap(cond), 53 | Then: wrap(then), 54 | Else: wrap(elze), 55 | } 56 | } 57 | 58 | type ifFn struct { 59 | fnApply 60 | If Expr `json:"if"` 61 | Then Expr `json:"then"` 62 | Else Expr `json:"else"` 63 | } 64 | 65 | // Lambda creates an anonymous function. Mostly used with Collection functions. 66 | // 67 | // Parameters: 68 | // varName string|[]string - A string or an array of strings of arguments name to be bound in the body of the lambda. 69 | // expr Expr - An expression used as the body of the lambda. 70 | // 71 | // Returns: 72 | // Value - The result of the body expression. 73 | // 74 | // See: https://app.fauna.com/documentation/reference/queryapi#basic-forms 75 | func Lambda(varName, expr interface{}) Expr { 76 | return lambdaFn{ 77 | Lambda: wrap(varName), 78 | Expression: wrap(expr), 79 | } 80 | } 81 | 82 | type lambdaFn struct { 83 | fnApply 84 | Lambda Expr `json:"lambda"` 85 | Expression Expr `json:"expr"` 86 | } 87 | 88 | // At execute an expression at a given timestamp. 89 | // 90 | // Parameters: 91 | // timestamp time - The timestamp in which the expression will be evaluated. 92 | // expr Expr - An expression to be evaluated. 93 | // 94 | // Returns: 95 | // Value - The result of the given expression. 96 | // 97 | // See: https://app.fauna.com/documentation/reference/queryapi#basic-forms 98 | func At(timestamp, expr interface{}) Expr { 99 | return atFn{ 100 | At: wrap(timestamp), 101 | Expression: wrap(expr), 102 | } 103 | } 104 | 105 | type atFn struct { 106 | fnApply 107 | At Expr `json:"at"` 108 | Expression Expr `json:"expr"` 109 | } 110 | 111 | // LetBuilder builds Let expressions 112 | type LetBuilder struct { 113 | bindings unescapedArr 114 | } 115 | 116 | type letFn struct { 117 | fnApply 118 | Let Expr `json:"let"` 119 | In Expr `json:"in"` 120 | } 121 | 122 | // Bind binds a variable name to a value and returns a LetBuilder 123 | func (lb *LetBuilder) Bind(key string, in interface{}) *LetBuilder { 124 | binding := make(unescapedObj, 1) 125 | binding[key] = wrap(in) 126 | lb.bindings = append(lb.bindings, binding) 127 | return lb 128 | } 129 | 130 | // In sets the expression to be evaluated and returns the prepared Let. 131 | func (lb *LetBuilder) In(in Expr) Expr { 132 | return letFn{ 133 | Let: wrap(lb.bindings), 134 | In: in, 135 | } 136 | } 137 | 138 | // Let binds values to one or more variables. 139 | // 140 | // Returns: 141 | // *LetBuilder - Returns a LetBuilder. 142 | // 143 | // See: https://app.fauna.com/documentation/reference/queryapi#basic-forms 144 | func Let() *LetBuilder { return &LetBuilder{nil} } 145 | 146 | // Var refers to a value of a variable on the current lexical scope. 147 | // 148 | // Parameters: 149 | // name string - The variable name. 150 | // 151 | // Returns: 152 | // Value - The variable value. 153 | // 154 | // See: https://app.fauna.com/documentation/reference/queryapi#basic-forms 155 | func Var(name string) Expr { return varFn{Var: wrap(name)} } 156 | 157 | type varFn struct { 158 | fnApply 159 | Var Expr `json:"var"` 160 | } 161 | 162 | // Call invokes the specified function passing in a variable number of arguments 163 | // 164 | // Parameters: 165 | // ref Ref - The reference to the user defined functions to call. 166 | // args []Value - A series of values to pass as arguments to the user defined function. 167 | // 168 | // Returns: 169 | // Value - The return value of the user defined function. 170 | // 171 | // See: https://app.fauna.com/documentation/reference/queryapi#basic-forms 172 | func Call(ref interface{}, args ...interface{}) Expr { 173 | return callFn{Call: wrap(ref), Params: wrap(varargs(args...))} 174 | } 175 | 176 | type callFn struct { 177 | fnApply 178 | Call Expr `json:"call"` 179 | Params Expr `json:"arguments"` 180 | } 181 | 182 | // Query creates an instance of the @query type with the specified lambda 183 | // 184 | // Parameters: 185 | // lambda Lambda - A lambda representation. See Lambda() function. 186 | // 187 | // Returns: 188 | // Query - The lambda wrapped in a @query type. 189 | // 190 | // See: https://app.fauna.com/documentation/reference/queryapi#basic-forms 191 | func Query(lambda interface{}) Expr { return queryFn{Query: wrap(lambda)} } 192 | 193 | type queryFn struct { 194 | fnApply 195 | Query Expr `json:"query"` 196 | } 197 | 198 | // Select traverses into the provided value, returning the value at the given path. 199 | // 200 | // Parameters: 201 | // path []Path - An array representing a path to pull from an object. Path can be either strings or numbers. 202 | // value Object - The object to select from. 203 | // 204 | // Optional parameters: 205 | // default Value - A default value if the path does not exist. See Default() function. 206 | // 207 | // Returns: 208 | // Value - The value at the given path location. 209 | // 210 | // See: https://app.fauna.com/documentation/reference/queryapi#read-functions 211 | func Select(path, value interface{}, options ...OptionalParameter) Expr { 212 | fn := selectFn{Select: wrap(path), From: wrap(value)} 213 | return applyOptionals(fn, options) 214 | } 215 | 216 | type selectFn struct { 217 | fnApply 218 | Select Expr `json:"select"` 219 | From Expr `json:"from"` 220 | Default Expr `json:"default,omitempty" faunarepr:"optfn"` 221 | } 222 | 223 | // SelectAll traverses into the provided value flattening all values under the desired path. 224 | // 225 | // Parameters: 226 | // path []Path - An array representing a path to pull from an object. Path can be either strings or numbers. 227 | // value Object - The object to select from. 228 | // 229 | // Returns: 230 | // Value - The value at the given path location. 231 | // 232 | // See: https://app.fauna.com/documentation/reference/queryapi#read-functions 233 | func SelectAll(path, value interface{}) Expr { 234 | return selectAllFn{SelectAll: wrap(path), From: wrap(value)} 235 | } 236 | 237 | type selectAllFn struct { 238 | fnApply 239 | SelectAll Expr `json:"select_all"` 240 | From Expr `json:"from"` 241 | Default Expr `json:"default,omitempty" faunarepr:"optfn"` 242 | } 243 | -------------------------------------------------------------------------------- /faunadb/json.go: -------------------------------------------------------------------------------- 1 | package faunadb 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "encoding/json" 7 | "fmt" 8 | "io" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | // Unmarshal json string into a FaunaDB value. 14 | func UnmarshalJSON(buffer []byte, outValue *Value) error { 15 | reader := bytes.NewReader(buffer) 16 | 17 | if value, err := parseJSON(reader); err != nil { 18 | return err 19 | } else { 20 | *outValue = value 21 | } 22 | 23 | return nil 24 | } 25 | 26 | // Marshal a FaunaDB value into a json string. 27 | func MarshalJSON(value Value) ([]byte, error) { 28 | return json.Marshal(unwrap(value)) 29 | } 30 | 31 | func unwrap(value Value) interface{} { 32 | switch v := value.(type) { 33 | case ArrayV: 34 | array := make([]interface{}, len(v)) 35 | 36 | for i, elem := range v { 37 | array[i] = unwrap(elem) 38 | } 39 | 40 | return array 41 | 42 | case ObjectV: 43 | object := make(map[string]interface{}, len(v)) 44 | 45 | for key, elem := range v { 46 | object[key] = unwrap(elem) 47 | } 48 | 49 | return object 50 | 51 | default: 52 | return value 53 | } 54 | } 55 | 56 | func parseJSON(reader io.Reader) (Value, error) { 57 | decoder := json.NewDecoder(reader) 58 | decoder.UseNumber() 59 | 60 | parser := jsonParser{decoder} 61 | return parser.parseNext() 62 | } 63 | 64 | type wrongToken struct { 65 | expected string 66 | got json.Token 67 | } 68 | 69 | func (w wrongToken) Error() string { 70 | return fmt.Sprintf("Expected %s but got %#v", w.expected, w.got) 71 | } 72 | 73 | type jsonParser struct { 74 | decoder *json.Decoder 75 | } 76 | 77 | func (p *jsonParser) parseNext() (Value, error) { 78 | token, err := p.decoder.Token() 79 | if err != nil { 80 | return nil, err 81 | } 82 | 83 | switch token { 84 | case json.Delim('{'): 85 | return p.parseSpecialObject() 86 | case json.Delim('['): 87 | return p.parseArray() 88 | default: 89 | return p.parseLiteral(token) 90 | } 91 | } 92 | 93 | func (p *jsonParser) parseLiteral(token json.Token) (value Value, err error) { 94 | switch v := token.(type) { 95 | case string: 96 | value = StringV(v) 97 | case bool: 98 | value = BooleanV(v) 99 | case json.Number: 100 | value, err = p.parseJSONNumber(v) 101 | case nil: 102 | value = NullV{} 103 | default: 104 | err = wrongToken{"a literal", v} 105 | } 106 | 107 | return 108 | } 109 | 110 | func (p *jsonParser) parseSpecialObject() (value Value, err error) { 111 | if !p.hasMore() { 112 | value = ObjectV{} 113 | return 114 | } 115 | 116 | var firstKey string 117 | 118 | if firstKey, err = p.readString(); err == nil { 119 | switch firstKey { 120 | case "@ref": 121 | value, err = p.parseRef() 122 | case "@set": 123 | value, err = p.parseSet() 124 | case "@date": 125 | value, err = p.parseDate("2006-01-02", func(t time.Time) Value { return DateV(t) }) 126 | case "@ts": 127 | value, err = p.parseDate("2006-01-02T15:04:05.999999999Z", func(t time.Time) Value { return TimeV(t) }) 128 | case "@obj": 129 | value, err = p.readSingleObject() 130 | case "@bytes": 131 | value, err = p.parseBytes() 132 | case "@query": 133 | value, err = p.parseQuery() 134 | default: 135 | value, err = p.parseObject(firstKey) 136 | } 137 | } 138 | 139 | return 140 | } 141 | 142 | func (p *jsonParser) parseRef() (value Value, err error) { 143 | var obj ObjectV 144 | 145 | if obj, err = p.readSingleObject(); err == nil { 146 | var id string 147 | var col, db *RefV 148 | 149 | if v, ok := obj["id"]; ok { 150 | if err = v.Get(&id); err != nil { 151 | return 152 | } 153 | } 154 | 155 | if v, ok := obj["collection"]; ok { 156 | if err = v.Get(&col); err != nil { 157 | return 158 | } 159 | } 160 | 161 | if v, ok := obj["database"]; ok { 162 | if err = v.Get(&db); err != nil { 163 | return 164 | } 165 | } 166 | 167 | if col == nil && db == nil { 168 | value = nativeFromName(id) 169 | } else { 170 | value = RefV{id, col, col, db} 171 | } 172 | } 173 | 174 | return 175 | } 176 | 177 | func (p *jsonParser) parseSet() (value Value, err error) { 178 | var obj ObjectV 179 | 180 | if obj, err = p.readSingleObject(); err == nil { 181 | value = SetRefV{obj} 182 | } 183 | 184 | return 185 | } 186 | 187 | func (p *jsonParser) parseBytes() (value Value, err error) { 188 | var encoded string 189 | 190 | if encoded, err = p.readSingleString(); err == nil { 191 | bytes, err := base64.StdEncoding.DecodeString(encoded) 192 | if err == nil { 193 | value = BytesV(bytes) 194 | } 195 | } 196 | 197 | return 198 | } 199 | 200 | func (p *jsonParser) parseQuery() (value Value, err error) { 201 | var lambda json.RawMessage 202 | 203 | if err = p.decoder.Decode(&lambda); err == nil { 204 | value = QueryV{lambda} 205 | } 206 | 207 | var token json.Token 208 | if token, err = p.decoder.Token(); err == nil { 209 | if token != json.Delim('}') { 210 | err = wrongToken{"end of object", token} 211 | } 212 | } 213 | 214 | return 215 | } 216 | 217 | func (p *jsonParser) parseObject(firstKey string) (Value, error) { 218 | object := make(map[string]Value) 219 | 220 | if key := firstKey; key != "" { 221 | for { 222 | if value, err := p.parseNext(); err == nil { 223 | object[key] = value 224 | 225 | if !p.hasMore() { 226 | break 227 | } 228 | 229 | if key, err = p.readString(); err != nil { 230 | return nil, err 231 | } 232 | } else { 233 | return nil, err 234 | } 235 | } 236 | } 237 | 238 | return ObjectV(object), nil 239 | } 240 | 241 | func (p *jsonParser) parseArray() (Value, error) { 242 | var array []Value 243 | 244 | for { 245 | if !p.hasMore() { 246 | break 247 | } 248 | 249 | if value, err := p.parseNext(); err == nil { 250 | array = append(array, value) 251 | } else { 252 | return nil, err 253 | } 254 | } 255 | 256 | return ArrayV(array), nil 257 | } 258 | 259 | func (p *jsonParser) parseDate(format string, fn func(t time.Time) Value) (value Value, err error) { 260 | var str string 261 | 262 | if str, err = p.readSingleString(); err == nil { 263 | value, err = p.parseStrTime(str, format, fn) 264 | } 265 | 266 | return 267 | } 268 | 269 | func (p *jsonParser) parseStrTime(raw string, format string, fn func(time.Time) Value) (value Value, err error) { 270 | var t time.Time 271 | 272 | if t, err = time.Parse(format, raw); err == nil { 273 | value = fn(t) 274 | } 275 | 276 | return 277 | } 278 | 279 | func (p *jsonParser) parseJSONNumber(number json.Number) (Value, error) { 280 | var err error 281 | 282 | if strings.Contains(number.String(), ".") { 283 | var n float64 284 | if n, err = number.Float64(); err == nil { 285 | return DoubleV(n), nil 286 | } 287 | } else { 288 | var n int64 289 | if n, err = number.Int64(); err == nil { 290 | return LongV(n), nil 291 | } 292 | } 293 | 294 | return nil, err 295 | } 296 | 297 | func (p *jsonParser) readSingleString() (str string, err error) { 298 | if str, err = p.readString(); err == nil { 299 | err = p.ensureNoMoreTokens() 300 | } 301 | 302 | return 303 | } 304 | 305 | func (p *jsonParser) readSingleObject() (obj ObjectV, err error) { 306 | var value Value 307 | var ok bool 308 | 309 | if value, err = p.parseNext(); err == nil { 310 | if err = p.ensureNoMoreTokens(); err == nil { 311 | if obj, ok = value.(ObjectV); !ok { 312 | err = wrongToken{"a single object", value} 313 | } 314 | } 315 | } 316 | 317 | return 318 | } 319 | 320 | func (p *jsonParser) readString() (str string, err error) { 321 | var token json.Token 322 | var ok bool 323 | 324 | if token, err = p.decoder.Token(); err == nil { 325 | if str, ok = token.(string); !ok { 326 | err = wrongToken{"a string", token} 327 | } 328 | } 329 | 330 | return 331 | } 332 | 333 | func (p *jsonParser) ensureNoMoreTokens() error { 334 | if p.hasMore() { 335 | token, _ := p.decoder.Token() 336 | return wrongToken{"end of array or object", token} 337 | } 338 | 339 | return nil 340 | } 341 | 342 | func (p *jsonParser) hasMore() bool { 343 | if !p.decoder.More() { 344 | _, _ = p.decoder.Token() // Discarts next } or ] token 345 | return false 346 | } 347 | 348 | return true 349 | } 350 | -------------------------------------------------------------------------------- /faunadb/functions_datetime.go: -------------------------------------------------------------------------------- 1 | package faunadb 2 | 3 | // Time and Date 4 | 5 | // Time constructs a time from a ISO 8601 offset date/time string. 6 | // 7 | // Parameters: 8 | // str string - A string to convert to a time object. 9 | // 10 | // Returns: 11 | // time - A time object. 12 | // 13 | // See: https://app.fauna.com/documentation/reference/queryapi#time-and-date 14 | func Time(str interface{}) Expr { return timeFn{Time: wrap(str)} } 15 | 16 | type timeFn struct { 17 | fnApply 18 | Time Expr `json:"time"` 19 | } 20 | 21 | // TimeAdd returns a new time or date with the offset in terms of the unit 22 | // added. 23 | // 24 | // Parameters: 25 | // base - the base time or data 26 | // offset - the number of units 27 | // unit - the unit type 28 | // 29 | // Returns: 30 | // Expr 31 | // 32 | //See: https://docs.fauna.com/fauna/current/api/fql/functions/timeadd 33 | func TimeAdd(base interface{}, offset interface{}, unit interface{}) Expr { 34 | return timeAddFn{TimeAdd: wrap(base), Offset: wrap(offset), Unit: wrap(unit)} 35 | } 36 | 37 | type timeAddFn struct { 38 | fnApply 39 | TimeAdd Expr `json:"time_add"` 40 | Offset Expr `json:"offset"` 41 | Unit Expr `json:"unit"` 42 | } 43 | 44 | // TimeSubtract returns a new time or date with the offset in terms of the unit 45 | // subtracted. 46 | // 47 | // Parameters: 48 | // base - the base time or data 49 | // offset - the number of units 50 | // unit - the unit type 51 | // 52 | // Returns: 53 | // Expr 54 | // 55 | //See: https://docs.fauna.com/fauna/current/api/fql/functions/timesubtract 56 | func TimeSubtract(base interface{}, offset interface{}, unit interface{}) Expr { 57 | return timeSubtractFn{TimeSubtract: wrap(base), Offset: wrap(offset), Unit: wrap(unit)} 58 | } 59 | 60 | type timeSubtractFn struct { 61 | fnApply 62 | TimeSubtract Expr `json:"time_subtract"` 63 | Offset Expr `json:"offset"` 64 | Unit Expr `json:"unit"` 65 | } 66 | 67 | // TimeDiff returns the number of intervals in terms of the unit between 68 | // two times or dates. Both start and finish must be of the same 69 | // type. 70 | // 71 | // Parameters: 72 | // start the starting time or date, inclusive 73 | // finish the ending time or date, exclusive 74 | // unit the unit type// 75 | // Returns: 76 | // Expr 77 | // 78 | //See: https://docs.fauna.com/fauna/current/api/fql/functions/timediff 79 | func TimeDiff(start interface{}, finish interface{}, unit interface{}) Expr { 80 | return timeDiffFn{TimeDiff: wrap(start), Other: wrap(finish), Unit: wrap(unit)} 81 | } 82 | 83 | type timeDiffFn struct { 84 | fnApply 85 | TimeDiff Expr `json:"time_diff"` 86 | Other Expr `json:"other"` 87 | Unit Expr `json:"unit"` 88 | } 89 | 90 | // Date constructs a date from a ISO 8601 offset date/time string. 91 | // 92 | // Parameters: 93 | // str string - A string to convert to a date object. 94 | // 95 | // Returns: 96 | // date - A date object. 97 | // 98 | // See: https://app.fauna.com/documentation/reference/queryapi#time-and-date 99 | func Date(str interface{}) Expr { return dateFn{Date: wrap(str)} } 100 | 101 | type dateFn struct { 102 | fnApply 103 | Date Expr `json:"date"` 104 | } 105 | 106 | // Epoch constructs a time relative to the epoch "1970-01-01T00:00:00Z". 107 | // 108 | // Parameters: 109 | // num int64 - The number of units from Epoch. 110 | // unit string - The unit of number. One of TimeUnitSecond, TimeUnitMillisecond, TimeUnitMicrosecond, TimeUnitNanosecond. 111 | // 112 | // Returns: 113 | // time - A time object. 114 | // 115 | // See: https://app.fauna.com/documentation/reference/queryapi#time-and-date 116 | func Epoch(num, unit interface{}) Expr { return epochFn{Epoch: wrap(num), Unit: wrap(unit)} } 117 | 118 | type epochFn struct { 119 | fnApply 120 | Epoch Expr `json:"epoch"` 121 | Unit Expr `json:"unit"` 122 | } 123 | 124 | // Now returns the current snapshot time. 125 | // 126 | // Returns: 127 | // Expr 128 | // 129 | // See: https://docs.fauna.com/fauna/current/api/fql/functions/now 130 | func Now() Expr { 131 | return nowFn{Now: NullV{}} 132 | } 133 | 134 | type nowFn struct { 135 | fnApply 136 | Now Expr `json:"now" faunarepr:"noargs"` 137 | } 138 | 139 | // ToSeconds converts a time expression to seconds since the UNIX epoch. 140 | // 141 | // Parameters: 142 | // value Object - The expression to convert. 143 | // 144 | // Returns: 145 | // time - A time literal. 146 | func ToSeconds(value interface{}) Expr { 147 | return toSecondsFn{ToSeconds: wrap(value)} 148 | } 149 | 150 | type toSecondsFn struct { 151 | fnApply 152 | ToSeconds Expr `json:"to_seconds"` 153 | } 154 | 155 | // ToMillis converts a time expression to milliseconds since the UNIX epoch. 156 | // 157 | // Parameters: 158 | // value Object - The expression to convert. 159 | // 160 | // Returns: 161 | // time - A time literal. 162 | func ToMillis(value interface{}) Expr { 163 | return toMillisFn{ToMillis: wrap(value)} 164 | } 165 | 166 | type toMillisFn struct { 167 | fnApply 168 | ToMillis Expr `json:"to_millis"` 169 | } 170 | 171 | // ToMicros converts a time expression to microseconds since the UNIX epoch. 172 | // 173 | // Parameters: 174 | // value Object - The expression to convert. 175 | // 176 | // Returns: 177 | // time - A time literal. 178 | func ToMicros(value interface{}) Expr { 179 | return toMicrosFn{ToMicros: wrap(value)} 180 | } 181 | 182 | type toMicrosFn struct { 183 | fnApply 184 | ToMicros Expr `json:"to_micros"` 185 | } 186 | 187 | // Year returns the time expression's year, following the ISO-8601 standard. 188 | // 189 | // Parameters: 190 | // value Object - The expression to convert. 191 | // 192 | // Returns: 193 | // time - year. 194 | func Year(value interface{}) Expr { 195 | return yearFn{Year: wrap(value)} 196 | } 197 | 198 | type yearFn struct { 199 | fnApply 200 | Year Expr `json:"year"` 201 | } 202 | 203 | // Month returns a time expression's month of the year, from 1 to 12. 204 | // 205 | // Parameters: 206 | // value Object - The expression to convert. 207 | // 208 | // Returns: 209 | // time - Month. 210 | func Month(value interface{}) Expr { 211 | return monthFn{Month: wrap(value)} 212 | } 213 | 214 | type monthFn struct { 215 | fnApply 216 | Month Expr `json:"month"` 217 | } 218 | 219 | // Hour returns a time expression's hour of the day, from 0 to 23. 220 | // 221 | // Parameters: 222 | // value Object - The expression to convert. 223 | // 224 | // Returns: 225 | // time - year. 226 | func Hour(value interface{}) Expr { 227 | return hourFn{Hour: wrap(value)} 228 | } 229 | 230 | type hourFn struct { 231 | fnApply 232 | Hour Expr `json:"hour"` 233 | } 234 | 235 | // Minute returns a time expression's minute of the hour, from 0 to 59. 236 | // 237 | // Parameters: 238 | // value Object - The expression to convert. 239 | // 240 | // Returns: 241 | // time - year. 242 | func Minute(value interface{}) Expr { 243 | return minuteFn{Minute: wrap(value)} 244 | } 245 | 246 | type minuteFn struct { 247 | fnApply 248 | Minute Expr `json:"minute"` 249 | } 250 | 251 | // Second returns a time expression's second of the minute, from 0 to 59. 252 | // 253 | // Parameters: 254 | // value Object - The expression to convert. 255 | // 256 | // Returns: 257 | // time - year. 258 | func Second(value interface{}) Expr { 259 | return secondFn{Second: wrap(value)} 260 | } 261 | 262 | type secondFn struct { 263 | fnApply 264 | Second Expr `json:"second"` 265 | } 266 | 267 | // DayOfMonth returns a time expression's day of the month, from 1 to 31. 268 | // 269 | // Parameters: 270 | // value Object - The expression to convert. 271 | // 272 | // Returns: 273 | // time - day of month. 274 | func DayOfMonth(value interface{}) Expr { 275 | return dayOfMonthFn{DayOfMonth: wrap(value)} 276 | } 277 | 278 | type dayOfMonthFn struct { 279 | fnApply 280 | DayOfMonth Expr `json:"day_of_month"` 281 | } 282 | 283 | // DayOfWeek returns a time expression's day of the week following ISO-8601 convention, from 1 (Monday) to 7 (Sunday). 284 | // 285 | // Parameters: 286 | // value Object - The expression to convert. 287 | // 288 | // Returns: 289 | // time - day of week. 290 | func DayOfWeek(value interface{}) Expr { 291 | return dayOfWeekFn{DayOfWeek: wrap(value)} 292 | } 293 | 294 | type dayOfWeekFn struct { 295 | fnApply 296 | DayOfWeek Expr `json:"day_of_week"` 297 | } 298 | 299 | // DayOfYear eturns a time expression's day of the year, from 1 to 365, or 366 in a leap year. 300 | // 301 | // Parameters: 302 | // value Object - The expression to convert. 303 | // 304 | // Returns: 305 | // time - Day of the year. 306 | func DayOfYear(value interface{}) Expr { 307 | return dayOfYearFn{DayOfYear: wrap(value)} 308 | } 309 | 310 | type dayOfYearFn struct { 311 | fnApply 312 | DayOfYear Expr `json:"day_of_year"` 313 | } 314 | -------------------------------------------------------------------------------- /faunadb/functions_write.go: -------------------------------------------------------------------------------- 1 | package faunadb 2 | 3 | // Write 4 | 5 | // Create creates an document of the specified collection. 6 | // 7 | // Parameters: 8 | // ref Ref - A collection reference. 9 | // params Object - An object with attributes of the document created. 10 | // 11 | // Returns: 12 | // Object - A new document of the collection referenced. 13 | // 14 | // See: https://app.fauna.com/documentation/reference/queryapi#write-functions 15 | func Create(ref, params interface{}) Expr { return createFn{Create: wrap(ref), Params: wrap(params)} } 16 | 17 | type createFn struct { 18 | fnApply 19 | Create Expr `json:"create"` 20 | Params Expr `json:"params"` 21 | } 22 | 23 | // CreateClass creates a new class. 24 | // 25 | // Parameters: 26 | // params Object - An object with attributes of the class. 27 | // 28 | // Deprecated: Use CreateCollection instead, CreateClass is kept for backwards compatibility 29 | // 30 | // Returns: 31 | // Object - The new created class object. 32 | // 33 | // See: https://app.fauna.com/documentation/reference/queryapi#write-functions 34 | func CreateClass(params interface{}) Expr { return createClassFn{CreateClass: wrap(params)} } 35 | 36 | type createClassFn struct { 37 | fnApply 38 | CreateClass Expr `json:"create_class"` 39 | } 40 | 41 | // CreateCollection creates a new collection. 42 | // 43 | // Parameters: 44 | // params Object - An object with attributes of the collection. 45 | // 46 | // Returns: 47 | // Object - The new created collection object. 48 | // 49 | // See: https://app.fauna.com/documentation/reference/queryapi#write-functions 50 | func CreateCollection(params interface{}) Expr { 51 | return createCollectionFn{CreateCollection: wrap(params)} 52 | } 53 | 54 | type createCollectionFn struct { 55 | fnApply 56 | CreateCollection Expr `json:"create_collection"` 57 | } 58 | 59 | // CreateDatabase creates an new database. 60 | // 61 | // Parameters: 62 | // params Object - An object with attributes of the database. 63 | // 64 | // Returns: 65 | // Object - The new created database object. 66 | // 67 | // See: https://app.fauna.com/documentation/reference/queryapi#write-functions 68 | func CreateDatabase(params interface{}) Expr { return createDatabaseFn{CreateDatabase: wrap(params)} } 69 | 70 | type createDatabaseFn struct { 71 | fnApply 72 | CreateDatabase Expr `json:"create_database"` 73 | } 74 | 75 | // CreateIndex creates a new index. 76 | // 77 | // Parameters: 78 | // params Object - An object with attributes of the index. 79 | // 80 | // Returns: 81 | // Object - The new created index object. 82 | // 83 | // See: https://app.fauna.com/documentation/reference/queryapi#write-functions 84 | func CreateIndex(params interface{}) Expr { return createIndexFn{CreateIndex: wrap(params)} } 85 | 86 | type createIndexFn struct { 87 | fnApply 88 | CreateIndex Expr `json:"create_index"` 89 | } 90 | 91 | // CreateKey creates a new key. 92 | // 93 | // Parameters: 94 | // params Object - An object with attributes of the key. 95 | // 96 | // Returns: 97 | // Object - The new created key object. 98 | // 99 | // See: https://app.fauna.com/documentation/reference/queryapi#write-functions 100 | func CreateKey(params interface{}) Expr { return createKeyFn{CreateKey: wrap(params)} } 101 | 102 | type createKeyFn struct { 103 | fnApply 104 | CreateKey Expr `json:"create_key"` 105 | } 106 | 107 | // CreateFunction creates a new function. 108 | // 109 | // Parameters: 110 | // params Object - An object with attributes of the function. 111 | // 112 | // Returns: 113 | // Object - The new created function object. 114 | // 115 | // See: https://app.fauna.com/documentation/reference/queryapi#write-functions 116 | func CreateFunction(params interface{}) Expr { return createFunctionFn{CreateFunction: wrap(params)} } 117 | 118 | type createFunctionFn struct { 119 | fnApply 120 | CreateFunction Expr `json:"create_function"` 121 | } 122 | 123 | // CreateRole creates a new role. 124 | // 125 | // Parameters: 126 | // params Object - An object with attributes of the role. 127 | // 128 | // Returns: 129 | // Object - The new created role object. 130 | // 131 | // See: https://app.fauna.com/documentation/reference/queryapi#write-functions 132 | func CreateRole(params interface{}) Expr { return createRoleFn{CreateRole: wrap(params)} } 133 | 134 | type createRoleFn struct { 135 | fnApply 136 | CreateRole Expr `json:"create_role"` 137 | } 138 | 139 | // MoveDatabase moves a database to a new hierachy. 140 | // 141 | // Parameters: 142 | // from Object - Source reference to be moved. 143 | // to Object - New parent database reference. 144 | // 145 | // Returns: 146 | // Object - instance. 147 | // 148 | // See: https://app.fauna.com/documentation/reference/queryapi#write-functions 149 | func MoveDatabase(from interface{}, to interface{}) Expr { 150 | return moveDatabaseFn{MoveDatabase: wrap(from), To: wrap(to)} 151 | } 152 | 153 | type moveDatabaseFn struct { 154 | fnApply 155 | MoveDatabase Expr `json:"move_database"` 156 | To Expr `json:"to"` 157 | } 158 | 159 | // Update updates the provided document. 160 | // 161 | // Parameters: 162 | // ref Ref - The reference to update. 163 | // params Object - An object representing the parameters of the document. 164 | // 165 | // Returns: 166 | // Object - The updated object. 167 | // 168 | // See: https://app.fauna.com/documentation/reference/queryapi#write-functions 169 | func Update(ref, params interface{}) Expr { return updateFn{Update: wrap(ref), Params: wrap(params)} } 170 | 171 | type updateFn struct { 172 | fnApply 173 | Update Expr `json:"update"` 174 | Params Expr `json:"params"` 175 | } 176 | 177 | // Replace replaces the provided document. 178 | // 179 | // Parameters: 180 | // ref Ref - The reference to replace. 181 | // params Object - An object representing the parameters of the document. 182 | // 183 | // Returns: 184 | // Object - The replaced object. 185 | // 186 | // See: https://app.fauna.com/documentation/reference/queryapi#write-functions 187 | func Replace(ref, params interface{}) Expr { 188 | return replaceFn{Replace: wrap(ref), Params: wrap(params)} 189 | } 190 | 191 | type replaceFn struct { 192 | fnApply 193 | Replace Expr `json:"replace"` 194 | Params Expr `json:"params"` 195 | } 196 | 197 | // Delete deletes the provided document. 198 | // 199 | // Parameters: 200 | // ref Ref - The reference to delete. 201 | // 202 | // Returns: 203 | // Object - The deleted object. 204 | // 205 | // See: https://app.fauna.com/documentation/reference/queryapi#write-functions 206 | func Delete(ref interface{}) Expr { return deleteFn{Delete: wrap(ref)} } 207 | 208 | type deleteFn struct { 209 | fnApply 210 | Delete Expr `json:"delete"` 211 | } 212 | 213 | // Insert adds an event to the provided document's history. 214 | // 215 | // Parameters: 216 | // ref Ref - The reference to insert against. 217 | // ts time - The valid time of the inserted event. 218 | // action string - Whether the event shoulde be a ActionCreate, ActionUpdate or ActionDelete. 219 | // 220 | // Returns: 221 | // Object - The deleted object. 222 | // 223 | // See: https://app.fauna.com/documentation/reference/queryapi#write-functions 224 | func Insert(ref, ts, action, params interface{}) Expr { 225 | return insertFn{Insert: wrap(ref), TS: wrap(ts), Action: wrap(action), Params: wrap(params)} 226 | } 227 | 228 | type insertFn struct { 229 | fnApply 230 | Insert Expr `json:"insert"` 231 | TS Expr `json:"ts"` 232 | Action Expr `json:"action"` 233 | Params Expr `json:"params"` 234 | } 235 | 236 | // Remove deletes an event from the provided document's history. 237 | // 238 | // Parameters: 239 | // ref Ref - The reference of the document whose event should be removed. 240 | // ts time - The valid time of the inserted event. 241 | // action string - The event action (ActionCreate, ActionUpdate or ActionDelete) that should be removed. 242 | // 243 | // Returns: 244 | // Object - The deleted object. 245 | // 246 | // See: https://app.fauna.com/documentation/reference/queryapi#write-functions 247 | func Remove(ref, ts, action interface{}) Expr { 248 | return removeFn{Remove: wrap(ref), Ts: wrap(ts), Action: wrap(action)} 249 | } 250 | 251 | type removeFn struct { 252 | fnApply 253 | Remove Expr `json:"remove"` 254 | Ts Expr `json:"ts"` 255 | Action Expr `json:"action"` 256 | } 257 | 258 | // CreateAccessProvider creates a new AccessProvider 259 | // 260 | // Parameters: 261 | // params Object - An object of parameters used to create a new access provider. 262 | // - name: A valid schema name 263 | // - issuer: A unique string 264 | // - jwks_uri: A valid HTTPS URL 265 | // - roles: An optional list of Role refs 266 | // - data: An optional user-defined metadata for the AccessProvider 267 | // 268 | // Returns: 269 | // Object - The new created access provider. 270 | // 271 | // See: the [docs](https://app.fauna.com/documentation/reference/queryapi#write-functions). 272 | // 273 | func CreateAccessProvider(params interface{}) Expr { 274 | return createAccessProviderFn{CreateAccessProvider: wrap(params)} 275 | } 276 | 277 | type createAccessProviderFn struct { 278 | fnApply 279 | CreateAccessProvider Expr `json:"create_access_provider"` 280 | } 281 | -------------------------------------------------------------------------------- /faunadb/stream_test.go: -------------------------------------------------------------------------------- 1 | package faunadb_test 2 | 3 | import ( 4 | "io" 5 | "strconv" 6 | "sync" 7 | "testing" 8 | "time" 9 | 10 | "github.com/stretchr/testify/suite" 11 | 12 | f "github.com/fauna/faunadb-go/v4/faunadb" 13 | ) 14 | 15 | func TestRunStreamTests(t *testing.T) { 16 | suite.Run(t, new(StreamsTestSuite)) 17 | } 18 | 19 | type StreamsTestSuite struct { 20 | suite.Suite 21 | client *f.FaunaClient 22 | } 23 | 24 | var ( 25 | streamCollection f.RefV 26 | ) 27 | 28 | type counterMutex struct { 29 | mu sync.Mutex 30 | i int64 31 | } 32 | 33 | func (c *counterMutex) Inc() { 34 | c.mu.Lock() 35 | defer c.mu.Unlock() 36 | c.i = c.i + 1 37 | } 38 | 39 | func (c *counterMutex) Value() int64 { 40 | c.mu.Lock() 41 | defer c.mu.Unlock() 42 | return c.i 43 | } 44 | 45 | func (s *StreamsTestSuite) TestStreamDocumentRef() { 46 | var subscription f.StreamSubscription 47 | 48 | ref := s.createDocument() 49 | 50 | subscription = s.client.Stream(ref) 51 | subscription.Start() 52 | for evt := range subscription.StreamEvents() { 53 | switch evt.Type() { 54 | case f.StartEventT: 55 | s.client.Query(f.Update(ref, f.Obj{"data": f.Obj{"x": time.Now().String()}})) 56 | case f.VersionEventT: 57 | s.Equal(evt.Type(), f.VersionEventT) 58 | subscription.Close() 59 | case f.ErrorEventT: 60 | s.defaultStreamError(evt) 61 | } 62 | } 63 | } 64 | 65 | func (s *StreamsTestSuite) TestRejectNonReadOnlyQuery() { 66 | query := f.CreateCollection(f.Obj{"name": "collection"}) 67 | sub := s.client.Stream(query) 68 | err := sub.Start() 69 | s.EqualError(err, "Response error 400. Errors: [](invalid expression): Call performs a write, which is not allowed in stream requests., details: []") 70 | } 71 | 72 | func (s *StreamsTestSuite) TestSelectFields() { 73 | ref := s.createDocument() 74 | 75 | subscription := s.client.Stream(ref, f.Fields("diff", "prev", "document")) 76 | subscription.Start() 77 | for evt := range subscription.StreamEvents() { 78 | switch evt.Type() { 79 | case f.StartEventT: 80 | s.Equal(f.StartEventT, evt.Type()) 81 | s.NotZero(evt.Txn()) 82 | e := evt.(f.StartEvent) 83 | s.NotNil(e.Event()) 84 | s.client.Query(f.Update(ref, f.Obj{"data": f.Obj{"x": time.Now().String()}})) 85 | case f.VersionEventT: 86 | s.Equal(f.VersionEventT, evt.Type()) 87 | s.NotZero(evt.Txn()) 88 | evt := evt.(f.VersionEvent) 89 | body := evt.Event() 90 | s.NotNil(body) 91 | 92 | s.True(s.keyInMap("diff", body.(f.ObjectV))) 93 | s.True(s.keyInMap("prev", body.(f.ObjectV))) 94 | s.True(s.keyInMap("document", body.(f.ObjectV))) 95 | s.False(s.keyInMap("action", body.(f.ObjectV))) 96 | 97 | subscription.Close() 98 | case f.ErrorEventT: 99 | s.defaultStreamError(evt) 100 | } 101 | } 102 | } 103 | 104 | func (s *StreamsTestSuite) TestUpdateLastTxnTime() { 105 | ref := s.createDocument() 106 | lastTxnTime := s.client.GetLastTxnTime() 107 | 108 | subscription := s.client.Stream(ref) 109 | subscription.Start() 110 | for evt := range subscription.StreamEvents() { 111 | switch evt.Type() { 112 | case f.StartEventT: 113 | s.Equal(f.StartEventT, evt.Type()) 114 | s.NotZero(evt.Txn()) 115 | e := evt.(f.StartEvent) 116 | s.NotNil(e.Event()) 117 | 118 | s.Greater(s.client.GetLastTxnTime(), lastTxnTime) 119 | s.GreaterOrEqual(s.client.GetLastTxnTime(), e.Txn()) 120 | 121 | s.client.Query(f.Update(ref, f.Obj{"data": f.Obj{"x": time.Now().String()}})) 122 | 123 | case f.VersionEventT: 124 | s.Equal(f.VersionEventT, evt.Type()) 125 | 126 | s.NotZero(evt.Txn()) 127 | s.Equal(evt.Txn(), s.client.GetLastTxnTime()) 128 | 129 | subscription.Close() 130 | case f.ErrorEventT: 131 | s.defaultStreamError(evt) 132 | } 133 | } 134 | } 135 | 136 | func (s *StreamsTestSuite) TestHandleBadQuery() { 137 | query := f.StringV("just a boring string") 138 | 139 | sub := s.client.Stream(query) 140 | err := sub.Start() 141 | s.EqualError(err, "Response error 400. Errors: [](invalid argument): Expected a Document Ref or Version, or a Set Ref, got String., details: []") 142 | 143 | } 144 | 145 | func (s *StreamsTestSuite) TestStartActiveStream() { 146 | query := s.createDocument() 147 | 148 | sub := s.client.Stream(query) 149 | sub.Start() 150 | for evt := range sub.StreamEvents() { 151 | switch evt.Type() { 152 | case f.StartEventT: 153 | s.Require().Equal(f.StreamConnActive, sub.Status()) 154 | s.Require().EqualError(sub.Start(), "stream subscription already started") 155 | sub.Close() 156 | case f.ErrorEventT: 157 | s.defaultStreamError(evt) 158 | } 159 | } 160 | s.Equal(f.StreamConnClosed, sub.Status()) 161 | } 162 | 163 | func (s *StreamsTestSuite) TestAuthRevalidation() { 164 | ref := s.createDocument() 165 | 166 | serverKey, err := f.CreateKeyWithRole("server") 167 | s.Require().NoError(err) 168 | var secret string 169 | var serverKeyRef f.RefV 170 | serverKey.At(f.ObjKey("secret")).Get(&secret) 171 | serverKey.At(f.ObjKey("ref")).Get(&serverKeyRef) 172 | client := s.client.NewSessionClient(secret) 173 | 174 | subscription := client.Stream(ref) 175 | subscription.Start() 176 | for evt := range subscription.StreamEvents() { 177 | switch evt.Type() { 178 | case f.StartEventT: 179 | s.Equal(f.StartEventT, evt.Type()) 180 | s.NotZero(evt.Txn()) 181 | e := evt.(f.StartEvent) 182 | s.NotNil(e.Event()) 183 | 184 | _, err := f.AdminQuery(f.Delete(serverKeyRef)) 185 | s.Require().NoError(err) 186 | 187 | s.client.Query(f.Update(ref, f.Obj{"data": f.Obj{"x": time.Now().String()}})) 188 | case f.ErrorEventT: 189 | evt := evt.(f.ErrorEvent) 190 | if evt.Error() == io.EOF { 191 | return 192 | } 193 | s.EqualError(evt.Error(), `stream_error: code='permission denied' description='Authorization lost during stream evaluation.'`) 194 | subscription.Close() 195 | } 196 | } 197 | } 198 | 199 | func (s *StreamsTestSuite) TestListenToLargeEvents() { 200 | var subscription f.StreamSubscription 201 | 202 | type Val struct { 203 | Value string 204 | } 205 | var arr []Val 206 | 207 | for i := 0; i < 10; i++ { 208 | arr = append(arr, Val{Value: strconv.Itoa(i)}) 209 | } 210 | 211 | ref := s.createDocument() 212 | subscription = s.client.Stream(ref) 213 | 214 | subscription.Start() 215 | 216 | for evt := range subscription.StreamEvents() { 217 | switch evt.Type() { 218 | 219 | case f.StartEventT: 220 | s.Require().Equal(f.StreamConnActive, subscription.Status()) 221 | s.Require().EqualError(subscription.Start(), "stream subscription already started") 222 | s.client.Query(f.Update(&ref, f.Obj{"data": f.Obj{"values": arr}})) 223 | 224 | case f.VersionEventT: 225 | s.Equal(f.VersionEventT, evt.Type()) 226 | e := evt.(f.VersionEvent) 227 | var expected []Val 228 | e.Event().At(f.ObjKey("document", "data", "values")).Get(&expected) 229 | s.Equal(expected, arr) 230 | subscription.Close() 231 | } 232 | } 233 | } 234 | 235 | func (s *StreamsTestSuite) TestListenToSetEvents() { 236 | var ( 237 | expected, actual f.Value 238 | action string 239 | ) 240 | 241 | subscription := s.client.Stream(f.Documents(streamCollection)) 242 | s.Require().NoError(subscription.Start()) 243 | 244 | for evt := range subscription.StreamEvents() { 245 | switch evt.Type() { 246 | case f.StartEventT: 247 | expected = s.createDocument() 248 | 249 | case f.SetEventT: 250 | e := evt.(f.SetEvent) 251 | e.Event().At(f.ObjKey("document", "ref")).Get(&actual) 252 | e.Event().At(f.ObjKey("action")).Get(&action) 253 | s.Equal(actual, expected) 254 | s.Equal(action, "add") 255 | subscription.Close() 256 | } 257 | } 258 | } 259 | 260 | func (s *StreamsTestSuite) defaultStreamError(evt f.StreamEvent) { 261 | s.Equal(f.ErrorEventT, evt.Type()) 262 | s.NotZero(evt.Txn()) 263 | e := evt.(f.ErrorEvent) 264 | s.FailNow(e.Error().Error()) 265 | } 266 | 267 | func (s *StreamsTestSuite) keyInMap(key string, m f.ObjectV) (ok bool) { 268 | _, ok = m[key] 269 | return 270 | } 271 | 272 | func (s *StreamsTestSuite) SetupSuite() { 273 | client, err := f.SetupTestDB() 274 | s.Require().NoError(err) 275 | 276 | s.client = client 277 | s.setupSchema() 278 | } 279 | 280 | func (s *StreamsTestSuite) setupSchema() { 281 | val := s.query( 282 | f.CreateCollection(f.Obj{"name": "streams_collection"}), 283 | ) 284 | val.At(refField).Get(&streamCollection) 285 | } 286 | 287 | func (s *StreamsTestSuite) TearDownSuite() { 288 | f.DeleteTestDB() 289 | } 290 | 291 | func (s *StreamsTestSuite) query(expr f.Expr) f.Value { 292 | value, err := s.client.Query(expr) 293 | s.Require().NoError(err) 294 | 295 | return value 296 | } 297 | 298 | func (s *StreamsTestSuite) createDocument(data ...interface{}) (ref f.RefV) { 299 | value, err := s.client.Query(f.Create(streamCollection, f.Obj{"data": f.Obj{"v": data}})) 300 | s.Require().NoError(err) 301 | value.At(refField).Get(&ref) 302 | 303 | return 304 | } 305 | 306 | func (s *StreamsTestSuite) queryAndDecode(expr f.Expr, i interface{}) { 307 | value := s.query(expr) 308 | s.Require().NoError(value.Get(i)) 309 | } 310 | 311 | func (s *StreamsTestSuite) adminQueryAndDecode(expr f.Expr, i interface{}) { 312 | value := s.adminQuery(expr) 313 | s.Require().NoError(value.Get(i)) 314 | } 315 | 316 | func (s *StreamsTestSuite) adminQuery(expr f.Expr) (value f.Value) { 317 | value, err := f.AdminQuery(expr) 318 | s.Require().NoError(err) 319 | 320 | return 321 | } 322 | -------------------------------------------------------------------------------- /faunadb/functions_collections.go: -------------------------------------------------------------------------------- 1 | package faunadb 2 | 3 | // Collections 4 | 5 | // Map applies the lambda expression on each element of a collection or Page. 6 | // It returns the result of each application on a collection of the same type. 7 | // 8 | // Parameters: 9 | // coll []Value - The collection of elements to iterate. 10 | // lambda Lambda - A lambda function to be applied to each element of the collection. See Lambda() function. 11 | // 12 | // Returns: 13 | // []Value - A new collection with elements transformed by the lambda function. 14 | // 15 | // See: https://app.fauna.com/documentation/reference/queryapi#collections 16 | func Map(coll, lambda interface{}) Expr { return mapFn{Map: wrap(lambda), Collection: wrap(coll)} } 17 | 18 | type mapFn struct { 19 | fnApply 20 | Map Expr `json:"map"` 21 | Collection Expr `json:"collection"` 22 | } 23 | 24 | // Foreach applies the lambda expression on each element of a collection or Page. 25 | // The original collection is returned. 26 | // 27 | // Parameters: 28 | // coll []Value - The collection of elements to iterate. 29 | // lambda Lambda - A lambda function to be applied to each element of the collection. See Lambda() function. 30 | // 31 | // Returns: 32 | // []Value - The original collection. 33 | // 34 | // See: https://app.fauna.com/documentation/reference/queryapi#collections 35 | func Foreach(coll, lambda interface{}) Expr { 36 | return foreachFn{Foreach: wrap(lambda), Collection: wrap(coll)} 37 | } 38 | 39 | type foreachFn struct { 40 | fnApply 41 | Foreach Expr `json:"foreach"` 42 | Collection Expr `json:"collection"` 43 | } 44 | 45 | // Filter applies the lambda expression on each element of a collection or Page. 46 | // It returns a new collection of the same type containing only the elements in which the 47 | // function application returned true. 48 | // 49 | // Parameters: 50 | // coll []Value - The collection of elements to iterate. 51 | // lambda Lambda - A lambda function to be applied to each element of the collection. The lambda function must return a boolean value. See Lambda() function. 52 | // 53 | // Returns: 54 | // []Value - A new collection. 55 | // 56 | // See: https://app.fauna.com/documentation/reference/queryapi#collections 57 | func Filter(coll, lambda interface{}) Expr { 58 | return filterFn{Filter: wrap(lambda), Collection: wrap(coll)} 59 | } 60 | 61 | type filterFn struct { 62 | fnApply 63 | Filter Expr `json:"filter"` 64 | Collection Expr `json:"collection"` 65 | } 66 | 67 | // Take returns a new collection containing num elements from the head of the original collection. 68 | // 69 | // Parameters: 70 | // num int64 - The number of elements to take from the collection. 71 | // coll []Value - The collection of elements. 72 | // 73 | // Returns: 74 | // []Value - A new collection. 75 | // 76 | // See: https://app.fauna.com/documentation/reference/queryapi#collections 77 | func Take(num, coll interface{}) Expr { return takeFn{Take: wrap(num), Collection: wrap(coll)} } 78 | 79 | type takeFn struct { 80 | fnApply 81 | Take Expr `json:"take"` 82 | Collection Expr `json:"collection"` 83 | } 84 | 85 | // Drop returns a new collection containing the remaining elements from the original collection 86 | // after num elements have been removed. 87 | // 88 | // Parameters: 89 | // num int64 - The number of elements to drop from the collection. 90 | // coll []Value - The collection of elements. 91 | // 92 | // Returns: 93 | // []Value - A new collection. 94 | // 95 | // See: https://app.fauna.com/documentation/reference/queryapi#collections 96 | func Drop(num, coll interface{}) Expr { return dropFn{Drop: wrap(num), Collection: wrap(coll)} } 97 | 98 | type dropFn struct { 99 | fnApply 100 | Drop Expr `json:"drop"` 101 | Collection Expr `json:"collection"` 102 | } 103 | 104 | // Prepend returns a new collection that is the result of prepending elems to coll. 105 | // 106 | // Parameters: 107 | // elems []Value - Elements to add to the beginning of the other collection. 108 | // coll []Value - The collection of elements. 109 | // 110 | // Returns: 111 | // []Value - A new collection. 112 | // 113 | // See: https://app.fauna.com/documentation/reference/queryapi#collections 114 | func Prepend(elems, coll interface{}) Expr { 115 | return prependFn{Prepend: wrap(elems), Collection: wrap(coll)} 116 | } 117 | 118 | type prependFn struct { 119 | fnApply 120 | Prepend Expr `json:"prepend"` 121 | Collection Expr `json:"collection"` 122 | } 123 | 124 | // Append returns a new collection that is the result of appending elems to coll. 125 | // 126 | // Parameters: 127 | // elems []Value - Elements to add to the end of the other collection. 128 | // coll []Value - The collection of elements. 129 | // 130 | // Returns: 131 | // []Value - A new collection. 132 | // 133 | // See: https://app.fauna.com/documentation/reference/queryapi#collections 134 | func Append(elems, coll interface{}) Expr { 135 | return appendFn{Append: wrap(elems), Collection: wrap(coll)} 136 | } 137 | 138 | type appendFn struct { 139 | fnApply 140 | Append Expr `json:"append"` 141 | Collection Expr `json:"collection"` 142 | } 143 | 144 | // IsEmpty returns true if the collection is the empty set, else false. 145 | // 146 | // Parameters: 147 | // coll []Value - The collection of elements. 148 | // 149 | // Returns: 150 | // bool - True if the collection is empty, else false. 151 | // 152 | // See: https://app.fauna.com/documentation/reference/queryapi#collections 153 | func IsEmpty(coll interface{}) Expr { return isEmptyFn{IsEmpty: wrap(coll)} } 154 | 155 | type isEmptyFn struct { 156 | fnApply 157 | IsEmpty Expr `json:"is_empty"` 158 | } 159 | 160 | // IsNonEmpty returns false if the collection is the empty set, else true 161 | // 162 | // Parameters: 163 | // coll []Value - The collection of elements. 164 | // 165 | // Returns: 166 | // bool - True if the collection is not empty, else false. 167 | // 168 | // See: https://app.fauna.com/documentation/reference/queryapi#collections 169 | func IsNonEmpty(coll interface{}) Expr { return isNonEmptyFn{IsNonEmpty: wrap(coll)} } 170 | 171 | type isNonEmptyFn struct { 172 | fnApply 173 | IsNonEmpty Expr `json:"is_nonempty"` 174 | } 175 | 176 | // Contains checks if the provided value contains the path specified. 177 | // 178 | // Parameters: 179 | // path Path - An array representing a path to check for the existence of. Path can be either strings or ints. 180 | // value Object - An object to search against. 181 | // 182 | // Returns: 183 | // bool - true if the path contains any value, false otherwise. 184 | // 185 | // Deprecated: Use ContainsPath instead. Contains will be removed in API v4. 186 | // 187 | // See: https://app.fauna.com/documentation/reference/queryapi#miscellaneous-functions 188 | func Contains(path, value interface{}) Expr { 189 | return containsFn{Contains: wrap(path), Value: wrap(value)} 190 | } 191 | 192 | type containsFn struct { 193 | fnApply 194 | Contains Expr `json:"contains"` 195 | Value Expr `json:"in"` 196 | } 197 | 198 | // ContainsPath checks if the provided value contains the path specified. 199 | // 200 | // Parameters: 201 | // path Path - An array representing a path to check for the existence of. Path can be either strings or ints. 202 | // value Object - Value to search against. 203 | // 204 | // Returns: 205 | // bool - true if the path contains any value, false otherwise. 206 | // 207 | // See: https://app.fauna.com/documentation/reference/queryapi#miscellaneous-functions 208 | func ContainsPath(path, value interface{}) Expr { 209 | return containsPathFn{ContainsPath: wrap(path), Value: wrap(value)} 210 | } 211 | 212 | type containsPathFn struct { 213 | fnApply 214 | ContainsPath Expr `json:"contains_path"` 215 | Value Expr `json:"in"` 216 | } 217 | 218 | // ContainsValue checks if the provided value contains the value specified. 219 | // 220 | // Parameters: 221 | // value Expr - Value to check for the existence of. 222 | // in Expr - An object/array/page/ref to search against. 223 | // 224 | // Returns: 225 | // bool - true if the value is found, false otherwise. 226 | // 227 | // See: https://app.fauna.com/documentation/reference/queryapi#miscellaneous-functions 228 | func ContainsValue(value, in interface{}) Expr { 229 | return containsValueFn{ContainsValue: wrap(value), Value: wrap(in)} 230 | } 231 | 232 | type containsValueFn struct { 233 | fnApply 234 | ContainsValue Expr `json:"contains_value"` 235 | Value Expr `json:"in"` 236 | } 237 | 238 | // ContainsField checks if the provided value contains the field specified. 239 | // 240 | // Parameters: 241 | // field Expr - The field to check for the existence of. Field can only be a string. 242 | // value Expr - Value to search against. 243 | // 244 | // Returns: 245 | // bool - true if the field exists, false otherwise. 246 | // 247 | // See: https://app.fauna.com/documentation/reference/queryapi#miscellaneous-functions 248 | func ContainsField(field, value interface{}) Expr { 249 | return containsFieldFn{ContainsField: wrap(field), Value: wrap(value)} 250 | } 251 | 252 | type containsFieldFn struct { 253 | fnApply 254 | ContainsField Expr `json:"contains_field"` 255 | Value Expr `json:"in"` 256 | } 257 | 258 | // Count returns the number of elements in the collection. 259 | // 260 | // Parameters: 261 | // collection Expr - the collection 262 | // 263 | // Returns: 264 | // a new Expr instance 265 | // 266 | // See: https://docs.fauna.com/fauna/current/api/fql/functions/count 267 | func Count(collection interface{}) Expr { 268 | return countFn{Count: wrap(collection)} 269 | } 270 | 271 | type countFn struct { 272 | fnApply 273 | Count Expr `json:"count"` 274 | } 275 | 276 | // Sum sums the elements in the collection. 277 | // 278 | // Parameters: 279 | // collection Expr - the collection 280 | // 281 | // Returns: 282 | // a new Expr instance 283 | // 284 | // See: https://docs.fauna.com/fauna/current/api/fql/functions/sum 285 | func Sum(collection interface{}) Expr { 286 | return sumFn{Sum: wrap(collection)} 287 | } 288 | 289 | type sumFn struct { 290 | fnApply 291 | Sum Expr `json:"sum"` 292 | } 293 | 294 | // Mean returns the mean of all elements in the collection. 295 | // 296 | // Parameters: 297 | // 298 | // collection Expr - the collection 299 | // 300 | // Returns: 301 | // a new Expr instance 302 | // 303 | // See: https://docs.fauna.com/fauna/current/api/fql/functions/mean 304 | func Mean(collection interface{}) Expr { 305 | return meanFn{Mean: wrap(collection)} 306 | } 307 | 308 | type meanFn struct { 309 | fnApply 310 | Mean Expr `json:"mean"` 311 | } 312 | 313 | // Reverse accepts a set, array or page and returns the same type with elements in reversed order. 314 | // 315 | // Parameters: 316 | // 317 | // collection Expr - the collection 318 | // 319 | // Returns: 320 | // a new Expr instance 321 | // 322 | // See: https://docs.fauna.com/fauna/current/api/fql/functions/reverse 323 | func Reverse(collection interface{}) Expr { 324 | return reverseFn{Reverse: wrap(collection)} 325 | } 326 | 327 | type reverseFn struct { 328 | fnApply 329 | Reverse Expr `json:"reverse"` 330 | } 331 | -------------------------------------------------------------------------------- /faunadb/values.go: -------------------------------------------------------------------------------- 1 | package faunadb 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/json" 6 | "time" 7 | ) 8 | 9 | /* 10 | Value represents valid FaunaDB values returned from the server. Values also implement the Expr interface. 11 | They can be sent back and forth to FaunaDB with no extra escaping needed. 12 | 13 | The Get method is used to decode a FaunaDB value into a Go type. For example: 14 | 15 | var t time.Time 16 | 17 | faunaTime, _ := client.Query(Time("now")) 18 | _ := faunaTime.Get(&t) 19 | 20 | The At method uses field extractors to traverse the data to specify a field: 21 | 22 | var firstEmail string 23 | 24 | profile, _ := client.Query(RefCollection(Collection("profile), "43")) 25 | profile.At(ObjKey("emails").AtIndex(0)).Get(&firstEmail) 26 | 27 | For more information, check https://app.fauna.com/documentation/reference/queryapi#simple-type. 28 | */ 29 | type Value interface { 30 | Expr 31 | Get(interface{}) error // Decode a FaunaDB value into a native Go type 32 | At(Field) FieldValue // Traverse the value using the provided field extractor 33 | } 34 | 35 | // StringV represents a valid JSON string. 36 | type StringV string 37 | 38 | // Get implements the Value interface by decoding the underlying value to either a StringV or a string type. 39 | func (str StringV) Get(i interface{}) error { return newValueDecoder(i).assign(str) } 40 | 41 | // At implements the Value interface by returning an invalid field since StringV is not traversable. 42 | func (str StringV) At(field Field) FieldValue { return field.get(str) } 43 | 44 | // LongV represents a valid JSON number. 45 | type LongV int64 46 | 47 | // Get implements the Value interface by decoding the underlying value to either a LongV or a numeric type. 48 | func (num LongV) Get(i interface{}) error { return newValueDecoder(i).assign(num) } 49 | 50 | // At implements the Value interface by returning an invalid field since LongV is not traversable. 51 | func (num LongV) At(field Field) FieldValue { return field.get(num) } 52 | 53 | // DoubleV represents a valid JSON double. 54 | type DoubleV float64 55 | 56 | // Get implements the Value interface by decoding the underlying value to either a DoubleV or a float type. 57 | func (num DoubleV) Get(i interface{}) error { return newValueDecoder(i).assign(num) } 58 | 59 | // At implements the Value interface by returning an invalid field since DoubleV is not traversable. 60 | func (num DoubleV) At(field Field) FieldValue { return field.get(num) } 61 | 62 | // BooleanV represents a valid JSON boolean. 63 | type BooleanV bool 64 | 65 | // Get implements the Value interface by decoding the underlying value to either a BooleanV or a boolean type. 66 | func (boolean BooleanV) Get(i interface{}) error { return newValueDecoder(i).assign(boolean) } 67 | 68 | // At implements the Value interface by returning an invalid field since BooleanV is not traversable. 69 | func (boolean BooleanV) At(field Field) FieldValue { return field.get(boolean) } 70 | 71 | // DateV represents a FaunaDB date type. 72 | type DateV time.Time 73 | 74 | // Get implements the Value interface by decoding the underlying value to either a DateV or a time.Time type. 75 | func (date DateV) Get(i interface{}) error { return newValueDecoder(i).assign(date) } 76 | 77 | // At implements the Value interface by returning an invalid field since DateV is not traversable. 78 | func (date DateV) At(field Field) FieldValue { return field.get(date) } 79 | 80 | // MarshalJSON implements json.Marshaler by escaping its value according to FaunaDB date representation. 81 | func (date DateV) MarshalJSON() ([]byte, error) { 82 | return escape("@date", time.Time(date).Format("2006-01-02")) 83 | } 84 | 85 | // TimeV represents a FaunaDB time type. 86 | type TimeV time.Time 87 | 88 | // Get implements the Value interface by decoding the underlying value to either a TimeV or a time.Time type. 89 | func (localTime TimeV) Get(i interface{}) error { return newValueDecoder(i).assign(localTime) } 90 | 91 | // At implements the Value interface by returning an invalid field since TimeV is not traversable. 92 | func (localTime TimeV) At(field Field) FieldValue { return field.get(localTime) } 93 | 94 | // MarshalJSON implements json.Marshaler by escaping its value according to FaunaDB time representation. 95 | func (localTime TimeV) MarshalJSON() ([]byte, error) { 96 | return escape("@ts", time.Time(localTime).Format("2006-01-02T15:04:05.999999999Z")) 97 | } 98 | 99 | // RefV represents a FaunaDB ref type. 100 | type RefV struct { 101 | ID string 102 | Collection *RefV 103 | Class *RefV //Deprecated: As of 2.7 Class is deprecated, use Collection instead 104 | Database *RefV 105 | } 106 | 107 | // Get implements the Value interface by decoding the underlying ref to a RefV. 108 | func (ref RefV) Get(i interface{}) error { return newValueDecoder(i).assign(ref) } 109 | 110 | // At implements the Value interface by returning an invalid field since RefV is not traversable. 111 | func (ref RefV) At(field Field) FieldValue { return field.get(ref) } 112 | 113 | // MarshalJSON implements json.Marshaler by escaping its value according to FaunaDB ref representation. 114 | func (ref RefV) MarshalJSON() ([]byte, error) { 115 | values := map[string]interface{}{"id": ref.ID} 116 | 117 | if ref.Collection != nil { 118 | values["collection"] = ref.Collection 119 | } 120 | 121 | if ref.Database != nil { 122 | values["database"] = ref.Database 123 | } 124 | 125 | return escape("@ref", values) 126 | } 127 | 128 | var ( 129 | nativeClasses = RefV{"classes", nil, nil, nil} 130 | nativeCollections = RefV{"collections", nil, nil, nil} 131 | nativeIndexes = RefV{"indexes", nil, nil, nil} 132 | nativeDatabases = RefV{"databases", nil, nil, nil} 133 | nativeFunctions = RefV{"functions", nil, nil, nil} 134 | nativeRoles = RefV{"roles", nil, nil, nil} 135 | nativeKeys = RefV{"keys", nil, nil, nil} 136 | nativeTokens = RefV{"tokens", nil, nil, nil} 137 | nativeCredentials = RefV{"credentials", nil, nil, nil} 138 | ) 139 | 140 | func NativeClasses() *RefV { return &nativeClasses } 141 | func NativeCollections() *RefV { return &nativeCollections } 142 | func NativeIndexes() *RefV { return &nativeIndexes } 143 | func NativeDatabases() *RefV { return &nativeDatabases } 144 | func NativeFunctions() *RefV { return &nativeFunctions } 145 | func NativeRoles() *RefV { return &nativeRoles } 146 | func NativeKeys() *RefV { return &nativeKeys } 147 | func NativeTokens() *RefV { return &nativeTokens } 148 | func NativeCredentials() *RefV { return &nativeCredentials } 149 | 150 | func nativeFromName(id string) *RefV { 151 | switch id { 152 | case "collections": 153 | return &nativeCollections 154 | case "classes": 155 | return &nativeClasses 156 | case "indexes": 157 | return &nativeIndexes 158 | case "databases": 159 | return &nativeDatabases 160 | case "functions": 161 | return &nativeFunctions 162 | case "roles": 163 | return &nativeRoles 164 | case "keys": 165 | return &nativeKeys 166 | case "tokens": 167 | return &nativeTokens 168 | case "credentials": 169 | return &nativeCredentials 170 | } 171 | 172 | return &RefV{id, nil, nil, nil} 173 | } 174 | 175 | // SetRefV represents a FaunaDB setref type. 176 | type SetRefV struct { 177 | Parameters map[string]Value 178 | } 179 | 180 | // Get implements the Value interface by decoding the underlying value to a SetRefV. 181 | func (set SetRefV) Get(i interface{}) error { return newValueDecoder(i).assign(set) } 182 | 183 | // At implements the Value interface by returning an invalid field since SetRefV is not traversable. 184 | func (set SetRefV) At(field Field) FieldValue { return field.get(set) } 185 | 186 | // MarshalJSON implements json.Marshaler by escaping its value according to FaunaDB setref representation. 187 | func (set SetRefV) MarshalJSON() ([]byte, error) { return escape("@set", set.Parameters) } 188 | 189 | // ObjectV represents a FaunaDB object type. 190 | type ObjectV map[string]Value 191 | 192 | // Get implements the Value interface by decoding the underlying value to either a ObjectV or a native map type. 193 | func (obj ObjectV) Get(i interface{}) error { return newValueDecoder(i).decodeMap(obj) } 194 | 195 | // At implements the Value interface by traversing the object and extracting the provided field. 196 | func (obj ObjectV) At(field Field) FieldValue { return field.get(obj) } 197 | 198 | // MarshalJSON implements json.Marshaler by escaping its value according to FaunaDB object representation. 199 | func (obj ObjectV) MarshalJSON() ([]byte, error) { return escape("object", map[string]Value(obj)) } 200 | 201 | // ArrayV represents a FaunaDB array type. 202 | type ArrayV []Value 203 | 204 | // Get implements the Value interface by decoding the underlying value to either an ArrayV or a native slice type. 205 | func (arr ArrayV) Get(i interface{}) error { return newValueDecoder(i).decodeArray(arr) } 206 | 207 | // At implements the Value interface by traversing the array and extracting the provided field. 208 | func (arr ArrayV) At(field Field) FieldValue { return field.get(arr) } 209 | 210 | // NullV represents a valid JSON null. 211 | type NullV struct{} 212 | 213 | // Get implements the Value interface by decoding the underlying value to a either a NullV or a nil pointer. 214 | func (null NullV) Get(i interface{}) error { return nil } 215 | 216 | // At implements the Value interface by returning an invalid field since NullV is not traversable. 217 | func (null NullV) At(field Field) FieldValue { return field.get(null) } 218 | 219 | // MarshalJSON implements json.Marshaler by escaping its value according to JSON null representation. 220 | func (null NullV) MarshalJSON() ([]byte, error) { return []byte("null"), nil } 221 | 222 | // BytesV represents a FaunaDB binary blob type. 223 | type BytesV []byte 224 | 225 | // Get implements the Value interface by decoding the underlying value to either a ByteV or a []byte type. 226 | func (bytes BytesV) Get(i interface{}) error { return newValueDecoder(i).assign(bytes) } 227 | 228 | // At implements the Value interface by returning an invalid field since BytesV is not traversable. 229 | func (bytes BytesV) At(field Field) FieldValue { return field.get(bytes) } 230 | 231 | // MarshalJSON implements json.Marshaler by escaping its value according to FaunaDB bytes representation. 232 | func (bytes BytesV) MarshalJSON() ([]byte, error) { 233 | encoded := base64.StdEncoding.EncodeToString(bytes) 234 | return escape("@bytes", encoded) 235 | } 236 | 237 | // QueryV represents a `@query` value in FaunaDB. 238 | type QueryV struct { 239 | lambda json.RawMessage 240 | } 241 | 242 | // Get implements the Value interface by decoding the underlying value to a QueryV. 243 | func (query QueryV) Get(i interface{}) error { return newValueDecoder(i).assign(query) } 244 | 245 | // At implements the Value interface by returning an invalid field since QueryV is not traversable. 246 | func (query QueryV) At(field Field) FieldValue { return field.get(query) } 247 | 248 | // MarshalJSON implements json.Marshaler by escaping its value according to FaunaDB query representation. 249 | func (query QueryV) MarshalJSON() ([]byte, error) { return escape("@query", &query.lambda) } 250 | 251 | // Implement Expr for all values 252 | 253 | func (str StringV) expr() {} 254 | func (num LongV) expr() {} 255 | func (num DoubleV) expr() {} 256 | func (boolean BooleanV) expr() {} 257 | func (date DateV) expr() {} 258 | func (localTime TimeV) expr() {} 259 | func (ref RefV) expr() {} 260 | func (set SetRefV) expr() {} 261 | func (obj ObjectV) expr() {} 262 | func (arr ArrayV) expr() {} 263 | func (null NullV) expr() {} 264 | func (bytes BytesV) expr() {} 265 | func (query QueryV) expr() {} 266 | 267 | func escape(key string, value interface{}) ([]byte, error) { 268 | return json.Marshal(map[string]interface{}{key: value}) 269 | } 270 | -------------------------------------------------------------------------------- /faunadb/functions_types.go: -------------------------------------------------------------------------------- 1 | package faunadb 2 | 3 | // ToString attempts to convert an expression to a string literal. 4 | // 5 | // Parameters: 6 | // value Object - The expression to convert. 7 | // 8 | // Returns: 9 | // string - A string literal. 10 | func ToString(value interface{}) Expr { 11 | return toStringFn{ToString: wrap(value)} 12 | } 13 | 14 | type toStringFn struct { 15 | fnApply 16 | ToString Expr `json:"to_string"` 17 | } 18 | 19 | // ToNumber attempts to convert an expression to a numeric literal - 20 | // either an int64 or float64. 21 | // 22 | // Parameters: 23 | // value Object - The expression to convert. 24 | // 25 | // Returns: 26 | // number - A numeric literal. 27 | func ToNumber(value interface{}) Expr { 28 | return toNumberFn{ToNumber: wrap(value)} 29 | } 30 | 31 | type toNumberFn struct { 32 | fnApply 33 | ToNumber Expr `json:"to_number"` 34 | } 35 | 36 | // ToDouble attempts to convert an expression to a double 37 | // 38 | // Parameters: 39 | // value Object - The expression to convert. 40 | // 41 | // Returns: 42 | // number - A double literal. 43 | func ToDouble(value interface{}) Expr { 44 | return toDoubleFn{ToDouble: wrap(value)} 45 | } 46 | 47 | type toDoubleFn struct { 48 | fnApply 49 | ToDouble Expr `json:"to_double"` 50 | } 51 | 52 | // ToInteger attempts to convert an expression to an integer literal 53 | // 54 | // Parameters: 55 | // value Object - The expression to convert. 56 | // 57 | // Returns: 58 | // number - An integer literal. 59 | func ToInteger(value interface{}) Expr { 60 | return toIntegerFn{ToInteger: wrap(value)} 61 | } 62 | 63 | type toIntegerFn struct { 64 | fnApply 65 | ToInteger Expr `json:"to_integer"` 66 | } 67 | 68 | // ToObject attempts to convert an expression to an object 69 | // 70 | // Parameters: 71 | // value Object - The expression to convert. 72 | // 73 | // Returns: 74 | // object - An object. 75 | func ToObject(value interface{}) Expr { 76 | return toObjectFn{ToObject: wrap(value)} 77 | } 78 | 79 | type toObjectFn struct { 80 | fnApply 81 | ToObject Expr `json:"to_object"` 82 | } 83 | 84 | // ToArray attempts to convert an expression to an array 85 | // 86 | // Parameters: 87 | // value Object - The expression to convert. 88 | // 89 | // Returns: 90 | // array - An array. 91 | func ToArray(value interface{}) Expr { 92 | return toArrayFn{ToArray: wrap(value)} 93 | } 94 | 95 | type toArrayFn struct { 96 | fnApply 97 | ToArray Expr `json:"to_array"` 98 | } 99 | 100 | // ToTime attempts to convert an expression to a time literal. 101 | // 102 | // Parameters: 103 | // value Object - The expression to convert. 104 | // 105 | // Returns: 106 | // time - A time literal. 107 | func ToTime(value interface{}) Expr { 108 | return toTimeFn{ToTime: wrap(value)} 109 | } 110 | 111 | type toTimeFn struct { 112 | fnApply 113 | ToTime Expr `json:"to_time"` 114 | } 115 | 116 | // ToDate attempts to convert an expression to a date literal. 117 | // 118 | // Parameters: 119 | // value Object - The expression to convert. 120 | // 121 | // Returns: 122 | // date - A date literal. 123 | func ToDate(value interface{}) Expr { 124 | return toDateFn{ToDate: wrap(value)} 125 | } 126 | 127 | type toDateFn struct { 128 | fnApply 129 | ToDate Expr `json:"to_date"` 130 | } 131 | 132 | // IsNumber checks if the expression is a number 133 | // 134 | // Parameters: 135 | // expr Expr - The expression to check. 136 | // 137 | // Returns: 138 | // bool - returns true if the expression is a number 139 | func IsNumber(expr interface{}) Expr { 140 | return isNumberFn{IsNumber: wrap(expr)} 141 | } 142 | 143 | type isNumberFn struct { 144 | fnApply 145 | IsNumber Expr `json:"is_number"` 146 | } 147 | 148 | // IsDouble checks if the expression is a double 149 | // 150 | // Parameters: 151 | // expr Expr - The expression to check. 152 | // 153 | // Returns: 154 | // bool - returns true if the expression is a double 155 | func IsDouble(expr interface{}) Expr { 156 | return isDoubleFn{IsDouble: wrap(expr)} 157 | } 158 | 159 | type isDoubleFn struct { 160 | fnApply 161 | IsDouble Expr `json:"is_double"` 162 | } 163 | 164 | // IsInteger checks if the expression is an integer 165 | // 166 | // Parameters: 167 | // expr Expr - The expression to check. 168 | // 169 | // Returns: 170 | // bool - returns true if the expression is an integer 171 | func IsInteger(expr interface{}) Expr { 172 | return isIntegerFn{IsInteger: wrap(expr)} 173 | } 174 | 175 | type isIntegerFn struct { 176 | fnApply 177 | IsInteger Expr `json:"is_integer"` 178 | } 179 | 180 | // IsBoolean checks if the expression is a boolean 181 | // 182 | // Parameters: 183 | // expr Expr - The expression to check. 184 | // 185 | // Returns: 186 | // bool - returns true if the expression is a boolean 187 | func IsBoolean(expr interface{}) Expr { 188 | return isBooleanFn{IsBoolean: wrap(expr)} 189 | } 190 | 191 | type isBooleanFn struct { 192 | fnApply 193 | IsBoolean Expr `json:"is_boolean"` 194 | } 195 | 196 | // IsNull checks if the expression is null 197 | // 198 | // Parameters: 199 | // expr Expr - The expression to check. 200 | // 201 | // Returns: 202 | // bool - returns true if the expression is null 203 | func IsNull(expr interface{}) Expr { 204 | return isNullFn{IsNull: wrap(expr)} 205 | } 206 | 207 | type isNullFn struct { 208 | fnApply 209 | IsNull Expr `json:"is_null"` 210 | } 211 | 212 | // IsBytes checks if the expression are bytes 213 | // 214 | // Parameters: 215 | // expr Expr - The expression to check. 216 | // 217 | // Returns: 218 | // bool - returns true if the expression are bytes 219 | func IsBytes(expr interface{}) Expr { 220 | return isBytesFn{IsBytes: wrap(expr)} 221 | } 222 | 223 | type isBytesFn struct { 224 | fnApply 225 | IsBytes Expr `json:"is_bytes"` 226 | } 227 | 228 | // IsTimestamp checks if the expression is a timestamp 229 | // 230 | // Parameters: 231 | // expr Expr - The expression to check. 232 | // 233 | // Returns: 234 | // bool - returns true if the expression is a timestamp 235 | func IsTimestamp(expr interface{}) Expr { 236 | return isTimestampFn{IsTimestamp: wrap(expr)} 237 | } 238 | 239 | type isTimestampFn struct { 240 | fnApply 241 | IsTimestamp Expr `json:"is_timestamp"` 242 | } 243 | 244 | // IsDate checks if the expression is a date 245 | // 246 | // Parameters: 247 | // expr Expr - The expression to check. 248 | // 249 | // Returns: 250 | // bool - returns true if the expression is a date 251 | func IsDate(expr interface{}) Expr { 252 | return isDateFn{IsDate: wrap(expr)} 253 | } 254 | 255 | type isDateFn struct { 256 | fnApply 257 | IsDate Expr `json:"is_date"` 258 | } 259 | 260 | // IsString checks if the expression is a string 261 | // 262 | // Parameters: 263 | // expr Expr - The expression to check. 264 | // 265 | // Returns: 266 | // bool - returns true if the expression is a string 267 | func IsString(expr interface{}) Expr { 268 | return isStringFn{IsString: wrap(expr)} 269 | } 270 | 271 | type isStringFn struct { 272 | fnApply 273 | IsString Expr `json:"is_string"` 274 | } 275 | 276 | // IsArray checks if the expression is an array 277 | // 278 | // Parameters: 279 | // expr Expr - The expression to check. 280 | // 281 | // Returns: 282 | // bool - returns true if the expression is an array 283 | func IsArray(expr interface{}) Expr { 284 | return isArrayFn{IsArray: wrap(expr)} 285 | } 286 | 287 | type isArrayFn struct { 288 | fnApply 289 | IsArray Expr `json:"is_array"` 290 | } 291 | 292 | // IsObject checks if the expression is an object 293 | // 294 | // Parameters: 295 | // expr Expr - The expression to check. 296 | // 297 | // Returns: 298 | // bool - returns true if the expression is an object 299 | func IsObject(expr interface{}) Expr { 300 | return isObjectFn{IsObject: wrap(expr)} 301 | } 302 | 303 | type isObjectFn struct { 304 | fnApply 305 | IsObject Expr `json:"is_object"` 306 | } 307 | 308 | // IsRef checks if the expression is a ref 309 | // 310 | // Parameters: 311 | // expr Expr - The expression to check. 312 | // 313 | // Returns: 314 | // bool - returns true if the expression is a ref 315 | func IsRef(expr interface{}) Expr { 316 | return isRefFn{IsRef: wrap(expr)} 317 | } 318 | 319 | type isRefFn struct { 320 | fnApply 321 | IsRef Expr `json:"is_ref"` 322 | } 323 | 324 | // IsSet checks if the expression is a set 325 | // 326 | // Parameters: 327 | // expr Expr - The expression to check. 328 | // 329 | // Returns: 330 | // bool - returns true if the expression is a set 331 | func IsSet(expr interface{}) Expr { 332 | return isSetFn{IsSet: wrap(expr)} 333 | } 334 | 335 | type isSetFn struct { 336 | fnApply 337 | IsSet Expr `json:"is_set"` 338 | } 339 | 340 | // IsDoc checks if the expression is a document 341 | // 342 | // Parameters: 343 | // expr Expr - The expression to check. 344 | // 345 | // Returns: 346 | // bool - returns true if the expression is a document 347 | func IsDoc(expr interface{}) Expr { 348 | return isDocFn{IsDoc: wrap(expr)} 349 | } 350 | 351 | type isDocFn struct { 352 | fnApply 353 | IsDoc Expr `json:"is_doc"` 354 | } 355 | 356 | // IsLambda checks if the expression is a Lambda 357 | // 358 | // Parameters: 359 | // expr Expr - The expression to check. 360 | // 361 | // Returns: 362 | // bool - returns true if the expression is a Lambda 363 | func IsLambda(expr interface{}) Expr { 364 | return isLambdaFn{IsLambda: wrap(expr)} 365 | } 366 | 367 | type isLambdaFn struct { 368 | fnApply 369 | IsLambda Expr `json:"is_lambda"` 370 | } 371 | 372 | // IsCollection checks if the expression is a collection 373 | // 374 | // Parameters: 375 | // expr Expr - The expression to check. 376 | // 377 | // Returns: 378 | // bool - returns true if the expression is a collection 379 | func IsCollection(expr interface{}) Expr { 380 | return isCollectionFn{IsCollection: wrap(expr)} 381 | } 382 | 383 | type isCollectionFn struct { 384 | fnApply 385 | IsCollection Expr `json:"is_collection"` 386 | } 387 | 388 | // IsDatabase checks if the expression is a database 389 | // 390 | // Parameters: 391 | // expr Expr - The expression to check. 392 | // 393 | // Returns: 394 | // bool - returns true if the expression is a database 395 | func IsDatabase(expr interface{}) Expr { 396 | return isDatabaseFn{IsDatabase: wrap(expr)} 397 | } 398 | 399 | type isDatabaseFn struct { 400 | fnApply 401 | IsDatabase Expr `json:"is_database"` 402 | } 403 | 404 | // IsIndex checks if the expression is an index 405 | // 406 | // Parameters: 407 | // expr Expr - The expression to check. 408 | // 409 | // Returns: 410 | // bool - returns true if the expression is an index 411 | func IsIndex(expr interface{}) Expr { 412 | return isIndexFn{IsIndex: wrap(expr)} 413 | } 414 | 415 | type isIndexFn struct { 416 | fnApply 417 | IsIndex Expr `json:"is_index"` 418 | } 419 | 420 | // IsFunction checks if the expression is a function 421 | // 422 | // Parameters: 423 | // expr Expr - The expression to check. 424 | // 425 | // Returns: 426 | // bool - returns true if the expression is a function 427 | func IsFunction(expr interface{}) Expr { 428 | return isFunctionFn{IsFunction: wrap(expr)} 429 | } 430 | 431 | type isFunctionFn struct { 432 | fnApply 433 | IsFunction Expr `json:"is_function"` 434 | } 435 | 436 | // IsKey checks if the expression is a key 437 | // 438 | // Parameters: 439 | // expr Expr - The expression to check. 440 | // 441 | // Returns: 442 | // bool - returns true if the expression is a key 443 | func IsKey(expr interface{}) Expr { 444 | return isKeyFn{IsKey: wrap(expr)} 445 | } 446 | 447 | type isKeyFn struct { 448 | fnApply 449 | IsKey Expr `json:"is_key"` 450 | } 451 | 452 | // IsToken checks if the expression is a token 453 | // 454 | // Parameters: 455 | // expr Expr - The expression to check. 456 | // 457 | // Returns: 458 | // bool - returns true if the expression is a token 459 | func IsToken(expr interface{}) Expr { 460 | return isTokenFn{IsToken: wrap(expr)} 461 | } 462 | 463 | type isTokenFn struct { 464 | fnApply 465 | IsToken Expr `json:"is_token"` 466 | } 467 | 468 | // IsCredentials checks if the expression is a credentials 469 | // 470 | // Parameters: 471 | // expr Expr - The expression to check. 472 | // 473 | // Returns: 474 | // bool - returns true if the expression is a credential 475 | func IsCredentials(expr interface{}) Expr { 476 | return isCredentialsFn{IsCredentials: wrap(expr)} 477 | } 478 | 479 | type isCredentialsFn struct { 480 | fnApply 481 | IsCredentials Expr `json:"is_credentials"` 482 | } 483 | 484 | // IsRole checks if the expression is a role 485 | // 486 | // Parameters: 487 | // expr Expr - The expression to check. 488 | // 489 | // Returns: 490 | // bool - returns true if the expression is a role 491 | func IsRole(expr interface{}) Expr { 492 | return isRoleFn{IsRole: wrap(expr)} 493 | } 494 | 495 | type isRoleFn struct { 496 | fnApply 497 | IsRole Expr `json:"is_role"` 498 | } 499 | -------------------------------------------------------------------------------- /faunadb/query.go: -------------------------------------------------------------------------------- 1 | package faunadb 2 | 3 | type fnApply interface { 4 | Expr 5 | } 6 | 7 | // OptionalParameter describes optional parameters for query language functions 8 | type OptionalParameter func(Expr) Expr 9 | 10 | func applyOptionals(expr Expr, options []OptionalParameter) Expr { 11 | for _, option := range options { 12 | switch expr.(type) { 13 | case invalidExpr: 14 | return expr 15 | default: 16 | expr = option(expr) 17 | } 18 | } 19 | return expr 20 | } 21 | 22 | // Event's action types. Usually used as a parameter for Insert or Remove functions. 23 | // 24 | // See: https://app.fauna.com/documentation/reference/queryapi#simple-type-events 25 | const ( 26 | ActionCreate = "create" 27 | ActionUpdate = "update" 28 | ActionDelete = "delete" 29 | ActionAdd = "add" 30 | ActionRemove = "remove" 31 | ) 32 | 33 | // Time unit. Usually used as a parameter for Time functions. 34 | // 35 | // See: https://app.fauna.com/documentation/reference/queryapi#epochnum-unit 36 | const ( 37 | TimeUnitDay = "day" 38 | TimeUnitHalfDay = "half day" 39 | TimeUnitHour = "hour" 40 | TimeUnitMinute = "minute" 41 | TimeUnitSecond = "second" 42 | TimeUnitMillisecond = "millisecond" 43 | TimeUnitMicrosecond = "microsecond" 44 | TimeUnitNanosecond = "nanosecond" 45 | ) 46 | 47 | // Normalizers for Casefold 48 | // 49 | // See: https://app.fauna.com/documentation/reference/queryapi#string-functions 50 | const ( 51 | NormalizerNFKCCaseFold = "NFKCCaseFold" 52 | NormalizerNFC = "NFC" 53 | NormalizerNFD = "NFD" 54 | NormalizerNFKC = "NFKC" 55 | NormalizerNFKD = "NFKD" 56 | ) 57 | 58 | // Helper functions 59 | 60 | func varargs(expr ...interface{}) interface{} { 61 | if len(expr) == 1 { 62 | return expr[0] 63 | } 64 | 65 | return expr 66 | } 67 | 68 | // Optional parameters 69 | 70 | // EventsOpt is an boolean optional parameter that describes if the query should include historical events. 71 | // For more information about events, check https://app.fauna.com/documentation/reference/queryapi#simple-type-events. 72 | // 73 | // Functions that accept this optional parameter are: Paginate. 74 | // 75 | // Deprecated: The Events function was renamed to EventsOpt to support the new history API. 76 | // EventsOpt is provided here for backwards compatibility. Instead of using Paginate with the EventsOpt parameter, 77 | // you should use the new Events function. 78 | func EventsOpt(events interface{}) OptionalParameter { 79 | return func(expr Expr) Expr { 80 | switch e := expr.(type) { 81 | case eventsParam: 82 | return e.setEvents(wrap(events)) 83 | default: 84 | return e 85 | } 86 | } 87 | } 88 | 89 | type eventsParam interface { 90 | setEvents(length Expr) Expr 91 | } 92 | 93 | func (fn paginateFn) setEvents(e Expr) Expr { 94 | fn.Events = e 95 | return fn 96 | } 97 | 98 | // TS is a timestamp optional parameter that specifies in which timestamp a query should be executed. 99 | // 100 | // Functions that accept this optional parameter are: Get, Exists, and Paginate. 101 | func TS(timestamp interface{}) OptionalParameter { 102 | return func(expr Expr) Expr { 103 | switch e := expr.(type) { 104 | case tsParam: 105 | return e.setTS(wrap(timestamp)) 106 | default: 107 | return e 108 | } 109 | } 110 | } 111 | 112 | type tsParam interface { 113 | setTS(length Expr) Expr 114 | } 115 | 116 | func (fn paginateFn) setTS(e Expr) Expr { 117 | fn.TS = e 118 | return fn 119 | } 120 | 121 | func (fn existsFn) setTS(e Expr) Expr { 122 | fn.TS = e 123 | return fn 124 | } 125 | 126 | func (fn getFn) setTS(e Expr) Expr { 127 | fn.TS = e 128 | return fn 129 | } 130 | 131 | func Cursor(ref interface{}) OptionalParameter { 132 | return func(expr Expr) Expr { 133 | switch e := expr.(type) { 134 | case cursorParam: 135 | return e.setCursor(wrap(ref)) 136 | default: 137 | return e 138 | } 139 | } 140 | } 141 | 142 | type cursorParam interface { 143 | setCursor(cursor Expr) Expr 144 | } 145 | 146 | func (fn paginateFn) setCursor(e Expr) Expr { 147 | fn.Cursor = e 148 | return fn 149 | } 150 | 151 | // After is an optional parameter used when cursoring that refers to the specified cursor's the next page, inclusive. 152 | // For more information about pages, check https://app.fauna.com/documentation/reference/queryapi#simple-type-pages. 153 | // 154 | // Functions that accept this optional parameter are: Paginate. 155 | func After(ref interface{}) OptionalParameter { 156 | return func(expr Expr) Expr { 157 | switch e := expr.(type) { 158 | case afterParam: 159 | return e.setAfter(wrap(ref)) 160 | default: 161 | return e 162 | } 163 | } 164 | } 165 | 166 | type afterParam interface { 167 | setAfter(after Expr) Expr 168 | } 169 | 170 | func (fn paginateFn) setAfter(e Expr) Expr { 171 | fn.After = e 172 | return fn 173 | } 174 | 175 | // Before is an optional parameter used when cursoring that refers to the specified cursor's previous page, exclusive. 176 | // For more information about pages, check https://app.fauna.com/documentation/reference/queryapi#simple-type-pages. 177 | // 178 | // Functions that accept this optional parameter are: Paginate. 179 | func Before(ref interface{}) OptionalParameter { 180 | return func(expr Expr) Expr { 181 | switch e := expr.(type) { 182 | case beforeParam: 183 | return e.setBefore(wrap(ref)) 184 | default: 185 | return e 186 | } 187 | } 188 | } 189 | 190 | type beforeParam interface { 191 | setBefore(before Expr) Expr 192 | } 193 | 194 | func (fn paginateFn) setBefore(e Expr) Expr { 195 | fn.Before = e 196 | return fn 197 | } 198 | 199 | // Number is a numeric optional parameter that specifies an optional number. 200 | // 201 | // Functions that accept this optional parameter are: Repeat. 202 | func Number(num interface{}) OptionalParameter { 203 | return func(expr Expr) Expr { 204 | switch e := expr.(type) { 205 | case numberParam: 206 | return e.setNumber(wrap(num)) 207 | default: 208 | return e 209 | } 210 | } 211 | } 212 | 213 | type numberParam interface { 214 | setNumber(num Expr) Expr 215 | } 216 | 217 | func (fn repeatFn) setNumber(e Expr) Expr { 218 | fn.Number = e 219 | return fn 220 | } 221 | 222 | // Size is a numeric optional parameter that specifies the size of a pagination cursor. 223 | // 224 | // Functions that accept this optional parameter are: Paginate. 225 | func Size(size interface{}) OptionalParameter { 226 | return func(expr Expr) Expr { 227 | switch e := expr.(type) { 228 | case sizeParam: 229 | return e.setSize(wrap(size)) 230 | default: 231 | return e 232 | } 233 | } 234 | } 235 | 236 | type sizeParam interface { 237 | setSize(size Expr) Expr 238 | } 239 | 240 | func (fn paginateFn) setSize(e Expr) Expr { 241 | fn.Size = e 242 | return fn 243 | } 244 | 245 | // NumResults is a numeric optional parameter that specifies the number of results returned. 246 | // 247 | // Functions that accept this optional parameter are: FindStrRegex. 248 | func NumResults(num interface{}) OptionalParameter { 249 | return func(expr Expr) Expr { 250 | switch e := expr.(type) { 251 | case numParam: 252 | return e.setNum(wrap(num)) 253 | default: 254 | return e 255 | } 256 | } 257 | } 258 | 259 | type numParam interface { 260 | setNum(num Expr) Expr 261 | } 262 | 263 | func (fn findStrRegexFn) setNum(e Expr) Expr { 264 | fn.NumResults = e 265 | return fn 266 | } 267 | 268 | // Start is a numeric optional parameter that specifies the start of where to search. 269 | // 270 | // Functions that accept this optional parameter are: FindStr . 271 | func Start(start interface{}) OptionalParameter { 272 | return func(expr Expr) Expr { 273 | switch e := expr.(type) { 274 | case startParam: 275 | return e.setStart(wrap(start)) 276 | default: 277 | return e 278 | } 279 | } 280 | } 281 | 282 | type startParam interface { 283 | setStart(start Expr) Expr 284 | } 285 | 286 | func (fn findStrFn) setStart(e Expr) Expr { 287 | fn.Start = e 288 | return fn 289 | } 290 | 291 | func (fn findStrRegexFn) setStart(e Expr) Expr { 292 | fn.Start = e 293 | return fn 294 | } 295 | 296 | // StrLength is a numeric optional parameter that specifies the amount to copy. 297 | // 298 | // Functions that accept this optional parameter are: FindStr and FindStrRegex. 299 | func StrLength(length interface{}) OptionalParameter { 300 | return func(expr Expr) Expr { 301 | switch e := expr.(type) { 302 | case strLengthParam: 303 | return e.setLength(wrap(length)) 304 | default: 305 | return e 306 | } 307 | } 308 | } 309 | 310 | type strLengthParam interface { 311 | setLength(length Expr) Expr 312 | } 313 | 314 | func (fn subStringFn) setLength(e Expr) Expr { 315 | fn.Length = e 316 | return fn 317 | } 318 | 319 | // OnlyFirst is a boolean optional parameter that only replace the first string 320 | // 321 | // Functions that accept this optional parameter are: ReplaceStrRegex 322 | func OnlyFirst() OptionalParameter { 323 | return func(expr Expr) Expr { 324 | switch e := expr.(type) { 325 | case firstParam: 326 | return e.setOnlyFirst() 327 | default: 328 | return e 329 | } 330 | } 331 | } 332 | 333 | type firstParam interface { 334 | setOnlyFirst() Expr 335 | } 336 | 337 | func (fn replaceStrRegexFn) setOnlyFirst() Expr { 338 | fn.First = BooleanV(true) 339 | return fn 340 | } 341 | 342 | // Sources is a boolean optional parameter that specifies if a pagination cursor should include 343 | // the source sets along with each element. 344 | // 345 | // Functions that accept this optional parameter are: Paginate. 346 | func Sources(sources interface{}) OptionalParameter { 347 | return func(expr Expr) Expr { 348 | switch e := expr.(type) { 349 | case sourcesParam: 350 | return e.setSources(wrap(sources)) 351 | default: 352 | return e 353 | } 354 | } 355 | } 356 | 357 | type sourcesParam interface { 358 | setSources(sources Expr) Expr 359 | } 360 | 361 | func (fn paginateFn) setSources(e Expr) Expr { 362 | fn.Sources = e 363 | return fn 364 | } 365 | 366 | // Default is an optional parameter that specifies the default value for a select operation when 367 | // the desired value path is absent. 368 | // 369 | // Functions that accept this optional parameter are: Select. 370 | func Default(value interface{}) OptionalParameter { 371 | return func(expr Expr) Expr { 372 | switch e := expr.(type) { 373 | case defaultParam: 374 | return e.setDefault(wrap(value)) 375 | default: 376 | return e 377 | } 378 | } 379 | } 380 | 381 | type defaultParam interface { 382 | setDefault(value Expr) Expr 383 | } 384 | 385 | func (fn selectFn) setDefault(e Expr) Expr { 386 | fn.Default = e 387 | return fn 388 | } 389 | 390 | func (fn selectAllFn) setDefault(e Expr) Expr { 391 | fn.Default = e 392 | return fn 393 | } 394 | 395 | // Separator is a string optional parameter that specifies the separator for a concat operation. 396 | // 397 | // Functions that accept this optional parameter are: Concat. 398 | func Separator(sep interface{}) OptionalParameter { 399 | return func(expr Expr) Expr { 400 | switch e := expr.(type) { 401 | case separatorParam: 402 | return e.setSeparator(wrap(sep)) 403 | default: 404 | return e 405 | } 406 | } 407 | } 408 | 409 | type separatorParam interface { 410 | setSeparator(sep Expr) Expr 411 | } 412 | 413 | func (fn concatFn) setSeparator(e Expr) Expr { 414 | fn.Separator = e 415 | return fn 416 | } 417 | 418 | // Precision is an optional parameter that specifies the precision for a Trunc and Round operations. 419 | // 420 | // Functions that accept this optional parameter are: Round and Trunc. 421 | func Precision(precision interface{}) OptionalParameter { 422 | return func(expr Expr) Expr { 423 | switch e := expr.(type) { 424 | case precisionParam: 425 | return e.setPrecision(wrap(precision)) 426 | default: 427 | return e 428 | } 429 | } 430 | } 431 | 432 | type precisionParam interface { 433 | setPrecision(precision Expr) Expr 434 | } 435 | 436 | func (fn roundFn) setPrecision(e Expr) Expr { 437 | fn.Precision = e 438 | return fn 439 | } 440 | 441 | func (fn truncFn) setPrecision(e Expr) Expr { 442 | fn.Precision = e 443 | return fn 444 | } 445 | 446 | // ConflictResolver is an optional parameter that specifies the lambda for resolving Merge conflicts 447 | // 448 | // Functions that accept this optional parameter are: Merge 449 | func ConflictResolver(lambda interface{}) OptionalParameter { 450 | return func(expr Expr) Expr { 451 | switch e := expr.(type) { 452 | case lambdaParam: 453 | return e.setLambda(wrap(lambda)) 454 | default: 455 | return e 456 | } 457 | } 458 | } 459 | 460 | type lambdaParam interface { 461 | setLambda(lambda Expr) Expr 462 | } 463 | 464 | func (fn mergeFn) setLambda(e Expr) Expr { 465 | fn.Lambda = e 466 | return fn 467 | } 468 | 469 | // Normalizer is a string optional parameter that specifies the normalization function for casefold operation. 470 | // 471 | // Functions that accept this optional parameter are: Casefold. 472 | func Normalizer(norm interface{}) OptionalParameter { 473 | return func(expr Expr) Expr { 474 | switch e := expr.(type) { 475 | case normalizerParam: 476 | return e.setNormalizer(wrap(norm)) 477 | default: 478 | return e 479 | } 480 | } 481 | } 482 | 483 | type normalizerParam interface { 484 | setNormalizer(normalizer Expr) Expr 485 | } 486 | 487 | func (fn casefoldFn) setNormalizer(e Expr) Expr { 488 | fn.Normalizer = e 489 | return fn 490 | } 491 | -------------------------------------------------------------------------------- /faunadb/functions_strings.go: -------------------------------------------------------------------------------- 1 | package faunadb 2 | 3 | // String 4 | 5 | // Format formats values into a string. 6 | // 7 | // Parameters: 8 | // format string - format a string with format specifiers. 9 | // 10 | // Optional parameters: 11 | // values []string - list of values to format into string. 12 | // 13 | // Returns: 14 | // string - A string. 15 | // 16 | // See: https://app.fauna.com/documentation/reference/queryapi#string-functions 17 | func Format(format interface{}, values ...interface{}) Expr { 18 | return formatFn{Format: wrap(format), Values: wrap(varargs(values...))} 19 | } 20 | 21 | type formatFn struct { 22 | fnApply 23 | Format Expr `json:"format"` 24 | Values Expr `json:"values"` 25 | } 26 | 27 | // Concat concatenates a list of strings into a single string. 28 | // 29 | // Parameters: 30 | // terms []string - A list of strings to concatenate. 31 | // 32 | // Optional parameters: 33 | // separator string - The separator to use between each string. See Separator() function. 34 | // 35 | // Returns: 36 | // string - A string with all terms concatenated. 37 | // 38 | // See: https://app.fauna.com/documentation/reference/queryapi#string-functions 39 | func Concat(terms interface{}, options ...OptionalParameter) Expr { 40 | fn := concatFn{Concat: wrap(terms)} 41 | return applyOptionals(fn, options) 42 | } 43 | 44 | type concatFn struct { 45 | fnApply 46 | Concat Expr `json:"concat"` 47 | Separator Expr `json:"separator,omitempty" faunarepr:"optfn"` 48 | } 49 | 50 | // Casefold normalizes strings according to the Unicode Standard section 5.18 "Case Mappings". 51 | // 52 | // Parameters: 53 | // str string - The string to casefold. 54 | // 55 | // Optional parameters: 56 | // normalizer string - The algorithm to use. One of: NormalizerNFKCCaseFold, NormalizerNFC, NormalizerNFD, NormalizerNFKC, NormalizerNFKD. 57 | // 58 | // Returns: 59 | // string - The normalized string. 60 | // 61 | // See: https://app.fauna.com/documentation/reference/queryapi#string-functions 62 | func Casefold(str interface{}, options ...OptionalParameter) Expr { 63 | fn := casefoldFn{Casefold: wrap(str)} 64 | return applyOptionals(fn, options) 65 | } 66 | 67 | type casefoldFn struct { 68 | fnApply 69 | Casefold Expr `json:"casefold"` 70 | Normalizer Expr `json:"normalizer,omitempty" faunarepr:"optfn"` 71 | } 72 | 73 | // StartsWith returns true if the string starts with the given prefix value, or false if otherwise 74 | // 75 | // Parameters: 76 | // 77 | // value string - the string to evaluate 78 | // search string - the prefix to search for 79 | // 80 | // Returns: 81 | // boolean - does `value` start with `search 82 | // 83 | // See https://docs.fauna.com/fauna/current/api/fql/functions/startswith 84 | func StartsWith(value interface{}, search interface{}) Expr { 85 | return startsWithFn{StartsWith: wrap(value), Search: wrap(search)} 86 | } 87 | 88 | type startsWithFn struct { 89 | fnApply 90 | StartsWith Expr `json:"startswith"` 91 | Search Expr `json:"search"` 92 | } 93 | 94 | // EndsWith returns true if the string ends with the given suffix value, or false if otherwise 95 | // 96 | // Parameters: 97 | // 98 | // value string - the string to evaluate 99 | // search string - the suffix to search for 100 | // 101 | // Returns: 102 | // boolean - does `value` end with `search` 103 | // 104 | // See https://docs.fauna.com/fauna/current/api/fql/functions/endswith 105 | func EndsWith(value interface{}, search interface{}) Expr { 106 | return endsWithFn{EndsWith: wrap(value), Search: wrap(search)} 107 | } 108 | 109 | type endsWithFn struct { 110 | fnApply 111 | EndsWith Expr `json:"endswith"` 112 | Search Expr `json:"search"` 113 | } 114 | 115 | // ContainsStr returns true if the string contains the given substring, or false if otherwise 116 | // 117 | // Parameters: 118 | // 119 | // value string - the string to evaluate 120 | // search string - the substring to search for 121 | // 122 | // Returns: 123 | // boolean - was the search result found 124 | // 125 | // See https://docs.fauna.com/fauna/current/api/fql/functions/containsstr 126 | func ContainsStr(value interface{}, search interface{}) Expr { 127 | return containsStrFn{ContainsStr: wrap(value), Search: wrap(search)} 128 | } 129 | 130 | type containsStrFn struct { 131 | fnApply 132 | ContainsStr Expr `json:"containsstr"` 133 | Search Expr `json:"search"` 134 | } 135 | 136 | // ContainsStrRegex returns true if the string contains the given pattern, or false if otherwise 137 | // 138 | // Parameters: 139 | // 140 | // value string - the string to evaluate 141 | // pattern string - the pattern to search for 142 | // 143 | // Returns: 144 | // boolean - was the search result found 145 | // 146 | // See https://docs.fauna.com/fauna/current/api/fql/functions/containsstrregex 147 | func ContainsStrRegex(value interface{}, pattern interface{}) Expr { 148 | return containsStrRegexFn{ContainsStrRegex: wrap(value), Pattern: wrap(pattern)} 149 | } 150 | 151 | type containsStrRegexFn struct { 152 | fnApply 153 | ContainsStrRegex Expr `json:"containsstrregex"` 154 | Pattern Expr `json:"pattern"` 155 | } 156 | 157 | // RegexEscape It takes a string and returns a regex which matches the input string verbatim. 158 | // 159 | // Parameters: 160 | // 161 | // value string - the string to analyze 162 | // pattern - the pattern to search for 163 | // 164 | // Returns: 165 | // boolean - was the search result found 166 | // 167 | // See https://docs.fauna.com/fauna/current/api/fql/functions/regexescape 168 | func RegexEscape(value interface{}) Expr { 169 | return regexEscapeFn{RegexEscape: wrap(value)} 170 | } 171 | 172 | type regexEscapeFn struct { 173 | fnApply 174 | RegexEscape Expr `json:"regexescape"` 175 | } 176 | 177 | // FindStr locates a substring in a source string. Optional parameters: Start 178 | // 179 | // Parameters: 180 | // str string - The source string 181 | // find string - The string to locate 182 | // 183 | // Optional parameters: 184 | // start int - a position to start the search. See Start() function. 185 | // 186 | // Returns: 187 | // string - The offset of where the substring starts or -1 if not found 188 | // 189 | // See: https://app.fauna.com/documentation/reference/queryapi#string-functions 190 | func FindStr(str, find interface{}, options ...OptionalParameter) Expr { 191 | fn := findStrFn{FindStr: wrap(str), Find: wrap(find)} 192 | return applyOptionals(fn, options) 193 | } 194 | 195 | type findStrFn struct { 196 | fnApply 197 | FindStr Expr `json:"findstr"` 198 | Find Expr `json:"find"` 199 | Start Expr `json:"start,omitempty" faunarepr:"optfn"` 200 | } 201 | 202 | // FindStrRegex locates a java regex pattern in a source string. Optional parameters: Start 203 | // 204 | // Parameters: 205 | // str string - The sourcestring 206 | // pattern string - The pattern to locate. 207 | // 208 | // Optional parameters: 209 | // start long - a position to start the search. See Start() function. 210 | // 211 | // Returns: 212 | // string - The offset of where the substring starts or -1 if not found 213 | // 214 | // See: https://app.fauna.com/documentation/reference/queryapi#string-functions 215 | func FindStrRegex(str, pattern interface{}, options ...OptionalParameter) Expr { 216 | fn := findStrRegexFn{FindStrRegex: wrap(str), Pattern: wrap(pattern)} 217 | return applyOptionals(fn, options) 218 | } 219 | 220 | type findStrRegexFn struct { 221 | fnApply 222 | FindStrRegex Expr `json:"findstrregex"` 223 | Pattern Expr `json:"pattern"` 224 | Start Expr `json:"start,omitempty" faunarepr:"optfn"` 225 | NumResults Expr `json:"num_results,omitempty" faunarepr:"optfn"` 226 | } 227 | 228 | // Length finds the length of a string in codepoints 229 | // 230 | // Parameters: 231 | // str string - A string to find the length in codepoints 232 | // 233 | // Returns: 234 | // int - A length of a string. 235 | // 236 | // See: https://app.fauna.com/documentation/reference/queryapi#string-functions 237 | func Length(str interface{}) Expr { return lengthFn{Length: wrap(str)} } 238 | 239 | type lengthFn struct { 240 | fnApply 241 | Length Expr `json:"length"` 242 | } 243 | 244 | // LowerCase changes all characters in the string to lowercase 245 | // 246 | // Parameters: 247 | // str string - A string to convert to lowercase 248 | // 249 | // Returns: 250 | // string - A string in lowercase. 251 | // 252 | // See: https://app.fauna.com/documentation/reference/queryapi#string-functions 253 | func LowerCase(str interface{}) Expr { return lowercaseFn{Lowercase: wrap(str)} } 254 | 255 | type lowercaseFn struct { 256 | fnApply 257 | Lowercase Expr `json:"lowercase"` 258 | } 259 | 260 | // LTrim returns a string wtih leading white space removed. 261 | // 262 | // Parameters: 263 | // str string - A string to remove leading white space 264 | // 265 | // Returns: 266 | // string - A string with all leading white space removed 267 | // 268 | // See: https://app.fauna.com/documentation/reference/queryapi#string-functions 269 | func LTrim(str interface{}) Expr { return lTrimFn{LTrim: wrap(str)} } 270 | 271 | type lTrimFn struct { 272 | fnApply 273 | LTrim Expr `json:"ltrim"` 274 | } 275 | 276 | // Repeat returns a string wtih repeated n times 277 | // 278 | // Parameters: 279 | // str string - A string to repeat 280 | // number int - The number of times to repeat the string 281 | // 282 | // Optional parameters: 283 | // Number - Only replace the first found pattern. See OnlyFirst() function. 284 | // 285 | // Returns: 286 | // string - A string concatendanted the specified number of times 287 | // 288 | // See: https://app.fauna.com/documentation/reference/queryapi#string-functions 289 | func Repeat(str interface{}, options ...OptionalParameter) Expr { 290 | fn := repeatFn{Repeat: wrap(str)} 291 | return applyOptionals(fn, options) 292 | } 293 | 294 | type repeatFn struct { 295 | fnApply 296 | Repeat Expr `json:"repeat"` 297 | Number Expr `json:"number,omitempty" faunarepr:"fn=optfn,name=Number"` 298 | } 299 | 300 | // ReplaceStr returns a string with every occurence of the "find" string changed to "replace" string 301 | // 302 | // Parameters: 303 | // str string - A source string 304 | // find string - The substring to locate in in the source string 305 | // replace string - The string to replaice the "find" string when located 306 | // 307 | // Returns: 308 | // string - returns a string with every occurence of the "find" string changed to "replace" 309 | // 310 | // See: https://app.fauna.com/documentation/reference/queryapi#string-functions 311 | func ReplaceStr(str, find, replace interface{}) Expr { 312 | return replaceStrFn{ 313 | ReplaceStr: wrap(str), 314 | Find: wrap(find), 315 | Replace: wrap(replace), 316 | } 317 | } 318 | 319 | type replaceStrFn struct { 320 | fnApply 321 | ReplaceStr Expr `json:"replacestr"` 322 | Find Expr `json:"find"` 323 | Replace Expr `json:"replace"` 324 | } 325 | 326 | // ReplaceStrRegex returns a string with occurence(s) of the java regular expression "pattern" changed to "replace" string. Optional parameters: OnlyFirst 327 | // 328 | // Parameters: 329 | // value string - The source string 330 | // pattern string - A java regular expression to locate 331 | // replace string - The string to replace the pattern when located 332 | // 333 | // Optional parameters: 334 | // OnlyFirst - Only replace the first found pattern. See OnlyFirst() function. 335 | // 336 | // Returns: 337 | // string - A string with occurence(s) of the java regular expression "pattern" changed to "replace" string 338 | // 339 | // See: https://app.fauna.com/documentation/reference/queryapi#string-functions 340 | func ReplaceStrRegex(value, pattern, replace interface{}, options ...OptionalParameter) Expr { 341 | fn := replaceStrRegexFn{ 342 | ReplaceStrRegex: wrap(value), 343 | Pattern: wrap(pattern), 344 | Replace: wrap(replace), 345 | } 346 | return applyOptionals(fn, options) 347 | } 348 | 349 | type replaceStrRegexFn struct { 350 | fnApply 351 | ReplaceStrRegex Expr `json:"replacestrregex"` 352 | Pattern Expr `json:"pattern"` 353 | Replace Expr `json:"replace"` 354 | First Expr `json:"first,omitempty" faunarepr:"fn=optfn,name=OnlyFirst,noargs=true"` 355 | } 356 | 357 | // RTrim returns a string wtih trailing white space removed. 358 | // 359 | // Parameters: 360 | // str string - A string to remove trailing white space 361 | // 362 | // Returns: 363 | // string - A string with all trailing white space removed 364 | // 365 | // See: https://app.fauna.com/documentation/reference/queryapi#string-functions 366 | func RTrim(str interface{}) Expr { return rTrimFn{RTrim: wrap(str)} } 367 | 368 | type rTrimFn struct { 369 | fnApply 370 | RTrim Expr `json:"rtrim"` 371 | } 372 | 373 | // Space function returns "N" number of spaces 374 | // 375 | // Parameters: 376 | // value int - the number of spaces 377 | // 378 | // Returns: 379 | // string - function returns string with n spaces 380 | // 381 | // See: https://app.fauna.com/documentation/reference/queryapi#string-functions 382 | func Space(value interface{}) Expr { return spaceFn{Space: wrap(value)} } 383 | 384 | type spaceFn struct { 385 | fnApply 386 | Space Expr `json:"space"` 387 | } 388 | 389 | // SubString returns a subset of the source string. Optional parameters: StrLength 390 | // 391 | // Parameters: 392 | // str string - A source string 393 | // start int - The position in the source string where SubString starts extracting characters 394 | // 395 | // Optional parameters: 396 | // StrLength int - A value for the length of the extracted substring. See StrLength() function. 397 | // 398 | // Returns: 399 | // string - function returns a subset of the source string 400 | // 401 | // See: https://app.fauna.com/documentation/reference/queryapi#string-functions 402 | func SubString(str, start interface{}, options ...OptionalParameter) Expr { 403 | fn := subStringFn{SubString: wrap(str), Start: wrap(start)} 404 | return applyOptionals(fn, options) 405 | } 406 | 407 | type subStringFn struct { 408 | fnApply 409 | SubString Expr `json:"substring"` 410 | Start Expr `json:"start"` 411 | Length Expr `json:"length,omitempty" faunarepr:"fn=optfn,name=StrLength"` 412 | } 413 | 414 | // TitleCase changes all characters in the string to TitleCase 415 | // 416 | // Parameters: 417 | // str string - A string to convert to TitleCase 418 | // 419 | // Returns: 420 | // string - A string in TitleCase. 421 | // 422 | // See: https://app.fauna.com/documentation/reference/queryapi#string-functions 423 | func TitleCase(str interface{}) Expr { return titleCaseFn{Titlecase: wrap(str)} } 424 | 425 | type titleCaseFn struct { 426 | fnApply 427 | Titlecase Expr `json:"titlecase"` 428 | } 429 | 430 | // Trim returns a string wtih trailing white space removed. 431 | // 432 | // Parameters: 433 | // str string - A string to remove trailing white space 434 | // 435 | // Returns: 436 | // string - A string with all trailing white space removed 437 | // 438 | // See: https://app.fauna.com/documentation/reference/queryapi#string-functions 439 | func Trim(str interface{}) Expr { return trimFn{Trim: wrap(str)} } 440 | 441 | type trimFn struct { 442 | fnApply 443 | Trim Expr `json:"trim"` 444 | } 445 | 446 | // UpperCase changes all characters in the string to uppercase 447 | // 448 | // Parameters: 449 | // string - A string to convert to uppercase 450 | // 451 | // Returns: 452 | // string - A string in uppercase. 453 | // 454 | // See: https://app.fauna.com/documentation/reference/queryapi#string-functions 455 | func UpperCase(str interface{}) Expr { return upperCaseFn{UpperCase: wrap(str)} } 456 | 457 | type upperCaseFn struct { 458 | fnApply 459 | UpperCase Expr `json:"uppercase"` 460 | } 461 | -------------------------------------------------------------------------------- /faunadb/deserialization_test.go: -------------------------------------------------------------------------------- 1 | package faunadb 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "math" 7 | "testing" 8 | "time" 9 | 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func TestUnmarshal(t *testing.T) { 14 | var value Value 15 | 16 | require.NoError(t, UnmarshalJSON([]byte(`{"@ts":"2019-01-01T01:30:20.000000005Z"}`), &value)) 17 | require.Equal(t, TimeV(time.Date(2019, time.January, 1, 1, 30, 20, 5, time.UTC)), value) 18 | 19 | require.NoError(t, UnmarshalJSON([]byte(`["str", 10]`), &value)) 20 | require.Equal(t, ArrayV{StringV("str"), LongV(10)}, value) 21 | 22 | require.NoError(t, UnmarshalJSON([]byte(`{"x":10, "y": 20}`), &value)) 23 | require.Equal(t, ObjectV{"x": LongV(10), "y": LongV(20)}, value) 24 | 25 | require.NoError(t, UnmarshalJSON([]byte(`{"x":10, "y": 20, "z": ["str", {"w": 30}]}`), &value)) 26 | require.Equal(t, ObjectV{"x": LongV(10), "y": LongV(20), "z": ArrayV{StringV("str"), ObjectV{"w": LongV(30)}}}, value) 27 | } 28 | 29 | func TestDeserializeStringV(t *testing.T) { 30 | var str StringV 31 | 32 | require.NoError(t, decodeJSON(`"test"`, &str)) 33 | require.Equal(t, StringV("test"), str) 34 | } 35 | 36 | func TestDeserializeString(t *testing.T) { 37 | var str string 38 | 39 | require.NoError(t, decodeJSON(`"test"`, &str)) 40 | require.Equal(t, "test", str) 41 | } 42 | 43 | func TestDeserializeLongV(t *testing.T) { 44 | var num LongV 45 | 46 | require.NoError(t, decodeJSON("9223372036854775807", &num)) 47 | require.Equal(t, LongV(math.MaxInt64), num) 48 | } 49 | 50 | func TestDeserializeLong(t *testing.T) { 51 | var num int64 52 | 53 | require.NoError(t, decodeJSON("9223372036854775807", &num)) 54 | require.Equal(t, int64(math.MaxInt64), num) 55 | } 56 | 57 | func TestNotDeserializeUint(t *testing.T) { 58 | var num uint64 59 | 60 | require.EqualError(t, 61 | decodeJSON("18446744073709551615", &num), 62 | `strconv.ParseInt: parsing "18446744073709551615": value out of range`, 63 | ) 64 | } 65 | 66 | func TestDeserializeDoubleV(t *testing.T) { 67 | var num DoubleV 68 | 69 | require.NoError(t, decodeJSON("10.64", &num)) 70 | require.Equal(t, DoubleV(10.64), num) 71 | } 72 | 73 | func TestDeserializeDouble(t *testing.T) { 74 | var num float64 75 | 76 | require.NoError(t, decodeJSON("10.64", &num)) 77 | require.Equal(t, 10.64, num) 78 | } 79 | 80 | func TestConvertNumbers(t *testing.T) { 81 | var num int 82 | var float float32 83 | 84 | require.NoError(t, decodeJSON("10", &num)) 85 | require.Equal(t, 10, num) 86 | 87 | require.NoError(t, decodeJSON("10.32", &float)) 88 | require.Equal(t, float32(10.32), float) 89 | } 90 | 91 | func TestDeserializeBooleanV(t *testing.T) { 92 | var boolean BooleanV 93 | 94 | require.NoError(t, decodeJSON("true", &boolean)) 95 | require.Equal(t, BooleanV(true), boolean) 96 | } 97 | 98 | func TestDeserializeBooleanTrue(t *testing.T) { 99 | var boolean bool 100 | 101 | require.NoError(t, decodeJSON("true", &boolean)) 102 | require.True(t, boolean) 103 | } 104 | 105 | func TestDeserializeBooleanFalse(t *testing.T) { 106 | var boolean bool 107 | 108 | require.NoError(t, decodeJSON("false", &boolean)) 109 | require.False(t, boolean) 110 | } 111 | 112 | func TestDeserializeRefV(t *testing.T) { 113 | var ref RefV 114 | 115 | require.NoError(t, decodeJSON(`{"@ref":{"id":"42","collection":{"@ref":{"id":"spells","collection":{"@ref":{"id":"collections"}}}}}}`, &ref)) 116 | r1 := &RefV{"spells", NativeCollections(), NativeCollections(), nil} 117 | r2 := RefV{"42", r1, r1, nil} 118 | require.Equal(t, r2, r2, ref) 119 | } 120 | 121 | func TestDeserializeDateV(t *testing.T) { 122 | var date DateV 123 | 124 | require.NoError(t, decodeJSON(`{ "@date": "1970-01-03" }`, &date)) 125 | require.Equal(t, DateV(time.Date(1970, time.January, 3, 0, 0, 0, 0, time.UTC)), date) 126 | } 127 | 128 | func TestDeserializeDate(t *testing.T) { 129 | var date time.Time 130 | 131 | require.NoError(t, decodeJSON(`{ "@date": "1970-01-03" }`, &date)) 132 | require.Equal(t, time.Date(1970, time.January, 3, 0, 0, 0, 0, time.UTC), date) 133 | } 134 | 135 | func TestDeserializeTimeV(t *testing.T) { 136 | var localTime TimeV 137 | 138 | require.NoError(t, decodeJSON(`{ "@ts": "1970-01-01T00:00:00.000000005Z" }`, &localTime)) 139 | require.Equal(t, TimeV(time.Date(1970, time.January, 1, 0, 0, 0, 5, time.UTC)), localTime) 140 | } 141 | 142 | func TestDeserializeTime(t *testing.T) { 143 | var localTime time.Time 144 | 145 | require.NoError(t, decodeJSON(`{ "@ts": "1970-01-01T00:00:00.000000005Z" }`, &localTime)) 146 | require.Equal(t, time.Date(1970, time.January, 1, 0, 0, 0, 5, time.UTC), localTime) 147 | } 148 | 149 | func TestDeserializeBytesV(t *testing.T) { 150 | var bytes BytesV 151 | 152 | require.NoError(t, decodeJSON(`{"@bytes": "AQIDBA=="}`, &bytes)) 153 | require.Equal(t, BytesV{1, 2, 3, 4}, bytes) 154 | } 155 | 156 | func TestDeserializeBytes(t *testing.T) { 157 | var bytes []byte 158 | 159 | require.NoError(t, decodeJSON(`{"@bytes": "AQIDBA=="}`, &bytes)) 160 | require.Equal(t, []byte{1, 2, 3, 4}, bytes) 161 | } 162 | 163 | func TestDeserializeQueryV(t *testing.T) { 164 | var query QueryV 165 | 166 | lambda := json.RawMessage(`{"lambda": "x", "expr": {"var": "x"}}`) 167 | 168 | require.NoError(t, decodeJSON(`{"@query": {"lambda": "x", "expr": {"var": "x"}}}`, &query)) 169 | require.Equal(t, QueryV{lambda}, query) 170 | } 171 | 172 | func TestDeserializeQueryVInsideObjectV(t *testing.T) { 173 | var object ObjectV 174 | 175 | lambda := json.RawMessage(`{"lambda": "x", "expr": {"var": "x"}}`) 176 | 177 | require.NoError(t, decodeJSON(`{"a": "a", "b": {"lambda": {"@query": {"lambda": "x", "expr": {"var": "x"}}}}, "c": "c"}`, &object)) 178 | require.Equal(t, ObjectV{"a": StringV("a"), "b": ObjectV{"lambda": QueryV{lambda}}, "c": StringV("c")}, object) 179 | } 180 | 181 | func TestDeserializeVersionedQueryV(t *testing.T) { 182 | var query QueryV 183 | 184 | lambda1 := json.RawMessage(`{"lambda": "x", "api_version": "3", "expr": {"var": "x"}}`) 185 | 186 | require.NoError(t, decodeJSON(`{"@query": {"lambda": "x", "api_version": "3", "expr": {"var": "x"}}}`, &query)) 187 | require.Equal(t, QueryV{lambda1}, query) 188 | 189 | } 190 | 191 | func TestDeserializeInvalidQueryV(t *testing.T) { 192 | var object ObjectV 193 | 194 | require.EqualError(t, 195 | decodeJSON(`{"query":{"@query": {}, "invalid":"what?"}`, &object), 196 | `Expected end of object but got "invalid"`) 197 | } 198 | 199 | func TestDeserializeSetRefV(t *testing.T) { 200 | var setRef SetRefV 201 | 202 | json := ` 203 | { 204 | "@set": { 205 | "match": {"@ref":{"id":"spells_by_element","collection":{"@ref":{"id":"indexes"}}}}, 206 | "terms": "fire" 207 | } 208 | } 209 | ` 210 | 211 | require.NoError(t, decodeJSON(json, &setRef)) 212 | 213 | require.Equal(t, 214 | SetRefV{ObjectV{ 215 | "match": RefV{"spells_by_element", NativeIndexes(), NativeIndexes(), nil}, 216 | "terms": StringV("fire"), 217 | }}, 218 | setRef, 219 | ) 220 | } 221 | 222 | func TestDecodeEmptyValue(t *testing.T) { 223 | var str string 224 | var value StringV 225 | 226 | require.NoError(t, value.Get(&str)) 227 | require.Equal(t, "", str) 228 | } 229 | 230 | func TestDeserializeArrayV(t *testing.T) { 231 | var array ArrayV 232 | 233 | require.NoError(t, decodeJSON("[1]", &array)) 234 | require.Equal(t, ArrayV{LongV(1)}, array) 235 | } 236 | 237 | func TestDeserializeArray(t *testing.T) { 238 | var array []int64 239 | 240 | require.NoError(t, decodeJSON("[1, 2, 3]", &array)) 241 | require.Equal(t, []int64{1, 2, 3}, array) 242 | } 243 | 244 | func TestDeserializeEmptyArray(t *testing.T) { 245 | var array []int64 246 | 247 | require.NoError(t, decodeJSON("[]", &array)) 248 | require.Empty(t, array) 249 | } 250 | 251 | func TestDeserializeArrayOnInvalidTarget(t *testing.T) { 252 | var wrongReference map[string]string 253 | 254 | require.EqualError(t, 255 | decodeJSON("[]", &wrongReference), 256 | "Error while decoding fauna value at: . Can not decode array into a value of type \"map[string]string\"", 257 | ) 258 | } 259 | 260 | func TestDeserializeObjectV(t *testing.T) { 261 | var object ObjectV 262 | 263 | require.NoError(t, decodeJSON(`{ "key": "value" }`, &object)) 264 | require.Equal(t, ObjectV{"key": StringV("value")}, object) 265 | } 266 | 267 | func TestDeserializeObject(t *testing.T) { 268 | var object map[string]string 269 | 270 | require.NoError(t, decodeJSON(`{ "key": "value"} `, &object)) 271 | require.Equal(t, map[string]string{"key": "value"}, object) 272 | } 273 | 274 | func TestDeserializeObjectLiteral(t *testing.T) { 275 | var object map[string]string 276 | 277 | require.NoError(t, decodeJSON(`{ "@obj": { "@name": "Test" } }`, &object)) 278 | require.Equal(t, map[string]string{"@name": "Test"}, object) 279 | } 280 | 281 | func TestDeserializeEmptyObject(t *testing.T) { 282 | var object map[string]string 283 | 284 | require.NoError(t, decodeJSON(`{}`, &object)) 285 | require.Empty(t, object) 286 | } 287 | 288 | func TestDeserializeObjectOnInvalidTarget(t *testing.T) { 289 | var wrongReference []string 290 | 291 | require.EqualError(t, 292 | decodeJSON("{}", &wrongReference), 293 | "Error while decoding fauna value at: . Can not decode map into a value of type \"[]string\"", 294 | ) 295 | } 296 | 297 | func TestDeserializeStruct(t *testing.T) { 298 | var object struct{ Name string } 299 | 300 | require.NoError(t, decodeJSON(`{ "Name": "Jhon" }`, &object)) 301 | require.Equal(t, struct{ Name string }{"Jhon"}, object) 302 | } 303 | 304 | func TestDeserializeStructWithTags(t *testing.T) { 305 | type object struct { 306 | Name string `fauna:"name"` 307 | Age int64 `fauna:"age"` 308 | } 309 | 310 | var obj object 311 | 312 | require.NoError(t, decodeJSON(`{ "name": "Jhon", "age": 10 }`, &obj)) 313 | require.Equal(t, object{"Jhon", 10}, obj) 314 | } 315 | 316 | func TestDeserializeStructWithIgnoredFields(t *testing.T) { 317 | type object struct { 318 | Name string `fauna:"name"` 319 | Age int64 `fauna:"-"` 320 | } 321 | 322 | var obj object 323 | 324 | require.NoError(t, decodeJSON(`{ "name": "Jhon", "age": 10 }`, &obj)) 325 | require.Equal(t, object{"Jhon", 0}, obj) 326 | } 327 | 328 | func TestDeserializeStructWithPointers(t *testing.T) { 329 | type inner struct{ Name string } 330 | type object struct{ Inner *inner } 331 | 332 | var emptyObject, emptyInnerObject, obj *object 333 | 334 | require.NoError(t, decodeJSON(`{}`, &emptyObject)) 335 | require.Equal(t, &object{}, emptyObject) 336 | 337 | require.NoError(t, decodeJSON(`{ "Inner": {} }`, &emptyInnerObject)) 338 | require.Equal(t, &object{&inner{}}, emptyInnerObject) 339 | 340 | require.NoError(t, decodeJSON(`{ "Inner": { "Name": "Jhon"} }`, &obj)) 341 | require.Equal(t, &object{&inner{"Jhon"}}, obj) 342 | } 343 | 344 | func TestDeserializeStructWithEmbeddedStructs(t *testing.T) { 345 | type Embedded struct { 346 | Str string 347 | } 348 | 349 | type Data struct { 350 | Int int 351 | Embedded 352 | } 353 | 354 | var data Data 355 | 356 | require.NoError(t, decodeJSON(`{"Int":42,"Embedded":{"Str":"a string"}}`, &data)) 357 | require.Equal(t, Data{42, Embedded{"a string"}}, data) 358 | } 359 | 360 | func TestIgnoresUnmapedNamesInStruct(t *testing.T) { 361 | var object struct{ Name string } 362 | 363 | require.NoError(t, decodeJSON(`{ "Name": "Jhon", "SomeOtherThing": 42 }`, &object)) 364 | require.Equal(t, struct{ Name string }{"Jhon"}, object) 365 | } 366 | 367 | func TestIgnoresPrivateMembersOfStruct(t *testing.T) { 368 | type object struct { 369 | name string 370 | SomeOtherThing int64 371 | } 372 | 373 | var obj object 374 | 375 | require.NoError(t, decodeJSON(`{ "name": "Jhon", "SomeOtherThing": 42 }`, &obj)) 376 | require.Equal(t, object{"", 42}, obj) 377 | } 378 | 379 | func TestReportErrorPath(t *testing.T) { 380 | var obj struct{ Arr []int } 381 | var aMap map[string]int 382 | 383 | require.EqualError(t, 384 | decodeJSON(`{ "Arr": [1, "right"] }`, &obj), 385 | "Error while decoding fauna value at: Arr / 1. Can not assign value of type \"faunadb.StringV\" to a value of type \"int\"", 386 | ) 387 | 388 | require.EqualError(t, 389 | decodeJSON(`{ "One": 1, "Two": "2" }`, &aMap), 390 | "Error while decoding fauna value at: Two. Can not assign value of type \"faunadb.StringV\" to a value of type \"int\"", 391 | ) 392 | } 393 | 394 | func TestDeserializeNullV(t *testing.T) { 395 | var null NullV 396 | 397 | require.NoError(t, decodeJSON(`null`, &null)) 398 | require.Equal(t, NullV{}, null) 399 | } 400 | 401 | func TestDeserializeNull(t *testing.T) { 402 | var null string 403 | var pointer *string 404 | 405 | require.NoError(t, decodeJSON(`null`, &null)) 406 | require.NoError(t, decodeJSON(`null`, &pointer)) 407 | 408 | require.Equal(t, "", null) 409 | require.Nil(t, pointer) 410 | } 411 | 412 | func TestDeserializeComplexStruct(t *testing.T) { 413 | type nestedStruct struct { 414 | Nested string 415 | } 416 | 417 | type complexStruct struct { 418 | NonExistingField int 419 | nonPublicField int 420 | TaggedString string `fauna:"tagged"` 421 | Any Value 422 | Ref RefV 423 | Date time.Time 424 | Time time.Time 425 | LiteralObj map[string]string 426 | Str string 427 | Num int 428 | Float float64 429 | Boolean bool 430 | IntArr []int 431 | ObjArr []nestedStruct 432 | Matrix [][]int 433 | Map map[string]string 434 | Object nestedStruct 435 | Null *nestedStruct 436 | } 437 | 438 | json := ` 439 | { 440 | "Ref": { 441 | "@ref":{"id":"42","collection":{"@ref":{"id":"spells","collection":{"@ref":{"id":"collections"}}}}} 442 | }, 443 | "Any": "any value", 444 | "Date": { "@date": "1970-01-03" }, 445 | "Time": { "@ts": "1970-01-01T00:00:00.000000005Z" }, 446 | "LiteralObj": { "@obj": {"@name": "@Jhon" } }, 447 | "tagged": "TaggedString", 448 | "Str": "Jhon Knows", 449 | "Num": 31, 450 | "Float": 31.1, 451 | "Boolean": true, 452 | "IntArr": [1, 2, 3], 453 | "ObjArr": [{"Nested": "object1"}, {"Nested": "object2"}], 454 | "Matrix": [[1, 2], [3, 4]], 455 | "Map": { 456 | "key": "value" 457 | }, 458 | "Object": { 459 | "Nested": "object" 460 | }, 461 | "Null": null 462 | } 463 | ` 464 | r1 := &RefV{"spells", NativeCollections(), NativeCollections(), nil} 465 | 466 | expected := complexStruct{ 467 | TaggedString: "TaggedString", 468 | Ref: RefV{"42", r1, r1, nil}, 469 | Any: StringV("any value"), 470 | Date: time.Date(1970, time.January, 3, 0, 0, 0, 0, time.UTC), 471 | Time: time.Date(1970, time.January, 1, 0, 0, 0, 5, time.UTC), 472 | LiteralObj: map[string]string{"@name": "@Jhon"}, 473 | Str: "Jhon Knows", 474 | Num: 31, 475 | Float: 31.1, 476 | Boolean: true, 477 | IntArr: []int{ 478 | 1, 2, 3, 479 | }, 480 | ObjArr: []nestedStruct{ 481 | {"object1"}, 482 | {"object2"}, 483 | }, 484 | Matrix: [][]int{ 485 | {1, 2}, 486 | {3, 4}, 487 | }, 488 | Map: map[string]string{"key": "value"}, 489 | Object: nestedStruct{"object"}, 490 | Null: nil, 491 | } 492 | 493 | var object complexStruct 494 | 495 | require.NoError(t, decodeJSON(json, &object)) 496 | require.Equal(t, expected, object) 497 | } 498 | 499 | func TestDeserializeStructWithOmitEmptyTags(t *testing.T) { 500 | type object struct { 501 | Name string `fauna:"name,omitempty"` 502 | Age int64 `fauna:"age,omitempty"` 503 | } 504 | 505 | var obj object 506 | 507 | require.NoError(t, decodeJSON(`{ "name": "John", "age": 0 }`, &obj)) 508 | require.Equal(t, object{"John", 0}, obj) 509 | } 510 | 511 | func decodeJSON(raw string, target interface{}) (err error) { 512 | buffer := []byte(raw) 513 | 514 | var value Value 515 | 516 | if value, err = parseJSON(bytes.NewReader(buffer)); err == nil { 517 | err = value.Get(&target) 518 | } 519 | 520 | return 521 | } 522 | --------------------------------------------------------------------------------