Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 20 additions & 25 deletions app/cli/internal/action/testdata/schemas/basic.json
Original file line number Diff line number Diff line change
@@ -1,30 +1,25 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://github.com/chainloop-dev/chainloop/app/controlplane/extensions/core/dependencytrack/v1/registration-request",
"$ref": "#/$defs/registrationRequest",
"$defs": {
"registrationRequest": {
"properties": {
"instanceURI": {
"type": "string",
"format": "uri",
"description": "The URL of the Dependency-Track instance"
},
"apiKey": {
"type": "string",
"description": "The API key to use for authentication"
},
"port": {
"type": "number"
},
"allowAutoCreate": {
"type": "boolean",
"description": "Support of creating projects on demand"
}
},
"additionalProperties": false,
"type": "object",
"required": ["instanceURI", "apiKey"]
"properties": {
"instanceURI": {
"type": "string",
"format": "uri",
"description": "The URL of the Dependency-Track instance"
},
"apiKey": {
"type": "string",
"description": "The API key to use for authentication"
},
"allowAutoCreate": {
"type": "boolean",
"description": "Support of creating projects on demand"
},
"port": {
"type": "number"
}
}
},
"additionalProperties": false,
"type": "object",
"required": ["instanceURI", "apiKey"]
}
33 changes: 31 additions & 2 deletions app/controlplane/extensions/sdk/v1/fanout.go
Original file line number Diff line number Diff line change
Expand Up @@ -375,8 +375,37 @@ func FromConfig(data Configuration, v any) error {
return json.Unmarshal(data, v)
}

// generate a JSON schema from a struct, see
// generate a flat JSON schema from a struct using https://github.com/invopop/jsonschema
// We've put some limitations on the kind of input structs we support, for example:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a very good point!

// - Nested schemas are not supported
// - Array based properties are not supported

func generateJSONSchema(schema any) ([]byte, error) {
s := jsonschema.Reflect(schema)
if schema == nil {
return nil, fmt.Errorf("schema is nil")
}

r := &jsonschema.Reflector{}
// Set top-level properties flattened
// https://github.com/invopop/jsonschema#expandedstruct
r.ExpandedStruct = true

s := r.Reflect(schema)

// Double check that the schema is valid
// Nested schemas are not supported
if len(s.Definitions) > 0 {
return nil, fmt.Errorf("nested schemas are not supported")
}

// Array based properties are not supported
for _, k := range s.Properties.Keys() {
p, _ := s.Properties.Get(k)
s := p.(*jsonschema.Schema)
if s.Items != nil {
return nil, fmt.Errorf("array based properties are not supported")
}
}

return json.Marshal(s)
}
34 changes: 19 additions & 15 deletions app/controlplane/extensions/sdk/v1/fanout_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,13 @@ import (
"github.com/stretchr/testify/require"
)

type schema struct {
TestProperty string `json:"testProperty"`
}

var inputSchema = &sdk.InputSchema{
Registration: struct{ TestProperty string }{TestProperty: "test"},
Attachment: struct{ TestProperty string }{TestProperty: "test"},
Registration: schema{},
Attachment: schema{},
}

func TestNewBaseIntegration(t *testing.T) {
Expand Down Expand Up @@ -192,13 +196,13 @@ func TestString(t *testing.T) {
}
}

