snapshot of https://github.com/cortezaproject/corteza.git (2024.9.x, 2026-09-18; blobs > 131072B omitted)
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s

This commit is contained in:
upstream-mirror
2026-09-18 01:22:23 +05:00
commit 7e385fc126
8391 changed files with 1766236 additions and 0 deletions

129
server/pkg/errors/error.go Normal file
View File

@@ -0,0 +1,129 @@
package errors
import (
"errors"
"fmt"
)
type (
Error struct {
// serves as base error differentiator and informs error handling process
// if it makes sense to retry later or with different payload (or not at all)
kind kind
// simple error message with basic information
message string
// captured call stack
stack []*frame
// additional details
meta meta
// if error wraps another error
wrap error
}
ErrorHandler func(error) error
)
const (
stackFrameSkip = 4
)
func err(k kind, m string) *Error {
return &Error{
kind: k,
message: m,
// skip 3 steps when collecting frames
stack: collectStack(stackFrameSkip),
}
}
// Plain returns plain *Error, without call stack and formatted message
func Plain(k kind, m string, a ...interface{}) *Error {
return &Error{
kind: k,
message: fmt.Sprintf(m, a...),
}
}
// New returns *Error
func New(k kind, m string, fn ...mfn) *Error {
return err(k, m).Apply(fn...)
}
// New returns *Error with formatted message
func Newf(k kind, m string, a ...interface{}) *Error {
return err(k, fmt.Sprintf(m, a...))
}
// safe to show details of this error
func (Error) Safe() bool { return true }
// Error message
func (e Error) Error() string {
return e.message
}
// Unwrap wrapped error
func (e Error) Unwrap() error {
return e.wrap
}
// Wrap error
func (e *Error) Wrap(err error) *Error {
e.wrap = err
return e
}
// Recollect stack frames
func (e *Error) Stack(skip int) *Error {
e.stack = collectStack(skip)
return e
}
// Returns error's meta
func (e Error) Meta() meta {
return e.meta
}
// Apply applies modifier functions
func (e *Error) Apply(ffn ...mfn) *Error {
for _, fn := range ffn {
fn(e)
}
return e
}
// Is provided Is() method for equality checking with errors.Is
func (e *Error) Is(target error) bool {
t, ok := target.(*Error)
if !ok {
return false
}
return t.kind == e.kind && t.message == e.message
}
// Unwrap is alias for errors.Unwrap so users can avoid importing both errors packages
//
// This function DOES NOT SUPPRESS errors if they are not wrapped!
func Unwrap(err error) error {
if err != nil && errors.Unwrap(err) != nil {
return errors.Unwrap(err)
}
return err
}
// Is is alias for errors.Is so users can avoid importing both errors packages
func Is(err, target error) bool {
return errors.Is(err, target)
}
// As is alias for errors.As so users can avoid importing both errors packages
func As(err error, target interface{}) bool {
return errors.As(err, target)
}

167
server/pkg/errors/http.go Normal file
View File

