github.com/TrueBlocks/trueblocks-core/src/apps/chifra@v0.0.0-20241022031540-b362680128f7/internal/chunks/validate.go (about)

     1  // Copyright 2021 The TrueBlocks Authors. All rights reserved.
     2  // Use of this source code is governed by a license that can
     3  // be found in the LICENSE file.
     4  
     5  package chunksPkg
     6  
     7  import (
     8  	"errors"
     9  	"fmt"
    10  	"os"
    11  	"path/filepath"
    12  	"strings"
    13  
    14  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base"
    15  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/config"
    16  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/file"
    17  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/index"
    18  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/logger"
    19  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/pinning"
    20  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/validate"
    21  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/version"
    22  )
    23  
    24  func (opts *ChunksOptions) validateChunks() error {
    25  	chain := opts.Globals.Chain
    26  
    27  	opts.testLog()
    28  
    29  	if opts.BadFlag != nil {
    30  		return opts.BadFlag
    31  	}
    32  
    33  	if opts.Globals.IsApiMode() {
    34  		if len(opts.Tag) > 0 {
    35  			return validate.Usage("The {0} option is not available{1}.", "--tag", " in api mode")
    36  		}
    37  		if opts.Truncate != base.NOPOSN {
    38  			return validate.Usage("The {0} option is not available{1}.", "--truncate", " in api mode")
    39  		}
    40  		if opts.Mode == "pins" {
    41  			return validate.Usage("The {0} mode is not available{1}.", "pins", " in api mode")
    42  		}
    43  	} else if len(opts.Tag) > 0 {
    44  		if !version.IsValidVersion(opts.Tag) {
    45  			return validate.Usage("The {0} ({1}) must be a valid version string.", "--tag", opts.Tag)
    46  		}
    47  		if !config.KnownVersionTag(opts.Tag) {
    48  			return validate.Usage("The only valid value for {0} is {1}.", "--tag", "trueblocks-core@v2.0.0-release")
    49  		}
    50  	}
    51  
    52  	if opts.Mode == "pins" {
    53  		if !opts.List && !opts.Unpin && !opts.Count {
    54  			return validate.Usage("{0} mode requires {1}.", "pins", "either --list, --count, or --unpin")
    55  		}
    56  
    57  		if opts.Unpin {
    58  			if !file.FileExists(filepath.Join(".", "unpins")) {
    59  				return validate.Usage("The file {0} was not found in the local folder.", "./unpins")
    60  			}
    61  
    62  			hasOne := false
    63  			lines := file.AsciiFileToLines(filepath.Join(".", "unpins"))
    64  			for _, line := range lines {
    65  				if pinning.IsValid(line) {
    66  					hasOne = true
    67  					break
    68  				}
    69  			}
    70  			if !hasOne {
    71  				return validate.Usage("The {0} file does not contain any valid CIDs.", "./unpins")
    72  			}
    73  		}
    74  
    75  	} else {
    76  		if opts.List {
    77  			return validate.Usage("The {0} option is only available in {1} mode.", "--list", "pins")
    78  		} else if opts.Unpin {
    79  			return validate.Usage("The {0} option is only available in {1} mode.", "--unpin", "pins")
    80  		}
    81  	}
    82  
    83  	if opts.Count {
    84  		// TODO: Shouldn't this use validate.Enums?
    85  		if !strings.Contains("manifest|index|blooms|pins|stats", opts.Mode) {
    86  			return validate.Usage("The {0} option is only available in {1}.", "--count", "these modes: manifest|index|blooms|pins|stats")
    87  		}
    88  	}
    89  
    90  	if !config.IsChainConfigured(chain) {
    91  		return validate.Usage("chain {0} is not properly configured.", chain)
    92  	}
    93  
    94  	err := validate.ValidateEnumRequired("mode", opts.Mode, "[manifest|index|blooms|pins|addresses|appearances|stats]")
    95  	if err != nil {
    96  		return err
    97  	}
    98  
    99  	if opts.Diff {
   100  		if opts.Mode != "index" {
   101  			return validate.Usage("The {0} option is only available in {1} mode.", "--diff", "index")
   102  		}
   103  		path := os.Getenv("TB_CHUNKS_DIFFPATH")
   104  		if path == "" {
   105  			return validate.Usage("The {0} option requires {1}.", "--diff", "TB_CHUNKS_DIFFPATH to be set")
   106  		}
   107  		if !file.FolderExists(path) {
   108  			return fmt.Errorf("the path TB_CHUNKS_DIFFPATH=%s does not exist", path)
   109  		}
   110  	}
   111  
   112  	isIndexOrManifest := opts.Mode == "index" || opts.Mode == "manifest"
   113  	if !isIndexOrManifest {
   114  		if err = opts.isDisallowed(!isIndexOrManifest /* i.e., true */, opts.Mode); err != nil {
   115  			return err
   116  		}
   117  
   118  		if opts.Check {
   119  			return validate.Usage("The {0} option is not available{1}.", "--check", " in "+opts.Mode+" mode")
   120  		}
   121  	}
   122  
   123  	isPin := opts.Pin
   124  	isCheck := opts.Check
   125  	isPublish := opts.Publish
   126  	isRemote := opts.Remote
   127  	isRewrite := opts.Rewrite
   128  	isDeep := opts.Deep
   129  
   130  	if !isIndexOrManifest && (isPin || isPublish || isRemote || isDeep || isCheck) {
   131  		return validate.Usage("The {0} options is only available in {1} or {2} mode.", "--pin, --publish, --remote, --check, and --deep", "manifest", "index")
   132  	}
   133  
   134  	if opts.Mode == "manifest" && opts.Remote {
   135  		// this is okay
   136  	} else if !isCheck && !isPin && (isRemote || isDeep) {
   137  		return validate.Usage("The {0} options require {1}.", "--remote and --deep", "--pin or --check")
   138  	}
   139  
   140  	if isPin {
   141  		if isRemote {
   142  			apiKey, secret, jwt := config.GetKey("pinata").ApiKey, config.GetKey("pinata").Secret, config.GetKey("pinata").Jwt
   143  			if secret == "" && jwt == "" {
   144  				return validate.Usage("Either the {0} key or the {1} is required.", "secret", "jwt")
   145  			}
   146  			if secret != "" && apiKey == "" {
   147  				return validate.Usage("If the {0} key is present, so must be the {1}.", "secret", "apiKey")
   148  			}
   149  			if config.GetPinning().RemotePinUrl == "" {
   150  				return validate.Usage("The {0} option requires {1}.", "--pin --remote", "a remotePinUrl")
   151  			}
   152  		} else {
   153  			if !config.IpfsRunning() {
   154  				return validate.Usage("The {0} option requires {1}.", "--pin", "a locally running IPFS daemon")
   155  			}
   156  			if config.GetPinning().LocalPinUrl == "" {
   157  				return validate.Usage("The {0} option requires {1}.", "--pin", "a localPinUrl")
   158  			}
   159  		}
   160  	} else if isRewrite {
   161  		return validate.Usage("The {0} option requires {1}.", "--rewrite", "--pin")
   162  	}
   163  
   164  	if opts.Publish {
   165  		return validate.Usage("The {0} option is currenlty unavailable.", "--publish")
   166  	}
   167  
   168  	if opts.Mode != "index" {
   169  		if len(opts.Tag) > 0 {
   170  			return validate.Usage("The {0} option is only available {1}.", "--tag", "in index mode")
   171  		}
   172  		if opts.Truncate != base.NOPOSN {
   173  			return validate.Usage("The {0} option is only available {1}.", "--truncate", "in index mode")
   174  		}
   175  		if len(opts.Belongs) > 0 {
   176  			return validate.Usage("The {0} option requires {1}.", "--belongs", "the index mode")
   177  		}
   178  
   179  	} else {
   180  		if len(opts.Belongs) > 0 {
   181  			err := validate.ValidateAtLeastOneAddr(opts.Belongs)
   182  			if err != nil {
   183  				return err
   184  			}
   185  			if opts.Globals.Verbose {
   186  				return validate.Usage("Choose either {0} or {1}, not both.", "--verbose", "--belongs")
   187  			}
   188  			if len(opts.Blocks) == 0 {
   189  				return validate.Usage("You must specify at least one {0} with the {1} option", "block identifier", "--belongs")
   190  			}
   191  			if opts.Globals.Format != "json" {
   192  				return validate.Usage("The {0} option only works with {1}", "--belongs", "--fmt json")
   193  			}
   194  		}
   195  	}
   196  
   197  	if err = opts.isDisallowed(opts.Globals.IsApiMode(), "API"); err != nil {
   198  		return err
   199  	}
   200  
   201  	if err = opts.isDisallowed(opts.Globals.TestMode, "test"); err != nil {
   202  		return err
   203  	}
   204  
   205  	if len(opts.Publisher) > 0 {
   206  		err := validate.ValidateExactlyOneAddr([]string{opts.Publisher})
   207  		if err != nil {
   208  			return err
   209  		}
   210  	}
   211  
   212  	err = validate.ValidateIdentifiers(
   213  		chain,
   214  		opts.Blocks,
   215  		validate.ValidBlockIdWithRangeAndDate,
   216  		1,
   217  		&opts.BlockIds,
   218  	)
   219  	if err != nil {
   220  		if invalidLiteral, ok := err.(*validate.InvalidIdentifierLiteralError); ok {
   221  			return invalidLiteral
   222  		}
   223  		if errors.Is(err, validate.ErrTooManyRanges) {
   224  			return validate.Usage("Specify only a single block range at a time.")
   225  		}
   226  		return err
   227  	}
   228  
   229  	if opts.Diff && len(opts.BlockIds) != 1 {
   230  		return validate.Usage("The {0} option requires exactly one block identifier.", "--diff")
   231  	}
   232  
   233  	if opts.FirstBlock != 0 || opts.LastBlock != base.NOPOSN || opts.MaxAddrs != base.NOPOS {
   234  		if opts.FirstBlock >= opts.LastBlock {
   235  			msg := fmt.Sprintf("first_block (%d) must be strictly earlier than last_block (%d).", opts.FirstBlock, opts.LastBlock)
   236  			return validate.Usage(msg)
   237  		}
   238  		if len(opts.Belongs) == 0 && opts.Mode != "addresses" && opts.Mode != "appearances" {
   239  			return validate.Usage("some options are only available with {0}.", "the addresses, the appearances, or the index --belongs modes")
   240  		}
   241  		// TODO: We should check that the first and last blocks are inside the ranges implied by the block ids
   242  		// if len(opts.BlockIds) > 0 {
   243  		// }
   244  	}
   245  
   246  	if err := index.IsInitialized(chain, config.ExpectedVersion()); err != nil {
   247  		isTag := len(opts.Tag) != 0
   248  		isRemoteMan := opts.Mode == "manifest" && opts.Remote
   249  		if !isTag && !isRemoteMan {
   250  			if (errors.Is(err, index.ErrNotInitialized) || errors.Is(err, index.ErrIncorrectHash)) &&
   251  				!opts.Globals.IsApiMode() {
   252  				logger.Fatal(err)
   253  			} else {
   254  				return err
   255  			}
   256  		}
   257  		// It's okay to mismatch versions if we're tagging or downloading a new manifest
   258  	}
   259  
   260  	return opts.Globals.Validate()
   261  }
   262  
   263  func (opts *ChunksOptions) isDisallowed(test bool, mode string) error {
   264  	if test {
   265  		if opts.Pin {
   266  			return validate.Usage("The {0} option is not available{1}.", "--pin", " in "+mode+" mode")
   267  		}
   268  		if opts.Publish {
   269  			return validate.Usage("The {0} option is not available{1}.", "--publish", " in "+mode+" mode")
   270  		}
   271  		if opts.Remote {
   272  			return validate.Usage("The {0} option is not available{1}.", "--remote", " in "+mode+" mode")
   273  		}
   274  		if opts.Truncate != base.NOPOSN {
   275  			return validate.Usage("The {0} option is not available{1}.", "--truncate", " in "+mode+" mode")
   276  		}
   277  	}
   278  	return nil
   279  }