mirror of
https://github.com/Smaug123/gitea-repo-config
synced 2025-10-05 07:28:40 +00:00
Express discriminated union in JSON schema (#7)
This commit is contained in:
@@ -111,8 +111,21 @@ type GiteaConfig =
|
||||
|
||||
static member internal OfSerialised (s : SerialisedGiteaConfig) =
|
||||
{
|
||||
GiteaConfig.Users = s.Users |> Map.map (fun _ -> UserInfo.OfSerialised)
|
||||
Repos = s.Repos |> Map.map (fun _ -> Map.map (fun _ -> Repo.OfSerialised))
|
||||
GiteaConfig.Users =
|
||||
s.Users
|
||||
|> Seq.map (fun (KeyValue (user, info)) -> user, UserInfo.OfSerialised info)
|
||||
|> Map.ofSeq
|
||||
Repos =
|
||||
s.Repos
|
||||
|> Seq.map (fun (KeyValue (user, repos)) ->
|
||||
let repos =
|
||||
repos
|
||||
|> Seq.map (fun (KeyValue (repoName, repo)) -> repoName, Repo.OfSerialised repo)
|
||||
|> Map.ofSeq
|
||||
|
||||
user, repos
|
||||
)
|
||||
|> Map.ofSeq
|
||||
}
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
|
@@ -178,7 +178,6 @@ module Gitea =
|
||||
with e ->
|
||||
raise (AggregateException ($"Error migrating {user}:{r}", e))
|
||||
| None, None ->
|
||||
// TODO: express this in JsonSchema
|
||||
failwith $"You must supply exactly one of Native or GitHub for {user}:{r}."
|
||||
| Some _, Some _ ->
|
||||
failwith $"Repo {user}:{r} has both Native and GitHub set; you must set exactly one."
|
||||
|
@@ -1,87 +1,12 @@
|
||||
{
|
||||
"definitions": {
|
||||
"Nullable<SerialisedNativeRepo>": {
|
||||
"description": "If this repo is to be created natively on Gitea, the information about the repo.",
|
||||
"type": [
|
||||
"object",
|
||||
"null"
|
||||
],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"defaultBranch": {
|
||||
"description": "The default branch name for this repository, e.g. 'main'",
|
||||
"type": "string"
|
||||
},
|
||||
"private": {
|
||||
"description": "Whether this repository is a Gitea private repo",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"defaultBranch"
|
||||
]
|
||||
},
|
||||
"SerialisedRepo": {
|
||||
"type": [
|
||||
"object",
|
||||
"null"
|
||||
],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"description": {
|
||||
"description": "The text that will accompany this repository in the Gitea UI",
|
||||
"type": "string"
|
||||
},
|
||||
"gitHub": {
|
||||
"description": "If this repo is to sync from GitHub, the URI (e.g. 'https://github.com/Smaug123/nix-maui')",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"format": "uri"
|
||||
},
|
||||
"native": {
|
||||
"$ref": "#/definitions/Nullable<SerialisedNativeRepo>"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"description"
|
||||
]
|
||||
},
|
||||
"SerialisedUserInfo": {
|
||||
"type": [
|
||||
"object",
|
||||
"null"
|
||||
],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"isAdmin": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"website": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"format": "uri"
|
||||
},
|
||||
"visibility": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"email"
|
||||
]
|
||||
}
|
||||
},
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "SerialisedGiteaConfig",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"users",
|
||||
"repos"
|
||||
],
|
||||
"properties": {
|
||||
"users": {
|
||||
"type": "object",
|
||||
@@ -92,18 +17,102 @@
|
||||
"repos": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": [
|
||||
"object",
|
||||
"null"
|
||||
],
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/SerialisedRepo"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"users",
|
||||
"repos"
|
||||
]
|
||||
"definitions": {
|
||||
"SerialisedUserInfo": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"email"
|
||||
],
|
||||
"properties": {
|
||||
"isAdmin": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"website": {
|
||||
"type": [
|
||||
"null",
|
||||
"string"
|
||||
],
|
||||
"format": "uri"
|
||||
},
|
||||
"visibility": {
|
||||
"type": [
|
||||
"null",
|
||||
"string"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"SerialisedRepo": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "The text that will accompany this repository in the Gitea UI"
|
||||
},
|
||||
"gitHub": {
|
||||
"type": [
|
||||
"null",
|
||||
"string"
|
||||
],
|
||||
"description": "If this repo is to sync from GitHub, the URI (e.g. 'https://github.com/Smaug123/nix-maui')",
|
||||
"format": "uri"
|
||||
},
|
||||
"native": {
|
||||
"description": "If this repo is to be created natively on Gitea, the information about the repo.",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "null"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/SerialisedNativeRepo"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"oneOf": [
|
||||
{
|
||||
"required": [
|
||||
"description",
|
||||
"gitHub"
|
||||
]
|
||||
},
|
||||
{
|
||||
"required": [
|
||||
"description",
|
||||
"native"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"SerialisedNativeRepo": {
|
||||
"type": "object",
|
||||
"description": "Information about a repo that is to be created on Gitea without syncing from GitHub.",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"defaultBranch"
|
||||
],
|
||||
"properties": {
|
||||
"defaultBranch": {
|
||||
"type": "string",
|
||||
"description": "The default branch name for this repository, e.g. 'main'"
|
||||
},
|
||||
"private": {
|
||||
"type": "boolean",
|
||||
"description": "Whether this repository is a Gitea private repo"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,6 +1,7 @@
|
||||
namespace Gitea.Declarative
|
||||
|
||||
open System
|
||||
open System.Collections.Generic
|
||||
open System.ComponentModel
|
||||
open Newtonsoft.Json
|
||||
|
||||
@@ -51,7 +52,7 @@ type internal SerialisedUserInfo =
|
||||
type internal SerialisedGiteaConfig =
|
||||
{
|
||||
[<JsonProperty(Required = Required.Always)>]
|
||||
Users : Map<User, SerialisedUserInfo>
|
||||
Users : Dictionary<User, SerialisedUserInfo>
|
||||
[<JsonProperty(Required = Required.Always)>]
|
||||
Repos : Map<User, Map<RepoName, SerialisedRepo>>
|
||||
Repos : Dictionary<User, Dictionary<RepoName, SerialisedRepo>>
|
||||
}
|
||||
|
@@ -15,7 +15,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FsUnit" Version="5.1.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
||||
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.14" />
|
||||
<PackageReference Include="NJsonSchema" Version="10.8.0" />
|
||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
|
||||
<PackageReference Include="NUnit.Analyzers" Version="3.3.0" />
|
||||
|
@@ -3,53 +3,39 @@ namespace Gitea.Declarative.Test
|
||||
open System.IO
|
||||
open System.Reflection
|
||||
open Gitea.Declarative
|
||||
open NJsonSchema.Generation
|
||||
open NJsonSchema.Validation
|
||||
open NUnit.Framework
|
||||
open FsUnitTyped
|
||||
open NJsonSchema
|
||||
open Newtonsoft.Json
|
||||
open Newtonsoft.Json.Schema
|
||||
open Newtonsoft.Json.Schema.Generation
|
||||
open Newtonsoft.Json.Serialization
|
||||
|
||||
[<TestFixture>]
|
||||
module TestSchema =
|
||||
let schemaGen = JSchemaGenerator ()
|
||||
schemaGen.ContractResolver <- CamelCasePropertyNamesContractResolver ()
|
||||
|
||||
[<Test>]
|
||||
let ``Schema is consistent`` () =
|
||||
let schemaFile =
|
||||
Assembly.GetExecutingAssembly().Location
|
||||
|> FileInfo
|
||||
|> fun fi -> fi.Directory
|
||||
|> Utils.findFileAbove "Gitea.Declarative.Lib/GiteaConfig.schema.json"
|
||||
|
||||
let existing = JSchema.Parse (File.ReadAllText schemaFile.FullName)
|
||||
let derived = schemaGen.Generate typeof<SerialisedGiteaConfig>
|
||||
|
||||
existing.ToString () |> shouldEqual (derived.ToString ())
|
||||
|
||||
[<Test>]
|
||||
let ``Example conforms to schema`` () =
|
||||
let executing = Assembly.GetExecutingAssembly().Location |> FileInfo
|
||||
let schemaFile = Utils.findFileAbove "GiteaConfig.json" executing.Directory
|
||||
|
||||
let existing = JSchema.Parse (File.ReadAllText schemaFile.FullName)
|
||||
let schemaFile =
|
||||
Utils.findFileAbove "Gitea.Declarative.Lib/GiteaConfig.schema.json" executing.Directory
|
||||
|
||||
let schema = JsonSchema.FromJsonAsync(File.ReadAllText schemaFile.FullName).Result
|
||||
|
||||
let jsonFile = Utils.findFileAbove "GiteaConfig.json" executing.Directory
|
||||
let json = File.ReadAllText jsonFile.FullName
|
||||
|
||||
use reader = new JsonTextReader (new StringReader (json))
|
||||
use validatingReader = new JSchemaValidatingReader (reader)
|
||||
validatingReader.Schema <- existing
|
||||
let validator = JsonSchemaValidator ()
|
||||
let errors = validator.Validate (json, schema)
|
||||
|
||||
let messages = ResizeArray ()
|
||||
validatingReader.ValidationEventHandler.Add (fun args -> messages.Add args.Message)
|
||||
errors |> shouldBeEmpty
|
||||
|
||||
let ser = JsonSerializer ()
|
||||
ser.ContractResolver <- CamelCasePropertyNamesContractResolver ()
|
||||
let _config = ser.Deserialize<SerialisedGiteaConfig> validatingReader
|
||||
|
||||
messages |> shouldBeEmpty
|
||||
[<Test>]
|
||||
let ``Example can be loaded`` () =
|
||||
let executing = Assembly.GetExecutingAssembly().Location |> FileInfo
|
||||
let jsonFile = Utils.findFileAbove "GiteaConfig.json" executing.Directory
|
||||
GiteaConfig.get jsonFile |> ignore
|
||||
|
||||
[<Test>]
|
||||
[<Explicit "Run this to regenerate the schema file">]
|
||||
@@ -60,6 +46,27 @@ module TestSchema =
|
||||
|> fun fi -> fi.Directory
|
||||
|> Utils.findFileAbove "Gitea.Declarative.Lib/GiteaConfig.schema.json"
|
||||
|
||||
let schema = schemaGen.Generate typeof<SerialisedGiteaConfig>
|
||||
let settings = JsonSchemaGeneratorSettings ()
|
||||
|
||||
File.WriteAllText (schemaFile.FullName, schema.ToString ())
|
||||
settings.SerializerSettings <-
|
||||
JsonSerializerSettings (ContractResolver = CamelCasePropertyNamesContractResolver ())
|
||||
|
||||
let schema = JsonSchema.FromType (typeof<SerialisedGiteaConfig>, settings)
|
||||
|
||||
// Hack around the lack of discriminated unions in C#
|
||||
let serialisedRepoSchema = schema.Definitions.[typeof<SerialisedRepo>.Name]
|
||||
serialisedRepoSchema.RequiredProperties.Clear ()
|
||||
|
||||
do
|
||||
let schema = JsonSchema ()
|
||||
schema.RequiredProperties.Add "description"
|
||||
schema.RequiredProperties.Add "gitHub"
|
||||
serialisedRepoSchema.OneOf.Add schema
|
||||
|
||||
do
|
||||
let schema = JsonSchema ()
|
||||
schema.RequiredProperties.Add "description"
|
||||
schema.RequiredProperties.Add "native"
|
||||
serialisedRepoSchema.OneOf.Add schema
|
||||
|
||||
File.WriteAllText (schemaFile.FullName, schema.ToJson ())
|
||||
|
@@ -5,7 +5,7 @@ This is a small project to allow you to specify a [Gitea](https://github.com/go-
|
||||
# How to build and run
|
||||
|
||||
With Nix: `nix run github:Smaug123/dotnet-gitea-declarative -- --help`.
|
||||
The config file you provide as an argument should conform to [the schema](./Gitea.Declarative.Lib/GiteaConfig.schema.json).
|
||||
The config file you provide as an argument should conform to [the schema](./Gitea.Declarative.Lib/GiteaConfig.schema.json); there is [an example](./Gitea.Declarative.Test/GiteaConfig.json) in the tests.
|
||||
|
||||
## Building from source
|
||||
|
||||
|
61
nix/deps.nix
61
nix/deps.nix
@@ -51,6 +51,11 @@
|
||||
version = "4.0.1";
|
||||
sha256 = "0zxc0apx1gcx361jlq8smc9pfdgmyjh6hpka8dypc9w23nlsh6yj";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.CSharp";
|
||||
version = "4.3.0";
|
||||
sha256 = "0gw297dgkh0al1zxvgvncqs0j15lsna9l1wpqas4rflmys440xvb";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.Extensions.Configuration";
|
||||
version = "7.0.0";
|
||||
@@ -166,6 +171,11 @@
|
||||
version = "4.3.0";
|
||||
sha256 = "0j0c1wj4ndj21zsgivsc24whiya605603kxrbiw6wkfdync464wq";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Namotion.Reflection";
|
||||
version = "2.1.0";
|
||||
sha256 = "0ql10m9i5qm3cmcw6abk6wvm823vc4s8wzx351yffd6syd50mkb7";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Nerdbank.GitVersioning";
|
||||
version = "3.5.119";
|
||||
@@ -181,11 +191,6 @@
|
||||
version = "2.1.0";
|
||||
sha256 = "12n76gymxq715lkrw841vi5r84kx746cxxssp22pd08as75jzsj6";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Newtonsoft.Json";
|
||||
version = "12.0.3";
|
||||
sha256 = "17dzl305d835mzign8r15vkmav2hq8l6g7942dfjpnzr17wwl89x";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Newtonsoft.Json";
|
||||
version = "13.0.2";
|
||||
@@ -197,9 +202,9 @@
|
||||
sha256 = "0mcy0i7pnfpqm4pcaiyzzji4g0c8i3a5gjz28rrr28110np8304r";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Newtonsoft.Json.Schema";
|
||||
version = "3.0.14";
|
||||
sha256 = "1njk1arrf8pbx0i0p3yww459i70p0fcx02vs0jnbb6znvcy4mvh6";
|
||||
pname = "NJsonSchema";
|
||||
version = "10.8.0";
|
||||
sha256 = "1mzqskv4vx5mzq0rykjwgc323afs2km0hslr2xr6r9fz9qygd28h";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "NuGet.Frameworks";
|
||||
@@ -556,6 +561,11 @@
|
||||
version = "4.0.11";
|
||||
sha256 = "1pla2dx8gkidf7xkciig6nifdsb494axjvzvann8g2lp3dbqasm9";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.Dynamic.Runtime";
|
||||
version = "4.3.0";
|
||||
sha256 = "1d951hrvrpndk7insiag80qxjbf2y0y39y8h5hnq9612ws661glk";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.Globalization";
|
||||
version = "4.0.11";
|
||||
@@ -621,6 +631,11 @@
|
||||
version = "4.1.0";
|
||||
sha256 = "1gpdxl6ip06cnab7n3zlcg6mqp7kknf73s8wjinzi4p0apw82fpg";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.Linq.Expressions";
|
||||
version = "4.3.0";
|
||||
sha256 = "0ky2nrcvh70rqq88m9a5yqabsl4fyd17bpr63iy2mbivjs2nyypv";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.Memory";
|
||||
version = "4.5.4";
|
||||
@@ -656,6 +671,11 @@
|
||||
version = "4.0.12";
|
||||
sha256 = "1sybkfi60a4588xn34nd9a58png36i0xr4y4v4kqpg8wlvy5krrj";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.ObjectModel";
|
||||
version = "4.3.0";
|
||||
sha256 = "191p63zy5rpqx7dnrb3h7prvgixmk168fhvvkkvhlazncf8r3nc2";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.Private.Uri";
|
||||
version = "4.3.0";
|
||||
@@ -676,21 +696,41 @@
|
||||
version = "4.0.1";
|
||||
sha256 = "0ydqcsvh6smi41gyaakglnv252625hf29f7kywy2c70nhii2ylqp";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.Reflection.Emit";
|
||||
version = "4.3.0";
|
||||
sha256 = "11f8y3qfysfcrscjpjym9msk7lsfxkk4fmz9qq95kn3jd0769f74";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.Reflection.Emit.ILGeneration";
|
||||
version = "4.0.1";
|
||||
sha256 = "1pcd2ig6bg144y10w7yxgc9d22r7c7ww7qn1frdfwgxr24j9wvv0";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.Reflection.Emit.ILGeneration";
|
||||
version = "4.3.0";
|
||||
sha256 = "0w1n67glpv8241vnpz1kl14sy7zlnw414aqwj4hcx5nd86f6994q";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.Reflection.Emit.Lightweight";
|
||||
version = "4.0.1";
|
||||
sha256 = "1s4b043zdbx9k39lfhvsk68msv1nxbidhkq6nbm27q7sf8xcsnxr";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.Reflection.Emit.Lightweight";
|
||||
version = "4.3.0";
|
||||
sha256 = "0ql7lcakycrvzgi9kxz1b3lljd990az1x6c4jsiwcacrvimpib5c";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.Reflection.Extensions";
|
||||
version = "4.0.1";
|
||||
sha256 = "0m7wqwq0zqq9gbpiqvgk3sr92cbrw7cp3xn53xvw7zj6rz6fdirn";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.Reflection.Extensions";
|
||||
version = "4.3.0";
|
||||
sha256 = "02bly8bdc98gs22lqsfx9xicblszr2yan7v2mmw3g7hy6miq5hwq";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.Reflection.Metadata";
|
||||
version = "1.6.0";
|
||||
@@ -711,6 +751,11 @@
|
||||
version = "4.1.0";
|
||||
sha256 = "1bjli8a7sc7jlxqgcagl9nh8axzfl11f4ld3rjqsyxc516iijij7";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.Reflection.TypeExtensions";
|
||||
version = "4.3.0";
|
||||
sha256 = "0y2ssg08d817p0vdag98vn238gyrrynjdj4181hdg780sif3ykp1";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.Resources.ResourceManager";
|
||||
version = "4.0.1";
|
||||
|
Reference in New Issue
Block a user