From ee0d13b997884a4b03b31317c3e7d816e64b0917 Mon Sep 17 00:00:00 2001 From: Pedro Castro Date: Fri, 4 Jul 2025 11:51:00 -0300 Subject: [PATCH 1/2] Remove Json module --- src/SyntaxLookup.res | 44 +++-- src/bindings/RescriptCompilerApi.res | 237 +++++++++++++++++---------- src/common/BlogFrontmatter.res | 78 ++++++--- src/common/DocFrontmatter.res | 32 +++- src/vendor/Json.res | 19 --- src/vendor/Json.resi | 119 -------------- src/vendor/Json_decode.res | 223 ------------------------- src/vendor/Json_decode.resi | 28 ---- src/vendor/Json_encode.res | 61 ------- src/vendor/Json_encode.resi | 24 --- 10 files changed, 251 insertions(+), 614 deletions(-) delete mode 100644 src/vendor/Json.res delete mode 100644 src/vendor/Json.resi delete mode 100644 src/vendor/Json_decode.res delete mode 100644 src/vendor/Json_decode.resi delete mode 100644 src/vendor/Json_encode.res delete mode 100644 src/vendor/Json_encode.resi diff --git a/src/SyntaxLookup.res b/src/SyntaxLookup.res index a9aa4d147..22f198e6f 100644 --- a/src/SyntaxLookup.res +++ b/src/SyntaxLookup.res @@ -147,24 +147,32 @@ type props = {mdxSources: array} type params = {slug: string} let decode = (json: JSON.t) => { - open Json.Decode - let id = json->field("id", string, _) - let keywords = json->field("keywords", array(string, ...), _) - let name = json->field("name", string, _) - let summary = json->field("summary", string, _) - let category = json->field("category", string, _)->Category.fromString - let status = - json - ->optional(field("status", string, _), _) - ->Option.mapOr(Status.Active, Status.fromString) - - { - id, - keywords, - name, - summary, - category, - status, + open JSON + switch json { + | Object(dict{ + "id": String(id), + "keywords": Array(keywords), + "name": String(name), + "summary": String(summary), + "category": String(category), + "status": ?status, + }) => { + id, + name, + summary, + category: Category.fromString(category), + keywords: keywords->Array.filterMap(k => + switch k { + | String(k) => Some(k) + | _ => None + } + ), + status: switch status { + | Some(String(status)) => status->Status.fromString + | _ => Status.Active + }, + } + | _ => throw(Failure(`Failed to decode SyntaxLookup. ${__LOC__}`)) } } diff --git a/src/bindings/RescriptCompilerApi.res b/src/bindings/RescriptCompilerApi.res index 7b2b04b69..08b312aa3 100644 --- a/src/bindings/RescriptCompilerApi.res +++ b/src/bindings/RescriptCompilerApi.res @@ -18,11 +18,11 @@ module Lang = { } let decode = (json): t => { - open! Json.Decode - switch string(json) { - | "re" => Reason - | "res" => Res - | other => throw(DecodeError(`Unknown language "${other}"`)) + open JSON + switch json { + | String("re") => Reason + | String("res") => Res + | other => throw(Failure(`Unknown language "${other->stringify}". ${__LOC__}`)) } } } @@ -91,14 +91,24 @@ module LocMsg = { } let decode = (json): t => { - open Json.Decode - { - fullMsg: json->field("fullMsg", string, _), - shortMsg: json->field("shortMsg", string, _), - row: json->field("row", int, _), - column: json->field("column", int, _), - endRow: json->field("endRow", int, _), - endColumn: json->field("endColumn", int, _), + open JSON + switch json { + | Object(dict{ + "fullMsg": String(fullMsg), + "shortMsg": String(shortMsg), + "row": Number(row), + "column": Number(column), + "endRow": Number(endRow), + "endColumn": Number(endColumn), + }) => { + fullMsg, + shortMsg, + row: row->Float.toInt, + column: column->Float.toInt, + endRow: endRow->Float.toInt, + endColumn: endColumn->Float.toInt, + } + | _ => throw(Failure(`Failed to decode LocMsg. ${__LOC__}`)) } } @@ -143,12 +153,18 @@ module Warning = { | WarnErr({warnNumber: int, details: LocMsg.t}) // Describes an erronous warning let decode = (json): t => { - open! Json.Decode - - let warnNumber = field("warnNumber", int, json) + open JSON + let warnNumber = switch json { + | Object(dict{"warnNumber": Number(warnNumber)}) => warnNumber->Float.toInt + | _ => throw(Failure(`Failed to decode warn number. ${__LOC__}`)) + } let details = LocMsg.decode(json) - field("isError", bool, json) ? WarnErr({warnNumber, details}) : Warn({warnNumber, details}) + switch json { + | Object(dict{"isError": Boolean(isError)}) => + isError ? WarnErr({warnNumber, details}) : Warn({warnNumber, details}) + | _ => throw(Failure(`Failed to decode warnings. ${__LOC__}`)) + } } // Useful for showing errors in a more compact format @@ -178,11 +194,14 @@ module WarningFlag = { } let decode = (json): t => { - open Json.Decode - { - msg: field("msg", string, json), - warn_flags: field("warn_flags", string, json), - warn_error_flags: field("warn_error_flags", string, json), + open JSON + switch json { + | Object(dict{ + "msg": String(msg), + "warn_flags": String(warn_flags), + "warn_error_flags": String(warn_error_flags), + }) => {msg, warn_flags, warn_error_flags} + | _ => throw(Failure(`Failed to decode WarningFlag. ${__LOC__}`)) } } } @@ -206,27 +225,37 @@ module TypeHint = { | CoreType(data) let decodePosition = json => { - open Json.Decode - { - line: field("line", int, json), - col: field("col", int, json), + open JSON + switch json { + | Object(dict{"line": Number(line), "col": Number(col)}) => { + line: line->Float.toInt, + col: col->Float.toInt, + } + | _ => throw(Failure(`Failed to decode position. ${__LOC__}`)) } } let decode = (json): t => { - open Json.Decode - let data = { - start: field("start", decodePosition, json), - end: field("end", decodePosition, json), - hint: field("hint", string, json), + open JSON + let data = switch json { + | Object(dict{"start": startPosition, "end": endPosition, "hint": String(hint)}) => { + start: decodePosition(startPosition), + end: decodePosition(endPosition), + hint, + } + | _ => throw(Failure(`Failed to decode type hint position. ${__LOC__}`)) } - switch field("kind", string, json) { - | "expression" => Expression(data) - | "type_declaration" => TypeDeclaration(data) - | "binding" => Binding(data) - | "core_type" => CoreType(data) - | other => throw(DecodeError(`Unknown kind "${other}" type hint`)) + switch json { + | Object(dict{"kind": String(kind)}) => + switch kind { + | "expression" => Expression(data) + | "type_declaration" => TypeDeclaration(data) + | "binding" => Binding(data) + | "core_type" => CoreType(data) + | other => throw(Failure(`Unknown kind "${other}" type hint. ${__LOC__}`)) + } + | _ => throw(Failure(`Failed to decode type hint kind. ${__LOC__}`)) } } } @@ -242,12 +271,17 @@ module CompileSuccess = { } let decode = (~time: float, json): t => { - open Json.Decode - { - jsCode: field("js_code", string, json), - warnings: field("warnings", array(Warning.decode, ...), json), - typeHints: withDefault([], field("type_hints", array(TypeHint.decode, ...), ...), json), - time, + open JSON + switch json { + | Object(dict{ + "js_code": String(jsCode), + "warnings": Array(warnings), + "type_hints": Array(typeHints), + }) => + let warnings = warnings->Array.map(Warning.decode) + let typeHints = typeHints->Array.map(TypeHint.decode) + {jsCode, warnings, typeHints, time} + | _ => throw(Failure(`Failed to decode CompileSuccess. ${__LOC__}`)) } } } @@ -260,11 +294,14 @@ module ConvertSuccess = { } let decode = (json): t => { - open Json.Decode - { - code: field("code", string, json), - fromLang: field("fromLang", Lang.decode, json), - toLang: field("toLang", Lang.decode, json), + open JSON + switch json { + | Object(dict{"code": String(code), "fromLang": fromLang, "toLang": toLang}) => { + code, + fromLang: fromLang->Lang.decode, + toLang: toLang->Lang.decode, + } + | _ => throw(Failure(`Failed to decode ConvertSuccess. ${__LOC__}`)) } } } @@ -278,28 +315,41 @@ module CompileFail = { | OtherErr(array) let decode = (json): t => { - open! Json.Decode - - switch field("type", string, json) { - | "syntax_error" => - let locMsgs = field("errors", array(LocMsg.decode, ...), json) - // TODO: There seems to be a bug in the ReScript bundle that reports - // back multiple LocMsgs of the same value - locMsgs->LocMsg.dedupe->SyntaxErr - | "type_error" => - let locMsgs = field("errors", array(LocMsg.decode, ...), json) - TypecheckErr(locMsgs) - | "warning_error" => - let warnings = field("errors", array(Warning.decode, ...), json) - WarningErr(warnings) - | "other_error" => - let locMsgs = field("errors", array(LocMsg.decode, ...), json) - OtherErr(locMsgs) - - | "warning_flag_error" => - let warningFlag = WarningFlag.decode(json) - WarningFlagErr(warningFlag) - | other => throw(DecodeError(`Unknown type "${other}" in CompileFail result`)) + open JSON + switch json { + | String(type_) => + switch type_ { + | "syntax_error" => + let locMsgs = switch json { + | Object(dict{"erros": Array(errors)}) => errors->Array.map(LocMsg.decode) + | _ => throw(Failure(`Failed to decode erros from syntax_error. ${__LOC__}`)) + } + // TODO: There seems to be a bug in the ReScript bundle that reports + // back multiple LocMsgs of the same value + locMsgs->LocMsg.dedupe->SyntaxErr + | "type_error" => + let locMsgs = switch json { + | Object(dict{"erros": Array(errors)}) => errors->Array.map(LocMsg.decode) + | _ => throw(Failure(`Failed to decode erros from type_error. ${__LOC__}`)) + } + TypecheckErr(locMsgs) + | "warning_error" => + let warnings = switch json { + | Object(dict{"erros": Array(warnings)}) => warnings->Array.map(Warning.decode) + | _ => throw(Failure(`Failed to decode errors from warning_error. ${__LOC__}`)) + } + WarningErr(warnings) + | "other_error" => + let locMsgs = switch json { + | Object(dict{"erros": Array(errors)}) => errors->Array.map(LocMsg.decode) + | _ => throw(Failure(`Failed to decode errors from other_error. ${__LOC__}`)) + } + OtherErr(locMsgs) + + | "warning_flag_error" => WarningFlagErr(WarningFlag.decode(json)) + | other => throw(Failure(`Unknown type "${other}" in CompileFail result. ${__LOC__}`)) + } + | _ => throw(Failure(`Failed to decode CompileFail. ${__LOC__}`)) } } } @@ -313,14 +363,19 @@ module CompilationResult = { // TODO: We might change this specific api completely before launching let decode = (~time: float, json: JSON.t): t => { - open! Json.Decode - - try switch field("type", string, json) { - | "success" => Success(CompileSuccess.decode(~time, json)) - | "unexpected_error" => UnexpectedError(field("msg", string, json)) - | _ => Fail(CompileFail.decode(json)) - } catch { - | DecodeError(errMsg) => Unknown(errMsg, json) + open JSON + switch json { + | Object(dict{"type": String(type_)}) => + switch type_ { + | "success" => Success(CompileSuccess.decode(~time, json)) + | "unexpected_error" => + switch json { + | Object(dict{"msg": String(msg)}) => UnexpectedError(msg) + | _ => throw(Failure(`Failed to decode msg from unexpected_error. ${__LOC__}`)) + } + | _ => Fail(CompileFail.decode(json)) + } + | _ => throw(Failure(`Failed to decode CompilationResult. ${__LOC__}`)) } } } @@ -333,16 +388,22 @@ module ConversionResult = { | Unknown(string, JSON.t) let decode = (~fromLang: Lang.t, ~toLang: Lang.t, json): t => { - open! Json.Decode - try switch field("type", string, json) { - | "success" => Success(ConvertSuccess.decode(json)) - | "unexpected_error" => UnexpectedError(field("msg", string, json)) - | "syntax_error" => - let locMsgs = field("errors", array(LocMsg.decode, ...), json) - Fail({fromLang, toLang, details: locMsgs}) - | other => Unknown(`Unknown conversion result type "${other}"`, json) - } catch { - | DecodeError(errMsg) => Unknown(errMsg, json) + open JSON + switch json { + | Object(dict{ + "type": String(type_), + "msg": ?Some(String(msg)), + "errors": ?Some(Array(errors)), + }) => + switch type_ { + | "success" => Success(ConvertSuccess.decode(json)) + | "unexpected_error" => msg->UnexpectedError + | "syntax_error" => + let locMsgs = errors->Array.map(LocMsg.decode) + Fail({fromLang, toLang, details: locMsgs}) + | other => Unknown(`Unknown conversion result type "${other}"`, json) + } + | _ => throw(Failure(`Failed to decode ConversionResult. ${__LOC__}`)) } } } diff --git a/src/common/BlogFrontmatter.res b/src/common/BlogFrontmatter.res index f0145edc2..524f07760 100644 --- a/src/common/BlogFrontmatter.res +++ b/src/common/BlogFrontmatter.res @@ -110,7 +110,7 @@ let decodeBadge = (str: string): Badge.t => | "preview" => Preview | "roadmap" => Roadmap | "community" => Community - | str => throw(Json.Decode.DecodeError(`Unknown category "${str}"`)) + | str => throw(Failure(`Unknown category "${str}"`)) } exception AuthorNotFound(string) @@ -121,32 +121,58 @@ let decodeAuthor = (~fieldName: string, ~authors, username) => | None => throw(AuthorNotFound(`Couldn't find author "${username}" in field ${fieldName}`)) } -let authorDecoder = (~fieldName: string, ~authors) => { - open Json.Decode - - let multiple = j => array(string, j)->Array.map(a => decodeAuthor(~fieldName, ~authors, a)) - - let single = j => [string(j)->decodeAuthor(~fieldName, ~authors)] - - either(single, multiple) -} - let decode = (json: JSON.t): result => { - open Json.Decode - switch { - author: json->field("author", string, _)->decodeAuthor(~fieldName="author", ~authors), - co_authors: json - ->optional(field("co-authors", authorDecoder(~fieldName="co-authors", ~authors), ...), _) - ->Option.getOr([]), - date: json->field("date", string, _)->DateStr.fromString, - badge: json->optional(j => field("badge", string, j)->decodeBadge, _)->Null.fromOption, - previewImg: json->optional(field("previewImg", string, ...), _)->Null.fromOption, - articleImg: json->optional(field("articleImg", string, ...), _)->Null.fromOption, - title: json->field("title", string, _), - description: json->nullable(field("description", string, ...), _), - } { - | fm => Ok(fm) - | exception DecodeError(str) => Error(str) + open JSON + switch json { + | Object(dict{ + "author": String(author), + "co_authors": ?co_authors, + "date": String(date), + "badge": ?badge, + "previewImg": ?previewImg, + "articleImg": ?articleImg, + "title": String(title), + "description": ?description, + }) => + let author = decodeAuthor(~fieldName="author", ~authors, author) + let co_authors = switch co_authors { + | Some(Array(co_authors)) => + co_authors->Array.filterMap(a => + switch a { + | String(a) => decodeAuthor(~fieldName="co-authors", ~authors, a)->Some + | _ => None + } + ) + | _ => [] + } + let date = date->DateStr.fromString + let badge = switch badge { + | Some(String(badge)) => badge->decodeBadge->Null.Value + | _ => Null + } + let previewImg = switch previewImg { + | Some(String(previewImg)) => previewImg->Null.Value + | _ => Null + } + let articleImg = switch articleImg { + | Some(String(articleImg)) => articleImg->Null.Value + | _ => Null + } + let description = switch description { + | Some(String(description)) => description->Null.Value + | _ => Null + } + Ok({ + author, + co_authors, + date, + previewImg, + articleImg, + title, + badge, + description, + }) | exception AuthorNotFound(str) => Error(str) + | _ => Error(`Failed to decode: ${JSON.stringify(json)}`) } } diff --git a/src/common/DocFrontmatter.res b/src/common/DocFrontmatter.res index 2ffb76728..7027b9be2 100644 --- a/src/common/DocFrontmatter.res +++ b/src/common/DocFrontmatter.res @@ -6,13 +6,29 @@ type t = { } let decode = json => { - open! Json.Decode - try Some({ - title: field("title", string, json), - metaTitle: optional(field("metaTitle", string, ...), json)->Null.fromOption, - description: optional(field("description", string, ...), json)->Null.fromOption, - canonical: optional(field("canonical", string, ...), json)->Null.fromOption, - }) catch { - | DecodeError(_errMsg) => None + open JSON + switch json { + | Object(dict{ + "title": String(title), + "metaTitle": ?metaTitle, + "description": ?description, + "canonical": ?canonical, + }) => + Some({ + title, + metaTitle: switch metaTitle { + | Some(String(v)) => Null.Value(v) + | _ => Null.Null + }, + description: switch description { + | Some(String(v)) => Null.Value(v) + | _ => Null.Null + }, + canonical: switch canonical { + | Some(String(v)) => Null.Value(v) + | _ => Null.Null + }, + }) + | _ => None } } diff --git a/src/vendor/Json.res b/src/vendor/Json.res deleted file mode 100644 index deda10702..000000000 --- a/src/vendor/Json.res +++ /dev/null @@ -1,19 +0,0 @@ -module Decode = Json_decode -module Encode = Json_encode - -exception ParseError(string) - -let parse = s => - try Some(JSON.parseOrThrow(s)) catch { - | _ => None - } - -let parseOrRaise = s => - try JSON.parseOrThrow(s) catch { - | JsExn(e) => - let message = switch JsExn.message(e) { - | Some(m) => m - | None => "Unknown error" - } - throw(ParseError(message)) - } diff --git a/src/vendor/Json.resi b/src/vendor/Json.resi deleted file mode 100644 index debe862c1..000000000 --- a/src/vendor/Json.resi +++ /dev/null @@ -1,119 +0,0 @@ -@@ocaml.text(" Efficient JSON handling -This module has four aspects to it: -- Parsing, which turns a JSON string into an encoded JSON data structure -- Stringificaiton, which produces a JSON string from an encoded JSON data structure -- Encoding, which is the process of construction a JSON data structure -- Decoding, which is the process of deconstructing a JSON data structure -{3 Parsing} -{! parse} and {! parseOrRaise} will both (try to) parse a JSON string into a JSON -data structure ({! Js.Json.t}), but behaves differently when encountering a -parse error. [parseOrRaise] will raise a [ParseError], while [parse] will return -a [Js.Json.t result] indicating whether or not the parsing succeeded. There's -not much more to it: [string] in, [Js.Json.t] out. -The parsed result, and encoded JSON data structure, then needs to be decoded to -actually be usable. See {!section:Decoding} below. -{3 Stringification} -Stringification is the exact reverse of parsing. {! stringify} and {! stringifyAny} -both technically do the same thing, but where [stringifyAny] will take any value -and try to do its best with it, retuning a [string option], [stringify] on the -other hand uses the type system to guarantee success, but requires that the data -has been encoded in a JSON data structure first. See {!section:Encoding} below. -{3 Encoding} -Encoding creates a JSON data structure which can stringified directly with -{! stringify} or passed to other APIs requiring a typed JSON data structure. Or -you could just go straight to decoding it again, if that's your thing. Encoding -functions are in the {! Encode} module. -{3 Decoding} -Decoding is a more complex process, due to the highly dynamic nature of JSON -data structures. The {! Decode} module provides decoder combinators that can -be combined to create complex composite decoders for any _known_ JSON data -structure. It allows for custom decoders to produce user-defined types. - -@example {[ -(* Parsing a JSON string using Json.parse *) -let arrayOfInts str - match Json.parse str with - | Some value -> - match Json.Decode.(array int value) - | Ok arr -> arr - | Error _ -> [] - | None -> failWith \"Unable to parse JSON\" - -(* prints `[3, 2, 1]` *) -let _ = Js.log (arrayOfInts \"[1, 2, 3]\" |> Js.Array.reverse) -]} - -@example {[ -(* Stringifying a value using Json.stringify *) - -(* prints `null` *) -let _ = - Json.stringify (Encode.int 42) - |> Js.log -]} - -@example {[ -(* Encoding a JSON data structure using Json.Encode *) - -(* prints [\"foo\", \"bar\"] *) -let _ = - [| \"foo\", \"bar\" |] - |> Json.Encode.stringArray - |> Json.stringify - |> Js.log - -(* prints [\"foo\", \"bar\"] *) -let _ = - [| \"foo\", \"bar\" |] - |> Js.Array.map Encode.int - |> Json.Encode.jsonArray - |> Json.stringify - |> Js.log -]} - -@example {[ -(* Decoding a fixed JSON data structure using Json.Decode *) -let mapJsonObjectString f decoder encoder str = - match Json.parse str with - | Ok json -> - match Json.Decode.(dict decoder json) with - | Ok dict -> - dict |> Js.Dict.map f - |> Js.Dict.map encoder - |> Json.Encode.dict - |> Json.stringify - | Error _ -> [] - | Error _ -> [] - -let sum ns = - Array.fold_left (+) 0 - -(* prints `{ \"foo\": 6, \"bar\": 24 }` *) -let _ = - Js.log ( - mapJsonObjectString sun Json.Decode.(array int) Json.Encode.int {| - { - \"foo\": [1, 2, 3], - \"bar\": [9, 8, 7] - } - |} - ) -]} -") - -module Decode = Json_decode -module Encode = Json_encode - -exception ParseError(string) - -/** -`parse(s)` returns `option` if s is a valid json string, `None` -otherwise -*/ -let parse: string => option - -/** -`parse(s)` returns a `JSON.t` if `s` is a valid json string, raises -`ParseError` otherwise -*/ -let parseOrRaise: string => JSON.t diff --git a/src/vendor/Json_decode.res b/src/vendor/Json_decode.res deleted file mode 100644 index 7bebbb4f9..000000000 --- a/src/vendor/Json_decode.res +++ /dev/null @@ -1,223 +0,0 @@ -@new external _unsafeCreateUninitializedArray: int => array<'a> = "Array" - -let _isInteger = value => Float.isFinite(value) && Math.floor(value) === value - -type decoder<'a> = JSON.t => 'a - -exception DecodeError(string) - -let id = json => json - -let bool = json => - if typeof(json) == #boolean { - (Obj.magic((json: JSON.t)): bool) - } else { - throw(DecodeError("Expected boolean, got " ++ JSON.stringify(json))) - } - -let float = json => - if typeof(json) == #number { - (Obj.magic((json: JSON.t)): float) - } else { - throw(DecodeError("Expected number, got " ++ JSON.stringify(json))) - } - -let int = json => { - let f = float(json) - if _isInteger(f) { - (Obj.magic((f: float)): int) - } else { - throw(DecodeError("Expected integer, got " ++ JSON.stringify(json))) - } -} - -let string = json => - if typeof(json) == #string { - (Obj.magic((json: JSON.t)): string) - } else { - throw(DecodeError("Expected string, got " ++ JSON.stringify(json))) - } - -let char = json => { - let s = string(json) - if String.length(s) == 1 { - String.getUnsafe(s, 0)->Obj.magic - } else { - throw(DecodeError("Expected single-character string, got " ++ JSON.stringify(json))) - } -} - -let date = json => Date.fromString(string(json)) - -let nullable = (decode, json) => - if (Obj.magic(json): Null.t<'a>) === Null.null { - Null.null - } else { - Null.make(decode(json)) - } - -/* TODO: remove this? */ -let nullAs = (value, json) => - if (Obj.magic(json): Null.t<'a>) === Null.null { - value - } else { - throw(DecodeError("Expected null, got " ++ JSON.stringify(json))) - } - -let array = (decode, json) => - if Array.isArray(json) { - let source: array = Obj.magic((json: JSON.t)) - let length = Array.length(source) - let target = _unsafeCreateUninitializedArray(length) - for i in 0 to length - 1 { - let value = try decode(Array.getUnsafe(source, i)) catch { - | DecodeError(msg) => throw(DecodeError(msg ++ ("\n\tin array at index " ++ Int.toString(i)))) - } - - Array.setUnsafe(target, i, value) - } - target - } else { - throw(DecodeError("Expected array, got " ++ JSON.stringify(json))) - } - -let list = (decode, json) => array(decode, json)->List.fromArray - -let pair = (decodeA, decodeB, json) => - if Array.isArray(json) { - let source: array = Obj.magic((json: JSON.t)) - let length = Array.length(source) - if length == 2 { - try (decodeA(Array.getUnsafe(source, 0)), decodeB(Array.getUnsafe(source, 1))) catch { - | DecodeError(msg) => throw(DecodeError(msg ++ "\n\tin pair/tuple2")) - } - } else { - throw(DecodeError(`Expected array of length 2, got array of length ${length->Int.toString}`)) - } - } else { - throw(DecodeError("Expected array, got " ++ JSON.stringify(json))) - } - -let tuple2 = pair - -let tuple3 = (decodeA, decodeB, decodeC, json) => - if Array.isArray(json) { - let source: array = Obj.magic((json: JSON.t)) - let length = Array.length(source) - if length == 3 { - try ( - decodeA(Array.getUnsafe(source, 0)), - decodeB(Array.getUnsafe(source, 1)), - decodeC(Array.getUnsafe(source, 2)), - ) catch { - | DecodeError(msg) => throw(DecodeError(msg ++ "\n\tin tuple3")) - } - } else { - throw(DecodeError(`Expected array of length 3, got array of length ${length->Int.toString}`)) - } - } else { - throw(DecodeError("Expected array, got " ++ JSON.stringify(json))) - } - -let tuple4 = (decodeA, decodeB, decodeC, decodeD, json) => - if Array.isArray(json) { - let source: array = Obj.magic((json: JSON.t)) - let length = Array.length(source) - if length == 4 { - try ( - decodeA(Array.getUnsafe(source, 0)), - decodeB(Array.getUnsafe(source, 1)), - decodeC(Array.getUnsafe(source, 2)), - decodeD(Array.getUnsafe(source, 3)), - ) catch { - | DecodeError(msg) => throw(DecodeError(msg ++ "\n\tin tuple4")) - } - } else { - throw(DecodeError(`Expected array of length 4, got array of length ${length->Int.toString}`)) - } - } else { - throw(DecodeError("Expected array, got " ++ JSON.stringify(json))) - } - -let dict = (decode, json) => - if ( - typeof(json) == #object && - (!Array.isArray(json) && - !((Obj.magic(json): Null.t<'a>) === Null.null)) - ) { - let source: Dict.t = Obj.magic((json: JSON.t)) - let keys = Dict.keysToArray(source) - let l = Array.length(keys) - let target = Dict.make() - for i in 0 to l - 1 { - let key = Array.getUnsafe(keys, i) - let value = try decode(Dict.getUnsafe(source, key)) catch { - | DecodeError(msg) => throw(DecodeError(msg ++ "\n\tin dict")) - } - - Dict.set(target, key, value) - } - target - } else { - throw(DecodeError("Expected object, got " ++ JSON.stringify(json))) - } - -let field = (key, decode, json) => - if ( - typeof(json) == #object && - (!Array.isArray(json) && - !((Obj.magic(json): Null.t<'a>) === Null.null)) - ) { - let dict: Dict.t = Obj.magic((json: JSON.t)) - switch Dict.get(dict, key) { - | Some(value) => - try decode(value) catch { - | DecodeError(msg) => throw(DecodeError(msg ++ ("\n\tat field '" ++ (key ++ "'")))) - } - | None => throw(DecodeError(`Expected field '${key}'`)) - } - } else { - throw(DecodeError("Expected object, got " ++ JSON.stringify(json))) - } - -let rec at = (key_path, decoder, json) => - switch key_path { - | list{key} => field(key, decoder, json) - | list{first, ...rest} => field(first, at(rest, decoder, ...), json) - | list{} => throw(Invalid_argument("Expected key_path to contain at least one element")) - } - -let optional = (decode, json) => - try Some(decode(json)) catch { - | DecodeError(_) => None - } - -let oneOf = (decoders, json) => { - let rec inner = (decoders, errors) => - switch decoders { - | list{} => - let formattedErrors = "\n- " ++ Array.join(List.toArray(List.reverse(errors)), "\n- ") - throw( - DecodeError( - `All decoders given to oneOf failed. Here are all the errors: ${formattedErrors}\\nAnd the JSON being decoded: ` ++ - JSON.stringify(json), - ), - ) - | list{decode, ...rest} => - try decode(json) catch { - | DecodeError(e) => inner(rest, list{e, ...errors}) - } - } - inner(decoders, list{}) -} - -let either = (a, b) => oneOf(list{a, b}, ...) - -let withDefault = (default, decode, json) => - try decode(json) catch { - | DecodeError(_) => default - } - -let map = (f, decode, json) => f(decode(json)) - -let andThen = (b, a, json) => b(a(json))(json) diff --git a/src/vendor/Json_decode.resi b/src/vendor/Json_decode.resi deleted file mode 100644 index af54f08e3..000000000 --- a/src/vendor/Json_decode.resi +++ /dev/null @@ -1,28 +0,0 @@ -type decoder<'a> = JSON.t => 'a - -exception DecodeError(string) - -let id: decoder -let bool: JSON.t => bool -let float: JSON.t => float -let int: JSON.t => int -let string: JSON.t => string -let char: JSON.t => char -let date: JSON.t => Date.t -let nullable: ('a => 'b, 'a) => Null.t<'b> -let nullAs: ('a, JSON.t) => 'a -let array: (decoder<'a>, JSON.t) => array<'a> -let list: (decoder<'a>, JSON.t) => list<'a> -let pair: (decoder<'a>, decoder<'b>, JSON.t) => ('a, 'b) -let tuple2: (decoder<'a>, decoder<'b>, JSON.t) => ('a, 'b) -let tuple3: (decoder<'a>, decoder<'b>, decoder<'c>, JSON.t) => ('a, 'b, 'c) -let tuple4: (decoder<'a>, decoder<'b>, decoder<'c>, decoder<'d>, JSON.t) => ('a, 'b, 'c, 'd) -let dict: (decoder<'a>, JSON.t) => Dict.t<'a> -let field: (string, decoder<'a>, JSON.t) => 'a -let at: (list, decoder<'a>, JSON.t) => 'a -let optional: ('a => 'b, 'a) => option<'b> -let oneOf: (list>, JSON.t) => 'a -let either: (decoder<'a>, decoder<'a>) => JSON.t => 'a -let withDefault: ('a, 'b => 'a, 'b) => 'a -let map: ('a => 'b, 'c => 'a, 'c) => 'b -let andThen: ('a => 'b => 'c, 'b => 'a, 'b) => 'c diff --git a/src/vendor/Json_encode.res b/src/vendor/Json_encode.res deleted file mode 100644 index fc5118343..000000000 --- a/src/vendor/Json_encode.res +++ /dev/null @@ -1,61 +0,0 @@ -type encoder<'a> = 'a => JSON.t - -@val external null: JSON.t = "null" -external string: string => JSON.t = "%identity" -external float: float => JSON.t = "%identity" -external int: int => JSON.t = "%identity" -external bool: bool => JSON.t = "%identity" - -let char = c => string(String.make(c)) - -let date = d => string(Date.toJSON(d)->Option.getUnsafe) - -let nullable = (encode, x) => - switch x { - | None => null - | Some(v) => encode(v) - } - -let withDefault = (d, encode, x) => - switch x { - | None => d - | Some(v) => encode(v) - } - -external jsonDict: Dict.t => JSON.t = "%identity" -let dict = (encode, d) => { - let pairs = Dict.toArray(d) - let encodedPairs = pairs->Array.map(((k, v)) => (k, encode(v))) - jsonDict(Dict.fromArray(encodedPairs)) -} - -let object_ = (props): JSON.t => jsonDict(Dict.fromArray(props->List.toArray)) - -external jsonArray: array => JSON.t = "%identity" -let array = (encode, l) => jsonArray(l->Array.map(x => encode(x))) -let list = (encode, x) => - switch x { - | list{} => jsonArray([]) - | list{hd, ...tl} as l => - let a = encode(hd)->Array.make(~length=List.length(l)) - let rec fill = (i, x) => - switch x { - | list{} => a - | list{hd, ...tl} => - Array.setUnsafe(a, i, encode(hd)) - fill(i + 1, tl) - } - - jsonArray(fill(1, tl)) - } - -let pair = (encodeA, encodeB, (a, b)) => jsonArray([encodeA(a), encodeB(b)]) -let tuple2 = pair -let tuple3 = (encodeA, encodeB, encodeC, (a, b, c)) => - jsonArray([encodeA(a), encodeB(b), encodeC(c)]) -let tuple4 = (encodeA, encodeB, encodeC, encodeD, (a, b, c, d)) => - jsonArray([encodeA(a), encodeB(b), encodeC(c), encodeD(d)]) - -external stringArray: array => JSON.t = "%identity" -external numberArray: array => JSON.t = "%identity" -external boolArray: array => JSON.t = "%identity" diff --git a/src/vendor/Json_encode.resi b/src/vendor/Json_encode.resi deleted file mode 100644 index 2ccf46ee3..000000000 --- a/src/vendor/Json_encode.resi +++ /dev/null @@ -1,24 +0,0 @@ -type encoder<'a> = 'a => JSON.t - -@val external null: JSON.t = "null" -external string: string => JSON.t = "%identity" -external float: float => JSON.t = "%identity" -external int: int => JSON.t = "%identity" -external bool: bool => JSON.t = "%identity" -let char: char => JSON.t -let date: Date.t => JSON.t -let nullable: (encoder<'a>, option<'a>) => JSON.t -let withDefault: ('a, 'b => 'a, option<'b>) => 'a -external jsonDict: Dict.t => JSON.t = "%identity" -let dict: (encoder<'a>, Dict.t<'a>) => JSON.t -let object_: list<(string, JSON.t)> => JSON.t -external jsonArray: array => JSON.t = "%identity" -let array: (encoder<'a>, array<'a>) => JSON.t -let list: (encoder<'a>, list<'a>) => JSON.t -let pair: (encoder<'a>, encoder<'b>, ('a, 'b)) => JSON.t -let tuple2: (encoder<'a>, encoder<'b>, ('a, 'b)) => JSON.t -let tuple3: (encoder<'a>, encoder<'b>, encoder<'c>, ('a, 'b, 'c)) => JSON.t -let tuple4: (encoder<'a>, encoder<'b>, encoder<'c>, encoder<'d>, ('a, 'b, 'c, 'd)) => JSON.t -external stringArray: array => JSON.t = "%identity" -external numberArray: array => JSON.t = "%identity" -external boolArray: array => JSON.t = "%identity" From 7ad13429c19b1a5d36c8b85c55b7a26631fa2d0d Mon Sep 17 00:00:00 2001 From: Pedro Castro Date: Fri, 4 Jul 2025 12:20:55 -0300 Subject: [PATCH 2/2] add helper function optionToNull --- src/common/DocFrontmatter.res | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/common/DocFrontmatter.res b/src/common/DocFrontmatter.res index 7027b9be2..38adb68e4 100644 --- a/src/common/DocFrontmatter.res +++ b/src/common/DocFrontmatter.res @@ -7,6 +7,11 @@ type t = { let decode = json => { open JSON + let optionToNull = opt => + switch opt { + | Some(String(v)) => Null.Value(v) + | _ => Null + } switch json { | Object(dict{ "title": String(title), @@ -16,18 +21,9 @@ let decode = json => { }) => Some({ title, - metaTitle: switch metaTitle { - | Some(String(v)) => Null.Value(v) - | _ => Null.Null - }, - description: switch description { - | Some(String(v)) => Null.Value(v) - | _ => Null.Null - }, - canonical: switch canonical { - | Some(String(v)) => Null.Value(v) - | _ => Null.Null - }, + metaTitle: metaTitle->optionToNull, + description: description->optionToNull, + canonical: canonical->optionToNull, }) | _ => None }