diff --git a/CLAUDE.md b/CLAUDE.md index b27dfcf..d2662b1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -21,7 +21,7 @@ Uses `corral` for dependency management. `make` automatically runs `corral fetch - `github.com/ponylang/http.git` -- HTTP client - `github.com/ponylang/net_ssl.git` (via http) -- SSL/TLS - `github.com/kulibali/kiuatan.git` -- PEG parsing (for URI templates and Link headers) -- `github.com/ponylang/json.git` -- JSON parsing +- `github.com/ponylang/json-ng.git` -- JSON parsing (immutable, persistent collections) ## Source Layout @@ -45,6 +45,7 @@ github_rest_api/ search.pony -- SearchIssues + SearchResults generic user.pony -- User model license.pony -- License model + json_nav_util.pony -- JsonNavUtil (string_or_none for nullable JSON fields) paginated_list.pony -- PaginatedList[A] with prev/next page navigation request/ -- HTTP request infrastructure (temporary home, intended to be extracted to its own library) http.pony -- Credentials, ResultReceiver, RequestFactory @@ -52,7 +53,7 @@ github_rest_api/ http_post.pony -- HTTPPost (POST with JSON response) http_delete.pony -- HTTPDelete (DELETE, expects 204) request_error.pony -- RequestError (status, response_body, message) - json.pony -- JsonConverter interface + json.pony -- JsonConverter interface, JsonTypeString utility query_params.pony -- QueryParams (URL query string builder with percent-encoding) _test.pony -- QueryParams tests (example + property-based) simple_uri_template/ -- RFC 6570 path segment expansion (PEG-based; temporary home, intended to be extracted to its own library) diff --git a/corral.json b/corral.json index 66f872d..da9cada 100644 --- a/corral.json +++ b/corral.json @@ -15,8 +15,8 @@ "version": "0.6.4" }, { - "locator": "github.com/ponylang/json.git", - "version": "0.2.0" + "locator": "github.com/ponylang/json-ng.git", + "version": "0.1.0" } ], "info": { diff --git a/github_rest_api/asset.pony b/github_rest_api/asset.pony index 774328e..597096d 100644 --- a/github_rest_api/asset.pony +++ b/github_rest_api/asset.pony @@ -50,22 +50,20 @@ class val Asset browser_download_url = browser_download_url' primitive AssetJsonConverter is req.JsonConverter[Asset] - fun apply(json: JsonType val, creds: req.Credentials): Asset ? => - let obj = JsonExtractor(json).as_object()? - let id = JsonExtractor(obj("id")?).as_i64()? - let node_id = JsonExtractor(obj("node_id")?).as_string()? - let name = JsonExtractor(obj("name")?).as_string()? - let label = JsonExtractor(obj("label")?).as_string_or_none()? - let uploader = UserJsonConverter(obj("uploader")?, creds)? - let content_type = JsonExtractor(obj("content_type")?).as_string()? - let state = JsonExtractor(obj("state")?).as_string()? - let size = JsonExtractor(obj("size")?).as_i64()? - let download_count = JsonExtractor(obj("download_count")?).as_i64()? - let created_at = JsonExtractor(obj("created_at")?).as_string()? - let updated_at = JsonExtractor(obj("updated_at")?).as_string()? - let url = JsonExtractor(obj("url")?).as_string()? - let browser_download_url = - JsonExtractor(obj("browser_download_url")?).as_string()? + fun apply(json: JsonNav, creds: req.Credentials): Asset ? => + let id = json("id").as_i64()? + let node_id = json("node_id").as_string()? + let name = json("name").as_string()? + let label = JsonNavUtil.string_or_none(json("label"))? + let uploader = UserJsonConverter(json("uploader"), creds)? + let content_type = json("content_type").as_string()? + let state = json("state").as_string()? + let size = json("size").as_i64()? + let download_count = json("download_count").as_i64()? + let created_at = json("created_at").as_string()? + let updated_at = json("updated_at").as_string()? + let url = json("url").as_string()? + let browser_download_url = json("browser_download_url").as_string()? Asset(creds, id, diff --git a/github_rest_api/commit.pony b/github_rest_api/commit.pony index d7b2d14..9d648cc 100644 --- a/github_rest_api/commit.pony +++ b/github_rest_api/commit.pony @@ -67,20 +67,19 @@ primitive GetCommit p primitive CommitJsonConverter is req.JsonConverter[Commit] - fun apply(json: JsonType val, creds: req.Credentials): Commit ? => - let obj = JsonExtractor(json).as_object()? - let sha = JsonExtractor(obj("sha")?).as_string()? + fun apply(json: JsonNav, creds: req.Credentials): Commit ? => + let sha = json("sha").as_string()? let files = recover trn Array[CommitFile] end - for f in JsonExtractor(obj("files")?).as_array()?.values() do - let file = CommitFileJsonConverter(f, creds)? + for f in json("files").as_array()?.values() do + let file = CommitFileJsonConverter(JsonNav(f), creds)? files.push(file) end - let git_commit = GitCommitJsonConverter(obj("commit")?, creds)? - let url = JsonExtractor(obj("url")?).as_string()? - let html_url = JsonExtractor(obj("html_url")?).as_string()? - let comments_url = JsonExtractor(obj("comments_url")?).as_string()? + let git_commit = GitCommitJsonConverter(json("commit"), creds)? + let url = json("url").as_string()? + let html_url = json("html_url").as_string()? + let comments_url = json("comments_url").as_string()? Commit(creds, sha, diff --git a/github_rest_api/commit_file.pony b/github_rest_api/commit_file.pony index 63ee956..4619585 100644 --- a/github_rest_api/commit_file.pony +++ b/github_rest_api/commit_file.pony @@ -19,12 +19,11 @@ class val CommitFile filename = filename' primitive CommitFileJsonConverter is req.JsonConverter[CommitFile] - fun apply(json: JsonType val, + fun apply(json: JsonNav, creds: req.Credentials): CommitFile ? => - let obj = JsonExtractor(json).as_object()? - let sha = JsonExtractor(obj("sha")?).as_string()? - let status = JsonExtractor(obj("status")?).as_string()? - let filename = JsonExtractor(obj("filename")?).as_string()? + let sha = json("sha").as_string()? + let status = json("status").as_string()? + let filename = json("filename").as_string()? CommitFile(creds, sha, status, filename) diff --git a/github_rest_api/git_commit.pony b/github_rest_api/git_commit.pony index a237742..83d486c 100644 --- a/github_rest_api/git_commit.pony +++ b/github_rest_api/git_commit.pony @@ -21,11 +21,10 @@ class val GitCommit url = url' primitive GitCommitJsonConverter is req.JsonConverter[GitCommit] - fun apply(json: JsonType val, creds: req.Credentials): GitCommit ? => - let obj = JsonExtractor(json).as_object()? - let author = GitPersonJsonConverter(obj("author")?, creds)? - let committer = GitPersonJsonConverter(obj("committer")?, creds)? - let message = JsonExtractor(obj("message")?).as_string()? - let url = JsonExtractor(obj("url")?).as_string()? + fun apply(json: JsonNav, creds: req.Credentials): GitCommit ? => + let author = GitPersonJsonConverter(json("author"), creds)? + let committer = GitPersonJsonConverter(json("committer"), creds)? + let message = json("message").as_string()? + let url = json("url").as_string()? GitCommit(creds, author, committer, message, url) diff --git a/github_rest_api/git_person.pony b/github_rest_api/git_person.pony index 56404cf..750ca6e 100644 --- a/github_rest_api/git_person.pony +++ b/github_rest_api/git_person.pony @@ -10,9 +10,8 @@ class val GitPerson email = email' primitive GitPersonJsonConverter is req.JsonConverter[GitPerson] - fun apply(json: JsonType val, creds: req.Credentials): GitPerson ? => - let obj = JsonExtractor(json).as_object()? - let name = JsonExtractor(obj("name")?).as_string()? - let email = JsonExtractor(obj("email")?).as_string()? + fun apply(json: JsonNav, creds: req.Credentials): GitPerson ? => + let name = json("name").as_string()? + let email = json("email").as_string()? GitPerson(name, email) diff --git a/github_rest_api/issue.pony b/github_rest_api/issue.pony index 4c4c081..3c96112 100644 --- a/github_rest_api/issue.pony +++ b/github_rest_api/issue.pony @@ -1,4 +1,3 @@ -use "collections" use "json" use "promises" use req = "request" @@ -147,34 +146,33 @@ primitive GetRepositoryIssues primitive IssueJsonConverter is req.JsonConverter[Issue] - fun apply(json: JsonType val, creds: req.Credentials): Issue ? => - let obj = JsonExtractor(json).as_object()? - - let url = JsonExtractor(obj("url")?).as_string()? - let respository_url = JsonExtractor(obj("repository_url")?).as_string()? - let labels_url = JsonExtractor(obj("labels_url")?).as_string()? - let comments_url = JsonExtractor(obj("comments_url")?).as_string()? - let events_url = JsonExtractor(obj("events_url")?).as_string()? - let html_url = JsonExtractor(obj("html_url")?).as_string()? - - let number = JsonExtractor(obj("number")?).as_i64()? - let title = JsonExtractor(obj("title")?).as_string()? - let user = UserJsonConverter(obj("user")?, creds)? - let state = JsonExtractor(obj("state")?).as_string_or_none()? - let body = JsonExtractor(obj("body")?).as_string_or_none()? + fun apply(json: JsonNav, creds: req.Credentials): Issue ? => + let url = json("url").as_string()? + let respository_url = json("repository_url").as_string()? + let labels_url = json("labels_url").as_string()? + let comments_url = json("comments_url").as_string()? + let events_url = json("events_url").as_string()? + let html_url = json("html_url").as_string()? + + let number = json("number").as_i64()? + let title = json("title").as_string()? + let user = UserJsonConverter(json("user"), creds)? + let state = JsonNavUtil.string_or_none(json("state"))? + let body = JsonNavUtil.string_or_none(json("body"))? let labels = recover trn Array[Label] end - for i in JsonExtractor(obj("labels")?).as_array()?.values() do - let l = LabelJsonConverter(i, creds)? + for i in json("labels").as_array()?.values() do + let l = LabelJsonConverter(JsonNav(i), creds)? labels.push(l) end - let pull_request = - if obj.contains("pull_request") then - IssuePullRequestJsonConverter(obj("pull_request")?, creds)? - else - None - end + let pr_json = json("pull_request") + let pull_request = match pr_json.json() + | let _: JsonType => + IssuePullRequestJsonConverter(pr_json, creds)? + else + None + end Issue(creds, url, diff --git a/github_rest_api/issue_comment.pony b/github_rest_api/issue_comment.pony index a357646..9ed6b95 100644 --- a/github_rest_api/issue_comment.pony +++ b/github_rest_api/issue_comment.pony @@ -1,4 +1,3 @@ -use "collections" use "json" use "net" use "promises" @@ -54,13 +53,11 @@ primitive CreateIssueComment p, IssueCommentJsonConverter) - let m: Map[String, JsonType] = m.create() - m.update("body", comment) - let json = JsonObject.from_map(m).string() + let json = JsonObject.update("body", comment).string() try req.HTTPPost(creds.auth)(url, - json, + consume json, r, creds.token)? else @@ -115,25 +112,24 @@ primitive IssueCommentsURL end) primitive IssueCommentJsonConverter is req.JsonConverter[IssueComment] - fun apply(json: JsonType val, + fun apply(json: JsonNav, creds: req.Credentials): IssueComment ? => - let obj = JsonExtractor(json).as_object()? - let body = JsonExtractor(obj("body")?).as_string()? - let url = JsonExtractor(obj("url")?).as_string()? - let html_url = JsonExtractor(obj("html_url")?).as_string()? - let issue_url = JsonExtractor(obj("issue_url")?).as_string()? + let body = json("body").as_string()? + let url = json("url").as_string()? + let html_url = json("html_url").as_string()? + let issue_url = json("issue_url").as_string()? IssueComment(creds, body, url, html_url, issue_url) primitive IssueCommentsJsonConverter is req.JsonConverter[Array[IssueComment] val] - fun apply(json: JsonType val, + fun apply(json: JsonNav, creds: req.Credentials): Array[IssueComment] val ? => let comments = recover trn Array[IssueComment] end - for i in JsonExtractor(json).as_array()?.values() do - let comment = IssueCommentJsonConverter(i, creds)? + for i in json.as_array()?.values() do + let comment = IssueCommentJsonConverter(JsonNav(i), creds)? comments.push(comment) end diff --git a/github_rest_api/issue_pull_request.pony b/github_rest_api/issue_pull_request.pony index 5757653..9d139fb 100644 --- a/github_rest_api/issue_pull_request.pony +++ b/github_rest_api/issue_pull_request.pony @@ -29,12 +29,11 @@ class val IssuePullRequest merged_at = merged_at' primitive IssuePullRequestJsonConverter is req.JsonConverter[IssuePullRequest] - fun apply(json: JsonType val, creds: req.Credentials): IssuePullRequest ? => - let obj = JsonExtractor(json).as_object()? - let url = JsonExtractor(obj("url")?).as_string()? - let html_url = JsonExtractor(obj("html_url")?).as_string()? - let diff_url = JsonExtractor(obj("diff_url")?).as_string()? - let patch_url = JsonExtractor(obj("patch_url")?).as_string()? - let merged_at = JsonExtractor(obj("merged_at")?).as_string_or_none()? + fun apply(json: JsonNav, creds: req.Credentials): IssuePullRequest ? => + let url = json("url").as_string()? + let html_url = json("html_url").as_string()? + let diff_url = json("diff_url").as_string()? + let patch_url = json("patch_url").as_string()? + let merged_at = JsonNavUtil.string_or_none(json("merged_at"))? IssuePullRequest(url, html_url, diff_url, patch_url, merged_at) diff --git a/github_rest_api/json_nav_util.pony b/github_rest_api/json_nav_util.pony new file mode 100644 index 0000000..49387a6 --- /dev/null +++ b/github_rest_api/json_nav_util.pony @@ -0,0 +1,18 @@ +use "json" + +primitive JsonNavUtil + """ + Utility for extracting optional string fields from JSON. + + json-ng's JsonNav does not have an as_string_or_none() method. This + primitive provides equivalent functionality: given a JsonNav positioned on a + field, it returns the String value if present or None if the JSON value is + null. Raises an error if the navigation failed (key missing) or the value is + any other type. + """ + fun string_or_none(json: JsonNav): (String | None) ? => + match json.json() + | let s: String => s + | JsonNull => None + else error + end diff --git a/github_rest_api/label.pony b/github_rest_api/label.pony index dab9e43..17e11a7 100644 --- a/github_rest_api/label.pony +++ b/github_rest_api/label.pony @@ -1,4 +1,3 @@ -use "collections" use "json" use "promises" use req = "request" @@ -69,19 +68,18 @@ primitive CreateLabel p, LabelJsonConverter) - let m: Map[String, JsonType] = m.create() - m.update("name", name) + var obj = JsonObject.update("name", name) match color - | let c: String => m.update("color", c) + | let c: String => obj = obj.update("color", c) end match description - | let d: String => m.update("description", d) + | let d: String => obj = obj.update("description", d) end - let json = JsonObject.from_map(m).string() + let json = obj.string() try req.HTTPPost(creds.auth)(url, - json, + consume json, r, creds.token)? else @@ -133,15 +131,14 @@ primitive DeleteLabel p primitive LabelJsonConverter is req.JsonConverter[Label] - fun apply(json: JsonType val, creds: req.Credentials): Label ? => - let obj = JsonExtractor(json).as_object()? - let id = JsonExtractor(obj("id")?).as_i64()? - let node_id = JsonExtractor(obj("node_id")?).as_string()? - let url = JsonExtractor(obj("url")?).as_string()? - let name = JsonExtractor(obj("name")?).as_string()? - let color = JsonExtractor(obj("color")?).as_string()? - let default = JsonExtractor(obj("default")?).as_bool()? - let description = JsonExtractor(obj("description")?).as_string_or_none()? + fun apply(json: JsonNav, creds: req.Credentials): Label ? => + let id = json("id").as_i64()? + let node_id = json("node_id").as_string()? + let url = json("url").as_string()? + let name = json("name").as_string()? + let color = json("color").as_string()? + let default = json("default").as_bool()? + let description = JsonNavUtil.string_or_none(json("description"))? Label(creds, id, diff --git a/github_rest_api/license.pony b/github_rest_api/license.pony index 5751006..55c6ba9 100644 --- a/github_rest_api/license.pony +++ b/github_rest_api/license.pony @@ -24,13 +24,12 @@ class val License url = url' primitive LicenseJsonConverter is req.JsonConverter[License] - fun apply(json: JsonType val, creds: req.Credentials): License ? => - let obj = JsonExtractor(json).as_object()? - let node_id = JsonExtractor(obj("node_id")?).as_string()? - let name = JsonExtractor(obj("name")?).as_string()? - let key = JsonExtractor(obj("key")?).as_string()? - let spdx_id = JsonExtractor(obj("spdx_id")?).as_string()? - let url = JsonExtractor(obj("url")?).as_string()? + fun apply(json: JsonNav, creds: req.Credentials): License ? => + let node_id = json("node_id").as_string()? + let name = json("name").as_string()? + let key = json("key").as_string()? + let spdx_id = json("spdx_id").as_string()? + let url = json("url").as_string()? License(creds, node_id, diff --git a/github_rest_api/paginated_list.pony b/github_rest_api/paginated_list.pony index ee7708b..a59b3ae 100644 --- a/github_rest_api/paginated_list.pony +++ b/github_rest_api/paginated_list.pony @@ -74,14 +74,14 @@ class val PaginatedListJsonConverter[A: Any val] _creds = creds _converter = converter - fun apply(json: JsonType val, + fun apply(json: JsonNav, link_header: String, creds: req.Credentials): PaginatedList[A] ? => let entries = recover trn Array[A] end - for i in JsonExtractor(json).as_array()?.values() do - let e = _converter(i, creds)? + for i in json.as_array()?.values() do + let e = _converter(JsonNav(i), creds)? entries.push(e) end @@ -114,12 +114,12 @@ actor PaginatedResultReceiver[A: Any val] _p = p _converter = c - be success(json: JsonDoc val, link_header: String) => + be success(json: JsonNav, link_header: String) => try - _p(_converter(json.data, link_header, _creds)?) + _p(_converter(json, link_header, _creds)?) else let m = recover val - "Unable to convert json for " + json.string() + "Unable to convert json for " + req.JsonTypeString(json) end _p(req.RequestError(where message' = m)) @@ -224,13 +224,10 @@ class PaginatedJsonRequesterHandler[A: Any val] is HTTPHandler let y: String iso = String.from_iso_array(consume x) if _status == 200 then - try - let json = recover val - JsonDoc.>parse(consume y)? - end - _receiver.success(json, _link_header) - else - _receiver.failure(_status, "", "Failed to parse response") + match JsonParser.parse(consume y) + | let json: JsonType => _receiver.success(JsonNav(json), _link_header) + | let _: JsonParseError => _receiver.failure(_status, "", + "Failed to parse response") end elseif (_status != 301) and (_status != 307) then _receiver.failure(_status, consume y, "") diff --git a/github_rest_api/pull_request.pony b/github_rest_api/pull_request.pony index 3756a27..397cc1c 100644 --- a/github_rest_api/pull_request.pony +++ b/github_rest_api/pull_request.pony @@ -86,25 +86,23 @@ primitive GetPullRequest p primitive PullRequestJsonConverter is req.JsonConverter[PullRequest] - fun apply(json: JsonType val, creds: req.Credentials): PullRequest ? => - let obj = JsonExtractor(json).as_object()? - - let number = JsonExtractor(obj("number")?).as_i64()? - let title = JsonExtractor(obj("title")?).as_string()? - let body = JsonExtractor(obj("body")?).as_string_or_none()? - let state = JsonExtractor(obj("state")?).as_string()? + fun apply(json: JsonNav, creds: req.Credentials): PullRequest ? => + let number = json("number").as_i64()? + let title = json("title").as_string()? + let body = JsonNavUtil.string_or_none(json("body"))? + let state = json("state").as_string()? let labels = recover trn Array[Label] end - for i in JsonExtractor(obj("labels")?).as_array()?.values() do - let l = LabelJsonConverter(i, creds)? + for i in json("labels").as_array()?.values() do + let l = LabelJsonConverter(JsonNav(i), creds)? labels.push(l) end - let base = PullRequestBaseJsonConverter(obj("base")?, creds)? + let base = PullRequestBaseJsonConverter(json("base"), creds)? - let url = JsonExtractor(obj("url")?).as_string()? - let html_url = JsonExtractor(obj("html_url")?).as_string()? - let comments_url = JsonExtractor(obj("comments_url")?).as_string()? + let url = json("url").as_string()? + let html_url = json("html_url").as_string()? + let comments_url = json("comments_url").as_string()? PullRequest(creds, number, diff --git a/github_rest_api/pull_request_base.pony b/github_rest_api/pull_request_base.pony index 3427831..3fe8b70 100644 --- a/github_rest_api/pull_request_base.pony +++ b/github_rest_api/pull_request_base.pony @@ -24,12 +24,11 @@ class val PullRequestBase repo = repo' primitive PullRequestBaseJsonConverter is req.JsonConverter[PullRequestBase] - fun apply(json: JsonType val, creds: req.Credentials): PullRequestBase ? => - let obj = JsonExtractor(json).as_object()? - let label = JsonExtractor(obj("label")?).as_string()? - let reference = JsonExtractor(obj("ref")?).as_string()? - let sha = JsonExtractor(obj("sha")?).as_string()? - let user = UserJsonConverter(obj("user")?, creds)? - let repo = RepositoryJsonConverter(obj("repo")?, creds)? + fun apply(json: JsonNav, creds: req.Credentials): PullRequestBase ? => + let label = json("label").as_string()? + let reference = json("ref").as_string()? + let sha = json("sha").as_string()? + let user = UserJsonConverter(json("user"), creds)? + let repo = RepositoryJsonConverter(json("repo"), creds)? PullRequestBase(creds, label, reference, sha, user, repo) diff --git a/github_rest_api/pull_request_file.pony b/github_rest_api/pull_request_file.pony index 309afc8..eddad8c 100644 --- a/github_rest_api/pull_request_file.pony +++ b/github_rest_api/pull_request_file.pony @@ -59,14 +59,14 @@ primitive GetPullRequestFiles primitive PullRequestFilesJsonConverter is req.JsonConverter[Array[PullRequestFile] val] - fun apply(json: JsonType val, + fun apply(json: JsonNav, creds: req.Credentials): Array[PullRequestFile] val ? => let files = recover trn Array[PullRequestFile] end - for i in JsonExtractor(json).as_array()?.values() do - let j = JsonExtractor(i).as_object()? - let filename = JsonExtractor(j("filename")?).as_string()? + for i in json.as_array()?.values() do + let json_i = JsonNav(i) + let filename = json_i("filename").as_string()? let file = PullRequestFile(creds, filename) files.push(file) end diff --git a/github_rest_api/release.pony b/github_rest_api/release.pony index 6583fab..1a1f49b 100644 --- a/github_rest_api/release.pony +++ b/github_rest_api/release.pony @@ -1,4 +1,3 @@ -use "collections" use "json" use "promises" use req = "request" @@ -117,21 +116,20 @@ primitive CreateRelease p, ReleaseJsonConverter) - let m: Map[String, JsonType] = m.create() - m.update("tag_name", tag_name) - m.update("name", name) - m.update("body", body) + var obj = JsonObject + .update("tag_name", tag_name) + .update("name", name) + .update("body", body) match target_commitish | let tc: String => - m.update("target_commitish", tc) + obj = obj.update("target_commitish", tc) end - m.update("draft", draft) - m.update("prerelease", prerelease) - let json = JsonObject.from_map(m).string() + obj = obj.update("draft", draft).update("prerelease", prerelease) + let json = obj.string() try req.HTTPPost(creds.auth)(url, - json, + consume json, r, creds.token)? else @@ -142,32 +140,31 @@ primitive CreateRelease p primitive ReleaseJsonConverter is req.JsonConverter[Release] - fun apply(json: JsonType val, creds: req.Credentials): Release ? => - let obj = JsonExtractor(json).as_object()? - let id = JsonExtractor(obj("id")?).as_i64()? - let node_id = JsonExtractor(obj("node_id")?).as_string()? - let author = UserJsonConverter(obj("author")?, creds)? - let tag_name = JsonExtractor(obj("tag_name")?).as_string()? - let target_commitish = JsonExtractor(obj("target_commitish")?).as_string()? - let name = JsonExtractor(obj("name")?).as_string()? - let body = JsonExtractor(obj("body")?).as_string()? - let draft = JsonExtractor(obj("draft")?).as_bool()? - let prerelease = JsonExtractor(obj("prerelease")?).as_bool()? - let created_at = JsonExtractor(obj("created_at")?).as_string()? - let published_at = JsonExtractor(obj("published_at")?).as_string()? + fun apply(json: JsonNav, creds: req.Credentials): Release ? => + let id = json("id").as_i64()? + let node_id = json("node_id").as_string()? + let author = UserJsonConverter(json("author"), creds)? + let tag_name = json("tag_name").as_string()? + let target_commitish = json("target_commitish").as_string()? + let name = json("name").as_string()? + let body = json("body").as_string()? + let draft = json("draft").as_bool()? + let prerelease = json("prerelease").as_bool()? + let created_at = json("created_at").as_string()? + let published_at = json("published_at").as_string()? let assets = recover trn Array[Asset] end - for i in JsonExtractor(obj("assets")?).as_array()?.values() do - let a = AssetJsonConverter(i, creds)? + for i in json("assets").as_array()?.values() do + let a = AssetJsonConverter(JsonNav(i), creds)? assets.push(a) end - let url = JsonExtractor(obj("url")?).as_string()? - let assets_url = JsonExtractor(obj("assets_url")?).as_string()? - let upload_url = JsonExtractor(obj("upload_url")?).as_string()? - let html_url = JsonExtractor(obj("html_url")?).as_string()? - let tarball_url = JsonExtractor(obj("tarball_url")?).as_string()? - let zipball_url = JsonExtractor(obj("zipball_url")?).as_string()? + let url = json("url").as_string()? + let assets_url = json("assets_url").as_string()? + let upload_url = json("upload_url").as_string()? + let html_url = json("html_url").as_string()? + let tarball_url = json("tarball_url").as_string()? + let zipball_url = json("zipball_url").as_string()? Release(creds, id, diff --git a/github_rest_api/repository.pony b/github_rest_api/repository.pony index ba3610e..662c7c4 100644 --- a/github_rest_api/repository.pony +++ b/github_rest_api/repository.pony @@ -463,99 +463,96 @@ primitive GetOrganizationRepositories p primitive RepositoryJsonConverter is req.JsonConverter[Repository] - fun apply(json: JsonType val, + fun apply(json: JsonNav, creds: req.Credentials): Repository ? => - let obj = JsonExtractor(json).as_object()? - let id = JsonExtractor(obj("id")?).as_i64()? - let node_id = JsonExtractor(obj("node_id")?).as_string()? - let name = JsonExtractor(obj("name")?).as_string()? - let full_name = JsonExtractor(obj("full_name")?).as_string()? - let description = JsonExtractor(obj("description")?).as_string_or_none()? - let owner = UserJsonConverter(obj("owner")?, creds)? - let private = JsonExtractor(obj("private")?).as_bool()? - let fork = JsonExtractor(obj("fork")?).as_bool()? - let created_at = JsonExtractor(obj("created_at")?).as_string()? - let pushed_at = JsonExtractor(obj("pushed_at")?).as_string()? - let updated_at = JsonExtractor(obj("updated_at")?).as_string()? - let homepage = JsonExtractor(obj("homepage")?).as_string_or_none()? - let default_branch = JsonExtractor(obj("default_branch")?).as_string()? + let id = json("id").as_i64()? + let node_id = json("node_id").as_string()? + let name = json("name").as_string()? + let full_name = json("full_name").as_string()? + let description = JsonNavUtil.string_or_none(json("description"))? + let owner = UserJsonConverter(json("owner"), creds)? + let private = json("private").as_bool()? + let fork = json("fork").as_bool()? + let created_at = json("created_at").as_string()? + let pushed_at = json("pushed_at").as_string()? + let updated_at = json("updated_at").as_string()? + let homepage = JsonNavUtil.string_or_none(json("homepage"))? + let default_branch = json("default_branch").as_string()? let organization = try - UserJsonConverter(obj("organization")?, creds)? + UserJsonConverter(json("organization"), creds)? else None end - let size = JsonExtractor(obj("size")?).as_i64()? - let forks = JsonExtractor(obj("forks")?).as_i64()? - let forks_count = JsonExtractor(obj("forks_count")?).as_i64()? + let size = json("size").as_i64()? + let forks = json("forks").as_i64()? + let forks_count = json("forks_count").as_i64()? let network_count = - try JsonExtractor(obj("network_count")?).as_i64()? else None end - let open_issues = JsonExtractor(obj("open_issues")?).as_i64()? - let open_issues_count = JsonExtractor(obj("open_issues_count")?).as_i64()? - let stargazers_count = JsonExtractor(obj("stargazers_count")?).as_i64()? + try json("network_count").as_i64()? else None end + let open_issues = json("open_issues").as_i64()? + let open_issues_count = json("open_issues_count").as_i64()? + let stargazers_count = json("stargazers_count").as_i64()? let subscribers_count = - try JsonExtractor(obj("subscribers_count")?).as_i64()? else None end - let watchers = JsonExtractor(obj("watchers")?).as_i64()? - let watchers_count = JsonExtractor(obj("watchers_count")?).as_i64()? - let language = JsonExtractor(obj("language")?).as_string_or_none()? + try json("subscribers_count").as_i64()? else None end + let watchers = json("watchers").as_i64()? + let watchers_count = json("watchers_count").as_i64()? + let language = JsonNavUtil.string_or_none(json("language"))? let license = try - LicenseJsonConverter(obj("license")?, creds)? + LicenseJsonConverter(json("license"), creds)? else None end - let archived = JsonExtractor(obj("archived")?).as_bool()? - let disabled = JsonExtractor(obj("disabled")?).as_bool()? - let has_downloads = JsonExtractor(obj("has_downloads")?).as_bool()? - let has_issues = JsonExtractor(obj("has_issues")?).as_bool()? - let has_pages = JsonExtractor(obj("has_pages")?).as_bool()? - let has_projects = JsonExtractor(obj("has_projects")?).as_bool()? - let has_wiki = JsonExtractor(obj("has_wiki")?).as_bool()? - - let url = JsonExtractor(obj("url")?).as_string()? - let html_url = JsonExtractor(obj("html_url")?).as_string()? - let archive_url = JsonExtractor(obj("archive_url")?).as_string()? - let assignees_url = JsonExtractor(obj("assignees_url")?).as_string()? - let blobs_url = JsonExtractor(obj("blobs_url")?).as_string()? - let branches_url = JsonExtractor(obj("branches_url")?).as_string()? - let comments_url = JsonExtractor(obj("comments_url")?).as_string()? - let commits_url = JsonExtractor(obj("commits_url")?).as_string()? - let compare_url = JsonExtractor(obj("compare_url")?).as_string()? - let contents_url = JsonExtractor(obj("contents_url")?).as_string()? - let contributors_url = JsonExtractor(obj("contributors_url")?).as_string()? - let deployments_url = JsonExtractor(obj("deployments_url")?).as_string()? - let downloads_url = JsonExtractor(obj("downloads_url")?).as_string()? - let events_url = JsonExtractor(obj("events_url")?).as_string()? - let forks_url = JsonExtractor(obj("forks_url")?).as_string()? - let git_commits_url = JsonExtractor(obj("git_commits_url")?).as_string()? - let git_refs_url = JsonExtractor(obj("git_refs_url")?).as_string()? - let git_tags_url = JsonExtractor(obj("git_tags_url")?).as_string()? - let issue_comment_url = - JsonExtractor(obj("issue_comment_url")?).as_string()? - let issue_events_url = JsonExtractor(obj("issue_events_url")?).as_string()? - let issues_url = JsonExtractor(obj("issues_url")?).as_string()? - let keys_url = JsonExtractor(obj("keys_url")?).as_string()? - let labels_url = JsonExtractor(obj("labels_url")?).as_string()? - let languages_url = JsonExtractor(obj("languages_url")?).as_string()? - let merges_url = JsonExtractor(obj("merges_url")?).as_string()? - let milestones_url = JsonExtractor(obj("milestones_url")?).as_string()? - let notifications_url = - JsonExtractor(obj("notifications_url")?).as_string()? - let pulls_url = JsonExtractor(obj("pulls_url")?).as_string()? - let releases_url = JsonExtractor(obj("releases_url")?).as_string()? - let stargazers_url = JsonExtractor(obj("stargazers_url")?).as_string()? - let statuses_url = JsonExtractor(obj("statuses_url")?).as_string()? - let subscribers_url = JsonExtractor(obj("subscribers_url")?).as_string()? - let subscription_url = JsonExtractor(obj("subscription_url")?).as_string()? - let tags_url = JsonExtractor(obj("tags_url")?).as_string()? - let trees_url = JsonExtractor(obj("trees_url")?).as_string()? - - let clone_url = JsonExtractor(obj("clone_url")?).as_string()? - let git_url = JsonExtractor(obj("git_url")?).as_string()? - let mirror_url = JsonExtractor(obj("mirror_url")?).as_string_or_none()? - let ssh_url = JsonExtractor(obj("ssh_url")?).as_string()? - let svn_url = JsonExtractor(obj("svn_url")?).as_string()? + let archived = json("archived").as_bool()? + let disabled = json("disabled").as_bool()? + let has_downloads = json("has_downloads").as_bool()? + let has_issues = json("has_issues").as_bool()? + let has_pages = json("has_pages").as_bool()? + let has_projects = json("has_projects").as_bool()? + let has_wiki = json("has_wiki").as_bool()? + + let url = json("url").as_string()? + let html_url = json("html_url").as_string()? + let archive_url = json("archive_url").as_string()? + let assignees_url = json("assignees_url").as_string()? + let blobs_url = json("blobs_url").as_string()? + let branches_url = json("branches_url").as_string()? + let comments_url = json("comments_url").as_string()? + let commits_url = json("commits_url").as_string()? + let compare_url = json("compare_url").as_string()? + let contents_url = json("contents_url").as_string()? + let contributors_url = json("contributors_url").as_string()? + let deployments_url = json("deployments_url").as_string()? + let downloads_url = json("downloads_url").as_string()? + let events_url = json("events_url").as_string()? + let forks_url = json("forks_url").as_string()? + let git_commits_url = json("git_commits_url").as_string()? + let git_refs_url = json("git_refs_url").as_string()? + let git_tags_url = json("git_tags_url").as_string()? + let issue_comment_url = json("issue_comment_url").as_string()? + let issue_events_url = json("issue_events_url").as_string()? + let issues_url = json("issues_url").as_string()? + let keys_url = json("keys_url").as_string()? + let labels_url = json("labels_url").as_string()? + let languages_url = json("languages_url").as_string()? + let merges_url = json("merges_url").as_string()? + let milestones_url = json("milestones_url").as_string()? + let notifications_url = json("notifications_url").as_string()? + let pulls_url = json("pulls_url").as_string()? + let releases_url = json("releases_url").as_string()? + let stargazers_url = json("stargazers_url").as_string()? + let statuses_url = json("statuses_url").as_string()? + let subscribers_url = json("subscribers_url").as_string()? + let subscription_url = json("subscription_url").as_string()? + let tags_url = json("tags_url").as_string()? + let trees_url = json("trees_url").as_string()? + + let clone_url = json("clone_url").as_string()? + let git_url = json("git_url").as_string()? + let mirror_url = JsonNavUtil.string_or_none(json("mirror_url"))? + let ssh_url = json("ssh_url").as_string()? + let svn_url = json("svn_url").as_string()? Repository(creds, id, diff --git a/github_rest_api/request/http.pony b/github_rest_api/request/http.pony index 50b2cfc..5bc54f3 100644 --- a/github_rest_api/request/http.pony +++ b/github_rest_api/request/http.pony @@ -24,12 +24,12 @@ actor ResultReceiver[A: Any val] _p = p _converter = c - be success(json: JsonDoc val) => + be success(json: JsonNav) => try - _p(_converter(json.data, _creds)?) + _p(_converter(json, _creds)?) else let m = recover val - "Unable to convert json for " + json.string() + "Unable to convert json for " + JsonTypeString(json) end _p(RequestError(where message' = m)) diff --git a/github_rest_api/request/http_get.pony b/github_rest_api/request/http_get.pony index 45e89fa..a093d96 100644 --- a/github_rest_api/request/http_get.pony +++ b/github_rest_api/request/http_get.pony @@ -29,7 +29,7 @@ class JsonRequester client(consume r)? interface tag JsonRequesterResultReceiver - be success(json: JsonDoc val) + be success(json: JsonNav) be failure(status: U16, response_body: String, message: String) class JsonRequesterHandlerFactory is HandlerFactory @@ -96,13 +96,10 @@ class JsonRequesterHandler is HTTPHandler let y: String iso = String.from_iso_array(consume x) if _status == 200 then - try - let json = recover val - JsonDoc.>parse(consume y)? - end - _receiver.success(json) - else - _receiver.failure(_status, "", "Failed to parse response") + match JsonParser.parse(consume y) + | let json: JsonType => _receiver.success(JsonNav(json)) + | let _: JsonParseError => _receiver.failure(_status, "", + "Failed to parse response") end elseif (_status != 301) and (_status != 307) then _receiver.failure(_status, consume y, "") diff --git a/github_rest_api/request/http_post.pony b/github_rest_api/request/http_post.pony index 03616bc..05b454a 100644 --- a/github_rest_api/request/http_post.pony +++ b/github_rest_api/request/http_post.pony @@ -4,7 +4,7 @@ use "net" use "ssl/net" interface tag PostResultReceiver - be success(json: JsonDoc val) + be success(json: JsonNav) be failure(status: U16, response_body: String, message: String) class HTTPPost @@ -82,13 +82,10 @@ class HTTPPostHandler is HTTPHandler let y = String.from_iso_array(consume x) if _status == 201 then - try - let json = recover val - JsonDoc.>parse(consume y)? - end - _receiver.success(json) - else - _receiver.failure(_status, "", "Failed to parse response") + match JsonParser.parse(consume y) + | let json: JsonType => _receiver.success(JsonNav(json)) + | let _: JsonParseError => _receiver.failure(_status, "", + "Failed to parse response") end else _receiver.failure(_status, consume y, "") diff --git a/github_rest_api/request/json.pony b/github_rest_api/request/json.pony index 7896bb5..513581e 100644 --- a/github_rest_api/request/json.pony +++ b/github_rest_api/request/json.pony @@ -1,6 +1,19 @@ -use "collections" use "json" use "net" interface val JsonConverter[A: Any #share] - fun apply(json: JsonType val, creds: Credentials): A ? + fun apply(json: JsonNav, creds: Credentials): A ? + +primitive JsonTypeString + """Convert a JsonNav's value to its JSON string representation for error messages.""" + fun apply(json: JsonNav): String => + match json.json() + | let o: JsonObject => o.string() + | let a: JsonArray => a.string() + | let s: String => s + | let i: I64 => i.string() + | let f: F64 => f.string() + | let b: Bool => b.string() + | JsonNull => "null" + | NotFound => "NotFound" + end diff --git a/github_rest_api/search.pony b/github_rest_api/search.pony index 744dbf1..562212e 100644 --- a/github_rest_api/search.pony +++ b/github_rest_api/search.pony @@ -93,17 +93,16 @@ class val PaginatedSearchJsonConverter[A: Any val] _creds = creds _converter = converter - fun apply(json: JsonType val, + fun apply(json: JsonNav, link_header: String, creds: req.Credentials): SearchResults[A] ? => - let obj = JsonExtractor(json).as_object()? - let total_count = JsonExtractor(obj("total_count")?).as_i64()? - let incomplete = JsonExtractor(obj("incomplete_results")?).as_bool()? + let total_count = json("total_count").as_i64()? + let incomplete = json("incomplete_results").as_bool()? let items = recover trn Array[A] end - for i in JsonExtractor(obj("items")?).as_array()?.values() do - let item = _converter(i, creds)? + for i in json("items").as_array()?.values() do + let item = _converter(JsonNav(i), creds)? items.push(item) end @@ -135,12 +134,12 @@ actor SearchResultReceiver[A: Any val] _p = p _converter = c - be success(json: JsonDoc val, link_header: String) => + be success(json: JsonNav, link_header: String) => try - _p(_converter(json.data, link_header, _creds)?) + _p(_converter(json, link_header, _creds)?) else let m = recover val - "Unable to convert json for " + json.string() + "Unable to convert json for " + req.JsonTypeString(json) end _p(req.RequestError(where message' = m)) @@ -244,13 +243,10 @@ class SearchJsonRequesterHandler[A: Any val] is HTTPHandler let y: String iso = String.from_iso_array(consume x) if _status == 200 then - try - let json = recover val - JsonDoc.>parse(consume y)? - end - _receiver.success(json, _link_header) - else - _receiver.failure(_status, "", "Failed to parse response") + match JsonParser.parse(consume y) + | let json: JsonType => _receiver.success(JsonNav(json), _link_header) + | let _: JsonParseError => _receiver.failure(_status, "", + "Failed to parse response") end elseif (_status != 301) and (_status != 307) then _receiver.failure(_status, consume y, "") diff --git a/github_rest_api/user.pony b/github_rest_api/user.pony index 50f0ccc..9e649da 100644 --- a/github_rest_api/user.pony +++ b/github_rest_api/user.pony @@ -63,29 +63,25 @@ class val User site_admin = site_admin' primitive UserJsonConverter is req.JsonConverter[User] - fun apply(json: JsonType val, creds: req.Credentials): User ? => - let obj = JsonExtractor(json).as_object()? - let login = JsonExtractor(obj("login")?).as_string()? - let id = JsonExtractor(obj("id")?).as_i64()? - let node_id = JsonExtractor(obj("node_id")?).as_string()? - let avatar_url = JsonExtractor(obj("avatar_url")?).as_string()? - let gravatar_id = JsonExtractor(obj("gravatar_id")?).as_string()? - let url = JsonExtractor(obj("url")?).as_string()? - let html_url = JsonExtractor(obj("html_url")?).as_string()? - let followers_url = JsonExtractor(obj("followers_url")?).as_string()? - let following_url = JsonExtractor(obj("following_url")?).as_string()? - let gists_url = JsonExtractor(obj("gists_url")?).as_string()? - let starred_url = JsonExtractor(obj("starred_url")?).as_string()? - let subscriptions_url = - JsonExtractor(obj("subscriptions_url")?).as_string()? - let organizations_url = - JsonExtractor(obj("organizations_url")?).as_string()? - let repos_url = JsonExtractor(obj("repos_url")?).as_string()? - let events_url = JsonExtractor(obj("events_url")?).as_string()? - let received_events_url = - JsonExtractor(obj("received_events_url")?).as_string()? - let user_type = JsonExtractor(obj("type")?).as_string()? - let site_admin = JsonExtractor(obj("site_admin")?).as_bool()? + fun apply(json: JsonNav, creds: req.Credentials): User ? => + let login = json("login").as_string()? + let id = json("id").as_i64()? + let node_id = json("node_id").as_string()? + let avatar_url = json("avatar_url").as_string()? + let gravatar_id = json("gravatar_id").as_string()? + let url = json("url").as_string()? + let html_url = json("html_url").as_string()? + let followers_url = json("followers_url").as_string()? + let following_url = json("following_url").as_string()? + let gists_url = json("gists_url").as_string()? + let starred_url = json("starred_url").as_string()? + let subscriptions_url = json("subscriptions_url").as_string()? + let organizations_url = json("organizations_url").as_string()? + let repos_url = json("repos_url").as_string()? + let events_url = json("events_url").as_string()? + let received_events_url = json("received_events_url").as_string()? + let user_type = json("type").as_string()? + let site_admin = json("site_admin").as_bool()? User(creds, login,