Skip to content

Commit

Permalink
Merge pull request #850 from Permify/next
Browse files Browse the repository at this point in the history
Next
  • Loading branch information
tolgaOzen authored Nov 18, 2023
2 parents b845dda + dc50bc7 commit fdbe100
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 41 deletions.
25 changes: 25 additions & 0 deletions docs/docs/getting-started/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,31 @@ Below you can examine an example schema validation yaml file. It consists 3 part
- `relationships` sample data to test your model,
- `scenarios` to test access check queries within created scenarios.

### Defining the Schema:

You can define the `schema` in the YAML file in one of two ways:

1. **Directly in the File:** Define the schema directly within the YAML file.

```yaml
schema: >-
entity user {}
entity organization {
...
}
2. **Via URL or File Path:** Specify a URL or a file path to an external schema file.
**Example with URL:**
```yaml
schema: https://example.com/path/to/schema.txt
```
**Example with File Path:**
```yaml
schema: /path/to/your/schema/file.txt
```
Here is an example Schema Validation file,
```yaml
Expand Down
9 changes: 9 additions & 0 deletions pkg/cmd/coverage.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/Permify/permify/pkg/cmd/flags"
cov "github.com/Permify/permify/pkg/development/coverage"
"github.com/Permify/permify/pkg/development/file"
"github.com/Permify/permify/pkg/schema"
)

// NewCoverageCommand - creates a new coverage command
Expand Down Expand Up @@ -62,6 +63,14 @@ func coverage() func(cmd *cobra.Command, args []string) error {
return err
}

loader := schema.NewSchemaLoader()
loaded, err := loader.LoadSchema(s.Schema)
if err != nil {
return err
}

s.Schema = loaded

color.Notice.Println("initiating coverage analysis... 🚀")

schemaCoverageInfo := cov.Run(*s)
Expand Down
4 changes: 2 additions & 2 deletions pkg/cmd/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,12 @@ func validate() func(cmd *cobra.Command, args []string) error {
color.Notice.Println("schema is creating... 🚀")

loader := schema.NewSchemaLoader()
schema, err := loader.LoadSchema(s.Schema)
loaded, err := loader.LoadSchema(s.Schema)
if err != nil {
return err
}

sch, err := parser.NewParser(schema).Parse()
sch, err := parser.NewParser(loaded).Parse()
if err != nil {
return err
}
Expand Down
10 changes: 1 addition & 9 deletions pkg/schema/builder_test.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,13 @@
package schema

import (
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

base "github.com/Permify/permify/pkg/pb/base/v1"
)

// TestBuilder -
func TestBuilder(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "builder-suite")
}