func TestValidateRegistrationRequest(t *testing.T) {
var schema struct {
Username string `json:"username"`
Email string `json:"email" jsonschema:"format=email"`
Optional int `json:"optional,omitempty"`
}
type registrationSchema struct {
Username string `json:"username"`
Email string `json:"email" jsonschema:"format=email"`
Optional int `json:"optional,omitempty"`
}

func TestValidateRegistrationRequest(t *testing.T) {
testCases := []struct {
name string
input map[string]interface{}
Expand Down Expand Up @@ -250,7 +254,7 @@ func TestValidateRegistrationRequest(t *testing.T) {
got, err := sdk.NewFanOut(
&sdk.NewParams{
ID: "ID", Version: "123",
InputSchema: &sdk.InputSchema{Registration: &schema, Attachment: struct{}{}},
InputSchema: &sdk.InputSchema{Registration: &registrationSchema{}, Attachment: &attachmentSchema{}},
}, sdk.WithEnvelope())

require.NoError(t, err)
Expand All @@ -267,12 +271,12 @@ func TestValidateRegistrationRequest(t *testing.T) {
}
}

func TestValidateAttachmentRequest(t *testing.T) {
var schema struct {
ProjectID int `json:"projectID,omitempty" jsonschema:"oneof_required=projectID,minLength=1"`
ProjectName string `json:"projectName,omitempty" jsonschema:"oneof_required=projectName,minLength=1"`
}
type attachmentSchema struct {
ProjectID int `json:"projectID,omitempty" jsonschema:"oneof_required=projectID,minLength=1"`
ProjectName string `json:"projectName,omitempty" jsonschema:"oneof_required=projectName,minLength=1"`
}

func TestValidateAttachmentRequest(t *testing.T) {
testCases := []struct {
name string
input map[string]interface{}
Expand Down Expand Up @@ -317,7 +321,7 @@ func TestValidateAttachmentRequest(t *testing.T) {
got, err := sdk.NewFanOut(
&sdk.NewParams{
ID: "ID", Version: "123",
InputSchema: &sdk.InputSchema{Registration: struct{}{}, Attachment: &schema},
InputSchema: &sdk.InputSchema{Registration: &registrationSchema{}, Attachment: &attachmentSchema{}},
}, sdk.WithEnvelope())

require.NoError(t, err)
Expand Down
80 changes: 80 additions & 0 deletions app/controlplane/extensions/sdk/v1/fanout_unit_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//
// Copyright 2023 The Chainloop Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package sdk

import (
"testing"

"github.com/stretchr/testify/assert"
)

type emptySchema struct {
}

type basicSchema struct {
Foo string `json:"foo"`
}

type arrayBased struct {
FooA []string
}

type nested struct {
Bar string `json:"bar"`
Foo basicSchema
}

func TestGenerateJSONSchema(t *testing.T) {
testCases := []struct {
name string
input interface{}
wantErrMsg string
}{
{
name: "invalid, missing schema",
wantErrMsg: "schema is nil",
},
{
name: "valid empty schema",
input: emptySchema{},
},
{
name: "valid input",
input: basicSchema{},
},
{
name: "invalid, nested",
input: nested{},
wantErrMsg: "nested schemas are not supported",
},
{
name: "invalid, array property",
input: arrayBased{},
wantErrMsg: "array based properties are not supported",
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
_, err := generateJSONSchema(tc.input)
if tc.wantErrMsg != "" {
assert.ErrorContains(t, err, tc.wantErrMsg)
} else {
assert.NoError(t, err)
}
})
}
}
6 changes: 5 additions & 1 deletion app/controlplane/internal/dispatcher/dispatcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,11 @@ func (s *dispatcherTestSuite) SetupTest() {
customImplementation := mockedSDK.NewFanOutExtension(s.T())
customImplementation.On("Register", ctx, mock.Anything).Return(&sdk.RegistrationResponse{Configuration: []byte("deadbeef")}, nil)
customImplementation.On("Attach", ctx, mock.Anything).Return(&sdk.AttachmentResponse{Configuration: []byte("deadbeef")}, nil)
fanOutSchemas := &sdk.InputSchema{Registration: struct{ TestProperty string }{}, Attachment: struct{ TestProperty string }{}}
type schema struct {
TestProperty string
}

fanOutSchemas := &sdk.InputSchema{Registration: schema{}, Attachment: schema{}}

b, err := sdk.NewFanOut(
&sdk.NewParams{
Expand Down