1
0
Fork 0
mirror of https://github.com/gocsaf/csaf.git synced 2025-12-22 18:15:42 +01:00
gocsaf/util/json.go
2023-01-22 20:55:26 +01:00

234 lines
5.5 KiB
Go

// This file is Free Software under the MIT License
// without warranty, see README.md and LICENSES/MIT.txt for details.
//
// SPDX-License-Identifier: MIT
//
// SPDX-FileCopyrightText: 2021 German Federal Office for Information Security (BSI) <https://www.bsi.bund.de>
// Software-Engineering: 2021 Intevation GmbH <https://intevation.de>
package util
import (
"context"
"encoding/json"
"errors"
"fmt"
"time"
"github.com/PaesslerAG/gval"
"github.com/PaesslerAG/jsonpath"
)
// ReMarshalJSON transforms data from src to dst via JSON marshalling.
func ReMarshalJSON(dst, src any) error {
intermediate, err := json.Marshal(src)
if err != nil {
return err
}
return json.Unmarshal(intermediate, dst)
}
// PathEval is a helper to evaluate JSON paths on documents.
type PathEval struct {
builder gval.Language
exprs map[string]gval.Evaluable
}
// NewPathEval creates a new PathEval.
func NewPathEval() *PathEval {
return &PathEval{
builder: gval.Full(jsonpath.Language()),
exprs: map[string]gval.Evaluable{},
}
}
// Eval evalutes expression expr on document doc.
// Returns the result of the expression.
func (pe *PathEval) Eval(expr string, doc any) (any, error) {
if doc == nil {
return nil, errors.New("no document to extract data from")
}
eval := pe.exprs[expr]
if eval == nil {
var err error
if eval, err = pe.builder.NewEvaluable(expr); err != nil {
return nil, err
}
pe.exprs[expr] = eval
}
return eval(context.Background(), doc)
}
// PathEvalMatcher is a pair of an expression and an action
// when doing extractions via PathEval.Match.
type PathEvalMatcher struct {
// Expr is the expression to evaluate
Expr string
// Action is executed with the result of the match.
Action func(any) error
// Optional expresses if the expression is optional.
Optional bool
}
// ReMarshalMatcher is an action to re-marshal the result to another type.
func ReMarshalMatcher(dst any) func(any) error {
return func(src any) error {
return ReMarshalJSON(dst, src)
}
}
// BoolMatcher stores the matched result in a bool.
func BoolMatcher(dst *bool) func(any) error {
return func(x any) error {
b, ok := x.(bool)
if !ok {
return errors.New("not a bool")
}
*dst = b
return nil
}
}
// StringMatcher stores the matched result in a string.
func StringMatcher(dst *string) func(any) error {
return func(x any) error {
s, ok := x.(string)
if !ok {
return errors.New("not a string")
}
*dst = s
return nil
}
}
// StringTreeMatcher returns a matcher which add strings from
// stringss and recursively from arrays from strings.
func StringTreeMatcher(strings *[]string) func(any) error {
// Only add unique strings.
unique := func(s string) {
for _, t := range *strings {
if s == t {
return
}
}
*strings = append(*strings, s)
}
var recurse func(any) error
recurse = func(x any) error {
switch y := x.(type) {
case string:
unique(y)
case []any:
for _, z := range y {
if err := recurse(z); err != nil {
return err
}
}
default:
return fmt.Errorf("unsupported type: %T", x)
}
return nil
}
return recurse
}
// TimeMatcher stores a time with a given format.
func TimeMatcher(dst *time.Time, format string) func(any) error {
return func(x any) error {
s, ok := x.(string)
if !ok {
return errors.New("not a string")
}
t, err := time.Parse(format, s)
if err != nil {
return nil
}
*dst = t
return nil
}
}
// Extract extracts a value from a given document with a given expression/action.
func (pe *PathEval) Extract(
expr string,
action func(any) error,
optional bool,
doc any,
) error {
optErr := func(err error) error {
if err == nil || optional {
return nil
}
return fmt.Errorf("extract failed '%s': %v", expr, err)
}
x, err := pe.Eval(expr, doc)
if err != nil {
return optErr(err)
}
return optErr(action(x))
}
// Match matches a list of PathEvalMatcher pairs against a document.
func (pe *PathEval) Match(matcher []PathEvalMatcher, doc any) error {
for _, m := range matcher {
if err := pe.Extract(m.Expr, m.Action, m.Optional, doc); err != nil {
return err
}
}
return nil
}
// StringsFromTree returns strings from the given exprs.
// 1. If a expression results a string this string is used.
// 2. if a expression results in an array the elements
// of this array are recursively treated with 1. and 2.
func (pe *PathEval) StringsFromTree(
exprs []string,
optional bool,
doc any,
) ([]string, error) {
results := make([]string, 0, len(exprs))
matcher := StringTreeMatcher(&results)
for _, expr := range exprs {
if err := pe.Extract(expr, matcher, optional, doc); err != nil {
return nil, err
}
}
return results, nil
}
// Strings searches the given document for the given set of expressions
// and returns the corresponding strings. The optional flag indicates
// if the expression evaluation have to succseed or not.
func (pe *PathEval) Strings(
exprs []string,
optional bool,
doc any,
) ([]string, error) {
results := make([]string, 0, len(exprs))
var result string
matcher := StringMatcher(&result)
for _, expr := range exprs {
if err := pe.Extract(expr, matcher, optional, doc); err != nil {
return nil, err
}
results = append(results, result)
}
return results, nil
}
// AsStrings transforms an []interface{string, string,... }
// to a []string.
func AsStrings(x any) ([]string, bool) {
strs, ok := x.([]any)
if !ok {
return nil, false
}
res := make([]string, 0, len(strs))
for _, y := range strs {
if s, ok := y.(string); ok {
res = append(res, s)
}
}
return res, true
}