Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactoring #28

Merged
merged 6 commits into from
Nov 29, 2017
Merged
Show file tree
Hide file tree
Changes from 3 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
4 changes: 2 additions & 2 deletions api/messages/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"net/http"

"github.com/fabbricadigitale/scimd/api"
"github.com/fabbricadigitale/scimd/schemas/core"
"github.com/fabbricadigitale/scimd/schemas/datatype"
)

//ErrorURI error message urn
Expand All @@ -28,7 +28,7 @@ func NewError(e error) Error {
case *json.SyntaxError:
scimError.Status = string(http.StatusBadRequest)
scimError.ScimType = "invalidSyntax"
case *core.InvalidaDataTypeError:
case *datatype.InvalidaDataTypeError:
scimError.Status = string(http.StatusBadRequest)
scimError.ScimType = "invalidValue"
case *json.UnmarshalTypeError:
Expand Down
10 changes: 5 additions & 5 deletions api/messages/error_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,18 @@ import (
"net/http"
"testing"

"github.com/fabbricadigitale/scimd/schemas/core"
"github.com/fabbricadigitale/scimd/schemas/datatype"
"github.com/stretchr/testify/require"
)

func TestNewError(t *testing.T) {

// Unexpected Data Type
var s = "randomvalue"
_, err := core.NewDataType(s)
_, err := datatype.New(s)

require.Equal(t, Error{
Schemas: append([]string{}, ErrorURN),
Schemas: append([]string{}, ErrorURI),
Status: string(http.StatusBadRequest),
ScimType: "invalidValue",
Detail: err.Error(),
Expand All @@ -32,7 +32,7 @@ func TestNewError(t *testing.T) {
err = json.Unmarshal([]byte(byt), &p)

require.Equal(t, Error{
Schemas: append([]string{}, ErrorURN),
Schemas: append([]string{}, ErrorURI),
Status: string(http.StatusBadRequest),
ScimType: "invalidValue",
Detail: err.Error(),
Expand All @@ -44,7 +44,7 @@ func TestNewError(t *testing.T) {
err = json.Unmarshal([]byte(byt), &p)

require.Equal(t, Error{
Schemas: append([]string{}, ErrorURN),
Schemas: append([]string{}, ErrorURI),
Status: string(http.StatusBadRequest),
ScimType: "invalidSyntax",
Detail: err.Error(),
Expand Down
2 changes: 1 addition & 1 deletion api/messages/list.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package messages

import (
"github.com/fabbricadigitale/scimd/schemas/core/resource"
"github.com/fabbricadigitale/scimd/schemas/resource"
)

const ListResponseURI = "urn:ietf:params:scim:api:messages:2.0:ListResponse"
Expand Down
30 changes: 17 additions & 13 deletions docs/DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Golang `type` | SCIM Data Type | SCIM Schema "type" | JSON Type |
Rules:
* **JSON Type** must be used when encoding/decoding
* **SCIM Schema "type"** is the set of values that `core.Attribute.Type` can assume
* **SCIM Data Type** `scimd`'s `type`s *(with the same name)* are also defined within the [`core`](../schemas/core/data_type.go) package
* **SCIM Data Type** `scimd`'s `type`s *(with the same name)* are also defined within the [`datatype`](../schemas/datatype/) package
* **Golang `type`** are just the underlying Go `type` (ie. not use them directly)

Indeed:
Expand All @@ -60,23 +60,27 @@ type Complex map[string]interface{}

Furthermore, all `type`s implement:
```go
type DataType interface {
package datatype

// ...

type DataTyper interface {
// ...
}
```

### Singular and Multi-Valued

By convention, the `type` of a **single-value** (for *singular attribute*) is any `type` that implements `DataType`, `type` thus must be one of:
By convention, the `type` of a **single-value** (for *singular attribute*) is any `type` that implements `DataTyper`, `type` thus must be one of:
```go
String, Boolean, Decimal, Integer, DateTime, Binary, Reference, Complex
```

Instead, the `type` of a **multi-value** (for *multi-valued attribute*) must be `[]DataType`.
Instead, the `type` of a **multi-value** (for *multi-valued attribute*) must be `[]DataTyper`.

To check if a value is **single** or **multi**

> A value that's not a `DataType` nor `[]DataType` is considered a **Null** value.
> A value that's not a `DataTyper` nor `[]DataTyper` is considered a **Null** value.

### Unassigned and Null Values

Expand All @@ -99,17 +103,17 @@ Overriding default values when [Creating Resources](https://tools.ietf.org/html/
[Replacing with PUT](https://tools.ietf.org/html/rfc7644#section-3.5.1)
> Clients that want to override a server's defaults MAY specify "null" for a single-valued attribute, or an empty array "[]" for a multi-valued attribute, to clear all values.

**Unassigned** and **Null** values have particular meaning when using `map` (`core.Complex` is a `map` too). Thus, assuming:
**Unassigned** and **Null** values have particular meaning when using `map` (`datatype.Complex` is a `map` too). Thus, assuming:
```go
// m is a map[string]interface{}
v, ok := m[key]
```

You should use:

* `core.IsNull(v)` to check if a value is **Null**
* `!core.IsNull(v)` to check if an attribute's value is NOT **Unassigned** (ie. is assigned)
* `ok && core.IsNull(v)` to check if an attribute's **value must be cleared**
* `datatype.IsNull(v)` to check if a value is **Null**
* `!datatype.IsNull(v)` to check if an attribute's value is NOT **Unassigned** (ie. is assigned)
* `ok && datatype.IsNull(v)` to check if an attribute's **value must be cleared**


## Resources
Expand Down Expand Up @@ -144,18 +148,18 @@ For instance, `core.ServiceProviderConfig` (that's a *Structured Resource*) can
### Mapped Resources

To handle any other type of resource (including *User Resource*, *Group Resource* and new ones may be defined in future),
`scimd` uses `core.resource.Resource` that implements a flexible data rapresentation using Go `map` internally.
`scimd` uses `resource.Resource` that implements a flexible data rapresentation using Go `map` internally.

A *mapped resource*:

* still embed `core.Common` for common attribures (ie. `Schemas`, `ID`, `Meta`, etc)
* has a `map` of `core.Complex` indexed by schema URI
* each `core.Complex` (that's another `map`) can hold values needed by the bound schema
* has a `map` of `datatype.Complex` indexed by schema URI
* each `datatype.Complex` (that's another `map`) can hold values needed by the bound schema

Notes:
> Members of `core.Common` use Go primitive `type`s, instead `map`s hold *Data Types*

> `core.resource.Resource` is **NOT** responsible to enforce attributes structure and characteristics defined by bound schemas *(other APIs are needed to accomplish that)*.
> `resource.Resource` is **NOT** responsible to enforce attributes structure and characteristics defined by bound schemas *(other APIs are needed to accomplish that)*.

In this way, it can represent data for any schemas (or composition of them in case of extensions).

Expand Down
19 changes: 1 addition & 18 deletions schemas/core/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,23 +43,6 @@ func (c *Common) GetCommon() *Common {
return c
}

func (c *Common) GetResourceType() *ResourceType {
func (c *Common) ResourceType() *ResourceType {
return GetResourceTypeRepository().Get(c.Meta.ResourceType)
}

func (c *Common) GetSchema() *Schema {
if rt := c.GetResourceType(); rt != nil {
return GetSchemaRepository().Get(rt.Schema)
}
return nil
}

func (c *Common) GetSchemaExtensions() map[string]*Schema {
repo := GetSchemaRepository()
schExts := c.GetResourceType().SchemaExtensions
schemas := map[string]*Schema{}
for _, ext := range schExts {
schemas[ext.Schema] = repo.Get(ext.Schema)
}
return schemas
}
20 changes: 11 additions & 9 deletions schemas/core/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package core
import (
"encoding/json"
"strings"

"github.com/fabbricadigitale/scimd/schemas/datatype"
)

// ByName returns the *Attribute with the given name, performing a insensitive match. It returns nil if no attribute was found.
Expand Down Expand Up @@ -30,8 +32,8 @@ func byKeyInsensitive(key string, data map[string]json.RawMessage) *json.RawMess
}

// Unmarshal a SCIM a complex value by attributes definition
func (attributes *Attributes) Unmarshal(data map[string]json.RawMessage) (*Complex, error) {
ret := Complex{}
func (attributes *Attributes) Unmarshal(data map[string]json.RawMessage) (*datatype.Complex, error) {
ret := datatype.Complex{}
for _, a := range *attributes {
if part := byKeyInsensitive(a.Name, data); part != nil {
value, err := a.Unmarshal(*part)
Expand All @@ -55,15 +57,15 @@ func (attribute *Attribute) Unmarshal(data json.RawMessage) (interface{}, error)
return unmarshalSingular(attribute, data)
}

func unmarshalSingular(attr *Attribute, data json.RawMessage) (DataType, error) {
func unmarshalSingular(attr *Attribute, data json.RawMessage) (datatype.DataTyper, error) {

if len(data) == 4 && strings.ToLower(string(data)) == "null" {
return nil, nil
}

var err error

if attr.Type == ComplexType {
if attr.Type == datatype.ComplexType {
var subParts map[string]json.RawMessage
if err = json.Unmarshal(data, &subParts); err != nil {
return nil, err
Expand All @@ -75,8 +77,8 @@ func unmarshalSingular(attr *Attribute, data json.RawMessage) (DataType, error)
return nil, err
}

var p DataType
if p, err = NewDataType(attr.Type); err != nil {
var p datatype.DataTyper
if p, err = datatype.New(attr.Type); err != nil {
return nil, err
}

Expand All @@ -86,13 +88,13 @@ func unmarshalSingular(attr *Attribute, data json.RawMessage) (DataType, error)
return p.Value(), nil
}

func unmarshalMulti(attr *Attribute, data json.RawMessage) ([]DataType, error) {
func unmarshalMulti(attr *Attribute, data json.RawMessage) ([]datatype.DataTyper, error) {
var parts []json.RawMessage
if err := json.Unmarshal(data, &parts); err != nil {
return nil, err
}

ret := make([]DataType, len(parts))
ret := make([]datatype.DataTyper, len(parts))

for i, p := range parts {
value, err := unmarshalSingular(attr, p)
Expand All @@ -106,6 +108,6 @@ func unmarshalMulti(attr *Attribute, data json.RawMessage) ([]DataType, error) {
}

// Unmarshal SCIM values by schema definition
func (schema *Schema) Unmarshal(data map[string]json.RawMessage) (*Complex, error) {
func (schema *Schema) Unmarshal(data map[string]json.RawMessage) (*datatype.Complex, error) {
return schema.Attributes.Unmarshal(data)
}
Loading