@@ -0,0 +1,167 @@
package errors
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"sort"
"strings"
"syscall"
"github.com/cortezaproject/corteza/server/pkg/locale"
)
type (
translatable interface {
Translate(tr func(string, string, ...string) string) error
}
)
// ServeHTTP Prepares and encodes given error for HTTP transport
//
// mask arg hides extra/debug info
//
// Proper HTTP status codes are generally not used in the API due to compatibility issues
// This should be addressed in the future versions when/if we restructure the API
func ServeHTTP(w http.ResponseWriter, r *http.Request, err error, mask bool) {
// due to backward compatibility,
// custom HTTP statuses are disabled for now.
ServeHTTPWithCode(w, r, http.StatusOK, err, mask)
}
// ProperlyServeHTTP Prepares and encodes given error for HTTP transport, same as ServeHTTP but with proper status codes
func ProperlyServeHTTP(w http.ResponseWriter, r *http.Request, err error, mask bool) {
var (
code = http.StatusInternalServerError
)
if e, is := err.(*Error); is {
code = e.kind.httpStatus()
}
ServeHTTPWithCode(w, r, code, err, mask)
}
// Serves error via
func ServeHTTPWithCode(w http.ResponseWriter, r *http.Request, code int, err error, mask bool) {
var (
// Very naive approach on parsing accept headers
acceptsJson = strings.Contains(r.Header.Get("accept"), "application/json")
)
if !mask && !acceptsJson {
// Prettify error for plain text debug output
w.Header().Set("Content-Type", "plain/text")
w.WriteHeader(code)
writeHttpPlain(w, err, mask)
_, _ = fmt.Fprintln(w, "Note: you are seeing this because system is running in development mode")
_, _ = fmt.Fprintln(w, "and HTTP request is made without \"Accept: .../json\" headers")
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
writeHttpJSON(r.Context(), w, err, mask)
}
func writeHttpPlain(w io.Writer, err error, mask bool) {
var (
dlmt = strings.Repeat("-", 80)
write = func(s string, a ...interface{}) {
if _, err := fmt.Fprintf(w, s, a...); err != nil {
panic(fmt.Errorf("failed to write HTTP response: %w", err))
}
}
)
// handle client-aborted connections with no output
if errors.Is(err, syscall.EPIPE) || errors.Is(err, syscall.ECONNRESET) {
return
}
write("Error: ")
write(err.Error())
write("\n")
if _, is := err.(interface{ Safe() bool }); !is || mask {
// do not output any details on un-safe errors or
// when a masked output is preferred
return
}
if err, is := err.(*Error); is {
write(dlmt + "\n")
var (
ml, kk = err.meta.StringKeys()
)
sort.Strings(kk)
for _, key := range kk {
write("%s:", key)
write(strings.Repeat(" ", ml-len(key)+1))
write("%v\n", err.meta[key])
}
if len(err.stack) > 0 {
write(dlmt + "\n")
write("Call stack:\n")
for i, f := range err.stack {
write("%3d. %s:%d\n %s()\n", len(err.stack)-i, f.File, f.Line, f.Func)
}
}
if we := err.Unwrap(); we != nil {
write(dlmt + "\n")
write("Wrapped error:\n\n")
writeHttpPlain(w, we, mask)
}
}
write(dlmt + "\n")
}
// writeHttpJSON
func writeHttpJSON(ctx context.Context, w io.Writer, err error, mask bool) {
var (
wrap = struct {
Error interface{} `json:"error"`
}{}
)
// handle client-aborted connections with no output
if errors.Is(err, syscall.EPIPE) || errors.Is(err, syscall.ECONNRESET) {
return
}
if se, is := err.(interface{ Safe() bool }); !is || !se.Safe() || mask {
// trim error details when not debugging or error is not safe or maske
err = errors.New(err.Error())
}
// if error is translatable, pass in the lambda that returns
// translated error
//
// we're using global locale service directly for now
if tr, is := err.(translatable); is {
err = tr.Translate(func(ns string, key string, pairs ...string) string {
return locale.Global().T(ctx, ns, key, pairs...)
})
}
if c, is := err.(json.Marshaler); is {
// take advantage of JSON marshaller on error
wrap.Error = c
} else {
wrap.Error = map[string]string{"message": err.Error()}
}
if err = json.NewEncoder(w).Encode(wrap); err != nil {
panic(fmt.Errorf("failed to encode error: %w", err))
}
}

View File

@@ -0,0 +1,90 @@
package errors
import (
"bytes"
"context"
"fmt"
"os"
"syscall"
"testing"
"github.com/stretchr/testify/require"
)
func Example_writeHttpPlain() {
writeHttpPlain(os.Stdout, fmt.Errorf("dummy error"), true)
// Output:
// Error: dummy error
}
func Example_writeHttpJSON() {
writeHttpJSON(context.Background(), os.Stdout, fmt.Errorf("dummy error"), true)
// Output:
// {"error":{"message":"dummy error"}}
}
func Example_writeHttpJSON_clientAbortedConnectionReset() {
writeHttpJSON(context.Background(), os.Stdout, syscall.ECONNRESET, true)
// Output:
}
func Example_writeHttpPlain_clientAbortedConnectionReset() {
writeHttpPlain(os.Stdout, syscall.ECONNRESET, true)
// Output:
}
func Example_writeHttpJSON_clientAbortedConnectionPipe() {
writeHttpJSON(context.Background(), os.Stdout, syscall.EPIPE, true)
// Output:
}
func Example_writeHttpPlain_clientAbortedConnectionPipe() {
writeHttpPlain(os.Stdout, syscall.EPIPE, true)
// Output:
}
func Example_writeHttpPlain_masked() {
err := New(0, "dummy error", Meta("a", "b"), Meta(&Error{}, "nope"))
err.stack = nil // will not test the stack as file path & line numbers might change
writeHttpPlain(os.Stdout, err, true)
// Output:
// Error: dummy error
}
func Example_writeHttpPlain_unmasked() {
err := New(0, "dummy error", Meta("a", "b"), Meta(&Error{}, "nope"))
err.stack = nil // will not test the stack as file path & line numbers might change
writeHttpPlain(os.Stdout, err, false)
// Output:
// Error: dummy error
// --------------------------------------------------------------------------------
// a: b
// --------------------------------------------------------------------------------
}
func Test_writeHttpJSON(t *testing.T) {
var (
err = New(0, "dummy error", Meta("meta", "meta"))
buf = bytes.NewBuffer(nil)
req = require.New(t)
)
buf.Truncate(0)
writeHttpJSON(context.Background(), buf, err, false)
req.Contains(buf.String(), "dummy error")
req.Contains(buf.String(), "meta")
req.Contains(buf.String(), "stack")
// when errors are masked (production env) we do not add meta or stack
buf.Truncate(0)
writeHttpJSON(context.Background(), buf, err, true)
req.Contains(buf.String(), "dummy error")
req.NotContains(buf.String(), "meta")
req.NotContains(buf.String(), "stack")
}

29
server/pkg/errors/json.go Normal file
View File

@@ -0,0 +1,29 @@
package errors
import (
"encoding/json"
"github.com/cortezaproject/corteza/server/pkg/locale"
)
func (e Error) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
Message string `json:"message"`
Meta meta `json:"meta,omitempty"`
Stack []*frame `json:"stack,omitempty"`
Wrap error `json:"wrap,omitempty"`
}{
Message: e.Error(),
Meta: e.meta,
Stack: e.stack,
Wrap: e.wrap,
})
}
func (e Error) Translate(tr func(string, string, ...string) string) error {
if msg := tr(e.meta.AsString(locale.ErrorMetaNamespace{}), e.meta.AsString(locale.ErrorMetaKey{}), e.meta.pairs()...); len(msg) > 0 {
e.message = msg
}
return &e
}

