![]() |
VOOZH | about |
dotnet add package Graeae.Models --version 0.3.5
NuGet\Install-Package Graeae.Models -Version 0.3.5
<PackageReference Include="Graeae.Models" Version="0.3.5" />
<PackageVersion Include="Graeae.Models" Version="0.3.5" />Directory.Packages.props
<PackageReference Include="Graeae.Models" />Project file
paket add Graeae.Models --version 0.3.5
#r "nuget: Graeae.Models, 0.3.5"
#:package Graeae.Models@0.3.5
#addin nuget:?package=Graeae.Models&version=0.3.5Install as a Cake Addin
#tool nuget:?package=Graeae.Models&version=0.3.5Install as a Cake Tool
NOTICE This library was built as a proof-of-concept to show how JsonSchema.Net could be used to support the more commonly used OpenApi.Net.
👁 Build & Test
👁 Percentage of issues still open
👁 Average time to resolve an issue
👁 License
👁 alternate text is missing from this package README image
👁 alternate text is missing from this package README image
OpenAPI models for System.Text.Json. Supports specification versions v3.0.x & v3.1.
This project is supported by the json-everything project:
Graeae (pronounced "gray-eye") is the collective name of the sisters in Greek mythology who share an eye, often referred to as the Fates. Because these sisters share an eye, there must be some common interface between the sisters and the eye.
In terms of web development the eye is an API, the sisters are clients, and that common interface is an OpenAPI document.
The library supports OpenAPI v3.1 (de)serialization out of the box.
// read from a file
var yamlText = File.ReadAllText("openapi.yaml");
var openApiDoc = YamlSerializer.Deserialize<OpenApiDocument>(yamlText);
// verify and resolve references
openApiDoc.Initialize();
// back to text
var asText = YamlSerializer.Serialize(openApiDoc);
HINT Because YAML is a superset of JSON, the YamlSerializer class also supports JSON files, so you don't need to check which format the file is in.
During initialization, if the document contains references that cannot be resolved, a RefResolutionException will be thrown.
To validate a payload against a JSON Schema within your OpenAPI document, use the .EvaluatePayload() extension method.
var payload = JsonNode.Parse("<content from HttpRequest or elsewhere>");
var schemaComponentLocation = JsonPointer.Parse("/pointer/to/schema");
var results = openApiDoc.EvaluatePayload(payload, schemaComponentLocation);
If your schema is under the paths section in the OpenAPI document, it may be easier and more readable to get the pointer using the JsonPointer.Create() method and passing the individual segments. This avoids having to escape forward slashes /.
var schemaInPathLocation = JsonPointer.Create("paths", "/pets/{petId}", "get", "parameters", 0 , "schema");
Of course, if the path is well-known at dev time, you can also just access it directly:
var schemaInPath = openApiDoc.Paths["/pets/{petId}"].Get.Parameters[0].Schema;
var results = schemaInPath.Evaluate(payload);
To support OpenAPI v3.0.x, you'll need to enable JSON Schema draft 4 support first. To do that, add this to your app initialization:
using Graeae.Models.SchemaDraft4;
Draft4Support.Enable();
When building schemas, you may find that the default extensions for JsonSchema.Net's JsonSchemaBuilder are insufficient since it only supports draft 6 and after out of the box. For example, in draft 4, exclusiveMinimum takes a boolean, but in draft 6 and after, it needs to be a number.
To support these differences, additional extension methods have been added to support specific draft 4 and OpenAPI functionality. They're available for when they're needed, but otherwise the extensions that come with JsonSchema.Net will work.
| Extension | Function |
|---|---|
.OasId(Uri)<br>.OasId(string) |
Adds the id (no $) keyword |
.OasType(SchemaValueType) |
Adds a type keyword variant that supports the OAS notion |
.Nullable(bool) |
Adds the nullable keyword |
.ExclusiveMaximum(bool) |
Adds a boolean-valued exclusiveMaximum keyword |
.ExclusiveMinimum(bool) |
Adds a boolean-valued exclusiveMinimum keyword |
Lastly, when validating a draft 4 schema, it's important to specify that it's draft 4 using the EvaluationsOptions.EvaluateAs property.
var results = schema.Evaluate(instance, new EvaluationOptions
{
OutputFormat = OutputFormat.List,
EvaluateAs = Draft4Support.Draft4Version
});
The OpenApiDocument.Initialize() method will scan the document model and attempt to resolve any references. References to locations within the document are automatically supported, however references to external locations are not supported by default.
To enable external reference resolution, you'll need to set the Ref.Fetch function property. This static property is a function which takes a single Uri argument and returns an Task<JsonNode?> which will then be deserialized into the appropriate model.
A (very) basic implementation that supports http(s): URIs is provided as the Ref.FetchJson() method. It also supports YAML content. It's likely you'll want to provide your own method for production scenarios, but this will get you started.
Ref.Fetch = Ref.FetchJson;
The models are read/write to make it simple to define an OpenAPI document in code.
<details> <summary>Expand to see a code sample</summary>
(from https://github.com/OAI/OpenAPI-Specification/blob/main/examples/v3.0/petstore.yaml)
YAML:
openapi: "3.0.0"
info:
version: 1.0.0
title: Swagger Petstore
license:
name: MIT
servers:
- url: http://petstore.swagger.io/v1
paths:
/pets:
get:
summary: List all pets
operationId: listPets
tags:
- pets
parameters:
- name: limit
in: query
description: How many items to return at one time (max 100)
required: false
schema:
type: integer
maximum: 100
format: int32
responses:
'200':
description: A paged array of pets
headers:
x-next:
description: A link to the next page of responses
schema:
type: string
content:
application/json:
schema:
$ref: "#/components/schemas/Pets"
default:
description: unexpected error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
post:
summary: Create a pet
operationId: createPets
tags:
- pets
responses:
'201':
description: Null response
default:
description: unexpected error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/pets/{petId}:
get:
summary: Info for a specific pet
operationId: showPetById
tags:
- pets
parameters:
- name: petId
in: path
required: true
description: The id of the pet to retrieve
schema:
type: string
responses:
'200':
description: Expected response to a valid request
content:
application/json:
schema:
$ref: "#/components/schemas/Pet"
default:
description: unexpected error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
components:
schemas:
Pet:
type: object
required:
- id
- name
properties:
id:
type: integer
format: int64
name:
type: string
tag:
type: string
Pets:
type: array
maxItems: 100
items:
$ref: "#/components/schemas/Pet"
Error:
type: object
required:
- code
- message
properties:
code:
type: integer
format: int32
message:
type: string
Equivalent C#:
var document = new OpenApiDocument("3.0.0",
new("Swagger Petstore", "1.0.0")
{
License = new("MIT")
}
)
{
Servers = new []
{
new Server("http://petstore.swagger.io/v1")
},
Paths = new()
{
["/pets"] = new()
{
Get = new()
{
Summary = "List all pets",
OperationId = "listPets",
Tags = new []{"pets"},
Parameters = new []
{
new Parameter("limit", ParameterLocation.Query)
{
Description = "How many items to return at one time (max 100)",
Required = false,
Schema = new JsonSchemaBuilder()
.Type(SchemaValueType.Integer)
.Maximum(100)
.Format(Formats.Int32)
}
},
Responses = new()
{
[HttpStatusCode.OK] = new("A paged array of pets")
{
Headers = new()
{
["x-next"] = new ()
{
Description = "A link to the next page of responses",
Schema = new JsonSchemaBuilder().Type(SchemaValueType.String)
}
},
Content = new()
{
["application/json"] = new()
{
Schema = Ref.To.Schema("Pets")
}
}
},
Default = new("unexpected error")
{
Content = new()
{
["application/json"] = new()
{
Schema = Ref.To.Schema("Error")
}
}
}
}
},
Post = new()
{
Summary = "Create a pet",
OperationId = "createPets",
Tags = new []{"pets"},
Responses = new()
{
[HttpStatusCode.Created] = new("Null response"),
Default = new("unexpected error")
{
Content = new(){
["application/json"] = new()
{
Schema = Ref.To.Schema("Error")
}
}
}
}
}
},
["/pets/{petId}"] = new()
{
Get = new()
{
Summary = "Info for a specific pet",
OperationId = "showPetById",
Tags = new []{"pets"},
Parameters = new []
{
new Parameter("petId", ParameterLocation.Path)
{
Required = true,
Description = "The id of the pet to retrieve",
Schema = new JsonSchemaBuilder()
.Type(SchemaValueType.String)
}
},
Responses = new()
{
[HttpStatusCode.OK] = new("Expected response to a valid request")
{
Content = new()
{
["application/json"] = new()
{
Schema = Ref.To.Schema("Pet")
}
}
},
Default = new("unexpected error")
{
Content = new()
{
["application/json"] = new()
{
Schema = Ref.To.Schema("Error")
}
}
}
}
}
}
},
Components = new()
{
Schemas = new()
{
["Pet"] = new JsonSchemaBuilder()
.Type(SchemaValueType.Object)
.Required("id", "name")
.Properties(
("id", new JsonSchemaBuilder()
.Type(SchemaValueType.Integer)
.Format(Formats.Int64)
),
("name", new JsonSchemaBuilder().Type(SchemaValueType.String)),
("tag", new JsonSchemaBuilder().Type(SchemaValueType.String))
),
["Pets"] = new JsonSchemaBuilder()
.Type(SchemaValueType.Array)
.MaxItems(100)
.Items(Ref.To.Schema("Pet")),
["Error"] = new JsonSchemaBuilder()
.Type(SchemaValueType.Object)
.Required("code", "message")
.Properties(
("code", new JsonSchemaBuilder()
.Type(SchemaValueType.Integer)
.Format(Formats.Int32)
),
("message", new JsonSchemaBuilder().Type(SchemaValueType.String))
)
}
}
};
</details>
There are several more examples in the test project.
This project is in its infancy and is open for help and suggestions. Additional functionality such as code generation is planned as extension libraries.
Feel free to open issues & pull requests.
Remember to follow the and .
To chat about this project, please join me in Slack.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 net5.0 was computed. net5.0-windows net5.0-windows was computed. net6.0 net6.0 is compatible. net6.0-android net6.0-android was computed. net6.0-ios net6.0-ios was computed. net6.0-maccatalyst net6.0-maccatalyst was computed. net6.0-macos net6.0-macos was computed. net6.0-tvos net6.0-tvos was computed. net6.0-windows net6.0-windows was computed. net7.0 net7.0 was computed. net7.0-android net7.0-android was computed. net7.0-ios net7.0-ios was computed. net7.0-maccatalyst net7.0-maccatalyst was computed. net7.0-macos net7.0-macos was computed. net7.0-tvos net7.0-tvos was computed. net7.0-windows net7.0-windows was computed. net8.0 net8.0 is compatible. net8.0-android net8.0-android was computed. net8.0-browser net8.0-browser was computed. net8.0-ios net8.0-ios was computed. net8.0-maccatalyst net8.0-maccatalyst was computed. net8.0-macos net8.0-macos was computed. net8.0-tvos net8.0-tvos was computed. net8.0-windows net8.0-windows was computed. net9.0 net9.0 was computed. net9.0-android net9.0-android was computed. net9.0-browser net9.0-browser was computed. net9.0-ios net9.0-ios was computed. net9.0-maccatalyst net9.0-maccatalyst was computed. net9.0-macos net9.0-macos was computed. net9.0-tvos net9.0-tvos was computed. net9.0-windows net9.0-windows was computed. net10.0 net10.0 was computed. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 netcoreapp2.0 was computed. netcoreapp2.1 netcoreapp2.1 was computed. netcoreapp2.2 netcoreapp2.2 was computed. netcoreapp3.0 netcoreapp3.0 was computed. netcoreapp3.1 netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 netstandard2.0 is compatible. netstandard2.1 netstandard2.1 was computed. |
| .NET Framework | net461 net461 was computed. net462 net462 was computed. net463 net463 was computed. net47 net47 was computed. net471 net471 was computed. net472 net472 was computed. net48 net48 was computed. net481 net481 was computed. |
| MonoAndroid | monoandroid monoandroid was computed. |
| MonoMac | monomac monomac was computed. |
| MonoTouch | monotouch monotouch was computed. |
| Tizen | tizen40 tizen40 was computed. tizen60 tizen60 was computed. |
| Xamarin.iOS | xamarinios xamarinios was computed. |
| Xamarin.Mac | xamarinmac xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos xamarinwatchos was computed. |
Showing the top 1 NuGet packages that depend on Graeae.Models:
| Package | Downloads |
|---|---|
|
Graeae.AspNet
Asp.Net operational functionality using Graeae.Models |
This package is not used by any popular GitHub repositories.
[0.3.5](https://github.com/gregsdennis/Graeae/pull/14)
- Fixed an [issue](https://github.com/gregsdennis/Graeae/issues/13) where OAuth objects incorrectly required various URLs.
[0.3.4](https://github.com/gregsdennis/Graeae/commit/f3f6bb0c4ec29879f8fb24573900f51f61e0bfae) (v0.3.3 redacted)
- Added .Net Standard 2.0 support.
[0.3.2](https://github.com/gregsdennis/Graeae/pull/11)
- Fixed a bug that `Draft4Support.Enable()` crashed when called multiple times.
[0.3.1](https://github.com/gregsdennis/Graeae/pull/10)
_v0.3.1.1 is a re-publish to include these release notes._
- Added `.EvaluatePayload()` extension on `OpenApiDocument`.
- Updated array members from `IEnumerable<T>` to `IReadOnlyList<T>` to enable indexer access.
[0.3.0](https://github.com/gregsdennis/Graeae/pull/6)
- Updated _JsonSchema.Net_.
- Set up for Native AOT.
[0.2.1](https://github.com/gregsdennis/Graeae/pull/4)
Automatically register draft 4 `type` keyword.