diff --git a/src/_modules/Json.sol b/src/_modules/Json.sol index 56cd92e2..e701f554 100644 --- a/src/_modules/Json.sol +++ b/src/_modules/Json.sol @@ -15,8 +15,13 @@ struct JsonResult { } library JsonError { + bytes32 constant INVALID_JSON = keccak256("INVALID_JSON"); bytes32 constant IMMUTABLE_JSON = keccak256("IMMUTABLE_JSON"); + function invalid() internal pure returns (JsonResult memory res) { + return JsonResult(Error(INVALID_JSON, "Invalid json").toResult()); + } + function immutableJson() internal pure returns (JsonResult memory res) { return JsonResult(Error(IMMUTABLE_JSON, "Json object is immutable").toResult()); } @@ -42,6 +47,10 @@ library LibJsonResult { function toError(JsonResult memory self) internal pure returns (Error memory) { return self._inner.toError(); } + + function toValue(JsonResult memory self) internal pure returns (JsonObject memory) { + return abi.decode(self._inner.toValue(), (JsonObject)); + } } function Ok(JsonObject memory value) pure returns (JsonResult memory) { @@ -70,6 +79,18 @@ library json { return vulcan.hevm.parseJson(jsonObj.serialized); } + function isValid(string memory jsonObj) internal pure returns (bool) { + try vulcan.hevm.parseJson(jsonObj) { + return true; + } catch { + return false; + } + } + + function containsKey(JsonObject memory obj, string memory key) internal view returns (bool) { + return vulcan.hevm.keyExists(obj.serialized, key); + } + /// @dev Parses the value of the `key` contained on `jsonStr` as uint256. /// @param obj The json object. /// @param key The key from the `jsonStr` to parse. @@ -191,8 +212,12 @@ library json { /// @dev Creates a new JsonObject struct. /// @return The JsonObject struct. - function create(string memory obj) internal pure returns (JsonObject memory) { - return JsonObject({id: "", serialized: obj}); + function create(string memory obj) internal pure returns (JsonResult memory) { + if (!isValid(obj)) { + return JsonError.invalid(); + } + + return Ok(JsonObject({id: "", serialized: obj})); } /// @dev Serializes and sets the key and value for the provided json object. diff --git a/src/_modules/experimental/Request.sol b/src/_modules/experimental/Request.sol index 5274eb93..495b87e7 100644 --- a/src/_modules/experimental/Request.sol +++ b/src/_modules/experimental/Request.sol @@ -185,11 +185,13 @@ library LibRequestBuilder { } function body(RequestBuilder memory self, string memory _body) internal pure returns (RequestBuilder memory) { - if (self.request.isOk()) { - Request memory req = self.request.toValue(); - req.body = bytes(_body); - self.request = Ok(req); + if (self.request.isError()) { + return self; } + + Request memory req = self.request.toValue(); + req.body = bytes(_body); + self.request = Ok(req); return self; } @@ -215,26 +217,38 @@ library LibRequestBuilder { pure returns (RequestBuilder memory) { - if (self.request.isOk()) { - Request memory req = self.request.toValue(); - uint256 len = req.headers.length; - req.headers = new Header[](len + 1); - for (uint256 i; i < len; i++) { - req.headers[i] = req.headers[i]; - } - req.headers[len] = Header({key: key, value: value}); - self.request = Ok(req); + if (self.request.isError()) { + return self; + } + + Request memory req = self.request.toValue(); + uint256 len = req.headers.length; + req.headers = new Header[](len + 1); + for (uint256 i; i < len; i++) { + req.headers[i] = req.headers[i]; } + req.headers[len] = Header({key: key, value: value}); + self.request = Ok(req); return self; } function json(RequestBuilder memory self, JsonObject memory obj) internal pure returns (RequestBuilder memory) { - return self.json(obj.serialized); + // We assume the json has already been validated + return self.header("Content-Type", "application/json").body(obj.serialized); } function json(RequestBuilder memory self, string memory serialized) internal pure returns (RequestBuilder memory) { - // TODO: parse json and set error if it fails - return self.header("Content-Type", "application/json").body(serialized); + if (self.request.isError()) { + return self; + } + + JsonResult memory res = jsonModule.create(serialized); + if (res.isError()) { + self.request = RequestResult(res._inner); + return self; + } + + return self.json(res.toValue()); } } @@ -279,12 +293,13 @@ library LibRequest { } library LibResponse { - // TODO: validate response and return error if there are issues function json(Response memory self) internal pure returns (JsonResult memory) { - return Ok(jsonModule.create(string(self.body))); + // create() will validate the json + return jsonModule.create(string(self.body)); } function text(Response memory self) internal pure returns (StringResult memory) { + // TODO: maybe do some encoding validation? or check not empty? return Ok(string(self.body)); } diff --git a/test/_modules/Json.t.sol b/test/_modules/Json.t.sol index 6d20b1b2..b3ef3214 100644 --- a/test/_modules/Json.t.sol +++ b/test/_modules/Json.t.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity >=0.8.13 <0.9.0; import {Test, expect, println, json, JsonObject, vulcan} from "../../src/test.sol"; @@ -10,7 +11,7 @@ contract JsonTest is Test { } function testParseImmutable() external { - Foo memory obj = abi.decode(json.create('{"foo":"bar"}').parse(), (Foo)); + Foo memory obj = abi.decode(json.create('{"foo":"bar"}').unwrap().parse(), (Foo)); expect(obj.foo).toEqual("bar"); } @@ -20,81 +21,101 @@ contract JsonTest is Test { expect(obj.foo).toEqual("bar"); } + function testIsValid() external { + expect(json.isValid('{"foo":"bar"}')).toEqual(true); + expect(json.isValid("{}")).toEqual(true); + expect(json.isValid("[]")).toEqual(true); + expect(json.isValid('{"foo":"bar"')).toEqual(false); + expect(json.isValid('{"foo":bar"}')).toEqual(false); + expect(json.isValid("asdfasf")).toEqual(false); + } + + function testContainsKey() external { + JsonObject memory obj = json.create('{"foo":"bar"}').unwrap(); + expect(obj.containsKey(".foo")).toEqual(true); + expect(obj.containsKey(".bar")).toEqual(false); + } + + function testGetMaxUint() external { + uint256 i = json.create('{"foo":"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"}').unwrap() + .getUint(".foo"); + expect(i).toEqual(type(uint256).max); + } + function testGetUint() external { - expect(json.create('{"foo":123}').getUint(".foo")).toEqual(123); + expect(json.create('{"foo":123}').unwrap().getUint(".foo")).toEqual(123); } function testGetUintArray() external { - uint256[] memory arr = json.create('{"foo":[123]}').getUintArray(".foo"); + uint256[] memory arr = json.create('{"foo":[123]}').unwrap().getUintArray(".foo"); expect(arr.length).toEqual(1); expect(arr[0]).toEqual(123); } function testGetInt() external { - expect(json.create('{"foo":-123}').getInt(".foo")).toEqual(-123); + expect(json.create('{"foo":-123}').unwrap().getInt(".foo")).toEqual(-123); } function testGetIntArray() external { - int256[] memory arr = json.create('{"foo":[-123]}').getIntArray(".foo"); + int256[] memory arr = json.create('{"foo":[-123]}').unwrap().getIntArray(".foo"); expect(arr.length).toEqual(1); expect(arr[0]).toEqual(-123); } function testGetBool() external { - expect(json.create('{"foo":true}').getBool(".foo")).toEqual(true); + expect(json.create('{"foo":true}').unwrap().getBool(".foo")).toEqual(true); } function testGetBoolArray() external { - bool[] memory arr = json.create('{"foo":[true]}').getBoolArray(".foo"); + bool[] memory arr = json.create('{"foo":[true]}').unwrap().getBoolArray(".foo"); expect(arr.length).toEqual(1); expect(arr[0]).toEqual(true); } function testGetAddress() external { - expect(json.create('{"foo":"0x0000000000000000000000000000000000000001"}').getAddress(".foo")).toEqual( + expect(json.create('{"foo":"0x0000000000000000000000000000000000000001"}').unwrap().getAddress(".foo")).toEqual( address(1) ); } function testGetAddressArray() external { address[] memory arr = - json.create('{"foo":["0x0000000000000000000000000000000000000001"]}').getAddressArray(".foo"); + json.create('{"foo":["0x0000000000000000000000000000000000000001"]}').unwrap().getAddressArray(".foo"); expect(arr.length).toEqual(1); expect(arr[0]).toEqual(address(1)); } function testGetString() external { - expect(json.create('{"foo":"bar"}').getString(".foo")).toEqual("bar"); + expect(json.create('{"foo":"bar"}').unwrap().getString(".foo")).toEqual("bar"); } function testGetStringArray() external { - string[] memory arr = json.create('{"foo":["bar"]}').getStringArray(".foo"); + string[] memory arr = json.create('{"foo":["bar"]}').unwrap().getStringArray(".foo"); expect(arr.length).toEqual(1); expect(arr[0]).toEqual("bar"); } function testGetBytes() external { - expect(json.create('{"foo":"0x1234"}').getBytes(".foo")).toEqual(hex"1234"); + expect(json.create('{"foo":"0x1234"}').unwrap().getBytes(".foo")).toEqual(hex"1234"); } function testGetBytesArray() external { - bytes[] memory arr = json.create('{"foo":["0x1234"]}').getBytesArray(".foo"); + bytes[] memory arr = json.create('{"foo":["0x1234"]}').unwrap().getBytesArray(".foo"); expect(arr.length).toEqual(1); expect(arr[0]).toEqual(hex"1234"); } function testGetBytes32() external { expect( - json.create('{"foo":"0x0000000000000000000000000000000000000000000000000000000000000001"}').getBytes32( - ".foo" - ) + json.create('{"foo":"0x0000000000000000000000000000000000000000000000000000000000000001"}').unwrap() + .getBytes32(".foo") ).toEqual(bytes32(uint256(1))); } function testGetBytes32Array() external { bytes32[] memory arr = json.create( '{"foo":["0x0000000000000000000000000000000000000000000000000000000000000001"]}' - ).getBytes32Array(".foo"); + ).unwrap().getBytes32Array(".foo"); expect(arr.length).toEqual(1); expect(arr[0]).toEqual(bytes32(uint256(1))); } diff --git a/test/_modules/Request.t.sol b/test/_modules/Request.t.sol index ad118e71..479c7db8 100644 --- a/test/_modules/Request.t.sol +++ b/test/_modules/Request.t.sol @@ -27,6 +27,26 @@ contract RequestTest is Test { expect(obj.getString(".authenticated")).toEqual("true"); } + function testJsonBody() external { + RequestClient memory client = request.create(); + + Response memory res = client.post("https://httpbin.org/post").json('{ "foo": "bar" }').send().unwrap(); + + expect(res.status).toEqual(200); + + JsonObject memory obj = res.json().unwrap(); + + expect(obj.getString(".json.foo")).toEqual("bar"); + } + + function testJsonBodyFail() external { + RequestClient memory client = request.create(); + + ResponseResult memory res = client.post("https://httpbin.org/post").json('{ "foo": "bar" ').send(); + + expect(res.isError()).toEqual(true); + } + function testRequestFail() external { Response memory res = request.get("https://httpbin.org/404").unwrap(); expect(res.status).toEqual(404);