// Copyright 2017 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package firestore import ( "errors" "fmt" "time" "github.com/golang/protobuf/ptypes" pb "google.golang.org/genproto/googleapis/firestore/v1" ) // A Precondition modifies a Firestore update or delete operation. type Precondition interface { // Returns the corresponding Precondition proto. preconditionProto() (*pb.Precondition, error) } // Exists is a Precondition that checks for the existence of a resource before // writing to it. If the check fails, the write does not occur. var Exists Precondition func init() { // Initialize here so godoc doesn't show the internal value. Exists = exists(true) } type exists bool func (e exists) preconditionProto() (*pb.Precondition, error) { return &pb.Precondition{ ConditionType: &pb.Precondition_Exists{bool(e)}, }, nil } func (e exists) String() string { if e { return "Exists" } return "DoesNotExist" } // LastUpdateTime returns a Precondition that checks that a resource must exist and // must have last been updated at the given time. If the check fails, the write // does not occur. func LastUpdateTime(t time.Time) Precondition { return lastUpdateTime(t) } type lastUpdateTime time.Time func (u lastUpdateTime) preconditionProto() (*pb.Precondition, error) { ts, err := ptypes.TimestampProto(time.Time(u)) if err != nil { return nil, err } return &pb.Precondition{ ConditionType: &pb.Precondition_UpdateTime{ts}, }, nil } func (u lastUpdateTime) String() string { return fmt.Sprintf("LastUpdateTime(%s)", time.Time(u)) } func processPreconditionsForDelete(preconds []Precondition) (*pb.Precondition, error) { // At most one option permitted. switch len(preconds) { case 0: return nil, nil case 1: return preconds[0].preconditionProto() default: return nil, fmt.Errorf("firestore: conflicting preconditions: %+v", preconds) } } func processPreconditionsForUpdate(preconds []Precondition) (*pb.Precondition, error) { // At most one option permitted, and it cannot be Exists. switch len(preconds) { case 0: // If the user doesn't provide any options, default to Exists(true). return exists(true).preconditionProto() case 1: if _, ok := preconds[0].(exists); ok { return nil, errors.New("cannot use Exists with Update") } return preconds[0].preconditionProto() default: return nil, fmt.Errorf("firestore: conflicting preconditions: %+v", preconds) } } func processPreconditionsForVerify(preconds []Precondition) (*pb.Precondition, error) { // At most one option permitted. switch len(preconds) { case 0: return nil, nil case 1: return preconds[0].preconditionProto() default: return nil, fmt.Errorf("firestore: conflicting preconditions: %+v", preconds) } } // A SetOption modifies a Firestore set operation. type SetOption interface { fieldPaths() (fps []FieldPath, all bool, err error) } // MergeAll is a SetOption that causes all the field paths given in the data argument // to Set to be overwritten. It is not supported for struct data. var MergeAll SetOption = merge{all: true} // Merge returns a SetOption that causes only the given field paths to be // overwritten. Other fields on the existing document will be untouched. It is an // error if a provided field path does not refer to a value in the data passed to // Set. func Merge(fps ...FieldPath) SetOption { for _, fp := range fps { if err := fp.validate(); err != nil { return merge{err: err} } } return merge{paths: fps} } type merge struct { all bool paths []FieldPath err error } func (m merge) String() string { if m.err != nil { return fmt.Sprintf("", m.err) } if m.all { return "MergeAll" } return fmt.Sprintf("Merge(%+v)", m.paths) } func (m merge) fieldPaths() (fps []FieldPath, all bool, err error) { if m.err != nil { return nil, false, m.err } if err := checkNoDupOrPrefix(m.paths); err != nil { return nil, false, err } if m.all { return nil, true, nil } return m.paths, false, nil } func processSetOptions(opts []SetOption) (fps []FieldPath, all bool, err error) { switch len(opts) { case 0: return nil, false, nil case 1: return opts[0].fieldPaths() default: return nil, false, fmt.Errorf("conflicting options: %+v", opts) } }