{"id":15060965,"url":"https://github.com/cognitedata/oryx","last_synced_at":"2025-04-13T11:48:12.953Z","repository":{"id":38706403,"uuid":"202505943","full_name":"cognitedata/oryx","owner":"cognitedata","description":".NET Cross platform and highly composable middleware for building web request handlers in F#","archived":false,"fork":false,"pushed_at":"2024-10-29T12:10:46.000Z","size":508,"stargazers_count":202,"open_issues_count":6,"forks_count":10,"subscribers_count":75,"default_branch":"master","last_synced_at":"2024-10-29T14:39:03.164Z","etag":null,"topics":["dotnet","dotnet-standard","fsharp","functional-programming","middleware","web-client"],"latest_commit_sha":null,"homepage":"","language":"F#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cognitedata.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":"CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-08-15T08:42:31.000Z","updated_at":"2024-10-29T12:10:49.000Z","dependencies_parsed_at":"2023-11-13T13:29:13.091Z","dependency_job_id":"3276b66d-bf28-45d4-9f24-6de37df7d573","html_url":"https://github.com/cognitedata/oryx","commit_stats":{"total_commits":156,"total_committers":14,"mean_commits":"11.142857142857142","dds":0.3076923076923077,"last_synced_commit":"9ebb0aa40968c45b48fae634c7597a03891d97b6"},"previous_names":[],"tags_count":85,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cognitedata%2Foryx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cognitedata%2Foryx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cognitedata%2Foryx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cognitedata%2Foryx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cognitedata","download_url":"https://codeload.github.com/cognitedata/oryx/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248710410,"owners_count":21149186,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dotnet","dotnet-standard","fsharp","functional-programming","middleware","web-client"],"created_at":"2024-09-24T23:07:27.007Z","updated_at":"2025-04-13T11:48:12.915Z","avatar_url":"https://github.com/cognitedata.png","language":"F#","funding_links":[],"categories":["HTTP Clients"],"sub_categories":["Performance Analysis"],"readme":"# Oryx\n\n![Build and Test](https://github.com/cognitedata/oryx/workflows/Build%20and%20Test/badge.svg)\n[![codecov](https://codecov.io/gh/cognitedata/oryx/branch/master/graph/badge.svg)](https://codecov.io/gh/cognitedata/oryx)\n[![Nuget](https://img.shields.io/nuget/vpre/oryx)](https://www.nuget.org/packages/Oryx/)\n\nOryx is a high-performance .NET cross-platform functional HTTP request handler library for writing HTTP clients and\norchestrating web requests in F#.\n\n\u003e An SDK for writing HTTP web clients and orchestrating web requests.\n\nThis library enables you to write Web and REST clients and SDKs for various APIs and is currently used by the [.NET SDK\nfor Cognite Data Fusion (CDF)](https://github.com/cognitedata/cognite-sdk-dotnet).\n\nOryx is heavily inspired by the [AsyncRx](https://github.com/dbrattli/AsyncRx) and\n[Giraffe](https://github.com/giraffe-fsharp/Giraffe) frameworks and applies the same ideas to the client making the web\nrequests. You can think of Oryx as the client equivalent of Giraffe, where the HTTP request processing pipeline starting\nat the client, going all the way to the server and back again.\n\n## Installation\n\nOryx is available as a [NuGet package](https://www.nuget.org/packages/Oryx/). To install:\n\nUsing Package Manager:\n\n```sh\nInstall-Package Oryx\n```\n\nUsing .NET CLI:\n\n```sh\ndotnet add package Oryx\n```\n\nOr [directly in Visual Studio](https://docs.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-in-visual-studio).\n\n## Getting Started\n\n```fs\nopen System.Net.Http\nopen System.Text.Json\n\nopen FSharp.Control.TaskBuilder\n\nopen Oryx\nopen Oryx.SystemTextJson.ResponseReader\n\n[\u003cLiteral\u003e]\nlet Url = \"https://en.wikipedia.org/w/api.php\"\n\nlet options = JsonSerializerOptions()\n\nlet query term = [\n    struct (\"action\", \"opensearch\")\n    struct (\"search\", term)\n]\n\nlet asyncMain argv = task {\n    use client = new HttpClient ()\n    let request term =\n        httpRequest\n        |\u003e GET\n        |\u003e withHttpClient client\n        |\u003e withUrl Url\n        |\u003e withQuery (query term)\n        |\u003e fetch\n        |\u003e json options\n        \n    let! result = request \"F#\" |\u003e runAsync\n    printfn \"Result: %A\" result\n}\n\n[\u003cEntryPoint\u003e]\nlet main argv =\n    asyncMain().GetAwaiter().GetResult()\n    0 // return an integer exit code\n```\n\n## Fundamentals\n\nThe main building blocks in Oryx are the `HttpContext` and the `HttpHandler`. The context contains all the state needed\nfor making the request, and also contains any response metadata such as headers, response code, etc received from the\nremote server:\n\n```fs\ntype Context = {\n    Request: HttpRequest\n    Response: HttpResponse\n}\n```\n\nThe `HttpContext` is constructed using a pipeline of asynchronous HTTP handlers.\n\n```fs\ntype IHttpNext\u003c'TSource\u003e =\n    abstract member OnSuccessAsync: ctx: HttpContext * content: 'TSource -\u003e Task\u003cunit\u003e\n    abstract member OnErrorAsync: ctx: HttpContext * error: exn -\u003e Task\u003cunit\u003e\n    abstract member OnCancelAsync: ctx: HttpContext -\u003e Task\u003cunit\u003e\n\n\ntype HttpHandler\u003c'TSource\u003e = IHttpNext\u003c'TSource\u003e -\u003e Task\u003cunit\u003e\n```\n\nThe relationship can be seen as:\n\n```fs\ndo! handler success error cancel\n```\n\nAn HTTP handler (`HttpHandler`) is a pipeline that uses or subscribes `handler success error cancel` the given\ncontinuations `success`, `error` and `cancel`, and return a `Task` of unit.\n\nEach `HttpHandler` usually transforms the `HttpRequest`, `HttpResponse` or the `content` before passing it down the\npipeline by invoking the next `success` continuation. It may also signal an error by invoking `error` with an\nexception to fail the processing of the pipeline.\n\nThe easiest way to get your head around the Oryx `HttpHandler` is to think of it as a functional web request processing\npipeline. Each handler has the `HttpContext` and `content` at its disposal and can decide whether it wants to fail the\nrequest calling `error`, or continue the request by calling the `success` handler.\n\n## HTTP Handlers\n\nThe context and content may then be transformed for individual requests using a series of HTTP handlers. HTTP handlers\nare like lego bricks and may be composed into more complex HTTP handlers. The HTTP handlers included with Oryx are:\n\n- `cache` - Caches the last result of a given handler, both the context and the content.\n- `catch` - Catches errors and continues using another handler.\n- `choose` - Choose the first handler that succeeds in a list of handlers.\n- `chunk` - Chunks a sequence of HTTP handlers into sequential and concurrent batches.\n- `concurrent` - Runs a sequence of HTTP handlers concurrently.\n- `empty` - Creates a default empty request. You would usually start the chain with this handler.\n- `fail`- Fails the pipeline and pushes an exception downstream.\n- `fetch` - Fetches from remote using the current context\n- `skip` - Handler that skips (ignores) the content and outputs unit.\n- `get` - Retrieves the content (for use in `http` builder)\n- `log` - Log information about the given request.\n- `map` - Map the content of the HTTP handler.\n- `panic` - Fails the pipeline and pushes an exception downstream. This error cannot be catched or skipped.\n- `parse` - Parse response stream to a user-specified type synchronously.\n- `parseAsync` - Parse response stream to a user-specified type asynchronously.\n- `sequential` - Runs a sequence of HTTP handlers sequentially.\n- `singleton` - Handler that produces a single content value.\n- `validate` - Validate content using a predicate function.\n- `withBearerToken` - Adds an `Authorization` header with `Bearer` token.\n- `withCancellationToken` - Adds a cancellation token to use for the context. This is particularly useful when using\n  Oryx together with C# client code that supplies a cancellation token.\n- `withContent` - Add HTTP content to the fetch request\n- `withMetrics` - Add and `IMetrics` interface to produce metrics info.\n- `withError` - Detect if the HTTP request failed, and then fail processing.\n- `withHeader` - Adds a header to the context.\n- `withHeaders` - Adds headers to the context.\n- `withHttpClient` - Adds the `HttpClient` to use for making requests using the `fetch` handler.\n- `withHttpClientFactory` - Adds an `HttpClient` factory function to use for producing the `HttpClient`.\n- `withLogger` - Adds an\n  [`ILogger`](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.ilogger?view=dotnet-plat-ext-3.1)\n  for logging requests and responses.\n- `withLogLevel` - The log level\n  ([`LogLevel`](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loglevel?view=dotnet-plat-ext-3.1))\n  that the logging should be performed at. Oryx will disable logging for `LogLevel.None` and this is also the default\n  log level.\n- `withLogFormat` - Specify the log format of the log messages written.\n- `withLogMessage` - Log information about the given request supplying a user-specified message.\n- `withMethod` - with HTTP method. You can use GET, PUT, POST instead.\n- `withQuery` - Add URL query parameters\n- `withResponseType` - Sets the Accept header of the request.\n- `withTokenRenewer` - Enables refresh of bearer tokens without building a new context.\n- `withUrl` - Use the given URL for the request.\n- `withUrlBuilder` - Use the given URL builder for the request.\n- `withUrlBuilder` - Adds the URL builder to use. An URL builder constructs the URL for the `Request` part of the\n  context.\n\nIn addition there are several extension for decoding JSON and Protobuf responses:\n\n- `json` - Decodes the given `application/json` response into a user-specified type.\n- `protobuf` - - Decodes the given `application/protobuf` response into a Protobuf specific type.\n\nSee [JSON and Protobuf Content Handling](#json-and-protobuf-content-handling) for more information.\n\n### HTTP verbs\n\nThe HTTP verbs are convenience functions using the `withMethod` under the hood:\n\n- `GET` - HTTP get request\n- `PUT` - HTTP put request\n- `POST` - HTTP post request\n- `DELETE` - HTTP delete request\n- `OPTIONS` - HTTP options request\n\n## Composition\n\nThe real magic of Oryx is composition. The fact that everything is an `HttpHandler` makes it easy to compose HTTP\nhandlers together. You can think of them as Lego bricks that you can fit together. Two or more `HttpHandler` functions\nmay be composed together using the pipelining, i.e using the `|\u003e` operator. This enables you to compose your\nweb requests and decode the response, e.g as we do when listing Assets in the [Cognite Data Fusion\nSDK](https://github.com/cognitedata/cognite-sdk-dotnet/blob/master/Oryx.Cognite/src/Handler.fs):\n\n```fs\n    let list (query: AssetQuery) (source: HttpHandler\u003cunit\u003e) : HttpHandler\u003cItemsWithCursor\u003cAssetReadDto\u003e\u003e =\n        let url = Url +/ \"list\"\n\n        source\n        |\u003e POST\n        |\u003e withVersion V10\n        |\u003e withResource url\n        |\u003e withContent (() -\u003e new JsonPushStreamContent\u003cAssetQuery\u003e(query, jsonOptions))\n        |\u003e fetch\n        |\u003e withError decodeError\n        |\u003e json jsonOptions\n```\n\nThe function `list` is now also an `HttpHandler` and may be composed with other handlers to create complex chains\nfor doing multiple sequential or concurrent requests to a web service. And you can do this without having to worry\nabout error handling.\n\n## Retrying Requests\n\nSince Oryx is based on `HttpClient` from `System.Net.Http`, you may also use [Polly](https://github.com/App-vNext/Polly)\nfor handling resilience.\n\n## Concurrent and Sequential Handlers\n\nA `sequential` operator for running a list of HTTP handlers in sequence.\n\n```fs\nval sequential:\n    handlers     : seq\u003cHttpHandler\u003c'TResult\u003e\u003e\n                -\u003e HttpHandler\u003clist\u003c'TResult\u003e\u003e\n```\n\nAnd a `concurrent` operator that runs a list of HTTP handlers in parallel.\n\n```fs\nval concurrent:\n    handlers     : seq\u003cHttpHandler\u003c'TResult\u003e\u003e\n                -\u003e HttpHandler\u003clist\u003c'TResult\u003e\u003e\n```\n\nYou can also combine sequential and concurrent requests by chunking the request. The `chunk` handler uses `chunkSize`\nand `maxConcurrency` to decide how much will be done in parallel. It takes a list of items and a handler that transforms\nthese items into HTTP handlers. This is nice if you need to e.g read thousands of items from a web service in\nmultiple requests.\n\n```fs\nval chunk:\n   chunkSize     : int -\u003e\n   maxConcurrency: int -\u003e\n   handler       : (seq\u003c'TSource\u003e -\u003e HttpHandler\u003cseq\u003c'TResult\u003e\u003e) -\u003e\n   items         : seq\u003c'TSource\u003e\n                -\u003e HttpHandler\u003cseq\u003c'TResult\u003e\u003e\n```\n\nNote that chunk will fail if one of the inner requests fails so for e.g a writing scenario you most likely want to\ncreate your own custom chunk operator that has different error semantics. If you write such operators then feel free to\nopen a PR so we can include them in the library.\n\n## Error handling\n\nTo produce a custom error response you can use the `withError` handler _after_ e.g `fetch`. The supplied `errorHandler`\nis given full access the the `HttpResponse` and the `HttpContent` and may produce a custom `exception`.\n\n```fs\nval withError:\n   errorHandler  : (HttpResponse -\u003e HttpContent -\u003e Task\u003cexn\u003e) -\u003e\n   source        : HttpHandler\u003cHttpContent\u003e -\u003e\n   next          : IAsyncNext\u003cHttpContext,HttpContent\u003e\n                -\u003e Task\u003cunit\u003e\n```\n\nIt's also possible to catch errors using the `catch` handler _before_ e.g `fetch`. The function takes an `errorHandler`\nthat is given the returned error and produces a new `HttpHandler` that may then decide to transform the error and\ncontinue processing or fail with an error. This is very helpful when a failed request not necessarily means an error,\ne.g if you need to check if an object with a given id exists at the server. It's not possible to catch a\n`PanicException`, so wrapping an exception in a `PanicException` can be used if you need to signal a fatal error and\nbypass a `catch` operator.\n\n```fs\nval catch:\n   errorHandler  : (HttpContext -\u003e exn -\u003e HttpHandler\u003c'TSource\u003e) -\u003e\n   source        : HttpHandler\u003c'TSource\u003e -\u003e\n                -\u003e HttpHandler\u003c'TSource\u003e -\u003e\n\n```\n\nA `choose` operator takes a list of HTTP handlers and tries each of them until one of them succeeds. The `choose`\noperator will record every error that happens except for `SkipException` that can be used for skipping to the next\nhandler. Other errors will be recorded. If multiple error happens they will be provided as an `AggregateException`. If\nyou need break out of `choose` and force an exception without skipping to the next handler you can use the\n`PanicException`.\n\n```fs\nval choose:\n   Handlers    : list\u003c(HttpHandler\u003c'TSource\u003e -\u003eHttpHandler\u003c'TResult\u003e)\u003e -\u003e\n   source      : HttpHandler\u003c'TSource\u003e\n              -\u003e HttpHandler\u003c'TResult\u003e\n\n```\n\n## JSON and Protobuf Content Handling\n\nOryx can serialize (and deserialize) content using:\n\n- [`System.Text.Json`](https://docs.microsoft.com/en-us/dotnet/api/system.text.json?view=netcore-3.1)\n- [`Newtonsoft.Json`](https://www.newtonsoft.com/json)\n- [`Thoth.Json.Net`](https://github.com/thoth-org/Thoth.Json.Net)\n- [`Google.Protobuf`](https://developers.google.com/protocol-buffers)\n\n### System.Text.Json\n\nSupport for `System.Text.Json` is made available using the\n[`Oryx.SystemTextJson`](https://www.nuget.org/packages/Oryx.SystemTextJson/) extension.\n\nThe `json` decode HTTP handler takes a `JsonSerializerOptions` to decode the response into user-defined type of `'T`.\n\n```fs\nval json:\n   options: JsonSerializerOptions\n         -\u003e HttpHandler\u003c'TResult\u003e\n```\n\nContent can be handled using `type JsonPushStreamContent\u003c'a\u003e (content : 'T, options : JsonSerializerOptions)`.\n\n### Newtonsoft.Json\n\nSupport for `Newtonsoft.Json` is made available using the\n[`Oryx.NewtonsoftJson`](https://www.nuget.org/packages/Oryx.NewtonsoftJson/) extension.\n\nThe `json` decode HTTP handler decodes the response into a user-defined type of `'TResult`.\n\n```fs\nval json : HttpHandler\u003cHttpContent,'TResult\u003e\n```\n\nContent can be handled using `type JsonPushStreamContent (content : JToken)`.\n\n### Thoth.Json.Net\n\nSupport for `Thoth.Json.Net` is made available using the\n[`Oryx.ThothJsonNet`](https://www.nuget.org/packages/Oryx.ThothJsonNet/) extension.\n\nThe `json` decoder takes a `Decoder` from `Thoth.Json.Net` to decode the response into a user-defined type of `'T`.\n\n```fs\nval json:\n   decoder: Decoder\u003c'TResult\u003e\n         -\u003e HttpHandler\u003c'TResult\u003e\n```\n\nContent can be handled using `type JsonPushStreamContent (content : JsonValue)`.\n\n### Protobuf\n\nProtobuf support is made available using the [`Oryx.Protobuf`](https://www.nuget.org/packages/Oryx.ThothJsonNet/)\nextension.\n\nThe `protobuf` decoder takes a `Stream -\u003e 'T` usually generated by `Google.Protobuf` to decode the response into user\ndefined type of `'T`.\n\n```fs\nval protobuf: (System.IO.Stream -\u003e 'TResult) -\u003e  HttpHandler\u003cSystem.Net.Http.HttpContent\u003e -\u003e HttpHandler\u003c'TResult\u003e\n```\n\nBoth encode and decode uses streaming all the way so no large strings or arrays will be allocated in the process.\n\nContent can be handled using `type ProtobufPushStreamContent (content : IMessage)`.\n\n## Computational Expression Builder\n\nWorking with `HttpContext` objects can be a bit painful. To make it simpler to handle multiple requests using handlers\nyou can use the `req` builder that will let you work with the `content` and hide the complexity of both the `Context`\nand the `HttpNext`.\n\n```fs\nhttp {\n    let! assetDto = Assets.Entity.get key\n\n    let asset = assetDto |\u003e Asset.FromAssetReadDto\n    if expands.Contains(\"Parent\") \u0026\u0026 assetDto.ParentId.IsSome then\n        let! parentDto = Assets.Entity.get assetDto.ParentId.Value\n        let parent = parentDto |\u003e Asset.FromAssetReadDto\n        let expanded = { asset with Parent = Some parent }\n        return expanded\n    else\n        return asset\n}\n```\n\nThe request may then be composed with other handlers, e.g chunked, retried, and/or logged.\n\nTo run a handler you can use the `runAsync` function.\n\n```fs\nval runAsync:\n   handler: HttpHandler\u003c'TResult\u003e\n         -\u003e Task\u003cResult\u003c'TResult,exn\u003e\u003e\n```\n\nor the unsafe version that may throw exceptions:\n\n```fs\nval runUnsafeAsync:\n   handler: HttpHandler\u003cunit,'TResult\u003e\n         -\u003e Task\u003c'TResult\u003e\n```\n\n## Logging and Metrics\n\nOryx supports logging using the logging handlers. To setup for logging you first need to enable logging in the context\nby both setting a logger of type `ILogger`\n([Microsoft.Extensions.Logging](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.ilogger?view=dotnet-plat-ext-3.1))\nand the logging level to something higher than `LogLevel.None`.\n\n```fs\nval withLogger : (logger: ILogger) -\u003e (context: EmptyContext) -\u003e (context: EmptyContext)\nval withLogLevel : (logLevel: LogLevel) -\u003e (context: EmptyContext) -\u003e (context: EmptyContext)\nval withLogFormat (format: string) (context: EmptyContext) -\u003e (context: EmptyContext)\n```\n\nThe default format string is:\n\n`\"Oryx: {Message} {HttpMethod} {Uri}\\n{RequestContent}\\n{ResponseContent}\"`\n\nYou can also use a custom log format string by setting the log format using `withLogFormat`. The available place holders\nyou may use are:\n\n- `Elapsed` - The elapsed request time for `fetch` in milliseconds.\n- `HttpMethod` - The HTTP method used, i.e `PUT`, `GET`, `POST`, `DELETE` or `PATCH`.\n- `Message` - A user-supplied message using `logWithMessage`.\n- `ResponseContent` - The response content received. Must implement `ToString` to give meaningful output.\n- `RequestContent` - The request content being sent. Must implement `ToString` to give meaningful output.\n- `ResponseHeader[key]` - The response header received, replace `key` with the name of the header field.\n- `Url` - The URL used for fetching.\n\n**Note:** Oryx will not call `.ToString ()` but will hand it over to the `ILogger` for the actual string interpolation,\ngiven that the message will end up being logged.\n\nNOTE: The logging handler (`log`) do not alter the types of the pipeline and may be composed anywhere. But to give\nmeaningful output they should be composed after fetching (`fetch`). To log errors, the log handler should be placed\nafter error handling (`withError`), and to log decoded responses the log handler should be placed after the decoder (i.e\n`json`).\n\n```fs\nval withLogger:\n   logger: ILogger -\u003e\n   source: HttpHandler\u003c'TSource\u003e\n        -\u003e HttpHandler\u003c'TSource\u003e\n\nval withLogLevel:\n   logLevel: LogLevel -\u003e\n   source  : HttpHandler\u003c'TSource\u003e\n          -\u003e HttpHandler\u003c'TSource\u003e\n\nval withLogMessage:\n   msg : string              -\u003e\n   next: IHttpNext\u003c'TSource\u003e\n      -\u003e IHttpNext\u003c'TSource\u003e\n\nval withLogMessage:\n   msg   : string -\u003e\n   source: HttpHandler\u003c'TSource\u003e\n        -\u003e HttpHandler\u003c'TSource\u003e\n```\n\nOryx may also emit metrics using the `IMetrics` interface (Oryx specific) that you can use with e.g Prometheus.\n\n```fs\ntype IMetrics =\n    abstract member Counter : metric: string -\u003e labels: IDictionary\u003cstring, string\u003e -\u003e increase: int64 -\u003e unit\n    abstract member Gauge : metric: string -\u003e labels: IDictionary\u003cstring, string\u003e -\u003e value: float -\u003e unit\n```\n\nThe currently defined Metrics are:\n\n- `Metric.FetchInc` - (\"MetricFetchInc\") The increase in the number of fetches when using the `fetch` handler.\n- `Metric.FetchErrorInc` - (\"MetricFetchErrorInc\"). The increase in the number of fetch errors when using the `fetch`\n  handler.\n- `Metrics.FetchRetryInc` - (\"MetricsFetchRetryInc\"). The increase in the number of retries when using the `retry`\n  handler.\n- `Metric.FetchLatencyUpdate` - (\"MetricFetchLatencyUpdate\"). The update in fetch latency (in milliseconds) when using\n  the `fetch` handler.\n- `Metric.DecodeErrorInc` - (\"Metric.DecodeErrorInc\"). The increase in decode errors when using a `json` decode handler.\n\nLabels are currently not set but are added for future use, e.g setting the error code for fetch errors etc.\n\n## Extending Oryx\n\nIt's easy to extend Oryx with your own HTTP handlers. Everything is functions, so you can easily add your own HTTP handlers.\n\n### Custom HTTP Handlers\n\nCustom HTTP handlers may e.g populate the context, make asynchronous web requests and parse response content. HTTP\nhandlers are functions that takes an `HttpHandler'TSource\u003e`, and returns an `HttpHandler\u003c'TSource\u003e`. Example:\n\n```fs\nlet withResource (resource: string) (source: HttpHandler\u003c'TSource): HttpHandler\u003c'TSource\u003e =\n    source\n    |\u003e update (fun ctx -\u003e\n        { ctx with\n            Request =\n                { ctx.Request with Items = ctx.Request.Items.Add(PlaceHolder.Resource, Value.String resource) } })\n```\n\n```fs\n/// Parse response stream to a user specified type synchronously.\nlet parse\u003c'TResult\u003e (parser: Stream -\u003e 'TResult) (source: HttpHandler\u003cHttpContent\u003e) : HttpHandler\u003c'TResult\u003e =\n    fun next -\u003e\n        { new IHttpNext\u003cHttpContent\u003e with\n            member _.OnSuccessAsync(ctx, content: HttpContent) =\n                task {\n                    let! stream = content.ReadAsStreamAsync()\n\n                    try\n                        let item = parser stream\n                        return! next.OnSuccessAsync(ctx, item)\n                    with\n                    | ex -\u003e\n                        ctx.Request.Metrics.Counter Metric.DecodeErrorInc ctx.Request.Labels 1L\n                        raise ex\n                }\n\n            member _.OnErrorAsync(ctx, exn) = next.OnErrorAsync(ctx, exn)\n            member _.OnCancelAsync(ctx) = next.OnCancelAsync(ctx) }\n        |\u003e source\n\n```\n\n## What is new in Oryx v5\n\nOryx v5 continues to simplify the HTTP handlers by reducing the number of generic parameters so you only need to specify\nthe type the handler is producing (not what it's consuming). The `HttpHandler` have also been reduced to plain functions.\n\n```fs\ntype IHttpNext\u003c'TSource\u003e =\n    abstract member OnSuccessAsync: ctx: HttpContext * content: 'TSource -\u003e Task\u003cunit\u003e\n    abstract member OnErrorAsync: ctx: HttpContext * error: exn -\u003e Task\u003cunit\u003e\n    abstract member OnCancelAsync: ctx: HttpContext -\u003e Task\u003cunit\u003e\n\n\ntype HttpHandler\u003c'TSource\u003e = IHttpNext\u003c'TSource\u003e -\u003e Task\u003cunit\u003e\n```\n\nThe great advantage is that you can now use the normal pipe operator (`|\u003e`) instead of Kleisli composition (`\u003e=\u003e`).\nwhich will give you better type hinting and debugging in most IDEs.\n\n```fs\nuse client = new HttpClient()\n\nlet common =\n    httpRequest\n    |\u003e GET\n    |\u003e withHttpClient client\n    |\u003e withUrl Url\n    |\u003e cache\n\nlet! result =\n    request common \"F#\"\n    |\u003e runUnsafeAsync\nprintfn $\"Result: {result}\"\n\nlet! result =\n    request common \"C#\"\n    |\u003e runUnsafeAsync\n```\n\n## What is new in Oryx v4\n\n- A `validate` handler has been added that can validate the passing\n  content using a predicate function. If the predicate fails then the\n  error path will be taken.\n\n- A `protect` handler has been added that protects the pipeline from\n  exceptions (thrown upwards) and protocol error with regards to error /\n  complete handling. E.g not allowed to call `OnNextAsync()` after\n  `OnErrorAsync()`.\n\n- The semantics of the `choose` operator have been modified so it\n  continues processing the next handler if the current handler produces\n  error i.e `OnErrorAsync`. Previously it was triggered by not calling\n  `.OnNextAsync()`\n\n- Oryx v4 makes the content non-optional to simplify the HTTP handlers.\n\n```fs\ntype IHttpNext\u003c'TSource\u003e =\n    abstract member OnNextAsync: ctx: HttpContext * content: 'TSource -\u003e Task\u003cunit\u003e\n    abstract member OnErrorAsync: ctx: HttpContext * error: exn -\u003e Task\u003cunit\u003e\n    abstract member OnCompletedAsync: ctx: HttpContext -\u003e Task\u003cunit\u003e\n\ntype HttpHandler\u003c'TSource, 'TResult\u003e =\n    abstract member Subscribe: next: IHttpNext\u003c'TResult\u003e -\u003e IHttpNext\u003c'TSource\u003e\n\ntype HttpHandler\u003c'TSource\u003e = HttpHandler\u003c'TSource, 'TSource\u003e\n```\n\n## What is new in Oryx v3\n\nOryx v3 will significantly simplify the typing of HTTP handlers by:\n\n1. Be based on Async Observables instead of result returning continuations. The result returning continuations were\n   problematic in the sense that they both push values down in addition to returning (pulling) async values up, thus\n   each HTTP handler needed to care about the input (`TSource`), output (`TNext`), the final result (`TResult`) and\n   error (`TError`) types. By never returning anything (`Task\u003cunit\u003e`) we get rid of the annoying return type.\n2. Error type (`'TError`) is now simply an exception (`exn`).\n3. Core logic refactored into a generic middleware (that can be reused for other purposes).\n\nThis change effectively makes Oryx an Async Observable (with context):\n\n```fs\ntype IHttpNext\u003c'TSource\u003e =\n    abstract member OnNextAsync: ctx: HttpContext * ?content: 'TSource -\u003e Task\u003cunit\u003e\n    abstract member OnErrorAsync: ctx: HttpContext * error: exn -\u003e Task\u003cunit\u003e\n    abstract member OnCompletedAsync: ctx: HttpContext -\u003e Task\u003cunit\u003e\n\ntype IHttpHandler\u003c'TSource, 'TResult\u003e =\n    abstract member Subscribe: next: IHttpNext\u003c'TResult\u003e -\u003e IHttpNext\u003c'TSource\u003e\n\ntype IHttpHandler\u003c'TSource\u003e = IHttpHandler\u003c'TSource, 'TSource\u003e\n```\n\nThe difference from observables is that the `IHttpHandler` subscribe method returns another \"observer\" (`IHttpNext`)\ninstead of a `Disposable` and this observable is the side-effect that injects values into the pipeline (`Subject`). The\ncomposition stays exactly the same so all HTTP pipelines will works as before. The typing just gets simpler to handle.\n\nThe custom error type (`TError`) has also been removed and we now use plain exceptions for all errors. Any custom error\ntypes now needs to be an Exception subtype.\n\nThe `retry` operator has been deprecated. Use [Polly](https://github.com/App-vNext/Polly) instead. It might get back in\na later release but the observable pattern makes it hard to retry something upstream.\n\nA `choose` operator has been added. This operator takes a list of HTTP handlers and tries each of them until one of\nthem succeeds.\n\n## What is new in Oryx v2\n\nWe needed to change Oryx to preserve any response headers and status-code that got lost after decoding the response\ncontent into a custom type. The response used to be a custom `'T` so it could not hold any additional info.\nWe changed this so the response is now an `HttpResponse` type:\n\n```fs\ntype HttpResponse\u003c'T\u003e =\n    {\n        /// Response content\n        Content: 'T\n        /// Map of received headers\n        Headers: Map\u003cstring, seq\u003cstring\u003e\u003e\n        /// Http status code\n        StatusCode: HttpStatusCode\n        /// True if response is successful\n        IsSuccessStatusCode: bool\n        /// Reason phrase which typically is sent by servers together with the status code\n        ReasonPhrase: string\n    }\n\n    /// Replaces the content of the HTTP response.\n    member x.Replace\u003c'TResult\u003e(content: 'TResult): HttpResponse\u003c'TResult\u003e =\n        {\n            Content = content\n            StatusCode = x.StatusCode\n            IsSuccessStatusCode = x.IsSuccessStatusCode\n            Headers = x.Headers\n            ReasonPhrase = x.ReasonPhrase\n        }\n\ntype Context\u003c'T\u003e =\n    {\n        Request: HttpRequest\n        Response: HttpResponse\u003c'T\u003e\n    }\n```\n\n## Upgrade from Oryx v4 to v5\n\nThe context builders are gone. In Oryx v5 there is only HTTP handlers (`HttpHandler`). This means that there is only one\nway to build and transform the context. This might seem inefficient when you need to reuse the same part of the context\nfor multiple requests. The way to handle this is to use the `cache` handler.\n\n## Upgrade from Oryx v3 to v4\n\nThe `throw` operator have been renamed to `fail`. The `throw` operator\nis still available but will give an obsoleted warning.\n\nThe content used through the handler pipeline is now non-optional. Thus\ncustom code such as:\n\n```fs\nlet withResource (resource: string): HttpHandler\u003c'TSource\u003e =\n    { new IHttpHandler\u003c'TSource, 'TResult\u003e with\n        member _.Subscribe(next) =\n            { new IHttpNext\u003c'TSource\u003e with\n                member _.OnNextAsync(ctx, ?content) =\n                    next.OnNextAsync(\n                        { ctx with\n                            Request =\n                                { ctx.Request with\n                                    Items = ctx.Request.Items.Add(PlaceHolder.Resource, String resource)\n                                }\n                        },\n                        ?content = content\n                    )\n\n                member _.OnErrorAsync(ctx, exn) = next.OnErrorAsync(ctx, exn)\n                member _.OnCompletedAsync() = next.OnCompletedAsync()\n            }}\n```\n\nNeeds to be refactored to:\n\n```fs\nlet withResource (resource: string): HttpHandler\u003c'TSource\u003e =\n    { new HttpHandler\u003c'TSource, 'TResult\u003e with\n        member _.Subscribe(next) =\n            { new IHttpNext\u003c'TSource\u003e with\n                member _.OnNextAsync(ctx, content) =\n                    next.OnNextAsync(\n                        { ctx with\n                            Request =\n                                { ctx.Request with\n                                    Items = ctx.Request.Items.Add(PlaceHolder.Resource, String resource)\n                                }\n                        },\n                        content = content\n                    )\n\n                member _.OnErrorAsync(ctx, exn) = next.OnErrorAsync(ctx, exn)\n                member _.OnCompletedAsync() = next.OnCompletedAsync()\n            }}\n```\n\n## Upgrade from Oryx v2 to v3\n\nOryx v3 is mostly backwards compatible with v2. Your chains of operators will for most part look and work exactly the\nsame. There are however some notable changes:\n\n- `Context` have been renamed to `HttpContext`.\n- `HttpHandler` have been renamed `HttpHandler`. This is because `HttpHandler` is now an interface.\n- The `retry` operator has been deprecated for now. Use [Polly](https://github.com/App-vNext/Polly) instead.\n- The `catch` operator needs to run **after** the error producing operator e.g `fetch` (not before). This is because\n  Oryx v3 pushes results \"down\" instead of returning them \"up\" the chain of operators. The good thing with this change\n  is that a handler can now continue processing the rest of the pipeline after catching an error. This was not possible\n  in v2 / v1 where the `catch` operator had to abort processing and produce a result.\n- The log operator needs to be placed **after** the handler you want it to log. E.g to log JSON decoded data you need to\n  place it after `json`.\n- Http handlers take 2 generic types instead of 4. E.g `fetch\u003c'TSource, 'TNext, 'TResult, 'TError\u003e` now becomes\n  `fetch\u003c'TSource, 'TNext\u003e` and the last two types can simply be removed from your code.\n- `ResponseError` is gone. You need to sub-class an exception instead. This means that the `'TError' type is also gone\n  from the handlers.\n- Custom context builders do not need any changes except renaming `Context` to `HttpContext`.\n- Custom HTTP handlers must be refactored. Instead of returning a result (Ok/Error) the handler needs to push down the\n  result either using the Ok path `next.OnNextAsync()` or fail with an error `next.OnErrorAsync()`. This is very similar\n  to e.g Reactive Extensions (Rx) `OnNext` / `OnError`. E.g:\n\n```fs\n let withResource (resource: string) (next: NextFunc\u003c_,_\u003e) (context: HttpContext) =\n    next { context with Request = { context.Request with Items = context.Request.Items.Add(PlaceHolder.Resource, String resource) } }\n```\n\nNeeds to be refactored to:\n\n```fs\nlet withResource (resource: string): HttpHandler\u003c'TSource\u003e =\n    { new HttpHandler\u003c'TSource, 'TResult\u003e with\n        member _.Subscribe(next) =\n            { new IHttpNext\u003c'TSource\u003e with\n                member _.OnNextAsync(ctx, ?content) =\n                    next.OnNextAsync(\n                        { ctx with\n                            Request =\n                                { ctx.Request with\n                                    Items = ctx.Request.Items.Add(PlaceHolder.Resource, String resource)\n                                }\n                        },\n                        ?content = content\n                    )\n\n                member _.OnErrorAsync(ctx, exn) = next.OnErrorAsync(ctx, exn)\n                member _.OnCompletedAsync() = next.OnCompletedAsync()\n            }}\n```\n\nIt's a bit more verbose, but the hot path of the code is mostly the same as before.\n\n## Upgrade from Oryx v1 to v2\n\nThe context is now initiated with a content `'T` of `unit`. E.g your custom HTTP handlers that is used before `fetch`\nneed to be rewritten from using a `'TSource` of `HttpResponseMessage` to `unit` e.g:\n\n```diff\n- let withLogMessage (msg: string) (next: HttpFunc\u003cHttpResponseMessage, 'T, 'TError\u003e) (context: EmptyContext) =\n+ let withLogMessage (msg: string) (next: HttpFunc\u003cunit, 'T, 'TError\u003e) (context: EmptyContext) =\n```\n\nThere is now also a `runAsync'` overload that returns the full `HttpResponse` record i.e:\n`Task\u003cResult\u003cHttpResponse\u003c'TResult\u003e, HandlerError\u003c'TError\u003e\u003e\u003e`. This makes it possible to get the response status-code,\nresponse-headers etc even after decoding of the content. This is great when using Oryx for a web-proxy or protocol\nconverter where you need to pass on any response-headers.\n\n## Using Oryx with Giraffe\n\nYou can use Oryx within your Giraffe server if you need to make HTTP requests to other services. But then you must be\ncareful about the order when opening namespaces so you know if you use the `\u003e=\u003e` operator from Oryx or Giraffe. Usually,\nthis will not be a problem since the Giraffe `\u003e=\u003e` will be used within your e.g `WebApp.fs` or `Server.fs`, while the\nOryx `\u003e=\u003e` will be used within the controller handler function itself e.g `Controllers/Index.fs`. Thus just make sure\nyou open Oryx after Giraffe in the controller files.\n\n```fs\nopen Giraffe\nopen Oryx\n```\n\n## Libraries using Oryx:\n\n- [Cognite SDK .NET](https://github.com/cognitedata/cognite-sdk-dotnet)\n- [oryx-netatmo](https://github.com/dbrattli/oryx-netatmo) (Currently a bit outdated)\n\n## Code of Conduct\n\nThis project follows https://www.contributor-covenant.org, see our [Code of\nConduct](https://github.com/cognitedata/oryx/blob/master/CODE_OF_CONDUCT.md).\n\n## License\n\nApache v2, see [LICENSE](https://github.com/cognitedata/oryx/blob/master/LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcognitedata%2Foryx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcognitedata%2Foryx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcognitedata%2Foryx/lists"}