Skip to content

Commit

Permalink
Merge pull request #683 from microsoft/feature/url-template
Browse files Browse the repository at this point in the history
adds support for url templating
  • Loading branch information
baywet authored Oct 27, 2021
2 parents 70a8cf0 + 26e21b1 commit 629b40d
Show file tree
Hide file tree
Showing 88 changed files with 1,410 additions and 768 deletions.
2 changes: 1 addition & 1 deletion .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
"command": "powershell",
"args": [
"-command",
"Remove-Item -Recurse -Include TestResults -Path tests"
"Remove-Item -Recurse -Include TestResults -Path ${workspaceFolder}/tests"
]
}
},
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- Switched to URL templates instead of string contract for URL building #683
- Fixed a bug where CSharp method names would not follow naming conventions #730

## [0.0.10] - 2021-10-06

### Changed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ public void SetsSelectQueryParameters()
var requestInfo = new RequestInformation
{
HttpMethod = HttpMethod.GET,
UrlTemplate = "http://localhost/me?select={select}"
};
requestInfo.SetURI("http://localhost/me", "", true);
Action<GetQueryParameters> q = x => x.Select = new[] { "id", "displayName" };
var qParams = new GetQueryParameters();
q.Invoke(qParams);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,20 @@ namespace Microsoft.Kiota.Abstractions.Tests
{
public class RequestInformationTests
{
[Fact]
public void SetUriAppendsUrlSegments()
{
// Arrange
var testRequest = new RequestInformation()
{
HttpMethod = HttpMethod.GET,
URI = new Uri("http://localhost")
};
// Act
testRequest.SetURI(testRequest.URI.OriginalString,"/me",false);
// Assert
Assert.Equal("http://localhost/me", testRequest.URI.OriginalString);
}

[Fact]
public void SetUriExtractsQueryParameters()
{
// Arrange
var testRequest = new RequestInformation()
{
HttpMethod = HttpMethod.GET,
URI = new Uri("http://localhost")
UrlTemplate = "http://localhost/{path}/me?foo={foo}"
};
// Act
testRequest.SetURI("http://localhost/me?foo=bar", "", true);
testRequest.QueryParameters.Add("foo", "bar");
testRequest.PathParameters.Add("path", "baz");
// Assert
Assert.Equal("http://localhost/me", testRequest.URI.OriginalString);
Assert.Equal("http://localhost/baz/me?foo=bar", testRequest.URI.ToString());
Assert.NotEmpty(testRequest.QueryParameters);
Assert.Equal("foo",testRequest.QueryParameters.First().Key);
Assert.Equal("bar", testRequest.QueryParameters.First().Value.ToString());
Expand Down
6 changes: 5 additions & 1 deletion abstractions/dotnet/src/Microsoft.Kiota.Abstractions.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@
<TargetFramework>net5.0</TargetFramework>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<RepositoryUrl>https://github.com/microsoft/kiota</RepositoryUrl>
<Version>1.0.21</Version>
<Version>1.0.22</Version>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<!-- Enable this line once we go live to prevent breaking changes -->
<!-- <PackageValidationBaselineVersion>1.0.0</PackageValidationBaselineVersion> -->
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Tavis.UriTemplates" Version="1.1.1" />
</ItemGroup>

</Project>
67 changes: 44 additions & 23 deletions abstractions/dotnet/src/RequestInformation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.IO;
using System.Linq;
using Microsoft.Kiota.Abstractions.Serialization;
using Tavis.UriTemplates;

namespace Microsoft.Kiota.Abstractions
{
Expand All @@ -15,36 +16,56 @@ namespace Microsoft.Kiota.Abstractions
/// </summary>
public class RequestInformation
{
private Uri _rawUri;
/// <summary>
/// The URI of the request.
/// </summary>
public Uri URI { get; set; }
/// <summary>
/// Sets the URI of the request.
/// </summary>
/// <param name="currentPath">the current path (scheme, host, port, path, query parameters) of the request.</param>
/// <param name="pathSegment">the segment to append to the current path.</param>
/// <param name="isRawUrl">whether the path segment is a raw url. When true, the segment is not happened and the current path is parsed for query parameters.</param>
/// <exception cref="UriFormatException">Thrown when the built URI is an invalid format.</exception>
public void SetURI(string currentPath, string pathSegment, bool isRawUrl)
{
if (isRawUrl)
{
if(string.IsNullOrEmpty(currentPath))
throw new ArgumentNullException(nameof(currentPath));
var parseUri = new Uri(currentPath);
var parseQueryString = parseUri.Query.TrimStart('?'); //remove leading ? if needed
foreach(var qsp in parseQueryString.Split('&').Select(x => x.Split('=')).Where(x => !string.IsNullOrEmpty(x[0]))) {
QueryParameters.Add(qsp[0], qsp.Length > 1 ? qsp[1] : null);
}
URI = new Uri(parseUri.GetComponents(UriComponents.SchemeAndServer | UriComponents.Path, UriFormat.Unescaped));
public Uri URI {
set {
if(value == null)
throw new ArgumentNullException(nameof(value));
QueryParameters.Clear();
PathParameters.Clear();
_rawUri = value;
}
else
{
URI = new Uri(currentPath + pathSegment);
get {
if(_rawUri != null)
return _rawUri;
else if(PathParameters.TryGetValue("request-raw-url", out var rawUrl) &&
rawUrl is string rawUrlString) {
URI = new Uri(rawUrlString);
return _rawUri;
}
else
{
var parsedUrlTemplate = new UriTemplate(UrlTemplate);
foreach(var urlTemplateParameter in PathParameters)
{
// if the value is boolean, lets pass in a lowercase string as the final url will be uppercase due to the way ToString() works for booleans
var sanitizedValue = (urlTemplateParameter.Value is bool boolValue) ? boolValue.ToString().ToLower() : urlTemplateParameter.Value;
parsedUrlTemplate.SetParameter(urlTemplateParameter.Key, sanitizedValue);
}

foreach(var queryStringParameter in QueryParameters)
if(queryStringParameter.Value != null)
{
// if the value is boolean, lets pass in a lowercase string as the final url will be uppercase due to the way ToString() works for booleans
var sanitizedValue = (queryStringParameter.Value is bool boolValue) ? boolValue.ToString().ToLower() : queryStringParameter.Value;
parsedUrlTemplate.SetParameter(queryStringParameter.Key, sanitizedValue);
}
return new Uri(parsedUrlTemplate.Resolve());
}
}
}
/// <summary>
/// The Url template for the current request.
/// </summary>
public string UrlTemplate { get; set; }
/// <summary>
/// The path parameters to use for the URL template when generating the URI.
/// </summary>
public IDictionary<string, object> PathParameters { get; set; } = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// The <see cref="HttpMethod">HTTP method</see> of the request.
/// </summary>
public HttpMethod HttpMethod { get; set; }
Expand Down
5 changes: 4 additions & 1 deletion abstractions/go/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@ module github.com/microsoft/kiota/abstractions/go

go 1.16

require github.com/google/uuid v1.3.0 // indirect
require (
github.com/google/uuid v1.3.0 // indirect
github.com/yosida95/uritemplate/v3 v3.0.1 // indirect
)
2 changes: 2 additions & 0 deletions abstractions/go/go.sum
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/yosida95/uritemplate/v3 v3.0.1 h1:+Fs//CsT+x231WmUQhMHWMxZizMvpnkOVWop02mVCfs=
github.com/yosida95/uritemplate/v3 v3.0.1/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
75 changes: 48 additions & 27 deletions abstractions/go/request_information.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,62 +3,83 @@ package abstractions
import (
"errors"
"reflect"
"strings"

"net/url"
u "net/url"

s "github.com/microsoft/kiota/abstractions/go/serialization"
t "github.com/yosida95/uritemplate/v3"
)

/* This type represents an abstract HTTP request. */
type RequestInformation struct {
Method HttpMethod
URI u.URL
uri *u.URL
Headers map[string]string
QueryParameters map[string]string
Content []byte
PathParameters map[string]string
UrlTemplate string
options map[string]RequestOption
}

const raw_url_key = "request-raw-url"

func NewRequestInformation() *RequestInformation {
return &RequestInformation{
URI: u.URL{},
Headers: make(map[string]string),
QueryParameters: make(map[string]string),
options: make(map[string]RequestOption),
PathParameters: make(map[string]string),
}
}

func (request *RequestInformation) SetUri(currentPath string, pathSegment string, isRawUrl bool) error {
if isRawUrl {
if currentPath == "" {
return errors.New("current path cannot be empty")
}
questionMarkSplat := strings.Split(currentPath, "?")
schemeHostAndPath := questionMarkSplat[0]
uri, err := url.Parse(schemeHostAndPath)
func (request *RequestInformation) GetUri() (*u.URL, error) {
if request.uri != nil {
return request.uri, nil
} else if request.UrlTemplate == "" {
return nil, errors.New("uri cannot be empty")
} else if request.PathParameters == nil {
return nil, errors.New("uri template parameters cannot be nil")
} else if request.QueryParameters == nil {
return nil, errors.New("uri query parameters cannot be nil")
} else if request.PathParameters[raw_url_key] != "" {
uri, err := u.Parse(request.PathParameters[raw_url_key])
if err != nil {
return err
return nil, err
}
request.URI = *uri
if len(questionMarkSplat) > 1 {
queryParameters := questionMarkSplat[1]
for _, queryParameter := range strings.Split(queryParameters, "&") {
keyValue := strings.Split(queryParameter, "=")
if len(keyValue) == 2 {
request.QueryParameters[keyValue[0]] = keyValue[1]
} else if len(keyValue) == 1 {
request.QueryParameters[keyValue[0]] = ""
}
}
err = request.SetUri(*uri)
if err != nil {
return nil, err
}
return request.uri, nil
} else {
uri, err := url.Parse(currentPath + pathSegment)
uriTemplate, err := t.New(request.UrlTemplate)
if err != nil {
return err
return nil, err
}
request.URI = *uri
values := t.Values{}
for key, value := range request.PathParameters {
values.Set(key, t.String(value))
}
for key, value := range request.QueryParameters {
values.Set(key, t.String(value))
}
url, err := uriTemplate.Expand(values)
if err != nil {
return nil, err
}
uri, err := u.Parse(url)
return uri, err
}
}

func (request *RequestInformation) SetUri(url u.URL) error {
request.uri = &url
for k := range request.PathParameters {
delete(request.PathParameters, k)
}
for k := range request.QueryParameters {
delete(request.QueryParameters, k)
}
return nil
}
Expand Down
4 changes: 3 additions & 1 deletion abstractions/java/lib/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ dependencies {
implementation 'com.google.guava:guava:31.0.1-jre'

implementation 'org.javatuples:javatuples:1.2'

implementation 'com.github.hal4j:uritemplate:1.2.3'
}

publishing {
Expand All @@ -46,7 +48,7 @@ publishing {
publications {
gpr(MavenPublication) {
artifactId 'kiota-abstractions'
version '1.0.21'
version '1.0.22'
from(components.java)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.lang.reflect.Field;
import java.lang.IllegalAccessException;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import javax.annotation.Nonnull;
Expand All @@ -12,7 +13,14 @@ public void AddQueryParameters(@Nonnull final Map<String, Object> target) {
final Field[] fields = this.getClass().getFields();
for(final Field field : fields) {
try {
target.put(field.getName(), field.get(this));
var value = field.get(this);
if(value != null) {
if(value.getClass().isArray()) {
target.put(field.getName(), Arrays.asList((Object[])value));
} else {
target.put(field.getName(), value);
}
}
} catch (IllegalAccessException ex) {
//TODO log
}
Expand Down
Loading

0 comments on commit 629b40d

Please sign in to comment.