package query import ( "encoding/json" "fmt" "github.com/pkg/errors" ) // Operation denotes a single operation in a query. type Operation struct { ID OperationID `json:"id"` Spec OperationSpec `json:"spec"` } func (o *Operation) UnmarshalJSON(data []byte) error { type Alias Operation raw := struct { *Alias Kind OperationKind `json:"kind"` Spec json.RawMessage `json:"spec"` }{} err := json.Unmarshal(data, &raw) if err != nil { return err } if raw.Alias != nil { *o = *(*Operation)(raw.Alias) } spec, err := unmarshalOpSpec(raw.Kind, raw.Spec) if err != nil { return errors.Wrapf(err, "failed to unmarshal operation %q", o.ID) } o.Spec = spec return nil } func unmarshalOpSpec(k OperationKind, data []byte) (OperationSpec, error) { createOpSpec, ok := kindToOp[k] if !ok { return nil, fmt.Errorf("unknown operation spec kind %v", k) } spec := createOpSpec() if len(data) > 0 { err := json.Unmarshal(data, spec) if err != nil { return nil, err } } return spec, nil } func (o Operation) MarshalJSON() ([]byte, error) { type Alias Operation raw := struct { Kind OperationKind `json:"kind"` Alias }{ Kind: o.Spec.Kind(), Alias: (Alias)(o), } return json.Marshal(raw) } type NewOperationSpec func() OperationSpec // OperationSpec specifies an operation as part of a query. type OperationSpec interface { // Kind returns the kind of the operation. Kind() OperationKind } // OperationID is a unique ID within a query for the operation. type OperationID string // OperationKind denotes the kind of operations. type OperationKind string var kindToOp = make(map[OperationKind]NewOperationSpec) // RegisterOpSpec registers an operation spec with a given kind. // If the kind has already been registered the call panics. // // TODO:(nathanielc) make this part of RegisterMethod/RegisterFunction func RegisterOpSpec(k OperationKind, c NewOperationSpec) { if kindToOp[k] != nil { panic(fmt.Errorf("duplicate registration for operation kind %v", k)) } kindToOp[k] = c } func NumberOfOperations() int { return len(kindToOp) }