edge/pkg/module/share/module.go
William Petit 8e574c299b
All checks were successful
arcad/edge/pipeline/pr-master This commit looks good
feat(storage): rpc based implementation
2023-09-28 12:36:30 -06:00

343 lines
8.7 KiB
Go

package share
import (
"time"
"forge.cadoles.com/arcad/edge/pkg/app"
"forge.cadoles.com/arcad/edge/pkg/module/util"
"forge.cadoles.com/arcad/edge/pkg/storage/share"
"github.com/dop251/goja"
"github.com/pkg/errors"
)
const (
AnyType share.ValueType = "*"
AnyName string = "*"
)
type Module struct {
appID app.ID
store share.Store
}
func (m *Module) Name() string {
return "share"
}
func (m *Module) Export(export *goja.Object) {
if err := export.Set("upsertResource", m.upsertResource); err != nil {
panic(errors.Wrap(err, "could not set 'upsertResource' function"))
}
if err := export.Set("findResources", m.findResources); err != nil {
panic(errors.Wrap(err, "could not set 'findResources' function"))
}
if err := export.Set("deleteAttributes", m.deleteAttributes); err != nil {
panic(errors.Wrap(err, "could not set 'deleteAttributes' function"))
}
if err := export.Set("deleteResource", m.deleteResource); err != nil {
panic(errors.Wrap(err, "could not set 'deleteResource' function"))
}
if err := export.Set("ANY_TYPE", AnyType); err != nil {
panic(errors.Wrap(err, "could not set 'ANY_TYPE' property"))
}
if err := export.Set("ANY_NAME", AnyName); err != nil {
panic(errors.Wrap(err, "could not set 'ANY_NAME' property"))
}
if err := export.Set("TYPE_TEXT", share.TypeText); err != nil {
panic(errors.Wrap(err, "could not set 'TYPE_TEXT' property"))
}
if err := export.Set("TYPE_NUMBER", share.TypeNumber); err != nil {
panic(errors.Wrap(err, "could not set 'TYPE_NUMBER' property"))
}
if err := export.Set("TYPE_BOOL", share.TypeBool); err != nil {
panic(errors.Wrap(err, "could not set 'TYPE_BOOL' property"))
}
if err := export.Set("TYPE_PATH", share.TypePath); err != nil {
panic(errors.Wrap(err, "could not set 'TYPE_PATH' property"))
}
}
func (m *Module) upsertResource(call goja.FunctionCall, rt *goja.Runtime) goja.Value {
ctx := util.AssertContext(call.Argument(0), rt)
resourceID := assertResourceID(call.Argument(1), rt)
var attributes []share.Attribute
if len(call.Arguments) > 2 {
attributes = assertAttributes(call.Arguments[2:], rt)
} else {
attributes = make([]share.Attribute, 0)
}
for _, attr := range attributes {
if err := share.AssertType(attr.Value(), attr.Type()); err != nil {
panic(rt.ToValue(errors.WithStack(err)))
}
}
resource, err := m.store.UpdateAttributes(ctx, m.appID, resourceID, attributes...)
if err != nil {
panic(rt.ToValue(errors.WithStack(err)))
}
return rt.ToValue(toGojaResource(resource))
}
func (m *Module) deleteAttributes(call goja.FunctionCall, rt *goja.Runtime) goja.Value {
ctx := util.AssertContext(call.Argument(0), rt)
resourceID := assertResourceID(call.Argument(1), rt)
var names []string
if len(call.Arguments) > 2 {
names = assertStrings(call.Arguments[2:], rt)
} else {
names = make([]string, 0)
}
err := m.store.DeleteAttributes(ctx, m.appID, resourceID, names...)
if err != nil {
panic(rt.ToValue(errors.WithStack(err)))
}
return nil
}
func (m *Module) findResources(call goja.FunctionCall, rt *goja.Runtime) goja.Value {
ctx := util.AssertContext(call.Argument(0), rt)
funcs := make([]share.FindResourcesOptionFunc, 0)
if len(call.Arguments) > 1 {
name := util.AssertString(call.Argument(1), rt)
if name != AnyName {
funcs = append(funcs, share.WithName(name))
}
}
if len(call.Arguments) > 2 {
valueType := assertValueType(call.Argument(2), rt)
if valueType != AnyType {
funcs = append(funcs, share.WithType(valueType))
}
}
resources, err := m.store.FindResources(ctx, funcs...)
if err != nil {
panic(rt.ToValue(errors.WithStack(err)))
}
return rt.ToValue(toGojaResources(resources))
}
func (m *Module) deleteResource(call goja.FunctionCall, rt *goja.Runtime) goja.Value {
ctx := util.AssertContext(call.Argument(0), rt)
resourceID := assertResourceID(call.Argument(1), rt)
err := m.store.DeleteResource(ctx, m.appID, resourceID)
if err != nil {
panic(rt.ToValue(errors.WithStack(err)))
}
return nil
}
func ModuleFactory(appID app.ID, store share.Store) app.ServerModuleFactory {
return func(server *app.Server) app.ServerModule {
return &Module{
appID: appID,
store: store,
}
}
}
func assertResourceID(v goja.Value, r *goja.Runtime) share.ResourceID {
value := v.Export()
switch typ := value.(type) {
case string:
return share.ResourceID(typ)
case share.ResourceID:
return typ
default:
panic(r.ToValue(errors.Errorf("expected value to be a string or ResourceID, got '%T'", value)))
}
}
func assertAttributes(values []goja.Value, r *goja.Runtime) []share.Attribute {
attributes := make([]share.Attribute, len(values))
for idx, val := range values {
export := val.Export()
rawAttr, ok := export.(map[string]any)
if !ok {
panic(r.ToValue(errors.Errorf("unexpected attribute value, got '%v'", export)))
}
rawName, exists := rawAttr["name"]
if !exists {
panic(r.ToValue(errors.Errorf("could not find 'name' property on attribute '%v'", export)))
}
name, ok := rawName.(string)
if !ok {
panic(r.ToValue(errors.Errorf("unexpected value for attribute property 'name': expected 'string', got '%T'", rawName)))
}
rawType, exists := rawAttr["type"]
if !exists {
panic(r.ToValue(errors.Errorf("could not find 'type' property on attribute '%v'", export)))
}
var valueType share.ValueType
switch typ := rawType.(type) {
case share.ValueType:
valueType = typ
case string:
valueType = share.ValueType(typ)
default:
panic(r.ToValue(errors.Errorf("unexpected value for attribute property 'type': expected 'string' or 'ValueType', got '%T'", rawType)))
}
value, exists := rawAttr["value"]
if !exists {
panic(r.ToValue(errors.Errorf("could not find 'value' property on attribute '%v'", export)))
}
attributes[idx] = share.NewBaseAttribute(
name,
valueType,
value,
)
}
return attributes
}
func assertStrings(values []goja.Value, r *goja.Runtime) []string {
strings := make([]string, len(values))
for idx, v := range values {
strings[idx] = util.AssertString(v, r)
}
return strings
}
func assertValueType(v goja.Value, r *goja.Runtime) share.ValueType {
value := v.Export()
switch typ := value.(type) {
case string:
return share.ValueType(typ)
case share.ValueType:
return typ
default:
panic(r.ToValue(errors.Errorf("expected value to be a string or ValueType, got '%T'", value)))
}
}
type gojaResource struct {
ID share.ResourceID `goja:"id" json:"id"`
Origin app.ID `goja:"origin" json:"origin"`
Attributes []*gojaAttribute `goja:"attributes" json:"attributes"`
}
func (r *gojaResource) Has(call goja.FunctionCall, rt *goja.Runtime) goja.Value {
name := util.AssertString(call.Argument(0), rt)
valueType := assertValueType(call.Argument(1), rt)
hasAttr := share.HasAttribute(toResource(r), name, valueType)
return rt.ToValue(hasAttr)
}
func (r *gojaResource) Get(call goja.FunctionCall, rt *goja.Runtime) goja.Value {
name := util.AssertString(call.Argument(0), rt)
valueType := assertValueType(call.Argument(1), rt)
var defaultValue any
if len(call.Arguments) > 2 {
defaultValue = call.Argument(2).Export()
}
attr := share.GetAttribute(toResource(r), name, valueType)
if attr == nil {
return rt.ToValue(defaultValue)
}
return rt.ToValue(attr.Value())
}
type gojaAttribute struct {
Name string `goja:"name" json:"name"`
Type share.ValueType `goja:"type" json:"type"`
Value any `goja:"value" json:"value"`
CreatedAt time.Time `goja:"createdAt" json:"createdAt"`
UpdatedAt time.Time `goja:"updatedAt" json:"updatedAt"`
}
func toGojaResource(res share.Resource) *gojaResource {
attributes := make([]*gojaAttribute, len(res.Attributes()))
for idx, attr := range res.Attributes() {
attributes[idx] = &gojaAttribute{
Name: attr.Name(),
Type: attr.Type(),
Value: attr.Value(),
CreatedAt: attr.CreatedAt(),
UpdatedAt: attr.UpdatedAt(),
}
}
return &gojaResource{
ID: res.ID(),
Origin: res.Origin(),
Attributes: attributes,
}
}
func toGojaResources(resources []share.Resource) []*gojaResource {
gojaResources := make([]*gojaResource, len(resources))
for idx, res := range resources {
gojaResources[idx] = toGojaResource(res)
}
return gojaResources
}
func toResource(res *gojaResource) share.Resource {
return share.NewBaseResource(
res.Origin,
res.ID,
toAttributes(res.Attributes)...,
)
}
func toAttributes(gojaAttributes []*gojaAttribute) []share.Attribute {
attributes := make([]share.Attribute, len(gojaAttributes))
for idx, gojaAttr := range gojaAttributes {
attr := share.NewBaseAttribute(
gojaAttr.Name,
gojaAttr.Type,
gojaAttr.Value,
)
attr.SetCreatedAt(gojaAttr.CreatedAt)
attr.SetUpdatedAt(gojaAttr.UpdatedAt)
attributes[idx] = attr
}
return attributes
}