ocket8888 commented on code in PR #7224:
URL: https://github.com/apache/trafficcontrol/pull/7224#discussion_r1048646070


##########
traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices.go:
##########
@@ -1584,12 +1591,53 @@ func Validate(tx *sql.Tx, ds *tc.DeliveryServiceV5) 
error {
        if err := validateTypeFields(tx, ds); err != nil {
                errs = append(errs, fmt.Errorf("type fields: %w", err))
        }
+       userErr, sysErr := validateRequiredCapabilities(tx, ds)
+       if userErr != nil {
+               errs = append(errs, errors.New("required capabilities: 
"+userErr.Error()))
+       }
+       if sysErr != nil {
+               errs = append(errs, errors.New("reading/ scanning required 
capabilities: "+sysErr.Error()))

Review Comment:
   constructing errors from errors should wrap them e.g. using `fmt.Errorf` 
with the `%w` formatting parameter.
   
   Also, this is returning a system-only error to the user, by appending it to 
a list of user-facing errors. If a system error occurs, a `500 Internal Server 
Error` must be written back to the client, and no information about server-side 
problems or configuration should be made visible to the client. The best way to 
do this would be to change this function to return two errors, one system and 
one user-facing, so that you can handle it appropriately e.g. with 
`api.HandleErr`, but you could also conceivably handle it by returning a "dummy 
error" from a package-internal constant and then the caller has to check that 
the returned error is that error using `errors.Is` and do something special in 
that case. I'm not convinced that this approach is less work than just changing 
the function's call signature, but however you want to accomplish that without 
ignoring the server-side error is fine.



##########
traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices.go:
##########
@@ -1584,12 +1591,53 @@ func Validate(tx *sql.Tx, ds *tc.DeliveryServiceV5) 
error {
        if err := validateTypeFields(tx, ds); err != nil {
                errs = append(errs, fmt.Errorf("type fields: %w", err))
        }
+       userErr, sysErr := validateRequiredCapabilities(tx, ds)
+       if userErr != nil {
+               errs = append(errs, errors.New("required capabilities: 
"+userErr.Error()))
+       }
+       if sysErr != nil {
+               errs = append(errs, errors.New("reading/ scanning required 
capabilities: "+sysErr.Error()))
+       }
        if len(errs) == 0 {
                return nil
        }
        return util.JoinErrs(errs)
 }
 
+func validateRequiredCapabilities(tx *sql.Tx, ds *tc.DeliveryServiceV5) 
(error, error) {
+       missing := make([]string, 0)
+       var missingCap string
+       query := `SELECT missing
+FROM (
+    SELECT UNNEST($1::TEXT[])
+    EXCEPT
+    SELECT UNNEST(ARRAY_AGG(name)) FROM server_capability) t(missing)

Review Comment:
   I think creating this anonymous table is entirely unnecessary. It seems that 
`SELECT col FROM (*table-valued expression with one column*) t(col)` would 
always be the same as just the `*table-valued expression with one column*`, 
because the expression only has one column and you are selecting that column 
from every row in the expression. Similarly, `UNNEST` converts an array into a 
set of elements and `ARRAY_AGG` aggregates a set of elements into an array, so 
they are more or less inverse functions. That is, `UNNEST(ARRAY_AGG(x))` is 
just the same as `x`, so there's no reason to wrap it those operations.



##########
traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices.go:
##########
@@ -1584,12 +1591,53 @@ func Validate(tx *sql.Tx, ds *tc.DeliveryServiceV5) 
error {
        if err := validateTypeFields(tx, ds); err != nil {
                errs = append(errs, fmt.Errorf("type fields: %w", err))
        }
+       userErr, sysErr := validateRequiredCapabilities(tx, ds)
+       if userErr != nil {
+               errs = append(errs, errors.New("required capabilities: 
"+userErr.Error()))
+       }
+       if sysErr != nil {
+               errs = append(errs, errors.New("reading/ scanning required 
capabilities: "+sysErr.Error()))
+       }
        if len(errs) == 0 {
                return nil
        }
        return util.JoinErrs(errs)
 }
 
+func validateRequiredCapabilities(tx *sql.Tx, ds *tc.DeliveryServiceV5) 
(error, error) {
+       missing := make([]string, 0)
+       var missingCap string
+       query := `SELECT missing
+FROM (
+    SELECT UNNEST($1::TEXT[])
+    EXCEPT
+    SELECT UNNEST(ARRAY_AGG(name)) FROM server_capability) t(missing)
+`
+       if len(ds.RequiredCapabilities) > 0 {
+               rows, err := tx.Query(query, pq.Array(ds.RequiredCapabilities))
+               if err != nil {
+                       return nil, err
+               }
+               defer rows.Close()
+               for rows.Next() {
+                       err = rows.Scan(&missingCap)
+                       if err != nil {
+                               return nil, err
+                       }
+                       missing = append(missing, missingCap)
+               }
+               if len(missing) > 0 {
+                       var msg string
+                       for _, m := range missing {
+                               msg = msg + m + " "
+                       }
+                       userErr := fmt.Errorf("the following capabilities do 
not exist: %s", msg)

Review Comment:
   there's a standard library function for this: 
[`strings.Join`](https://pkg.go.dev/strings#Join)



##########
traffic_ops/traffic_ops_golang/server/servers.go:
##########
@@ -825,10 +826,20 @@ func getServers(h http.Header, params map[string]string, 
tx *sqlx.Tx, user *auth
                }
 
                var joinSubQuery string
-               if err = 
tx.QueryRow(deliveryservice.HasRequiredCapabilitiesQuery, 
dsID).Scan(&dsHasRequiredCapabilities); err != nil {
+               rows, err := 
tx.Query(deliveryservice.GetRequiredCapabilitiesQuery, dsID)

Review Comment:
   Does the `HasRequiredCapabilitiesQuery` not work here anymore? I would 
assume that's because of the structural changes to the table(s). So should the 
old query just be deleted, since we know it doesn't work?
   
   It looks like this still returns a single row, so you could keep it shorter 
by continuing to use `QueryRow`.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to