231 lines
7.8 KiB
Go
Executable File
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)
|
|
}
|