Object Display V2 문법
Display V2는 Sui Move object value를 사람이 읽을 수 있는 string, JSON 또는 encoded representation으로 render하기 위한 template language이다. Display object는 key와 value가 모두 format string인 key-value pair set을 정의한다.
Format string 형식
format string은 literal text와 { 및 }로 구분된 expression의 조합이다.
Hello, {name}! You have {balance} SUI.
literal brace를 출력하려면 두 번 쓴다. {{는 {를, }}는 }를 생성한다.
Expression 표현식
{...} 안의 각 expression은 3개 부분으로 구성된다.
{ chain | alternate | ... : transform }
^^^^^ ^^^^^^^^^ ^^^^^^^^^
primary fallbacks output format
- Chain:
|로 구분되며 left-to-right로 evaluate된다. 첫 번째 non-null result가 사용된다. - Transform: value가 render되는 방식을 제어하는 optional output format이며
:prefix를 사용한다.
모든 chain이 null로 evaluate되면 전체 format string은 null로 evaluate된다.
Access chain 접근 경로
chain은 object data 안으로 navigate한다. root object 또는 literal value에서 시작한 뒤 accessor sequence를 따른다.
root를 생략하면 chain은 현재 표시 중인 object에서 시작한다. $self로 해당 root object를 명시적으로 참조할 수도 있다.
{name} — equivalent to {$self.name}
{$self} — the root object itself
{$self.id} — explicit access from the root object
Field access 접근
dot notation으로 named field에 접근한다.
{name} — top-level field "name"
{inner.value} — nested field
{url.url.bytes} — deeply nested field
Positional access 접근
numeric index로 unnamed(positional) field에 접근한다.
{pos.0} — first positional field
{tuple.0.1} — nested positional access
vector 및 array indexing
[...]를 사용해 vector 또는 VecMap 안으로 index한다.
{items[0u64]} — first element
{items[2u32]} — third element (any numeric type works)
{items[idx]} — use another field's value as the index
{items[ns[0u64]]} — nested: use element of `ns` as index into `items`
VecMap<K, V>의 경우 index는 key와 match된다.
{scores[6u32]} — look up key 6u32 in the VecMap, returns the value
Dynamic field access (->) 접근
onchain storage에서 dynamic field를 load한다.
{parent->['key']} — dynamic field with string key 'key'
{parent->['key'].x} — access field 'x' on the loaded dynamic field value
{parent.id->['key']} — explicit UID access (equivalent if parent starts with UID)
{parent->[field]} — use a field's evaluated value as the dynamic field name
각 -> access는 budget에서 object load 1개를 사용한다.
Dynamic object field access (=>) 접근
dynamic object field를 load한다(value는 full Sui object).
{parent=>['key']} — dynamic object field with string key 'key'
{parent=>['key'].x} — access field 'x' on the loaded object
{parent.id=>['key'].id} — load the object and read its UID
각 => access는 object load 2개를 사용한다(wrapper Field 1개, object 1개).
Derived object access (~>) 접근
parent object와 derived key를 사용해 onchain storage에서 derived object를 load한다.
{parent~>['key']} — derived object with string key 'key'
{parent~>['key'].x} — access field 'x' on the loaded object
{parent.id~>['key'].id} — load the object and read its UID
{registry~>[$self]} — use the current object as the derived key
각 ~> access는 budget에서 object load 1개를 사용한다.
Literal value 값
Literal은 expression root로 나타날 수 있으며 dynamic field, dynamic object field, derived object lookup의 key로도 사용할 수 있다.
Scalar 값
| Syntax | Type | Example |
|---|---|---|
$self | Current object | {$self.id} |
true or false | bool | {true} |
42u8 | u8 | {255u8} |
1000u16 | u16 | {1000u16} |
100000u32 | u32 | {100000u32} |
999u64 | u64 | {999u64} |
123u128 | u128 | {123u128} |
42u256 | u256 | {42u256} |
@0x1abc | address | {@0x1abc} |
42 같은 bare number는 유효하지 않다. type suffix가 항상 필요하다.
String 값
| Syntax | Description |
|---|---|
'hello' | UTF-8 string (0x1::string::String) |
b'hello' | byte string(UTF-8 encoded bytes, vector<u8>) |
x'48656c6c6f' | hex byte string(vector<u8>) |
string은 single quote를 사용한다. 개별 byte에는 .bytes[i](string literal의 경우) 또는 direct indexing [i](byte literal의 경우)로 접근한다.
{'ABC'.bytes[0u64]} — 65 (ASCII 'A')
{b'ABC'[1u64]} — 66 (ASCII 'B')
Vector 값
{vector[1u8, 2u8, 3u8]} — inferred element type
{vector<u64>[5u64, 6u64]} — explicit element type
{vector<u32>} — empty vector with type annotation
Struct literal 값
positional field:
{0x1::m::MyStruct(42u64, 'hello')}
{0x2::table::Table<address, u64>(a, b)} — with type parameters
named field:
{0x1::m::MyStruct { id: @0x123, value: 42u64 }}
enum 및 variant literal
{0x1::option::Option<u64>::Some#1(42u64)} — variant name + index
{0x1::m::MyEnum::Variant#0(field1, field2)} — positional fields
variant name 뒤의 #N suffix는 variant의 discriminant index이다.
Alternate(fallback) 처리
fallback chain을 제공하려면 |를 사용한다. 첫 번째 chain이 null(missing field, out-of-bounds index 등)을 반환하면 다음 alternate를 시도한다.
{name | 'Unknown'} — fall back to literal string
{preferred_name | name} — try preferred_name first
{a | b | c} — try three options in order
{bar | 42u64} — fall back to numeric literal
모든 alternate가 실패하면 expression은 null로 evaluate되고 전체 format string은 null이 된다.
Transform 변환
transform은 :transform syntax로 final value에 적용된다. value가 output으로 serialize되는 방식을 제어한다.
str (default)
value를 사람이 읽을 수 있는 string으로 render한다. transform을 지정하지 않으면 이것이 default이다.
{amount} — same as {amount:str}
{amount:str} — explicit
| Value type | Output |
|---|---|
u8-u256 | decimal number: 42 |
bool | true or false |
address | 0x-prefixed canonical hex: 0x0000...1234 |
String or vector<u8> | UTF-8 decoded string |
hex
value를 lowercase hexadecimal로 render하며 type width에 맞춰 zero-pad한다.
{value:hex}
| Value type | Output |
|---|---|
u8 | 2 characters: 2a |
u16 | 4 characters: 002a |
u32 | 8 characters: 0000002a |
u64 | 16 characters: 000000000000002a |
u128 | 32 characters |
u256 | 64 characters |
address | 64 characters(0x prefix 없음) |
vector<u8> | 각 byte를 2 hex character로 |
base64
value의 byte representation을 Base64-encode한다.
{value:base64} — standard base64, with padding
{value:base64(nopad)} — without padding
{value:base64(url)} — URL-safe alphabet
{value:base64(url, nopad)} — URL-safe, without padding
bcs
전체 value를 BCS-serialize한 뒤 결과를 base64-encode한다. base64와 같은 modifier를 지원한다.
{value:bcs} — BCS bytes, standard base64
{value:bcs(url, nopad)} — BCS bytes, URL-safe base64 without padding
이는 str 또는 hex로 render할 수 없는 aggregate type(struct, enum, vector)에 유용하다.
json
value를 structured JSON value(quoted string 아님)로 출력한다. expression이 format string의 유일한 content일 때만 유효하다.
{inner:json}
| Value type | JSON output |
|---|---|
bool | true or false |
u8, u16, u32 | Number: 42 |
u64, u128, u256 | String: "42" |
address | String: "0x0000...1234" |
String | String: "hello" |
vector<u8> | String(base64): "AQID" |
| struct | Object: { "field": value, ... } |
| enum | Object: { "@variant": "Name", "field": value, ... } |
| vector | Array: [v1, v2, ...] |
timestamp (alias: ts)
numeric value를 Unix millisecond로 해석하고 ISO 8601로 format한다.
{created_at:ts} — "2023-04-12T17:00:00Z"
{1681318800000u64:ts} — literal timestamp
i64에 들어가는 numeric type에서만 동작한다.
url
str과 유사하지만 reserved URL character를 percent-encode한다(RFC 3986 unreserved set: A-Z a-z 0-9 - . _ ~).
{label:url} — "hello%20world" for "hello world"
numeric 및 address type은 reserved character가 없으므로 변경 없이 pass through된다.
Option auto-unwrapping 처리
Option<T> value는 자동으로 unwrap된다. option이 Some(v)이면 access가 v 안으로 계속된다. None이면 chain은 null로 evaluate된다.
{maybe_name} — null if None, the string value if Some("...")
Enum variant field access 접근
enum에서 field에 접근할 때 current variant가 요청된 field를 가질 때만 access가 성공한다. 그렇지 않으면 null로 evaluate된다. 이를 통해 하나의 Display definition이 여러 variant를 자연스럽게 처리할 수 있다.
enum Status {
Pending { message: String },
Active { progress: u32 },
Done { count: u128, timestamp: u64 },
}
("pending", "message = {message}")
("active", "progress = {progress}")
("complete", "count = {count}, timestamp = {timestamp}")
Pending value의 경우 "pending" key만 output을 생성한다. 나머지는 null이다.
Escaped brace 처리
output에 literal { 또는 }를 포함하려면 두 번 쓴다.
{{{ns[0u8]}, {ns[1u16]}, {ns[2u8]}}}
이는 {2, 1, 0}을 출력한다. outer {{와 }}는 literal brace이고, inner {...}는 expression이다.
Display object structure 구조
Display object는 (key, value) format string pair의 list이다. key와 value 모두 format string으로 parse되므로 computed key를 허용한다.
("name", "{name}")
("description", "{desc | 'No description'}")
("image_url", "https://example.com/images/{image_id}")
("balance", "{amount:hex}")
("data", "{inner:json}")
결과(주어진 object에 대해):
{
"name": "My NFT",
"description": "A cool NFT",
"image_url": "https://example.com/images/42",
"balance": "000000000000002a",
"data": { "x": 100, "y": 200 }
}
key는 unique하고 non-null string으로 evaluate되어야 한다. evaluate에 실패한 value는 null을 생성한다(partial failure 허용).
Limit 제한
parsing과 evaluation은 configurable limit로 제한된다.
| Limit | Default | Description |
|---|---|---|
max_depth | 32 | parsing 중 maximum nesting depth |
max_nodes | 32,768 | Display의 모든 format string에 걸친 maximum AST node |
max_loads | 8 | maximum object load(->는 1, =>는 2) |
limit를 초과하면 error(TooDeep, TooBig, TooManyLoads)가 생성된다.
Formal grammar 정의
format ::= strand*
strand ::= text | expr
text ::= part+
part ::= TEXT | '{{' | '}}'
expr ::= '{' chain ('|' chain)* (':' xform)? '}'
chain ::= (literal | IDENT) accessor*
accessor ::= '.' IDENT
| '.' NUM_DEC
| '[' chain ']'
| '->' '[' chain ']'
| '=>' '[' chain ']'
| '~>' '[' chain ']'
literal ::= self | address | bool | number | string | vector | struct | enum
self ::= '$' 'self'
address ::= '@' (NUM_DEC | NUM_HEX)
bool ::= 'true' | 'false'
number ::= (NUM_DEC | NUM_HEX) numeric
string ::= ('b' | 'x')? STRING
vector ::= 'vector' '<' type ','? '>' ('[' ']')?
| 'vector' ('<' type ','? '>')? array
array ::= '[' chain (',' chain)* ','? ']'
struct ::= datatype fields
enum ::= datatype '::' (IDENT '#')? NUM_DEC fields
fields ::= '(' chain (',' chain)* ','? ')'
| '{' named (',' named)* ','? '}'
named ::= IDENT ':' chain
type ::= 'address' | 'bool' | 'vector' '<' type '>' | numeric | datatype
datatype ::= NUM_HEX '::' IDENT ('<' type (',' type)* ','? '>')?
numeric ::= 'u8' | 'u16' | 'u32' | 'u64' | 'u128' | 'u256'
xform ::= 'str' | 'hex' | 'json' | 'timestamp' | 'url'
| 'base64' xmod?
| 'bcs' xmod?
xmod ::= '(' b64mod (',' b64mod)* ','? ')'
b64mod ::= 'url' | 'nopad'
Error reference 참조
| Error | Cause |
|---|---|
UnexpectedToken | parsing 중 unexpected token으로 인한 syntax error |
UnexpectedEos | unexpected end of input으로 인한 syntax error |
TooBig | AST node budget(max_nodes) 초과 |
TooDeep | nesting depth(max_depth) 초과 |
TooManyLoads | object load budget(max_loads) 초과 |
TooMuchOutput | rendering 중 output size limit 초과 |
TransformInvalid | transform을 value type에 적용할 수 없음(예: struct에 :hex) |
VectorTypeMismatch | vector literal의 mixed element type |