/// <summary> /// Responsible for processing all HTTP GET requests in to the femah API. Receives an ApiRequest object, orchestrates and hands off to /// the ApiResponseBuilder class to create the ApiResponse object. /// </summary> /// <param name="apiRequest" type="ApiRequest">A custom request object built predominantly from the http context request object, contains everything we need to route the API request appropriately.</param> /// <returns type="ApiResponse">The complete response to the GET request from the API, including body and HTTP status code.</returns> public static ApiResponse ProcessGetRequest(ApiRequest apiRequest) { using (var apiResponseBuilder = new ApiResponseBuilder()) { //Example GET: http://example.com/femah.axd/api/featureswitches - Lists all Feature Switches FeatureSwitching.AllFeatures() //Example GET: http://example.com/femah.axd/api/featureswitches/improvedcheckout - Retrieve the Feature Switch ImprovedCheckout //Example GET: http://example.com/femah.axd/api/featureswitchtypes - Lists all Feature Switch Types FeatureSwitching.AllSwitchTypes() ApiResponse response; switch (apiRequest.Service) { case ApiRequest.ApiService.featureswitches: if (string.IsNullOrEmpty(apiRequest.Parameter)) response = apiResponseBuilder.CreateWithFeatureSwitches(Femah.AllFeatures()); else { var featureSwitch = Femah.GetFeature(apiRequest.Parameter); if (featureSwitch != null) { response = apiResponseBuilder.CreateWithFeatureSwitches(featureSwitch); break; } response = apiResponseBuilder.WithBody(String.Empty).WithHttpStatusCode(HttpStatusCode.NotFound); } break; case ApiRequest.ApiService.featureswitchtypes: if (!string.IsNullOrEmpty(apiRequest.Parameter)) { response = apiResponseBuilder.WithBody( String.Format("Error: Service '{0}' does not support parameter querying.", apiRequest.Service)) .WithHttpStatusCode(HttpStatusCode.MethodNotAllowed); break; } response = apiResponseBuilder.CreateWithFeatureSwitchTypes(Femah.AllSwitchTypes()); break; default: response = apiResponseBuilder.WithBody( String.Format("Error: '{0}' is not a valid Femah API service.", apiRequest.Service)) .WithHttpStatusCode(HttpStatusCode.MethodNotAllowed); break; } return response; } }
/// <summary> /// Responsible for processing all HTTP PUT requests in to the femah API. Receives an ApiRequest object, orchestrates and hands off to /// the ApiResponseBuilder class to create the ApiResponse object. /// </summary> /// <param name="apiRequest" type="ApiRequest">A custom request object built predominantly from the http context request object, contains everything we need to route the API request appropriately.</param> /// <returns type="ApiResponse">The complete response to the PUT request from the API, including body and HTTP status code.</returns> public static ApiResponse ProcessPutRequest(ApiRequest apiRequest) { using (var apiResponseBuilder = new ApiResponseBuilder()) { ApiResponse response = null; if (String.IsNullOrEmpty(apiRequest.Parameter)) { response = apiResponseBuilder.WithBody( String.Format("Error: Service '{0}' requires a parameter to be passed. Url must match the format /femah.axd/api/[service]/[parameter].", apiRequest.Service)) .WithHttpStatusCode(HttpStatusCode.MethodNotAllowed); return response; } switch (apiRequest.Service) { //Example PUT: http://example.com/femah.axd/api/featureswitches/TestFeatureSwitch1 - Update a single Feature Switch (IsEnabled, SetCustomAttributes, FeatureType) case ApiRequest.ApiService.featureswitches: var featureSwitch = DeSerialiseJsonBodyToFeatureSwitch(apiRequest.Body); if (featureSwitch != null) response = apiResponseBuilder.CreateWithUpdatedFeatureSwitch(featureSwitch); else { response = apiResponseBuilder.WithBody("Error: Unable to deserialise the request body. Either the JSON is invalid or the supplied 'FeatureType' value is incorrect, have you used the AssemblyQualifiedName as the 'FeatureType' in the request?") .WithHttpStatusCode(HttpStatusCode.BadRequest); } break; default: response = apiResponseBuilder.WithBody( String.Format("Error: Service '{0}' does not support parameter querying.", apiRequest.Service)) .WithHttpStatusCode(HttpStatusCode.MethodNotAllowed); break; } return response; } }
public void ProcessPutRequestSetsHttpStatusCodeTo400AndReturnsGenericErrorMessageInResponseBodyIfPutRequestBodyContainsAJsonArrayOfFeatureSwitches() { //Arrange const string validFeatureType = "Femah.Core.FeatureSwitchTypes.SimpleFeatureSwitch, Femah.Core, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null"; var apiRequest = new ApiRequest { HttpMethod = "PUT", Service = ApiRequest.ApiService.featureswitches, Parameter = "TestFeatureSwitch", Body = string.Format("{{\"IsEnabled\":true,\"Name\":\"TestFeatureSwitch1\",\"FeatureType\":\"{0}\",\"Description\":\"Define a short description of the feature switch type here.\",\"ConfigurationInstructions\":\"Add configuration context and instructions to be displayed in the admin UI\"}},{{\"IsEnabled\":true,\"Name\":\"TestFeatureSwitch2\",\"FeatureType\":\"{0}\",\"Description\":\"Define a short description of the feature switch type here.\",\"ConfigurationInstructions\":\"Add configuration context and instructions to be displayed in the admin UI\"}}", validFeatureType) }; //TODO: I'd like this to be a more specific error with regards to formatting const string expectedJsonBody = "\"Error: Unable to deserialise the request body. Either the JSON is invalid or the supplied 'FeatureType' value is incorrect, have you used the AssemblyQualifiedName as the 'FeatureType' in the request?\""; //Act ApiResponse apiResponse = ProcessApiRequest.ProcessPutRequest(apiRequest); //Assert Assert.AreEqual((int)HttpStatusCode.BadRequest, apiResponse.HttpStatusCode); Assert.AreEqual(expectedJsonBody, apiResponse.Body); }
public void ProcessPutRequestSetsHttpStatusCodeTo304IfPutRequestBodyIsValidJsonButFeatureSwitchHasNoChanges() { //Arrange const string validFeatureType = "Femah.Core.FeatureSwitchTypes.SimpleFeatureSwitch, Femah.Core, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null"; string jsonRequestAndResponse = string.Format( "{{\"IsEnabled\":true,\"Name\":\"TestFeatureSwitch1\",\"FeatureType\":\"{0}\",\"Description\":\"Define a short description of the feature switch type here.\",\"ConfigurationInstructions\":\"Add configuration context and instructions to be displayed in the admin UI\"}}", validFeatureType); var apiRequest = new ApiRequest { HttpMethod = "PUT", Service = ApiRequest.ApiService.featureswitches, Parameter = "TestFeatureSwitch", Body = jsonRequestAndResponse }; var featureSwitch = new SimpleFeatureSwitch { Name = "TestFeatureSwitch1", IsEnabled = true, FeatureType = validFeatureType }; var providerMock = new Mock<IFeatureSwitchProvider>(); providerMock.Setup(p => p.Get("TestFeatureSwitch1")) .Returns(featureSwitch); Femah.Configure() .FeatureSwitchEnum(typeof(FeatureSwitches)) .Provider(providerMock.Object) .Initialise(); //Act ApiResponse apiResponse = ProcessApiRequest.ProcessPutRequest(apiRequest); //Assert Assert.AreEqual((int)HttpStatusCode.NotModified, apiResponse.HttpStatusCode); Assert.AreEqual(string.Empty, apiResponse.Body); }
public PutApiRequestFactory ForServiceType(ApiRequest.ApiService service) { _service = service; return this; }
public ApiRequest Build(HttpRequestBase request) { var apiRequest = new ApiRequest(); if (request.HttpMethod == "PUT" && request.ContentType != "application/json") { apiRequest.ErrorMessage = string.Format("Error: Content-Type '{0}' of request is not supported, expecting 'application/json'.", request.ContentType); apiRequest.ErrorMessageHttpStatusCode = HttpStatusCode.UnsupportedMediaType; return apiRequest; } if (request.Url == null) { apiRequest.ErrorMessage = string.Format("Error: The requested Url '{0}' is null.", request.Url); apiRequest.ErrorMessageHttpStatusCode = HttpStatusCode.InternalServerError; return apiRequest; } var uriSegments = request.Url.Segments; try { apiRequest.HttpMethod = request.HttpMethod; //If we have a request body then retrieve it from the InputStream. if (request.InputStream != null) { string requestBody; using (var stream = new MemoryStream()) { request.InputStream.Seek(0, SeekOrigin.Begin); request.InputStream.CopyTo(stream); requestBody = Encoding.UTF8.GetString(stream.ToArray()); } apiRequest.Body = requestBody; } if (uriSegments.Count() < 4 || uriSegments.Count()> 5) { apiRequest.ErrorMessage = string.Format("Error: The requested Url '{0}' does not match the expected format /femah.axd/api/[service]/[parameter].", request.Url); apiRequest.ErrorMessageHttpStatusCode = HttpStatusCode.InternalServerError; return apiRequest; } //Retrieve the service passed in on the Url, service is mandatory. string service = uriSegments[3].ToLower().Replace("/", ""); ApiRequest.ApiService apiService; if (EnumExtensions.TryParse(service, out apiService)) { if (Enum.IsDefined(typeof (ApiRequest.ApiService), apiService)) apiRequest.Service = apiService; } else { apiRequest.Service = null; } if (uriSegments.Count() == 5) { //Retrieve the optional parameter of the service passed in on the Url, this is optional so handle this a little better. string member = uriSegments[4].ToLower().Replace("/", ""); apiRequest.Parameter = member; } } catch (IndexOutOfRangeException) { apiRequest.ErrorMessage = string.Format("Error: The requested Url '{0}' does not match the expected format /femah.axd/api/[service]/[parameter].", request.Url); apiRequest.ErrorMessageHttpStatusCode = HttpStatusCode.InternalServerError; } return apiRequest; }