qmp-testing-suite/golang-producer-consumer/vendor/gitlab-app.eng.qops.net/golang/qmp/qsl/manager.go

231 lines
7.8 KiB
Go
Executable File

// Package qsl creates QMessages compliant with the QMP specification.
package qsl
import (
"encoding/json"
"errors"
"io/ioutil"
"strconv"
"sync"
"gitlab-app.eng.qops.net/golang/qmp/qsl/beacon"
"gitlab-app.eng.qops.net/golang/qmp/qsl/internal/qm"
"gitlab-app.eng.qops.net/golang/qmp/qsl/internal/schema"
)
// Instance is the singleton Manager.
var Instance Manager
var once sync.Once
// ErrNeedRegister indicates a NewReader or NewWriter without RegisterApplication.
var ErrNeedRegister = errors.New("cannot create QSL readers or writers without calling RegisterApplication first")
// ErrAlreadyRegistered indicates an additional attempt to RegisterApplication.
var ErrAlreadyRegistered = errors.New("qsl.RegisterApplication should only be called ONCE")
// Manager creates reader and writer QMessage generators.
type Manager interface {
NewReader(string) (Reader, error)
NewWriter(string) (Writer, error)
}
// Message is the QMessage interface for handling and serializing
// QMS data.
type Message interface {
// GetAvroObject returns the map[string]interface{} version of the data.
GetAvroObject() map[string]interface{}
// GetSerializedBytes attempts to serialize the message as avro binary for publishing.
GetSerializedBytes() ([]byte, error)
// GetWriterSchema returns the schema used to serialize the message.
GetWriterSchema() string
// GetMessageID returns the ID of the message and an error if it's unset.
GetMessageID() (string, error)
// GetTimestamp returns the timestamp of the message creation and an error
// if it's unset.
GetTimestamp() (int64, error)
// GetWriterHost gets the host ($QUALTRICSHOSTNAME) of the message's creator
// and an error if it's unset.
GetWriterHost() (string, error)
// GetWriterApp gets the client name (passed to qsl.RegisterApplication())
// of the message's creator and an error if it's unset.
GetWriterApp() (string, error)
// GetTraceID() returns the UUIDv4 from the message's creation and an error
// if it's unset.
GetTraceID() (string, error)
// GetOrganizationID() returns the organization ID of the message and an error
// if it's neither a string nor a nil.
GetOrganizationID() (string, error)
// SetWriterSchema changes the writer schema and message ID of the Message or
// returns a non-nil error if the Message is Using Original Bytes.
SetWriterSchema(string, string) error
// SetOrganizationID sets the organization ID of the Message if the Message is
// not Using Original Bytes.
SetOrganizationID(string) error
// SetWriterHost sets the WriterHost ($QUALTRICSHOSTNAME) of the Message if
// the Message is not Using Original Bytes.
SetWriterHost(string) error
// SetTraceID sets the TraceID (defaults to a UUIDv4) of the Message if the
// Message is not Using Original Bytes.
SetTraceID(string) error
// Get returns the value in the payload of the Message at the given key and
// an error if the key does not exist in the payload field.
Get(string) (interface{}, error)
// Set changes the value of the payload of the Message at the given key and
// returns an error if the payload key does not exist.
Set(string, interface{}) error
// GetPayload returns the payload field of the Message and an error if it does
// not exist.
GetPayload() (map[string]interface{}, error)
// GetPayload sets the payload field of the Message and returns an error if
// the new payload does not match the Writer Schema.
SetPayload(map[string]interface{}) error
// GetOriginalBytes returns the serialized avro bytes the message was originally
// parsed from and an error if the Message wasn't created by reading avro bytes.
GetOriginalBytes() ([]byte, error)
// GetReaderSchema returns the schema used to originally parse serialized avro
// bytes and an error if the Message wasn't created by reading avro bytes.
GetReaderSchema() (string, error)
// UseOriginalBytes sets whether the Message should be modifiable and
// serializable or if it should use the serialized avro bytes it parsed when
// created. Returns an error if set to true when the Message wasn't originally
// read from serialized bytes.
UseOriginalBytes(bool) error
// UsingOriginalBytes indicates whether the Message is modifiable or if it will
// only serialize to the bytes originally parsed to create the Message. Always
// returns false if the Message wasn't created by reading avro bytes.
UsingOriginalBytes() bool
}
// Reader generates QMessages from encoded avro values.
type Reader interface {
NewMessage([]byte) (Message, error)
}
// Writer generates QMessages from native data or existing Messages.
type Writer interface {
NewMessage() (Message, error)
NewFromObject(map[string]interface{}) (Message, error)
NewFromMessage(Message) (Message, error)
}
type schemaLocalManager interface {
Get(string) (string, error)
}
type qmManager struct {
appName string
schemaManager schemaLocalManager
}
// RegisterApplication creates a new Manager singleton and starts the version beacon.
func RegisterApplication(appName string) error {
if Instance != nil {
return ErrAlreadyRegistered
}
var err error
var manager Manager
manager, err = newManager(appName)
if err == nil {
once.Do(func() {
version := "unknown"
name := "qpcl-go-client"
beacon.Register(appName, name, version, beacon.MetricsPrefix)
Instance = manager
})
}
return err
}
// newManager creats a new Manager with a fixed app name.
func newManager(appName string) (*qmManager, error) {
manager, err := schema.NewManager()
if err != nil {
return nil, err
}
return &qmManager{appName: appName, schemaManager: manager}, nil
}
// GetLibraryVersion reads the file at the given path for a
// json formatted "id" (the name) and "version" (the library
// version). If any error occurs, such as the path not existing
// or being an invalid .json file, then it returns the default
// "unknown" version and "serialization" name.
func GetLibraryVersion(path string) (string, string) {
defaultVersion := "unknown"
defaultName := "serialization"
jsonBytes, err := ioutil.ReadFile(path)
if err != nil {
return defaultVersion, defaultName
}
var versionIDMap map[string]interface{}
err = json.Unmarshal(jsonBytes, &versionIDMap)
if err != nil {
return defaultVersion, defaultName
}
var name, version string
nameValue, ok := versionIDMap["id"]
if !ok {
name = defaultName
} else if nameString, ok := nameValue.(string); !ok {
name = defaultName
} else {
name = nameString
}
versionValue, ok := versionIDMap["version"]
if !ok {
version = defaultVersion
} else if versionFloat, ok := versionValue.(float64); ok {
version = strconv.FormatFloat(versionFloat, 'f', -1, 64)
} else {
version = defaultVersion
}
return version, name
}
// NewReader attempts to create a QMessage Reader that uses the
// schema spec specified by the given schema ID.
func NewReader(schemaID string) (Reader, error) {
if Instance == nil {
return nil, ErrNeedRegister
}
return Instance.NewReader(schemaID)
}
// qmManager.NewReader attempts to create a Reader with a
// given schema.
func (q *qmManager) NewReader(schemaID string) (Reader, error) {
schemaSpec, err := q.schemaManager.Get(schemaID)
if err != nil {
return nil, err
}
return newReader(schemaSpec, q.schemaManager), nil
}
// NewWriter attempts to create a QMessage Writer that uses the
// schema spec specified by the given schema ID.
func NewWriter(schemaID string) (Writer, error) {
if Instance == nil {
return nil, ErrNeedRegister
}
return Instance.NewWriter(schemaID)
}
// qmManager.NewWriter attempts to create a Writer with a
// given schema.
func (q *qmManager) NewWriter(schemaID string) (Writer, error) {
schemaSpec, err := q.schemaManager.Get(schemaID)
if err != nil {
return nil, err
}
return newWriter(schemaSpec, schemaID, q.appName, q.schemaManager), nil
}
// Union will create a goavro union given the key and value.
func Union(key string, value interface{}) interface{} {
return qm.Union(key, value)
}