| // Copyright 2025 The etcd Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package validate |
| |
| import ( |
| "errors" |
| "fmt" |
| |
| "github.com/anishathalye/porcupine" |
| "go.uber.org/zap" |
| ) |
| |
| type RobustnessResult struct { |
| Assumptions Result |
| Linearization LinearizationResult |
| Watch Result |
| Serializable Result |
| } |
| |
| type Result struct { |
| Status ResultStatus |
| Message string |
| } |
| |
| type ResultStatus string |
| |
| var ( |
| Unknown ResultStatus |
| Success ResultStatus = "Success" |
| Failure ResultStatus = "Failure" |
| Timeout ResultStatus = "Timeout" |
| ) |
| |
| func (r RobustnessResult) Error() error { |
| if err := r.Assumptions.Error(); err != nil { |
| return fmt.Errorf("assumptions: %w", err) |
| } |
| if err := r.Linearization.Error(); err != nil { |
| return fmt.Errorf("linearization: %w", err) |
| } |
| if err := r.Watch.Error(); err != nil { |
| return fmt.Errorf("watch: %w", err) |
| } |
| if err := r.Serializable.Error(); err != nil { |
| return fmt.Errorf("serializable: %w", err) |
| } |
| return nil |
| } |
| |
| func ResultFromError(err error) Result { |
| if err != nil { |
| return Result{ |
| Status: Failure, |
| Message: err.Error(), |
| } |
| } |
| return Result{ |
| Status: Success, |
| } |
| } |
| |
| func (r Result) Error() error { |
| switch r.Status { |
| case Success, Unknown: |
| return nil |
| default: |
| return errors.New(r.String()) |
| } |
| } |
| |
| func (r Result) String() string { |
| if r.Message != "" { |
| return fmt.Sprintf("%s: %s", r.Status, r.Message) |
| } |
| return string(r.Status) |
| } |
| |
| type LinearizationResult struct { |
| Info porcupine.LinearizationInfo |
| Model porcupine.Model |
| Result |
| } |
| |
| func (r *LinearizationResult) Visualize(lg *zap.Logger, path string) error { |
| if r.Model.Step == nil && r.Model.StepContext == nil { |
| lg.Info("Skipping linearization visualization as model is not initialized") |
| return nil |
| } |
| err := r.Error() |
| if err != nil { |
| lg.Info("Skipping linearization visualization", zap.Error(err)) |
| return nil |
| } |
| |
| lg.Info("Saving visualization", zap.String("path", path)) |
| err = porcupine.VisualizePath(r.Model, r.Info, path) |
| if err != nil { |
| return fmt.Errorf("failed to visualize, err: %w", err) |
| } |
| return nil |
| } |
| |
| func (r *LinearizationResult) AddToVisualization(serializable []porcupine.Operation) { |
| annotations := []porcupine.Annotation{} |
| for _, op := range serializable { |
| annotations = append(annotations, porcupine.Annotation{ |
| ClientId: op.ClientId, |
| Start: op.Call, |
| End: op.Return, |
| Description: r.Model.DescribeOperation(op.Input, op.Output), |
| Details: r.Model.DescribeOperationMetadata(op.Metadata), |
| }) |
| } |
| r.Info.AddAnnotations(annotations) |
| } |