File size: 5,942 Bytes
b110593
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
//                           _       _
// __      _____  __ ___   ___  __ _| |_ ___
// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
//  \ V  V /  __/ (_| |\ V /| | (_| | ||  __/
//   \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
//
//  Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
//
//  CONTACT: [email protected]
//

package validation

import (
	"context"
	"fmt"
	"strings"

	"github.com/go-openapi/strfmt"
	"github.com/weaviate/weaviate/entities/additional"
	"github.com/weaviate/weaviate/entities/models"
	"github.com/weaviate/weaviate/entities/schema/crossref"
	"github.com/weaviate/weaviate/usecases/config"
)

type exists func(_ context.Context, class string, _ strfmt.UUID, _ *additional.ReplicationProperties, _ string) (bool, error)

const (
	// ErrorMissingActionObjects message
	ErrorMissingActionObjects string = "no objects, object and subject, are added. Add 'objects' by using the 'objects' key in the root of the JSON"
	// ErrorMissingActionObjectsObject message
	ErrorMissingActionObjectsObject string = "no object-thing is added. Add the 'object' inside the 'objects' part of the JSON"
	// ErrorMissingActionObjectsSubject message
	ErrorMissingActionObjectsSubject string = "no subject-thing is added. Add the 'subject' inside the 'objects' part of the JSON"
	// ErrorMissingActionObjectsObjectLocation message
	ErrorMissingActionObjectsObjectLocation string = "no 'locationURL' is found in the object-thing. Add the 'locationURL' inside the 'object-thing' part of the JSON"
	// ErrorMissingActionObjectsObjectType message
	ErrorMissingActionObjectsObjectType string = "no 'type' is found in the object-thing. Add the 'type' inside the 'object-thing' part of the JSON"
	// ErrorInvalidActionObjectsObjectType message
	ErrorInvalidActionObjectsObjectType string = "object-thing requires one of the following values in 'type': '%s', '%s' or '%s'"
	// ErrorMissingActionObjectsSubjectLocation message
	ErrorMissingActionObjectsSubjectLocation string = "no 'locationURL' is found in the subject-thing. Add the 'locationURL' inside the 'subject-thing' part of the JSON"
	// ErrorMissingActionObjectsSubjectType message
	ErrorMissingActionObjectsSubjectType string = "no 'type' is found in the subject-thing. Add the 'type' inside the 'subject-thing' part of the JSON"
	// ErrorInvalidActionObjectsSubjectType message
	ErrorInvalidActionObjectsSubjectType string = "subject-thing requires one of the following values in 'type': '%s', '%s' or '%s'"
	// ErrorMisingObject message
	ErrorMissingObject = "the given object is empty"
	// ErrorMissingClass message
	ErrorMissingClass string = "the given class is empty"
	// ErrorMissingContext message
	ErrorMissingContext string = "the given context is empty"
	// ErrorNoExternalCredentials message
	ErrorNoExternalCredentials string = "no credentials available for the Weaviate instance for %s given in the %s"
	// ErrorExternalNotFound message
	ErrorExternalNotFound string = "given statuscode of '%s' is '%d', but 200 was expected for LocationURL given in the %s"
	// ErrorInvalidCRefType message
	ErrorInvalidCRefType string = "'cref' type '%s' does not exists"
	// ErrorNotFoundInDatabase message
	ErrorNotFoundInDatabase string = "%s: no object with id %s found"
	// ErrorInvalidProperties message
	ErrorInvalidProperties string = "properties of object %v must be of type map[string]interface"
)

type Validator struct {
	exists           exists
	config           *config.WeaviateConfig
	replicationProps *additional.ReplicationProperties
}

func New(exists exists, config *config.WeaviateConfig,

	repl *additional.ReplicationProperties,

) *Validator {
	return &Validator{
		exists:           exists,
		config:           config,
		replicationProps: repl,
	}
}

func (v *Validator) Object(ctx context.Context, class *models.Class,
	incoming *models.Object, existing *models.Object,
) error {
	if err := validateClass(incoming.Class); err != nil {
		return err
	}

	return v.properties(ctx, class, incoming, existing)
}

func validateClass(class string) error {
	// If the given class is empty, return an error
	if class == "" {
		return fmt.Errorf(ErrorMissingClass)
	}

	// No error
	return nil
}

// ValidateSingleRef validates a single ref based on location URL and existence of the object in the database
func (v *Validator) ValidateSingleRef(cref *models.SingleRef) (*crossref.Ref, error) {
	ref, err := crossref.ParseSingleRef(cref)
	if err != nil {
		return nil, fmt.Errorf("invalid reference: %w", err)
	}

	// target id must be lowercase
	ref.TargetID = strfmt.UUID(strings.ToLower(ref.TargetID.String()))

	if !ref.Local {
		return nil, fmt.Errorf("unrecognized cross-ref ref format")
	}

	return ref, nil
}

func (v *Validator) ValidateExistence(ctx context.Context, ref *crossref.Ref, errorVal string, tenant string) error {
	// locally check for object existence
	ok, err := v.exists(ctx, ref.Class, ref.TargetID, v.replicationProps, tenant)
	if err != nil {
		if tenant == "" {
			return err
		}
		// since refs can be created to non-MT classes, check again if non-MT object exists
		// (use empty tenant, if previously was given)
		ok2, err2 := v.exists(ctx, ref.Class, ref.TargetID, v.replicationProps, "")
		if err2 != nil {
			// return orig error
			return err
		}
		ok = ok2
	}
	if !ok {
		return fmt.Errorf(ErrorNotFoundInDatabase, errorVal, ref.TargetID)
	}

	return nil
}

func (v *Validator) ValidateMultipleRef(ctx context.Context, refs models.MultipleRef,
	errorVal string, tenant string,
) ([]*crossref.Ref, error) {
	parsedRefs := make([]*crossref.Ref, len(refs))

	if refs == nil {
		return parsedRefs, nil
	}

	for i, ref := range refs {
		parsedRef, err := v.ValidateSingleRef(ref)
		if err != nil {
			return nil, err
		}
		parsedRefs[i] = parsedRef

	}
	return parsedRefs, nil
}