var _ = Describe("compiler", func() {
var _ = Describe("schema", func() {
Context("Schema", func() {
It("Case 1", func() {
is := Schema(
Expand Down
134 changes: 111 additions & 23 deletions pkg/schema/loader.go
Original file line number Diff line number Diff line change
@@ -1,64 +1,138 @@
package schema

import (
"errors"
"fmt"
"io"
"net/http"
"net/url"
"os"
"strings"
"path/filepath"
"regexp"
)

type SchemaType int
// Type defines an enumeration for different schema types.
type Type int

const (
URL SchemaType = iota
// URL represents a schema type for URLs.
URL Type = iota

// File represents a schema type for file paths.
File

// Inline represents a schema type for inline data.
Inline
)

type SchemaLoader struct {
loaders map[SchemaType]func(string) (string, error)
// Loader is a struct that holds a map of loader functions, each corresponding to a Type.
type Loader struct {
// loaders is a map where each Type is associated with a corresponding function
// that takes a string as input and returns a string and an error.
// These functions are responsible for loading data based on the Type.
loaders map[Type]func(string) (string, error)
}

func NewSchemaLoader() *SchemaLoader {
return &SchemaLoader{
loaders: map[SchemaType]func(string) (string, error){
URL: loadFromURL,
File: loadFromFile,
// NewSchemaLoader initializes and returns a new Loader instance.
// It sets up the map of loader functions for each Type.
func NewSchemaLoader() *Loader {
return &Loader{
loaders: map[Type]func(string) (string, error){
// URL loader function for handling URL type schemas.
URL: loadFromURL,

// File loader function for handling file path type schemas.
File: loadFromFile,

// Inline loader function for handling inline type schemas.
Inline: loadInline,
},
}
}

func (s *SchemaLoader) LoadSchema(input string) (string, error) {
return s.loaders[determineSchemaType(input)](input)
// LoadSchema loads a schema based on its type
func (s *Loader) LoadSchema(input string) (string, error) {
schemaType, err := determineSchemaType(input)
if err != nil {
return "", fmt.Errorf("error determining schema type: %w", err)
}

loaderFunc, exists := s.loaders[schemaType]
if !exists {
return "", fmt.Errorf("loader function not found for schema type: %v", schemaType)
}

return loaderFunc(input)
}

func determineSchemaType(input string) SchemaType {
// determineSchemaType determines the type of schema based on the input string
func determineSchemaType(input string) (Type, error) {
if isURL(input) {
return URL
} else if isFilePath(input) {
return File
} else {
return Inline
return URL, nil
}

valid, err := isFilePath(input)
if err != nil {
return Inline, nil
}
if valid {
return File, nil
}

return Inline, nil
}

func isURL(input string) bool {
return strings.HasPrefix(input, "http://") || strings.HasPrefix(input, "https://")
parsedURL, err := url.Parse(input)
if err != nil {
return false
}

// Check if the URL has a valid scheme and host
return parsedURL.Scheme != "" && parsedURL.Host != ""
}

func isFilePath(input string) bool {
func isFilePath(input string) (bool, error) {
// Regex to validate the path format
if match, _ := regexp.MatchString(`^[a-zA-Z0-9_\- /]+$`, input); !match {
return false, errors.New("path format is not valid")
}

_, err := os.Stat(input)
return err == nil
if err != nil {
if os.IsNotExist(err) {
return false, errors.New("file does not exist")
}
if os.IsPermission(err) {
return false, errors.New("permission denied")
}
return false, err
}

return true, nil
}

func loadFromURL(url string) (string, error) {
resp, err := http.Get(url)
func loadFromURL(inputURL string) (string, error) {
// Parse and validate the URL
parsedURL, err := url.Parse(inputURL)
if err != nil {
return "", err
}

// Add checks here to validate the scheme, host, etc., of parsedURL
// For example, ensure the scheme is either http or https
if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" {
return "", errors.New("invalid URL scheme")
}

// Perform the HTTP GET request
resp, err := http.Get(parsedURL.String())
if err != nil {
return "", err
}
defer resp.Body.Close()

// Read the response body
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
Expand All @@ -68,6 +142,14 @@ func loadFromURL(url string) (string, error) {
}

func loadFromFile(path string) (string, error) {
// Clean the path
cleanPath := filepath.Clean(path)

// Check if the cleaned path is trying to traverse directories
if filepath.IsAbs(cleanPath) || filepath.HasPrefix(cleanPath, "..") {
return "", errors.New("invalid file path")
}

content, err := os.ReadFile(path)
if err != nil {
return "", err
Expand All @@ -76,6 +158,12 @@ func loadFromFile(path string) (string, error) {
return string(content), nil
}

// loadInline is a function that handles inline schema types.
func loadInline(schema string) (string, error) {
// Add validation if necessary. For example:
if schema == "" {
return "", errors.New("schema is empty")
}

return schema, nil
}
8 changes: 1 addition & 7 deletions pkg/schema/loader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,13 @@ package schema

import (
"strings"
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

func TestLoader(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Loader Suite")
}

var _ = Describe("Loader", func() {
Describe("LoadSchema function", func() {
Context("LoadSchema function", func() {
It("should load schema from URL", func() {
loader := NewSchemaLoader()
schema, _ := loader.LoadSchema("https://gist.githubusercontent.com/neo773/d50f089c141bf61776c22157413ddbac/raw/ed2eb12108e49fce11be27d0387b8b01912b9d98/gistfile1.txt")
Expand Down
13 changes: 13 additions & 0 deletions pkg/schema/schema_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package schema

import (
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

func TestSchema(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "schema-suite")
}

0 comments on commit fdbe100

Please sign in to comment.