Files
2026-07-13 13:14:29 +08:00

264 lines
6.4 KiB
Go

package info
import (
"fmt"
"strings"
str "github.com/trustwallet/assets-go-libs/strings"
"github.com/trustwallet/assets-go-libs/validation"
"github.com/trustwallet/go-primitives/coin"
"github.com/trustwallet/go-primitives/types"
)
// Asset info specific validators.
func ValidateAssetRequiredKeys(a AssetModel) error {
var fields []string
if a.Name != nil && !isEmpty(*a.Name) {
fields = append(fields, "name")
}
if a.Symbol != nil && !isEmpty(*a.Symbol) {
fields = append(fields, "symbol")
}
if a.Type != nil && !isEmpty(*a.Type) {
fields = append(fields, "type")
}
if a.Decimals != nil {
fields = append(fields, "decimals")
}
if a.Description != nil && !isEmpty(*a.Description) {
fields = append(fields, "description")
}
if a.Website != nil {
fields = append(fields, "website")
}
if a.Explorer != nil && !isEmpty(*a.Explorer) {
fields = append(fields, "explorer")
}
if a.Status != nil && !isEmpty(*a.Status) {
fields = append(fields, "status")
}
if a.ID != nil && !isEmpty(*a.ID) {
fields = append(fields, "id")
}
if len(fields) != len(requiredAssetFields) {
return fmt.Errorf("%w: %s", validation.ErrMissingField,
strings.Join(str.Difference(requiredAssetFields, fields), ", "))
}
return nil
}
func ValidateAssetType(assetType string, chain coin.Coin) error {
chainFromType, err := types.GetChainFromAssetType(assetType)
if err != nil {
return fmt.Errorf("failed to get chain from asset type: %w", err)
}
if chainFromType != chain {
return fmt.Errorf("%w: asset type field", validation.ErrInvalidField)
}
if strings.ToUpper(assetType) != assetType {
return fmt.Errorf("%w: asset type should be ALLCAPS", validation.ErrInvalidField)
}
return nil
}
func ValidateAssetID(id, address string) error {
if id != address {
if !strings.EqualFold(id, address) {
return fmt.Errorf("%w: invalid id field", validation.ErrInvalidField)
}
return fmt.Errorf("%w: invalid case for id field", validation.ErrInvalidField)
}
return nil
}
func ValidateAssetDecimalsAccordingType(assetType string, decimals int) error {
if assetType == "BEP2" && decimals != 8 {
return fmt.Errorf("%w: invalid decimals field, BEP2 tokens have 8 decimals", validation.ErrInvalidField)
}
return nil
}
// CoinModel info specific validators.
func ValidateCoinRequiredKeys(c CoinModel) error {
var fields []string
if c.Name != nil && !isEmpty(*c.Name) {
fields = append(fields, "name")
}
if c.Symbol != nil && !isEmpty(*c.Symbol) {
fields = append(fields, "symbol")
}
if c.Type != nil && !isEmpty(*c.Type) {
fields = append(fields, "type")
}
if c.Decimals != nil {
fields = append(fields, "decimals")
}
if c.Description != nil && !isEmpty(*c.Description) {
fields = append(fields, "description")
}
if c.Website != nil && !isEmpty(*c.Website) {
fields = append(fields, "website")
}
if c.Explorer != nil && !isEmpty(*c.Explorer) {
fields = append(fields, "explorer")
}
if c.Status != nil && !isEmpty(*c.Status) {
fields = append(fields, "status")
}
if len(fields) != len(requiredCoinFields) {
return fmt.Errorf("%w: %s", validation.ErrMissingField,
strings.Join(str.Difference(requiredCoinFields, fields), ", "))
}
return nil
}
func ValidateLinks(links []Link) error {
if len(links) < 2 {
return nil
}
for _, l := range links {
if l.Name == nil || l.URL == nil {
return fmt.Errorf("%w: missing required fields links.url and links.name", validation.ErrMissingField)
}
if !linkNameAllowed(*l.Name) {
return fmt.Errorf("invalid value for links.name filed, allowed only: %s",
strings.Join(supportedLinkNames(), ", "))
}
prefix := allowedLinkKeys[*l.Name]
if prefix != "" {
if !strings.HasPrefix(*l.URL, prefix) {
return fmt.Errorf("invalid value '%s' for %s link url, allowed only with prefix: %s",
*l.URL, *l.Name, prefix)
}
}
if !strings.HasPrefix(*l.URL, "https://") {
return fmt.Errorf("invalid value for links.url field, allowed only with https:// prefix")
}
if *l.Name == "medium" {
if !strings.Contains(*l.URL, "medium.com") {
return fmt.Errorf("invalid value for links.url field, should contain medium.com")
}
}
}
return nil
}
func ValidateCoinType(assetType string) error {
if assetType != "coin" {
return fmt.Errorf("%w: only \"coin\" type allowed for coin field", validation.ErrInvalidField)
}
return nil
}
func ValidateTags(tags, allowedTags []string) error {
for _, t := range tags {
if !str.Contains(t, allowedTags) {
return fmt.Errorf("%w: tag '%s' not allowed", validation.ErrInvalidField, t)
}
}
return nil
}
// Both infos can be validated by this validators.
func ValidateDecimals(decimals int) error {
if decimals > 30 || decimals < 0 {
return fmt.Errorf("%w: decimals field", validation.ErrInvalidField)
}
return nil
}
func ValidateStatus(status string) error {
for _, f := range allowedStatusValues {
if f == status {
return nil
}
}
return fmt.Errorf("%w: allowed status field values: %s", validation.ErrInvalidField,
strings.Join(allowedStatusValues, ", "))
}
func ValidateDescription(description string) error {
if len(description) > 600 {
return fmt.Errorf("%w: invalid length for description field", validation.ErrInvalidField)
}
for _, ch := range whiteSpaceCharacters {
if strings.Contains(description, ch) {
return fmt.Errorf("%w: description contains not allowed characters (new line, double space)",
validation.ErrInvalidField)
}
}
return nil
}
func ValidateDescriptionWebsite(description, website string) error {
if description != "-" && website == "" {
return fmt.Errorf("%w: website field", validation.ErrMissingField)
}
return nil
}
func ValidateExplorer(explorer, name string, chain coin.Coin, addr, tokenType string) error {
// explorer url is composed with values outside info.json.
if chain == coin.Nativeinjective() {
return nil
}
explorerExpected, err := coin.GetCoinExploreURL(chain, addr, tokenType)
if err != nil {
explorerExpected = ""
}
explorerActual := explorer
if !strings.EqualFold(explorerActual, explorerExpected) {
explorerAlt := explorerURLAlternatives(chain.Handle, name)
if len(explorerAlt) == 0 {
return nil
}
var matchCount int
for _, e := range explorerAlt {
if strings.EqualFold(e, explorerActual) {
matchCount++
}
}
if matchCount == 0 {
return fmt.Errorf("invalid value for explorer field, %s insted of %s", explorerActual, explorerExpected)
}
}
return nil
}
func isEmpty(field string) bool {
return field == ""
}