Skip to content

Commit 3fdf83f

Browse files
authored
Merge pull request #40 from FacturAPI/FAC-1703/feat/add-status-field-on-error-object
Agregar propiedad "Status" a objeto de error
2 parents 6e1ad4f + e09eea0 commit 3fdf83f

13 files changed

+169
-511
lines changed

CHANGELOG.md

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,20 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## [4.11.0] = 2025-12-10
8+
## [4.12.0] = Unreleased
99

1010
### Added
1111

12-
- Methods to carta porte catalogs
12+
- `FacturapiException.Status` now surfaces the HTTP status code when available.
13+
14+
## [4.11.0] - 2025-12-10
15+
16+
### Added
17+
18+
- Methods to carta porte catalogs
1319
- `SearchAirTranportCodes`, `SearchTransportConfigs`,`SearchRightsOfPassage`, `SearchCustomsDocuments`, `SearchPackagingTypes` `SearchTrailerTypes`, `SearchHazardousMaterials`, `SearchNavalAuthorizations`, `SearchPortStations`, `SearchMarineContainers`
1420

15-
## [4.10.1] = 2025-10-23
21+
## [4.10.1] - 2025-10-23
1622

1723
### Added
1824

@@ -56,7 +62,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
5662

5763
### Added
5864

59-
- Type IepsMode for Tax model
65+
- Type IepsMode for Tax model
6066
- Type Factor for Tax model
6167

6268
## [4.7.0] = 2025-02-25
@@ -74,7 +80,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7480
- Add List of Live Api Keys `Organizations.ListAsyncLiveApiKey`
7581
- Add Delete of a Live Api Key `Organization.DeleteAsyncLiveApiKey`
7682

77-
7883
## [4.5.0] - 2024-05-06
7984

8085
### Added
@@ -103,7 +108,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
103108
- Update payment form constants
104109
- Add new method for delete certs in organization
105110

106-
107111
## [4.1.0] - 2023-12-06
108112

109113
### Added
@@ -174,21 +178,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
174178
- Added missing properties on `Invoice` class: `ExternalId`, `Type`, `Stamp` and `Complements`.
175179

176180
## [1.0.3] - 2020-08-19
181+
177182
### Fixed
178183

179184
- `Invoice.Payments[].Related` should be a List
180185

181186
## [1.0.2] - 2019-09-12
187+
182188
### Fixed
183189

184190
- Bug at uploading certificates.
185191

186192
## [1.0.1] - 2019-08-25
193+
187194
### Fixed
188195

189196
- Problem uploading logo and certs for an organization.
190197

191198
## [1.0.0] - 2019-02-21
199+
192200
### Added
193201

194202
- Bunch of properties missing from the Invoice class like Payments, ForeignTrade, Related, etc.

FacturAPIException.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
using System;
2-
using System.Collections.Generic;
3-
using System.Linq;
4-
using System.Text;
5-
using System.Threading.Tasks;
62

73
namespace Facturapi
84
{
95
public class FacturapiException : Exception
106
{
7+
public int? Status { get; private set; }
8+
119
public FacturapiException() : base() { }
12-
public FacturapiException(string message) : base(message) { }
10+
public FacturapiException(string message, int? status = null) : base(message)
11+
{
12+
Status = status;
13+
}
1314
}
1415
}

Wrappers/BaseWrapper.cs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
using Newtonsoft.Json;
2+
using Newtonsoft.Json.Linq;
23
using System;
34
using System.Net.Http;
45
using System.Text;
6+
using System.Threading.Tasks;
57