192
server/pkg/errors/kind.go Normal file
View File

@@ -0,0 +1,192 @@
package errors
import (
"fmt"
"net/http"
)
type (
kind uint8
)
const (
// internal (unspecified) error
// these errors yield HTTP status 500 and hide
// details unless in development environment
KindInternal kind = iota
// Data validation error
KindInvalidData
// Requested data not found
KindNotFound
// Stale data submitted
// Internally data was already updated or is locked in another session
KindStaleData
// Data already exists
KindDuplicateData
// Data sensitivity related issue
KindSensitiveData
// Access control
KindUnauthorized
// Expecting authenticated user
KindUnauthenticated
// External system failure
KindExternal
// store error
KindStore
// object store (file upload/download)
KindObjStore
// automation error
KindAutomation
)
// translates error kind into http status
func (k kind) httpStatus() int {
switch k {
case KindInvalidData, KindSensitiveData:
return http.StatusBadRequest
case KindNotFound:
return http.StatusNotFound
case KindStaleData:
return http.StatusConflict
case KindDuplicateData:
return http.StatusConflict
case KindUnauthorized:
return http.StatusUnauthorized
case KindUnauthenticated:
return http.StatusForbidden
default:
return http.StatusInternalServerError
}
}
func Internal(m string, aa ...interface{}) *Error {
return err(KindInternal, fmt.Sprintf(m, aa...))
}
func Store(m string, aa ...interface{}) *Error {
return err(KindStore, fmt.Sprintf(m, aa...))
}
func ObjStore(m string, aa ...interface{}) *Error {
return err(KindObjStore, fmt.Sprintf(m, aa...))
}
func InvalidData(m string, aa ...interface{}) *Error {
return err(KindInvalidData, fmt.Sprintf(m, aa...))
}
func NotFound(m string, aa ...interface{}) *Error {
return err(KindNotFound, fmt.Sprintf(m, aa...))
}
func StaleData(m string, aa ...interface{}) *Error {
return err(KindStaleData, fmt.Sprintf(m, aa...))
}
func DuplicateData(m string, aa ...interface{}) *Error {
return err(KindDuplicateData, fmt.Sprintf(m, aa...))
}
func SensitiveData(m string, aa ...interface{}) *Error {
return err(KindSensitiveData, fmt.Sprintf(m, aa...))
}
func Unauthorized(m string, aa ...interface{}) *Error {
return err(KindUnauthorized, fmt.Sprintf(m, aa...))
}
func Unauthenticated(m string, aa ...interface{}) *Error {
return err(KindUnauthenticated, fmt.Sprintf(m, aa...))
}
func External(m string, aa ...interface{}) *Error {
return err(KindExternal, fmt.Sprintf(m, aa...))
}
func Automation(m string, aa ...interface{}) *Error {
return err(KindAutomation, fmt.Sprintf(m, aa...))
}
func IsKind(err error, k kind) bool {
t, ok := err.(*Error)
if !ok {
return false
}
return t.kind == k
}
// IsAny returns true if error is of type *Error
func IsAny(err error) bool {
var is bool
if err != nil {
_, is = err.(*Error)
}
return is
}
func IsInternal(err error) bool {
return IsKind(err, KindInternal)
}
func IsStore(err error) bool {
return IsKind(err, KindStore)
}
func IsObjStore(err error) bool {
return IsKind(err, KindObjStore)
}
func IsInvalidData(err error) bool {
return IsKind(err, KindInvalidData)
}
func IsNotFound(err error) bool {
return IsKind(err, KindNotFound)
}
func IsStaleData(err error) bool {
return IsKind(err, KindStaleData)
}
func IsDuplicateData(err error) bool {
return IsKind(err, KindDuplicateData)
}
func IsSensitiveData(err error) bool {
return IsKind(err, KindSensitiveData)
}
func IsUnauthorized(err error) bool {
return IsKind(err, KindUnauthorized)
}
func IsUnauthenticated(err error) bool {
return IsKind(err, KindUnauthenticated)
}
func IsExternal(err error) bool {
return IsKind(err, KindExternal)
}
func IsAutomation(err error) bool {
return IsKind(err, KindAutomation)
}

