diff --git a/.gitignore b/.gitignore index 43e1adf..7a53e95 100644 --- a/.gitignore +++ b/.gitignore @@ -4,26 +4,50 @@ # User-specific files *.suo *.user +*.userosscache *.sln.docstates -# Build results +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs +# Build results [Dd]ebug/ +[Dd]ebugPublic/ [Rr]elease/ +[Rr]eleases/ x64/ -build/ +x86/ +bld/ [Bb]in/ [Oo]bj/ +[Ll]og/ +#[Aa]rtifacts/ -# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets -!packages/*/build/ +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +artifacts/ + *_i.c *_p.c +*_i.h *.ilk *.meta *.obj @@ -43,21 +67,31 @@ build/ *.vssscc .builds *.pidb -*.log +*.svclog *.scc +# Chutzpah Test files +_Chutzpah* + # Visual C++ cache files ipch/ *.aps *.ncb +*.opendb *.opensdf *.sdf *.cachefile +*.VC.db +*.VC.VC.opendb # Visual Studio profiler *.psess *.vsp *.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ # Guidance Automation Toolkit *.gpState @@ -65,6 +99,10 @@ ipch/ # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode # TeamCity is a build add-in _TeamCity* @@ -73,8 +111,16 @@ _TeamCity* *.dotCover # NCrunch -*.ncrunch* +_NCrunch_* .*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ # Installshield output folder [Ee]xpress/ @@ -93,67 +139,129 @@ DocProject/Help/html publish/ # Publish Web Output -*.Publish.xml - -# NuGet Packages Directory -## TODO: If you have NuGet Package Restore enabled, uncomment the next line -packages/ - -# Windows Azure Build Output -csx +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ *.build.csdef -# Windows Store app package directory +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ # Others -sql/ -*.Cache ClientBin/ -[Ss]tyle[Cc]op.* ~$* *~ *.dbmdl -*.[Pp]ublish.xml +*.dbproj.schemaview *.pfx *.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ # RIA/Silverlight projects Generated_Code/ -# Backup & report files from converting an old project file to a newer -# Visual Studio version. Backup files are not needed, because we have git ;-) +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files -App_Data/*.mdf -App_Data/*.ldf - - -#LightSwitch generated files -GeneratedArtifacts/ -_Pvt_Extensions/ -ModelManifest.xml - -# ========================= -# Windows detritus -# ========================= - -# Windows image file caches -Thumbs.db -ehthumbs.db - -# Folder config file -Desktop.ini - -# Recycle Bin used on file shares -$RECYCLE.BIN/ - -# Mac desktop service store files -.DS_Store - -# Nuget packages -*.nupkg +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CAKE +**/tools/Addins/ +**/tools/Cake/ +**/tools/GitVersion.CommandLine +**/tools/nuget.exe +**/tools/NUnit.ConsoleRunner +**/tools/run-ps.cmd +**/tools/publish-nugetpackages.ps1 +**/tools/packages.config.md5sum + +# Xamarin +/source/Components +/tools/xunit.runner.console/tools diff --git a/.nuget/NuGet.Config b/.nuget/NuGet.Config new file mode 100644 index 0000000..67f8ea0 --- /dev/null +++ b/.nuget/NuGet.Config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.nuget/packages.config b/.nuget/packages.config deleted file mode 100644 index 6bc0ed0..0000000 --- a/.nuget/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/Nancy.CustomErrors.Example/Program.cs b/Nancy.CustomErrors.Example/Program.cs deleted file mode 100755 index 740c5e2..0000000 --- a/Nancy.CustomErrors.Example/Program.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Nancy.Hosting.Self; - -namespace Nancy.CustomErrors.Example -{ - class Program - { - static void Main(string[] args) - { - using (var host = new NancyHost(new Uri("http://localhost:1234"))) - { - host.Start(); - Console.ReadKey(); - } - } - } - - public class TestBootstrapper : DefaultNancyBootstrapper - { - protected override void ApplicationStartup(Nancy.TinyIoc.TinyIoCContainer container, Nancy.Bootstrapper.IPipelines pipelines) - { - base.ApplicationStartup(container, pipelines); - - CustomErrors.Enable(pipelines, new ErrorConfiguration()); - } - } - - public class TestModule : NancyModule - { - public TestModule() - : base("/") - { - Get["/test"] = _ => - { - var response = Response.AsText("test", "application/json"); - response.StatusCode = HttpStatusCode.InternalServerError; - return response; - }; - - Get["/err"] = _ => - { - throw new Exception("asdadsfdaf"); - }; - } - } - - public class ErrorConfiguration : CustomErrorsConfiguration - { - public ErrorConfiguration() - { - // Map error status codes to custom view names - ErrorViews[HttpStatusCode.NotFound] = "error"; - ErrorViews[HttpStatusCode.InternalServerError] = "error"; - ErrorViews[HttpStatusCode.Forbidden] = "error"; - } - public override ErrorResponse HandleError(NancyContext context, Exception ex, ISerializer serializer) - { - var error = new Error - { - FullException = ex.ToString(), - Message = ex.Message - }; - - return new ErrorResponse(error, serializer).WithStatusCode(HttpStatusCode.InternalServerError) as ErrorResponse; - } - } -} diff --git a/Nancy.CustomErrors.Example/Views/error.html b/Nancy.CustomErrors.Example/Views/error.html deleted file mode 100755 index b4fe785..0000000 --- a/Nancy.CustomErrors.Example/Views/error.html +++ /dev/null @@ -1 +0,0 @@ -ERROR! \ No newline at end of file diff --git a/Nancy.CustomErrors.Example/packages.config b/Nancy.CustomErrors.Example/packages.config deleted file mode 100755 index 152fbff..0000000 --- a/Nancy.CustomErrors.Example/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/Nancy.CustomErrors.Tests/CustomErrorsFixture.cs b/Nancy.CustomErrors.Tests/CustomErrorsFixture.cs deleted file mode 100644 index c80e24e..0000000 --- a/Nancy.CustomErrors.Tests/CustomErrorsFixture.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Net; -using Nancy.Bootstrapper; -using Nancy.Testing; -using NSubstitute; -using Xunit; - -namespace Nancy.CustomErrors.Tests -{ - public class CustomErrorsFixture - { - [Fact] - public void Should_throw_with_null_pipelines_passed_to_enable() - { - Assert.Throws(() => CustomErrors.Enable(null, new CustomErrorsConfiguration())); - } - - [Fact] - public void Should_throw_with_null_configuration_passed_to_enable() - { - Assert.Throws(() => CustomErrors.Enable(Substitute.For(), null)); - } - - [Fact] - public void Should_add_error_hook_when_enabled() - { - var pipelines = Substitute.For(); - pipelines.OnError.Returns(Substitute.For()); - - CustomErrors.Enable(pipelines, Substitute.For()); - - pipelines.OnError.Received(1).AddItemToEndOfPipeline(Arg.Any>()); - } - } -} diff --git a/Nancy.CustomErrors.Tests/ErrorStatusCodeHandlerFixture.cs b/Nancy.CustomErrors.Tests/ErrorStatusCodeHandlerFixture.cs deleted file mode 100644 index edc1208..0000000 --- a/Nancy.CustomErrors.Tests/ErrorStatusCodeHandlerFixture.cs +++ /dev/null @@ -1,152 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Nancy.Testing; -using Xunit; - -namespace Nancy.CustomErrors.Tests -{ - public class ErrorStatusCodeHandlerFixture - { - private readonly CustomErrorsConfiguration configuration; - private readonly Browser browser; - - public ErrorStatusCodeHandlerFixture() - { - configuration = new CustomErrorsConfiguration(); - - browser = new Browser(new ConfigurableBootstrapper(with => - { - with.ApplicationStartup((container, pipelines) => CustomErrors.Enable(pipelines, configuration)); - with.Module(); - with.StatusCodeHandler(); - })); - } - - - [Fact] - public void Should_return_custom_error_response_for_route_not_found() - { - var response = browser.Get("/nuffin", with => with.Header("Accept", "application/json")); - - Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); - Assert.Equal("The requested resource could not be found.", response.Body.DeserializeJson().Message); - } - - [Fact] - public void Should_return_json_for_application_json_accept_header() - { - var response = browser.Get("/error", with => with.Header("Accept", "application/json")); - - Assert.NotNull(response.Body.DeserializeJson()); - } - - [Fact] - public void Should_return_json_for_text_json_accept_header() - { - var response = browser.Get("/error", with => with.Header("Accept", "text/json")); - - Assert.NotNull(response.Body.DeserializeJson()); - } - - [Fact] - public void Should_return_html_for_text_html_accept_header() - { - var response = browser.Get("/error", with => with.Header("Accept", "text/html")); - - response.Body["title"].ShouldExistOnce().And.ShouldContain(configuration.ErrorTitle); - } - - [Fact] - public void Should_return_html_no_accept_header() - { - var response = browser.Get("/error"); - - response.Body["title"].ShouldExistOnce().And.ShouldContain(configuration.ErrorTitle); - } - - [Fact] - public void Should_return_custom_error_response_for_uncaught_exception() - { - var response = browser.Get("/error", with => with.Header("Accept", "application/json")); - - Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); - Assert.Equal("ERROR MESSAGE HERE", response.Body.DeserializeJson().Message); - } - - [Fact] - public void Should_return_custom_error_response_for_forbidden() - { - var response = browser.Get("forbidden", with => with.Header("Accept", "application/json")); - - Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); - Assert.NotNull(response.Body.DeserializeJson()); - } - - [Fact] - public void Should_return_custom_error_response_for_unauthorised() - { - var response = browser.Get("unauthorised", with => with.Header("Accept", "application/json")); - - Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); - Assert.NotNull(response.Body.DeserializeJson()); - } - - [Fact] - public void Should_render_custom_html_for_uncaught_exception() - { - var response = browser.Get("error"); - - response.Body["title"].ShouldExistOnce().And.ShouldContain(configuration.ErrorTitle); - response.Body["h1"].ShouldExistOnce().And.ShouldContain("ERROR MESSAGE HERE"); - } - - [Fact] - public void Should_suppress_full_stack_trace_by_default() - { - var response = browser.Get("/err", with => with.Header("Accept", "application/json")); - - Assert.Null(response.Body.DeserializeJson().FullException); - } - - [Fact] - public void Should_expose_full_stack_trace_in_debug_mode() - { - CustomErrors.Configuration.Debug = true; - - var response = browser.Get("/error", with => with.Header("Accept", "application/json")); - - Assert.NotNull(response.Body.DeserializeJson().FullException); - - CustomErrors.Configuration.Debug = false; - } - - [Fact] - public void Should_retain_headers_already_set() - { - var response = browser.Get("/headers", with => with.Header("Accept", "application/json")); - - Assert.NotNull(response.Headers.Where(h => h.Key == "CustomHeader")); - } - } - - - public class TestModule : NancyModule - { - public TestModule() - { - Get["error"] = _ => - { - throw new Exception("ERROR MESSAGE HERE"); - }; - - Get["forbidden"] = _ => HttpStatusCode.Forbidden; - Get["unauthorised"] = _ => HttpStatusCode.Unauthorized; - Get["headers"] = - _ => - new Response().WithStatusCode(HttpStatusCode.InternalServerError) - .WithHeader("CustomHeader", "CustomHeaderValue"); - } - } -} diff --git a/Nancy.CustomErrors.Tests/Nancy.CustomErrors.Tests.csproj b/Nancy.CustomErrors.Tests/Nancy.CustomErrors.Tests.csproj deleted file mode 100644 index 8957f70..0000000 --- a/Nancy.CustomErrors.Tests/Nancy.CustomErrors.Tests.csproj +++ /dev/null @@ -1,86 +0,0 @@ - - - - - Debug - AnyCPU - {F1930050-9A83-42BB-9E14-1D5D7C6F255B} - Library - Properties - Nancy.CustomErrors.Tests - Nancy.CustomErrors.Tests - v4.0 - 512 - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\packages\CsQuery.1.3.4\lib\net40\CsQuery.dll - - - ..\packages\Nancy.0.23.0\lib\net40\Nancy.dll - - - ..\packages\Nancy.Testing.0.23.0\lib\net40\Nancy.Testing.dll - - - ..\packages\NSubstitute.1.7.2.0\lib\NET40\NSubstitute.dll - - - - - - - - - - ..\packages\xunit.1.9.2\lib\net20\xunit.dll - - - - - - - - - - - - - {cd7a7211-6563-499b-913d-8e5af649bfe7} - Nancy.CustomErrors - - - - - PreserveNewest - - - - - - - - \ No newline at end of file diff --git a/Nancy.CustomErrors.Tests/packages.config b/Nancy.CustomErrors.Tests/packages.config deleted file mode 100644 index 5aa926e..0000000 --- a/Nancy.CustomErrors.Tests/packages.config +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/Nancy.CustomErrors.sln b/Nancy.CustomErrors.sln index 1d8d98f..87163f5 100644 --- a/Nancy.CustomErrors.sln +++ b/Nancy.CustomErrors.sln @@ -1,9 +1,9 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2013 -VisualStudioVersion = 12.0.31101.0 +# Visual Studio 15 +VisualStudioVersion = 15.0.26430.6 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nancy.CustomErrors", "Nancy.CustomErrors\Nancy.CustomErrors.csproj", "{CD7A7211-6563-499B-913D-8E5AF649BFE7}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nancy.CustomErrors", "src\Nancy.CustomErrors\Nancy.CustomErrors.csproj", "{CD7A7211-6563-499B-913D-8E5AF649BFE7}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{56730B51-095D-4010-B942-DCB2696540C0}" ProjectSection(SolutionItems) = preProject @@ -13,16 +13,23 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution version.json = version.json EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nancy.CustomErrors.Tests", "Nancy.CustomErrors.Tests\Nancy.CustomErrors.Tests.csproj", "{F1930050-9A83-42BB-9E14-1D5D7C6F255B}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nancy.CustomErrors.Tests", "tests\Nancy.CustomErrors.Tests\Nancy.CustomErrors.Tests.csproj", "{F1930050-9A83-42BB-9E14-1D5D7C6F255B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nancy.CustomErrors.Example", "Nancy.CustomErrors.Example\Nancy.CustomErrors.Example.csproj", "{5A8E7514-E653-4C00-8623-1E71D7DCE6C5}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nancy.CustomErrors.Example", "tests\Nancy.CustomErrors.Example\Nancy.CustomErrors.Example.csproj", "{5A8E7514-E653-4C00-8623-1E71D7DCE6C5}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{1831399C-85E4-4E38-893C-F85DB092CB04}" - ProjectSection(SolutionItems) = preProject - .nuget\packages.config = .nuget\packages.config - EndProjectSection +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nancy.CustomErrors.NetStandard1.6", "src\Nancy.CustomErrors.NetStandard1.6\Nancy.CustomErrors.NetStandard1.6.csproj", "{BBFAEB4A-212F-4F00-AAE7-6723231C6969}" +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Nancy.CustomErrors.Shared", "src\Nancy.CustomErrors.Shared\Nancy.CustomErrors.Shared.shproj", "{0B0C2E66-9FD4-4475-AB31-81AB2F39F4DA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "source", "source", "{ED70835A-FD7E-43C7-803D-84CD326DA186}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{9CDF45E1-7B40-4BA2-A537-1407CA214AD0}" EndProject Global + GlobalSection(SharedMSBuildProjectFiles) = preSolution + src\Nancy.CustomErrors.Shared\Nancy.CustomErrors.Shared.projitems*{0b0c2e66-9fd4-4475-ab31-81ab2f39f4da}*SharedItemsImports = 13 + src\Nancy.CustomErrors.Shared\Nancy.CustomErrors.Shared.projitems*{cd7a7211-6563-499b-913d-8e5af649bfe7}*SharedItemsImports = 4 + EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU @@ -40,8 +47,19 @@ Global {5A8E7514-E653-4C00-8623-1E71D7DCE6C5}.Debug|Any CPU.Build.0 = Debug|Any CPU {5A8E7514-E653-4C00-8623-1E71D7DCE6C5}.Release|Any CPU.ActiveCfg = Release|Any CPU {5A8E7514-E653-4C00-8623-1E71D7DCE6C5}.Release|Any CPU.Build.0 = Release|Any CPU + {BBFAEB4A-212F-4F00-AAE7-6723231C6969}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BBFAEB4A-212F-4F00-AAE7-6723231C6969}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BBFAEB4A-212F-4F00-AAE7-6723231C6969}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BBFAEB4A-212F-4F00-AAE7-6723231C6969}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {CD7A7211-6563-499B-913D-8E5AF649BFE7} = {ED70835A-FD7E-43C7-803D-84CD326DA186} + {F1930050-9A83-42BB-9E14-1D5D7C6F255B} = {9CDF45E1-7B40-4BA2-A537-1407CA214AD0} + {5A8E7514-E653-4C00-8623-1E71D7DCE6C5} = {9CDF45E1-7B40-4BA2-A537-1407CA214AD0} + {BBFAEB4A-212F-4F00-AAE7-6723231C6969} = {ED70835A-FD7E-43C7-803D-84CD326DA186} + {0B0C2E66-9FD4-4475-AB31-81AB2F39F4DA} = {ED70835A-FD7E-43C7-803D-84CD326DA186} + EndGlobalSection EndGlobal diff --git a/Nancy.CustomErrors/CustomErrors.cs b/Nancy.CustomErrors/CustomErrors.cs deleted file mode 100644 index 5a0d446..0000000 --- a/Nancy.CustomErrors/CustomErrors.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using Nancy.Bootstrapper; -using Nancy.Responses; - -namespace Nancy.CustomErrors -{ - public class CustomErrors - { - private static CustomErrorsConfiguration _configuration; - public static CustomErrorsConfiguration Configuration - { - get - { - return _configuration; - } - } - - public static void Enable(IPipelines pipelines, CustomErrorsConfiguration configuration) - { - Enable(pipelines, configuration, new DefaultJsonSerializer()); - } - - public static void Enable(IPipelines pipelines, CustomErrorsConfiguration configuration, ISerializer serializer) - { - if (pipelines == null) - { - throw new ArgumentNullException("pipelines"); - } - - if (configuration == null) - { - throw new ArgumentNullException("configuration"); - } - - _configuration = configuration; - - pipelines.OnError.AddItemToEndOfPipeline(GetErrorHandler(configuration, serializer)); - } - - private static Func GetErrorHandler(CustomErrorsConfiguration configuration, ISerializer serializer) - { - return (context, ex) => configuration.HandleError(context, ex, serializer); - } - } -} \ No newline at end of file diff --git a/Nancy.CustomErrors/CustomErrorsConfiguration.cs b/Nancy.CustomErrors/CustomErrorsConfiguration.cs deleted file mode 100644 index e101bec..0000000 --- a/Nancy.CustomErrors/CustomErrorsConfiguration.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Nancy.CustomErrors -{ - public class CustomErrorsConfiguration - { - public string NotFoundTitle = "404 Not Found"; - public string NotFoundSummary = "The requested resource could not be found."; - public string ForbiddenTitle = "Forbidden"; - public string ForbiddenSummary = "You do not have permission to do that."; - public string UnauthorizedTitle = "Unauthorized"; - public string UnauthorizedSummary = "You do not have permission to do that."; - public string ErrorTitle = "Error"; - public string ErrorSummary = "An unexpected error occurred."; - - public bool AlwaysReturnJson = false; - - /// - /// If set to true, then we will emit full stack traces in our ErrorResponse - /// - public bool Debug = false; - - /// - /// Converts a thrown exception to the appropriate ErrorResponse. Override this method if you need - /// to handle custom exception types, or implement your own error handling logic. The default - /// implementation converts all thrown exceptions to a regular ErrorResponse with an HttpStatusCode - /// of 500 - /// - /// - /// - /// - /// - public virtual ErrorResponse HandleError(NancyContext context, Exception ex, ISerializer serializer) - { - var error = new Error - { - FullException = ex.ToString(), - Message = ex.Message - }; - - return new ErrorResponse(error, serializer).WithStatusCode(HttpStatusCode.InternalServerError) as ErrorResponse; - } - - /// - /// Maps different HttpStatusCodes to the appropriate views. - /// - public IDictionary ErrorViews = new Dictionary - { - { HttpStatusCode.NotFound, "Error" }, - { HttpStatusCode.InternalServerError, "Error" }, - { HttpStatusCode.Forbidden, "Error" } - }; - } -} diff --git a/Nancy.CustomErrors/Error.cs b/Nancy.CustomErrors/Error.cs deleted file mode 100644 index 2948a12..0000000 --- a/Nancy.CustomErrors/Error.cs +++ /dev/null @@ -1,9 +0,0 @@ - -namespace Nancy.CustomErrors -{ - public class Error - { - public string Message { get; set; } - public string FullException { get; set; } - } -} \ No newline at end of file diff --git a/Nancy.CustomErrors/ErrorResponse.cs b/Nancy.CustomErrors/ErrorResponse.cs deleted file mode 100644 index 1ce1315..0000000 --- a/Nancy.CustomErrors/ErrorResponse.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Nancy.Responses; - -namespace Nancy.CustomErrors -{ - public class ErrorResponse : JsonResponse - { - private readonly Error _error; - public string ErrorMessage { get { return _error.Message; } } - public string FullException { get { return _error.FullException; } } - public ErrorResponse(Error error) : this(error, new DefaultJsonSerializer()) { } - public ErrorResponse(Error error, ISerializer serializer) : base(error, serializer) - { - if (!CustomErrors.Configuration.Debug) - { - error.FullException = null; - } - - _error = error; - } - } -} diff --git a/Nancy.CustomErrors/ErrorStatusCodeHandler.cs b/Nancy.CustomErrors/ErrorStatusCodeHandler.cs deleted file mode 100644 index a14ee0f..0000000 --- a/Nancy.CustomErrors/ErrorStatusCodeHandler.cs +++ /dev/null @@ -1,172 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using System.Net; -using Nancy.ErrorHandling; -using Nancy.Responses; -using Nancy.Responses.Negotiation; -using Nancy.ViewEngines; - -namespace Nancy.CustomErrors -{ - public class ErrorStatusCodeHandler : DefaultViewRenderer, IStatusCodeHandler - { - private readonly ISerializer _serializer; - - public ErrorStatusCodeHandler(IViewFactory viewFactory) - : this(viewFactory, new DefaultJsonSerializer()) - { - } - - public ErrorStatusCodeHandler(IViewFactory viewFactory, ISerializer serializer) - : base(viewFactory) - { - _serializer = serializer; - } - - public bool HandlesStatusCode(HttpStatusCode statusCode, NancyContext context) - { - return statusCode == HttpStatusCode.NotFound - || statusCode == HttpStatusCode.InternalServerError - || statusCode == HttpStatusCode.Forbidden - || statusCode == HttpStatusCode.Unauthorized; - } - - public void Handle(HttpStatusCode statusCode, NancyContext context) - { - var headers = context.Response.Headers.Select(h => Tuple.Create(h.Key, h.Value)).ToArray(); - - if (!ShouldRenderFriendlyErrorPage(context)) - { - // Assume a valid error response was built earlier in the request lifecycle - // Nothing more for us to do here, so just bail out. - if (context.Response is ErrorResponse) - { - return; - } - - var err = new Error - { - Message = CustomErrors.Configuration.ErrorSummary - }; - - if (context.Response is NotFoundResponse) - { - // Normally we return 404's ourselves so we have an ErrorResponse. - // But if no route is matched, Nancy will set a NotFound response itself. - // When this happens we still want to return our nice JSON response. - err.Message = CustomErrors.Configuration.NotFoundSummary; - } - else - { - switch (statusCode) - { - case HttpStatusCode.Forbidden : - case HttpStatusCode.Unauthorized : - err.Message = CustomErrors.Configuration.UnauthorizedSummary; - break; - case HttpStatusCode.NotFound : - err.Message = CustomErrors.Configuration.NotFoundSummary; - context.Response = new ErrorResponse(new Error - { - Message = CustomErrors.Configuration.NotFoundSummary - }); - break; - } - } - - context.Response = new ErrorResponse(err, _serializer).WithHeaders(headers).WithStatusCode(statusCode); - - return; - } - - var error = context.Response as ErrorResponse; - - var model = new ErrorViewModel - { - Details = error == null ? "" : error.FullException - }; - - switch (statusCode) - { - case HttpStatusCode.Forbidden: - model.Title = CustomErrors.Configuration.ForbiddenTitle; - model.Summary = CustomErrors.Configuration.ForbiddenSummary; - - break; - - case HttpStatusCode.Unauthorized: - model.Title = CustomErrors.Configuration.UnauthorizedTitle; - model.Summary = error == null ? CustomErrors.Configuration.UnauthorizedSummary : error.ErrorMessage; - - break; - - case HttpStatusCode.NotFound: - model.Title = CustomErrors.Configuration.NotFoundTitle; - model.Summary = CustomErrors.Configuration.NotFoundSummary; - - break; - case HttpStatusCode.InternalServerError: - model.Title = CustomErrors.Configuration.ErrorTitle; - model.Summary = error == null ? CustomErrors.Configuration.ErrorSummary : error.ErrorMessage; - - break; - } - - try - { - context.Response = - RenderView(context, CustomErrors.Configuration.ErrorViews[statusCode], model) - .WithStatusCode(statusCode) - .WithHeaders(headers); - } - catch(Exception e) - { - context.Response = new Response - { - StatusCode = HttpStatusCode.InternalServerError, - ContentType = "text/plain", - Contents = stream => - { - var writer = new StreamWriter(stream); - writer.AutoFlush = true; - writer.Write(string.Format("Could not locate your error view! Details: {0}", e.Message)); - } - }; - } - } - - private static bool ShouldRenderFriendlyErrorPage(NancyContext context) - { - if (CustomErrors.Configuration.AlwaysReturnJson) - { - return false; - } - - var ranges = - context.Request.Headers.Accept.OrderByDescending(o => o.Item2) - .Select(o => MediaRange.FromString(o.Item1)) - .ToList(); - - foreach (var range in ranges) - { - if (range.Matches("application/json")) - { - return false; - } - - if (range.Matches("text/json")) - { - return false; - } - - if (range.Matches("text/html")) - { - return true; - } - } - - return true; - } - } -} \ No newline at end of file diff --git a/Nancy.CustomErrors/ErrorViewModel.cs b/Nancy.CustomErrors/ErrorViewModel.cs deleted file mode 100644 index 3895ced..0000000 --- a/Nancy.CustomErrors/ErrorViewModel.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Nancy.CustomErrors -{ - public class ErrorViewModel - { - public string Title { get; set; } - public string Summary { get; set; } - public string Details { get; set; } - } -} diff --git a/Nancy.CustomErrors/Nancy.CustomErrors.nuspec b/Nancy.CustomErrors/Nancy.CustomErrors.nuspec deleted file mode 100644 index 1adf745..0000000 --- a/Nancy.CustomErrors/Nancy.CustomErrors.nuspec +++ /dev/null @@ -1,16 +0,0 @@ - - - - $id$ - $version$ - $title$ - $author$ - $author$ - https://github.com/bernos/Nancy.CustomErrors - Adds exception handling for cases when custom error views cannot be found - false - $description$ - Copyright 2014 - Nancyfx - - \ No newline at end of file diff --git a/Nancy.CustomErrors/ResponseFormatterExtensions.cs b/Nancy.CustomErrors/ResponseFormatterExtensions.cs deleted file mode 100644 index c10a4c8..0000000 --- a/Nancy.CustomErrors/ResponseFormatterExtensions.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Linq; - -namespace Nancy.CustomErrors -{ - public static class ResponseFormatterExtensions - { - public static Response AsError(this IResponseFormatter formatter, string message, - HttpStatusCode statusCode = HttpStatusCode.InternalServerError) - { - var serializer = formatter.Serializers.FirstOrDefault(s => s.CanSerialize("application/json")); - - return new ErrorResponse(new Error {Message = message}, serializer).WithStatusCode(statusCode); - } - } -} \ No newline at end of file diff --git a/Nancy.CustomErrors/packages.config b/Nancy.CustomErrors/packages.config deleted file mode 100644 index 722d3f8..0000000 --- a/Nancy.CustomErrors/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/build.cake b/build.cake new file mode 100644 index 0000000..adf0adb --- /dev/null +++ b/build.cake @@ -0,0 +1,360 @@ +/////////////////////////////////////////////////////////////////////////////// +// Directives +/////////////////////////////////////////////////////////////////////////////// + +#l "tools/versionUtils.cake" +#l "tools/settingsUtils.cake" +#tool "nuget:?package=NUnit.ConsoleRunner" +#tool "nuget:?package=xunit.runner.console" + +/////////////////////////////////////////////////////////////////////////////// +// ARGUMENTS +/////////////////////////////////////////////////////////////////////////////// + +var settings = SettingsUtils.LoadSettings(Context); +var versionInfo = VersionUtils.LoadVersion(Context, settings); + +/////////////////////////////////////////////////////////////////////////////// +// GLOBAL VARIABLES +/////////////////////////////////////////////////////////////////////////////// + +var solutions = GetFiles(settings.Build.SolutionFilePath); +var solutionPaths = solutions.Select(solution => solution.GetDirectory()); + +/////////////////////////////////////////////////////////////////////////////// +// SETUP / TEARDOWN +/////////////////////////////////////////////////////////////////////////////// + +Setup((c) => +{ + // Executed BEFORE the first task. + settings.Display(c); + versionInfo.Display(c); +}); + +Teardown((c) => +{ + // Executed AFTER the last task. + Information("Finished running tasks."); +}); + +/////////////////////////////////////////////////////////////////////////////// +// TASK DEFINITIONS +/////////////////////////////////////////////////////////////////////////////// + +Task("CleanAll") + .Description("Cleans all directories that are used during the build process.") + .Does(() => +{ + // Clean solution directories. + foreach(var path in solutionPaths) + { + Information("Cleaning {0}", path); + CleanDirectories(path + "/**/bin"); + CleanDirectories(path + "/**/obj"); + CleanDirectories(path + "/packages/**/*"); + CleanDirectories(path + "/artifacts/**/*"); + CleanDirectories(path + "/packages"); + CleanDirectories(path + "/artifacts"); + } + + var pathTest = MakeAbsolute(Directory(settings.Test.SourcePath)).FullPath; + Information("Cleaning {0}", pathTest); + try { CleanDirectories(pathTest + "/**/bin"); } catch {} + try { CleanDirectories(pathTest + "/**/obj"); } catch {} +}); + +Task("Clean") + .Description("Cleans all directories that are used during the build process.") + .WithCriteria(settings.ExecuteBuild) + .Does(() => +{ + // Clean solution directories. + foreach(var path in solutionPaths) + { + Information("Cleaning {0}", path); + try { CleanDirectories(path + "/**/bin/" + settings.Configuration); } catch {} + try { CleanDirectories(path + "/**/obj/" + settings.Configuration); } catch {} + } + + var pathTest = MakeAbsolute(Directory(settings.Test.SourcePath)).FullPath; + Information("Cleaning {0}", pathTest); + try { CleanDirectories(pathTest + "/**/bin/" + settings.Configuration); } catch {} + try { CleanDirectories(pathTest + "/**/obj/" + settings.Configuration); } catch {} + +}); + +Task("CleanPackages") + .Description("Cleans all packages that are used during the build process.") + .Does(() => +{ + // Clean solution directories. + foreach(var path in solutionPaths) + { + Information("Cleaning {0}", path); + CleanDirectories(path + "/packages/**/*"); + CleanDirectories(path + "/packages"); + } +}); + +Task("Restore") + .Description("Restores all the NuGet packages that are used by the specified solution.") + .WithCriteria(settings.ExecuteBuild) + .Does(() => +{ + // Restore all NuGet packages. + foreach(var solution in solutions) + { + Information("Restoring {0}...", solution); + NuGetRestore(solution, new NuGetRestoreSettings { ConfigFile = settings.NuGet.NuGetConfig }); + } +}); + +Task("Build") + .Description("Builds all the different parts of the project.") + .WithCriteria(settings.ExecuteBuild) + .IsDependentOn("Clean") + .IsDependentOn("Restore") + .IsDependentOn("UpdateVersion") + .Does(() => +{ + if (settings.Version.AutoIncrementVersion) + { + RunTarget("IncrementVersion"); + } + + // Build all solutions. + foreach(var solution in solutions) + { + Information("Building {0}", solution); + try { + MSBuild(solution, s => + s.SetPlatformTarget(PlatformTarget.MSIL) + .SetMaxCpuCount(settings.Build.MaxCpuCount) + .WithProperty("TreatWarningsAsErrors",settings.Build.TreatWarningsAsErrors.ToString()) + .WithTarget("Build") + .SetConfiguration(settings.Configuration)); + } + catch (Exception ex) + { + Error("Files to build project: " + solution + ". Error: " + ex.Message); + } + } +}); + +Task("UnitTest") + .Description("Run unit tests for the solution.") + .WithCriteria(settings.ExecuteUnitTest) + .IsDependentOn("Build") + .Does(() => +{ + // Run all unit tests we can find. + + var assemplyFilePath = string.Format("{0}/**/bin/{1}/{2}", settings.Test.SourcePath, settings.Configuration, settings.Test.AssemblyFileSpec); + + Information("Unit Test Files: {0}", assemplyFilePath); + + var unitTestAssemblies = GetFiles(assemplyFilePath); + + foreach(var uta in unitTestAssemblies) + { + Information("Executing Tests for {0}", uta); + + switch (settings.Test.Framework) + { + case TestFrameworkTypes.NUnit2: + NUnit(uta.ToString(), new NUnitSettings { }); + break; + case TestFrameworkTypes.NUnit3: + NUnit3(uta.ToString(), new NUnit3Settings { Configuration=settings.Configuration }); + break; + case TestFrameworkTypes.XUnit: + XUnit(uta.ToString(), new XUnitSettings { OutputDirectory = settings.Test.ResultsPath }); + break; + case TestFrameworkTypes.XUnit2: + XUnit2(uta.ToString(), new XUnit2Settings { OutputDirectory = settings.Test.ResultsPath, XmlReportV1 = true }); + break; + } + } +}); + +Task("Package") + .Description("Packages all nuspec files into nupkg packages.") + .WithCriteria(settings.ExecutePackage) + .IsDependentOn("UnitTest") + .Does(() => +{ + var artifactsPath = Directory(settings.NuGet.ArtifactsPath); + var nugetProps = new Dictionary() { {"Configuration", settings.Configuration} }; + + CreateDirectory(artifactsPath); + + var nuspecFiles = GetFiles(settings.NuGet.NuSpecFileSpec); + foreach(var nsf in nuspecFiles) + { + Information("Packaging {0}", nsf); + + if (settings.NuGet.UpdateVersion) { + VersionUtils.UpdateNuSpecVersion(Context, settings, versionInfo, nsf.ToString()); + } + + if (settings.NuGet.UpdateLibraryDependencies) { + VersionUtils.UpdateNuSpecVersionDependency(Context, settings, versionInfo, nsf.ToString()); + } + + NuGetPack(nsf, new NuGetPackSettings { + Version = versionInfo.ToString(), + ReleaseNotes = versionInfo.ReleaseNotes, + Symbols = true, + Properties = nugetProps, + OutputDirectory = artifactsPath + }); + } +}); + +Task("Publish") + .Description("Publishes all of the nupkg packages to the nuget server. ") + .IsDependentOn("Package") + .Does(() => +{ + var authError = false; + + if (settings.NuGet.FeedApiKey.ToLower() == "local") + { + settings.NuGet.FeedUrl = Directory(settings.NuGet.FeedUrl).Path.FullPath; + //Information("Using Local repository: {0}", settings.NuGet.FeedUrl); + } + + Information("Publishing Packages from {0} to {1} for version {2}", settings.NuGet.ArtifactsPath, settings.NuGet.FeedUrl, versionInfo.ToString()); + + // Lets get the list of packages (we can skip anything that is not part of the current version being built) + var nupkgFiles = GetFiles(settings.NuGet.NuGetPackagesSpec).Where(x => x.ToString().Contains(versionInfo.ToString())).ToList(); + + Information("\t{0}", string.Join("\n\t", nupkgFiles.Select(x => x.GetFilename().ToString()).ToList())); + + foreach (var n in nupkgFiles) + { + try + { + NuGetPush(n, new NuGetPushSettings { + Source = settings.NuGet.FeedUrl, + ApiKey = settings.NuGet.FeedApiKey, + ConfigFile = settings.NuGet.NuGetConfig, + Verbosity = NuGetVerbosity.Normal + }); + } + catch (Exception ex) + { + Information("\tFailed to published: ", ex.Message); + + if (ex.Message.Contains("403")) { authError = true; } + } + } + + if (authError && settings.NuGet.FeedApiKey == "VSTS") + { + Warning("\tYou may need to Configuration Your Credentials.\r\n\t\tCredentialProvider.VSS.exe -Uri {0}", settings.NuGet.FeedUrl); + } +}); + +Task("UnPublish") + .Description("UnPublishes all of the current nupkg packages from the nuget server. Issue: versionToDelete must use : instead of . due to bug in cake") + .Does(() => +{ + var v = Argument("versionToDelete", versionInfo.ToString()).Replace(":","."); + + var nuspecFiles = GetFiles(settings.NuGet.NuSpecFileSpec); + foreach(var f in nuspecFiles) + { + Information("UnPublishing {0}", f.GetFilenameWithoutExtension()); + + var args = string.Format("delete {0} {1} -Source {2} -NonInteractive", + f.GetFilenameWithoutExtension(), + v, + settings.NuGet.FeedUrl + ); + + //if (settings.NuGet.FeedApiKey != "VSTS" ) { + args = args + string.Format(" -ApiKey {0}", settings.NuGet.FeedApiKey); + //} + + if (!string.IsNullOrEmpty(settings.NuGet.NuGetConfig)) { + args = args + string.Format(" -Config {0}", settings.NuGet.NuGetConfig); + } + + Information("NuGet Command Line: {0}", args); + using (var process = StartAndReturnProcess("tools\\nuget.exe", new ProcessSettings { + Arguments = args + })) + { + process.WaitForExit(); + Information("nuget delete exit code: {0}", process.GetExitCode()); + } + } +}); + +Task("UpdateVersion") + .Description("Updates the version number in the necessary files") + .Does(() => +{ + Information("Updating Version to {0}", versionInfo.ToString()); + + VersionUtils.UpdateVersion(Context, settings, versionInfo); +}); + +Task("IncrementVersion") + .Description("Increments the version number and then updates it in the necessary files") + .Does(() => +{ + var oldVer = versionInfo.ToString(); + if (versionInfo.IsPreRelease) versionInfo.PreRelease++; else versionInfo.Build++; + + Information("Incrementing Version {0} to {1}", oldVer, versionInfo.ToString()); + + RunTarget("UpdateVersion"); +}); + +Task("BuildNewVersion") + .Description("Increments and Builds a new version") + .IsDependentOn("IncrementVersion") + .IsDependentOn("Build") + .Does(() => +{ +}); + +Task("PublishNewVersion") + .Description("Increments, Builds, and publishes a new version") + .IsDependentOn("BuildNewVersion") + .IsDependentOn("Publish") + .Does(() => +{ +}); + +Task("DisplaySettings") + .Description("Displays All Settings.") + .Does(() => +{ + // Settings will be displayed as they are part of the Setup task +}); + +Task("DisplayHelp") + .Description("Displays All Settings.") + .Does(() => +{ + // Settings will be displayed as they are part of the Setup task + SettingsUtils.DisplayHelp(Context); +}); + +/////////////////////////////////////////////////////////////////////////////// +// TARGETS +/////////////////////////////////////////////////////////////////////////////// + +Task("Default") + .Description("This is the default task which will be ran if no specific target is passed in.") + .IsDependentOn("Build"); + +/////////////////////////////////////////////////////////////////////////////// +// EXECUTION +/////////////////////////////////////////////////////////////////////////////// + +RunTarget(settings.Target); \ No newline at end of file diff --git a/build.osx.sh b/build.osx.sh new file mode 100644 index 0000000..6e8f207 --- /dev/null +++ b/build.osx.sh @@ -0,0 +1,101 @@ +#!/usr/bin/env bash + +########################################################################## +# This is the Cake bootstrapper script for Linux and OS X. +# This file was downloaded from https://github.com/cake-build/resources +# Feel free to change this file to fit your needs. +########################################################################## + +# Define directories. +SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) +TOOLS_DIR=$SCRIPT_DIR/tools +NUGET_EXE=$TOOLS_DIR/nuget.exe +CAKE_EXE=$TOOLS_DIR/Cake/Cake.exe +PACKAGES_CONFIG=$TOOLS_DIR/packages.config +PACKAGES_CONFIG_MD5=$TOOLS_DIR/packages.config.md5sum + +# Define md5sum or md5 depending on Linux/OSX +MD5_EXE= +if [[ "$(uname -s)" == "Darwin" ]]; then + MD5_EXE="md5 -r" +else + MD5_EXE="md5sum" +fi + +# Define default arguments. +SCRIPT="build.cake" +TARGET="Default" +CONFIGURATION="Release" +VERBOSITY="verbose" +DRYRUN= +SHOW_VERSION=false +SCRIPT_ARGUMENTS=() + +# Parse arguments. +for i in "$@"; do + case $1 in + -s|--script) SCRIPT="$2"; shift ;; + -t|--target) TARGET="$2"; shift ;; + -c|--configuration) CONFIGURATION="$2"; shift ;; + -v|--verbosity) VERBOSITY="$2"; shift ;; + -d|--dryrun) DRYRUN="-dryrun" ;; + --version) SHOW_VERSION=true ;; + --) shift; SCRIPT_ARGUMENTS+=("$@"); break ;; + *) SCRIPT_ARGUMENTS+=("$1") ;; + esac + shift +done + +# Make sure the tools folder exist. +if [ ! -d "$TOOLS_DIR" ]; then + mkdir "$TOOLS_DIR" +fi + +# Make sure that packages.config exist. +if [ ! -f "$TOOLS_DIR/packages.config" ]; then + echo "Downloading packages.config..." + curl -Lsfo "$TOOLS_DIR/packages.config" http://cakebuild.net/download/bootstrapper/packages + if [ $? -ne 0 ]; then + echo "An error occured while downloading packages.config." + exit 1 + fi +fi + +# Download NuGet if it does not exist. +if [ ! -f "$NUGET_EXE" ]; then + echo "Downloading NuGet..." + curl -Lsfo "$NUGET_EXE" https://dist.nuget.org/win-x86-commandline/latest/nuget.exe + if [ $? -ne 0 ]; then + echo "An error occured while downloading nuget.exe." + exit 1 + fi +fi + +# Restore tools from NuGet. +pushd "$TOOLS_DIR" >/dev/null +if [ ! -f $PACKAGES_CONFIG_MD5 ] || [ "$( cat $PACKAGES_CONFIG_MD5 | sed 's/\r$//' )" != "$( $MD5_EXE $PACKAGES_CONFIG | awk '{ print $1 }' )" ]; then + find . -type d ! -name . | xargs rm -rf +fi + +mono "$NUGET_EXE" install -ExcludeVersion +if [ $? -ne 0 ]; then + echo "Could not restore NuGet packages." + exit 1 +fi + +$MD5_EXE $PACKAGES_CONFIG | awk '{ print $1 }' >| $PACKAGES_CONFIG_MD5 + +popd >/dev/null + +# Make sure that Cake has been installed. +if [ ! -f "$CAKE_EXE" ]; then + echo "Could not find Cake.exe at '$CAKE_EXE'." + exit 1 +fi + +# Start Cake +if $SHOW_VERSION; then + exec mono "$CAKE_EXE" -version +else + exec mono "$CAKE_EXE" $SCRIPT -verbosity=$VERBOSITY -configuration=$CONFIGURATION -target=$TARGET $DRYRUN "${SCRIPT_ARGUMENTS[@]}" +fi \ No newline at end of file diff --git a/build.ps1 b/build.ps1 index d74b339..dd0c32c 100644 --- a/build.ps1 +++ b/build.ps1 @@ -1,43 +1,189 @@ -Param( - [Parameter(Position=1,Mandatory=0)] - [string[]]$task_list = @(), +########################################################################## +# This is the Cake bootstrapper script for PowerShell. +# This file was downloaded from https://github.com/cake-build/resources +# Feel free to change this file to fit your needs. +########################################################################## + +<# + +.SYNOPSIS +This is a Powershell script to bootstrap a Cake build. + +.DESCRIPTION +This Powershell script will download NuGet if missing, restore NuGet tools (including Cake) +and execute your Cake build script with the parameters you provide. + +.PARAMETER Script +The build script to execute. +.PARAMETER Target +The build script target to run. +.PARAMETER Configuration +The build configuration to use. +.PARAMETER Verbosity +Specifies the amount of information to be displayed. +.PARAMETER Experimental +Tells Cake to use the latest Roslyn release. +.PARAMETER WhatIf +Performs a dry run of the build script. +No tasks will be executed. +.PARAMETER Mono +Tells Cake to use the Mono scripting engine. +.PARAMETER SkipToolPackageRestore +Skips restoring of packages. +.PARAMETER ScriptArgs +Remaining arguments are added here. + +.LINK +http://cakebuild.net + +#> - [Parameter()] - [string]$BuildMetaData +[CmdletBinding()] +Param( + [string]$Script = "build.cake", + [string]$Target = "Default", + [ValidateSet("Release", "Debug")] + [string]$Configuration = "Release", + [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")] + [string]$Verbosity = "Verbose", + [switch]$Experimental, + [Alias("DryRun","Noop")] + [switch]$WhatIf, + [switch]$Mono, + [switch]$SkipToolPackageRestore, + [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)] + [string[]]$ScriptArgs ) -$build_file = 'default.ps1' +[Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null +function MD5HashFile([string] $filePath) +{ + if ([string]::IsNullOrEmpty($filePath) -or !(Test-Path $filePath -PathType Leaf)) + { + return $null + } -# Properties for the psake build script -$properties = @{ + [System.IO.Stream] $file = $null; + [System.Security.Cryptography.MD5] $md5 = $null; + try + { + $md5 = [System.Security.Cryptography.MD5]::Create() + $file = [System.IO.File]::OpenRead($filePath) + return [System.BitConverter]::ToString($md5.ComputeHash($file)) + } + finally + { + if ($file -ne $null) + { + $file.Dispose() + } + } +} - # Build configuration to use - "configuration" = "Release"; +Write-Host "Preparing to run build script..." - # Version number to use if running the Publish build task. - # This will be read from the command line args - "version" = $version; +if(!$PSScriptRoot){ + $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent +} - # Path to the solution file - "solution" = 'Nancy.CustomErrors.sln'; +$TOOLS_DIR = Join-Path $PSScriptRoot "tools" +$NUGET_EXE = Join-Path $TOOLS_DIR "nuget.exe" +$CAKE_EXE = Join-Path $TOOLS_DIR "Cake/Cake.exe" +$NUGET_URL = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" +$PACKAGES_CONFIG = Join-Path $TOOLS_DIR "packages.config" +$PACKAGES_CONFIG_MD5 = Join-Path $TOOLS_DIR "packages.config.md5sum" - # Folder containing source code - "source_folder" = ''; +# Should we use mono? +$UseMono = ""; +if($Mono.IsPresent) { + Write-Verbose -Message "Using the Mono based scripting engine." + $UseMono = "-mono" +} - # Folder to output deployable packages to. This folder should be ignored - # from any source control, as we dont commit build artifacts to source - # control - "deploy_folder" = 'deploy'; +# Should we use the new Roslyn? +$UseExperimental = ""; +if($Experimental.IsPresent -and !($Mono.IsPresent)) { + Write-Verbose -Message "Using experimental version of Roslyn." + $UseExperimental = "-experimental" +} - # Build number metadata that will be appended to semver numers - "build_meta" = $BuildMetaData; - - "projects" = @( - "Nancy.CustomErrors") +# Is this a dry run? +$UseDryRun = ""; +if($WhatIf.IsPresent) { + $UseDryRun = "-dryrun" +} +# Make sure tools folder exists +if ((Test-Path $PSScriptRoot) -and !(Test-Path $TOOLS_DIR)) { + Write-Verbose -Message "Creating tools directory..." + New-Item -Path $TOOLS_DIR -Type directory | out-null } +# Make sure that packages.config exist. +if (!(Test-Path $PACKAGES_CONFIG)) { + Write-Verbose -Message "Downloading packages.config..." + try { (New-Object System.Net.WebClient).DownloadFile("http://cakebuild.net/download/bootstrapper/packages", $PACKAGES_CONFIG) } catch { + Throw "Could not download packages.config." + } +} + +# Try find NuGet.exe in path if not exists +if (!(Test-Path $NUGET_EXE)) { + Write-Verbose -Message "Trying to find nuget.exe in PATH..." + $existingPaths = $Env:Path -Split ';' | Where-Object { (![string]::IsNullOrEmpty($_)) -and (Test-Path $_ -PathType Container) } + $NUGET_EXE_IN_PATH = Get-ChildItem -Path $existingPaths -Filter "nuget.exe" | Select -First 1 + if ($NUGET_EXE_IN_PATH -ne $null -and (Test-Path $NUGET_EXE_IN_PATH.FullName)) { + Write-Verbose -Message "Found in PATH at $($NUGET_EXE_IN_PATH.FullName)." + $NUGET_EXE = $NUGET_EXE_IN_PATH.FullName + } +} + +# Try download NuGet.exe if not exists +if (!(Test-Path $NUGET_EXE)) { + Write-Verbose -Message "Downloading NuGet.exe..." + try { + (New-Object System.Net.WebClient).DownloadFile($NUGET_URL, $NUGET_EXE) + } catch { + Throw "Could not download NuGet.exe." + } +} + +# Save nuget.exe path to environment to be available to child processed +$ENV:NUGET_EXE = $NUGET_EXE -import-module .\packages\psake.4.4.1\tools\psake.psm1 +# Restore tools from NuGet? +if(-Not $SkipToolPackageRestore.IsPresent) { + Push-Location + Set-Location $TOOLS_DIR + + # Check for changes in packages.config and remove installed tools if true. + [string] $md5Hash = MD5HashFile($PACKAGES_CONFIG) + if((!(Test-Path $PACKAGES_CONFIG_MD5)) -Or + ($md5Hash -ne (Get-Content $PACKAGES_CONFIG_MD5 ))) { + Write-Verbose -Message "Missing or changed package.config hash..." + Remove-Item * -Recurse -Exclude packages.config,nuget.exe,settingsUtils.cake,versionUtils.cake + } + + Write-Verbose -Message "Restoring tools from NuGet..." + $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$TOOLS_DIR`"" + + if ($LASTEXITCODE -ne 0) { + Throw "An error occured while restoring NuGet tools." + } + else + { + $md5Hash | Out-File $PACKAGES_CONFIG_MD5 -Encoding "ASCII" + } + Write-Verbose -Message ($NuGetOutput | out-string) + Pop-Location +} + +# Make sure that Cake has been installed. +if (!(Test-Path $CAKE_EXE)) { + Throw "Could not find Cake.exe at $CAKE_EXE" +} -invoke-psake $build_file $task_list -Properties $properties \ No newline at end of file +# Start Cake +Write-Host "Running build script..." +Invoke-Expression "& `"$CAKE_EXE`" `"$Script`" -target=`"$Target`" -configuration=`"$Configuration`" -verbosity=`"$Verbosity`" $UseMono $UseDryRun $UseExperimental $ScriptArgs" +exit $LASTEXITCODE \ No newline at end of file diff --git a/default.ps1 b/default.ps1 deleted file mode 100644 index 069a50c..0000000 --- a/default.ps1 +++ /dev/null @@ -1,65 +0,0 @@ -Properties { - $projects = $null - $configuration = "Release" - $source_folder = $null - $solution = $null - $build_meta = $null -} - -Task Default -Depends Build - - -Task Publish -Depends Package { - $version = getVersionBase - - foreach($project in $projects) { - Get-ChildItem | Where-Object -FilterScript { - ($_.Name.Contains("$project.$version")) -and !($_.Name.Contains(".symbols")) -and ($_.Extension -eq '.nupkg') - } | ForEach-Object { - exec { nuget push $_.FullName } - } - } -} - -Task Package -Depends Test { - foreach($project in $projects) { - Get-ChildItem -Path "$project\*.csproj" | ForEach-Object { - exec { nuget pack -sym $_.FullName -Prop Configuration=$configuration } - } - } -} - -Task Test -Depends Build { - Get-ChildItem $source_folder -Recurse -Include *Tests.csproj | % { - Exec { & ".\packages\xunit.runners.1.9.2\tools\xunit.console.clr4.exe" "$($_.DirectoryName)\bin\$configuration\$($_.BaseName).dll" /noshadow } - } -} - -Task Build -Depends Clean,Set-Versions { - Exec { msbuild "$solution" /t:Build /p:Configuration=$configuration } -} - -Task Clean { - Exec { msbuild "$solution" /t:Clean /p:Configuration=$configuration } -} - -Task Set-Versions { - $version = getVersionBase - - if ($build_meta) { - "##teamcity[buildNumber '$version+$build_meta']" | Write-Host - } else { - "##teamcity[buildNumber '$version']" | Write-Host - } - - Get-ChildItem -Recurse -Force | Where-Object { $_.Name -eq "AssemblyInfo.cs" } | ForEach-Object { - (Get-Content $_.FullName) | ForEach-Object { - ($_ -replace 'AssemblyVersion\(.*\)', ('AssemblyVersion("' + $version + '")')) -replace 'AssemblyFileVersion\(.*\)', ('AssemblyFileVersion("' + $version + '")') - } | Set-Content $_.FullName -Encoding UTF8 - } -} - -function getVersionBase { - $versionInfo = (Get-Content "version.json") -join "`n" | ConvertFrom-Json - "$($versionInfo.major).$($versionInfo.minor).$($versionInfo.patch)"; -} \ No newline at end of file diff --git a/nuspec/Nancy.CustomErrors.nuspec b/nuspec/Nancy.CustomErrors.nuspec new file mode 100644 index 0000000..467e82d --- /dev/null +++ b/nuspec/Nancy.CustomErrors.nuspec @@ -0,0 +1,33 @@ + + + + Nancy.CustomErrors + 2.0.0-pre01 + Nancy Custom Errors + Brendan ravensorb + Brendan McMahon + https://github.com/bernos/Nancy.CustomErrors + Adds exception handling for cases when custom error views cannot be found + false + Simple custom error handling for Nancyfx projects + Copyright 2017 + Nancyfx + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/settings.json b/settings.json new file mode 100644 index 0000000..3d57486 --- /dev/null +++ b/settings.json @@ -0,0 +1,37 @@ +{ + "version": { + "VersionFile": "./version.json", + "AssemblyInfoFile": "./Nancy.CustomErrors/Properties/AssemblyInfo.cs", + "LoadFrom": "versionfile", + "AutoIncrementVersion": false + }, + "build": { + "SourcePath": "./src", + "SolutionFileSpec": "./*.sln", + "TreatWarningsAsErrors": false + }, + "xamarin":{ + "EnableXamarinIOS": false, + "MacAgentIPAddress": "", + "MacAgentUserName": "", + "MacAgentUserPassword": "" + }, + "test": { + "SourcePath": "./tests", + "ResultsPath": "./artifacts", + "AssemblyFileSpec": "*.Tests.dll", + "Framework": "XUnit2" + }, + "nuget": { + "NuGetConfig": "./.nuget/NuGet.config", + "FeedUrl": "", + "FeedAPIKey": "", + "ArtifactsPath": "./artifacts/packages", + "NuspecPath": "./nuspec", + "UpdateVersion": true, + "UpdateLibraryDependencies": false, + "VersionDependencyForLibrary": "greaterthanorequal", + "LibraryNamespaceBase": "", + "LibraryMinVersionDependency": "" + } +} \ No newline at end of file diff --git a/src/AssemblyInfo.Shared.cs b/src/AssemblyInfo.Shared.cs new file mode 100644 index 0000000..b7c94c4 --- /dev/null +++ b/src/AssemblyInfo.Shared.cs @@ -0,0 +1,36 @@ +// *********************************************************************** +// +// Copyright (c) . All rights reserved. +// +// +// +// *********************************************************************** +// + +using System.Reflection; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyCompany("Brendan McMahon")] +[assembly: AssemblyProduct("Nancy.CustomErrors")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyConfiguration("")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("2.0.0")] +//[assembly: AssemblyFileVersion("2.0.0")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +//[assembly: ComVisible(false)] diff --git a/src/Nancy.CustomErrors.NetStandard1.6/Nancy.CustomErrors.NetStandard1.6.csproj b/src/Nancy.CustomErrors.NetStandard1.6/Nancy.CustomErrors.NetStandard1.6.csproj new file mode 100644 index 0000000..e5a42d3 --- /dev/null +++ b/src/Nancy.CustomErrors.NetStandard1.6/Nancy.CustomErrors.NetStandard1.6.csproj @@ -0,0 +1,15 @@ + + + + netstandard1.6 + Nancy.CustomErrors + Nancy.CustomErrors + + + + + + + + + \ No newline at end of file diff --git a/src/Nancy.CustomErrors.Shared/CustomErrors.cs b/src/Nancy.CustomErrors.Shared/CustomErrors.cs new file mode 100644 index 0000000..41cb18f --- /dev/null +++ b/src/Nancy.CustomErrors.Shared/CustomErrors.cs @@ -0,0 +1,87 @@ +// *********************************************************************** +// Assembly : Nancy.CustomErrors +// Author : +// Created : 05-13-2017 +// +// Last Modified By : +// Last Modified On : 06-23-2017 +// *********************************************************************** +// +// Copyright (c) . All rights reserved. +// +// +// *********************************************************************** +using System; +using Nancy.Bootstrapper; +using Nancy.Responses; + +namespace Nancy.CustomErrors +{ + /// + /// Class CustomErrors. + /// + public static class CustomErrors + { + /// + /// The configuration + /// + private static CustomErrorsConfiguration _configuration; + /// + /// Gets the configuration. + /// + /// The configuration. + public static CustomErrorsConfiguration Configuration + { + get { return _configuration ?? (_configuration = new CustomErrorsConfiguration()); } + } + + /// + /// Enables the specified pipelines. + /// + /// The pipelines. + /// The configuration. + public static void Enable(IPipelines pipelines, CustomErrorsConfiguration configuration) + { + Enable(pipelines, configuration, new DefaultJsonSerializer(Nancy.Bootstrapper.NancyBootstrapperLocator.Bootstrapper.GetEnvironment())); + } + + /// + /// Enables the specified pipelines. + /// + /// The pipelines. + /// The configuration. + /// The serializer. + /// + /// pipelines + /// or + /// configuration + /// + public static void Enable(IPipelines pipelines, CustomErrorsConfiguration configuration, ISerializer serializer) + { + if (pipelines == null) + { + throw new ArgumentNullException(nameof(pipelines)); + } + + if (configuration == null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + _configuration = configuration; + + pipelines.OnError.AddItemToEndOfPipeline(GetErrorHandler(configuration, serializer)); + } + + /// + /// Gets the error handler. + /// + /// The configuration. + /// The serializer. + /// Func<NancyContext, Exception, Response>. + private static Func GetErrorHandler(CustomErrorsConfiguration configuration, ISerializer serializer) + { + return (context, ex) => configuration.HandleError(context, ex, serializer); + } + } +} \ No newline at end of file diff --git a/src/Nancy.CustomErrors.Shared/CustomErrorsConfiguration.cs b/src/Nancy.CustomErrors.Shared/CustomErrorsConfiguration.cs new file mode 100644 index 0000000..7a9b245 --- /dev/null +++ b/src/Nancy.CustomErrors.Shared/CustomErrorsConfiguration.cs @@ -0,0 +1,98 @@ +// *********************************************************************** +// Assembly : Nancy.CustomErrors +// Author : +// Created : 05-13-2017 +// +// Last Modified By : +// Last Modified On : 05-13-2017 +// *********************************************************************** +// +// Copyright (c) . All rights reserved. +// +// +// *********************************************************************** +using System; +using System.Collections.Generic; + +namespace Nancy.CustomErrors +{ + /// + /// Class CustomErrorsConfiguration. + /// + public class CustomErrorsConfiguration + { + /// + /// The not found title + /// + public string NotFoundTitle = "404 Not Found"; + /// + /// The not found summary + /// + public string NotFoundSummary = "The requested resource could not be found."; + /// + /// The forbidden title + /// + public string ForbiddenTitle = "Forbidden"; + /// + /// The forbidden summary + /// + public string ForbiddenSummary = "You do not have permission to do that."; + /// + /// The unauthorized title + /// + public string UnauthorizedTitle = "Unauthorized"; + /// + /// The unauthorized summary + /// + public string UnauthorizedSummary = "You do not have permission to do that."; + /// + /// The error title + /// + public string ErrorTitle = "Error"; + /// + /// The error summary + /// + public string ErrorSummary = "An unexpected error occurred."; + + /// + /// The always return json + /// + public bool AlwaysReturnJson = false; + + /// + /// If set to true, then we will emit full stack traces in our ErrorResponse + /// + public bool Debug = false; + + /// + /// Converts a thrown exception to the appropriate ErrorResponse. Override this method if you need + /// to handle custom exception types, or implement your own error handling logic. The default + /// implementation converts all thrown exceptions to a regular ErrorResponse with an HttpStatusCode + /// of 500 + /// + /// The context. + /// The ex. + /// The serializer. + /// ErrorResponse. + public virtual ErrorResponse HandleError(NancyContext context, Exception ex, ISerializer serializer) + { + var error = new Error + { + FullException = ex.ToString(), + Message = ex.Message + }; + + return new ErrorResponse(error, serializer, context.Environment).WithStatusCode(HttpStatusCode.InternalServerError) as ErrorResponse; + } + + /// + /// Maps different HttpStatusCodes to the appropriate views. + /// + public IDictionary ErrorViews = new Dictionary + { + { HttpStatusCode.NotFound, "Error" }, + { HttpStatusCode.InternalServerError, "Error" }, + { HttpStatusCode.Forbidden, "Error" } + }; + } +} diff --git a/src/Nancy.CustomErrors.Shared/Error.cs b/src/Nancy.CustomErrors.Shared/Error.cs new file mode 100644 index 0000000..0573b66 --- /dev/null +++ b/src/Nancy.CustomErrors.Shared/Error.cs @@ -0,0 +1,33 @@ +// *********************************************************************** +// Assembly : Nancy.CustomErrors +// Author : +// Created : 05-13-2017 +// +// Last Modified By : +// Last Modified On : 05-13-2017 +// *********************************************************************** +// +// Copyright (c) . All rights reserved. +// +// +// *********************************************************************** + +namespace Nancy.CustomErrors +{ + /// + /// Class Error. + /// + public class Error + { + /// + /// Gets or sets the message. + /// + /// The message. + public string Message { get; set; } + /// + /// Gets or sets the full exception. + /// + /// The full exception. + public string FullException { get; set; } + } +} \ No newline at end of file diff --git a/src/Nancy.CustomErrors.Shared/ErrorResponse.cs b/src/Nancy.CustomErrors.Shared/ErrorResponse.cs new file mode 100644 index 0000000..11e1bb2 --- /dev/null +++ b/src/Nancy.CustomErrors.Shared/ErrorResponse.cs @@ -0,0 +1,54 @@ +// *********************************************************************** +// Assembly : Nancy.CustomErrors +// Author : +// Created : 05-13-2017 +// +// Last Modified By : +// Last Modified On : 05-13-2017 +// *********************************************************************** +// +// Copyright (c) . All rights reserved. +// +// +// *********************************************************************** +using Nancy.Configuration; +using Nancy.Responses; + +namespace Nancy.CustomErrors +{ + /// + /// Class ErrorResponse. + /// + public class ErrorResponse : JsonResponse + { + /// + /// The error + /// + private readonly Error _error; + /// + /// Gets the error message. + /// + /// The error message. + public string ErrorMessage { get { return _error.Message; } } + /// + /// Gets the full exception. + /// + /// The full exception. + public string FullException { get { return _error.FullException; } } + /// + /// Initializes a new instance of the class. + /// + /// The error. + /// The serializer. + /// The environment. + public ErrorResponse(Error error, ISerializer serializer, INancyEnvironment environment) : base(error, serializer, environment) + { + if (!CustomErrors.Configuration.Debug) + { + error.FullException = null; + } + + _error = error; + } + } +} diff --git a/src/Nancy.CustomErrors.Shared/ErrorStatusCodeHandler.cs b/src/Nancy.CustomErrors.Shared/ErrorStatusCodeHandler.cs new file mode 100644 index 0000000..6a13d5f --- /dev/null +++ b/src/Nancy.CustomErrors.Shared/ErrorStatusCodeHandler.cs @@ -0,0 +1,197 @@ +// *********************************************************************** +// Assembly : Nancy.CustomErrors +// Author : +// Created : 05-13-2017 +// +// Last Modified By : +// Last Modified On : 05-13-2017 +// *********************************************************************** +// +// Copyright (c) . All rights reserved. +// +// +// *********************************************************************** +using Nancy.ErrorHandling; +using Nancy.ViewEngines; +using System; +using System.IO; +using System.Linq; + +namespace Nancy.CustomErrors +{ + /// + /// Class ErrorStatusCodeHandler. + /// + public class ErrorStatusCodeHandler : DefaultViewRenderer, IStatusCodeHandler + { + /// + /// The serializer + /// + private readonly ISerializer _serializer; + + /// + /// Initializes a new instance of the class. + /// + /// The view factory. + public ErrorStatusCodeHandler(IViewFactory viewFactory) : base(viewFactory) + { + } + + //Made this private as some DI containers like Autofac and Ninject have issues when multiple registered instances of an interface exist and + // convention based DI is used for constructors + /// + /// Initializes a new instance of the class. + /// + /// The view factory. + /// The serializer. + private ErrorStatusCodeHandler(IViewFactory viewFactory, ISerializer serializer) + : base(viewFactory) + { + _serializer = serializer; + } + + /// + /// Handleses the status code. + /// + /// The status code. + /// The context. + /// true if XXXX, false otherwise. + public bool HandlesStatusCode(HttpStatusCode statusCode, NancyContext context) + { + return statusCode == HttpStatusCode.NotFound + || statusCode == HttpStatusCode.InternalServerError + || statusCode == HttpStatusCode.Forbidden + || statusCode == HttpStatusCode.Unauthorized; + } + + /// + /// Handles the specified status code. + /// + /// The status code. + /// The context. + public void Handle(HttpStatusCode statusCode, NancyContext context) + { + var headers = context.Response.Headers.Select(h => Tuple.Create(h.Key, h.Value)).ToArray(); + + if (!ShouldRenderFriendlyErrorPage(context)) + { + // Assume a valid error response was built earlier in the request lifecycle + // Nothing more for us to do here, so just bail out. + if (context.Response is ErrorResponse) + { + return; + } + + var err = new Error + { + Message = CustomErrors.Configuration.ErrorSummary, + }; + + if (context.Response is NotFoundResponse) + { + // Normally we return 404's ourselves so we have an ErrorResponse. + // But if no route is matched, Nancy will set a NotFound response itself. + // When this happens we still want to return our nice JSON response. + err.Message = CustomErrors.Configuration.NotFoundSummary; + } + else + { + switch (statusCode) + { + case HttpStatusCode.Forbidden : + case HttpStatusCode.Unauthorized : + err.Message = CustomErrors.Configuration.UnauthorizedSummary; + break; + case HttpStatusCode.NotFound : + err.Message = CustomErrors.Configuration.NotFoundSummary; + context.Response = new ErrorResponse(new Error + { + Message = CustomErrors.Configuration.NotFoundSummary + }, _serializer, context.Environment); + break; + } + } + + context.Response = new ErrorResponse(err, _serializer, context.Environment).WithHeaders(headers).WithStatusCode(statusCode); + + return; + } + + var error = context.Response as ErrorResponse; + + var model = new ErrorViewModel + { + Details = error == null ? "" : error.FullException, + Message = error == null ? "" : error.ErrorMessage + }; + + switch (statusCode) + { + case HttpStatusCode.Forbidden: + model.Title = CustomErrors.Configuration.ForbiddenTitle; + model.Summary = CustomErrors.Configuration.ForbiddenSummary; + + break; + + case HttpStatusCode.Unauthorized: + model.Title = CustomErrors.Configuration.UnauthorizedTitle; + model.Summary = error == null ? CustomErrors.Configuration.UnauthorizedSummary : error.ErrorMessage; + + break; + + case HttpStatusCode.NotFound: + model.Title = CustomErrors.Configuration.NotFoundTitle; + model.Summary = CustomErrors.Configuration.NotFoundSummary; + + break; + case HttpStatusCode.InternalServerError: + model.Title = CustomErrors.Configuration.ErrorTitle; + model.Summary = error == null ? CustomErrors.Configuration.ErrorSummary : error.ErrorMessage; + + break; + } + + try + { + context.Response = + RenderView(context, CustomErrors.Configuration.ErrorViews[statusCode], model) + .WithStatusCode(statusCode) + .WithHeaders(headers); + } + catch(Exception e) + { + context.Response = new Response + { + StatusCode = HttpStatusCode.InternalServerError, + ContentType = "text/plain", + Contents = stream => + { + var writer = new StreamWriter(stream) {AutoFlush = true}; + writer.Write($"Could not locate your error view! Details: {e.Message}"); + } + }; + } + } + + /// + /// Shoulds the render friendly error page. + /// + /// The context. + /// true if XXXX, false otherwise. + private static bool ShouldRenderFriendlyErrorPage(NancyContext context) + { + if (CustomErrors.Configuration.AlwaysReturnJson) + { + return false; + } + + if (context.Request.Headers.Accept.OrderByDescending(o => o.Item2) + .Any(o => o.Item1 == "application/json" || o.Item1 == "text/json")) + { + return false; + } + + return true; + } + } +} \ No newline at end of file diff --git a/src/Nancy.CustomErrors.Shared/ErrorViewModel.cs b/src/Nancy.CustomErrors.Shared/ErrorViewModel.cs new file mode 100644 index 0000000..457fcf4 --- /dev/null +++ b/src/Nancy.CustomErrors.Shared/ErrorViewModel.cs @@ -0,0 +1,38 @@ +// *********************************************************************** +// Assembly : Nancy.CustomErrors +// Author : +// Created : 05-13-2017 +// +// Last Modified By : +// Last Modified On : 05-13-2017 +// *********************************************************************** +// +// Copyright (c) . All rights reserved. +// +// +// *********************************************************************** +namespace Nancy.CustomErrors +{ + /// + /// Class ErrorViewModel. + /// + public class ErrorViewModel + { + /// + /// Gets or sets the title. + /// + /// The title. + public string Title { get; set; } + /// + /// Gets or sets the summary. + /// + /// The summary. + public string Summary { get; set; } + public string Message { get; set; } + /// + /// Gets or sets the details. + /// + /// The details. + public string Details { get; set; } + } +} diff --git a/src/Nancy.CustomErrors.Shared/Nancy.CustomErrors.Shared.projitems b/src/Nancy.CustomErrors.Shared/Nancy.CustomErrors.Shared.projitems new file mode 100644 index 0000000..01be28a --- /dev/null +++ b/src/Nancy.CustomErrors.Shared/Nancy.CustomErrors.Shared.projitems @@ -0,0 +1,20 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + 0b0c2e66-9fd4-4475-ab31-81ab2f39f4da + + + Nancy.CustomErrors.Shared + + + + + + + + + + + \ No newline at end of file diff --git a/src/Nancy.CustomErrors.Shared/Nancy.CustomErrors.Shared.shproj b/src/Nancy.CustomErrors.Shared/Nancy.CustomErrors.Shared.shproj new file mode 100644 index 0000000..8cbcca3 --- /dev/null +++ b/src/Nancy.CustomErrors.Shared/Nancy.CustomErrors.Shared.shproj @@ -0,0 +1,13 @@ + + + + 0b0c2e66-9fd4-4475-ab31-81ab2f39f4da + 14.0 + + + + + + + + diff --git a/src/Nancy.CustomErrors.Shared/ResponseFormatterExtensions.cs b/src/Nancy.CustomErrors.Shared/ResponseFormatterExtensions.cs new file mode 100644 index 0000000..87d41f3 --- /dev/null +++ b/src/Nancy.CustomErrors.Shared/ResponseFormatterExtensions.cs @@ -0,0 +1,39 @@ +// *********************************************************************** +// Assembly : Nancy.CustomErrors +// Author : +// Created : 05-13-2017 +// +// Last Modified By : +// Last Modified On : 05-13-2017 +// *********************************************************************** +// +// Copyright (c) . All rights reserved. +// +// +// *********************************************************************** +using System.Linq; +using Nancy.Responses.Negotiation; + +namespace Nancy.CustomErrors +{ + /// + /// Class ResponseFormatterExtensions. + /// + public static class ResponseFormatterExtensions + { + /// + /// Ases the error. + /// + /// The formatter. + /// The message. + /// The status code. + /// Response. + public static Response AsError(this IResponseFormatter formatter, string message, + HttpStatusCode statusCode = HttpStatusCode.InternalServerError) + { + var serializer = formatter.SerializerFactory.GetSerializer(new MediaRange("application/json")); + + return new ErrorResponse(new Error {Message = message}, serializer, formatter.Environment).WithStatusCode(statusCode); + } + } +} \ No newline at end of file diff --git a/Nancy.CustomErrors/Nancy.CustomErrors.csproj b/src/Nancy.CustomErrors/Nancy.CustomErrors.csproj similarity index 72% rename from Nancy.CustomErrors/Nancy.CustomErrors.csproj rename to src/Nancy.CustomErrors/Nancy.CustomErrors.csproj index b43491d..ba25d4e 100644 --- a/Nancy.CustomErrors/Nancy.CustomErrors.csproj +++ b/src/Nancy.CustomErrors/Nancy.CustomErrors.csproj @@ -9,8 +9,11 @@ Properties Nancy.CustomErrors Nancy.CustomErrors - v4.0 + v4.6.1 512 + + ..\..\ + true true @@ -20,6 +23,7 @@ DEBUG;TRACE prompt 4 + false pdbonly @@ -28,11 +32,11 @@ TRACE prompt 4 + false - - False - ..\packages\Nancy.0.23.0\lib\net40\Nancy.dll + + ..\..\packages\Nancy.2.0.0-clinteastwood\lib\net452\Nancy.dll @@ -43,19 +47,20 @@ - - - - - - - + + Properties\AssemblyInfo.Shared.cs + - + + Designer + + + + + \ No newline at end of file diff --git a/Nancy.CustomErrors.Tests/Properties/AssemblyInfo.cs b/tests/Nancy.CustomErrors.Tests/Properties/AssemblyInfo.cs similarity index 91% rename from Nancy.CustomErrors.Tests/Properties/AssemblyInfo.cs rename to tests/Nancy.CustomErrors.Tests/Properties/AssemblyInfo.cs index d49ad41..6a8b630 100644 --- a/Nancy.CustomErrors.Tests/Properties/AssemblyInfo.cs +++ b/tests/Nancy.CustomErrors.Tests/Properties/AssemblyInfo.cs @@ -31,6 +31,6 @@ // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: -// [assembly: AssemblyVersion("1.1.1")] -[assembly: AssemblyVersion("1.1.1")] -[assembly: AssemblyFileVersion("1.1.1")] +// [assembly: AssemblyVersion("2.0.0")] +[assembly: AssemblyVersion("2.0.0")] +[assembly: AssemblyFileVersion("2.0.0")] diff --git a/Nancy.CustomErrors.Tests/Views/Error.html b/tests/Nancy.CustomErrors.Tests/Views/Error.html similarity index 100% rename from Nancy.CustomErrors.Tests/Views/Error.html rename to tests/Nancy.CustomErrors.Tests/Views/Error.html diff --git a/tests/Nancy.CustomErrors.Tests/app.config b/tests/Nancy.CustomErrors.Tests/app.config new file mode 100644 index 0000000..0e30424 --- /dev/null +++ b/tests/Nancy.CustomErrors.Tests/app.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tests/Nancy.CustomErrors.Tests/packages.config b/tests/Nancy.CustomErrors.Tests/packages.config new file mode 100644 index 0000000..864390e --- /dev/null +++ b/tests/Nancy.CustomErrors.Tests/packages.config @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tools/packages.config b/tools/packages.config new file mode 100644 index 0000000..a4d70bc --- /dev/null +++ b/tools/packages.config @@ -0,0 +1,4 @@ + + + + diff --git a/tools/settingsUtils.cake b/tools/settingsUtils.cake new file mode 100644 index 0000000..01a423e --- /dev/null +++ b/tools/settingsUtils.cake @@ -0,0 +1,332 @@ +#addin "Cake.Json" + +public class SettingsUtils +{ + public static Settings LoadSettings(ICakeContext context) + { + var settingsFile = context.Argument("settingsFile", ".\\settings.json"); + + context.Information("Loading Settings: {0}", settingsFile); + + if (!context.FileExists(settingsFile)) + { + context.Error("Settings File Does Not Exist"); + return null; + } + + var obj = context.DeserializeJsonFromFile(settingsFile); + + obj.SettingsFile = settingsFile; + + // Allow for any overrides + obj.Target = context.Argument("target", obj.Target); + obj.Configuration = context.Argument("configuration", obj.Configuration); + obj.VersionFile = context.Argument("versionFile", obj.VersionFile); + + obj.ExecuteBuild = GetBoolArgument(context, "build", obj.ExecuteBuild); + obj.ExecuteBuild = !GetBoolArgument(context, "skipBuild", !obj.ExecuteBuild); + + obj.ExecutePackage = GetBoolArgument(context, "package", obj.ExecutePackage); + obj.ExecutePackage = !GetBoolArgument(context, "skipPackage", !obj.ExecutePackage); + + obj.ExecuteUnitTest = GetBoolArgument(context, "unitTest", obj.ExecuteUnitTest); + obj.ExecuteUnitTest = !GetBoolArgument(context, "skipUnitTest", !obj.ExecuteUnitTest); + + obj.ExecuteClean = GetBoolArgument(context, "clean", obj.ExecuteClean); + obj.ExecuteClean = !GetBoolArgument(context, "skipClean", !obj.ExecuteClean); + + if (obj.Xamarin == null) obj.Xamarin = new XamarinSettings(); + + obj.Xamarin.EnableXamarinIOS = GetBoolArgument(context, "enableXamarinIOS", obj.Xamarin.EnableXamarinIOS); + obj.Xamarin.MacAgentIPAddress = context.Argument("macAgentIP", obj.Xamarin.MacAgentIPAddress); + obj.Xamarin.MacAgentUserName = context.Argument("macUserName", obj.Xamarin.MacAgentUserName); + obj.Xamarin.MacAgentUserPassword = context.Argument("macPassword", obj.Xamarin.MacAgentUserPassword); + + if (obj.NuGet == null) obj.NuGet = new NuGetSettings(); + + obj.NuGet.FeedUrl = context.Argument("nugetFeed", obj.NuGet.FeedUrl); + obj.NuGet.FeedUrl = context.Argument("nugetFeedUrl", obj.NuGet.FeedUrl); + + obj.NuGet.FeedApiKey = context.Argument("nugetApiKey", obj.NuGet.FeedApiKey); + + obj.NuGet.LibraryMinVersionDependency = (context.Argument("dependencyVersion", obj.NuGet.LibraryMinVersionDependency)).Replace(":","."); + obj.NuGet.VersionDependencyTypeForLibrary = context.Argument("dependencyType", obj.NuGet.VersionDependencyTypeForLibrary); + + return obj; + } + + private static bool GetBoolArgument(ICakeContext context, string argumentName, bool defaultValue) + { + var result = context.Argument(argumentName, defaultValue.ToString()).ToLower() == "true" || + context.Argument(argumentName, argumentName.ToString()) == "1"; + + return result; + } + + public static void DisplayHelp(ICakeContext context) + { + var defaultValues = new Settings(); + + context.Information("Command Line Help/Syntax:"); + context.Information("\t.\\build.ps1 \t\t\t\t(Default: {0})", defaultValues.Target); + context.Information("\t\t-Configuration=\t\t(Default: {0})", defaultValues.Configuration); + context.Information("\t\t-settingsFile=\t\t(Default: {0})", defaultValues.SettingsFile); + context.Information("\t\t-versionFile=\t\t(Default: {0})", defaultValues.VersionFile); + context.Information("\t\t-build=<0|1>\t\t\t\t(Default: {0})", defaultValues.ExecuteBuild); + context.Information("\t\t-package=<0|1>\t\t\t\t(Default: {0})", defaultValues.ExecutePackage); + context.Information("\t\t-unitTest=<0|1>\t\t\t\t(Default: {0})", defaultValues.ExecuteUnitTest); + context.Information("\t\t-clean=<0|1>\t\t\t\t(Default: {0})", defaultValues.ExecuteClean); + context.Information("\t\t-nugetFeed=\t\t(Default: {0})", defaultValues.NuGet.FeedUrl); + context.Information("\t\t-nugetApiKey=\t(Default: {0})", defaultValues.NuGet.FeedApiKey); + context.Information("\t\t-dependencyVersion=\t(Default: {0})", defaultValues.NuGet.LibraryMinVersionDependency); + context.Information("\t\t-dependencyType=\t\t(Default: {0})", defaultValues.NuGet.VersionDependencyTypeForLibrary); + context.Information("\t\t-enableXamarinIOS=<0|1>\t\t\t(Default: {0})", defaultValues.Xamarin.EnableXamarinIOS); + context.Information("\t\t-macAgentIP=\t\t(Default: {0})", defaultValues.Xamarin.MacAgentIPAddress); + context.Information("\t\t-macUserName=\t\t(Default: {0})", defaultValues.Xamarin.MacAgentUserName); + context.Information("\t\t-macPassword=\t\t(Default: {0})", defaultValues.Xamarin.MacAgentUserPassword); + context.Information(""); + context.Information("Examples:"); + context.Information("\t.\\build Build -Configuration=Release"); + context.Information("\t.\\build UnitTest -build=0"); + } +} + +public class Settings +{ + public Settings() + { + ExecuteBuild = true; + ExecutePackage = true; + ExecuteUnitTest = true; + ExecuteClean = true; + + Target = "DisplayHelp"; + Configuration = "Release"; + SettingsFile = ".\\settings.json"; + VersionFile = ".\\version.json"; + + Version = new VersionSettings(); + Build = new BuildSettings(); + Xamarin = new XamarinSettings(); + Test = new TestSettings(); + NuGet = new NuGetSettings(); + } + + public string Target {get;set;} + public string Configuration {get;set;} + public string SettingsFile {get;set;} + public string VersionFile {get;set;} + + public bool ExecuteBuild {get;set;} + public bool ExecutePackage {get;set;} + public bool ExecuteUnitTest {get;set;} + public bool ExecuteClean {get;set;} + + public VersionSettings Version {get;set;} + public BuildSettings Build {get;set;} + public TestSettings Test {get;set;} + public NuGetSettings NuGet {get;set;} + public XamarinSettings Xamarin {get;set;} + + public void Display(ICakeContext context) + { + context.Information("Settings:"); + + context.Information("\tTarget: {0}", Target); + context.Information("\tConfiguration: {0}", Configuration); + context.Information("\tSettings File: {0}", SettingsFile); + context.Information("\tVersion File: {0}", VersionFile); + + context.Information("\tExecute Build: {0}", ExecuteBuild); + context.Information("\tExecute Package: {0}", ExecutePackage); + context.Information("\tExecute UnitTests: {0}", ExecuteUnitTest); + context.Information("\tExecute Clean: {0}", ExecuteClean); + + Version.Display(context); + Build.Display(context); + Xamarin.Display(context); + Test.Display(context); + NuGet.Display(context); + } +} + +public class VersionSettings +{ + public VersionSettings() + { + LoadFrom = VersionSourceTypes.versionfile; + } + + public string VersionFile {get;set;} + public string AssemblyInfoFile {get;set;} + public VersionSourceTypes LoadFrom {get;set;} + public bool AutoIncrementVersion {get;set;} + + public void Display(ICakeContext context) + { + context.Information("Version Settings:"); + context.Information("\tVersion File: {0}", VersionFile); + context.Information("\tAssemblyInfo File: {0}", AssemblyInfoFile); + context.Information("\tLoad From: {0}", LoadFrom); + context.Information("\tAutoIncrement Version: {0}", AutoIncrementVersion); + } +} + +public class BuildSettings +{ + public BuildSettings() + { + SourcePath = "./source"; + SolutionFileSpec = "*.sln"; + TreatWarningsAsErrors = false; + MaxCpuCount = 0; + } + + public string SourcePath {get;set;} + public string SolutionFileSpec {get;set;} + public bool TreatWarningsAsErrors {get;set;} + public int MaxCpuCount {get;set;} + + public string SolutionFilePath { + get { + if (SolutionFileSpec.Contains("/")) return SolutionFileSpec; + + return string.Format("{0}{1}{2}", SourcePath, SolutionFileSpec.Contains("*") ? "/**/" : "", SolutionFileSpec); + } + } + + public void Display(ICakeContext context) + { + context.Information("Build Settings:"); + context.Information("\tSource Path: {0}", SourcePath); + context.Information("\tSolution File Spec: {0}", SolutionFileSpec); + context.Information("\tSolution File Path: {0}", SolutionFilePath); + context.Information("\tTreat Warnings As Errors: {0}", TreatWarningsAsErrors); + context.Information("\tMax Cpu Count: {0}", MaxCpuCount); + } +} + +public class XamarinSettings +{ + public XamarinSettings() + { + EnableXamarinIOS = false; + } + + public bool EnableXamarinIOS {get;set;} + public string MacAgentIPAddress {get;set;} + public string MacAgentUserName {get;set;} + public string MacAgentUserPassword {get;set;} + + public void Display(ICakeContext context) + { + context.Information("Xamarin Settings:"); + context.Information("\tEnable Xamarin IOS: {0}", EnableXamarinIOS); + context.Information("\tMac Agent IP Address: {0}", MacAgentIPAddress); + context.Information("\tMac Agent User Name: {0}", MacAgentUserName); + //context.Information("\tMac Agent User Password: {0}", MacAgentUserPassword); + } +} + +public class TestSettings +{ + public TestSettings() + { + SourcePath = "./tests"; + ResultsPath = "./tests"; + AssemblyFileSpec = "*.UnitTests.dll"; + Framework = TestFrameworkTypes.NUnit3; + } + + public string SourcePath {get;set;} + public string ResultsPath {get;set;} + public string AssemblyFileSpec {get;set;} + public TestFrameworkTypes Framework {get;set;} + + public void Display(ICakeContext context) + { + context.Information("Test Settings:"); + context.Information("\tSource Path: {0}", SourcePath); + context.Information("\tResults Path: {0}", ResultsPath); + context.Information("\tTest Assemploes File Spec: {0}", AssemblyFileSpec); + } +} + +public class NuGetSettings +{ + public NuGetSettings() + { + NuSpecPath = "./nuspec"; + NuGetConfig = "./.nuget/NuGet.Config"; + ArtifactsPath = "artifacts/packages"; + UpdateVersion = false; + VersionDependencyTypeForLibrary = VersionDependencyTypes.none; + UpdateLibraryDependencies = false; + LibraryNamespaceBase = null; + LibraryMinVersionDependency = null; + } + + public string NuGetConfig {get;set;} + public string FeedUrl {get;set;} + public string FeedApiKey {get;set;} + public string NuSpecPath {get;set;} + public string ArtifactsPath {get;set;} + public bool UpdateVersion {get;set;} + public VersionDependencyTypes VersionDependencyTypeForLibrary {get;set;} + public bool UpdateLibraryDependencies {get;set;} + public string LibraryNamespaceBase {get;set;} + public string LibraryMinVersionDependency {get;set;} + + public string NuSpecFileSpec { + get { + return string.Format("{0}/**/*.nuspec", NuSpecPath); + } + } + + public string NuGetPackagesSpec { + get { + return string.Format("{0}/*.nupkg", ArtifactsPath); + } + } + + public void Display(ICakeContext context) + { + context.Information("NuGet Settings:"); + context.Information("\tNuGet Config: {0}", NuGetConfig); + context.Information("\tFeed Url: {0}", FeedUrl); + //context.Information("\tFeed API Key: {0}", FeedApiKey); + context.Information("\tNuSpec Path: {0}", NuSpecPath); + context.Information("\tNuSpec File Spec: {0}", NuSpecFileSpec); + context.Information("\tArtifacts Path: {0}", ArtifactsPath); + context.Information("\tNuGet Packages Spec: {0}", NuGetPackagesSpec); + context.Information("\tUpdate Version: {0}", UpdateVersion); + context.Information("\tUpdate Library Dependencies: {0}", UpdateLibraryDependencies); + context.Information("\tForce Version Match: {0}", VersionDependencyTypeForLibrary); + context.Information("\tLibrary Namespace Base: {0}", LibraryNamespaceBase); + context.Information("\tLibrary Min Version Dependency: {0}", LibraryMinVersionDependency); + } +} + +public enum VersionDependencyTypes { + none, + exact, + greaterthan, + greaterthanorequal, + lessthan +} + +public enum VersionSourceTypes { + none, + versionfile, + assemblyinfo, + git, + tfs +} + +public enum TestFrameworkTypes { + none, + NUnit2, + NUnit3, + XUnit, + XUnit2 +} \ No newline at end of file diff --git a/tools/versionUtils.cake b/tools/versionUtils.cake new file mode 100644 index 0000000..7d9908d --- /dev/null +++ b/tools/versionUtils.cake @@ -0,0 +1,236 @@ +#addin "Cake.Json" +#addin "Cake.FileHelpers" + +#tool nuget:?package=GitVersion.CommandLine + +public class VersionUtils +{ + public static VersionInfo LoadVersion(ICakeContext context, Settings settings) + { + if (context == null) + { + throw new ArgumentNullException("context"); + } + + VersionInfo verInfo = null; + + switch (settings.Version.LoadFrom) + { + case VersionSourceTypes.none: + break; + case VersionSourceTypes.versionfile: + verInfo = LoadVersionFromJson(context, settings.Version.VersionFile); + break; + case VersionSourceTypes.assemblyinfo: + verInfo = LoadVersionFromAssemblyInfo(context, settings.Version.AssemblyInfoFile); + break; + case VersionSourceTypes.git: + verInfo = LoadVersionFromGit(context); + break; + case VersionSourceTypes.tfs: + //verInfo = LoadVersionFromTfs(context); + break; + } + + if (verInfo != null) + { + verInfo.CakeVersion = typeof(ICakeContext).Assembly.GetName().Version.ToString(); + } + + return verInfo; + } + + private static VersionInfo LoadVersionFromJson(ICakeContext context, string versionFile) + { + context.Information("Loading Version Info From File: {0}", versionFile); + if (string.IsNullOrEmpty(versionFile) || !context.FileExists(versionFile)) + { + context.Error("Version File Does Not Exist"); + return null; + } + + var obj = context.DeserializeJsonFromFile(versionFile); + + return obj; + } + + private static VersionInfo LoadVersionFromAssemblyInfo(ICakeContext context, string assemblyInfoFile) + { + context.Information("Fetching Version Info from AssemblyInfo File: {0}", assemblyInfoFile); + + if (!string.IsNullOrEmpty(assemblyInfoFile) || !context.FileExists(assemblyInfoFile)) + { + context.Error("AssemblyInfo file does not exist"); + return null; + } + + try { + var assemblyInfo = context.ParseAssemblyInfo(assemblyInfoFile); + var v = Version.Parse(assemblyInfo.AssemblyVersion); + + var verInfo = new VersionInfo { + Major = v.Major, + Minor = v.Minor, + Build = v.Build, + Semantic = assemblyInfo.AssemblyInformationalVersion, + Milestone = string.Concat("v", v.ToString()) + }; + + return verInfo; + } + catch {} + + return null; + } + + private static VersionInfo LoadVersionFromGit(ICakeContext context) + { + context.Information("Fetching Verson Infop from Git"); + + try { + GitVersion assertedVersions = context.GitVersion(new GitVersionSettings + { + OutputType = GitVersionOutput.Json, + }); + + var verInfo = new VersionInfo { + Major = assertedVersions.Major, + Minor = assertedVersions.Minor, + Build = assertedVersions.Patch, + Semantic = assertedVersions.LegacySemVerPadded, + Milestone = string.Concat("v", assertedVersions.MajorMinorPatch) + }; + + context.Information("Calculated Semantic Version: {0}", verInfo.Semantic); + + return verInfo; + } catch {} + + return null; + } + + public static void UpdateVersion(ICakeContext context, Settings settings, VersionInfo verInfo) + { + if (context == null) + { + throw new ArgumentNullException("context"); + } + + if (!string.IsNullOrEmpty(settings.Version.VersionFile) && context.FileExists(settings.Version.VersionFile)) + { + context.Information("Updating Version File {0}", settings.Version.VersionFile); + + context.SerializeJsonToFile(settings.Version.VersionFile, verInfo); + } + + if (!string.IsNullOrEmpty(settings.Version.AssemblyInfoFile) && context.FileExists(settings.Version.AssemblyInfoFile)) + { + context.Information("Updating Assembly Info File {0}", settings.Version.AssemblyInfoFile); + + context.ReplaceRegexInFiles(settings.Version.AssemblyInfoFile, "AssemblyVersion\\(.*\\)", string.Format("AssemblyVersion(\"{0}\")", verInfo.ToString(false))); + context.ReplaceRegexInFiles(settings.Version.AssemblyInfoFile, "AssemblyFileVersion\\(.*\\)", string.Format("AssemblyFileVersion(\"{0}\")", verInfo.ToString(false))); + } + } + + public static void UpdateNuSpecVersion(ICakeContext context, Settings settings, VersionInfo verInfo, FilePath nuspecFile) + { + if (context == null) + { + throw new ArgumentNullException("context"); + } + + var xpq = string.Format("/n:package/n:metadata/n:version"); + + context.Information("\tUpdating Version in Nuspec File {0} to {1}", nuspecFile, verInfo.ToString()); + + try { + context.XmlPoke(nuspecFile, xpq, verInfo.ToString(), new XmlPokeSettings { + PreserveWhitespace = true + , Namespaces = new Dictionary { + { /* Prefix */ "n", /* URI */ "http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd"} + } + }); + } catch {} // Its ok to throw these away as it most likely means the file didn't exist or the XPath didn't find any nodes + } + + public static void UpdateNuSpecVersionDependency(ICakeContext context, Settings settings, VersionInfo verInfo, FilePath nuspecFile) + { + if (context == null) + { + throw new ArgumentNullException("context"); + } + + if (string.IsNullOrEmpty(settings.NuGet.LibraryNamespaceBase)) return; + + var xpq = string.Format("/n:package/n:metadata/n:dependencies//n:dependency[starts-with(@id, '{0}')]/@version", settings.NuGet.LibraryNamespaceBase); + + var replacementStr = !string.IsNullOrEmpty(settings.NuGet.LibraryMinVersionDependency) ? settings.NuGet.LibraryMinVersionDependency : verInfo.ToString(); + + switch (settings.NuGet.VersionDependencyTypeForLibrary) + { + case VersionDependencyTypes.none: break; + case VersionDependencyTypes.exact: replacementStr = string.Format("[{0}]", replacementStr); break; + case VersionDependencyTypes.greaterthan: replacementStr = string.Format("(,{0})", replacementStr); break; + case VersionDependencyTypes.greaterthanorequal: replacementStr = string.Format("(,{0}]", replacementStr); break; + case VersionDependencyTypes.lessthan: replacementStr = string.Format("({0},)", replacementStr); break; + } + + context.Information("\tUpdating Version for {0} Namespace Assemblies in Nuspec File {1} to {2}", settings.NuGet.LibraryNamespaceBase, nuspecFile, replacementStr); + + try { + context.XmlPoke(nuspecFile, xpq, replacementStr, new XmlPokeSettings { + PreserveWhitespace = true + , Namespaces = new Dictionary { + { /* Prefix */ "n", /* URI */ "http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd"} + } + }); + } catch {} // Its ok to throw these away as it most likely means the file didn't exist or the XPath didn't find any nodes + } +} + +public class VersionInfo +{ + [Newtonsoft.Json.JsonProperty("major")] + public int Major {get;set;} + [Newtonsoft.Json.JsonProperty("minor")] + public int Minor {get;set;} + [Newtonsoft.Json.JsonProperty("build")] + public int Build {get;set;} + [Newtonsoft.Json.JsonProperty("preRelease")] + public int? PreRelease {get;set;} + [Newtonsoft.Json.JsonProperty("releaseNotes")] + public string[] ReleaseNotes {get;set;} + + [Newtonsoft.Json.JsonIgnore] + public string Semantic {get;set;} + [Newtonsoft.Json.JsonIgnore] + public string Milestone {get;set;} + [Newtonsoft.Json.JsonIgnore] + public string CakeVersion {get;set;} + + [Newtonsoft.Json.JsonIgnore] + public bool IsPreRelease { get { return PreRelease != null && PreRelease != 0; } } + + public new string ToString(bool includePreRelease = true) + { + var str = string.Format("{0:#0}.{1:#0}.{2:#0}", Major, Minor, Build); + if (IsPreRelease && includePreRelease) str += string.Format("-pre{0:00}", PreRelease); + + return str; + } + + public void Display(ICakeContext context) + { + context.Information("Version:"); + context.Information("\tMajor: {0}", Major); + context.Information("\tMinor: {0}", Minor); + context.Information("\tBuild: {0}", Build); + context.Information("\tIs PreRelease: {0}", IsPreRelease); + context.Information("\tPreRelease: {0}", PreRelease); + context.Information("\tSemantic: {0}", Semantic); + context.Information("\tMilestone: {0}", Milestone); + context.Information("\tCake Version: {0}", CakeVersion); + + if (ReleaseNotes != null) context.Information("\tRelease Notes: {0}", ReleaseNotes); + } +} \ No newline at end of file diff --git a/version.json b/version.json index ff2064e..697e456 100644 --- a/version.json +++ b/version.json @@ -1,5 +1 @@ -{ - "major" : 1, - "minor" : 2, - "patch" : 0 -} \ No newline at end of file +{"major":2,"minor":0,"build":0,"preRelease":1,"releaseNotes":null} \ No newline at end of file