This is related to issue #49 and I get exactly the same error message and backtrace in my application. This time I got copilot to trace through the API code, review PR jolicode/harvest-openapi-generator#30 and get a solid understanding of how and when you can end up with an Error model that has a NULL message, thus throwing this exception:
JoliCode\Harvest\Api\Model\Error::getMessage(): Return value must be of type string, null returned
These were the key findings:
- The Error model declares getMessage(): string but the backing $message property is optional (nullable is absent and it isn’t in required), so when Harvest omits the field—as it does for OAuth/429 payloads that only expose error / error_description—the property remains null and getMessage() throws. See Error.php:19-46.
- The generated normalizer simply forwards whatever the payload contains into setMessage() without guarding for null, and if the key is missing it never initializes the property at all (ErrorNormalizer.php:34-68).
- The OpenAPI source embedded with the library still models the schema as only code + message with no nullable hint (harvest-openapi.yaml:3310-3332); therefore the generator emits a non-nullable getter.
The same thing applies to the getCode method of the Error model, where the normalizer also only sets the code if it is non-empty, so it's also possible to have a case where getCode() could throw a type error as well. It looks like it should just be a matter of updating the yaml specifications to set nullable to TRUE for the message and code properties of the error model. If you do that in the generator code, then in theory that should generate an error object that shouldn't throw exceptions if either or both of those are null when trying to get them. That's less than ideal for code trying to understand the error that occurred and act appropriately, but better than just getting a type error.
What would be really great is, in case of an erroneous API response, would be to make the response object available via the error object. For example, my case is handling throttling, where it sends a 429 code, along with a Retry-After header, but no message that gets set on the Error object. So all I know for sure is the request was throttled and I just have to use a safe value to wait to try again, since I cannot obtain the retry-after header that way.
I appreciate that all the real changes need to be made in the generator itself, not in this codebase, and I wish I had the time to clone that repo and dig through it to try some things out, test and see if that works etc and then send you a pull request over there. Unfortunately I just have to get my application working properly, so for now the best I can do is use copilot to dig through and help narrow it down and post an issue here that you can work with.
This is related to issue #49 and I get exactly the same error message and backtrace in my application. This time I got copilot to trace through the API code, review PR jolicode/harvest-openapi-generator#30 and get a solid understanding of how and when you can end up with an Error model that has a NULL message, thus throwing this exception:
JoliCode\Harvest\Api\Model\Error::getMessage(): Return value must be of type string, null returnedThese were the key findings:
The same thing applies to the getCode method of the Error model, where the normalizer also only sets the code if it is non-empty, so it's also possible to have a case where getCode() could throw a type error as well. It looks like it should just be a matter of updating the yaml specifications to set nullable to TRUE for the message and code properties of the error model. If you do that in the generator code, then in theory that should generate an error object that shouldn't throw exceptions if either or both of those are null when trying to get them. That's less than ideal for code trying to understand the error that occurred and act appropriately, but better than just getting a type error.
What would be really great is, in case of an erroneous API response, would be to make the response object available via the error object. For example, my case is handling throttling, where it sends a 429 code, along with a Retry-After header, but no message that gets set on the Error object. So all I know for sure is the request was throttled and I just have to use a safe value to wait to try again, since I cannot obtain the retry-after header that way.
I appreciate that all the real changes need to be made in the generator itself, not in this codebase, and I wish I had the time to clone that repo and dig through it to try some things out, test and see if that works etc and then send you a pull request over there. Unfortunately I just have to get my application working properly, so for now the best I can do is use copilot to dig through and help narrow it down and post an issue here that you can work with.