diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d655aad..b6478e5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,8 +16,6 @@ jobs: matrix: os: [ubuntu-latest, macos-latest] go: [ - '1.16', - '1.17', '1.18', '1.19', '1.20', @@ -25,10 +23,6 @@ jobs: ] include: # Set the minimum Go patch version for the given Go minor - - go: '1.16' - GO_VERSION: '~1.16.0' - - go: '1.17' - GO_VERSION: '~1.17.0' - go: '1.18' GO_VERSION: '~1.18.0' - go: '1.19' diff --git a/README.md b/README.md index 731eecb..5430e63 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,23 @@ [API documentation](https://godoc.org/github.com/leodido/go-urn). +Starting with version 1.3 this library also supports [RFC 7643 SCIM URNs](https://datatracker.ietf.org/doc/html/rfc7643#section-10). + ## Installation ``` go get github.com/leodido/go-urn ``` +## Features + +1. RFC 2141 URNs parsing (default) +2. RFC 7643 SCIM URNs parsing +3. Fallback mode: first try to parse the input as a RFC 7643 SCIM URN, then fallback to RFC 2141 URN generic format. +4. Normalization as per RFC 2141 +4. Lexical equivalence as per RFC 2141 +5. Precise, fine-grained errors + ## Performances This implementation results to be really fast. @@ -24,35 +35,36 @@ Notice it also performs, while parsing: 2. specific-string normalization ``` -ok/00/urn:a:b______________________________________/-4 20000000 265 ns/op 182 B/op 6 allocs/op -ok/01/URN:foo:a123,456_____________________________/-4 30000000 296 ns/op 200 B/op 6 allocs/op -ok/02/urn:foo:a123%2c456___________________________/-4 20000000 331 ns/op 208 B/op 6 allocs/op -ok/03/urn:ietf:params:scim:schemas:core:2.0:User___/-4 20000000 430 ns/op 280 B/op 6 allocs/op -ok/04/urn:ietf:params:scim:schemas:extension:enterp/-4 20000000 411 ns/op 312 B/op 6 allocs/op -ok/05/urn:ietf:params:scim:schemas:extension:enterp/-4 20000000 472 ns/op 344 B/op 6 allocs/op -ok/06/urn:burnout:nss______________________________/-4 30000000 257 ns/op 192 B/op 6 allocs/op -ok/07/urn:abcdefghilmnopqrstuvzabcdefghilm:x_______/-4 20000000 375 ns/op 213 B/op 6 allocs/op -ok/08/urn:urnurnurn:urn____________________________/-4 30000000 265 ns/op 197 B/op 6 allocs/op -ok/09/urn:ciao:@!=%2c(xyz)+a,b.*@g=$_'_____________/-4 20000000 307 ns/op 248 B/op 6 allocs/op -ok/10/URN:x:abc%1dz%2f%3az_________________________/-4 30000000 259 ns/op 212 B/op 6 allocs/op -no/11/URN:-xxx:x___________________________________/-4 20000000 445 ns/op 320 B/op 6 allocs/op -no/12/urn::colon:nss_______________________________/-4 20000000 461 ns/op 320 B/op 6 allocs/op -no/13/urn:abcdefghilmnopqrstuvzabcdefghilmn:specifi/-4 10000000 660 ns/op 320 B/op 6 allocs/op -no/14/URN:a!?:x____________________________________/-4 20000000 507 ns/op 320 B/op 6 allocs/op -no/15/urn:urn:NSS__________________________________/-4 20000000 429 ns/op 288 B/op 6 allocs/op -no/16/urn:white_space:NSS__________________________/-4 20000000 482 ns/op 320 B/op 6 allocs/op -no/17/urn:concat:no_spaces_________________________/-4 20000000 539 ns/op 328 B/op 7 allocs/op -no/18/urn:a:/______________________________________/-4 20000000 470 ns/op 320 B/op 7 allocs/op -no/19/urn:UrN:NSS__________________________________/-4 20000000 399 ns/op 288 B/op 6 allocs/op +Parse/ok/00/urn:a:b______________________________________/-10 71113568 84.88 ns/op 211 B/op 3 allocs/op +Parse/ok/01/URN:foo:a123,456_____________________________/-10 49303754 126.1 ns/op 232 B/op 6 allocs/op +Parse/ok/02/urn:foo:a123%2c456___________________________/-10 46723497 122.1 ns/op 240 B/op 6 allocs/op +Parse/ok/03/urn:ietf:params:scim:schemas:core:2.0:User___/-10 34231863 175.1 ns/op 312 B/op 6 allocs/op +Parse/ok/04/urn:ietf:params:scim:schemas:extension:enterp/-10 25406808 233.6 ns/op 344 B/op 6 allocs/op +Parse/ok/05/urn:ietf:params:scim:schemas:extension:enterp/-10 22353264 265.6 ns/op 376 B/op 6 allocs/op +Parse/ok/06/urn:burnout:nss______________________________/-10 52932087 112.9 ns/op 224 B/op 6 allocs/op +Parse/ok/07/urn:abcdefghilmnopqrstuvzabcdefghilm:x_______/-10 45005554 134.3 ns/op 243 B/op 4 allocs/op +Parse/ok/08/urn:urnurnurn:urn____________________________/-10 46788519 124.7 ns/op 229 B/op 6 allocs/op +Parse/ok/09/urn:ciao:@!=%2c(xyz)+a,b.*@g=$_'_____________/-10 39037539 153.8 ns/op 264 B/op 6 allocs/op +Parse/ok/10/URN:x:abc%1dz%2f%3az_________________________/-10 45692990 121.4 ns/op 243 B/op 5 allocs/op +Parse/no/11/URN:-xxx:x___________________________________/-10 26935477 221.1 ns/op 355 B/op 5 allocs/op +Parse/no/12/urn::colon:nss_______________________________/-10 25088925 232.4 ns/op 355 B/op 5 allocs/op +Parse/no/13/urn:abcdefghilmnopqrstuvzabcdefghilmn:specifi/-10 21206989 295.1 ns/op 355 B/op 5 allocs/op +Parse/no/14/URN:a!?:x____________________________________/-10 26705482 223.5 ns/op 355 B/op 5 allocs/op +Parse/no/15/urn:urn:NSS__________________________________/-10 31609467 202.1 ns/op 307 B/op 5 allocs/op +Parse/no/16/urn:white_space:NSS__________________________/-10 26144792 232.2 ns/op 355 B/op 5 allocs/op +Parse/no/17/urn:concat:no_spaces_________________________/-10 23717426 251.1 ns/op 346 B/op 6 allocs/op +Parse/no/18/urn:a:/______________________________________/-10 27442077 221.9 ns/op 339 B/op 5 allocs/op +Parse/no/19/urn:UrN:NSS__________________________________/-10 32096002 187.4 ns/op 307 B/op 5 allocs/op ``` ---- +* [1]: Apple M1 Pro -* [1]: Intel Core i7-7600U CPU @ 2.80GHz - ---- ## Example + +For more examples take a look at the [examples file](examples_test.go). + + ```go package main @@ -64,6 +76,35 @@ import ( func main() { var uid = "URN:foo:a123,456" + // Parse the input string as a RFC 2141 URN only + u, e := urn.NewMachine().Parse(uid) + if e != nil { + fmt.Errorf(err) + + return + } + + fmt.Println(u.ID) + fmt.Println(u.SS) + + // Output: + // foo + // a123,456 +} +``` + +```go +package main + +import ( + "fmt" + "github.com/leodido/go-urn" +) + +func main() { + var uid = "URN:foo:a123,456" + + // Parse the input string as a RFC 2141 URN only u, ok := urn.Parse([]byte(uid)) if !ok { panic("error parsing urn") @@ -78,4 +119,33 @@ func main() { } ``` -[![Analytics](https://ga-beacon.appspot.com/UA-49657176-1/go-urn?flat)](https://github.com/igrigorik/ga-beacon) \ No newline at end of file +```go +package main + +import ( + "fmt" + "github.com/leodido/go-urn" +) + +func main() { + input := "urn:ietf:params:scim:api:messages:2.0:ListResponse" + + // Parsing the input string as a RFC 7643 SCIM URN + u, ok := urn.Parse([]byte(input), urn.WithParsingMode(urn.RFC7643Only)) + if !ok { + panic("error parsing urn") + } + + fmt.Println(u.IsSCIM()) + scim := u.SCIM() + fmt.Println(scim.Type.String()) + fmt.Println(scim.Name) + fmt.Println(scim.Other) + + // Output: + // true + // api + // messages + // 2.0:ListResponse +} +``` \ No newline at end of file diff --git a/docs/urn.dot b/docs/urn.dot index a9ef0de..586e642 100644 --- a/docs/urn.dot +++ b/docs/urn.dot @@ -2,7 +2,11 @@ digraph urn { rankdir=LR; node [ shape = point ]; ENTRY; - en_46; + en_5; + en_44; + en_48; + en_83; + en_95; en_1; eof_1; eof_2; @@ -49,6 +53,52 @@ digraph urn { eof_43; eof_44; eof_45; + eof_46; + eof_47; + eof_48; + eof_49; + eof_50; + eof_51; + eof_52; + eof_53; + eof_54; + eof_55; + eof_56; + eof_57; + eof_58; + eof_59; + eof_60; + eof_61; + eof_62; + eof_63; + eof_64; + eof_65; + eof_66; + eof_67; + eof_68; + eof_69; + eof_70; + eof_71; + eof_72; + eof_73; + eof_74; + eof_75; + eof_76; + eof_77; + eof_78; + eof_79; + eof_80; + eof_81; + eof_82; + eof_83; + eof_84; + eof_85; + eof_86; + eof_88; + eof_89; + eof_91; + eof_92; + eof_93; node [ shape = circle, height = 0.2 ]; err_1 [ label=""]; err_2 [ label=""]; @@ -95,22 +145,77 @@ digraph urn { err_43 [ label=""]; err_44 [ label=""]; err_45 [ label=""]; + err_46 [ label=""]; + err_47 [ label=""]; + err_48 [ label=""]; + err_49 [ label=""]; + err_50 [ label=""]; + err_51 [ label=""]; + err_52 [ label=""]; + err_53 [ label=""]; + err_54 [ label=""]; + err_55 [ label=""]; + err_56 [ label=""]; + err_57 [ label=""]; + err_58 [ label=""]; + err_59 [ label=""]; + err_60 [ label=""]; + err_61 [ label=""]; + err_62 [ label=""]; + err_63 [ label=""]; + err_64 [ label=""]; + err_65 [ label=""]; + err_66 [ label=""]; + err_67 [ label=""]; + err_68 [ label=""]; + err_69 [ label=""]; + err_70 [ label=""]; + err_71 [ label=""]; + err_72 [ label=""]; + err_73 [ label=""]; + err_74 [ label=""]; + err_75 [ label=""]; + err_76 [ label=""]; + err_77 [ label=""]; + err_78 [ label=""]; + err_79 [ label=""]; + err_80 [ label=""]; + err_81 [ label=""]; + err_82 [ label=""]; + err_83 [ label=""]; + err_84 [ label=""]; + err_85 [ label=""]; + err_86 [ label=""]; + err_87 [ label=""]; + err_88 [ label=""]; + err_89 [ label=""]; + err_90 [ label=""]; + err_91 [ label=""]; + err_92 [ label=""]; + err_93 [ label=""]; + err_94 [ label=""]; node [ fixedsize = true, height = 0.65, shape = doublecircle ]; - 44; - 45; - 46; + 87; + 88; + 89; + 90; + 91; + 92; + 93; + 94; + 95; node [ shape = circle ]; - 1 -> 2 [ label = "'U', 'u' / mark" ]; - 1 -> err_1 [ label = "DEF / err_parse" ]; + 1 -> 2 [ label = "'U', 'u' / mark, throw_pre_urn_err" ]; + 1 -> err_1 [ label = "DEF / err_pre, err_parse" ]; 2 -> 3 [ label = "'R', 'r'" ]; - 2 -> err_2 [ label = "DEF / err_parse" ]; + 2 -> err_2 [ label = "DEF / err_pre, err_parse" ]; 3 -> 4 [ label = "'N', 'n'" ]; 3 -> err_3 [ label = "DEF / err_pre, err_parse" ]; - 4 -> 5 [ label = "':' / set_pre" ]; - 4 -> err_4 [ label = "DEF / err_parse" ]; + 4 -> 87 [ label = "':' / set_pre, 232:33" ]; + 4 -> err_4 [ label = "DEF / err_pre, err_parse" ]; 5 -> 6 [ label = "'0'..'9', 'A'..'T', 'V'..'Z', 'a'..'t', 'v'..'z' / mark" ]; - 5 -> 41 [ label = "'U', 'u' / mark" ]; - 5 -> err_5 [ label = "DEF / err_nid, err_parse" ]; + 5 -> 41 [ label = "'U', 'u' / mark, throw_pre_urn_err" ]; + 5 -> err_5 [ label = "DEF / err_nid, err_pre, err_parse" ]; 6 -> 7 [ label = "'-', '0'..'9', 'A'..'Z', 'a'..'z'" ]; 6 -> 38 [ label = "':' / set_nid" ]; 6 -> err_6 [ label = "DEF / err_nid, err_parse" ]; @@ -206,40 +311,147 @@ digraph urn { 36 -> err_36 [ label = "DEF / err_nid, err_parse" ]; 37 -> 38 [ label = "':' / set_nid" ]; 37 -> err_37 [ label = "DEF / err_nid, err_parse" ]; - 38 -> 44 [ label = "'!', '$', '''..'.', '0'..';', '=', '@'..'Z', '_', 'a'..'z' / mark" ]; + 38 -> 88 [ label = "'!', '$', '''..'.', '0'..';', '=', '@'..'Z', '_', 'a'..'z' / mark" ]; 38 -> 39 [ label = "'%' / mark" ]; 38 -> err_38 [ label = "DEF / err_nss, err_parse" ]; 39 -> 40 [ label = "'0'..'9', 'a'..'z'" ]; 39 -> 40 [ label = "'A'..'Z' / tolower" ]; 39 -> err_39 [ label = "DEF / err_hex, err_nss, err_parse" ]; - 40 -> 45 [ label = "'0'..'9', 'a'..'z'" ]; - 40 -> 45 [ label = "'A'..'Z' / tolower" ]; + 40 -> 89 [ label = "'0'..'9', 'a'..'z'" ]; + 40 -> 89 [ label = "'A'..'Z' / tolower" ]; 40 -> err_40 [ label = "DEF / err_hex, err_nss, err_parse" ]; 41 -> 7 [ label = "'-', '0'..'9', 'A'..'Q', 'S'..'Z', 'a'..'q', 's'..'z'" ]; 41 -> 38 [ label = "':' / set_nid" ]; 41 -> 42 [ label = "'R', 'r'" ]; - 41 -> err_41 [ label = "DEF / err_nid, err_parse" ]; + 41 -> err_41 [ label = "DEF / err_nid, err_pre, err_parse" ]; 42 -> 8 [ label = "'-', '0'..'9', 'A'..'M', 'O'..'Z', 'a'..'m', 'o'..'z'" ]; 42 -> 38 [ label = "':' / set_nid" ]; 42 -> 43 [ label = "'N', 'n'" ]; - 42 -> err_42 [ label = "DEF / err_pre, err_nid, err_parse" ]; + 42 -> err_42 [ label = "DEF / err_nid, err_pre, err_parse" ]; 43 -> 9 [ label = "'-', '0'..'9', 'A'..'Z', 'a'..'z'" ]; - 43 -> err_43 [ label = "DEF / err_urn, err_nid, err_parse" ]; - 44 -> 44 [ label = "'!', '$', '''..'.', '0'..';', '=', '@'..'Z', '_', 'a'..'z'" ]; - 44 -> 39 [ label = "'%'" ]; - 44 -> err_44 [ label = "DEF / err_nss, err_parse" ]; - 45 -> 44 [ label = "'!', '$', '''..'.', '0'..';', '=', '@'..'Z', '_', 'a'..'z'" ]; - 45 -> 39 [ label = "'%'" ]; - 45 -> err_45 [ label = "DEF / err_hex, err_nss, err_parse" ]; - 46 -> 46 [ label = "0..'\\t', '\\v'..'\\f', 14..255" ]; + 43 -> err_43 [ label = "DEF / err_nid, err_urn, err_parse" ]; + 44 -> 45 [ label = "'U', 'u' / mark, throw_pre_urn_err" ]; + 44 -> err_44 [ label = "DEF / err_pre" ]; + 45 -> 46 [ label = "'R', 'r'" ]; + 45 -> err_45 [ label = "DEF / err_pre" ]; + 46 -> 47 [ label = "'N', 'n'" ]; + 46 -> err_46 [ label = "DEF / err_pre" ]; + 47 -> 90 [ label = "':' / set_pre, 212:36" ]; + 47 -> err_47 [ label = "DEF / err_pre" ]; + 48 -> 49 [ label = "'i' / mark" ]; + 48 -> err_48 [ label = "DEF / err_scim_nid" ]; + 49 -> 50 [ label = "'e'" ]; + 49 -> err_49 [ label = "DEF / err_scim_nid" ]; + 50 -> 51 [ label = "'t'" ]; + 50 -> err_50 [ label = "DEF / err_scim_nid" ]; + 51 -> 52 [ label = "'f'" ]; + 51 -> err_51 [ label = "DEF / err_scim_nid" ]; + 52 -> 53 [ label = "':'" ]; + 52 -> err_52 [ label = "DEF / err_scim_nid" ]; + 53 -> 54 [ label = "'p'" ]; + 53 -> err_53 [ label = "DEF / err_scim_nid" ]; + 54 -> 55 [ label = "'a'" ]; + 54 -> err_54 [ label = "DEF / err_scim_nid" ]; + 55 -> 56 [ label = "'r'" ]; + 55 -> err_55 [ label = "DEF / err_scim_nid" ]; + 56 -> 57 [ label = "'a'" ]; + 56 -> err_56 [ label = "DEF / err_scim_nid" ]; + 57 -> 58 [ label = "'m'" ]; + 57 -> err_57 [ label = "DEF / err_scim_nid" ]; + 58 -> 59 [ label = "'s'" ]; + 58 -> err_58 [ label = "DEF / err_scim_nid" ]; + 59 -> 60 [ label = "':'" ]; + 59 -> err_59 [ label = "DEF / err_scim_nid" ]; + 60 -> 61 [ label = "'s'" ]; + 60 -> err_60 [ label = "DEF / err_scim_nid" ]; + 61 -> 62 [ label = "'c'" ]; + 61 -> err_61 [ label = "DEF / err_scim_nid" ]; + 62 -> 63 [ label = "'i'" ]; + 62 -> err_62 [ label = "DEF / err_scim_nid" ]; + 63 -> 64 [ label = "'m'" ]; + 63 -> err_63 [ label = "DEF / err_scim_nid" ]; + 64 -> 65 [ label = "':' / set_nid, create_scim" ]; + 64 -> err_64 [ label = "DEF / err_scim_nid" ]; + 65 -> 66 [ label = "'a' / mark" ]; + 65 -> 73 [ label = "'p' / mark" ]; + 65 -> 77 [ label = "'s' / mark" ]; + 65 -> err_65 [ label = "DEF / err_scim_type" ]; + 66 -> 67 [ label = "'p'" ]; + 66 -> err_66 [ label = "DEF / err_scim_type" ]; + 67 -> 68 [ label = "'i'" ]; + 67 -> err_67 [ label = "DEF / err_scim_type" ]; + 68 -> 69 [ label = "':' / set_scim_type" ]; + 68 -> err_68 [ label = "DEF / err_scim_type" ]; + 69 -> 91 [ label = "'0'..'9', 'A'..'Z', 'a'..'z' / mark_scim_name" ]; + 69 -> err_69 [ label = "DEF / err_scim_name" ]; + 70 -> 92 [ label = "'!', '$', '''..'.', '0'..';', '=', '@'..'Z', '_', 'a'..'z' / mark_scim_other" ]; + 70 -> 71 [ label = "'%' / mark_scim_other" ]; + 70 -> err_70 [ label = "DEF / err_scim_other" ]; + 71 -> 72 [ label = "'0'..'9', 'a'..'z'" ]; + 71 -> 72 [ label = "'A'..'Z' / tolower" ]; + 71 -> err_71 [ label = "DEF / err_hex, err_scim_other" ]; + 72 -> 93 [ label = "'0'..'9', 'a'..'z'" ]; + 72 -> 93 [ label = "'A'..'Z' / tolower" ]; + 72 -> err_72 [ label = "DEF / err_hex, err_scim_other" ]; + 73 -> 74 [ label = "'a'" ]; + 73 -> err_73 [ label = "DEF / err_scim_type" ]; + 74 -> 75 [ label = "'r'" ]; + 74 -> err_74 [ label = "DEF / err_scim_type" ]; + 75 -> 76 [ label = "'a'" ]; + 75 -> err_75 [ label = "DEF / err_scim_type" ]; + 76 -> 68 [ label = "'m'" ]; + 76 -> err_76 [ label = "DEF / err_scim_type" ]; + 77 -> 78 [ label = "'c'" ]; + 77 -> err_77 [ label = "DEF / err_scim_type" ]; + 78 -> 79 [ label = "'h'" ]; + 78 -> err_78 [ label = "DEF / err_scim_type" ]; + 79 -> 80 [ label = "'e'" ]; + 79 -> err_79 [ label = "DEF / err_scim_type" ]; + 80 -> 81 [ label = "'m'" ]; + 80 -> err_80 [ label = "DEF / err_scim_type" ]; + 81 -> 82 [ label = "'a'" ]; + 81 -> err_81 [ label = "DEF / err_scim_type" ]; + 82 -> 68 [ label = "'s'" ]; + 82 -> err_82 [ label = "DEF / err_scim_type" ]; + 83 -> 84 [ label = "'U', 'u' / mark, throw_pre_urn_err" ]; + 83 -> err_83 [ label = "DEF / err_pre" ]; + 84 -> 85 [ label = "'R', 'r'" ]; + 84 -> err_84 [ label = "DEF / err_pre" ]; + 85 -> 86 [ label = "'N', 'n'" ]; + 85 -> err_85 [ label = "DEF / err_pre" ]; + 86 -> 94 [ label = "':' / set_pre, 226:37" ]; + 86 -> err_86 [ label = "DEF / err_pre" ]; + 87 -> err_87 [ label = "DEF / err_pre, err_parse" ]; + 88 -> 88 [ label = "'!', '$', '''..'.', '0'..';', '=', '@'..'Z', '_', 'a'..'z'" ]; + 88 -> 39 [ label = "'%'" ]; + 88 -> err_88 [ label = "DEF / err_nss, err_parse" ]; + 89 -> 88 [ label = "'!', '$', '''..'.', '0'..';', '=', '@'..'Z', '_', 'a'..'z'" ]; + 89 -> 39 [ label = "'%'" ]; + 89 -> err_89 [ label = "DEF / err_hex, err_nss, err_parse" ]; + 90 -> err_90 [ label = "DEF / err_pre" ]; + 91 -> 91 [ label = "'0'..'9', 'A'..'Z', 'a'..'z'" ]; + 91 -> 70 [ label = "':' / set_scim_name" ]; + 91 -> err_91 [ label = "DEF / err_scim_name" ]; + 92 -> 92 [ label = "'!', '$', '''..'.', '0'..';', '=', '@'..'Z', '_', 'a'..'z'" ]; + 92 -> 71 [ label = "'%'" ]; + 92 -> err_92 [ label = "DEF / err_scim_other" ]; + 93 -> 92 [ label = "'!', '$', '''..'.', '0'..';', '=', '@'..'Z', '_', 'a'..'z'" ]; + 93 -> 71 [ label = "'%'" ]; + 93 -> err_93 [ label = "DEF / err_hex, err_scim_other" ]; + 94 -> err_94 [ label = "DEF / err_pre" ]; + 95 -> 95 [ label = "0..'\\t', '\\v'..'\\f', 14..255" ]; ENTRY -> 1 [ label = "IN" ]; - en_46 -> 46 [ label = "fail" ]; + en_5 -> 5 [ label = "urn" ]; + en_44 -> 44 [ label = "urn_only" ]; + en_48 -> 48 [ label = "scim" ]; + en_83 -> 83 [ label = "scim_only" ]; + en_95 -> 95 [ label = "fail" ]; en_1 -> 1 [ label = "main" ]; - 1 -> eof_1 [ label = "EOF / err_parse" ]; - 2 -> eof_2 [ label = "EOF / err_parse" ]; + 1 -> eof_1 [ label = "EOF / err_pre, err_parse" ]; + 2 -> eof_2 [ label = "EOF / err_pre, err_parse" ]; 3 -> eof_3 [ label = "EOF / err_pre, err_parse" ]; - 4 -> eof_4 [ label = "EOF / err_parse" ]; - 5 -> eof_5 [ label = "EOF / err_nid, err_parse" ]; + 4 -> eof_4 [ label = "EOF / err_pre, err_parse" ]; + 5 -> eof_5 [ label = "EOF / err_nid, err_pre, err_parse" ]; 6 -> eof_6 [ label = "EOF / err_nid, err_parse" ]; 7 -> eof_7 [ label = "EOF / err_nid, err_parse" ]; 8 -> eof_8 [ label = "EOF / err_nid, err_parse" ]; @@ -275,9 +487,55 @@ digraph urn { 38 -> eof_38 [ label = "EOF / err_nss, err_parse" ]; 39 -> eof_39 [ label = "EOF / err_hex, err_nss, err_parse" ]; 40 -> eof_40 [ label = "EOF / err_hex, err_nss, err_parse" ]; - 41 -> eof_41 [ label = "EOF / err_nid, err_parse" ]; - 42 -> eof_42 [ label = "EOF / err_pre, err_nid, err_parse" ]; - 43 -> eof_43 [ label = "EOF / err_urn, err_nid, err_parse" ]; - 44 -> eof_44 [ label = "EOF / set_nss" ]; - 45 -> eof_45 [ label = "EOF / set_nss" ]; + 41 -> eof_41 [ label = "EOF / err_nid, err_pre, err_parse" ]; + 42 -> eof_42 [ label = "EOF / err_nid, err_pre, err_parse" ]; + 43 -> eof_43 [ label = "EOF / err_nid, err_urn, err_parse" ]; + 44 -> eof_44 [ label = "EOF / err_pre" ]; + 45 -> eof_45 [ label = "EOF / err_pre" ]; + 46 -> eof_46 [ label = "EOF / err_pre" ]; + 47 -> eof_47 [ label = "EOF / err_pre" ]; + 48 -> eof_48 [ label = "EOF / err_scim_nid" ]; + 49 -> eof_49 [ label = "EOF / err_scim_nid" ]; + 50 -> eof_50 [ label = "EOF / err_scim_nid" ]; + 51 -> eof_51 [ label = "EOF / err_scim_nid" ]; + 52 -> eof_52 [ label = "EOF / err_scim_nid" ]; + 53 -> eof_53 [ label = "EOF / err_scim_nid" ]; + 54 -> eof_54 [ label = "EOF / err_scim_nid" ]; + 55 -> eof_55 [ label = "EOF / err_scim_nid" ]; + 56 -> eof_56 [ label = "EOF / err_scim_nid" ]; + 57 -> eof_57 [ label = "EOF / err_scim_nid" ]; + 58 -> eof_58 [ label = "EOF / err_scim_nid" ]; + 59 -> eof_59 [ label = "EOF / err_scim_nid" ]; + 60 -> eof_60 [ label = "EOF / err_scim_nid" ]; + 61 -> eof_61 [ label = "EOF / err_scim_nid" ]; + 62 -> eof_62 [ label = "EOF / err_scim_nid" ]; + 63 -> eof_63 [ label = "EOF / err_scim_nid" ]; + 64 -> eof_64 [ label = "EOF / err_scim_nid" ]; + 65 -> eof_65 [ label = "EOF / err_scim_type" ]; + 66 -> eof_66 [ label = "EOF / err_scim_type" ]; + 67 -> eof_67 [ label = "EOF / err_scim_type" ]; + 68 -> eof_68 [ label = "EOF / err_scim_type" ]; + 69 -> eof_69 [ label = "EOF / err_scim_name" ]; + 70 -> eof_70 [ label = "EOF / err_scim_other" ]; + 71 -> eof_71 [ label = "EOF / err_hex, err_scim_other" ]; + 72 -> eof_72 [ label = "EOF / err_hex, err_scim_other" ]; + 73 -> eof_73 [ label = "EOF / err_scim_type" ]; + 74 -> eof_74 [ label = "EOF / err_scim_type" ]; + 75 -> eof_75 [ label = "EOF / err_scim_type" ]; + 76 -> eof_76 [ label = "EOF / err_scim_type" ]; + 77 -> eof_77 [ label = "EOF / err_scim_type" ]; + 78 -> eof_78 [ label = "EOF / err_scim_type" ]; + 79 -> eof_79 [ label = "EOF / err_scim_type" ]; + 80 -> eof_80 [ label = "EOF / err_scim_type" ]; + 81 -> eof_81 [ label = "EOF / err_scim_type" ]; + 82 -> eof_82 [ label = "EOF / err_scim_type" ]; + 83 -> eof_83 [ label = "EOF / err_pre" ]; + 84 -> eof_84 [ label = "EOF / err_pre" ]; + 85 -> eof_85 [ label = "EOF / err_pre" ]; + 86 -> eof_86 [ label = "EOF / err_pre" ]; + 88 -> eof_88 [ label = "EOF / set_nss, base_type" ]; + 89 -> eof_89 [ label = "EOF / set_nss, base_type" ]; + 91 -> eof_91 [ label = "EOF / set_scim_name, set_nss, scim_type" ]; + 92 -> eof_92 [ label = "EOF / set_scim_other, set_nss, scim_type" ]; + 93 -> eof_93 [ label = "EOF / set_scim_other, set_nss, scim_type" ]; } diff --git a/docs/urn.png b/docs/urn.png index 8c7ba45..2727266 100644 Binary files a/docs/urn.png and b/docs/urn.png differ diff --git a/examples_test.go b/examples_test.go index 81a516c..f6d6091 100644 --- a/examples_test.go +++ b/examples_test.go @@ -2,6 +2,7 @@ package urn_test import ( "fmt" + "github.com/leodido/go-urn" ) @@ -11,10 +12,12 @@ func ExampleParse() { if u, ok := urn.Parse([]byte(uid)); ok { fmt.Println(u.ID) fmt.Println(u.SS) + fmt.Println(u.SCIM()) } // Output: foo // a123,456 + // } func ExampleURN_MarshalJSON() { @@ -51,3 +54,29 @@ func ExampleURN_Equal() { // Output: URN:foo:a123,456 equals URN:FOO:a123,456 } + +func ExampleParse_scim() { + input := "urn:ietf:params:scim:api:messages:2.0:ListResponse" + + u, ok := urn.Parse([]byte(input), urn.WithParsingMode(urn.RFC7643Only)) + if !ok { + panic("invalid SCIM urn") + } + data, err := u.MarshalJSON() + if err != nil { + panic("couldn't marshal") + } + fmt.Println(string(data)) + fmt.Println(u.IsSCIM()) + scim := u.SCIM() + fmt.Println(scim.Type.String()) + fmt.Println(scim.Name) + fmt.Println(scim.Other) + + // Output: + // "urn:ietf:params:scim:api:messages:2.0:ListResponse" + // true + // api + // messages + // 2.0:ListResponse +} diff --git a/go.mod b/go.mod index 855bc90..779cc5d 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,11 @@ module github.com/leodido/go-urn -go 1.16 +go 1.18 -require github.com/stretchr/testify v1.8.2 +require github.com/stretchr/testify v1.8.4 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum index 6a56e69..fa4b6e6 100644 --- a/go.sum +++ b/go.sum @@ -1,17 +1,10 @@ -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/kind.go b/kind.go new file mode 100644 index 0000000..5e72f80 --- /dev/null +++ b/kind.go @@ -0,0 +1,9 @@ +package urn + +type Kind int + +const ( + NONE Kind = iota + RFC2141 + RFC7643 +) diff --git a/machine.go b/machine.go index f8d57b4..e4418d9 100644 --- a/machine.go +++ b/machine.go @@ -2,27 +2,39 @@ package urn import ( "fmt" + + scimschema "github.com/leodido/go-urn/scim/schema" ) var ( - errPrefix = "expecting the prefix to be the \"urn\" string (whatever case) [col %d]" - errIdentifier = "expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its start) [col %d]" - errSpecificString = "expecting the specific string to be a string containing alnum, hex, or others ([()+,-.:=@;$_!*']) chars [col %d]" - errNoUrnWithinID = "expecting the identifier to not contain the \"urn\" reserved string [col %d]" - errHex = "expecting the specific string hex chars to be well-formed (%%alnum{2}) [col %d]" - errParse = "parsing error [col %d]" + errPrefix = "expecting the prefix to be the \"urn\" string (whatever case) [col %d]" + errIdentifier = "expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its beginning) [col %d]" + errSpecificString = "expecting the specific string to be a string containing alnum, hex, or others ([()+,-.:=@;$_!*']) chars [col %d]" + errNoUrnWithinID = "expecting the identifier to not contain the \"urn\" reserved string [col %d]" + errHex = "expecting the specific string hex chars to be well-formed (%%alnum{2}) [col %d]" + errParse = "parsing error [col %d]" + errSCIMNamespace = "expecing the SCIM namespace identifier (ietf:params:scim) [col %d]" + errSCIMType = "expecting a correct SCIM type (schemas, api, param) [col %d]" + errSCIMName = "expecting one or more alnum char in the SCIM name part [col %d]" + errSCIMOther = "expecting a well-formed other SCIM part [col %d]" + errSCIMOtherIncomplete = "expecting a not empty SCIM other part after colon [col %d]" ) const start int = 1 -const firstFinal int = 44 +const firstFinal int = 87 -const enFail int = 46 +const enUrn int = 5 +const enUrnOnly int = 44 +const enScim int = 48 +const enScimOnly int = 83 +const enFail int = 95 const enMain int = 1 // Machine is the interface representing the FSM type Machine interface { Error() error Parse(input []byte) (*URN, error) + WithParsingMode(ParsingMode) } type machine struct { @@ -31,11 +43,23 @@ type machine struct { p, pe, eof, pb int err error tolower []int + parsingMode ParsingMode + parsingModeSet bool } // NewMachine creates a new FSM able to parse RFC 2141 strings. -func NewMachine() Machine { - m := &machine{} +func NewMachine(options ...Option) Machine { + m := &machine{ + parsingModeSet: false, + } + + for _, o := range options { + o(m) + } + // Set default parsing mode + if !m.parsingModeSet { + m.WithParsingMode(DefaultParsingMode) + } return m } @@ -51,7 +75,7 @@ func (m *machine) text() []byte { return m.data[m.pb:m.p] } -// Parse parses the input byte array as a RFC 2141 string. +// Parse parses the input byte array as a RFC 2141 or RFC7643 string. func (m *machine) Parse(input []byte) (*URN, error) { m.data = input m.p = 0 @@ -61,8 +85,23 @@ func (m *machine) Parse(input []byte) (*URN, error) { m.err = nil m.tolower = []int{} output := &URN{} - { - m.cs = start + + switch m.parsingMode { + case RFC2141Only: + m.cs = enUrnOnly + break + + case RFC7643Only: + m.cs = enScimOnly + break + + case All: + fallthrough + default: + { + m.cs = start + } + break } { if (m.p) == (m.pe) { @@ -79,6 +118,8 @@ func (m *machine) Parse(input []byte) (*URN, error) { goto stCase3 case 4: goto stCase4 + case 87: + goto stCase87 case 5: goto stCase5 case 6: @@ -147,22 +188,118 @@ func (m *machine) Parse(input []byte) (*URN, error) { goto stCase37 case 38: goto stCase38 - case 44: - goto stCase44 + case 88: + goto stCase88 case 39: goto stCase39 case 40: goto stCase40 - case 45: - goto stCase45 + case 89: + goto stCase89 case 41: goto stCase41 case 42: goto stCase42 case 43: goto stCase43 + case 44: + goto stCase44 + case 45: + goto stCase45 case 46: goto stCase46 + case 47: + goto stCase47 + case 90: + goto stCase90 + case 48: + goto stCase48 + case 49: + goto stCase49 + case 50: + goto stCase50 + case 51: + goto stCase51 + case 52: + goto stCase52 + case 53: + goto stCase53 + case 54: + goto stCase54 + case 55: + goto stCase55 + case 56: + goto stCase56 + case 57: + goto stCase57 + case 58: + goto stCase58 + case 59: + goto stCase59 + case 60: + goto stCase60 + case 61: + goto stCase61 + case 62: + goto stCase62 + case 63: + goto stCase63 + case 64: + goto stCase64 + case 65: + goto stCase65 + case 66: + goto stCase66 + case 67: + goto stCase67 + case 68: + goto stCase68 + case 69: + goto stCase69 + case 91: + goto stCase91 + case 70: + goto stCase70 + case 92: + goto stCase92 + case 71: + goto stCase71 + case 72: + goto stCase72 + case 93: + goto stCase93 + case 73: + goto stCase73 + case 74: + goto stCase74 + case 75: + goto stCase75 + case 76: + goto stCase76 + case 77: + goto stCase77 + case 78: + goto stCase78 + case 79: + goto stCase79 + case 80: + goto stCase80 + case 81: + goto stCase81 + case 82: + goto stCase82 + case 83: + goto stCase83 + case 84: + goto stCase84 + case 85: + goto stCase85 + case 86: + goto stCase86 + case 94: + goto stCase94 + case 95: + goto stCase95 } goto stOut stCase1: @@ -175,45 +312,59 @@ func (m *machine) Parse(input []byte) (*URN, error) { goto tr0 tr0: + m.err = fmt.Errorf(errPrefix, m.p) + (m.p)-- + + { + goto st95 + } + m.err = fmt.Errorf(errParse, m.p) (m.p)-- { - goto st46 + goto st95 } goto st0 - tr3: + tr5: + + m.err = fmt.Errorf(errIdentifier, m.p) + (m.p)-- + + { + goto st95 + } m.err = fmt.Errorf(errPrefix, m.p) (m.p)-- { - goto st46 + goto st95 } m.err = fmt.Errorf(errParse, m.p) (m.p)-- { - goto st46 + goto st95 } goto st0 - tr6: + tr8: m.err = fmt.Errorf(errIdentifier, m.p) (m.p)-- { - goto st46 + goto st95 } m.err = fmt.Errorf(errParse, m.p) (m.p)-- { - goto st46 + goto st95 } goto st0 @@ -223,14 +374,14 @@ func (m *machine) Parse(input []byte) (*URN, error) { (m.p)-- { - goto st46 + goto st95 } m.err = fmt.Errorf(errParse, m.p) (m.p)-- { - goto st46 + goto st95 } goto st0 @@ -240,69 +391,180 @@ func (m *machine) Parse(input []byte) (*URN, error) { (m.p)-- { - goto st46 + goto st95 } m.err = fmt.Errorf(errSpecificString, m.p) (m.p)-- { - goto st46 + goto st95 } m.err = fmt.Errorf(errParse, m.p) (m.p)-- { - goto st46 + goto st95 } goto st0 - tr50: + tr51: - m.err = fmt.Errorf(errPrefix, m.p) + m.err = fmt.Errorf(errIdentifier, m.p) (m.p)-- { - goto st46 + goto st95 } - m.err = fmt.Errorf(errIdentifier, m.p) + m.err = fmt.Errorf(errNoUrnWithinID, m.p) (m.p)-- { - goto st46 + goto st95 } m.err = fmt.Errorf(errParse, m.p) (m.p)-- { - goto st46 + goto st95 } goto st0 tr52: - m.err = fmt.Errorf(errNoUrnWithinID, m.p) + m.err = fmt.Errorf(errPrefix, m.p) (m.p)-- { - goto st46 + goto st95 } - m.err = fmt.Errorf(errIdentifier, m.p) + goto st0 + tr57: + + // In case we are in fallback mode we are now gonna jump to normal RFC2141 URN parsing + if m.parsingMode == All { + // TODO: store why the machine fallback to the RFC2141 one? + output.scim = nil + // Rewind the cursor after the prefix ends ("urn:") + (m.p) = (4) - 1 + + // Go to the "urn" machine from this point on + { + goto st5 + } + } + m.err = fmt.Errorf(errSCIMNamespace, m.p) (m.p)-- { - goto st46 + goto st95 } - m.err = fmt.Errorf(errParse, m.p) + goto st0 + tr75: + + // In case we are in fallback mode we are now gonna jump to normal RFC2141 URN parsing + if m.parsingMode == All { + // TODO: store why the machine fallback to the RFC2141 one? + output.scim = nil + // Rewind the cursor after the prefix ends ("urn:") + (m.p) = (4) - 1 + + // Go to the "urn" machine from this point on + { + goto st5 + } + } + m.err = fmt.Errorf(errSCIMType, m.p) (m.p)-- { - goto st46 + goto st95 + } + + goto st0 + tr82: + + // In case we are in fallback mode we are now gonna jump to normal RFC2141 URN parsing + if m.parsingMode == All { + // TODO: store why the machine fallback to the RFC2141 one? + output.scim = nil + // Rewind the cursor after the prefix ends ("urn:") + (m.p) = (4) - 1 + + // Go to the "urn" machine from this point on + { + goto st5 + } + } + m.err = fmt.Errorf(errSCIMName, m.p) + (m.p)-- + + { + goto st95 + } + + goto st0 + tr84: + + // In case we are in fallback mode we are now gonna jump to normal RFC2141 URN parsing + if m.parsingMode == All { + // TODO: store why the machine fallback to the RFC2141 one? + output.scim = nil + // Rewind the cursor after the prefix ends ("urn:") + (m.p) = (4) - 1 + + // Go to the "urn" machine from this point on + { + goto st5 + } + } + if m.p == m.pe { + m.err = fmt.Errorf(errSCIMOtherIncomplete, m.p-1) + } else { + m.err = fmt.Errorf(errSCIMOther, m.p) + } + (m.p)-- + + { + goto st95 + } + + goto st0 + tr87: + + m.err = fmt.Errorf(errHex, m.p) + (m.p)-- + + { + goto st95 + } + + // In case we are in fallback mode we are now gonna jump to normal RFC2141 URN parsing + if m.parsingMode == All { + // TODO: store why the machine fallback to the RFC2141 one? + output.scim = nil + // Rewind the cursor after the prefix ends ("urn:") + (m.p) = (4) - 1 + + // Go to the "urn" machine from this point on + { + goto st5 + } + } + if m.p == m.pe { + m.err = fmt.Errorf(errSCIMOtherIncomplete, m.p-1) + } else { + m.err = fmt.Errorf(errSCIMOther, m.p) + } + (m.p)-- + + { + goto st95 } goto st0 @@ -314,6 +576,18 @@ func (m *machine) Parse(input []byte) (*URN, error) { m.pb = m.p + // Throw an error when: + // - we are entering here matching the the prefix in the namespace identifier part + // - looking ahead (3 chars) we find a colon + if pos := m.p + 3; pos < m.pe && m.data[pos] == 58 && output.prefix != "" { + m.err = fmt.Errorf(errNoUrnWithinID, pos) + (m.p)-- + + { + goto st95 + } + } + goto st2 st2: if (m.p)++; (m.p) == (m.pe) { @@ -338,21 +612,29 @@ func (m *machine) Parse(input []byte) (*URN, error) { case 110: goto st4 } - goto tr3 + goto tr0 st4: if (m.p)++; (m.p) == (m.pe) { goto _testEof4 } stCase4: if (m.data)[(m.p)] == 58 { - goto tr5 + goto tr4 } goto tr0 - tr5: + tr4: output.prefix = string(m.text()) - - goto st5 + { + goto st48 + } + goto st87 + st87: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof87 + } + stCase87: + goto tr0 st5: if (m.p)++; (m.p) == (m.pe) { goto _testEof5 @@ -360,24 +642,24 @@ func (m *machine) Parse(input []byte) (*URN, error) { stCase5: switch (m.data)[(m.p)] { case 85: - goto tr8 + goto tr7 case 117: - goto tr8 + goto tr7 } switch { case (m.data)[(m.p)] < 65: if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { - goto tr7 + goto tr6 } case (m.data)[(m.p)] > 90: if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { - goto tr7 + goto tr6 } default: - goto tr7 + goto tr6 } - goto tr6 - tr7: + goto tr5 + tr6: m.pb = m.p @@ -405,7 +687,7 @@ func (m *machine) Parse(input []byte) (*URN, error) { default: goto st7 } - goto tr6 + goto tr8 st7: if (m.p)++; (m.p) == (m.pe) { goto _testEof7 @@ -429,7 +711,7 @@ func (m *machine) Parse(input []byte) (*URN, error) { default: goto st8 } - goto tr6 + goto tr8 st8: if (m.p)++; (m.p) == (m.pe) { goto _testEof8 @@ -453,7 +735,7 @@ func (m *machine) Parse(input []byte) (*URN, error) { default: goto st9 } - goto tr6 + goto tr8 st9: if (m.p)++; (m.p) == (m.pe) { goto _testEof9 @@ -477,7 +759,7 @@ func (m *machine) Parse(input []byte) (*URN, error) { default: goto st10 } - goto tr6 + goto tr8 st10: if (m.p)++; (m.p) == (m.pe) { goto _testEof10 @@ -501,7 +783,7 @@ func (m *machine) Parse(input []byte) (*URN, error) { default: goto st11 } - goto tr6 + goto tr8 st11: if (m.p)++; (m.p) == (m.pe) { goto _testEof11 @@ -525,7 +807,7 @@ func (m *machine) Parse(input []byte) (*URN, error) { default: goto st12 } - goto tr6 + goto tr8 st12: if (m.p)++; (m.p) == (m.pe) { goto _testEof12 @@ -549,7 +831,7 @@ func (m *machine) Parse(input []byte) (*URN, error) { default: goto st13 } - goto tr6 + goto tr8 st13: if (m.p)++; (m.p) == (m.pe) { goto _testEof13 @@ -573,7 +855,7 @@ func (m *machine) Parse(input []byte) (*URN, error) { default: goto st14 } - goto tr6 + goto tr8 st14: if (m.p)++; (m.p) == (m.pe) { goto _testEof14 @@ -597,7 +879,7 @@ func (m *machine) Parse(input []byte) (*URN, error) { default: goto st15 } - goto tr6 + goto tr8 st15: if (m.p)++; (m.p) == (m.pe) { goto _testEof15 @@ -621,7 +903,7 @@ func (m *machine) Parse(input []byte) (*URN, error) { default: goto st16 } - goto tr6 + goto tr8 st16: if (m.p)++; (m.p) == (m.pe) { goto _testEof16 @@ -645,7 +927,7 @@ func (m *machine) Parse(input []byte) (*URN, error) { default: goto st17 } - goto tr6 + goto tr8 st17: if (m.p)++; (m.p) == (m.pe) { goto _testEof17 @@ -669,7 +951,7 @@ func (m *machine) Parse(input []byte) (*URN, error) { default: goto st18 } - goto tr6 + goto tr8 st18: if (m.p)++; (m.p) == (m.pe) { goto _testEof18 @@ -693,7 +975,7 @@ func (m *machine) Parse(input []byte) (*URN, error) { default: goto st19 } - goto tr6 + goto tr8 st19: if (m.p)++; (m.p) == (m.pe) { goto _testEof19 @@ -717,7 +999,7 @@ func (m *machine) Parse(input []byte) (*URN, error) { default: goto st20 } - goto tr6 + goto tr8 st20: if (m.p)++; (m.p) == (m.pe) { goto _testEof20 @@ -741,7 +1023,7 @@ func (m *machine) Parse(input []byte) (*URN, error) { default: goto st21 } - goto tr6 + goto tr8 st21: if (m.p)++; (m.p) == (m.pe) { goto _testEof21 @@ -765,7 +1047,7 @@ func (m *machine) Parse(input []byte) (*URN, error) { default: goto st22 } - goto tr6 + goto tr8 st22: if (m.p)++; (m.p) == (m.pe) { goto _testEof22 @@ -789,7 +1071,7 @@ func (m *machine) Parse(input []byte) (*URN, error) { default: goto st23 } - goto tr6 + goto tr8 st23: if (m.p)++; (m.p) == (m.pe) { goto _testEof23 @@ -813,7 +1095,7 @@ func (m *machine) Parse(input []byte) (*URN, error) { default: goto st24 } - goto tr6 + goto tr8 st24: if (m.p)++; (m.p) == (m.pe) { goto _testEof24 @@ -837,7 +1119,7 @@ func (m *machine) Parse(input []byte) (*URN, error) { default: goto st25 } - goto tr6 + goto tr8 st25: if (m.p)++; (m.p) == (m.pe) { goto _testEof25 @@ -861,7 +1143,7 @@ func (m *machine) Parse(input []byte) (*URN, error) { default: goto st26 } - goto tr6 + goto tr8 st26: if (m.p)++; (m.p) == (m.pe) { goto _testEof26 @@ -885,7 +1167,7 @@ func (m *machine) Parse(input []byte) (*URN, error) { default: goto st27 } - goto tr6 + goto tr8 st27: if (m.p)++; (m.p) == (m.pe) { goto _testEof27 @@ -909,7 +1191,7 @@ func (m *machine) Parse(input []byte) (*URN, error) { default: goto st28 } - goto tr6 + goto tr8 st28: if (m.p)++; (m.p) == (m.pe) { goto _testEof28 @@ -933,7 +1215,7 @@ func (m *machine) Parse(input []byte) (*URN, error) { default: goto st29 } - goto tr6 + goto tr8 st29: if (m.p)++; (m.p) == (m.pe) { goto _testEof29 @@ -957,7 +1239,7 @@ func (m *machine) Parse(input []byte) (*URN, error) { default: goto st30 } - goto tr6 + goto tr8 st30: if (m.p)++; (m.p) == (m.pe) { goto _testEof30 @@ -981,7 +1263,7 @@ func (m *machine) Parse(input []byte) (*URN, error) { default: goto st31 } - goto tr6 + goto tr8 st31: if (m.p)++; (m.p) == (m.pe) { goto _testEof31 @@ -1005,7 +1287,7 @@ func (m *machine) Parse(input []byte) (*URN, error) { default: goto st32 } - goto tr6 + goto tr8 st32: if (m.p)++; (m.p) == (m.pe) { goto _testEof32 @@ -1029,7 +1311,7 @@ func (m *machine) Parse(input []byte) (*URN, error) { default: goto st33 } - goto tr6 + goto tr8 st33: if (m.p)++; (m.p) == (m.pe) { goto _testEof33 @@ -1053,7 +1335,7 @@ func (m *machine) Parse(input []byte) (*URN, error) { default: goto st34 } - goto tr6 + goto tr8 st34: if (m.p)++; (m.p) == (m.pe) { goto _testEof34 @@ -1077,7 +1359,7 @@ func (m *machine) Parse(input []byte) (*URN, error) { default: goto st35 } - goto tr6 + goto tr8 st35: if (m.p)++; (m.p) == (m.pe) { goto _testEof35 @@ -1101,7 +1383,7 @@ func (m *machine) Parse(input []byte) (*URN, error) { default: goto st36 } - goto tr6 + goto tr8 st36: if (m.p)++; (m.p) == (m.pe) { goto _testEof36 @@ -1125,7 +1407,7 @@ func (m *machine) Parse(input []byte) (*URN, error) { default: goto st37 } - goto tr6 + goto tr8 st37: if (m.p)++; (m.p) == (m.pe) { goto _testEof37 @@ -1134,7 +1416,7 @@ func (m *machine) Parse(input []byte) (*URN, error) { if (m.data)[(m.p)] == 58 { goto tr10 } - goto tr6 + goto tr8 tr10: output.ID = string(m.text()) @@ -1179,40 +1461,40 @@ func (m *machine) Parse(input []byte) (*URN, error) { m.pb = m.p - goto st44 - st44: + goto st88 + st88: if (m.p)++; (m.p) == (m.pe) { - goto _testEof44 + goto _testEof88 } - stCase44: + stCase88: switch (m.data)[(m.p)] { case 33: - goto st44 + goto st88 case 36: - goto st44 + goto st88 case 37: goto st39 case 61: - goto st44 + goto st88 case 95: - goto st44 + goto st88 } switch { case (m.data)[(m.p)] < 48: if 39 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 46 { - goto st44 + goto st88 } case (m.data)[(m.p)] > 59: switch { case (m.data)[(m.p)] > 90: if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { - goto st44 + goto st88 } case (m.data)[(m.p)] >= 64: - goto st44 + goto st88 } default: - goto st44 + goto st88 } goto tr41 tr43: @@ -1251,11 +1533,11 @@ func (m *machine) Parse(input []byte) (*URN, error) { switch { case (m.data)[(m.p)] < 65: if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { - goto st45 + goto st89 } case (m.data)[(m.p)] > 90: if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { - goto st45 + goto st89 } default: goto tr48 @@ -1265,46 +1547,58 @@ func (m *machine) Parse(input []byte) (*URN, error) { m.tolower = append(m.tolower, m.p-m.pb) - goto st45 - st45: + goto st89 + st89: if (m.p)++; (m.p) == (m.pe) { - goto _testEof45 + goto _testEof89 } - stCase45: + stCase89: switch (m.data)[(m.p)] { case 33: - goto st44 + goto st88 case 36: - goto st44 + goto st88 case 37: goto st39 case 61: - goto st44 + goto st88 case 95: - goto st44 + goto st88 } switch { case (m.data)[(m.p)] < 48: if 39 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 46 { - goto st44 + goto st88 } case (m.data)[(m.p)] > 59: switch { case (m.data)[(m.p)] > 90: if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { - goto st44 + goto st88 } case (m.data)[(m.p)] >= 64: - goto st44 + goto st88 } default: - goto st44 + goto st88 } goto tr44 - tr8: + tr7: m.pb = m.p + // Throw an error when: + // - we are entering here matching the the prefix in the namespace identifier part + // - looking ahead (3 chars) we find a colon + if pos := m.p + 3; pos < m.pe && m.data[pos] == 58 && output.prefix != "" { + m.err = fmt.Errorf(errNoUrnWithinID, pos) + (m.p)-- + + { + goto st95 + } + } + goto st41 st41: if (m.p)++; (m.p) == (m.pe) { @@ -1333,7 +1627,7 @@ func (m *machine) Parse(input []byte) (*URN, error) { default: goto st7 } - goto tr6 + goto tr5 st42: if (m.p)++; (m.p) == (m.pe) { goto _testEof42 @@ -1361,7 +1655,7 @@ func (m *machine) Parse(input []byte) (*URN, error) { default: goto st8 } - goto tr50 + goto tr5 st43: if (m.p)++; (m.p) == (m.pe) { goto _testEof43 @@ -1382,6 +1676,43 @@ func (m *machine) Parse(input []byte) (*URN, error) { default: goto st9 } + goto tr51 + stCase44: + switch (m.data)[(m.p)] { + case 85: + goto tr53 + case 117: + goto tr53 + } + goto tr52 + tr53: + + m.pb = m.p + + // Throw an error when: + // - we are entering here matching the the prefix in the namespace identifier part + // - looking ahead (3 chars) we find a colon + if pos := m.p + 3; pos < m.pe && m.data[pos] == 58 && output.prefix != "" { + m.err = fmt.Errorf(errNoUrnWithinID, pos) + (m.p)-- + + { + goto st95 + } + } + + goto st45 + st45: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof45 + } + stCase45: + switch (m.data)[(m.p)] { + case 82: + goto st46 + case 114: + goto st46 + } goto tr52 st46: if (m.p)++; (m.p) == (m.pe) { @@ -1389,97 +1720,731 @@ func (m *machine) Parse(input []byte) (*URN, error) { } stCase46: switch (m.data)[(m.p)] { - case 10: - goto st0 - case 13: - goto st0 + case 78: + goto st47 + case 110: + goto st47 } - goto st46 - stOut: - _testEof2: - m.cs = 2 - goto _testEof - _testEof3: - m.cs = 3 - goto _testEof - _testEof4: - m.cs = 4 - goto _testEof - _testEof5: - m.cs = 5 - goto _testEof - _testEof6: - m.cs = 6 - goto _testEof - _testEof7: - m.cs = 7 - goto _testEof - _testEof8: - m.cs = 8 - goto _testEof - _testEof9: - m.cs = 9 - goto _testEof - _testEof10: - m.cs = 10 - goto _testEof - _testEof11: - m.cs = 11 - goto _testEof - _testEof12: - m.cs = 12 - goto _testEof - _testEof13: - m.cs = 13 - goto _testEof - _testEof14: - m.cs = 14 - goto _testEof - _testEof15: - m.cs = 15 - goto _testEof - _testEof16: - m.cs = 16 - goto _testEof - _testEof17: - m.cs = 17 - goto _testEof - _testEof18: - m.cs = 18 - goto _testEof - _testEof19: - m.cs = 19 - goto _testEof - _testEof20: - m.cs = 20 - goto _testEof - _testEof21: - m.cs = 21 - goto _testEof - _testEof22: - m.cs = 22 - goto _testEof - _testEof23: - m.cs = 23 - goto _testEof - _testEof24: - m.cs = 24 - goto _testEof - _testEof25: - m.cs = 25 - goto _testEof - _testEof26: - m.cs = 26 - goto _testEof - _testEof27: - m.cs = 27 - goto _testEof - _testEof28: - m.cs = 28 - goto _testEof - _testEof29: - m.cs = 29 - goto _testEof + goto tr52 + st47: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof47 + } + stCase47: + if (m.data)[(m.p)] == 58 { + goto tr56 + } + goto tr52 + tr56: + + output.prefix = string(m.text()) + { + goto st5 + } + goto st90 + st90: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof90 + } + stCase90: + goto tr52 + st48: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof48 + } + stCase48: + if (m.data)[(m.p)] == 105 { + goto tr58 + } + goto tr57 + tr58: + + m.pb = m.p + + goto st49 + st49: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof49 + } + stCase49: + if (m.data)[(m.p)] == 101 { + goto st50 + } + goto tr57 + st50: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof50 + } + stCase50: + if (m.data)[(m.p)] == 116 { + goto st51 + } + goto tr57 + st51: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof51 + } + stCase51: + if (m.data)[(m.p)] == 102 { + goto st52 + } + goto tr57 + st52: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof52 + } + stCase52: + if (m.data)[(m.p)] == 58 { + goto st53 + } + goto tr57 + st53: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof53 + } + stCase53: + if (m.data)[(m.p)] == 112 { + goto st54 + } + goto tr57 + st54: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof54 + } + stCase54: + if (m.data)[(m.p)] == 97 { + goto st55 + } + goto tr57 + st55: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof55 + } + stCase55: + if (m.data)[(m.p)] == 114 { + goto st56 + } + goto tr57 + st56: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof56 + } + stCase56: + if (m.data)[(m.p)] == 97 { + goto st57 + } + goto tr57 + st57: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof57 + } + stCase57: + if (m.data)[(m.p)] == 109 { + goto st58 + } + goto tr57 + st58: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof58 + } + stCase58: + if (m.data)[(m.p)] == 115 { + goto st59 + } + goto tr57 + st59: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof59 + } + stCase59: + if (m.data)[(m.p)] == 58 { + goto st60 + } + goto tr57 + st60: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof60 + } + stCase60: + if (m.data)[(m.p)] == 115 { + goto st61 + } + goto tr57 + st61: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof61 + } + stCase61: + if (m.data)[(m.p)] == 99 { + goto st62 + } + goto tr57 + st62: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof62 + } + stCase62: + if (m.data)[(m.p)] == 105 { + goto st63 + } + goto tr57 + st63: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof63 + } + stCase63: + if (m.data)[(m.p)] == 109 { + goto st64 + } + goto tr57 + st64: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof64 + } + stCase64: + if (m.data)[(m.p)] == 58 { + goto tr74 + } + goto tr57 + tr74: + + output.ID = string(m.text()) + + output.scim = &SCIM{} + + goto st65 + st65: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof65 + } + stCase65: + switch (m.data)[(m.p)] { + case 97: + goto tr76 + case 112: + goto tr77 + case 115: + goto tr78 + } + goto tr75 + tr76: + + m.pb = m.p + + goto st66 + st66: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof66 + } + stCase66: + if (m.data)[(m.p)] == 112 { + goto st67 + } + goto tr75 + st67: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof67 + } + stCase67: + if (m.data)[(m.p)] == 105 { + goto st68 + } + goto tr75 + st68: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof68 + } + stCase68: + if (m.data)[(m.p)] == 58 { + goto tr81 + } + goto tr75 + tr81: + + output.scim.Type = scimschema.TypeFromString(string(m.text())) + + goto st69 + st69: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof69 + } + stCase69: + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto tr83 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr83 + } + default: + goto tr83 + } + goto tr82 + tr83: + + output.scim.pos = m.p + + goto st91 + st91: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof91 + } + stCase91: + if (m.data)[(m.p)] == 58 { + goto tr107 + } + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto st91 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto st91 + } + default: + goto st91 + } + goto tr82 + tr107: + + output.scim.Name = string(m.data[output.scim.pos:m.p]) + + goto st70 + st70: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof70 + } + stCase70: + switch (m.data)[(m.p)] { + case 33: + goto tr85 + case 36: + goto tr85 + case 37: + goto tr86 + case 61: + goto tr85 + case 95: + goto tr85 + } + switch { + case (m.data)[(m.p)] < 48: + if 39 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 46 { + goto tr85 + } + case (m.data)[(m.p)] > 59: + switch { + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto tr85 + } + case (m.data)[(m.p)] >= 64: + goto tr85 + } + default: + goto tr85 + } + goto tr84 + tr85: + + output.scim.pos = m.p + + goto st92 + st92: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof92 + } + stCase92: + switch (m.data)[(m.p)] { + case 33: + goto st92 + case 36: + goto st92 + case 37: + goto st71 + case 61: + goto st92 + case 95: + goto st92 + } + switch { + case (m.data)[(m.p)] < 48: + if 39 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 46 { + goto st92 + } + case (m.data)[(m.p)] > 59: + switch { + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto st92 + } + case (m.data)[(m.p)] >= 64: + goto st92 + } + default: + goto st92 + } + goto tr84 + tr86: + + output.scim.pos = m.p + + goto st71 + st71: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof71 + } + stCase71: + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto st72 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto st72 + } + default: + goto tr89 + } + goto tr87 + tr89: + + m.tolower = append(m.tolower, m.p-m.pb) + + goto st72 + st72: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof72 + } + stCase72: + switch { + case (m.data)[(m.p)] < 65: + if 48 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 57 { + goto st93 + } + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto st93 + } + default: + goto tr91 + } + goto tr87 + tr91: + + m.tolower = append(m.tolower, m.p-m.pb) + + goto st93 + st93: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof93 + } + stCase93: + switch (m.data)[(m.p)] { + case 33: + goto st92 + case 36: + goto st92 + case 37: + goto st71 + case 61: + goto st92 + case 95: + goto st92 + } + switch { + case (m.data)[(m.p)] < 48: + if 39 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 46 { + goto st92 + } + case (m.data)[(m.p)] > 59: + switch { + case (m.data)[(m.p)] > 90: + if 97 <= (m.data)[(m.p)] && (m.data)[(m.p)] <= 122 { + goto st92 + } + case (m.data)[(m.p)] >= 64: + goto st92 + } + default: + goto st92 + } + goto tr87 + tr77: + + m.pb = m.p + + goto st73 + st73: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof73 + } + stCase73: + if (m.data)[(m.p)] == 97 { + goto st74 + } + goto tr75 + st74: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof74 + } + stCase74: + if (m.data)[(m.p)] == 114 { + goto st75 + } + goto tr75 + st75: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof75 + } + stCase75: + if (m.data)[(m.p)] == 97 { + goto st76 + } + goto tr75 + st76: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof76 + } + stCase76: + if (m.data)[(m.p)] == 109 { + goto st68 + } + goto tr75 + tr78: + + m.pb = m.p + + goto st77 + st77: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof77 + } + stCase77: + if (m.data)[(m.p)] == 99 { + goto st78 + } + goto tr75 + st78: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof78 + } + stCase78: + if (m.data)[(m.p)] == 104 { + goto st79 + } + goto tr75 + st79: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof79 + } + stCase79: + if (m.data)[(m.p)] == 101 { + goto st80 + } + goto tr75 + st80: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof80 + } + stCase80: + if (m.data)[(m.p)] == 109 { + goto st81 + } + goto tr75 + st81: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof81 + } + stCase81: + if (m.data)[(m.p)] == 97 { + goto st82 + } + goto tr75 + st82: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof82 + } + stCase82: + if (m.data)[(m.p)] == 115 { + goto st68 + } + goto tr75 + stCase83: + switch (m.data)[(m.p)] { + case 85: + goto tr100 + case 117: + goto tr100 + } + goto tr52 + tr100: + + m.pb = m.p + + // Throw an error when: + // - we are entering here matching the the prefix in the namespace identifier part + // - looking ahead (3 chars) we find a colon + if pos := m.p + 3; pos < m.pe && m.data[pos] == 58 && output.prefix != "" { + m.err = fmt.Errorf(errNoUrnWithinID, pos) + (m.p)-- + + { + goto st95 + } + } + + goto st84 + st84: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof84 + } + stCase84: + switch (m.data)[(m.p)] { + case 82: + goto st85 + case 114: + goto st85 + } + goto tr52 + st85: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof85 + } + stCase85: + switch (m.data)[(m.p)] { + case 78: + goto st86 + case 110: + goto st86 + } + goto tr52 + st86: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof86 + } + stCase86: + if (m.data)[(m.p)] == 58 { + goto tr103 + } + goto tr52 + tr103: + + output.prefix = string(m.text()) + { + goto st48 + } + goto st94 + st94: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof94 + } + stCase94: + goto tr52 + st95: + if (m.p)++; (m.p) == (m.pe) { + goto _testEof95 + } + stCase95: + switch (m.data)[(m.p)] { + case 10: + goto st0 + case 13: + goto st0 + } + goto st95 + stOut: + _testEof2: + m.cs = 2 + goto _testEof + _testEof3: + m.cs = 3 + goto _testEof + _testEof4: + m.cs = 4 + goto _testEof + _testEof87: + m.cs = 87 + goto _testEof + _testEof5: + m.cs = 5 + goto _testEof + _testEof6: + m.cs = 6 + goto _testEof + _testEof7: + m.cs = 7 + goto _testEof + _testEof8: + m.cs = 8 + goto _testEof + _testEof9: + m.cs = 9 + goto _testEof + _testEof10: + m.cs = 10 + goto _testEof + _testEof11: + m.cs = 11 + goto _testEof + _testEof12: + m.cs = 12 + goto _testEof + _testEof13: + m.cs = 13 + goto _testEof + _testEof14: + m.cs = 14 + goto _testEof + _testEof15: + m.cs = 15 + goto _testEof + _testEof16: + m.cs = 16 + goto _testEof + _testEof17: + m.cs = 17 + goto _testEof + _testEof18: + m.cs = 18 + goto _testEof + _testEof19: + m.cs = 19 + goto _testEof + _testEof20: + m.cs = 20 + goto _testEof + _testEof21: + m.cs = 21 + goto _testEof + _testEof22: + m.cs = 22 + goto _testEof + _testEof23: + m.cs = 23 + goto _testEof + _testEof24: + m.cs = 24 + goto _testEof + _testEof25: + m.cs = 25 + goto _testEof + _testEof26: + m.cs = 26 + goto _testEof + _testEof27: + m.cs = 27 + goto _testEof + _testEof28: + m.cs = 28 + goto _testEof + _testEof29: + m.cs = 29 + goto _testEof _testEof30: m.cs = 30 goto _testEof @@ -1507,8 +2472,8 @@ func (m *machine) Parse(input []byte) (*URN, error) { _testEof38: m.cs = 38 goto _testEof - _testEof44: - m.cs = 44 + _testEof88: + m.cs = 88 goto _testEof _testEof39: m.cs = 39 @@ -1516,8 +2481,8 @@ func (m *machine) Parse(input []byte) (*URN, error) { _testEof40: m.cs = 40 goto _testEof - _testEof45: - m.cs = 45 + _testEof89: + m.cs = 89 goto _testEof _testEof41: m.cs = 41 @@ -1528,16 +2493,251 @@ func (m *machine) Parse(input []byte) (*URN, error) { _testEof43: m.cs = 43 goto _testEof + _testEof45: + m.cs = 45 + goto _testEof _testEof46: m.cs = 46 goto _testEof + _testEof47: + m.cs = 47 + goto _testEof + _testEof90: + m.cs = 90 + goto _testEof + _testEof48: + m.cs = 48 + goto _testEof + _testEof49: + m.cs = 49 + goto _testEof + _testEof50: + m.cs = 50 + goto _testEof + _testEof51: + m.cs = 51 + goto _testEof + _testEof52: + m.cs = 52 + goto _testEof + _testEof53: + m.cs = 53 + goto _testEof + _testEof54: + m.cs = 54 + goto _testEof + _testEof55: + m.cs = 55 + goto _testEof + _testEof56: + m.cs = 56 + goto _testEof + _testEof57: + m.cs = 57 + goto _testEof + _testEof58: + m.cs = 58 + goto _testEof + _testEof59: + m.cs = 59 + goto _testEof + _testEof60: + m.cs = 60 + goto _testEof + _testEof61: + m.cs = 61 + goto _testEof + _testEof62: + m.cs = 62 + goto _testEof + _testEof63: + m.cs = 63 + goto _testEof + _testEof64: + m.cs = 64 + goto _testEof + _testEof65: + m.cs = 65 + goto _testEof + _testEof66: + m.cs = 66 + goto _testEof + _testEof67: + m.cs = 67 + goto _testEof + _testEof68: + m.cs = 68 + goto _testEof + _testEof69: + m.cs = 69 + goto _testEof + _testEof91: + m.cs = 91 + goto _testEof + _testEof70: + m.cs = 70 + goto _testEof + _testEof92: + m.cs = 92 + goto _testEof + _testEof71: + m.cs = 71 + goto _testEof + _testEof72: + m.cs = 72 + goto _testEof + _testEof93: + m.cs = 93 + goto _testEof + _testEof73: + m.cs = 73 + goto _testEof + _testEof74: + m.cs = 74 + goto _testEof + _testEof75: + m.cs = 75 + goto _testEof + _testEof76: + m.cs = 76 + goto _testEof + _testEof77: + m.cs = 77 + goto _testEof + _testEof78: + m.cs = 78 + goto _testEof + _testEof79: + m.cs = 79 + goto _testEof + _testEof80: + m.cs = 80 + goto _testEof + _testEof81: + m.cs = 81 + goto _testEof + _testEof82: + m.cs = 82 + goto _testEof + _testEof84: + m.cs = 84 + goto _testEof + _testEof85: + m.cs = 85 + goto _testEof + _testEof86: + m.cs = 86 + goto _testEof + _testEof94: + m.cs = 94 + goto _testEof + _testEof95: + m.cs = 95 + goto _testEof _testEof: { } if (m.p) == (m.eof) { switch m.cs { - case 44, 45: + case 44, 45, 46, 47, 83, 84, 85, 86: + + m.err = fmt.Errorf(errPrefix, m.p) + (m.p)-- + + { + goto st95 + } + + case 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64: + + // In case we are in fallback mode we are now gonna jump to normal RFC2141 URN parsing + if m.parsingMode == All { + // TODO: store why the machine fallback to the RFC2141 one? + output.scim = nil + // Rewind the cursor after the prefix ends ("urn:") + (m.p) = (4) - 1 + + // Go to the "urn" machine from this point on + { + goto st5 + } + } + m.err = fmt.Errorf(errSCIMNamespace, m.p) + (m.p)-- + + { + goto st95 + } + + case 65, 66, 67, 68, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82: + + // In case we are in fallback mode we are now gonna jump to normal RFC2141 URN parsing + if m.parsingMode == All { + // TODO: store why the machine fallback to the RFC2141 one? + output.scim = nil + // Rewind the cursor after the prefix ends ("urn:") + (m.p) = (4) - 1 + + // Go to the "urn" machine from this point on + { + goto st5 + } + } + m.err = fmt.Errorf(errSCIMType, m.p) + (m.p)-- + + { + goto st95 + } + + case 69: + + // In case we are in fallback mode we are now gonna jump to normal RFC2141 URN parsing + if m.parsingMode == All { + // TODO: store why the machine fallback to the RFC2141 one? + output.scim = nil + // Rewind the cursor after the prefix ends ("urn:") + (m.p) = (4) - 1 + + // Go to the "urn" machine from this point on + { + goto st5 + } + } + m.err = fmt.Errorf(errSCIMName, m.p) + (m.p)-- + + { + goto st95 + } + + case 70: + + // In case we are in fallback mode we are now gonna jump to normal RFC2141 URN parsing + if m.parsingMode == All { + // TODO: store why the machine fallback to the RFC2141 one? + output.scim = nil + // Rewind the cursor after the prefix ends ("urn:") + (m.p) = (4) - 1 + + // Go to the "urn" machine from this point on + { + goto st5 + } + } + if m.p == m.pe { + m.err = fmt.Errorf(errSCIMOtherIncomplete, m.p-1) + } else { + m.err = fmt.Errorf(errSCIMOther, m.p) + } + (m.p)-- + + { + goto st95 + } + + case 88, 89: raw := m.text() output.SS = string(raw) @@ -1547,45 +2747,38 @@ func (m *machine) Parse(input []byte) (*URN, error) { } output.norm = string(raw) - case 1, 2, 4: - - m.err = fmt.Errorf(errParse, m.p) - (m.p)-- - - { - goto st46 - } + output.kind = RFC2141 - case 3: + case 1, 2, 3, 4: m.err = fmt.Errorf(errPrefix, m.p) (m.p)-- { - goto st46 + goto st95 } m.err = fmt.Errorf(errParse, m.p) (m.p)-- { - goto st46 + goto st95 } - case 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 41: + case 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37: m.err = fmt.Errorf(errIdentifier, m.p) (m.p)-- { - goto st46 + goto st95 } m.err = fmt.Errorf(errParse, m.p) (m.p)-- { - goto st46 + goto st95 } case 38: @@ -1594,60 +2787,92 @@ func (m *machine) Parse(input []byte) (*URN, error) { (m.p)-- { - goto st46 + goto st95 } m.err = fmt.Errorf(errParse, m.p) (m.p)-- { - goto st46 + goto st95 } - case 42: + case 71, 72: - m.err = fmt.Errorf(errPrefix, m.p) + m.err = fmt.Errorf(errHex, m.p) + (m.p)-- + + { + goto st95 + } + + // In case we are in fallback mode we are now gonna jump to normal RFC2141 URN parsing + if m.parsingMode == All { + // TODO: store why the machine fallback to the RFC2141 one? + output.scim = nil + // Rewind the cursor after the prefix ends ("urn:") + (m.p) = (4) - 1 + + // Go to the "urn" machine from this point on + { + goto st5 + } + } + if m.p == m.pe { + m.err = fmt.Errorf(errSCIMOtherIncomplete, m.p-1) + } else { + m.err = fmt.Errorf(errSCIMOther, m.p) + } (m.p)-- { - goto st46 + goto st95 } + case 5, 41, 42: + m.err = fmt.Errorf(errIdentifier, m.p) (m.p)-- { - goto st46 + goto st95 + } + + m.err = fmt.Errorf(errPrefix, m.p) + (m.p)-- + + { + goto st95 } m.err = fmt.Errorf(errParse, m.p) (m.p)-- { - goto st46 + goto st95 } case 43: - m.err = fmt.Errorf(errNoUrnWithinID, m.p) + m.err = fmt.Errorf(errIdentifier, m.p) (m.p)-- { - goto st46 + goto st95 } - m.err = fmt.Errorf(errIdentifier, m.p) + m.err = fmt.Errorf(errNoUrnWithinID, m.p) (m.p)-- { - goto st46 + goto st95 } m.err = fmt.Errorf(errParse, m.p) (m.p)-- { - goto st46 + goto st95 } case 39, 40: @@ -1656,22 +2881,50 @@ func (m *machine) Parse(input []byte) (*URN, error) { (m.p)-- { - goto st46 + goto st95 } m.err = fmt.Errorf(errSpecificString, m.p) (m.p)-- { - goto st46 + goto st95 } m.err = fmt.Errorf(errParse, m.p) (m.p)-- { - goto st46 + goto st95 + } + + case 91: + + output.scim.Name = string(m.data[output.scim.pos:m.p]) + + raw := m.text() + output.SS = string(raw) + // Iterate upper letters lowering them + for _, i := range m.tolower { + raw[i] = raw[i] + 32 + } + output.norm = string(raw) + + output.kind = RFC7643 + + case 92, 93: + + output.scim.Other = string(m.data[output.scim.pos:m.p]) + + raw := m.text() + output.SS = string(raw) + // Iterate upper letters lowering them + for _, i := range m.tolower { + raw[i] = raw[i] + 32 } + output.norm = string(raw) + + output.kind = RFC7643 } } @@ -1686,3 +2939,8 @@ func (m *machine) Parse(input []byte) (*URN, error) { return output, nil } + +func (m *machine) WithParsingMode(x ParsingMode) { + m.parsingMode = x + m.parsingModeSet = true +} diff --git a/machine.go.rl b/machine.go.rl index 3bc05a6..517ac4f 100644 --- a/machine.go.rl +++ b/machine.go.rl @@ -2,15 +2,22 @@ package urn import ( "fmt" + + scimschema "github.com/leodido/go-urn/scim/schema" ) var ( - errPrefix = "expecting the prefix to be the \"urn\" string (whatever case) [col %d]" - errIdentifier = "expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its start) [col %d]" - errSpecificString = "expecting the specific string to be a string containing alnum, hex, or others ([()+,-.:=@;$_!*']) chars [col %d]" - errNoUrnWithinID = "expecting the identifier to not contain the \"urn\" reserved string [col %d]" - errHex = "expecting the specific string hex chars to be well-formed (%%alnum{2}) [col %d]" - errParse = "parsing error [col %d]" + errPrefix = "expecting the prefix to be the \"urn\" string (whatever case) [col %d]" + errIdentifier = "expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its beginning) [col %d]" + errSpecificString = "expecting the specific string to be a string containing alnum, hex, or others ([()+,-.:=@;$_!*']) chars [col %d]" + errNoUrnWithinID = "expecting the identifier to not contain the \"urn\" reserved string [col %d]" + errHex = "expecting the specific string hex chars to be well-formed (%%alnum{2}) [col %d]" + errParse = "parsing error [col %d]" + errSCIMNamespace = "expecing the SCIM namespace identifier (ietf:params:scim) [col %d]" + errSCIMType = "expecting a correct SCIM type (schemas, api, param) [col %d]" + errSCIMName = "expecting one or more alnum char in the SCIM name part [col %d]" + errSCIMOther = "expecting a well-formed other SCIM part [col %d]" + errSCIMOtherIncomplete = "expecting a not empty SCIM other part after colon [col %d]" ) %%{ @@ -31,6 +38,17 @@ action set_pre { output.prefix = string(m.text()) } +action throw_pre_urn_err { + // Throw an error when: + // - we are entering here matching the the prefix in the namespace identifier part + // - looking ahead (3 chars) we find a colon + if pos := m.p + 3; pos < m.pe && m.data[pos] == 58 && output.prefix != "" { + m.err = fmt.Errorf(errNoUrnWithinID, pos) + fhold; + fgoto fail; + } +} + action set_nid { output.ID = string(m.text()) } @@ -81,9 +99,105 @@ action err_parse { fgoto fail; } -pre = ([uU][rR][nN] @err(err_pre)) >mark %set_pre; +action base_type { + output.kind = RFC2141; +} + +action err_scim_nid { + // In case we are in fallback mode we are now gonna jump to normal RFC2141 URN parsing + if m.parsingMode == All { + // TODO: store why the machine fallback to the RFC2141 one? + output.scim = nil; + // Rewind the cursor after the prefix ends ("urn:") + fexec 4; + // Go to the "urn" machine from this point on + fgoto urn; + } + m.err = fmt.Errorf(errSCIMNamespace, m.p) + fhold; + fgoto fail; +} + +action err_scim_type { + // In case we are in fallback mode we are now gonna jump to normal RFC2141 URN parsing + if m.parsingMode == All { + // TODO: store why the machine fallback to the RFC2141 one? + output.scim = nil; + // Rewind the cursor after the prefix ends ("urn:") + fexec 4; + // Go to the "urn" machine from this point on + fgoto urn; + } + m.err = fmt.Errorf(errSCIMType, m.p) + fhold; + fgoto fail; +} + +action err_scim_name { + // In case we are in fallback mode we are now gonna jump to normal RFC2141 URN parsing + if m.parsingMode == All { + // TODO: store why the machine fallback to the RFC2141 one? + output.scim = nil; + // Rewind the cursor after the prefix ends ("urn:") + fexec 4; + // Go to the "urn" machine from this point on + fgoto urn; + } + m.err = fmt.Errorf(errSCIMName, m.p) + fhold; + fgoto fail; +} + +action err_scim_other { + // In case we are in fallback mode we are now gonna jump to normal RFC2141 URN parsing + if m.parsingMode == All { + // TODO: store why the machine fallback to the RFC2141 one? + output.scim = nil; + // Rewind the cursor after the prefix ends ("urn:") + fexec 4; + // Go to the "urn" machine from this point on + fgoto urn; + } + if m.p == m.pe { + m.err = fmt.Errorf(errSCIMOtherIncomplete, m.p-1) + } else { + m.err = fmt.Errorf(errSCIMOther, m.p) + } + fhold; + fgoto fail; +} + +action scim_type { + output.kind = RFC7643; +} + +action create_scim { + output.scim = &SCIM{}; +} + +action set_scim_type { + output.scim.Type = scimschema.TypeFromString(string(m.text())) +} + +action mark_scim_name { + output.scim.pos = m.p +} + +action set_scim_name { + output.scim.Name = string(m.data[output.scim.pos:m.p]) +} -nid = (alnum >mark (alnum | '-'){0,31}) %set_nid; +action mark_scim_other { + output.scim.pos = m.p +} + +action set_scim_other { + output.scim.Other = string(m.data[output.scim.pos:m.p]) +} + +pre = ([uU] @err(err_pre) [rR] @err(err_pre) [nN] @err(err_pre)) >mark >throw_pre_urn_err %set_pre ; + +nid = (alnum >mark (alnum | '-'){0,31}) $err(err_nid) %set_nid; hex = '%' (digit | lower | upper >tolower){2} $err(err_hex); @@ -91,9 +205,31 @@ sss = (alnum | [()+,\-.:=@;$_!*']); nss = (sss | hex)+ $err(err_nss); +nid_not_urn = (nid - pre %err(err_urn)); + +urn := (nid_not_urn ':' nss >mark %set_nss) $err(err_parse) %eof(base_type); + +urn_only := pre ':' $err(err_pre) @{ fgoto urn; }; + +### SCIM BEG + +scim_nid = 'ietf:params:scim' >mark %set_nid %create_scim $err(err_scim_nid); + +scim_other = ':' (sss | hex)+ >mark_scim_other %set_scim_other $err(err_scim_other); + +scim_name = (alnum)+ >mark_scim_name %set_scim_name $err(err_scim_name); + +scim_type = ('schemas' | 'api' | 'param') >mark %set_scim_type $err(err_scim_type); + +scim := (scim_nid ':' scim_type ':' scim_name scim_other? %set_nss) %eof(scim_type); + +scim_only := pre ':' $err(err_pre) @{ fgoto scim; }; + +### SCIM END + fail := (any - [\n\r])* @err{ fgoto main; }; -main := (pre ':' (nid - pre %err(err_urn)) $err(err_nid) ':' nss >mark %set_nss) $err(err_parse); +main := (pre ':' $err(err_pre) @{ fgoto scim; }) $err(err_parse); }%% @@ -103,6 +239,7 @@ main := (pre ':' (nid - pre %err(err_urn)) $err(err_nid) ':' nss >mark %set_nss) type Machine interface { Error() error Parse(input []byte) (*URN, error) + WithParsingMode(ParsingMode) } type machine struct { @@ -111,11 +248,23 @@ type machine struct { p, pe, eof, pb int err error tolower []int + parsingMode ParsingMode + parsingModeSet bool } // NewMachine creates a new FSM able to parse RFC 2141 strings. -func NewMachine() Machine { - m := &machine{} +func NewMachine(options ...Option) Machine { + m := &machine{ + parsingModeSet: false, + } + + for _, o := range options { + o(m) + } + // Set default parsing mode + if !m.parsingModeSet { + m.WithParsingMode(DefaultParsingMode) + } %% access m.; %% variable p m.p; @@ -137,7 +286,7 @@ func (m *machine) text() []byte { return m.data[m.pb:m.p] } -// Parse parses the input byte array as a RFC 2141 string. +// Parse parses the input byte array as a RFC 2141 or RFC7643 string. func (m *machine) Parse(input []byte) (*URN, error) { m.data = input m.p = 0 @@ -148,7 +297,21 @@ func (m *machine) Parse(input []byte) (*URN, error) { m.tolower = []int{} output := &URN{} - %% write init; + switch m.parsingMode { + case RFC2141Only: + m.cs = en_urn_only + break + + case RFC7643Only: + m.cs = en_scim_only + break + + case All: + fallthrough + default: + %% write init; + break + } %% write exec; if m.cs < first_final || m.cs == en_fail { @@ -157,3 +320,8 @@ func (m *machine) Parse(input []byte) (*URN, error) { return output, nil } + +func (m *machine) WithParsingMode(x ParsingMode) { + m.parsingMode = x + m.parsingModeSet = true +} \ No newline at end of file diff --git a/machine_test.go b/machine_test.go index bb5074e..68bd488 100644 --- a/machine_test.go +++ b/machine_test.go @@ -4,11 +4,19 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) -func exec(t *testing.T, testCases []testCase) { +func TestDefaultParsingMode(t *testing.T) { + m := NewMachine() + require.NotNil(t, m) + require.IsType(t, &machine{}, m) + require.Equal(t, DefaultParsingMode, m.(*machine).parsingMode) +} + +func exec(t *testing.T, testCases []testCase, mode ParsingMode) { for ii, tt := range testCases { - urn, err := NewMachine().Parse([]byte(tt.in)) + urn, err := NewMachine(WithParsingMode(mode)).Parse([]byte(tt.in)) ok := err == nil if ok { @@ -18,18 +26,29 @@ func exec(t *testing.T, testCases []testCase) { assert.Equal(t, tt.obj.SS, urn.SS, herror(ii, tt)) assert.Equal(t, tt.str, urn.String(), herror(ii, tt)) assert.Equal(t, tt.norm, urn.Normalize().String(), herror(ii, tt)) + if mode == All || mode == RFC7643Only { + assert.Equal(t, tt.isSCIM, urn.IsSCIM(), herror(ii, tt)) + } } else { assert.False(t, tt.ok, herror(ii, tt)) assert.Empty(t, urn, herror(ii, tt)) - assert.Equal(t, tt.estr, err.Error()) + assert.Equal(t, tt.estr, err.Error(), herror(ii, tt)) } } } -func TestParse(t *testing.T) { - exec(t, genericTestCases) +func TestParse2141Only(t *testing.T) { + exec(t, urn2141OnlyTestCases, RFC2141Only) +} + +func TestParseUrnLex2141Only(t *testing.T) { + exec(t, urnlexTestCases, RFC2141Only) +} + +func TestSCIMOnly(t *testing.T) { + exec(t, scimOnlyTestCases, RFC7643Only) } -func TestParseUrnLex(t *testing.T) { - exec(t, urnlexTestCases) +func TestFallbackMode(t *testing.T) { + exec(t, fallbackTestCases, All) } diff --git a/options.go b/options.go new file mode 100644 index 0000000..c543835 --- /dev/null +++ b/options.go @@ -0,0 +1,9 @@ +package urn + +type Option func(Machine) + +func WithParsingMode(mode ParsingMode) Option { + return func(m Machine) { + m.WithParsingMode(mode) + } +} diff --git a/parsing_mode.go b/parsing_mode.go new file mode 100644 index 0000000..a7f7004 --- /dev/null +++ b/parsing_mode.go @@ -0,0 +1,11 @@ +package urn + +type ParsingMode int + +const ( + All ParsingMode = iota // Fallback mode + RFC2141Only + RFC7643Only +) + +const DefaultParsingMode = RFC2141Only diff --git a/performance_test.go b/performance_test.go index b931178..092cbc1 100644 --- a/performance_test.go +++ b/performance_test.go @@ -6,26 +6,26 @@ import ( ) var benchs = []testCase{ - genericTestCases[14], - genericTestCases[2], - genericTestCases[6], - genericTestCases[10], - genericTestCases[11], - genericTestCases[13], - genericTestCases[20], - genericTestCases[23], - genericTestCases[33], - genericTestCases[45], - genericTestCases[47], - genericTestCases[48], - genericTestCases[50], - genericTestCases[52], - genericTestCases[53], - genericTestCases[57], - genericTestCases[62], - genericTestCases[63], - genericTestCases[67], - genericTestCases[60], + urn2141OnlyTestCases[14], + urn2141OnlyTestCases[2], + urn2141OnlyTestCases[6], + urn2141OnlyTestCases[10], + urn2141OnlyTestCases[11], + urn2141OnlyTestCases[13], + urn2141OnlyTestCases[20], + urn2141OnlyTestCases[23], + urn2141OnlyTestCases[33], + urn2141OnlyTestCases[45], + urn2141OnlyTestCases[47], + urn2141OnlyTestCases[48], + urn2141OnlyTestCases[50], + urn2141OnlyTestCases[52], + urn2141OnlyTestCases[53], + urn2141OnlyTestCases[57], + urn2141OnlyTestCases[62], + urn2141OnlyTestCases[63], + urn2141OnlyTestCases[67], + urn2141OnlyTestCases[60], } // This is here to avoid compiler optimizations that diff --git a/scim.go b/scim.go new file mode 100644 index 0000000..1547813 --- /dev/null +++ b/scim.go @@ -0,0 +1,10 @@ +package urn + +import scimschema "github.com/leodido/go-urn/scim/schema" + +type SCIM struct { + Type scimschema.Type + Name string + Other string + pos int +} diff --git a/scim/schema/type.go b/scim/schema/type.go new file mode 100644 index 0000000..1349182 --- /dev/null +++ b/scim/schema/type.go @@ -0,0 +1,36 @@ +package scimschema + +type Type int + +const ( + Unsupported Type = iota + Schemas + API + Param +) + +func (t Type) String() string { + switch t { + case Schemas: + return "schemas" + case API: + return "api" + case Param: + return "param" + } + + return "" +} + +func TypeFromString(input string) Type { + switch input { + case "schemas": + return Schemas + case "api": + return API + case "param": + return Param + } + + return Unsupported +} diff --git a/scim/schema/type_test.go b/scim/schema/type_test.go new file mode 100644 index 0000000..2b3c13e --- /dev/null +++ b/scim/schema/type_test.go @@ -0,0 +1,25 @@ +package scimschema + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestTypeFromString(t *testing.T) { + uns := TypeFromString("wrong") + require.Equal(t, Unsupported, uns) + require.Empty(t, uns.String()) + + schemas := TypeFromString("schemas") + require.Equal(t, Schemas, schemas) + require.Equal(t, "schemas", schemas.String()) + + api := TypeFromString("api") + require.Equal(t, API, api) + require.Equal(t, "api", api.String()) + + param := TypeFromString("param") + require.Equal(t, Param, param) + require.Equal(t, "param", param.String()) +} diff --git a/scim_test.go b/scim_test.go new file mode 100644 index 0000000..ebe9cd9 --- /dev/null +++ b/scim_test.go @@ -0,0 +1,25 @@ +package urn + +import ( + "encoding/json" + "testing" + + scimschema "github.com/leodido/go-urn/scim/schema" + "github.com/stretchr/testify/require" +) + +func TestSCIMJSONMarshaling(t *testing.T) { + t.Run("roundtrip", func(t *testing.T) { + // Marshal + exp := SCIM{Type: scimschema.Schemas, Name: "core", Other: "extension:enterprise:2.0:User"} + mar, err := json.Marshal(exp) + require.NoError(t, err) + + // Unmarshal + var act SCIM + err = json.Unmarshal(mar, &act) + require.NoError(t, err) + + require.Equal(t, exp, act) + }) +} diff --git a/tables_test.go b/tables_test.go index fe1bf0c..0a908da 100644 --- a/tables_test.go +++ b/tables_test.go @@ -1,6 +1,7 @@ package urn import ( + "fmt" "strconv" "strings" ) @@ -19,12 +20,13 @@ func rxpad(str string, lim int) string { } type testCase struct { - in []byte // the input - ok bool // whether it is valid or not - obj *URN // a pointer to the resulting urn.URN instance - str string // string representation - norm string // norm string representation - estr string // error string + in []byte // the input + ok bool // whether it is valid or not + obj *URN // a pointer to the resulting urn.URN instance + str string // string representation + norm string // norm string representation + estr string // error string + isSCIM bool // whether it is a SCIM URN or not } var urnlexTestCases = []testCase{ @@ -40,6 +42,7 @@ var urnlexTestCases = []testCase{ "urn:lex:it:stato:legge:2003-09-21;456", "urn:lex:it:stato:legge:2003-09-21;456", "", + false, }, // Italian decree // fixme(leodido) @@ -56,6 +59,7 @@ var urnlexTestCases = []testCase{ // "it:ministero.giustizia:decreto:1992-07-24;358~art5", // "it:ministero.giustizia:decreto:1992-07-24;358~art5", // "", + // false, // }, // French act { @@ -69,6 +73,7 @@ var urnlexTestCases = []testCase{ "urn:lex:fr:etat:lois:2004-12-06;321", "urn:lex:fr:etat:lois:2004-12-06;321", "", + false, }, // Spanish act { @@ -82,6 +87,7 @@ var urnlexTestCases = []testCase{ "urn:lex:es:estado:ley:2002-07-12;123", "urn:lex:es:estado:ley:2002-07-12;123", "", + false, }, // Glarus Swiss Canton decree { @@ -95,6 +101,7 @@ var urnlexTestCases = []testCase{ "urn:lex:ch;glarus:regiere:erlass:2007-10-15;963", "urn:lex:ch;glarus:regiere:erlass:2007-10-15;963", "", + false, }, // EU Council Directive { @@ -108,6 +115,7 @@ var urnlexTestCases = []testCase{ "urn:lex:eu:council:directive:2010-03-09;2010-19-UE", "urn:lex:eu:council:directive:2010-03-09;2010-19-UE", "", + false, }, { []byte("urn:lex:eu:council:directive:2010-03-09;2010-19-UE"), @@ -120,6 +128,7 @@ var urnlexTestCases = []testCase{ "urn:lex:eu:council:directive:2010-03-09;2010-19-UE", "urn:lex:eu:council:directive:2010-03-09;2010-19-UE", "", + false, }, // US FSC decision { @@ -133,10 +142,194 @@ var urnlexTestCases = []testCase{ "urn:lex:us:federal.supreme.court:decision:1963-03-18;372.us.335", "urn:lex:us:federal.supreme.court:decision:1963-03-18;372.us.335", "", + false, }, } -var genericTestCases = []testCase{ +var scimOnlyTestCases = []testCase{ + // ok + { + []byte("urn:ietf:params:scim:schemas:core:2.0:User"), + true, + &URN{ + prefix: "urn", + ID: "ietf:params:scim", + SS: "schemas:core:2.0:User", + }, + "urn:ietf:params:scim:schemas:core:2.0:User", + "urn:ietf:params:scim:schemas:core:2.0:User", + "", + true, + }, + { + []byte("urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"), + true, + &URN{ + prefix: "urn", + ID: "ietf:params:scim", + SS: "schemas:extension:enterprise:2.0:User", + }, + "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User", + "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User", + "", + true, + }, + { + []byte("urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:userName"), + true, + &URN{ + prefix: "urn", + ID: "ietf:params:scim", + SS: "schemas:extension:enterprise:2.0:User:userName", + }, + "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:userName", + "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:userName", + "", + true, + }, + { + []byte("urn:ietf:params:scim:api:messages:2.0:ListResponse"), + true, + &URN{ + prefix: "urn", + ID: "ietf:params:scim", + SS: "api:messages:2.0:ListResponse", + }, + "urn:ietf:params:scim:api:messages:2.0:ListResponse", + "urn:ietf:params:scim:api:messages:2.0:ListResponse", + "", + true, + }, + { + []byte("urn:ietf:params:scim:schemas:core"), + true, + &URN{ + prefix: "urn", + ID: "ietf:params:scim", + SS: "schemas:core", + }, + "urn:ietf:params:scim:schemas:core", + "urn:ietf:params:scim:schemas:core", + "", + true, + }, + { + []byte("urn:ietf:params:scim:param:core"), + true, + &URN{ + prefix: "urn", + ID: "ietf:params:scim", + SS: "param:core", + }, + "urn:ietf:params:scim:param:core", + "urn:ietf:params:scim:param:core", + "", + true, + }, + + // no + { + []byte("arn:ietf:params:scim:schemas:core"), + false, + nil, + "", + "", + fmt.Sprintf(errPrefix, 0), + false, + }, + { + []byte("usn:ietf:params:scim:schemas:core"), + false, + nil, + "", + "", + fmt.Sprintf(errPrefix, 1), + false, + }, + { + []byte("urm:ietf:params:scim:schemas:core"), + false, + nil, + "", + "", + fmt.Sprintf(errPrefix, 2), + false, + }, + { + []byte("urno:ietf:params:scim:schemas:core"), + false, + nil, + "", + "", + fmt.Sprintf(errPrefix, 3), + false, + }, + { + []byte("urno"), + false, + nil, + "", + "", + fmt.Sprintf(errPrefix, 3), + false, + }, + { + []byte("urn:WRONG:schemas:core"), + false, + nil, + "", + "", + fmt.Sprintf(errSCIMNamespace, 4), + false, + }, + { + []byte("urn:ietf:params:scim:WRONG:core"), + false, + nil, + "", + "", + fmt.Sprintf(errSCIMType, 21), + false, + }, + { + []byte("urn:ietf:params:scim:schemas:$"), + false, + nil, + "", + "", + fmt.Sprintf(errSCIMName, 29), + false, + }, + { + []byte("urn:ietf:params:scim:schemas:core-"), + false, + nil, + "", + "", + fmt.Sprintf(errSCIMName, 33), + false, + }, + { + []byte("urn:ietf:params:scim:schemas:core:"), + false, + nil, + "", + "", + fmt.Sprintf(errSCIMOtherIncomplete, 33), + false, + }, + { + []byte("urn:ietf:params:scim:schemas:core:2.&"), + false, + nil, + "", + "", + fmt.Sprintf(errSCIMOther, 36), + false, + }, +} + +var urn2141OnlyTestCases = []testCase{ // ok { []byte("urn:simple:simple"), @@ -149,6 +342,7 @@ var genericTestCases = []testCase{ "urn:simple:simple", "urn:simple:simple", "", + false, }, { []byte("urn:ciao:%5D"), @@ -161,6 +355,7 @@ var genericTestCases = []testCase{ "urn:ciao:%5D", "urn:ciao:%5d", "", + false, }, // ok - RFC examples @@ -175,6 +370,7 @@ var genericTestCases = []testCase{ "URN:foo:a123,456", "urn:foo:a123,456", "", + false, }, { []byte("urn:foo:a123,456"), @@ -187,6 +383,7 @@ var genericTestCases = []testCase{ "urn:foo:a123,456", "urn:foo:a123,456", "", + false, }, { []byte("urn:FOO:a123,456"), @@ -199,6 +396,7 @@ var genericTestCases = []testCase{ "urn:FOO:a123,456", "urn:foo:a123,456", "", + false, }, { []byte("urn:foo:A123,456"), @@ -211,6 +409,7 @@ var genericTestCases = []testCase{ "urn:foo:A123,456", "urn:foo:A123,456", "", + false, }, { []byte("urn:foo:a123%2C456"), @@ -223,6 +422,7 @@ var genericTestCases = []testCase{ "urn:foo:a123%2C456", "urn:foo:a123%2c456", "", + false, }, { []byte("URN:FOO:a123%2c456"), @@ -235,6 +435,7 @@ var genericTestCases = []testCase{ "URN:FOO:a123%2c456", "urn:foo:a123%2c456", "", + false, }, { []byte("URN:FOO:ABC%FFabc123%2c456"), @@ -247,6 +448,7 @@ var genericTestCases = []testCase{ "URN:FOO:ABC%FFabc123%2c456", "urn:foo:ABC%ffabc123%2c456", "", + false, }, { []byte("URN:FOO:ABC%FFabc123%2C456%9A"), @@ -259,6 +461,7 @@ var genericTestCases = []testCase{ "URN:FOO:ABC%FFabc123%2C456%9A", "urn:foo:ABC%ffabc123%2c456%9a", "", + false, }, // ok - SCIM v2 @@ -273,6 +476,7 @@ var genericTestCases = []testCase{ "urn:ietf:params:scim:schemas:core:2.0:User", "urn:ietf:params:scim:schemas:core:2.0:User", "", + true, }, { []byte("urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"), @@ -285,6 +489,7 @@ var genericTestCases = []testCase{ "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User", "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User", "", + true, }, { []byte("urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:userName"), @@ -297,6 +502,7 @@ var genericTestCases = []testCase{ "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:userName", "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:userName", "", + true, }, { []byte("urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:meta.lastModified"), @@ -309,6 +515,7 @@ var genericTestCases = []testCase{ "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:meta.lastModified", "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:meta.lastModified", "", + true, }, // ok - minimum urn @@ -323,6 +530,7 @@ var genericTestCases = []testCase{ "urn:a:b", "urn:a:b", "", + false, }, { []byte("urn:a::"), @@ -335,6 +543,7 @@ var genericTestCases = []testCase{ "urn:a::", "urn:a::", "", + false, }, { []byte("urn:a:-"), @@ -347,6 +556,7 @@ var genericTestCases = []testCase{ "urn:a:-", "urn:a:-", "", + false, }, // ok - URN prefix is case-insensitive @@ -361,6 +571,7 @@ var genericTestCases = []testCase{ "URN:simple:simple", "urn:simple:simple", "", + false, }, { []byte("Urn:simple:simple"), @@ -373,6 +584,7 @@ var genericTestCases = []testCase{ "Urn:simple:simple", "urn:simple:simple", "", + false, }, // ok - ID can contain the "urn" string but it can not be exactly equal to it @@ -387,6 +599,7 @@ var genericTestCases = []testCase{ "urn:urna:simple", "urn:urna:simple", "", + false, }, { []byte("urn:burnout:nss"), @@ -399,6 +612,7 @@ var genericTestCases = []testCase{ "urn:burnout:nss", "urn:burnout:nss", "", + false, }, { []byte("urn:burn:nss"), @@ -411,6 +625,7 @@ var genericTestCases = []testCase{ "urn:burn:nss", "urn:burn:nss", "", + false, }, { []byte("urn:urnurnurn:x"), @@ -423,6 +638,7 @@ var genericTestCases = []testCase{ "urn:urnurnurn:x", "urn:urnurnurn:x", "", + false, }, // ok - ID can contains maximum 32 characters @@ -437,6 +653,7 @@ var genericTestCases = []testCase{ "urn:abcdefghilmnopqrstuvzabcdefghilm:x", "urn:abcdefghilmnopqrstuvzabcdefghilm:x", "", + false, }, // ok - ID can be alpha numeric @@ -451,6 +668,7 @@ var genericTestCases = []testCase{ "URN:123:x", "urn:123:x", "", + false, }, { []byte("URN:1ab:x"), @@ -463,6 +681,7 @@ var genericTestCases = []testCase{ "URN:1ab:x", "urn:1ab:x", "", + false, }, { []byte("URN:a1b:x"), @@ -475,6 +694,7 @@ var genericTestCases = []testCase{ "URN:a1b:x", "urn:a1b:x", "", + false, }, { []byte("URN:a12:x"), @@ -487,6 +707,7 @@ var genericTestCases = []testCase{ "URN:a12:x", "urn:a12:x", "", + false, }, { []byte("URN:cd2:x"), @@ -499,6 +720,7 @@ var genericTestCases = []testCase{ "URN:cd2:x", "urn:cd2:x", "", + false, }, // ok - ID can contain an hyphen (not in its first position, see below) @@ -513,6 +735,7 @@ var genericTestCases = []testCase{ "URN:abcd-:x", "urn:abcd-:x", "", + false, }, { []byte("URN:abcd-abcd:x"), @@ -525,6 +748,7 @@ var genericTestCases = []testCase{ "URN:abcd-abcd:x", "urn:abcd-abcd:x", "", + false, }, { []byte("URN:a123-456z:x"), @@ -537,6 +761,7 @@ var genericTestCases = []testCase{ "URN:a123-456z:x", "urn:a123-456z:x", "", + false, }, // ok - SS can contain the "urn" string, also be exactly equal to it @@ -551,6 +776,7 @@ var genericTestCases = []testCase{ "urn:urnx:urn", "urn:urnx:urn", "", + false, }, { []byte("urn:urnurnurn:urn"), @@ -563,6 +789,7 @@ var genericTestCases = []testCase{ "urn:urnurnurn:urn", "urn:urnurnurn:urn", "", + false, }, { []byte("urn:hey:urnurnurn"), @@ -575,6 +802,7 @@ var genericTestCases = []testCase{ "urn:hey:urnurnurn", "urn:hey:urnurnurn", "", + false, }, // ok - SS can contains and discerns multiple colons, also at the end @@ -589,6 +817,7 @@ var genericTestCases = []testCase{ "urn:ciao:a:b:c", "urn:ciao:a:b:c", "", + false, }, { []byte("urn:aaa:x:y:"), @@ -601,6 +830,7 @@ var genericTestCases = []testCase{ "urn:aaa:x:y:", "urn:aaa:x:y:", "", + false, }, { []byte("urn:aaa:x:y:"), @@ -613,6 +843,7 @@ var genericTestCases = []testCase{ "urn:aaa:x:y:", "urn:aaa:x:y:", "", + false, }, // ok - SS can contain (and also start with) some non-alphabetical (ie., OTHER) characters @@ -627,6 +858,7 @@ var genericTestCases = []testCase{ "urn:ciao:-", "urn:ciao:-", "", + false, }, { []byte("urn:ciao::"), @@ -639,6 +871,7 @@ var genericTestCases = []testCase{ "urn:ciao::", "urn:ciao::", "", + false, }, { []byte("urn:colon:::::nss"), @@ -651,6 +884,7 @@ var genericTestCases = []testCase{ "urn:colon:::::nss", "urn:colon:::::nss", "", + false, }, { []byte("urn:ciao:!"), @@ -663,6 +897,7 @@ var genericTestCases = []testCase{ "urn:ciao:!", "urn:ciao:!", "", + false, }, { []byte("urn:ciao:!!*"), @@ -675,6 +910,7 @@ var genericTestCases = []testCase{ "urn:ciao:!!*", "urn:ciao:!!*", "", + false, }, { []byte("urn:ciao:-!:-,:x"), @@ -687,6 +923,7 @@ var genericTestCases = []testCase{ "urn:ciao:-!:-,:x", "urn:ciao:-!:-,:x", "", + false, }, { []byte("urn:ciao:=@"), @@ -699,6 +936,7 @@ var genericTestCases = []testCase{ "urn:ciao:=@", "urn:ciao:=@", "", + false, }, { []byte("urn:ciao:@!=%2C(xyz)+a,b.*@g=$_'"), @@ -711,6 +949,7 @@ var genericTestCases = []testCase{ "urn:ciao:@!=%2C(xyz)+a,b.*@g=$_'", "urn:ciao:@!=%2c(xyz)+a,b.*@g=$_'", "", + false, }, // ok - SS can contain (and also start with) hexadecimal representation of octets @@ -725,6 +964,7 @@ var genericTestCases = []testCase{ "URN:hexes:%25", "urn:hexes:%25", "", + false, }, // Literal use of the "%" character in a namespace must be encoded using "%25" { []byte("URN:x:abc%1Dz%2F%3az"), @@ -737,6 +977,7 @@ var genericTestCases = []testCase{ "URN:x:abc%1Dz%2F%3az", "urn:x:abc%1dz%2f%3az", "", + false, }, // Literal use of the "%" character in a namespace must be encoded using "%25" // no - ID can not start with an hyphen @@ -746,7 +987,8 @@ var genericTestCases = []testCase{ nil, "", "", - `expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its start) [col 4]`, + `expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its beginning) [col 4]`, + false, }, { []byte("URN:---xxx:x"), @@ -754,7 +996,8 @@ var genericTestCases = []testCase{ nil, "", "", - `expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its start) [col 4]`, + `expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its beginning) [col 4]`, + false, }, // no - ID can not start with a colon @@ -764,7 +1007,8 @@ var genericTestCases = []testCase{ nil, "", "", - `expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its start) [col 4]`, + `expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its beginning) [col 4]`, + false, }, { []byte("urn::::nss"), @@ -772,7 +1016,8 @@ var genericTestCases = []testCase{ nil, "", "", - `expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its start) [col 4]`, + `expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its beginning) [col 4]`, + false, }, // no - ID can not contains more than 32 characters @@ -782,7 +1027,8 @@ var genericTestCases = []testCase{ nil, "", "", - `expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its start) [col 36]`, + `expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its beginning) [col 36]`, + false, }, // no - ID can not contain special characters @@ -792,7 +1038,8 @@ var genericTestCases = []testCase{ nil, "", "", - `expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its start) [col 5]`, + `expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its beginning) [col 5]`, + false, }, { []byte("URN:@,:x"), @@ -800,7 +1047,8 @@ var genericTestCases = []testCase{ nil, "", "", - `expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its start) [col 4]`, + `expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its beginning) [col 4]`, + false, }, { []byte("URN:#,:x"), @@ -808,7 +1056,8 @@ var genericTestCases = []testCase{ nil, "", "", - `expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its start) [col 4]`, + `expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its beginning) [col 4]`, + false, }, { []byte("URN:bc'.@:x"), @@ -816,7 +1065,8 @@ var genericTestCases = []testCase{ nil, "", "", - `expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its start) [col 6]`, + `expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its beginning) [col 6]`, + false, }, // no - ID can not be equal to "urn" @@ -827,6 +1077,7 @@ var genericTestCases = []testCase{ "", "", `expecting the identifier to not contain the "urn" reserved string [col 7]`, + false, }, { []byte("urn:URN:NSS"), @@ -835,6 +1086,7 @@ var genericTestCases = []testCase{ "", "", `expecting the identifier to not contain the "urn" reserved string [col 7]`, + false, }, { []byte("URN:URN:NSS"), @@ -843,6 +1095,7 @@ var genericTestCases = []testCase{ "", "", `expecting the identifier to not contain the "urn" reserved string [col 7]`, + false, }, { []byte("urn:UrN:NSS"), @@ -851,6 +1104,7 @@ var genericTestCases = []testCase{ "", "", `expecting the identifier to not contain the "urn" reserved string [col 7]`, + false, }, { []byte("urn:Urn:NSS"), @@ -859,6 +1113,7 @@ var genericTestCases = []testCase{ "", "", `expecting the identifier to not contain the "urn" reserved string [col 7]`, + false, }, // no - ID can not contain spaces @@ -868,7 +1123,8 @@ var genericTestCases = []testCase{ nil, "", "", - `expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its start) [col 9]`, + `expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its beginning) [col 9]`, + false, }, // no - SS can not contain spaces @@ -879,6 +1135,7 @@ var genericTestCases = []testCase{ "", "", `expecting the specific string to be a string containing alnum, hex, or others ([()+,-.:=@;$_!*']) chars [col 13]`, + false, }, // no - SS can not contain reserved characters (can accept them only if %-escaped) @@ -889,6 +1146,7 @@ var genericTestCases = []testCase{ "", "", `expecting the specific string hex chars to be well-formed (%alnum{2}) [col 7]`, + false, }, { []byte("urn:a:?"), @@ -897,6 +1155,7 @@ var genericTestCases = []testCase{ "", "", `expecting the specific string to be a string containing alnum, hex, or others ([()+,-.:=@;$_!*']) chars [col 6]`, + false, }, { []byte("urn:a:#"), @@ -905,6 +1164,7 @@ var genericTestCases = []testCase{ "", "", `expecting the specific string to be a string containing alnum, hex, or others ([()+,-.:=@;$_!*']) chars [col 6]`, + false, }, { []byte("urn:a:/"), @@ -913,6 +1173,7 @@ var genericTestCases = []testCase{ "", "", `expecting the specific string to be a string containing alnum, hex, or others ([()+,-.:=@;$_!*']) chars [col 6]`, + false, }, // no - Incomplete URNs @@ -922,7 +1183,8 @@ var genericTestCases = []testCase{ nil, "", "", - `expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its start) [col 4]`, + `expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its beginning) [col 4]`, + false, }, { []byte("urn::"), @@ -930,7 +1192,8 @@ var genericTestCases = []testCase{ nil, "", "", - `expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its start) [col 4]`, + `expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its beginning) [col 4]`, + false, }, { []byte("urn:a:"), @@ -939,6 +1202,7 @@ var genericTestCases = []testCase{ "", "", `expecting the specific string to be a string containing alnum, hex, or others ([()+,-.:=@;$_!*']) chars [col 6]`, + false, }, // { // "urn:a", @@ -1006,3 +1270,255 @@ var equivalenceTests = []struct { []byte("urn:FOO:a123,456"), }, } + +var fallbackTestCases = []testCase{ + // ok SCIM + { + []byte("urn:ietf:params:scim:schemas:core:2.0:User"), + true, + &URN{ + prefix: "urn", + ID: "ietf:params:scim", + SS: "schemas:core:2.0:User", + }, + "urn:ietf:params:scim:schemas:core:2.0:User", + "urn:ietf:params:scim:schemas:core:2.0:User", + "", + true, + }, + { + []byte("urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"), + true, + &URN{ + prefix: "urn", + ID: "ietf:params:scim", + SS: "schemas:extension:enterprise:2.0:User", + }, + "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User", + "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User", + "", + true, + }, + { + []byte("urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:userName"), + true, + &URN{ + prefix: "urn", + ID: "ietf:params:scim", + SS: "schemas:extension:enterprise:2.0:User:userName", + }, + "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:userName", + "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:userName", + "", + true, + }, + { + []byte("urn:ietf:params:scim:api:messages:3.0:Get"), + true, + &URN{ + prefix: "urn", + ID: "ietf:params:scim", + SS: "api:messages:3.0:Get", + }, + "urn:ietf:params:scim:api:messages:3.0:Get", + "urn:ietf:params:scim:api:messages:3.0:Get", + "", + true, + }, + { + []byte("urn:ietf:params:scim:schemas:core"), + true, + &URN{ + prefix: "urn", + ID: "ietf:params:scim", + SS: "schemas:core", + }, + "urn:ietf:params:scim:schemas:core", + "urn:ietf:params:scim:schemas:core", + "", + true, + }, + { + []byte("urn:ietf:params:scim:param:core"), + true, + &URN{ + prefix: "urn", + ID: "ietf:params:scim", + SS: "param:core", + }, + "urn:ietf:params:scim:param:core", + "urn:ietf:params:scim:param:core", + "", + true, + }, + // no SCIM, ok URN + { + []byte("urn:simple:ciao"), + true, + &URN{ + prefix: "urn", + ID: "simple", + SS: "ciao", + }, + "urn:simple:ciao", + "urn:simple:ciao", + "", + false, + }, + { + []byte("urn:WRONG4SCIM:schemas:core"), + true, + &URN{ + prefix: "urn", + ID: "WRONG4SCIM", + SS: "schemas:core", + }, + "urn:WRONG4SCIM:schemas:core", + "urn:wrong4scim:schemas:core", + "", + false, + }, + { + []byte("urn:ietf:params:scim:ERR:core"), + true, + &URN{ + prefix: "urn", + ID: "ietf", + SS: "params:scim:ERR:core", + }, + "urn:ietf:params:scim:ERR:core", + "urn:ietf:params:scim:ERR:core", + "", + false, + }, + { + []byte("urn:ietf:params:scim:schemas:$"), + true, + &URN{ + prefix: "urn", + ID: "ietf", + SS: "params:scim:schemas:$", + }, + "urn:ietf:params:scim:schemas:$", + "urn:ietf:params:scim:schemas:$", + "", + false, + }, + { + []byte("urn:ietf:params:scim:schemas:core-"), + true, + &URN{ + prefix: "urn", + ID: "ietf", + SS: "params:scim:schemas:core-", + }, + "urn:ietf:params:scim:schemas:core-", + "urn:ietf:params:scim:schemas:core-", + "", + false, + }, + { + []byte("urn:ietf:params:scim:api:core:"), + true, + &URN{ + prefix: "urn", + ID: "ietf", + SS: "params:scim:api:core:", + }, + "urn:ietf:params:scim:api:core:", + "urn:ietf:params:scim:api:core:", + "", + false, + }, + // no SCIM, no URN + { + []byte("arn:ietf:params:scim:schemas:core"), + false, + nil, + "", + "", + fmt.Sprintf(errPrefix, 0), + false, + }, + { + []byte("usn:ietf:params:scim:schemas:core"), + false, + nil, + "", + "", + fmt.Sprintf(errPrefix, 1), + false, + }, + { + []byte("urm:ietf:params:scim:schemas:core"), + false, + nil, + "", + "", + fmt.Sprintf(errPrefix, 2), + false, + }, + { + []byte("urno:ietf:params:scim:schemas:core"), + false, + nil, + "", + "", + fmt.Sprintf(errPrefix, 3), + false, + }, + { + []byte("urno"), + false, + nil, + "", + "", + fmt.Sprintf(errPrefix, 3), + false, + }, + { + []byte("URN:a!?:x"), + false, + nil, + "", + "", + `expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its beginning) [col 5]`, + false, + }, + { + []byte("urn:Urn:NSS"), + false, + nil, + "", + "", + `expecting the identifier to not contain the "urn" reserved string [col 7]`, + false, + }, + { + []byte("urn:spazio bianco:NSS"), + false, + nil, + "", + "", + `expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its beginning) [col 10]`, + false, + }, + { + []byte("urn:conca:z ws"), + false, + nil, + "", + "", + `expecting the specific string to be a string containing alnum, hex, or others ([()+,-.:=@;$_!*']) chars [col 11]`, + false, + }, + { + []byte("urn:ietf:params:scim:schemas:core:2.&"), + false, + nil, + "", + "", + fmt.Sprintf(errSpecificString, 36), + false, + }, +} diff --git a/urn.go b/urn.go index fa9fa3a..2529b80 100644 --- a/urn.go +++ b/urn.go @@ -20,6 +20,8 @@ type URN struct { ID string // Namespace identifier (NID) SS string // Namespace specific string (NSS) norm string // Normalized namespace specific string + kind Kind + scim *SCIM } // Normalize turns the receiving URN into its norm version. @@ -56,9 +58,9 @@ func (u *URN) String() string { return res } -// Parse is responsible to create an URN instance from a byte array matching the correct URN syntax. -func Parse(u []byte) (*URN, bool) { - urn, err := NewMachine().Parse(u) +// Parse is responsible to create an URN instance from a byte array matching the correct URN syntax (RFC 2141). +func Parse(u []byte, options ...Option) (*URN, bool) { + urn, err := NewMachine(options...).Parse(u) if err != nil { return nil, false } @@ -84,3 +86,15 @@ func (u *URN) UnmarshalJSON(bytes []byte) error { } return nil } + +func (u *URN) IsSCIM() bool { + return u.kind == RFC7643 +} + +func (u *URN) SCIM() *SCIM { + if !u.IsSCIM() { + return nil + } + + return u.scim +}