Add frame chunker and make unit test for simple blender render succeed
This commit is contained in:
parent
6aed4e71ff
commit
0629728ce9
@ -27,6 +27,7 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/benbjohnson/clock"
|
||||
oapi_middle "github.com/deepmap/oapi-codegen/pkg/middleware"
|
||||
"github.com/getkin/kin-openapi/openapi3filter"
|
||||
"github.com/labstack/echo/v4"
|
||||
@ -64,7 +65,8 @@ func main() {
|
||||
log.Info().Str("port", port).Msg("listening")
|
||||
|
||||
// Construct the services.
|
||||
compiler, err := job_compilers.Load()
|
||||
timeService := clock.New()
|
||||
compiler, err := job_compilers.Load(timeService)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("error loading job compilers")
|
||||
}
|
||||
|
6
go.mod
6
go.mod
@ -3,16 +3,18 @@ module gitlab.com/blender/flamenco-goja-test
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/benbjohnson/clock v1.3.0
|
||||
github.com/deepmap/oapi-codegen v1.9.0
|
||||
github.com/dop251/goja v0.0.0-20211217115348-3f9136fa235d
|
||||
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7
|
||||
github.com/getkin/kin-openapi v0.88.0
|
||||
github.com/golang-migrate/migrate/v4 v4.15.1 // indirect
|
||||
github.com/golang-migrate/migrate/v4 v4.15.1
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/labstack/echo/v4 v4.6.1
|
||||
github.com/mattn/go-colorable v0.1.12
|
||||
github.com/rs/zerolog v1.26.1
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/ziflex/lecho/v3 v3.1.0
|
||||
modernc.org/sqlite v1.14.4 // indirect
|
||||
golang.org/x/net v0.0.0-20211013171255-e13a2654a71e
|
||||
modernc.org/sqlite v1.14.4
|
||||
)
|
||||
|
13
go.sum
13
go.sum
@ -127,6 +127,8 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.6.1/go.mod h1:hLZ/AnkIKHLuPGjEiyghNE
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.7.2/go.mod h1:8EzeIqfWt2wWT4rJVu3f21TfrhJ8AEMzVybRNSb/b4g=
|
||||
github.com/aws/smithy-go v1.7.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
|
||||
github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
|
||||
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
|
||||
github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
@ -313,6 +315,7 @@ github.com/dop251/goja v0.0.0-20211217115348-3f9136fa235d/go.mod h1:R9ET47fwRVRP
|
||||
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7 h1:tYwu/z8Y0NkkzGEh3z21mSWggMg4LwLRFucLS7TjARg=
|
||||
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
|
||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
||||
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||
@ -487,6 +490,7 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-github/v35 v35.2.0/go.mod h1:s0515YVTI+IMrDoy9Y4pHt9ShGpzHvHO8rZ7L7acgvs=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
@ -677,10 +681,10 @@ github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.10.0 h1:Zx5DJFEYQXio93kgXnQ09fXNiUKsqv4OUEu2UtGcB1E=
|
||||
github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM=
|
||||
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
|
||||
@ -712,6 +716,7 @@ github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp
|
||||
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
||||
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v1.14.10 h1:MLn+5bFRlWMGoSRmJour3CL1w/qL96mvipqpwQW/Sfk=
|
||||
github.com/mattn/go-sqlite3 v1.14.10/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
@ -1033,6 +1038,7 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
@ -1101,7 +1107,6 @@ golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210913180222-943fd674d43e h1:+b/22bPvDYt4NPDcy4xAGCmON713ONAWFeY3Z7I3tR8=
|
||||
golang.org/x/net v0.0.0-20210913180222-943fd674d43e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211013171255-e13a2654a71e h1:Xj+JO91noE97IN6F/7WZxzC5QE6yENAQPrwIYhW3bsA=
|
||||
golang.org/x/net v0.0.0-20211013171255-e13a2654a71e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
@ -1612,11 +1617,13 @@ modernc.org/ccgo/v3 v3.12.92/go.mod h1:5yDdN7ti9KWPi5bRVWPl8UNhpEAtCjuEE7ayQnzzq
|
||||
modernc.org/ccgo/v3 v3.13.1/go.mod h1:aBYVOUfIlcSnrsRVU8VRS35y2DIfpgkmVkYZ0tpIXi4=
|
||||
modernc.org/ccgo/v3 v3.14.0 h1:Zr1Ny9+7r5yAiXpBdgp8XiXqkNA4ARrRphHGHVXeAp0=
|
||||
modernc.org/ccgo/v3 v3.14.0/go.mod h1:hBrkiBlUwvr5vV/ZH9YzXIp982jKE8Ek8tR1ytoAL6Q=
|
||||
modernc.org/ccorpus v1.11.1 h1:K0qPfpVG1MJh5BYazccnmhywH4zHuOgJXgbjzyp6dWA=
|
||||
modernc.org/ccorpus v1.11.1/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
|
||||
modernc.org/db v1.0.0/go.mod h1:kYD/cO29L/29RM0hXYl4i3+Q5VojL31kTUVpVJDw0s8=
|
||||
modernc.org/file v1.0.0/go.mod h1:uqEokAEn1u6e+J45e54dsEA/pw4o7zLrA2GwyntZzjw=
|
||||
modernc.org/fileutil v1.0.0/go.mod h1:JHsWpkrk/CnVV1H/eGlFf85BEpfkrp56ro8nojIq9Q8=
|
||||
modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=
|
||||
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
|
||||
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
|
||||
modernc.org/internal v1.0.0/go.mod h1:VUD/+JAkhCpvkUitlEOnhpVxCgsBI90oTzSCRcqQVSM=
|
||||
modernc.org/libc v1.7.13-0.20210308123627-12f642a52bb8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
|
||||
@ -1683,11 +1690,13 @@ modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs
|
||||
modernc.org/strutil v1.1.1 h1:xv+J1BXY3Opl2ALrBwyfEikFAj8pmqcpnfmuwUwcozs=
|
||||
modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
|
||||
modernc.org/tcl v1.5.2/go.mod h1:pmJYOLgpiys3oI4AeAafkcUfE+TKKilminxNyU/+Zlo=
|
||||
modernc.org/tcl v1.10.0 h1:vux2MNFhSXYqD8Kq4Uc9RjWcgv2c7Atx3da3VpLPPEw=
|
||||
modernc.org/tcl v1.10.0/go.mod h1:WzWapmP/7dHVhFoyPpEaNSVTL8xtewhouN/cqSJ5A2s=
|
||||
modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk=
|
||||
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
modernc.org/z v1.0.1-0.20210308123920-1f282aa71362/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA=
|
||||
modernc.org/z v1.0.1/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA=
|
||||
modernc.org/z v1.2.21 h1:UO0ptLQLHM2eNistCE6ofm1fMKKXk0n0IkrQly3z5HA=
|
||||
modernc.org/z v1.2.21/go.mod h1:uXrObx4pGqXWIMliC5MiKuwAyMrltzwpteOFUP1PWCc=
|
||||
modernc.org/zappy v1.0.0/go.mod h1:hHe+oGahLVII/aTTyWK/b53VDHMAGCBYYeZ9sn83HC4=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
|
@ -39,8 +39,9 @@ var ErrScriptIncomplete = errors.New("job compiler script incomplete")
|
||||
|
||||
// Service contains job compilers defined in JavaScript.
|
||||
type Service struct {
|
||||
compilers map[string]Compiler // Mapping from job type name to the job compiler of that type.
|
||||
registry *require.Registry // Goja module registry.
|
||||
compilers map[string]Compiler // Mapping from job type name to the job compiler of that type.
|
||||
registry *require.Registry // Goja module registry.
|
||||
timeService TimeService
|
||||
}
|
||||
|
||||
type Compiler struct {
|
||||
@ -57,9 +58,16 @@ type VM struct {
|
||||
// jobCompileFunc is a function that fills job.Tasks.
|
||||
type jobCompileFunc func(job *AuthoredJob) error
|
||||
|
||||
func Load() (*Service, error) {
|
||||
// TimeService is a service that can tell the current time.
|
||||
type TimeService interface {
|
||||
Now() time.Time
|
||||
}
|
||||
|
||||
// Load returns a job compiler service with all JS files loaded.
|
||||
func Load(ts TimeService) (*Service, error) {
|
||||
compiler := Service{
|
||||
compilers: map[string]Compiler{},
|
||||
compilers: map[string]Compiler{},
|
||||
timeService: ts,
|
||||
}
|
||||
|
||||
if err := compiler.loadScripts(); err != nil {
|
||||
@ -94,6 +102,7 @@ func (s *Service) Compile(ctx context.Context, sj api.SubmittedJob) (*AuthoredJo
|
||||
// Create an AuthoredJob from this SubmittedJob.
|
||||
aj := AuthoredJob{
|
||||
JobID: uuid.New().String(), // Ignore the submitted ID.
|
||||
Created: s.timeService.Now(),
|
||||
Name: sj.Name,
|
||||
JobType: sj.Type,
|
||||
Priority: sj.Priority,
|
||||
@ -103,14 +112,13 @@ func (s *Service) Compile(ctx context.Context, sj api.SubmittedJob) (*AuthoredJo
|
||||
Metadata: make(JobMetadata),
|
||||
}
|
||||
if sj.Settings != nil {
|
||||
for key, value := range *sj.Settings {
|
||||
for key, value := range sj.Settings.AdditionalProperties {
|
||||
aj.Settings[key] = value
|
||||
}
|
||||
}
|
||||
if sj.Metadata != nil {
|
||||
for key, value := range *sj.Metadata {
|
||||
// TODO: make sure OpenAPI understands these keys can only be strings.
|
||||
aj.Metadata[key] = value.(string)
|
||||
for key, value := range sj.Metadata.AdditionalProperties {
|
||||
aj.Metadata[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
|
115
internal/manager/job_compilers/job_compilers_test.go
Normal file
115
internal/manager/job_compilers/job_compilers_test.go
Normal file
@ -0,0 +1,115 @@
|
||||
// Package job_compilers contains functionality to convert a Flamenco job
|
||||
// definition into concrete tasks and commands to execute by Workers.
|
||||
package job_compilers
|
||||
|
||||
/* ***** BEGIN GPL LICENSE BLOCK *****
|
||||
*
|
||||
* Original Code Copyright (C) 2022 Blender Foundation.
|
||||
*
|
||||
* This file is part of Flamenco.
|
||||
*
|
||||
* Flamenco is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, either version 3 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* Flamenco is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* Flamenco. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* ***** END GPL LICENSE BLOCK ***** */
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/benbjohnson/clock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gitlab.com/blender/flamenco-goja-test/pkg/api"
|
||||
)
|
||||
|
||||
func exampleSubmittedJob() api.SubmittedJob {
|
||||
settings := api.JobSettings{
|
||||
AdditionalProperties: map[string]interface{}{
|
||||
"blender_cmd": "{blender}",
|
||||
"chunk_size": 3,
|
||||
"extract_audio": true,
|
||||
"filepath": "/render/sf/jobs/scene123.blend",
|
||||
"format": "PNG",
|
||||
"fps": 24,
|
||||
"frames": "1-10",
|
||||
"images_or_video": "images",
|
||||
"output_file_extension": ".png",
|
||||
"render_output": "/render/sf/frames/scene123",
|
||||
}}
|
||||
metadata := api.JobMetadata{
|
||||
AdditionalProperties: map[string]string{
|
||||
"project": "Sprite Fright",
|
||||
"user.email": "sybren@blender.org",
|
||||
"user.name": "Sybren Stüvel",
|
||||
}}
|
||||
sj := api.SubmittedJob{
|
||||
Name: "3Д рендеринг",
|
||||
Priority: 50,
|
||||
Type: "simple-blender-render",
|
||||
Settings: &settings,
|
||||
Metadata: &metadata,
|
||||
}
|
||||
return sj
|
||||
}
|
||||
|
||||
func mockedClock(t *testing.T) clock.Clock {
|
||||
c := clock.NewMock()
|
||||
now, err := time.Parse(time.RFC3339, "2006-01-02T15:04:05+07:00")
|
||||
assert.NoError(t, err)
|
||||
c.Set(now)
|
||||
return c
|
||||
}
|
||||
|
||||
func TestSimpleBlenderRenderHappy(t *testing.T) {
|
||||
c := mockedClock(t)
|
||||
|
||||
s, err := Load(c)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Compiling a job should be really fast.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
sj := exampleSubmittedJob()
|
||||
aj, err := s.Compile(ctx, sj)
|
||||
if err != nil {
|
||||
t.Logf("job compiler failed: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
assert.NotNil(t, aj)
|
||||
if aj == nil {
|
||||
// Don't bother with the rest of the test, it'll dereference a nil pointer anyway.
|
||||
return
|
||||
}
|
||||
|
||||
// Properties should be copied as-is.
|
||||
assert.Equal(t, sj.Name, aj.Name)
|
||||
assert.Equal(t, sj.Type, aj.JobType)
|
||||
assert.Equal(t, sj.Priority, aj.Priority)
|
||||
assert.EqualValues(t, sj.Settings.AdditionalProperties, aj.Settings)
|
||||
assert.EqualValues(t, sj.Metadata.AdditionalProperties, aj.Metadata)
|
||||
|
||||
settings := sj.Settings.AdditionalProperties
|
||||
|
||||
// Tasks should have been created to render the frames.
|
||||
assert.Equal(t, 4, len(aj.Tasks))
|
||||
t0 := aj.Tasks[0]
|
||||
assert.Equal(t, "render-1-3", t0.Name)
|
||||
assert.Equal(t, 1, len(t0.Commands))
|
||||
assert.Equal(t, "blender-render", t0.Commands[0].Type)
|
||||
assert.Equal(t, "{blender}", t0.Commands[0].Parameters["cmd"])
|
||||
assert.Equal(t, settings["filepath"], t0.Commands[0].Parameters["filepath"])
|
||||
assert.Equal(t, settings["format"], t0.Commands[0].Parameters["format"])
|
||||
assert.Equal(t, "1-3", t0.Commands[0].Parameters["frames"])
|
||||
assert.Equal(t, "/render/sf__intermediate-2006-01-02_090405/frames", t0.Commands[0].Parameters["render_output"])
|
||||
}
|
@ -21,16 +21,189 @@ package job_compilers
|
||||
* ***** END GPL LICENSE BLOCK ***** */
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func jsPrint(call goja.FunctionCall) goja.Value {
|
||||
// ----------------------------------------------------------
|
||||
// Functions that start with `JS` are exposed to JavaScript.
|
||||
// See newGojaVM() for the actual expose-as-globals code.
|
||||
// ----------------------------------------------------------
|
||||
|
||||
func JSPrint(call goja.FunctionCall) goja.Value {
|
||||
log.Info().Interface("args", call.Arguments).Msg("print")
|
||||
return goja.Undefined()
|
||||
}
|
||||
|
||||
func jsAlert(call goja.FunctionCall) goja.Value {
|
||||
func JSAlert(call goja.FunctionCall) goja.Value {
|
||||
log.Warn().Interface("args", call.Arguments).Msg("alert")
|
||||
return goja.Undefined()
|
||||
}
|
||||
|
||||
// JSFormatTimestampLocal returns the timestamp formatted as local time in a way that's compatible with filenames.
|
||||
func JSFormatTimestampLocal(timestamp time.Time) string {
|
||||
return timestamp.Local().Format("2006-01-02_150405")
|
||||
}
|
||||
|
||||
type ErrInvalidRange struct {
|
||||
Range string // The frame range that was invalid.
|
||||
Message string // The error message
|
||||
err error // Any wrapped error
|
||||
}
|
||||
|
||||
func (e ErrInvalidRange) Error() string {
|
||||
if e.err != nil {
|
||||
return fmt.Sprintf("invalid range \"%v\": %s (%s)", e.Range, e.Message, e.Error())
|
||||
}
|
||||
return fmt.Sprintf("invalid range \"%v\": %s", e.Range, e.Message)
|
||||
}
|
||||
|
||||
func (e ErrInvalidRange) Unwrap() error {
|
||||
return e.err
|
||||
}
|
||||
|
||||
func errInvalidRange(theRange, message string, errs ...error) error {
|
||||
e := ErrInvalidRange{
|
||||
Range: theRange,
|
||||
Message: message,
|
||||
}
|
||||
for _, err := range errs {
|
||||
if err != nil {
|
||||
e.err = err
|
||||
break
|
||||
}
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
const (
|
||||
chunkRegular = "-"
|
||||
chunkBlender = ".."
|
||||
)
|
||||
|
||||
// JSFrameChunker takes a range like "1..10,20..25,40" and returns chunked ranges.
|
||||
//
|
||||
// The returned ranges will be at most `chunkSize` frames long.
|
||||
//
|
||||
// Supports "regular" and "blender" notation, resp. "A-Z" and "A..Z". Returned
|
||||
// chunks will always be in "regular" notation because they're more compatible
|
||||
// with embedding in filenames.
|
||||
func JSFrameChunker(frameRange string, chunkSize int) ([]string, error) {
|
||||
frameRange = strings.TrimSpace(frameRange)
|
||||
if len(frameRange) == 0 {
|
||||
return nil, errInvalidRange(frameRange, "empty range")
|
||||
}
|
||||
if chunkSize < 1 {
|
||||
return nil, fmt.Errorf("invalid chunk size, must be positive number: %d", chunkSize)
|
||||
}
|
||||
|
||||
frames, err := frameRangeExplode(frameRange)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(frames) == 0 {
|
||||
return nil, errInvalidRange(frameRange, "empty range")
|
||||
}
|
||||
|
||||
min := func(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
var i int
|
||||
chunks := make([]string, 0)
|
||||
for i = 0; i < len(frames); i += chunkSize {
|
||||
chunkFrames := frames[i:min(i+chunkSize, len(frames))]
|
||||
chunkRange := frameRangeMerge(chunkFrames)
|
||||
chunks = append(chunks, chunkRange)
|
||||
}
|
||||
|
||||
return chunks, nil
|
||||
}
|
||||
|
||||
// Given a range of frames, return an array containing each frame number.
|
||||
func frameRangeExplode(frameRange string) ([]int, error) {
|
||||
// Store as map to avoid duplicate frames.
|
||||
frames := make(map[int]struct{}, 0)
|
||||
|
||||
// Convert from "blender" to "regular" range notation.
|
||||
frameRange = strings.ReplaceAll(frameRange, chunkBlender, chunkRegular)
|
||||
|
||||
// parseInt first trims whitespace before converting to integer.
|
||||
parseInt := func(s string) (int64, error) {
|
||||
return strconv.ParseInt(strings.TrimSpace(s), 10, 64)
|
||||
}
|
||||
|
||||
// Explode each comma-separated frame range.
|
||||
for _, part := range strings.Split(frameRange, ",") {
|
||||
startEnd := strings.Split(part, chunkRegular)
|
||||
switch len(startEnd) {
|
||||
case 1: // Single frame
|
||||
frame, err := parseInt(startEnd[0])
|
||||
if err != nil {
|
||||
return nil, errInvalidRange(frameRange, part, err)
|
||||
}
|
||||
frames[int(frame)] = struct{}{}
|
||||
case 2: // Frame range A-B
|
||||
startFrame, startErr := parseInt(startEnd[0])
|
||||
endFrame, endErr := parseInt(startEnd[1])
|
||||
if startErr != nil || endErr != nil {
|
||||
return nil, errInvalidRange(frameRange, part, startErr, endErr)
|
||||
}
|
||||
for frame := startFrame; frame <= endFrame; frame++ {
|
||||
frames[int(frame)] = struct{}{}
|
||||
}
|
||||
default:
|
||||
return nil, errInvalidRange(frameRange, part)
|
||||
}
|
||||
}
|
||||
|
||||
// Convert from map to sorted array.
|
||||
frameList := make([]int, 0, len(frames))
|
||||
for frame := range frames {
|
||||
frameList = append(frameList, frame)
|
||||
}
|
||||
sort.Ints(frameList)
|
||||
return frameList, nil
|
||||
}
|
||||
|
||||
// frameRangeMerge merges consecutive frames into ranges like "3..8,13,15..17".
|
||||
func frameRangeMerge(frames []int) string {
|
||||
startFrame := frames[0]
|
||||
prevFrame := frames[0]
|
||||
|
||||
ranges := make([]string, 0)
|
||||
|
||||
appendRange := func(fromFrame, toFrame int) {
|
||||
switch {
|
||||
case fromFrame == toFrame: // Last range was one frame only
|
||||
ranges = append(ranges, strconv.FormatInt(int64(fromFrame), 10))
|
||||
case fromFrame+1 == toFrame: // Last range was only two frames
|
||||
ranges = append(ranges, strconv.FormatInt(int64(fromFrame), 10))
|
||||
ranges = append(ranges, strconv.FormatInt(int64(toFrame), 10))
|
||||
default:
|
||||
ranges = append(ranges, fmt.Sprintf("%v%s%v", fromFrame, chunkRegular, toFrame))
|
||||
}
|
||||
}
|
||||
|
||||
var currentFrame int
|
||||
for _, currentFrame = range frames {
|
||||
if currentFrame > prevFrame+1 {
|
||||
// This frame starts a new range, so append the one we now know ended.
|
||||
appendRange(startFrame, prevFrame)
|
||||
startFrame = currentFrame
|
||||
}
|
||||
prevFrame = currentFrame
|
||||
}
|
||||
appendRange(startFrame, currentFrame)
|
||||
|
||||
return strings.Join(ranges, ",")
|
||||
}
|
||||
|
70
internal/manager/job_compilers/js_globals_test.go
Normal file
70
internal/manager/job_compilers/js_globals_test.go
Normal file
@ -0,0 +1,70 @@
|
||||
package job_compilers
|
||||
|
||||
/* ***** BEGIN GPL LICENSE BLOCK *****
|
||||
*
|
||||
* Original Code Copyright (C) 2022 Blender Foundation.
|
||||
*
|
||||
* This file is part of Flamenco.
|
||||
*
|
||||
* Flamenco is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, either version 3 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* Flamenco is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* Flamenco. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* ***** END GPL LICENSE BLOCK ***** */
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFrameChunkerHappyBlenderStyle(t *testing.T) {
|
||||
chunks, err := JSFrameChunker("1..10,20..25,40,3..8", 4)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, []string{"1-4", "5-8", "9,10,20,21", "22-25", "40"}, chunks)
|
||||
}
|
||||
|
||||
func TestFrameChunkerHappySmallInput(t *testing.T) {
|
||||
// No frames, should be an error
|
||||
chunks, err := JSFrameChunker(" ", 4)
|
||||
assert.ErrorIs(t, err, ErrInvalidRange{Message: "empty range"})
|
||||
|
||||
// Just one frame.
|
||||
chunks, err = JSFrameChunker("47", 4)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, []string{"47"}, chunks)
|
||||
|
||||
// Just one range of exactly one chunk.
|
||||
chunks, err = JSFrameChunker("1-3", 3)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, []string{"1-3"}, chunks)
|
||||
}
|
||||
|
||||
func TestFrameChunkerHappyRegularStyle(t *testing.T) {
|
||||
chunks, err := JSFrameChunker("1-10,20-25,40", 4)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, []string{"1-4", "5-8", "9,10,20,21", "22-25", "40"}, chunks)
|
||||
}
|
||||
|
||||
func TestFrameChunkerHappyExtraWhitespace(t *testing.T) {
|
||||
chunks, err := JSFrameChunker(" 1 .. 10,\t20..25\n,40 ", 4)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, []string{"1-4", "5-8", "9,10,20,21", "22-25", "40"}, chunks)
|
||||
}
|
||||
|
||||
func TestFrameRangeExplode(t *testing.T) {
|
||||
frames, err := frameRangeExplode("1..10,20..25,40")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, []int{
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
|
||||
20, 21, 22, 23, 24, 25, 40,
|
||||
}, frames)
|
||||
}
|
@ -96,9 +96,11 @@ func (s *Service) newGojaVM() *goja.Runtime {
|
||||
}
|
||||
}
|
||||
|
||||
// Set some global functions for script debugging purposes.
|
||||
mustSet("print", jsPrint)
|
||||
mustSet("alert", jsAlert)
|
||||
// Set some global functions.
|
||||
mustSet("print", JSPrint)
|
||||
mustSet("alert", JSAlert)
|
||||
mustSet("frameChunker", JSFrameChunker)
|
||||
mustSet("formatTimestampLocal", JSFormatTimestampLocal)
|
||||
|
||||
// Pre-import some useful modules.
|
||||
s.registry.Enable(vm)
|
||||
|
@ -81,20 +81,14 @@ function compileJob(job) {
|
||||
// Determine the intermediate render output path.
|
||||
function intermediatePath(job, finalDir) {
|
||||
const basename = path.basename(finalDir);
|
||||
const name = `${basename}__intermediate-${job.created}`;
|
||||
const name = `${basename}__intermediate-${formatTimestampLocal(job.created)}`;
|
||||
return path.join(path.dirname(finalDir), name);
|
||||
}
|
||||
|
||||
function frameChunker(frames, callback) {
|
||||
// TODO: actually implement.
|
||||
callback("1-10");
|
||||
callback("11-20");
|
||||
callback("21-30");
|
||||
}
|
||||
|
||||
function authorRenderTasks(settings, renderDir, renderOutput) {
|
||||
let renderTasks = [];
|
||||
frameChunker(settings.frames, function(chunk) {
|
||||
let chunks = frameChunker(settings.frames, settings.chunk_size);
|
||||
for (let chunk of chunks) {
|
||||
const task = author.Task(`render-${chunk}`);
|
||||
const command = author.Command("blender-render", {
|
||||
cmd: settings.blender_cmd,
|
||||
@ -105,7 +99,7 @@ function authorRenderTasks(settings, renderDir, renderOutput) {
|
||||
});
|
||||
task.addCommand(command);
|
||||
renderTasks.push(task);
|
||||
});
|
||||
}
|
||||
return renderTasks;
|
||||
}
|
||||
|
||||
|
@ -114,10 +114,7 @@ func (db *DB) FetchJob(ctx context.Context, jobID string) (*api.Job, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: make settings and metadata an explicit type.
|
||||
settings := make(map[string]interface{})
|
||||
metadata := make(map[string]interface{})
|
||||
|
||||
var settings api.JobSettings
|
||||
rows, err := db.sqldb.QueryContext(ctx, "SELECT key, value FROM job_settings WHERE job_id=?", jobID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -128,7 +125,7 @@ func (db *DB) FetchJob(ctx context.Context, jobID string) (*api.Job, error) {
|
||||
if err := rows.Scan(&key, &value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
settings[key] = value
|
||||
settings.AdditionalProperties[key] = value
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
@ -137,6 +134,7 @@ func (db *DB) FetchJob(ctx context.Context, jobID string) (*api.Job, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var metadata api.JobMetadata
|
||||
rows, err = db.sqldb.QueryContext(ctx, "SELECT key, value FROM job_metadata WHERE job_id=?", jobID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -147,7 +145,7 @@ func (db *DB) FetchJob(ctx context.Context, jobID string) (*api.Job, error) {
|
||||
if err := rows.Scan(&key, &value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
metadata[key] = value
|
||||
metadata.AdditionalProperties[key] = value
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
|
@ -261,12 +261,9 @@ components:
|
||||
properties:
|
||||
"name": {type: string}
|
||||
"type": {type: string}
|
||||
"status": {$ref: "#/components/schemas/JobStatus"}
|
||||
"priority": {type: integer, default: 50}
|
||||
"settings":
|
||||
type: object
|
||||
"metadata":
|
||||
type: object
|
||||
"settings": {$ref: "#/components/schemas/JobSettings"}
|
||||
"metadata": {$ref: "#/components/schemas/JobMetadata"}
|
||||
required: [name, type, priority]
|
||||
example:
|
||||
type: "simple-blender-render"
|
||||
@ -286,7 +283,7 @@ components:
|
||||
metadata:
|
||||
"user.name": "Sybren Stüvel"
|
||||
"user.email": "sybren@blender.org"
|
||||
project: "Sprite Fright"
|
||||
"project": "Sprite Fright"
|
||||
Job:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/SubmittedJob'
|
||||
@ -303,8 +300,22 @@ components:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Creation timestamp
|
||||
required: [id, created, updated]
|
||||
status: {$ref: "#/components/schemas/JobStatus"}
|
||||
required: [id, created, updated, status]
|
||||
|
||||
JobSettings:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
|
||||
JobMetadata:
|
||||
type: object
|
||||
description: Arbitrary metadata strings. More complex structures can be modeled by using `a.b.c` notation for the key.
|
||||
additionalProperties:
|
||||
type: string
|
||||
example:
|
||||
"user.name": Sybren Stüvel
|
||||
"user.email": sybren@blender.org
|
||||
"project": "Sprite Fright"
|
||||
|
||||
Error:
|
||||
type: object
|
||||
|
@ -18,48 +18,50 @@ import (
|
||||
// Base64 encoded, gzipped, json marshaled Swagger object
|
||||
var swaggerSpec = []string{
|
||||
|
||||
"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",
|
||||
"+iN+cj7sCKYt+aj3/nL/3wAAAP//aU95WLUhAAA=",
|
||||
"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",
|
||||
"///WNQTcViMAAA==",
|
||||
}
|
||||
|
||||
// GetSwagger returns the content of the embedded swagger specification file
|
||||
|
@ -4,6 +4,8 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -167,12 +169,23 @@ type Job struct {
|
||||
Created time.Time `json:"created"`
|
||||
|
||||
// UUID of the Job
|
||||
Id string `json:"id"`
|
||||
Id string `json:"id"`
|
||||
Status JobStatus `json:"status"`
|
||||
|
||||
// Creation timestamp
|
||||
Updated time.Time `json:"updated"`
|
||||
}
|
||||
|
||||
// Arbitrary metadata strings. More complex structures can be modeled by using `a.b.c` notation for the key.
|
||||
type JobMetadata struct {
|
||||
AdditionalProperties map[string]string `json:"-"`
|
||||
}
|
||||
|
||||
// JobSettings defines model for JobSettings.
|
||||
type JobSettings struct {
|
||||
AdditionalProperties map[string]interface{} `json:"-"`
|
||||
}
|
||||
|
||||
// JobStatus defines model for JobStatus.
|
||||
type JobStatus string
|
||||
|
||||
@ -195,12 +208,12 @@ type SecurityError struct {
|
||||
|
||||
// Job definition submitted to Flamenco.
|
||||
type SubmittedJob struct {
|
||||
Metadata *map[string]interface{} `json:"metadata,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Priority int `json:"priority"`
|
||||
Settings *map[string]interface{} `json:"settings,omitempty"`
|
||||
Status *JobStatus `json:"status,omitempty"`
|
||||
Type string `json:"type"`
|
||||
// Arbitrary metadata strings. More complex structures can be modeled by using `a.b.c` notation for the key.
|
||||
Metadata *JobMetadata `json:"metadata,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Priority int `json:"priority"`
|
||||
Settings *JobSettings `json:"settings,omitempty"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// TaskStatus defines model for TaskStatus.
|
||||
@ -225,3 +238,109 @@ type SubmitJobJSONRequestBody SubmitJobJSONBody
|
||||
|
||||
// RegisterWorkerJSONRequestBody defines body for RegisterWorker for application/json ContentType.
|
||||
type RegisterWorkerJSONRequestBody RegisterWorkerJSONBody
|
||||
|
||||
// Getter for additional properties for JobMetadata. Returns the specified
|
||||
// element and whether it was found
|
||||
func (a JobMetadata) Get(fieldName string) (value string, found bool) {
|
||||
if a.AdditionalProperties != nil {
|
||||
value, found = a.AdditionalProperties[fieldName]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Setter for additional properties for JobMetadata
|
||||
func (a *JobMetadata) Set(fieldName string, value string) {
|
||||
if a.AdditionalProperties == nil {
|
||||
a.AdditionalProperties = make(map[string]string)
|
||||
}
|
||||
a.AdditionalProperties[fieldName] = value
|
||||
}
|
||||
|
||||
// Override default JSON handling for JobMetadata to handle AdditionalProperties
|
||||
func (a *JobMetadata) UnmarshalJSON(b []byte) error {
|
||||
object := make(map[string]json.RawMessage)
|
||||
err := json.Unmarshal(b, &object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(object) != 0 {
|
||||
a.AdditionalProperties = make(map[string]string)
|
||||
for fieldName, fieldBuf := range object {
|
||||
var fieldVal string
|
||||
err := json.Unmarshal(fieldBuf, &fieldVal)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err)
|
||||
}
|
||||
a.AdditionalProperties[fieldName] = fieldVal
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Override default JSON handling for JobMetadata to handle AdditionalProperties
|
||||
func (a JobMetadata) MarshalJSON() ([]byte, error) {
|
||||
var err error
|
||||
object := make(map[string]json.RawMessage)
|
||||
|
||||
for fieldName, field := range a.AdditionalProperties {
|
||||
object[fieldName], err = json.Marshal(field)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error marshaling '%s': %w", fieldName, err)
|
||||
}
|
||||
}
|
||||
return json.Marshal(object)
|
||||
}
|
||||
|
||||
// Getter for additional properties for JobSettings. Returns the specified
|
||||
// element and whether it was found
|
||||
func (a JobSettings) Get(fieldName string) (value interface{}, found bool) {
|
||||
if a.AdditionalProperties != nil {
|
||||
value, found = a.AdditionalProperties[fieldName]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Setter for additional properties for JobSettings
|
||||
func (a *JobSettings) Set(fieldName string, value interface{}) {
|
||||
if a.AdditionalProperties == nil {
|
||||
a.AdditionalProperties = make(map[string]interface{})
|
||||
}
|
||||
a.AdditionalProperties[fieldName] = value
|
||||
}
|
||||
|
||||
// Override default JSON handling for JobSettings to handle AdditionalProperties
|
||||
func (a *JobSettings) UnmarshalJSON(b []byte) error {
|
||||
object := make(map[string]json.RawMessage)
|
||||
err := json.Unmarshal(b, &object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(object) != 0 {
|
||||
a.AdditionalProperties = make(map[string]interface{})
|
||||
for fieldName, fieldBuf := range object {
|
||||
var fieldVal interface{}
|
||||
err := json.Unmarshal(fieldBuf, &fieldVal)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err)
|
||||
}
|
||||
a.AdditionalProperties[fieldName] = fieldVal
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Override default JSON handling for JobSettings to handle AdditionalProperties
|
||||
func (a JobSettings) MarshalJSON() ([]byte, error) {
|
||||
var err error
|
||||
object := make(map[string]json.RawMessage)
|
||||
|
||||
for fieldName, field := range a.AdditionalProperties {
|
||||
object[fieldName], err = json.Marshal(field)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error marshaling '%s': %w", fieldName, err)
|
||||
}
|
||||
}
|
||||
return json.Marshal(object)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user