68
namespace Facturapi.Wrappers
79
{
@@ -27,5 +29,58 @@ public BaseWrapper(string apiKey, string apiVersion = "v2")
2729
NullValueHandling = NullValueHandling.Ignore
2830
};
2931
}
32+
33+
protected FacturapiException CreateException(string resultString, HttpResponseMessage response)
34+
{
35+
JObject error = null;
36+
37+
try
38+
{
39+
error = JsonConvert.DeserializeObject<JObject>(resultString, this.jsonSettings);
40+
}
41+
catch (JsonException)
42+
{
43+
// Intentionally swallow exception: resultString is not valid JSON.
44+
// This allows graceful handling of non-JSON error responses.
45+
}
46+
47+
var message = error?["message"]?.ToString() ?? "An error occurred";
48+
49+
int? status = null;
50+
var statusToken = error?["status"];
51+
if (statusToken != null)
52+
{
53+
if (statusToken.Type == JTokenType.Integer)
54+
{
55+
status = statusToken.Value<int>();
56+
}
57+
else if (statusToken.Type == JTokenType.Float)
58+
{
59+
status = Convert.ToInt32(statusToken.Value<double>());
60+
}
61+
else if (int.TryParse(statusToken.ToString(), out var parsedStatus))
62+
{
63+
status = parsedStatus;
64+
}
65+
}
66+
67+
if (status == null && response != null)
68+
{
69+
status = (int)response.StatusCode;
70+
}
71+
72+
return new FacturapiException(message, status);
73+
}
74+
75+
protected async Task ThrowIfErrorAsync(HttpResponseMessage response)
76+
{
77+
if (response.IsSuccessStatusCode)
78+
{
79+
return;
80+
}
81+
82+
var resultString = await response.Content.ReadAsStringAsync();
83+
throw CreateException(resultString, response);
84+
}
3085
}
3186
}

Wrappers/CatalogWrapper.cs

Lines changed: 20 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
using Newtonsoft.Json;
2-
using Newtonsoft.Json.Linq;
32
using System.Collections.Generic;
43
using System.Net.Http;
5-
using System.Text;
64
using System.Threading.Tasks;
75

