Source-generate client (#92)

This commit is contained in:
Patrick Stevens
2024-09-22 16:50:32 +01:00
committed by GitHub
parent 12c29f1a92
commit ff252e0dab
18 changed files with 7303 additions and 719 deletions

View File

@@ -1,5 +1,7 @@
namespace Gitea.Declarative namespace Gitea.Declarative
open System.Threading.Tasks
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
module Async = module Async =
@@ -8,3 +10,20 @@ module Async =
let! a = a let! a = a
return f a return f a
} }
/// The input function takes page first, then count.
/// Repeatedly calls `f` with increasing page numbers until all results are returned.
let getAllPaginated (f : int64 -> int64 -> 'ret array Task) : 'ret array Async =
let rec go (page : int64) (soFar : 'ret array) =
async {
let! newPage = f page 100L |> Async.AwaitTask
let soFar = Array.append soFar newPage
if newPage.Length < 100 then
return soFar
else
return! go (page + 1L) soFar
}
go 0L [||]

View File

@@ -240,48 +240,90 @@ type Repo =
Native = this.Native |> Option.map (fun s -> s.OverrideDefaults ()) Native = this.Native |> Option.map (fun s -> s.OverrideDefaults ())
} }
static member Render (client : IGiteaClient) (u : Gitea.Repository) : Repo Async = static member Render (client : GiteaClient.IGiteaClient) (u : GiteaClient.Repository) : Repo Async =
if u.Mirror = Some true && not (String.IsNullOrEmpty u.OriginalUrl) then match u.Mirror, u.OriginalUrl with
| Some true, Some originalUrl when originalUrl <> "" ->
{ {
Description = u.Description Description =
match u.Description with
| None -> "(no description)"
| Some d -> d
GitHub = GitHub =
{ {
Uri = Uri u.OriginalUrl Uri = Uri originalUrl
MirrorInterval = u.MirrorInterval MirrorInterval =
match u.MirrorInterval with
| None -> "8h0m0s"
| Some s -> s
} }
|> Some |> Some
Native = None Native = None
Deleted = None Deleted = None
} }
|> async.Return |> async.Return
else | _, _ ->
let repoFullName = u.FullName let repoFullName =
match u.FullName with
| None -> failwith "Repo unexpectedly had no full name!"
| Some f -> f
async { async {
let owner = u.Owner let owner =
match u.Owner with
| None -> failwith "Gitea unexpectedly gave us a repository with no owner!"
| Some owner -> owner
let loginName = owner.LoginName let loginName =
match owner.LoginName with
| None -> failwith "Owner of repo unexpectedly had no login name!"
| Some n -> n
let! mirrors = let! mirrors =
getAllPaginated (fun page count -> List.getPaginated (fun page count ->
client.RepoListPushMirrors (loginName, repoFullName, Some page, Some count) async {
let! ct = Async.CancellationToken
return!
client.RepoListPushMirrors (loginName, repoFullName, page, count, ct)
|> Async.AwaitTask
}
) )
let! (branchProtections : Gitea.BranchProtection[]) = let! (branchProtections : GiteaClient.BranchProtection list) =
client.RepoListBranchProtection (u.Owner.LoginName, u.FullName) async {
|> Async.AwaitTask let! ct = Async.CancellationToken
return! client.RepoListBranchProtection (loginName, repoFullName, ct) |> Async.AwaitTask
}
let! (collaborators : Gitea.User[]) = let! (collaborators : GiteaClient.User list) =
getAllPaginated (fun page count -> List.getPaginated (fun page count ->
client.RepoListCollaborators (u.Owner.LoginName, u.FullName, Some page, Some count) async {
let! ct = Async.CancellationToken
return!
client.RepoListCollaborators (loginName, repoFullName, page, count, ct)
|> Async.AwaitTask
}
) )
let defaultBranch = u.DefaultBranch let defaultBranch =
match u.DefaultBranch with
| None -> failwith "repo unexpectedly had no default branch!"
| Some d -> d
let collaborators = let collaborators =
collaborators |> Seq.map (fun user -> user.LoginName) |> Set.ofSeq collaborators
|> Seq.map (fun user ->
match user.LoginName with
| None -> failwith "user unexpectedly had no login name!"
| Some n -> n
)
|> Set.ofSeq
let description = u.Description let description =
match u.Description with
| None -> failwith "Unexpectedly got no description on a repo!"
| Some d -> d
return return
@@ -298,7 +340,7 @@ type Repo =
HasProjects = u.HasProjects HasProjects = u.HasProjects
HasIssues = u.HasIssues HasIssues = u.HasIssues
HasWiki = u.HasWiki HasWiki = u.HasWiki
DefaultMergeStyle = u.DefaultMergeStyle |> Option.ofObj |> Option.map MergeStyle.Parse DefaultMergeStyle = u.DefaultMergeStyle |> Option.map MergeStyle.Parse
DeleteBranchAfterMerge = u.DefaultDeleteBranchAfterMerge DeleteBranchAfterMerge = u.DefaultDeleteBranchAfterMerge
AllowSquashMerge = u.AllowSquashMerge AllowSquashMerge = u.AllowSquashMerge
AllowRebaseUpdate = u.AllowRebaseUpdate AllowRebaseUpdate = u.AllowRebaseUpdate
@@ -307,22 +349,30 @@ type Repo =
AllowMergeCommits = u.AllowMergeCommits AllowMergeCommits = u.AllowMergeCommits
Mirrors = Mirrors =
mirrors mirrors
|> Array.toList
|> List.map (fun m -> |> List.map (fun m ->
{ match m.RemoteAddress, m.RemoteName with
GitHubAddress = Uri m.RemoteAddress | None, _ -> failwith "Unexpectedly have a PushMirror but no remote address!"
RemoteName = Some m.RemoteName | Some _, None ->
} failwith "Unexpectedly have a PushMirror with no remote name!"
| Some s, Some remoteName ->
{
GitHubAddress = Uri s
RemoteName = Some remoteName
}
) )
ProtectedBranches = ProtectedBranches =
branchProtections branchProtections
|> Seq.map (fun bp -> |> Seq.map (fun bp ->
match bp.BranchName with
| None -> failwith "Unexpectedly have a BranchProtection with no branch name!"
| Some branchName ->
{ {
BranchName = bp.BranchName BranchName = branchName
BlockOnOutdatedBranch = bp.BlockOnOutdatedBranch BlockOnOutdatedBranch = bp.BlockOnOutdatedBranch
RequiredStatusChecks = RequiredStatusChecks =
if bp.EnableStatusCheck = Some true then if bp.EnableStatusCheck = Some true then
bp.StatusCheckContexts |> List.ofArray |> Some bp.StatusCheckContexts
else else
None None
} }
@@ -373,20 +423,24 @@ type UserInfo =
Visibility : string option Visibility : string option
} }
static member Render (u : Gitea.User) : UserInfo = static member Render (u : GiteaClient.User) : UserInfo =
let website =
u.Website
|> Option.bind (fun ws ->
match Uri.TryCreate (ws, UriKind.Absolute) with
| false, _ -> None
| true, uri -> Some uri
)
let email =
u.Email
|> Option.defaultWith (fun () -> failwith "Gitea user failed to have an email!")
{ {
IsAdmin = u.IsAdmin IsAdmin = u.IsAdmin
Email = u.Email Email = email
Website = Website = website
if String.IsNullOrEmpty u.Website then Visibility = u.Visibility
None
else
Some (Uri u.Website)
Visibility =
if String.IsNullOrEmpty u.Visibility then
None
else
Some u.Visibility
} }
static member internal OfSerialised (s : SerialisedUserInfo) = static member internal OfSerialised (s : SerialisedUserInfo) =

File diff suppressed because it is too large Load Diff

View File

@@ -20,19 +20,27 @@
<ItemGroup> <ItemGroup>
<Compile Include="AssemblyInfo.fs" /> <Compile Include="AssemblyInfo.fs" />
<None Include="swagger.v1.json" />
<Compile Include="GeneratedSwaggerGitea.fs">
<MyriadFile>swagger.v1.json</MyriadFile>
<MyriadParams>
<GenerateMockVisibility>public</GenerateMockVisibility>
<ClassName>GiteaClient</ClassName>
</MyriadParams>
</Compile>
<Compile Include="Generated2SwaggerGitea.fs">
<MyriadFile>GeneratedSwaggerGitea.fs</MyriadFile>
</Compile>
<Compile Include="Map.fs" /> <Compile Include="Map.fs" />
<Compile Include="Exception.fs" /> <Compile Include="Exception.fs" />
<Compile Include="List.fs" />
<Compile Include="Async.fs" /> <Compile Include="Async.fs" />
<Compile Include="GiteaClient.fs" />
<Compile Include="IGiteaClient.fs" />
<Compile Include="Domain.fs" /> <Compile Include="Domain.fs" />
<Compile Include="SerialisedConfigSchema.fs" /> <Compile Include="SerialisedConfigSchema.fs" />
<Compile Include="ConfigSchema.fs" /> <Compile Include="ConfigSchema.fs" />
<Compile Include="Array.fs" />
<Compile Include="UserInput.fs" /> <Compile Include="UserInput.fs" />
<Compile Include="Gitea.fs" /> <Compile Include="Gitea.fs" />
<EmbeddedResource Include="GiteaConfig.schema.json" /> <EmbeddedResource Include="GiteaConfig.schema.json" />
<Content Include="swagger.v1.json" />
<EmbeddedResource Include="version.json" /> <EmbeddedResource Include="version.json" />
<None Include="..\README.md" Pack="true" PackagePath="/" /> <None Include="..\README.md" Pack="true" PackagePath="/" />
</ItemGroup> </ItemGroup>

File diff suppressed because it is too large Load Diff

View File

@@ -1,29 +0,0 @@
namespace Gitea.Declarative
open System.Threading.Tasks
open SwaggerProvider
[<AutoOpen>]
module GiteaClient =
[<Literal>]
let Host = "file://" + __SOURCE_DIRECTORY__ + "/swagger.v1.json"
type Gitea = SwaggerClientProvider<Host>
/// The input function takes page first, then count.
/// Repeatedly calls `f` with increasing page numbers until all results are returned.
let getAllPaginated (f : int64 -> int64 -> 'ret array Task) : 'ret array Async =
let rec go (page : int64) (soFar : 'ret array) =
async {
let! newPage = f page 100L |> Async.AwaitTask
let soFar = Array.append soFar newPage
if newPage.Length < 100 then
return soFar
else
return! go (page + 1L) soFar
}
go 0L [||]

View File

@@ -1,90 +0,0 @@
namespace Gitea.Declarative
open System.Threading.Tasks
type IGiteaClient =
abstract AdminGetAllUsers : page : int64 option * limit : int64 option -> Gitea.User array Task
abstract AdminCreateUser : Gitea.CreateUserOption -> Gitea.User Task
abstract AdminDeleteUser : user : string -> unit Task
abstract AdminEditUser : user : string * Gitea.EditUserOption -> Gitea.User Task
abstract AdminCreateRepo : user : string * Gitea.CreateRepoOption -> Gitea.Repository Task
abstract UserListRepos : string * page : int64 option * count : int64 option -> Gitea.Repository array Task
abstract RepoAddPushMirror : user : string * repo : string * Gitea.CreatePushMirrorOption -> Gitea.PushMirror Task
abstract RepoListPushMirrors :
loginName : string * userName : string * page : int64 option * count : int64 option ->
Gitea.PushMirror array Task
abstract RepoDeletePushMirror : user : string * repo : string * remoteName : string -> unit Task
abstract RepoListBranchProtection : loginName : string * userName : string -> Gitea.BranchProtection array Task
abstract RepoDeleteBranchProtection : user : string * repo : string * branch : string -> unit Task
abstract RepoCreateBranchProtection :
user : string * repo : string * Gitea.CreateBranchProtectionOption -> Gitea.BranchProtection Task
abstract RepoEditBranchProtection :
user : string * repo : string * branch : string * Gitea.EditBranchProtectionOption ->
Gitea.BranchProtection Task
abstract RepoMigrate : Gitea.MigrateRepoOptions -> Gitea.Repository Task
abstract RepoGet : user : string * repo : string -> Gitea.Repository Task
abstract RepoDelete : user : string * repo : string -> unit Task
abstract RepoEdit : user : string * repo : string * Gitea.EditRepoOption -> Gitea.Repository Task
abstract RepoListCollaborators :
loginName : string * userName : string * page : int64 option * count : int64 option -> Gitea.User array Task
abstract RepoAddCollaborator : user : string * repo : string * collaborator : string -> unit Task
abstract RepoDeleteCollaborator : user : string * repo : string * collaborator : string -> unit Task
[<RequireQualifiedAccess>]
module IGiteaClient =
let fromReal (client : Gitea.Client) : IGiteaClient =
{ new IGiteaClient with
member _.AdminGetAllUsers (page, limit) = client.AdminGetAllUsers (page, limit)
member _.AdminCreateUser user = client.AdminCreateUser user
member _.AdminDeleteUser user = client.AdminDeleteUser user
member _.AdminEditUser (user, option) = client.AdminEditUser (user, option)
member _.AdminCreateRepo (user, option) = client.AdminCreateRepo (user, option)
member _.UserListRepos (user, page, count) =
client.UserListRepos (user, page, count)
member _.RepoAddPushMirror (user, repo, options) =
client.RepoAddPushMirror (user, repo, options)
member _.RepoListPushMirrors (loginName, userName, page, count) =
client.RepoListPushMirrors (loginName, userName, page, count)
member _.RepoDeletePushMirror (loginName, repo, remoteName) =
client.RepoDeletePushMirror (loginName, repo, remoteName)
member _.RepoListBranchProtection (login, user) =
client.RepoListBranchProtection (login, user)
member _.RepoDeleteBranchProtection (user, repo, branch) =
client.RepoDeleteBranchProtection (user, repo, branch)
member _.RepoCreateBranchProtection (user, repo, options) =
client.RepoCreateBranchProtection (user, repo, options)
member _.RepoEditBranchProtection (user, repo, branch, edit) =
client.RepoEditBranchProtection (user, repo, branch, edit)
member _.RepoMigrate options = client.RepoMigrate options
member _.RepoGet (user, repo) = client.RepoGet (user, repo)
member _.RepoDelete (user, repo) = client.RepoDelete (user, repo)
member _.RepoEdit (user, repo, options) = client.RepoEdit (user, repo, options)
member _.RepoListCollaborators (login, user, page, count) =
client.RepoListCollaborators (login, user, page, count)
member _.RepoAddCollaborator (user, repo, collaborator) =
client.RepoAddCollaborator (user, repo, collaborator)
member _.RepoDeleteCollaborator (user, repo, collaborator) =
client.RepoDeleteCollaborator (user, repo, collaborator)
}

View File

@@ -1,20 +1,20 @@
namespace Gitea.Declarative namespace Gitea.Declarative
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
module internal Array = module internal List =
/// f takes a page number and a limit (i.e. a desired page size). /// f takes a page number and a limit (i.e. a desired page size).
let getPaginated (f : int64 -> int64 -> 'a array Async) : 'a list Async = let getPaginated (f : int -> int -> 'a list Async) : 'a list Async =
let count = 30 let count = 30
let rec go (page : int) (acc : 'a array list) = let rec go (page : int) (acc : 'a list list) =
async { async {
let! result = f page count let! result = f page count
if result.Length >= count then if result.Length >= count then
return! go (page + 1) (result :: acc) return! go (page + 1) (result :: acc)
else else
return (result :: acc) |> Seq.concat |> Seq.toList return (result :: acc) |> List.concat
} }
go 1 [] go 1 []

View File

View File

@@ -15,7 +15,7 @@ module TestRepo =
[<Test>] [<Test>]
let ``We refuse to delete a repo if we get to Reconcile without positive confirmation`` () = let ``We refuse to delete a repo if we get to Reconcile without positive confirmation`` () =
let property (gitHubToken : string option) = let property (gitHubToken : string option) =
let client = GiteaClientMock.Unimplemented let client = GiteaClient.GiteaClientMock.Empty
let lf, messages = LoggerFactory.makeTest () let lf, messages = LoggerFactory.makeTest ()
let logger = lf.CreateLogger "test" let logger = lf.CreateLogger "test"
@@ -42,10 +42,10 @@ module TestRepo =
(user : User) (user : User)
(repos : Map<RepoName, Repo>) (repos : Map<RepoName, Repo>)
(userInfo : UserInfo) (userInfo : UserInfo)
(repo : Gitea.Repository) (repo : GiteaClient.Repository)
(reposToReturn : Gitea.Repository[]) (reposToReturn : GiteaClient.Repository list)
= =
let reposToReturn = Array.append [| repo |] reposToReturn let reposToReturn = repo :: reposToReturn
let reposToReturn = let reposToReturn =
if reposToReturn.Length >= 5 then if reposToReturn.Length >= 5 then
@@ -53,25 +53,35 @@ module TestRepo =
else else
reposToReturn reposToReturn
for repo in reposToReturn do
match repo.Name with
| None -> failwith "generator should have put a name on every repo"
| Some _ -> ()
let lf, messages = LoggerFactory.makeTest () let lf, messages = LoggerFactory.makeTest ()
let logger = lf.CreateLogger "test" let logger = lf.CreateLogger "test"
let client = let client =
{ GiteaClientMock.Unimplemented with { GiteaClient.GiteaClientMock.Empty with
UserListRepos = UserListRepos =
fun (_username, _page, _limit) -> fun (_username, _page, _limit, ct) ->
async { async {
return return
reposToReturn reposToReturn
|> Array.filter (fun r -> not (repos.ContainsKey (RepoName r.Name))) |> List.filter (fun r -> not (repos.ContainsKey (RepoName (Option.get r.Name))))
} }
|> Async.StartAsTask |> fun a -> Async.StartAsTask (a, ?cancellationToken = ct)
RepoListPushMirrors = fun _ -> async { return [||] } |> Async.StartAsTask RepoListPushMirrors =
fun (_, _, _, _, ct) ->
async { return [] } |> fun a -> Async.StartAsTask (a, ?cancellationToken = ct)
RepoListBranchProtection = fun _ -> async { return [||] } |> Async.StartAsTask RepoListBranchProtection =
fun (_, _, ct) -> async { return [] } |> fun a -> Async.StartAsTask (a, ?cancellationToken = ct)
RepoListCollaborators = fun _ -> async { return [||] } |> Async.StartAsTask RepoListCollaborators =
fun (_, _, _, _, ct) ->
async { return [] } |> fun a -> Async.StartAsTask (a, ?cancellationToken = ct)
} }
let config : GiteaConfig = let config : GiteaConfig =
@@ -121,14 +131,14 @@ module TestRepo =
let logger = lf.CreateLogger "test" let logger = lf.CreateLogger "test"
let client = let client =
{ GiteaClientMock.Unimplemented with { GiteaClient.GiteaClientMock.Empty with
UserListRepos = fun _ -> Task.FromResult [||] UserListRepos = fun _ -> Task.FromResult []
RepoListPushMirrors = fun _ -> async { return [||] } |> Async.StartAsTask RepoListPushMirrors = fun _ -> async { return [] } |> Async.StartAsTask
RepoListBranchProtection = fun _ -> async { return [||] } |> Async.StartAsTask RepoListBranchProtection = fun _ -> async { return [] } |> Async.StartAsTask
RepoListCollaborators = fun _ -> async { return [||] } |> Async.StartAsTask RepoListCollaborators = fun _ -> async { return [] } |> Async.StartAsTask
} }
let config : GiteaConfig = let config : GiteaConfig =
@@ -168,13 +178,10 @@ module TestRepo =
let existingRepos = existingRepos |> Map.add oneExistingRepoName oneExistingRepo let existingRepos = existingRepos |> Map.add oneExistingRepoName oneExistingRepo
let giteaUser = let giteaUser = Types.emptyUser (user.ToString ())
let result = Gitea.User ()
result.LoginName <- user.ToString ()
result
let client = let client =
{ GiteaClientMock.Unimplemented with { GiteaClient.GiteaClientMock.Empty with
UserListRepos = UserListRepos =
fun _ -> fun _ ->
async { async {
@@ -182,20 +189,20 @@ module TestRepo =
existingRepos existingRepos
|> Map.toSeq |> Map.toSeq
|> Seq.map (fun (RepoName repoName, _repoSpec) -> |> Seq.map (fun (RepoName repoName, _repoSpec) ->
let repo = Gitea.Repository () { Types.emptyRepo repoName "main" with
repo.Name <- repoName Name = Some repoName
repo.Owner <- giteaUser Owner = Some giteaUser
repo }
) )
|> Seq.toArray |> Seq.toList
} }
|> Async.StartAsTask |> Async.StartAsTask
RepoListPushMirrors = fun _ -> async { return [||] } |> Async.StartAsTask RepoListPushMirrors = fun _ -> async { return [] } |> Async.StartAsTask
RepoListBranchProtection = fun _ -> async { return [||] } |> Async.StartAsTask RepoListBranchProtection = fun _ -> async { return [] } |> Async.StartAsTask
RepoListCollaborators = fun _ -> async { return [||] } |> Async.StartAsTask RepoListCollaborators = fun _ -> async { return [] } |> Async.StartAsTask
} }
let config : GiteaConfig = let config : GiteaConfig =

View File

@@ -21,14 +21,14 @@ module TestUser =
let result = TaskCompletionSource<bool option> () let result = TaskCompletionSource<bool option> ()
let client = let client =
{ GiteaClientMock.Unimplemented with { GiteaClient.GiteaClientMock.Empty with
AdminCreateUser = AdminCreateUser =
fun options -> fun (options, ct) ->
async { async {
result.SetResult options.MustChangePassword result.SetResult options.MustChangePassword
return null return Types.emptyUser "username"
} }
|> Async.StartAsTask |> fun a -> Async.StartAsTask (a, ?cancellationToken = ct)
} }
[ User "username", AlignmentError.DoesNotExist desiredUser ] [ User "username", AlignmentError.DoesNotExist desiredUser ]

View File

@@ -1,108 +1,200 @@
namespace Gitea.Declarative.Test namespace Gitea.Declarative.Test
open System.Collections.Generic
open Gitea.Declarative open Gitea.Declarative
open System open System
open System.IO open System.IO
open FsCheck open FsCheck
open Microsoft.FSharp.Reflection open Microsoft.FSharp.Reflection
[<RequireQualifiedAccess>]
module Types =
let emptyUser (loginName : string) : GiteaClient.User =
{
Active = None
Created = None
Description = None
Email = None
Id = None
Language = None
Location = None
Login = None
Restricted = None
Visibility = None
Website = None
FullName = None
IsAdmin = None
LoginName = Some loginName
ProhibitLogin = None
AdditionalProperties = Dictionary ()
AvatarUrl = None
FollowersCount = None
FollowingCount = None
StarredReposCount = None
LastLogin = None
}
let emptyRepo (fullName : string) (defaultBranch : string) : GiteaClient.Repository =
{
Archived = None
Description = Some "a description here"
Empty = None
Fork = None
Id = None
Internal = None
Language = None
Link = None
Mirror = None
Name = None
Owner = Some (emptyUser "some-username")
Private = None
Website = None
AllowRebase = None
AllowMergeCommits = None
AllowRebaseExplicit = None
AllowRebaseUpdate = None
AllowSquashMerge = None
DefaultBranch = Some defaultBranch
HasIssues = None
HasProjects = None
HasWiki = None
HasPullRequests = None
DefaultMergeStyle = None
AdditionalProperties = Dictionary ()
AvatarUrl = None
CloneUrl = None
CreatedAt = None
DefaultAllowMaintainerEdit = None
DefaultDeleteBranchAfterMerge = None
ExternalTracker = None
ExternalWiki = None
ForksCount = None
FullName = Some fullName
HtmlUrl = None
IgnoreWhitespaceConflicts = None
InternalTracker = None
LanguagesUrl = None
MirrorInterval = None
MirrorUpdated = None
OpenIssuesCount = None
OpenPrCounter = None
OriginalUrl = None
Parent = None
Permissions = None
ReleaseCounter = None
RepoTransfer = None
Size = None
SshUrl = None
StarsCount = None
Template = None
UpdatedAt = None
WatchersCount = None
}
type CustomArb () = type CustomArb () =
static member UriGen = Gen.constant (Uri "http://example.com") |> Arb.fromGen static member UriGen = Gen.constant (Uri "http://example.com") |> Arb.fromGen
static member User : Arbitrary<Gitea.User> = static member User : Arbitrary<GiteaClient.User> =
gen { gen {
let user = Gitea.User () let! active = Arb.generate<_>
let! a = Arb.generate<_> let! created = Arb.generate<_>
user.Active <- a let! description = Arb.generate<_>
let! a = Arb.generate<_> let! email = Arb.generate<_>
user.Created <- a let! id = Arb.generate<_>
let! a = Arb.generate<_> let! language = Arb.generate<_>
user.Description <- a let! location = Arb.generate<_>
let! a = Arb.generate<_> let! login = Arb.generate<_>
user.Email <- a let! restricted = Arb.generate<_>
let! a = Arb.generate<_> let! visibility = Arb.generate<_>
user.Id <- a let! website = Arb.generate<_>
let! a = Arb.generate<_> let! fullname = Arb.generate<_>
user.Language <- a let! isAdmin = Arb.generate<_>
let! a = Arb.generate<_> let! loginName = Arb.generate<_>
user.Location <- a let! prohibitLogin = Arb.generate<_>
let! a = Arb.generate<_>
user.Login <- a return
let! a = Arb.generate<_> ({ Types.emptyUser loginName with
user.Restricted <- a Active = active
let! a = Arb.generate<_> Created = created
user.Visibility <- a Description = description
let! a = Arb.generate<_> Email = email
user.Website <- a Id = id
let! a = Arb.generate<_> Language = language
user.FullName <- a Location = location
let! a = Arb.generate<_> Login = login
user.IsAdmin <- a Restricted = restricted
let! a = Arb.generate<_> Visibility = visibility
user.LoginName <- a Website = website
let! a = Arb.generate<_> FullName = fullname
user.ProhibitLogin <- a IsAdmin = isAdmin
return user ProhibitLogin = prohibitLogin
}
: GiteaClient.User)
} }
|> Arb.fromGen |> Arb.fromGen
static member RepositoryGen : Arbitrary<Gitea.Repository> = static member RepositoryGen : Arbitrary<GiteaClient.Repository> =
gen { gen {
let repo = Gitea.Repository () let! archived = Arb.generate<_>
let! a = Arb.generate<_> let! description = Arb.generate<_>
repo.Archived <- a let! empty = Arb.generate<_>
let! a = Arb.generate<_> let! fork = Arb.generate<_>
repo.Description <- a let! id = Arb.generate<_>
let! a = Arb.generate<_> let! isInternal = Arb.generate<_>
repo.Empty <- a let! language = Arb.generate<_>
let! a = Arb.generate<_> let! link = Arb.generate<_>
repo.Fork <- a let! mirror = Arb.generate<_>
let! a = Arb.generate<_> let! name = Arb.generate<_>
repo.Id <- a let! fullName = Arb.generate<_>
let! a = Arb.generate<_> let! owner = Arb.generate<_>
repo.Internal <- a let! isPrivate = Arb.generate<_>
let! a = Arb.generate<_> let! website = Arb.generate<_>
repo.Language <- a let! allowRebase = Arb.generate<_>
let! a = Arb.generate<_> let! allowMergeCommits = Arb.generate<_>
repo.Link <- a let! allowRebaseExplicit = Arb.generate<_>
let! a = Arb.generate<_> let! allowRebaseUpdate = Arb.generate<_>
repo.Mirror <- a let! allowSquashMerge = Arb.generate<_>
let! a = Arb.generate<_> let! defaultBranch = Arb.generate<_>
repo.Name <- a let! hasIssues = Arb.generate<_>
let! a = Arb.generate<_> let! hasProjects = Arb.generate<_>
repo.Owner <- a let! hasWiki = Arb.generate<_>
let! a = Arb.generate<_> let! hasPullRequests = Arb.generate<_>
repo.Private <- a
let! a = Arb.generate<_>
repo.Website <- a
let! a = Arb.generate<_>
repo.AllowRebase <- a
let! a = Arb.generate<_>
repo.AllowMergeCommits <- a
let! a = Arb.generate<_>
repo.AllowRebaseExplicit <- a
let! a = Arb.generate<_>
repo.AllowRebaseUpdate <- a
let! a = Arb.generate<_>
repo.AllowSquashMerge <- a
let! a = Arb.generate<_>
repo.DefaultBranch <- a
let! a = Arb.generate<_>
repo.HasIssues <- a
let! a = Arb.generate<_>
repo.HasProjects <- a
let! a = Arb.generate<_>
repo.HasWiki <- a
let! a = Arb.generate<_>
repo.HasPullRequests <- a
let! a = let! mergeStyle =
FSharpType.GetUnionCases typeof<MergeStyle> FSharpType.GetUnionCases typeof<MergeStyle>
|> Array.map (fun uci -> FSharpValue.MakeUnion (uci, [||]) |> unbox<MergeStyle>) |> Array.map (fun uci -> FSharpValue.MakeUnion (uci, [||]) |> unbox<MergeStyle>)
|> Gen.elements |> Gen.elements
repo.DefaultMergeStyle <- MergeStyle.toString a let mergeStyle = (mergeStyle : MergeStyle).ToString ()
return repo
return
({ Types.emptyRepo fullName defaultBranch with
Archived = archived
Description = Some description
Empty = empty
Fork = fork
Id = id
Internal = isInternal
Language = language
Link = link
Mirror = mirror
Name = Some name
Owner = Some owner
Private = isPrivate
Website = website
AllowRebase = allowRebase
AllowMergeCommits = allowMergeCommits
AllowRebaseExplicit = allowRebaseExplicit
AllowRebaseUpdate = allowRebaseUpdate
AllowSquashMerge = allowSquashMerge
HasIssues = hasIssues
HasProjects = hasProjects
HasWiki = hasWiki
HasPullRequests = hasPullRequests
DefaultMergeStyle = Some mergeStyle
AdditionalProperties = Dictionary ()
}
: GiteaClient.Repository)
} }
|> Arb.fromGen |> Arb.fromGen

View File

@@ -63,7 +63,7 @@ module Reconcile =
let logger = loggerProvider.CreateLogger "Gitea.Declarative" let logger = loggerProvider.CreateLogger "Gitea.Declarative"
use httpClient = Utils.createHttpClient args.GiteaHost args.GiteaAdminApiToken use httpClient = Utils.createHttpClient args.GiteaHost args.GiteaAdminApiToken
let client = Gitea.Client httpClient |> IGiteaClient.fromReal let client = GiteaClient.GiteaClient.make httpClient
logger.LogInformation "Checking users..." logger.LogInformation "Checking users..."
let! userErrors = Gitea.checkUsers config client let! userErrors = Gitea.checkUsers config client

View File

@@ -50,7 +50,7 @@ module RefreshAuth =
let run (args : RefreshAuthArgs) : Async<int> = let run (args : RefreshAuthArgs) : Async<int> =
async { async {
use httpClient = Utils.createHttpClient args.GiteaHost args.GiteaAdminApiToken use httpClient = Utils.createHttpClient args.GiteaHost args.GiteaAdminApiToken
let client = Gitea.Client httpClient |> IGiteaClient.fromReal let client = GiteaClient.GiteaClient.make httpClient
use loggerProvider = Utils.createLoggerProvider () use loggerProvider = Utils.createLoggerProvider ()
let logger = loggerProvider.CreateLogger "Gitea.Declarative" let logger = loggerProvider.CreateLogger "Gitea.Declarative"
@@ -58,7 +58,7 @@ module RefreshAuth =
let! instructions = Gitea.toRefresh client let! instructions = Gitea.toRefresh client
if args.DryRun then if args.DryRun then
logger.LogInformation ("Stopping due to --dry-run.") logger.LogInformation "Stopping due to --dry-run."
else else
do! Gitea.refreshAuth logger client args.GitHubApiToken instructions do! Gitea.refreshAuth logger client args.GitHubApiToken instructions

View File

@@ -5,11 +5,11 @@ open Gitea.Declarative
type private ServerState = type private ServerState =
{ {
Users : Map<User, Gitea.CreateUserOption> Users : Map<User, GiteaClient.CreateUserOption>
Repos : (User * Repo) list Repos : (User * Repo) list
} }
member this.WithUser (create : Gitea.CreateUserOption) : ServerState = member this.WithUser (create : GiteaClient.CreateUserOption) : ServerState =
let user = User create.Username let user = User create.Username
match Map.tryFind user this.Users with match Map.tryFind user this.Users with
@@ -27,7 +27,7 @@ type private ServerState =
Repos = [] Repos = []
} }
type private ServerMessage = | AddUser of Gitea.CreateUserOption * AsyncReplyChannel<unit> type private ServerMessage = | AddUser of GiteaClient.CreateUserOption * AsyncReplyChannel<unit>
type Server = type Server =
private private
@@ -51,57 +51,18 @@ module Client =
return! loop (state.WithUser user) mailbox return! loop (state.WithUser user) mailbox
} }
let make () : Server * IGiteaClient = let make () : Server * GiteaClient.IGiteaClient =
let server = MailboxProcessor.Start (loop ServerState.Empty) let server = MailboxProcessor.Start (loop ServerState.Empty)
let client = let client =
{ new IGiteaClient with { GiteaClient.GiteaClientMock.Empty with
member _.AdminGetAllUsers (page, limit) = failwith "Not implemented" AdminCreateUser =
fun (createUserOption, _ct) ->
member _.AdminCreateUser createUserOption = async {
async { let! () = server.PostAndAsyncReply (fun reply -> AddUser (createUserOption, reply))
let! () = server.PostAndAsyncReply (fun reply -> AddUser (createUserOption, reply)) return Operations.createdUser createUserOption
return Operations.createdUser createUserOption }
} |> Async.StartAsTask
|> Async.StartAsTask
member _.AdminDeleteUser user = failwith "Not implemented"
member _.AdminEditUser (user, editUserOption) = failwith "Not implemented"
member _.AdminCreateRepo (user, createRepoOption) = failwith "Not implemented"
member _.UserListRepos (user, page, count) = failwith "Not implemented"
member _.RepoAddPushMirror (user, repo, createPushMirrorOption) = failwith "Not implemented"
member _.RepoDeletePushMirror (user, repo, remoteName) = failwith "Not implemented"
member _.RepoListPushMirrors (loginName, userName, page, count) = failwith "Not implemented"
member _.RepoListBranchProtection (loginName, userName) = failwith "Not implemented"
member _.RepoDeleteBranchProtection (user, repo, branch) = failwith "Not implemented"
member _.RepoCreateBranchProtection (user, repo, createBranchProtectionOption) =
failwith "Not implemented"
member _.RepoEditBranchProtection (user, repo, branch, editBranchProtectionOption) =
failwith "Not implemented"
member _.RepoMigrate migrateRepoOptions = failwith "Not implemented"
member _.RepoGet (user, repo) = failwith "Not implemented"
member _.RepoDelete (user, repo) = failwith "Not implemented"
member _.RepoEdit (user, repo, editRepoOption) = failwith "Not implemented"
member _.RepoListCollaborators (loginName, userName, page, count) = failwith "Not implemented"
member _.RepoAddCollaborator (user, repo, collaborator) = failwith "Not implemented"
member _.RepoDeleteCollaborator (user, repo, collaborator) = failwith "Not implemented"
} }
Server server, client Server server, client

View File

@@ -21,108 +21,3 @@ module Types =
type Repo = type Repo =
| GitHubMirror of Uri | GitHubMirror of Uri
| NativeRepo of NativeRepo | NativeRepo of NativeRepo
/// Allows us to use handy record-updating syntax.
/// (I have a considerable dislike of Moq and friends.)
type GiteaClientMock =
{
AdminGetAllUsers : int64 option * int64 option -> Gitea.User array Task
AdminCreateUser : Gitea.CreateUserOption -> Gitea.User Task
AdminDeleteUser : string -> unit Task
AdminEditUser : string * Gitea.EditUserOption -> Gitea.User Task
AdminCreateRepo : string * Gitea.CreateRepoOption -> Gitea.Repository Task
UserListRepos : string * int64 option * int64 option -> Gitea.Repository array Task
RepoAddPushMirror : string * string * Gitea.CreatePushMirrorOption -> Gitea.PushMirror Task
RepoDeletePushMirror : string * string * string -> unit Task
RepoListPushMirrors : string * string * int64 option * int64 option -> Gitea.PushMirror array Task
RepoListBranchProtection : string * string -> Gitea.BranchProtection array Task
RepoDeleteBranchProtection : string * string * string -> unit Task
RepoCreateBranchProtection : string * string * Gitea.CreateBranchProtectionOption -> Gitea.BranchProtection Task
RepoEditBranchProtection :
string * string * string * Gitea.EditBranchProtectionOption -> Gitea.BranchProtection Task
RepoMigrate : Gitea.MigrateRepoOptions -> Gitea.Repository Task
RepoGet : string * string -> Gitea.Repository Task
RepoDelete : string * string -> unit Task
RepoEdit : string * string * Gitea.EditRepoOption -> Gitea.Repository Task
RepoListCollaborators : string * string * int64 option * int64 option -> Gitea.User array Task
RepoAddCollaborator : string * string * string -> unit Task
RepoDeleteCollaborator : string * string * string -> unit Task
}
static member Unimplemented =
{
AdminGetAllUsers = fun _ -> failwith "Unimplemented"
AdminCreateUser = fun _ -> failwith "Unimplemented"
AdminDeleteUser = fun _ -> failwith "Unimplemented"
AdminEditUser = fun _ -> failwith "Unimplemented"
AdminCreateRepo = fun _ -> failwith "Unimplemented"
UserListRepos = fun _ -> failwith "Unimplemented"
RepoAddPushMirror = fun _ -> failwith "Unimplemented"
RepoDeletePushMirror = fun _ -> failwith "Unimplemented"
RepoListPushMirrors = fun _ -> failwith "Unimplemented"
RepoListBranchProtection = fun _ -> failwith "Unimplemented"
RepoDeleteBranchProtection = fun _ -> failwith "Unimplemented"
RepoCreateBranchProtection = fun _ -> failwith "Unimplemented"
RepoEditBranchProtection = fun _ -> failwith "Unimplemented"
RepoMigrate = fun _ -> failwith "Unimplemented"
RepoGet = fun _ -> failwith "Unimplemented"
RepoDelete = fun _ -> failwith "Unimplemented"
RepoEdit = fun _ -> failwith "Unimplemented"
RepoListCollaborators = fun _ -> failwith "Unimplemented"
RepoAddCollaborator = fun _ -> failwith "Unimplemented"
RepoDeleteCollaborator = fun _ -> failwith "Unimplemented"
}
interface IGiteaClient with
member this.AdminGetAllUsers (page, limit) = this.AdminGetAllUsers (page, limit)
member this.AdminCreateUser user = this.AdminCreateUser user
member this.AdminDeleteUser user = this.AdminDeleteUser user
member this.AdminEditUser (user, option) = this.AdminEditUser (user, option)
member this.AdminCreateRepo (user, option) = this.AdminCreateRepo (user, option)
member this.UserListRepos (user, page, count) = this.UserListRepos (user, page, count)
member this.RepoAddPushMirror (user, repo, options) =
this.RepoAddPushMirror (user, repo, options)
member this.RepoListPushMirrors (loginName, userName, page, count) =
this.RepoListPushMirrors (loginName, userName, page, count)
member this.RepoDeletePushMirror (loginName, userName, remoteName) =
this.RepoDeletePushMirror (loginName, userName, remoteName)
member this.RepoListBranchProtection (login, user) =
this.RepoListBranchProtection (login, user)
member this.RepoDeleteBranchProtection (user, repo, branch) =
this.RepoDeleteBranchProtection (user, repo, branch)
member this.RepoCreateBranchProtection (user, repo, options) =
this.RepoCreateBranchProtection (user, repo, options)
member this.RepoEditBranchProtection (user, repo, branch, edit) =
this.RepoEditBranchProtection (user, repo, branch, edit)
member this.RepoMigrate options = this.RepoMigrate options
member this.RepoGet (user, repo) = this.RepoGet (user, repo)
member this.RepoDelete (user, repo) = this.RepoDelete (user, repo)
member this.RepoEdit (user, repo, options) = this.RepoEdit (user, repo, options)
member this.RepoListCollaborators (login, user, page, count) =
this.RepoListCollaborators (login, user, page, count)
member this.RepoAddCollaborator (user, repo, collaborator) =
this.RepoAddCollaborator (user, repo, collaborator)
member this.RepoDeleteCollaborator (user, repo, collaborator) =
this.RepoDeleteCollaborator (user, repo, collaborator)

View File

@@ -1,18 +1,33 @@
namespace Gitea.InMemory namespace Gitea.InMemory
open Gitea.Declarative open System.Collections.Generic
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
module Operations = module Operations =
let createdUser (createUserOption : Gitea.CreateUserOption) : Gitea.User = let createdUser (createUserOption : GiteaClient.CreateUserOption) : GiteaClient.User =
let result = Gitea.User () let result : GiteaClient.User =
result.Email <- createUserOption.Email {
result.Restricted <- createUserOption.Restricted AdditionalProperties = Dictionary ()
// TODO: what is this username used for anyway Active = None
// result.LoginName <- createUserOption.Username AvatarUrl = None
result.Visibility <- createUserOption.Visibility Created = None
result.Created <- createUserOption.CreatedAt Description = None
result.FullName <- createUserOption.FullName Email = Some createUserOption.Email
result.LoginName <- createUserOption.LoginName FollowersCount = None
FollowingCount = None
FullName = createUserOption.FullName
Id = None
IsAdmin = None
Language = None
LastLogin = None
Location = None
Login = None
LoginName = createUserOption.LoginName
ProhibitLogin = failwith "todo"
Restricted = createUserOption.Restricted
StarredReposCount = None
Visibility = createUserOption.Visibility
Website = None
}
result result

View File

@@ -12,6 +12,8 @@ This is a small project to allow you to specify a [Gitea](https://github.com/go-
* Pull request configuration (e.g. whether rebase-merges are allowed, etc) * Pull request configuration (e.g. whether rebase-merges are allowed, etc)
* Collaborators * Collaborators
* Reconciliation of differences between configuration and reality in the above * Reconciliation of differences between configuration and reality in the above
* Note that deleting a line of configuration will generally simply take that property out of declarative management.
It does *not* return the configured property to its default value. Explicit is better than implicit.
* Deletion of repositories, guarded by the `"deleted": true` configuration * Deletion of repositories, guarded by the `"deleted": true` configuration
# Arguments # Arguments
@@ -31,7 +33,7 @@ See the [Demos file](./docs/demos.md).
# Development # Development
To upgrade the NuGet dependencies in the flake, run `nix build .#fetchDeps` and copy the resulting file into `nix/deps.nix`. To upgrade the NuGet dependencies in the flake, run `nix build .#default.passthru.fetch-deps && ./result` and copy the resulting file into `nix/deps.nix`.
## Formatting ## Formatting