Skip to content

Commit d841d93

Browse files
Merge pull request #184 from JordiParraCrespo/xrpl/feat/encode-decode-validate-mptoken-metadata
MPTokenMetadata encoding, decoding, and validation utilities
2 parents b5d47a9 + c69593e commit d841d93

File tree

6 files changed

+1560
-2
lines changed

6 files changed

+1560
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
#### xrpl
1313

14+
- `EncodeMPTokenMetadata`, `DecodeMPTokenMetadata` and `ValidateMPTokenMetadata` utils to encode, decode and validate MPTokenMetadata as per XLS-89 standard.
1415
- `AuthorizeChannel` to authorize a payment channel.
1516
- Added `Loan` and `LoanBroker` ledger entry types for the lending protocol.
1617
- Added loan transaction types:

pkg/typecheck/typecheck.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@ func IsStringNumericUint(s string, base, bitSize int) bool {
7474
return err == nil
7575
}
7676

77+
// IsMap checks if the given interface is a map.
78+
func IsMap(m any) bool {
79+
_, ok := m.(map[string]any)
80+
return ok
81+
}
82+
7783
// xrplNumberPattern matches optional sign, digits, optional decimal, optional exponent (scientific).
7884
// Allows leading zeros; rejects empty string, lone sign, or missing digits.
7985
var xrplNumberPattern = regexp.MustCompile(`^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?$`)

pkg/typecheck/typecheck_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,37 @@ func TestIsStringNumericUint(t *testing.T) {
501501
}
502502
}
503503

