/// <inheritdoc /> public void Apply(ApiOperationDescriptor descriptor) { var docs = descriptor.OperationType.GetXmlDocsElement(); if (docs == null) { return; } var exceptionElements = docs.Elements("exception"); foreach (var e in exceptionElements) { // The exception type is stored as T:[full name]. Strip the first two characters. var exceptionTypeText = e.Attribute("cref").Value.Substring(2); var exceptionType = Type.GetType(exceptionTypeText); if (exceptionType == null) { throw new InvalidOperationException( $"Could not find type {exceptionTypeText} as described by an exception tag in documentation for the operation {descriptor.OperationType.FullName}."); } var status = e.Attribute("status") == null?ToHttpStatus(exceptionType) : int.Parse(e.Attribute("status").Value); var description = e.Value; descriptor.AddResponse(new ResponseDescriptor( exceptionType, status, description, e.Attributes().ToDictionary(a => a.Name.LocalName, a => a.Value))); } }
private ApiOperationDescriptor CreateApiOperationDescriptor(Type type, string source) { var descriptor = new ApiOperationDescriptor(type, source) { AnonymousAccessAllowed = type.HasAttribute <AllowAnonymousAttribute>(true), IsExposed = type.HasAttribute <UnexposedOperationAttribute>(true) == false, ShouldAudit = !type.HasAttribute <DoNotAuditOperationAttribute>(true), RecordPerformanceMetrics = !type.HasAttribute <DoNotRecordPerformanceMetricsAttribute>(true), }; foreach (var linkAttribute in type.GetCustomAttributes <LinkAttribute>()) { descriptor.AddLink( new ApiOperationLink(descriptor, linkAttribute.RoutePattern, linkAttribute.Rel ?? descriptor.Name) { ResourceType = linkAttribute.ResourceType, }); } RegisterResponses(descriptor); foreach (var c in this._conventions) { c.Apply(descriptor); } return(descriptor); }
/// <inheritdoc /> public IApmSpan StartOperation(ApiOperationDescriptor operation, string spanKind, IDictionary <string, string> existingContext = null) { if (!global::Elastic.Apm.Agent.IsConfigured) { return(NullApmSpan.Instance); } var tracer = global::Elastic.Apm.Agent.Tracer; if (tracer.CurrentTransaction != null) { // If a transaction is already underway we want to set it's name for a more accurate picture (i.e. this is // a HTTP call but we want to use the operation name not the HTTP route name). tracer.CurrentTransaction.Name = operation.Name; // We will also start a new span as there may be work before and after in the framework that should // be tracked separately from the Blueprint processing work. return(new ElasticSpan( tracer.CurrentTransaction.StartSpan(operation.Name, ApiConstants.TypeRequest, "operation", ApiConstants.ActionExec))); } DistributedTracingData?distributedTracingData = null; if (existingContext != null && existingContext.TryGetValue("ElasticDTD", out var d)) { distributedTracingData = DistributedTracingData.TryDeserializeFromString(d); } return(new ElasticSpan(tracer.StartTransaction( operation.Name, spanKind, distributedTracingData))); }
private List <IApiAuthoriser> GetForOperation(ApiOperationDescriptor descriptor) { return(_operationTypeAuthorisers.GetOrAdd(descriptor.OperationType, t => { return this._apiAuthorisers.Where(checker => checker.AppliesTo(descriptor)).ToList(); })); }
/// <inheritdoc /> public void Apply(ApiOperationDescriptor descriptor) { if (this.IsSupported(descriptor.OperationType)) { string supportedMethod; var httpMethodAttribute = descriptor.OperationType.GetCustomAttribute <HttpMethodAttribute>(true); if (httpMethodAttribute != null) { supportedMethod = httpMethodAttribute.HttpMethod; } else { // By default, command are POST and everything else GET supportedMethod = typeof(ICommand).IsAssignableFrom(descriptor.OperationType) ? "POST" : "GET"; } descriptor.SetFeatureData(new HttpOperationFeatureData(supportedMethod)); descriptor.AllowMultipleHandlers = false; descriptor.RequiresReturnValue = true; RegisterResponses(descriptor); } }
public void When_Placeholder_Followed_By_Static_Then_Includes_All() { // Act var descriptor = new ApiOperationDescriptor(typeof(LinkGeneratorTestsOperation), "tests"); var link = new ApiOperationLink(descriptor, "/aUrl/{clientid:id}/some-more", "a.rel"); // Assert link.CreateRelativeUrl(new { id = 15484 }).Should().EndWith("aUrl/15484/some-more"); }
public void When_Additional_Properties_Exists_Ignore_When_IncludeExtra_False() { // Act var descriptor = new ApiOperationDescriptor(typeof(LinkGeneratorTestsOperation), "tests"); var link = new ApiOperationLink(descriptor, "/users", "a.rel"); // Assert link.CreateRelativeUrl(new { sortBy = "name", page = 2 }, false).Should().Be("users"); }
public void When_Url_With_Placeholder_Requiring_Encoding_Then_CreateUrl_Replaces_With_Encoded_Property_In_PropValues() { // Act var descriptor = new ApiOperationDescriptor(typeof(LinkGeneratorTestsOperation), "tests"); var link = new ApiOperationLink(descriptor, "/aUrl/{category}", "a.rel"); // Assert link.CreateRelativeUrl(new { category = "all dogs" }).Should().EndWith("aUrl/all%20dogs"); }
public void When_Placeholder_Specifies_Alternate_Property_And_Followed_By_Another_Placeholder_Then_Uses_That_Property_From_Instance_Values() { // Act var descriptor = new ApiOperationDescriptor(typeof(LinkGeneratorTestsOperation), "tests"); var link = new ApiOperationLink(descriptor, "/aUrl/{clientid:id}/{category}", "a.rel"); // Assert link.CreateRelativeUrl(new { id = 15484, category = "value" }).Should().EndWith("aUrl/15484/value"); }
public void When_Url_With_Placeholder_Then_Returns_Null_If_Property_Is_Null() { // Act var descriptor = new ApiOperationDescriptor(typeof(LinkGeneratorTestsOperation), "tests"); var link = new ApiOperationLink(descriptor, "/aUrl/{category}", "a.rel"); // Assert link.CreateRelativeUrl(new { category = (string)null }).Should().BeNull(); }
public void When_Url_No_Placeholders_CreateUrl_Returns_Url_As_Is() { // Arrange var descriptor = new ApiOperationDescriptor(typeof(LinkGeneratorTestsOperation), "tests"); var link = new ApiOperationLink(descriptor, "/aUrl", "a.rel"); // Assert link.CreateRelativeUrl(new object()).Should().EndWith("aUrl"); }
public void When_Placeholder_Has_Format_Then_CreateUrl_Uses_Format() { // Act var descriptor = new ApiOperationDescriptor(typeof(LinkGeneratorTestsOperation), "tests"); var date = new DateTime(2012, 04, 21); var link = new ApiOperationLink(descriptor, "/aUrl/{date(yyyy-MM-dd)}", "a.rel"); // Assert link.CreateRelativeUrl(new { date }).Should().EndWith("aUrl/2012-04-21"); }
public void When_Url_With_Invalid_Alternate_Placeholder_Then_Exception_Thrown() { // Act var descriptor = new ApiOperationDescriptor(typeof(LinkGeneratorTestsOperation), "tests"); Action tryCreate = () => new ApiOperationLink(descriptor, "/aUrl/{clientId:doesNotExist}", "a.rel", typeof(LinkGeneratorTestsResource)); // Assert tryCreate.Should().ThrowExactly <OperationLinkFormatException>() .WithMessage("Link /aUrl/{clientId:doesNotExist} for operation LinkGeneratorTestsOperation specifies placeholder {clientId:doesNotExist}. Cannot find alternate property doesNotExist on resource LinkGeneratorTestsResource"); }
public void When_Url_With_Placeholder_With_Missing_Property_In_ResourceType_Then_Exception_Thrown() { // Act var descriptor = new ApiOperationDescriptor(typeof(LinkGeneratorTestsOperation), "tests"); Action tryCreate = () => new ApiOperationLink(descriptor, "/aUrl/{clientId}", "a.rel", typeof(LinkGeneratorTestsResource)); // Assert tryCreate.Should().ThrowExactly <OperationLinkFormatException>() .WithMessage("Link /aUrl/{clientId} for operation LinkGeneratorTestsOperation specifies placeholder ClientId that cannot be found on resource LinkGeneratorTestsResource"); }
public void When_ResourceType_is_not_ILinkableResource_then_exception() { // Act var descriptor = new ApiOperationDescriptor(typeof(LinkGeneratorTestsOperation), "tests"); Action tryCreate = () => new ApiOperationLink(descriptor, "/aUrl/{clientId:doesNotExist}", "a.rel", typeof(string)); // Assert tryCreate.Should().ThrowExactly <OperationLinkFormatException>() .WithMessage("Resource type string is not assignable to ILinkableResource, cannot add a link for LinkGeneratorTestsOperation"); }
public void When_Url_With_Invalid_Placeholder_Then_Exception_thrown() { // Act var descriptor = new ApiOperationDescriptor(typeof(LinkGeneratorTestsOperation), "tests"); Action tryCreate = () => new ApiOperationLink(descriptor, "/aUrl/{cannotBeFound}", "a.rel"); // Assert tryCreate.Should().ThrowExactly <OperationLinkFormatException>() .WithMessage("URL /aUrl/{cannotBeFound} is invalid. Property cannotBeFound does not exist on operation type LinkGeneratorTestsOperation"); }
public void When_Placeholder_Has_Alternate_Name_Then_RoutingUrl_Strips() { // Arrange var descriptor = new ApiOperationDescriptor(typeof(LinkGeneratorTestsOperation), "tests"); // Act var link = new ApiOperationLink(descriptor, "/aUrl/{ClientId:Id}", "a.rel"); // Assert link.RoutingUrl.Should().Be("aUrl/{ClientId}"); }
public void When_Created_Then_Exposes_Rel_Property() { // Arrange var descriptor = new ApiOperationDescriptor(typeof(LinkGeneratorTestsOperation), "tests"); // Act var link = new ApiOperationLink(descriptor, "/aUrl", "a.rel"); // Assert link.Rel.Should().Be("a.rel"); }
public void When_Format_Has_QueryString_RoutingUrl_Strips() { // Arrange var descriptor = new ApiOperationDescriptor(typeof(LinkGeneratorTestsOperation), "tests"); // Act var link = new ApiOperationLink(descriptor, "/aUrl/{clientid:id}?format=pdf", "a.rel"); // Assert link.RoutingUrl.Should().Be("aUrl/{ClientId}"); }
public void When_Placeholder_Has_Different_Case_RoutingUrl_Should_Normalise() { // Arrange var descriptor = new ApiOperationDescriptor(typeof(LinkGeneratorTestsOperation), "tests"); // Act var link = new ApiOperationLink(descriptor, "/aUrl/{clientId:Id}", "a.rel"); // Assert link.RoutingUrl.Should().Be("aUrl/{ClientId}"); }
/// <inheritdoc /> public IApmSpan StartOperation(ApiOperationDescriptor operation, string spanKind, IDictionary <string, string> existingContext = null) { // Stackify requires us to pass a Func or Action, but Blueprint uses disposables. To make this work we have // Stackify wait on an "empty" async method that waits on the TCS completed below, that will be trigged when the returned // StackifyApmSpan is disposed, or sets an exception. var tcs = new TaskCompletionSource <bool>(); var dependency = ProfileTracer.CreateAsOperation(operation.Name); dependency.ExecAsync(async() => await tcs.Task); return(new StackifyApmSpan(this, tcs)); }
public void When_Placeholder_Has_Alternate_Name_Then_Placeholder_Created_With_AlternatePropertyName() { // Arrange var descriptor = new ApiOperationDescriptor(typeof(LinkGeneratorTestsOperation), "tests"); // Act var link = new ApiOperationLink(descriptor, "/aUrl/{ClientId:Id}", "a.rel"); // Assert link.Placeholders[0].AlternatePropertyName.Should().Be("Id"); link.Placeholders[0].OriginalText.Should().Be("{ClientId:Id}"); link.Placeholders[0].Property.Name.Should().Be("ClientId"); link.Placeholders[0].Format.Should().BeNull(); }
public void When_Additional_Properties_And_Placeholders_Exists_Adds_As_QueryString_When_IncludeExtra_True() { // Act var descriptor = new ApiOperationDescriptor(typeof(LinkGeneratorTestsOperation), "tests"); var link = new ApiOperationLink(descriptor, "/clients/{clientId}/users", "a.rel"); // Act var url = link.CreateRelativeUrl(new LinkGeneratorTestsOperation { ClientId = 12, SortBy = "name", Page = 2 }, true); // Assert url.Should().Be("clients/12/users?SortBy=name&Page=2"); }
/// <inheritdoc /> public IApmSpan StartOperation(ApiOperationDescriptor operation, string spanKind, IDictionary <string, string> existingContext = null) { var request = this._telemetryClient.StartOperation <RequestTelemetry>(operation.Name); if (existingContext != null && existingContext.TryGetValue("RootId", out var rootId) && existingContext.TryGetValue("ParentId", out var parentId)) { request.Telemetry.Context.Operation.Id = rootId; request.Telemetry.Context.Operation.ParentId = parentId; } return(new ApplicationInsightsApmSpan <RequestTelemetry>(this, request)); }
/// <inheritdoc /> public IApmSpan StartOperation(ApiOperationDescriptor operation, string spanKind, IDictionary <string, string> existingContext = null) { var spanBuilder = this._tracer.BuildSpan("operation.execute"); if (existingContext != null) { spanBuilder.AsChildOf(this._tracer.Extract(BuiltinFormats.TextMap, new TextMapExtractAdapter(existingContext))); } var span = spanBuilder.Start(); Tags.Component.Set(span, operation.Name); Tags.SpanKind.Set(span, spanKind); return(new OpenTracingSpan(this._tracer, span)); }
public void CreateGenerator() { options = new BlueprintApiOptions(); new OperationScanner() .AddOperation <LinkGeneratorTestsOperation>() .FindOperations(options.Model); var httpContext = new DefaultHttpContext(); httpContext.SetBaseUri("http://api.example.com/api/"); linkGenerator = new ApiLinkGenerator(options.Model, new HttpContextAccessor { HttpContext = httpContext }); descriptor = options.Model.FindOperation(typeof(LinkGeneratorTestsOperation)); }
private static void RegisterResponses(ApiOperationDescriptor descriptor) { var typedOperation = descriptor.OperationType .GetInterfaces() .SingleOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IReturn <>)); if (typedOperation != null) { descriptor.AddResponse( new ResponseDescriptor(typedOperation.GetGenericArguments()[0], 200, "OK")); } descriptor.AddResponse( new ResponseDescriptor(typeof(UnhandledExceptionOperationResult), 500, "Unexpected error")); descriptor.AddResponse( new ResponseDescriptor(typeof(ValidationFailedOperationResult), 422, "Validation failure")); }
private static void RegisterResponses(ApiOperationDescriptor descriptor) { var typedOperation = descriptor.OperationType .GetInterfaces() .SingleOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IReturn <>)); if (typedOperation != null) { var returnType = typedOperation.GetGenericArguments()[0]; // If we have a StatusCodeResult then we either // 1. Have a specific subclass and therefore know the expected response code and can therefore add a response // 2. Have the base class and therefore can not determine the actual expected response code so leave it open and do not add anything specific if (typeof(StatusCodeResult).IsAssignableFrom(returnType)) { var instanceProperty = returnType.GetField("Instance", BindingFlags.Public | BindingFlags.Static); if (instanceProperty != null) { // This is option 1, we have a specific subclass (see the .tt file that generates these, i.e. StatusCodeResult.Created) var statusCode = ((StatusCodeResult)instanceProperty.GetValue(null)).StatusCode; descriptor.AddResponse( new ResponseDescriptor((int)statusCode, statusCode.ToString())); } } else { descriptor.AddResponse( new ResponseDescriptor(returnType, (int)HttpStatusCode.OK, HttpStatusCode.OK.ToString())); } } descriptor.AddResponse( new ResponseDescriptor(typeof(UnhandledExceptionOperationResult), 500, "Unexpected error")); descriptor.AddResponse( new ResponseDescriptor(typeof(ValidationFailedOperationResult), 422, "Validation failure")); }
/// <inheritdoc /> public IApmSpan StartOperation(ApiOperationDescriptor operation, string spanKind, IDictionary <string, string> existingContext = null) { SpanContext parent = null; if (existingContext != null && existingContext.TryGetValue("SpanId", out var spanId) && existingContext.TryGetValue("TraceId", out var traceId) && existingContext.TryGetValue("SamplingPriority", out var samplingPriority)) { parent = new SpanContext( ulong.Parse(traceId), ulong.Parse(spanId), Enum.TryParse <SamplingPriority>(samplingPriority, out var p) ? p : SamplingPriority.AutoKeep); } var scope = Tracer.Instance.StartActive("operation.execute", parent); scope.Span.Type = "request"; scope.Span.ResourceName = operation.Name; scope.Span.SetTag(Tags.SpanKind, spanKind); return(new OpenTracingSpan(this, scope)); }
/// <inheritdoc /> public void Apply(ApiOperationDescriptor descriptor) { var staticFields = descriptor.OperationType.GetFields(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public); foreach (var f in staticFields) { if (f.FieldType != typeof(ApiExceptionFactory)) { continue; } if (f.GetValue(null) is ApiExceptionFactory factory) { descriptor.AddResponse(new ResponseDescriptor( typeof(ApiException), factory.HttpStatus, factory.Title, new Dictionary <string, string> { ["type"] = factory.Type, })); } } }