Skip to content

Commit e52615d

Browse files
wisepotatomaurei
authored andcommitted
feat: set up basiscs for ID fetching (#640)
* feat: set up basiscs for ID fetching * feat: add fixes for baseId test * chore: thats it for today * tests: add test for non-set base id * chore: refactor setup * chore: fix for double /api/v1 * chore: remove current request tests
1 parent b5207ec commit e52615d

File tree

5 files changed

+332
-29
lines changed

5 files changed

+332
-29
lines changed

src/JsonApiDotNetCore/Middleware/CurrentRequestMiddleware.cs

+118-28
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
using System;
2+
using System.ComponentModel;
23
using System.Linq;
34
using System.Threading.Tasks;
45
using JsonApiDotNetCore.Configuration;
56
using JsonApiDotNetCore.Internal;
67
using JsonApiDotNetCore.Internal.Contracts;
78
using JsonApiDotNetCore.Managers.Contracts;
89
using Microsoft.AspNetCore.Http;
10+
using Microsoft.AspNetCore.Http.Features;
911
using Microsoft.AspNetCore.Routing;
1012
using Microsoft.Extensions.Primitives;
1113

@@ -21,6 +23,7 @@ public class CurrentRequestMiddleware
2123
private ICurrentRequest _currentRequest;
2224
private IResourceGraph _resourceGraph;
2325
private IJsonApiOptions _options;
26+
private RouteValueDictionary _routeValues;
2427
private IControllerResourceMapping _controllerResourceMapping;
2528

2629
public CurrentRequestMiddleware(RequestDelegate next)
@@ -39,12 +42,15 @@ public async Task Invoke(HttpContext httpContext,
3942
_controllerResourceMapping = controllerResourceMapping;
4043
_resourceGraph = resourceGraph;
4144
_options = options;
45+
_routeValues = httpContext.GetRouteData().Values;
4246
var requestResource = GetCurrentEntity();
4347
if (requestResource != null)
4448
{
45-
_currentRequest.SetRequestResource(GetCurrentEntity());
49+
_currentRequest.SetRequestResource(requestResource);
4650
_currentRequest.IsRelationshipPath = PathIsRelationship();
47-
_currentRequest.BasePath = GetBasePath(_currentRequest.GetRequestResource().ResourceName);
51+
_currentRequest.BasePath = GetBasePath(requestResource.ResourceName);
52+
_currentRequest.BaseId = GetBaseId();
53+
_currentRequest.RelationshipId = GetRelationshipId();
4854
}
4955

5056
if (IsValid())
@@ -53,50 +59,127 @@ public async Task Invoke(HttpContext httpContext,
5359
}
5460
}
5561

56-
private string GetBasePath(string entityName)
62+
private string GetBaseId()
5763
{
58-
var r = _httpContext.Request;
59-
if (_options.RelativeLinks)
64+
var resource = _currentRequest.GetRequestResource();
65+
var individualComponents = SplitCurrentPath();
66+
if (individualComponents.Length < 2)
6067
{
61-
return GetNamespaceFromPath(r.Path, entityName);
68+
return null;
6269
}
63-
return $"{r.Scheme}://{r.Host}{GetNamespaceFromPath(r.Path, entityName)}";
70+
var indexOfResource = individualComponents.ToList().FindIndex(c => c == resource.ResourceName);
71+
var baseId = individualComponents.ElementAtOrDefault(indexOfResource + 1);
72+
if (baseId == null)
73+
{
74+
return null;
75+
}
76+
CheckIdType(baseId, resource.IdentityType);
77+
return baseId;
6478
}
79+
private string GetRelationshipId()
80+
{
81+
var resource = _currentRequest.GetRequestResource();
82+
if (!_currentRequest.IsRelationshipPath)
83+
{
84+
return null;
85+
}
86+
var components = SplitCurrentPath();
87+
var toReturn = components.ElementAtOrDefault(4);
6588

66-
internal static string GetNamespaceFromPath(string path, string entityName)
89+
if (toReturn == null)
90+
{
91+
return null;
92+
}
93+
var relType = _currentRequest.RequestRelationship.RightType;
94+
var relResource = _resourceGraph.GetResourceContext(relType);
95+
var relIdentityType = relResource.IdentityType;
96+
CheckIdType(toReturn, relIdentityType);
97+
return toReturn;
98+
}
99+
private string[] SplitCurrentPath()
67100
{
68-
var entityNameSpan = entityName.AsSpan();
69-
var pathSpan = path.AsSpan();
70-
const char delimiter = '/';
71-
for (var i = 0; i < pathSpan.Length; i++)
101+
var path = _httpContext.Request.Path.Value;
102+
var ns = $"/{GetNameSpace()}";
103+
var nonNameSpaced = path.Replace(ns, "");
104+
nonNameSpaced = nonNameSpaced.Trim('/');
105+
var individualComponents = nonNameSpaced.Split('/');
106+
return individualComponents;
107+
}
108+
109+
110+
private void CheckIdType(string value, Type idType)
111+
{
112+
try
72113
{
73-
if (pathSpan[i].Equals(delimiter))
114+
var converter = TypeDescriptor.GetConverter(idType);
115+
if (converter != null)
74116
{
75-
var nextPosition = i + 1;
76-
if (pathSpan.Length > i + entityNameSpan.Length)
117+
if (!converter.IsValid(value))
77118
{
78-
var possiblePathSegment = pathSpan.Slice(nextPosition, entityNameSpan.Length);
79-
if (entityNameSpan.SequenceEqual(possiblePathSegment))
119+
throw new JsonApiException(500, $"We could not convert the id '{value}'");
120+
}
121+
else
122+
{
123+
if (idType == typeof(int))
80124
{
81-
// check to see if it's the last position in the string
82-
// or if the next character is a /
83-
var lastCharacterPosition = nextPosition + entityNameSpan.Length;
84-
85-
if (lastCharacterPosition == pathSpan.Length || pathSpan.Length >= lastCharacterPosition + 2 && pathSpan[lastCharacterPosition].Equals(delimiter))
125+
if ((int)converter.ConvertFromString(value) < 0)
86126
{
87-
return pathSpan.Slice(0, i).ToString();
127+
throw new JsonApiException(500, "The base ID is an integer, and it is negative.");
88128
}
89129
}
90130
}
91131
}
92132
}
133+
catch (NotSupportedException)
134+
{
135+
136+
}
93137

94-
return string.Empty;
138+
}
139+
140+
private string GetBasePath(string resourceName = null)
141+
{
142+
var r = _httpContext.Request;
143+
if (_options.RelativeLinks)
144+
{
145+
return GetNameSpace(resourceName);
146+
}
147+
var ns = GetNameSpace(resourceName);
148+
var customRoute = GetCustomRoute(r.Path.Value, resourceName);
149+
var toReturn = $"{r.Scheme}://{r.Host}/{ns}";
150+
if(customRoute != null)
151+
{
152+
toReturn += $"/{customRoute}";
153+
}
154+
return toReturn;
155+
}
156+
157+
private object GetCustomRoute(string path, string resourceName)
158+
{
159+
var ns = GetNameSpace();
160+
var trimmedComponents = path.Trim('/').Split('/').ToList();
161+
var resourceNameIndex = trimmedComponents.FindIndex(c => c == resourceName);
162+
var newComponents = trimmedComponents.Take(resourceNameIndex ).ToArray();
163+
var customRoute = string.Join('/', newComponents);
164+
if(customRoute == ns)
165+
{
166+
return null;
167+
}
168+
else
169+
{
170+
return customRoute;
171+
}
172+
}
173+
174+
private string GetNameSpace(string resourceName = null)
175+
{
176+
177+
return _options.Namespace;
95178
}
96179

97180
protected bool PathIsRelationship()
98181
{
99-
var actionName = (string)_httpContext.GetRouteData().Values["action"];
182+
var actionName = (string)_routeValues["action"];
100183
return actionName.ToLower().Contains("relationships");
101184
}
102185

@@ -124,7 +207,9 @@ private bool IsValidAcceptHeader(HttpContext context)
124207
foreach (var acceptHeader in acceptHeaders)
125208
{
126209
if (ContainsMediaTypeParameters(acceptHeader) == false)
210+
{
127211
continue;
212+
}
128213

129214
FlushResponse(context, 406);
130215
return false;
@@ -165,16 +250,21 @@ private void FlushResponse(HttpContext context, int statusCode)
165250
/// <returns></returns>
166251
private ResourceContext GetCurrentEntity()
167252
{
168-
var controllerName = (string)_httpContext.GetRouteValue("controller");
253+
var controllerName = (string)_routeValues["controller"];
169254
if (controllerName == null)
255+
{
170256
return null;
257+
}
171258
var resourceType = _controllerResourceMapping.GetAssociatedResource(controllerName);
172259
var requestResource = _resourceGraph.GetResourceContext(resourceType);
173260
if (requestResource == null)
261+
{
174262
return requestResource;
175-
var rd = _httpContext.GetRouteData().Values;
176-
if (rd.TryGetValue("relationshipName", out object relationshipName))
263+
}
264+
if (_routeValues.TryGetValue("relationshipName", out object relationshipName))
265+
{
177266
_currentRequest.RequestRelationship = requestResource.Relationships.Single(r => r.PublicRelationshipName == (string)relationshipName);
267+
}
178268
return requestResource;
179269
}
180270
}

src/JsonApiDotNetCore/RequestServices/Contracts/ICurrentRequest.cs

+2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ public interface ICurrentRequest
2929
/// is the relationship attribute associated with the targeted relationship
3030
/// </summary>
3131
RelationshipAttribute RequestRelationship { get; set; }
32+
string BaseId { get; set; }
33+
string RelationshipId { get; set; }
3234

3335
/// <summary>
3436
/// Sets the current context entity for this entire request

src/JsonApiDotNetCore/RequestServices/CurrentRequest.cs

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ class CurrentRequest : ICurrentRequest
1010
public string BasePath { get; set; }
1111
public bool IsRelationshipPath { get; set; }
1212
public RelationshipAttribute RequestRelationship { get; set; }
13+
public string BaseId { get; set; }
14+
public string RelationshipId { get; set; }
1315

1416
/// <summary>
1517
/// The main resource of the request.

test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/CustomControllerTests.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ public async Task CustomRouteControllers_Creates_Proper_Relationship_Links()
128128
var deserializedBody = JsonConvert.DeserializeObject<JObject>(body);
129129

130130
var result = deserializedBody["data"]["relationships"]["owner"]["links"]["related"].ToString();
131-
Assert.EndsWith($"{route}/owner", deserializedBody["data"]["relationships"]["owner"]["links"]["related"].ToString());
131+
Assert.EndsWith($"{route}/owner", result);
132132
}
133133
}
134134
}

0 commit comments

Comments
 (0)