86
namespace Facturapi.Wrappers
@@ -15,183 +13,74 @@ public CatalogWrapper(string apiKey, string apiVersion = "v2") : base(apiKey, ap
1513

1614
public async Task<SearchResult<CatalogItem>> SearchProducts(Dictionary<string, object> query = null)
1715
{
18-
var response = await client.GetAsync(Router.SearchProductKeys(query));
19-
var resultString = await response.Content.ReadAsStringAsync();
20-
21-
if (!response.IsSuccessStatusCode)
22-
{
23-
var error = JsonConvert.DeserializeObject<JObject>(resultString, this.jsonSettings);
24-
throw new FacturapiException(error["message"].ToString());
25-
}
26-
27-
var searchResult = JsonConvert.DeserializeObject<SearchResult<CatalogItem>>(resultString, this.jsonSettings);
28-
return searchResult;
16+
return await this.SearchCatalogAsync(Router.SearchProductKeys(query));
2917
}
3018

3119
public async Task<SearchResult<CatalogItem>> SearchUnits(Dictionary<string, object> query = null)
3220
{
33-
var response = await client.GetAsync(Router.SearchUnitKeys(query));
34-
var resultString = await response.Content.ReadAsStringAsync();
35-
36-
if (!response.IsSuccessStatusCode)
37-
{
38-
var error = JsonConvert.DeserializeObject<JObject>(resultString, this.jsonSettings);
39-
throw new FacturapiException(error["message"].ToString());
40-
}
41-
42-
var searchResult = JsonConvert.DeserializeObject<SearchResult<CatalogItem>>(resultString, this.jsonSettings);
43-
return searchResult;
21+
return await this.SearchCatalogAsync(Router.SearchUnitKeys(query));
4422
}
4523

4624
// Carta Porte catalogs
4725
public async Task<SearchResult<CatalogItem>> SearchAirTransportCodes(Dictionary<string, object> query = null)
4826
{
49-
var response = await client.GetAsync(Router.SearchCartaporteAirTransportCodes(query));
50-
var resultString = await response.Content.ReadAsStringAsync();
51-
52-
if (!response.IsSuccessStatusCode)
53-
{
54-
var error = JsonConvert.DeserializeObject<JObject>(resultString, this.jsonSettings);
55-
throw new FacturapiException(error["message"].ToString());
56-
}
57-
58-
var searchResult = JsonConvert.DeserializeObject<SearchResult<CatalogItem>>(resultString, this.jsonSettings);
59-
return searchResult;
27+
return await this.SearchCatalogAsync(Router.SearchCartaporteAirTransportCodes(query));
6028
}
6129

6230
public async Task<SearchResult<CatalogItem>> SearchTransportConfigs(Dictionary<string, object> query = null)
6331
{
64-
var response = await client.GetAsync(Router.SearchCartaporteTransportConfigs(query));
65-
var resultString = await response.Content.ReadAsStringAsync();
66-
67-
if (!response.IsSuccessStatusCode)
68-
{
69-
var error = JsonConvert.DeserializeObject<JObject>(resultString, this.jsonSettings);
70-
throw new FacturapiException(error["message"].ToString());
71-
}
72-
73-
var searchResult = JsonConvert.DeserializeObject<SearchResult<CatalogItem>>(resultString, this.jsonSettings);
74-
return searchResult;
32+
return await this.SearchCatalogAsync(Router.SearchCartaporteTransportConfigs(query));
7533
}
7634

7735
public async Task<SearchResult<CatalogItem>> SearchRightsOfPassage(Dictionary<string, object> query = null)
7836
{
79-
var response = await client.GetAsync(Router.SearchCartaporteRightsOfPassage(query));
80-
var resultString = await response.Content.ReadAsStringAsync();
81-
82-
if (!response.IsSuccessStatusCode)
83-
{
84-
var error = JsonConvert.DeserializeObject<JObject>(resultString, this.jsonSettings);
85-
throw new FacturapiException(error["message"].ToString());
86-
}
87-
88-
var searchResult = JsonConvert.DeserializeObject<SearchResult<CatalogItem>>(resultString, this.jsonSettings);
89-
return searchResult;
37+
return await this.SearchCatalogAsync(Router.SearchCartaporteRightsOfPassage(query));
9038
}
9139

9240
public async Task<SearchResult<CatalogItem>> SearchCustomsDocuments(Dictionary<string, object> query = null)
9341
{
94-
var response = await client.GetAsync(Router.SearchCartaporteCustomsDocuments(query));
95-
var resultString = await response.Content.ReadAsStringAsync();
96-
97-
if (!response.IsSuccessStatusCode)
98-
{
99-
var error = JsonConvert.DeserializeObject<JObject>(resultString, this.jsonSettings);
100-
throw new FacturapiException(error["message"].ToString());
101-
}
102-
103-
var searchResult = JsonConvert.DeserializeObject<SearchResult<CatalogItem>>(resultString, this.jsonSettings);
104-
return searchResult;
42+
return await this.SearchCatalogAsync(Router.SearchCartaporteCustomsDocuments(query));
10543
}
10644

10745
public async Task<SearchResult<CatalogItem>> SearchPackagingTypes(Dictionary<string, object> query = null)
10846
{
109-
var response = await client.GetAsync(Router.SearchCartaportePackagingTypes(query));
110-
var resultString = await response.Content.ReadAsStringAsync();
111-
112-
if (!response.IsSuccessStatusCode)
113-
{
114-
var error = JsonConvert.DeserializeObject<JObject>(resultString, this.jsonSettings);
115-
throw new FacturapiException(error["message"].ToString());
116-
}
117-
118-
var searchResult = JsonConvert.DeserializeObject<SearchResult<CatalogItem>>(resultString, this.jsonSettings);
119-
return searchResult;
47+
return await this.SearchCatalogAsync(Router.SearchCartaportePackagingTypes(query));
12048
}
12149

12250
public async Task<SearchResult<CatalogItem>> SearchTrailerTypes(Dictionary<string, object> query = null)
12351
{
124-
var response = await client.GetAsync(Router.SearchCartaporteTrailerTypes(query));
125-
var resultString = await response.Content.ReadAsStringAsync();
126-
127-
if (!response.IsSuccessStatusCode)
128-
{
129-
var error = JsonConvert.DeserializeObject<JObject>(resultString, this.jsonSettings);
130-
throw new FacturapiException(error["message"].ToString());
131-
}
132-
133-
var searchResult = JsonConvert.DeserializeObject<SearchResult<CatalogItem>>(resultString, this.jsonSettings);
134-
return searchResult;
52+
return await this.SearchCatalogAsync(Router.SearchCartaporteTrailerTypes(query));
13553
}
13654

13755
public async Task<SearchResult<CatalogItem>> SearchHazardousMaterials(Dictionary<string, object> query = null)
13856
{
139-
var response = await client.GetAsync(Router.SearchCartaporteHazardousMaterials(query));
140-
var resultString = await response.Content.ReadAsStringAsync();
141-
142-
if (!response.IsSuccessStatusCode)
143-
{
144-
var error = JsonConvert.DeserializeObject<JObject>(resultString, this.jsonSettings);
145-
throw new FacturapiException(error["message"].ToString());
146-
}
147-
148-
var searchResult = JsonConvert.DeserializeObject<SearchResult<CatalogItem>>(resultString, this.jsonSettings);
149-
return searchResult;
57+
return await this.SearchCatalogAsync(Router.SearchCartaporteHazardousMaterials(query));
15058
}
15159

15260
public async Task<SearchResult<CatalogItem>> SearchNavalAuthorizations(Dictionary<string, object> query = null)
15361
{
154-
var response = await client.GetAsync(Router.SearchCartaporteNavalAuthorizations(query));
155-
var resultString = await response.Content.ReadAsStringAsync();
156-
157-
if (!response.IsSuccessStatusCode)
158-
{
159-
var error = JsonConvert.DeserializeObject<JObject>(resultString, this.jsonSettings);
160-
throw new FacturapiException(error["message"].ToString());
161-
}
162-
163-
var searchResult = JsonConvert.DeserializeObject<SearchResult<CatalogItem>>(resultString, this.jsonSettings);
164-
return searchResult;
62+
return await this.SearchCatalogAsync(Router.SearchCartaporteNavalAuthorizations(query));
16563
}
16664

16765
public async Task<SearchResult<CatalogItem>> SearchPortStations(Dictionary<string, object> query = null)
16866
{
169-
var response = await client.GetAsync(Router.SearchCartaportePortStations(query));
170-
var resultString = await response.Content.ReadAsStringAsync();
171-
172-
if (!response.IsSuccessStatusCode)
173-
{
174-
var error = JsonConvert.DeserializeObject<JObject>(resultString, this.jsonSettings);
175-
throw new FacturapiException(error["message"].ToString());
176-
}
177-
178-
var searchResult = JsonConvert.DeserializeObject<SearchResult<CatalogItem>>(resultString, this.jsonSettings);
179-
return searchResult;
67+
return await this.SearchCatalogAsync(Router.SearchCartaportePortStations(query));
18068
}
18169

18270
public async Task<SearchResult<CatalogItem>> SearchMarineContainers(Dictionary<string, object> query = null)
18371
{
184-
var response = await client.GetAsync(Router.SearchCartaporteMarineContainers(query));
185-
var resultString = await response.Content.ReadAsStringAsync();
72+
return await this.SearchCatalogAsync(Router.SearchCartaporteMarineContainers(query));
73+
}
18674

187-
if (!response.IsSuccessStatusCode)
75+
private async Task<SearchResult<CatalogItem>> SearchCatalogAsync(string url)
76+
{
77+
using (var response = await client.GetAsync(url))
18878
{
189-
var error = JsonConvert.DeserializeObject<JObject>(resultString, this.jsonSettings);
190-
throw new FacturapiException(error["message"].ToString());
79+
await this.ThrowIfErrorAsync(response);
80+
var resultString = await response.Content.ReadAsStringAsync();
81+
var searchResult = JsonConvert.DeserializeObject<SearchResult<CatalogItem>>(resultString, this.jsonSettings);
82+
return searchResult;
19183
}
192-
193-
var searchResult = JsonConvert.DeserializeObject<SearchResult<CatalogItem>>(resultString, this.jsonSettings);
194-
return searchResult;
19584
}
19685
}
19786
}

0 commit comments

Comments
 (0)