86
server/pkg/errors/meta.go Normal file
View File

@@ -0,0 +1,86 @@
package errors
import (
"encoding/json"
"fmt"
"sort"
"github.com/spf13/cast"
)
type (
meta map[interface{}]interface{}
)
// StringKeys returns max length of (string) keys and slice of all strings
func (m meta) StringKeys() (int, []string) {
var (
l, ml int
key string
kk = make([]string, 0, len(m))
)
// collecting keys so that we can sort them to ensure
// stable order and find out max length of the key
for k := range m {
switch c := k.(type) {
case string:
key = c
case fmt.Stringer:
key = c.String()
default:
continue
}
kk = append(kk, key)
if l = len(key); l > ml {
ml = l
}
}
sort.Strings(kk)
return ml, kk
}
func (m meta) AsString(key interface{}) string {
if _, has := m[key]; has {
if s, ok := m[key].(string); ok {
return s
}
}
return ""
}
func (m meta) AsInt(key interface{}) int {
if _, has := m[key]; has {
if s, ok := m[key].(int); ok {
return s
}
}
return 0
}
func (m meta) MarshalJSON() ([]byte, error) {
o := make(map[string]interface{})
_, kk := m.StringKeys()
for _, k := range kk {
o[k] = m[k]
}
return json.Marshal(o)
}
// returns string pairs (both key & value are strings=
func (m meta) pairs() []string {
out := make([]string, 0, len(m)*2)
for k := range m {
if key, ok := k.(string); ok {
out = append(out, key, cast.ToString(m[k]))
}
}
return out
}

View File

@@ -0,0 +1,74 @@
package errors
type (
mfn func(*Error)
)
// Adds meta
func Meta(k, v interface{}) mfn {
return func(e *Error) {
if e.meta == nil {
e.meta = meta{}
}
e.meta[k] = v
}
}
// Trim all keys from meta
func MetaTrim(kk ...interface{}) mfn {
return func(e *Error) {
for _, k := range kk {
delete(e.meta, k)
}
}
}
// StackSkip skips n frames in the stack
//
func StackSkip(n int) mfn {
return func(e *Error) {
if n > len(e.stack) {
e.stack = nil
}
e.stack = e.stack[n:]
}
}
// StackTrim removes n frames from the end of the stack
func StackTrim(n int) mfn {
return func(e *Error) {
if len(e.stack) < n {
e.stack = nil
}
e.stack = e.stack[:len(e.stack)-n]
}
}
// StackTrimAtFn removes all frames after (inclusive) the (1st) function match
func StackTrimAtFn(fn string) mfn {
return func(e *Error) {
for i := range e.stack {
if i > 2 && e.stack[i].Func == fn {
e.stack = e.stack[:i]
break
}
}
}
}
// Wrap embeds error
func Wrap(w error) mfn {
return func(e *Error) {
e.wrap = w
}
}
// Converts and attaches node.js stack to error
func AddNodeStack(stack []string) mfn {
return func(e *Error) {
e.stack = convertNodeStack(stack)
}
}