504+
func TestIsMap(t *testing.T) {
505+
tests := []struct {
506+
name string
507+
m interface{}
508+
want bool
509+
}{
510+
{
511+
name: "pass - Valid map",
512+
m: map[string]interface{}{},
513+
want: true,
514+
},
515+
{
516+
name: "pass - Invalid map",
517+
m: "Invalid map",
518+
want: false,
519+
},
520+
{
521+
name: "pass - Invalid map (2)",
522+
m: int(2),
523+
want: false,
524+
},
525+
}
526+
for _, tt := range tests {
527+
t.Run(tt.name, func(t *testing.T) {
528+
if got := IsMap(tt.m); got != tt.want {
529+
t.Errorf("IsMap(%v) = %v, want %v", tt.m, got, tt.want)
530+
}
531+
})
532+
}
533+
}
534+
504535
func TestIsXRPLNumber(t *testing.T) {
505536
tests := []struct {
506537
name string

xrpl/transaction/types/errors.go

Lines changed: 128 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
//revive:disable:var-naming
22
package types
33

4-
import "errors"
4+
import (
5+
"errors"
6+
"fmt"
7+
"strings"
8+
)
59

610
var (
711
// xchain bridge
@@ -55,4 +59,127 @@ var (
5559
ErrInvalidCredentialCount = errors.New("accepted credentials list must contain at least one and no more than the maximum allowed number of items")
5660
// ErrDuplicateCredentials is returned when duplicate credentials are present in the list.
5761
ErrDuplicateCredentials = errors.New("credentials list cannot contain duplicate elements")
62+
63+
// mptoken metadata
64+
65+
// ErrInvalidMPTokenMetadataHex is returned when the MPTokenMetadata field is not a hex string.
66+
ErrInvalidMPTokenMetadataHex = errors.New("mptoken metadata should be a hex string")
67+
68+
// ErrInvalidMPTokenMetadataJSON is returned when the MPTokenMetadata field is not a valid JSON object.
69+
ErrInvalidMPTokenMetadataJSON = errors.New("mptoken metadata should be a valid JSON object")
70+
71+
// ErrInvalidMPTokenMetadataSize is returned when the MPTokenMetadata field is longer than 1024 bytes.
72+
ErrInvalidMPTokenMetadataSize = errors.New("mptoken metadata byte length should be at most 1024 bytes")
73+
74+
// ErrInvalidMPTokenMetadataTicker is returned when the ticker does not match the required format (uppercase letters A-Z and digits 0-9, max 6 characters).
75+
ErrInvalidMPTokenMetadataTicker = errors.New("mptoken metadata ticker should contain only uppercase letters (A-Z) and digits (0-9), max 6 characters")
76+
77+
// ErrInvalidMPTokenMetadataRWASubClassRequired is returned when the asset subclass is required when the asset class is rwa.
78+
ErrInvalidMPTokenMetadataRWASubClassRequired = errors.New("mptoken metadata asset subclass is required when asset class is rwa")
79+
80+
// ErrInvalidMPTokenMetadataAdditionalInfo is returned when the additional info is not a string or a map.
81+
ErrInvalidMPTokenMetadataAdditionalInfo = errors.New("mptoken metadata additional info must be a string or a map")
82+
83+
// ErrInvalidMPTokenMetadataURIs is returned when the URIs is not an array of objects each with uri/u, category/c, and title/t properties.
84+
ErrInvalidMPTokenMetadataURIs = errors.New("mptoken metadata URIs should be an array of objects each with uri/u, category/c, and title/t properties")
5885
)
86+
87+
// ErrInvalidMPTokenMetadataUnknownField is returned when a field is unknown in MPToken metadata.
88+
type ErrInvalidMPTokenMetadataUnknownField struct {
89+
Field string
90+
}
91+
92+
// Error implements the error interface for ErrInvalidMPTokenMetadataUnknownField
93+
func (e ErrInvalidMPTokenMetadataUnknownField) Error() string {
94+
return fmt.Sprintf("mptoken metadata unknown field: %s", e.Field)
95+
}
96+
97+
// ErrInvalidMPTokenMetadataFieldCount is returned when the MPToken metadata has an invalid field count.
98+
type ErrInvalidMPTokenMetadataFieldCount struct {
99+
Count int
100+
}
101+
102+
// Error implements the error interface for ErrMarshalPayload
103+
func (e ErrInvalidMPTokenMetadataFieldCount) Error() string {
104+
return fmt.Sprintf("mptoken metadata field count should be at most %d", e.Count)
105+
}
106+
107+
// ErrInvalidMPTokenMetadataFieldCollision is returned when both long and compact forms of a field are present in MPToken metadata.
108+
type ErrInvalidMPTokenMetadataFieldCollision struct {
109+
Long string
110+
Compact string
111+
}
112+
113+
// Error implements the error interface for ErrInvalidMPTokenMetadataFieldCollision
114+
func (e ErrInvalidMPTokenMetadataFieldCollision) Error() string {
115+
return fmt.Sprintf("mptoken metadata field collision: %s and %s both present", e.Long, e.Compact)
116+
}
117+
118+
// ErrInvalidMPTokenMetadataMissingField is returned when a required field is missing from MPToken metadata.
119+
type ErrInvalidMPTokenMetadataMissingField struct {
120+
Field string
121+
}
122+
123+
// Error implements the error interface for ErrInvalidMPTokenMetadataMissingField
124+
func (e ErrInvalidMPTokenMetadataMissingField) Error() string {
125+
return fmt.Sprintf("mptoken metadata field missing: %s", e.Field)
126+
}
127+
128+
// ErrInvalidMPTokenMetadataInvalidString is returned when a string value is invalid.
129+
type ErrInvalidMPTokenMetadataInvalidString struct {
130+
Key string
131+
}
132+
133+
// Error implements the error interface for ErrInvalidMPTokenMetadataInvalidString
134+
func (e ErrInvalidMPTokenMetadataInvalidString) Error() string {
135+
return fmt.Sprintf("mptoken metadata field %s must be a valid string", e.Key)
136+
}
137+
138+
// ErrInvalidMPTokenMetadataEmptyString is returned when a string value is empty.
139+
type ErrInvalidMPTokenMetadataEmptyString struct {
140+
Key string
141+
}
142+
143+
// Error implements the error interface for ErrInvalidMPTokenMetadataEmptyString
144+
func (e ErrInvalidMPTokenMetadataEmptyString) Error() string {
145+
return fmt.Sprintf("mptoken metadata field %s cannot be empty", e.Key)
146+
}
147+
148+
// ErrInvalidMPTokenMetadataAssetClass is returned when the asset class is invalid.
149+
type ErrInvalidMPTokenMetadataAssetClass struct {
150+
AssetClassSet [6]string
151+
}
152+
153+
// Error implements the error interface for ErrInvalidMPTokenMetadataAssetClass
154+
func (e ErrInvalidMPTokenMetadataAssetClass) Error() string {
155+
return fmt.Sprintf("mptoken metadata asset class should be one of: %s", strings.Join(e.AssetClassSet[:], ", "))
156+
}
157+
158+
// ErrInvalidMPTokenMetadataAssetSubClass is returned when the asset subclass is invalid.
159+
type ErrInvalidMPTokenMetadataAssetSubClass struct {
160+
AssetSubclassSet []string
161+
}
162+
163+
// Error implements the error interface for ErrInvalidMPTokenMetadataAssetSubClass
164+
func (e ErrInvalidMPTokenMetadataAssetSubClass) Error() string {
165+
return fmt.Sprintf("mptoken metadata asset subclass should be one of: %s", strings.Join(e.AssetSubclassSet, ", "))
166+
}
167+
168+
// MPTokenMetadataValidationErrors is a custom error type that holds a list of validation failures.
169+
// It stores actual error objects to support wrapping/unwrapping.
170+
type MPTokenMetadataValidationErrors []error
171+
172+
// Error implements the error interface.
173+
func (v MPTokenMetadataValidationErrors) Error() string {
174+
var msgs []string
175+
for _, err := range v {
176+
msgs = append(msgs, err.Error())
177+
}
178+
return fmt.Sprintf("mptoken metadata validation failed with %d errors:\n- %s", len(v), strings.Join(msgs, "\n- "))
179+
}
180+
181+
// Unwrap returns the list of errors, allowing "errors.Is" support for lists.
182+
// Example: errors.Is(err, types.ErrInvalidMPTokenMetadataTicker)
183+
func (v MPTokenMetadataValidationErrors) Unwrap() []error {
184+
return v
185+
}

0 commit comments

Comments
 (0)