// Copyright 2025 International Digital Economy Academy
//
// 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.
///|
type JsonDecodeError = @json.JsonDecodeError
///|
pub impl ToJson for Dimension with fn to_json(self) {
match self {
DimAuto => Json::string("Auto")
DimLength(v) => Json::object({ "Length": Json::number(v) })
DimPercent(v) => Json::object({ "Percent": Json::number(v) })
DimFr(v) => Json::object({ "Fr": Json::number(v) })
DimMinContent => Json::string("MinContent")
DimMaxContent => Json::string("MaxContent")
DimFitContent(limit) => Json::object({ "FitContent": limit.to_json() })
DimMinMax(min, max) =>
Json::object({ "MinMax": Json::array([min.to_json(), max.to_json()]) })
DimRepeat(repetition, tracks) =>
Json::object({
"Repeat": Json::object({
"repetition": repetition.to_json(),
"tracks": Json::array(tracks.map(fn(track) { track.to_json() })),
}),
})
}
}
///|
pub impl @json.FromJson for Dimension with fn from_json(json, path) {
match json {
String("Auto") => DimAuto
String("MinContent") => DimMinContent
String("MaxContent") => DimMaxContent
String(_) => raise JsonDecodeError((path, "Dimension: unsupported string"))
{ "Length": value, .. } =>
DimLength(@json.from_json(value, path=path.add_key("Length")))
{ "Percent": value, .. } =>
DimPercent(@json.from_json(value, path=path.add_key("Percent")))
{ "Fr": value, .. } =>
DimFr(@json.from_json(value, path=path.add_key("Fr")))
{ "Flex": value, .. } =>
DimFr(@json.from_json(value, path=path.add_key("Flex")))
{ "FitContent": value, .. } =>
DimFitContent(@json.from_json(value, path=path.add_key("FitContent")))
{ "Repeat": { "repetition": repetition, "tracks": tracks, .. }, .. } =>
DimRepeat(
@json.from_json(
repetition,
path=path.add_key("Repeat").add_key("repetition"),
),
@json.from_json(tracks, path=path.add_key("Repeat").add_key("tracks")),
)
{ "MinMax": Array([min, max]), .. } =>
DimMinMax(
@json.from_json(min, path=path.add_key("MinMax").add_index(0)),
@json.from_json(max, path=path.add_key("MinMax").add_index(1)),
)
{ "MinMax": Array(_), .. } =>
raise JsonDecodeError((path, "Dimension: MinMax expects 2 values"))
{ "MinMax": _, .. } =>
raise JsonDecodeError((path, "Dimension: MinMax expects array"))
Object(_) => raise JsonDecodeError((path, "Dimension: unsupported object"))
_ => raise JsonDecodeError((path, "Dimension: unsupported json"))
}
}
///|
pub impl ToJson for GridPlacement with fn to_json(self) {
match self {
PlaceAuto => Json::string("Auto")
PlaceLine(v) => Json::object({ "Line": Json::number(v.to_double()) })
PlaceSpan(v) => Json::object({ "Span": Json::number(v.to_double()) })
}
}
///|
pub impl @json.FromJson for GridPlacement with fn from_json(json, path) {
match json {
String("Auto") => PlaceAuto
String(_) =>
raise JsonDecodeError((path, "GridPlacement: unsupported string"))
{ "Line": value, .. } =>
PlaceLine(@json.from_json(value, path=path.add_key("Line")))
{ "Span": value, .. } =>
PlaceSpan(@json.from_json(value, path=path.add_key("Span")))
Object(_) =>
raise JsonDecodeError((path, "GridPlacement: unsupported object"))
_ => raise JsonDecodeError((path, "GridPlacement: unsupported json"))
}
}
///|
pub impl ToJson for BoxSizing with fn to_json(self) {
match self {
BorderBox => Json::string("BorderBox")
ContentBox => Json::string("ContentBox")
}
}
///|
pub impl @json.FromJson for BoxSizing with fn from_json(json, path) {
match json {
String("BorderBox") => BorderBox
String("ContentBox") => ContentBox
String(_) => raise JsonDecodeError((path, "BoxSizing: unsupported string"))
_ => raise JsonDecodeError((path, "BoxSizing: expected string"))
}
}
///|
pub impl ToJson for GridTrackRepetition with fn to_json(self) {
match self {
RepeatCount(count) =>
Json::object({ "Count": Json::number(count.to_double()) })
RepeatAutoFill => Json::string("AutoFill")
RepeatAutoFit => Json::string("AutoFit")
}
}
///|
pub impl @json.FromJson for GridTrackRepetition with fn from_json(json, path) {
match json {
String("AutoFill") => RepeatAutoFill
String("AutoFit") => RepeatAutoFit
{ "Count": value, .. } =>
RepeatCount(@json.from_json(value, path=path.add_key("Count")))
String(_) =>
raise JsonDecodeError((path, "GridTrackRepetition: unsupported string"))
_ =>
raise JsonDecodeError(
(path, "GridTrackRepetition: expected string or object"),
)
}
}
///|
pub impl ToJson for Display with fn to_json(self) {
Json::string(
match self {
DisplayBlock => "Block"
DisplayFlex => "Flex"
DisplayGrid => "Grid"
DisplayNone => "None"
},
)
}
///|
pub impl @json.FromJson for Display with fn from_json(json, path) {
match json {
String("Block") => DisplayBlock
String("Flex") => DisplayFlex
String("Grid") => DisplayGrid
String("None") => DisplayNone
String(_) => raise JsonDecodeError((path, "Display: unsupported string"))
_ => raise JsonDecodeError((path, "Display: expected string"))
}
}
///|
pub impl ToJson for Position with fn to_json(self) {
Json::string(
match self {
PosRelative => "Relative"
PosAbsolute => "Absolute"
},
)
}
///|
pub impl @json.FromJson for Position with fn from_json(json, path) {
match json {
String("Relative") => PosRelative
String("Absolute") => PosAbsolute
String(_) => raise JsonDecodeError((path, "Position: unsupported string"))
_ => raise JsonDecodeError((path, "Position: expected string"))
}
}
///|
pub impl ToJson for Overflow with fn to_json(self) {
Json::string(
match self {
OverflowVisible => "Visible"
OverflowClip => "Clip"
OverflowHidden => "Hidden"
OverflowScroll => "Scroll"
},
)
}
///|
pub impl @json.FromJson for Overflow with fn from_json(json, path) {
match json {
String("Visible") => OverflowVisible
String("Clip") => OverflowClip
String("Hidden") => OverflowHidden
String("Scroll") => OverflowScroll
String(_) => raise JsonDecodeError((path, "Overflow: unsupported string"))
_ => raise JsonDecodeError((path, "Overflow: expected string"))
}
}
///|
pub impl ToJson for AlignItems with fn to_json(self) {
Json::string(
match self {
ItemsStart => "Start"
ItemsEnd => "End"
ItemsFlexStart => "FlexStart"
ItemsFlexEnd => "FlexEnd"
ItemsCenter => "Center"
ItemsBaseline => "Baseline"
ItemsStretch => "Stretch"
},
)
}
///|
pub impl @json.FromJson for AlignItems with fn from_json(json, path) {
match json {
String("Start") => ItemsStart
String("End") => ItemsEnd
String("FlexStart") => ItemsFlexStart
String("FlexEnd") => ItemsFlexEnd
String("Center") => ItemsCenter
String("Baseline") => ItemsBaseline
String("Stretch") => ItemsStretch
String(_) => raise JsonDecodeError((path, "AlignItems: unsupported string"))
_ => raise JsonDecodeError((path, "AlignItems: expected string"))
}
}
///|
pub impl ToJson for AlignContent with fn to_json(self) {
Json::string(
match self {
AlignStart => "Start"
AlignEnd => "End"
AlignFlexStart => "FlexStart"
AlignFlexEnd => "FlexEnd"
AlignCenter => "Center"
AlignStretch => "Stretch"
AlignSpaceBetween => "SpaceBetween"
AlignSpaceEvenly => "SpaceEvenly"
AlignSpaceAround => "SpaceAround"
},
)
}
///|
pub impl @json.FromJson for AlignContent with fn from_json(json, path) {
match json {
String("Start") => AlignStart
String("End") => AlignEnd
String("FlexStart") => AlignFlexStart
String("FlexEnd") => AlignFlexEnd
String("Center") => AlignCenter
String("Stretch") => AlignStretch
String("SpaceBetween") => AlignSpaceBetween
String("SpaceEvenly") => AlignSpaceEvenly
String("SpaceAround") => AlignSpaceAround
String(_) =>
raise JsonDecodeError((path, "AlignContent: unsupported string"))
_ => raise JsonDecodeError((path, "AlignContent: expected string"))
}
}
///|
pub impl ToJson for FlexDirection with fn to_json(self) {
Json::string(
match self {
FlexRow => "Row"
FlexRowReverse => "RowReverse"
FlexColumn => "Column"
FlexColumnReverse => "ColumnReverse"
},
)
}
///|
pub impl @json.FromJson for FlexDirection with fn from_json(json, path) {
match json {
String("Row") => FlexRow
String("RowReverse") => FlexRowReverse
String("Column") => FlexColumn
String("ColumnReverse") => FlexColumnReverse
String(_) =>
raise JsonDecodeError((path, "FlexDirection: unsupported string"))
_ => raise JsonDecodeError((path, "FlexDirection: expected string"))
}
}
///|
pub impl ToJson for FlexWrap with fn to_json(self) {
Json::string(
match self {
FlexNoWrap => "NoWrap"
FlexWrap => "Wrap"
FlexWrapReverse => "WrapReverse"
},
)
}
///|
pub impl @json.FromJson for FlexWrap with fn from_json(json, path) {
match json {
String("NoWrap") => FlexNoWrap
String("Wrap") => FlexWrap
String("WrapReverse") => FlexWrapReverse
String(_) => raise JsonDecodeError((path, "FlexWrap: unsupported string"))
_ => raise JsonDecodeError((path, "FlexWrap: expected string"))
}
}
///|
pub impl ToJson for GridAutoFlow with fn to_json(self) {
Json::string(
match self {
Row => "Row"
Column => "Column"
RowDense => "RowDense"
ColumnDense => "ColumnDense"
},
)
}
///|
pub impl @json.FromJson for GridAutoFlow with fn from_json(json, path) {
match json {
String("Row") => Row
String("Column") => Column
String("RowDense") => RowDense
String("ColumnDense") => ColumnDense
String(_) =>
raise JsonDecodeError((path, "GridAutoFlow: unsupported string"))
_ => raise JsonDecodeError((path, "GridAutoFlow: expected string"))
}
}
///|
fn parse_dimension_rect_partial(
json : Json,
base : Rect[Dimension],
path : @json.JsonPath,
) -> Rect[Dimension] raise JsonDecodeError {
guard json
is {
"left"? : left,
"right"? : right,
"top"? : top,
"bottom"? : bottom,
..
} else {
raise JsonDecodeError((path, "Rect: expected object"))
}
let mut out = base
match left {
Some(v) =>
out = { ..out, left: @json.from_json(v, path=path.add_key("left")) }
None => ()
}
match right {
Some(v) =>
out = { ..out, right: @json.from_json(v, path=path.add_key("right")) }
None => ()
}
match top {
Some(v) =>
out = { ..out, top: @json.from_json(v, path=path.add_key("top")) }
None => ()
}
match bottom {
Some(v) =>
out = { ..out, bottom: @json.from_json(v, path=path.add_key("bottom")) }
None => ()
}
out
}
///|
fn parse_dimension_size_partial(
json : Json,
base : Size[Dimension],
path : @json.JsonPath,
) -> Size[Dimension] raise JsonDecodeError {
guard json is { "width"? : width, "height"? : height, .. } else {
raise JsonDecodeError((path, "Size: expected object"))
}
let mut out = base
match width {
Some(v) =>
out = { ..out, width: @json.from_json(v, path=path.add_key("width")) }
None => ()
}
match height {
Some(v) =>
out = { ..out, height: @json.from_json(v, path=path.add_key("height")) }
None => ()
}
out
}
///|
fn parse_grid_line_partial(
json : Json,
base : Line[GridPlacement],
path : @json.JsonPath,
) -> Line[GridPlacement] raise JsonDecodeError {
guard json is { "start"? : start, "end"? : end, .. } else {
raise JsonDecodeError((path, "Line: expected object"))
}
let mut out = base
match start {
Some(v) =>
out = { ..out, start: @json.from_json(v, path=path.add_key("start")) }
None => ()
}
match end {
Some(v) =>
out = { ..out, end: @json.from_json(v, path=path.add_key("end")) }
None => ()
}
out
}
///|
fn parse_overflow_point_partial(
json : Json,
base : Point[Overflow],
path : @json.JsonPath,
) -> Point[Overflow] raise JsonDecodeError {
guard json is { "x"? : x, "y"? : y, .. } else {
raise JsonDecodeError((path, "Point: expected object"))
}
let mut out = base
match x {
Some(v) => out = { ..out, x: @json.from_json(v, path=path.add_key("x")) }
None => ()
}
match y {
Some(v) => out = { ..out, y: @json.from_json(v, path=path.add_key("y")) }
None => ()
}
out
}
///|
fn[T : ToJson] optional_to_json(value : T?) -> Json {
match value {
Some(value) => value.to_json()
None => Json::null()
}
}
///|
fn[T : @json.FromJson] nullable_from_json(
json : Json,
path : @json.JsonPath,
) -> T? raise JsonDecodeError {
match json {
Null => None
_ => Some(@json.from_json(json, path~))
}
}
///|
fn[T : ToJson] array_to_json(values : Array[T]) -> Json {
Json::array(values.map(fn(value) { value.to_json() }))
}
///|
pub impl ToJson for Style with fn to_json(self) {
Json::object({
"display": self.display.to_json(),
"box_sizing": self.box_sizing.to_json(),
"overflow": Json::object({
"x": self.overflow.x.to_json(),
"y": self.overflow.y.to_json(),
}),
"scrollbar_width": Json::number(self.scrollbar_width),
"position": self.position.to_json(),
"inset": Json::object({
"left": self.inset.left.to_json(),
"right": self.inset.right.to_json(),
"top": self.inset.top.to_json(),
"bottom": self.inset.bottom.to_json(),
}),
"size": Json::object({
"width": self.size.width.to_json(),
"height": self.size.height.to_json(),
}),
"min_size": Json::object({
"width": self.min_size.width.to_json(),
"height": self.min_size.height.to_json(),
}),
"max_size": Json::object({
"width": self.max_size.width.to_json(),
"height": self.max_size.height.to_json(),
}),
"aspect_ratio": optional_to_json(self.aspect_ratio),
"margin": Json::object({
"left": self.margin.left.to_json(),
"right": self.margin.right.to_json(),
"top": self.margin.top.to_json(),
"bottom": self.margin.bottom.to_json(),
}),
"padding": Json::object({
"left": self.padding.left.to_json(),
"right": self.padding.right.to_json(),
"top": self.padding.top.to_json(),
"bottom": self.padding.bottom.to_json(),
}),
"border": Json::object({
"left": self.border.left.to_json(),
"right": self.border.right.to_json(),
"top": self.border.top.to_json(),
"bottom": self.border.bottom.to_json(),
}),
"align_items": optional_to_json(self.align_items),
"align_self": optional_to_json(self.align_self),
"justify_items": optional_to_json(self.justify_items),
"justify_self": optional_to_json(self.justify_self),
"align_content": optional_to_json(self.align_content),
"justify_content": optional_to_json(self.justify_content),
"gap": Json::object({
"width": self.gap.width.to_json(),
"height": self.gap.height.to_json(),
}),
"flex_direction": self.flex_direction.to_json(),
"flex_wrap": self.flex_wrap.to_json(),
"flex_basis": self.flex_basis.to_json(),
"flex_grow": Json::number(self.flex_grow),
"flex_shrink": Json::number(self.flex_shrink),
"grid_template_rows": array_to_json(self.grid_template_rows),
"grid_template_columns": array_to_json(self.grid_template_columns),
"grid_auto_rows": array_to_json(self.grid_auto_rows),
"grid_auto_columns": array_to_json(self.grid_auto_columns),
"grid_auto_flow": self.grid_auto_flow.to_json(),
"grid_row": Json::object({
"start": self.grid_row.start.to_json(),
"end": self.grid_row.end.to_json(),
}),
"grid_column": Json::object({
"start": self.grid_column.start.to_json(),
"end": self.grid_column.end.to_json(),
}),
"grid_row_start": optional_to_json(self.grid_row_start),
"grid_column_start": optional_to_json(self.grid_column_start),
})
}
///|
pub impl @json.FromJson for Style with fn from_json(json, path) {
guard json
is {
"display"? : display,
"box_sizing"? : box_sizing,
"overflow"? : overflow,
"scrollbar_width"? : scrollbar_width,
"position"? : position,
"inset"? : inset,
"size"? : size,
"min_size"? : min_size,
"max_size"? : max_size,
"aspect_ratio"? : aspect_ratio,
"margin"? : margin,
"padding"? : padding,
"border"? : border,
"align_items"? : align_items,
"align_self"? : align_self,
"justify_items"? : justify_items,
"justify_self"? : justify_self,
"align_content"? : align_content,
"justify_content"? : justify_content,
"gap"? : gap,
"flex_direction"? : flex_direction,
"flex_wrap"? : flex_wrap,
"flex_basis"? : flex_basis,
"flex_grow"? : flex_grow,
"flex_shrink"? : flex_shrink,
"grid_template_rows"? : grid_template_rows,
"grid_template_columns"? : grid_template_columns,
"grid_auto_rows"? : grid_auto_rows,
"grid_auto_columns"? : grid_auto_columns,
"grid_auto_flow"? : grid_auto_flow,
"grid_row"? : grid_row,
"grid_column"? : grid_column,
"grid_row_start"? : grid_row_start,
"grid_column_start"? : grid_column_start,
..
} else {
raise JsonDecodeError((path, "Style: expected object"))
}
let mut style = Style::default()
match display {
Some(v) =>
style = {
..style,
display: @json.from_json(v, path=path.add_key("display")),
}
None => ()
}
match box_sizing {
Some(v) =>
style = {
..style,
box_sizing: @json.from_json(v, path=path.add_key("box_sizing")),
}
None => ()
}
match overflow {
Some(v) =>
style = {
..style,
overflow: parse_overflow_point_partial(
v,
style.overflow,
path.add_key("overflow"),
),
}
None => ()
}
match scrollbar_width {
Some(v) =>
style = {
..style,
scrollbar_width: @json.from_json(
v,
path=path.add_key("scrollbar_width"),
),
}
None => ()
}
match position {
Some(v) =>
style = {
..style,
position: @json.from_json(v, path=path.add_key("position")),
}
None => ()
}
match inset {
Some(v) =>
style = style.with_inset(
parse_dimension_rect_partial(v, style.inset, path.add_key("inset")),
)
None => ()
}
match size {
Some(v) =>
style = style.with_size(
parse_dimension_size_partial(v, style.size, path.add_key("size")),
)
None => ()
}
match min_size {
Some(v) =>
style = style.with_min_size(
parse_dimension_size_partial(
v,
style.min_size,
path.add_key("min_size"),
),
)
None => ()
}
match max_size {
Some(v) =>
style = style.with_max_size(
parse_dimension_size_partial(
v,
style.max_size,
path.add_key("max_size"),
),
)
None => ()
}
match aspect_ratio {
Some(v) =>
style = {
..style,
aspect_ratio: nullable_from_json(v, path.add_key("aspect_ratio")),
}
None => ()
}
match margin {
Some(v) =>
style = style.with_margin(
parse_dimension_rect_partial(v, style.margin, path.add_key("margin")),
)
None => ()
}
match padding {
Some(v) =>
style = style.with_padding(
parse_dimension_rect_partial(v, style.padding, path.add_key("padding")),
)
None => ()
}
match border {
Some(v) =>
style = style.with_border(
parse_dimension_rect_partial(v, style.border, path.add_key("border")),
)
None => ()
}
match align_items {
Some(v) =>
style = {
..style,
align_items: nullable_from_json(v, path.add_key("align_items")),
}
None => ()
}
match align_self {
Some(v) =>
style = {
..style,
align_self: nullable_from_json(v, path.add_key("align_self")),
}
None => ()
}
match justify_items {
Some(v) =>
style = {
..style,
justify_items: nullable_from_json(v, path.add_key("justify_items")),
}
None => ()
}
match justify_self {
Some(v) =>
style = {
..style,
justify_self: nullable_from_json(v, path.add_key("justify_self")),
}
None => ()
}
match align_content {
Some(v) =>
style = {
..style,
align_content: nullable_from_json(v, path.add_key("align_content")),
}
None => ()
}
match justify_content {
Some(v) =>
style = {
..style,
justify_content: nullable_from_json(v, path.add_key("justify_content")),
}
None => ()
}
match gap {
Some(v) =>
style = style.with_gap(
parse_dimension_size_partial(v, style.gap, path.add_key("gap")),
)
None => ()
}
match flex_direction {
Some(v) =>
style = {
..style,
flex_direction: @json.from_json(v, path=path.add_key("flex_direction")),
}
None => ()
}
match flex_wrap {
Some(v) =>
style = {
..style,
flex_wrap: @json.from_json(v, path=path.add_key("flex_wrap")),
}
None => ()
}
match flex_basis {
Some(v) =>
style = {
..style,
flex_basis: @json.from_json(v, path=path.add_key("flex_basis")),
}
None => ()
}
match flex_grow {
Some(v) =>
style = {
..style,
flex_grow: @json.from_json(v, path=path.add_key("flex_grow")),
}
None => ()
}
match flex_shrink {
Some(v) =>
style = {
..style,
flex_shrink: @json.from_json(v, path=path.add_key("flex_shrink")),
}
None => ()
}
match grid_template_rows {
Some(v) =>
style = {
..style,
grid_template_rows: @json.from_json(
v,
path=path.add_key("grid_template_rows"),
),
}
None => ()
}
match grid_template_columns {
Some(v) =>
style = {
..style,
grid_template_columns: @json.from_json(
v,
path=path.add_key("grid_template_columns"),
),
}
None => ()
}
match grid_auto_rows {
Some(v) =>
style = {
..style,
grid_auto_rows: @json.from_json(v, path=path.add_key("grid_auto_rows")),
}
None => ()
}
match grid_auto_columns {
Some(v) =>
style = {
..style,
grid_auto_columns: @json.from_json(
v,
path=path.add_key("grid_auto_columns"),
),
}
None => ()
}
match grid_auto_flow {
Some(v) =>
style = {
..style,
grid_auto_flow: @json.from_json(v, path=path.add_key("grid_auto_flow")),
}
None => ()
}
match grid_row {
Some(v) =>
style = style.with_grid_row(
parse_grid_line_partial(v, style.grid_row, path.add_key("grid_row")),
)
None => ()
}
match grid_column {
Some(v) =>
style = style.with_grid_column(
parse_grid_line_partial(
v,
style.grid_column,
path.add_key("grid_column"),
),
)
None => ()
}
match grid_row_start {
Some(v) =>
style = {
..style,
grid_row_start: nullable_from_json(v, path.add_key("grid_row_start")),
}
None => ()
}
match grid_column_start {
Some(v) =>
style = {
..style,
grid_column_start: nullable_from_json(
v,
path.add_key("grid_column_start"),
),
}
None => ()
}
style
}