View File

@@ -0,0 +1,85 @@
package errors
import (
"runtime"
"strconv"
"strings"
)
type (
frame struct {
Func string `json:"func"`
File string `json:"file"`
Line int `json:"line"`
}
)
const (
stackBuf = 128
)
func collectStack(skip int) []*frame {
var (
stack = make([]*frame, 0, stackBuf)
cc = make([]uintptr, stackBuf)
collected = runtime.Callers(skip, cc)
frames = runtime.CallersFrames(cc[:collected])
)
for {
f, n := frames.Next()
if !n {
break
}
aux := &frame{
Func: f.Function,
File: f.File,
Line: f.Line,
}
if li := strings.LastIndex(f.Function, "/"); li > 0 {
// remove prefix
aux.Func = aux.Func[li+1:]
}
stack = append(stack, aux)
}
return stack
}
// Converts node stack trace (from Error().stack) to internal structure
//
// Node stack traces are received with errors from Corredor (node.js) automation server
func convertNodeStack(nf []string) []*frame {
conv := make([]*frame, len(nf))
for i := range nf {
var (
f = &frame{}
file, lno string
)
if p := strings.Index(nf[i], " ("); p > 0 {
f.Func = nf[i][:p]
file = nf[i][p+2 : len(nf[i])-1]
} else {
file = nf[i]
}
parts := strings.Split(file, ":")
f.File = parts[0]
if len(parts) >= 2 {
lno = parts[1]
}
if lno != "" {
f.Line, _ = strconv.Atoi(lno)
}
conv[i] = f
}
return conv
}

View File

@@ -0,0 +1,47 @@
package errors
import (
"reflect"
"testing"
)
func Test_convertNodeStack(t *testing.T) {
converted := convertNodeStack([]string{
"Object.exec (/dev/corteza/server-corredor/usr/testing/server-scripts/foo.js:12:11)",
"Object.<anonymous> (/dev/corteza/server-corredor/node_modules/@cortezaproject/corteza-js/src/corredor/exec.ts:26:35)",
"Generator.next (<anonymous>)",
"/dev/corteza/server-corredor/node_modules/@cortezaproject/corteza-js/dist/index.js:277:71",
"new Promise (<anonymous>)",
"__awaiter (/dev/corteza/server-corredor/node_modules/@cortezaproject/corteza-js/dist/index.js:273:12)",
"Object.Exec (/dev/corteza/server-corredor/node_modules/@cortezaproject/corteza-js/dist/index.js:483:12)",
"ServerScripts.exec (/dev/corteza/server-corredor/src/services/server-scripts.ts:86:17)",
"Object.Exec (/dev/corteza/server-corredor/src/grpc-handlers/server-scripts.ts:142:11)",
"/dev/corteza/server-corredor/node_modules/grpc/src/server.js:593:13",
})
expecting := []*frame{
{"Object.exec", "/dev/corteza/server-corredor/usr/testing/server-scripts/foo.js", 12},
{"Object.<anonymous>", "/dev/corteza/server-corredor/node_modules/@cortezaproject/corteza-js/src/corredor/exec.ts", 26},
{"Generator.next", "<anonymous>", 0},
{"", "/dev/corteza/server-corredor/node_modules/@cortezaproject/corteza-js/dist/index.js", 277},
{"new Promise", "<anonymous>", 0},
{"__awaiter", "/dev/corteza/server-corredor/node_modules/@cortezaproject/corteza-js/dist/index.js", 273},
{"Object.Exec", "/dev/corteza/server-corredor/node_modules/@cortezaproject/corteza-js/dist/index.js", 483},
{"ServerScripts.exec", "/dev/corteza/server-corredor/src/services/server-scripts.ts", 86},
{"Object.Exec", "/dev/corteza/server-corredor/src/grpc-handlers/server-scripts.ts", 142},
{"", "/dev/corteza/server-corredor/node_modules/grpc/src/server.js", 593},
}
if len(expecting) != len(converted) {
t.Errorf("converted stack length (%d) not match expectations (%d)", len(converted), len(expecting))
}
for i := range expecting {
if !reflect.DeepEqual(converted[i], expecting[i]) {
t.Errorf("converted stack frame %d does not match\nexpecting: %v\ngot: %v", i, expecting[i], converted[i])
} else {
t.Logf("converted stack frame %d ok", i)
}
}
}