├── .github
└── workflows
│ └── tests.yml
├── .gitignore
├── LICENSE
├── README.md
├── docs
├── urn.dot
└── urn.png
├── examples_test.go
├── go.mod
├── go.sum
├── kind.go
├── lexicaleq_test.go
├── machine.go
├── machine.go.rl
├── machine_test.go
├── makefile
├── options.go
├── parsing_mode.go
├── performance_test.go
├── scim.go
├── scim
└── schema
│ ├── type.go
│ └── type_test.go
├── scim_test.go
├── tables_test.go
├── tools
├── removecomments
│ ├── go.mod
│ ├── go.sum
│ ├── removecomments.go
│ └── removecomments_test.go
└── snake2camel
│ ├── go.mod
│ ├── go.sum
│ ├── snake2camel.go
│ └── snake2camel_test.go
├── urn.go
├── urn8141.go
├── urn8141_test.go
└── urn_test.go
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: testing
2 |
3 | on:
4 | push:
5 | branches: ['master']
6 | pull_request:
7 | branches: ['master']
8 |
9 | permissions:
10 | contents: read
11 |
12 | jobs:
13 | test:
14 | strategy:
15 | fail-fast: true
16 | matrix:
17 | os: [ubuntu-latest, macos-latest]
18 | go: [
19 | '1.18',
20 | '1.19',
21 | '1.20',
22 | '1.21',
23 | ]
24 | include:
25 | # Set the minimum Go patch version for the given Go minor
26 | - go: '1.18'
27 | GO_VERSION: '~1.18.0'
28 | - go: '1.19'
29 | GO_VERSION: '~1.19.0'
30 | - go: '1.20'
31 | GO_VERSION: '~1.20.0'
32 | - go: '1.21'
33 | GO_VERSION: '~1.21.0'
34 | runs-on: ${{ matrix.os }}
35 |
36 | steps:
37 | - name: Set up Go ${{ matrix.go }}
38 | uses: actions/setup-go@v3
39 | with:
40 | go-version: ${{ matrix.GO_VERSION }}
41 | check-latest: true
42 |
43 | - name: Print environment
44 | id: vars
45 | run: |
46 | printf "Using Go at $(which go) (version $(go version))\n"
47 | printf "\n\nGo environment:\n\n"
48 | go env
49 | printf "\n\nSystem environment:\n\n"
50 | env
51 |
52 | - name: Cache the build cache
53 | uses: actions/cache@v3
54 | with:
55 | path: |
56 | ~/go/pkg/mod
57 | ~/.cache/go-build
58 | key: build-go-${{ matrix.go }}-${{ matrix.os }}-${{ hashFiles('**/go.sum') }}
59 | restore-keys: |
60 | build-go-${{ matrix.go }}-${{ matrix.os }}
61 |
62 | - name: Check out the source code
63 | uses: actions/checkout@v3
64 |
65 | - name: Run tests
66 | run: GO_ARGS="-v -race -covermode=atomic -coverprofile=coverage.out" make tests
67 |
68 | - name: Upload coverage
69 | uses: codecov/codecov-action@v3
70 | if: github.ref == 'refs/heads/master'
71 | with:
72 | token: ${{ secrets.CODECOV_TOKEN }}
73 | fail_ci_if_error: true
74 | verbose: true
75 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.exe
2 | *.dll
3 | *.so
4 | *.dylib
5 |
6 | *.test
7 |
8 | *.out
9 | *.txt
10 |
11 | vendor/
12 | /removecomments
13 | /snake2camel
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Leonardo Di Donato
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://app.circleci.com/pipelines/github/leodido/go-urn) [](https://codecov.io/gh/leodido/go-urn) [](https://godoc.org/github.com/leodido/go-urn)
2 |
3 | **A parser for URNs**.
4 |
5 | > As seen on [RFC 2141](https://datatracker.ietf.org/doc/html/rfc2141), [RFC 7643](https://datatracker.ietf.org/doc/html/rfc7643#section-10), and on [RFC 8141](https://datatracker.ietf.org/doc/html/rfc8141).
6 |
7 | [API documentation](https://godoc.org/github.com/leodido/go-urn).
8 |
9 | Starting with version 1.3 this library also supports [RFC 7643 SCIM URNs](https://datatracker.ietf.org/doc/html/rfc7643#section-10).
10 |
11 | Starting with version 1.4 this library also supports [RFC 8141 URNs (2017)](https://datatracker.ietf.org/doc/html/rfc8141).
12 |
13 | ## Installation
14 |
15 | ```
16 | go get github.com/leodido/go-urn
17 | ```
18 |
19 | ## Features
20 |
21 | 1. RFC 2141 URNs parsing (default)
22 | 2. RFC 8141 URNs parsing (supersedes RFC 2141)
23 | 3. RFC 7643 SCIM URNs parsing
24 | 4. Normalization as per RFCs
25 | 5. Lexical equivalence as per RFCs
26 | 6. Precise, fine-grained errors
27 |
28 | ## Performances
29 |
30 | This implementation results to be really fast.
31 |
32 | Usually below 400 ns on my machine[1](#mymachine).
33 |
34 | Notice it also performs, while parsing:
35 |
36 | 1. fine-grained and informative erroring
37 | 2. specific-string normalization
38 |
39 | ```
40 | ok/00/urn:a:b______________________________________/-10 51372006 109.0 ns/op 275 B/op 3 allocs/op
41 | ok/01/URN:foo:a123,456_____________________________/-10 36024072 160.8 ns/op 296 B/op 6 allocs/op
42 | ok/02/urn:foo:a123%2C456___________________________/-10 31901007 188.4 ns/op 320 B/op 7 allocs/op
43 | ok/03/urn:ietf:params:scim:schemas:core:2.0:User___/-10 22736756 266.6 ns/op 376 B/op 6 allocs/op
44 | ok/04/urn:ietf:params:scim:schemas:extension:enterp/-10 18291859 335.2 ns/op 408 B/op 6 allocs/op
45 | ok/05/urn:ietf:params:scim:schemas:extension:enterp/-10 15283087 379.4 ns/op 440 B/op 6 allocs/op
46 | ok/06/urn:burnout:nss______________________________/-10 39407593 155.1 ns/op 288 B/op 6 allocs/op
47 | ok/07/urn:abcdefghilmnopqrstuvzabcdefghilm:x_______/-10 27832718 211.4 ns/op 307 B/op 4 allocs/op
48 | ok/08/urn:urnurnurn:urn____________________________/-10 33269596 168.1 ns/op 293 B/op 6 allocs/op
49 | ok/09/urn:ciao:!!*_________________________________/-10 41100675 148.8 ns/op 288 B/op 6 allocs/op
50 | ok/10/urn:ciao:=@__________________________________/-10 37214253 149.7 ns/op 284 B/op 6 allocs/op
51 | ok/11/urn:ciao:@!=%2C(xyz)+a,b.*@g=$_'_____________/-10 26534240 229.8 ns/op 336 B/op 7 allocs/op
52 | ok/12/URN:x:abc%1Dz%2F%3az_________________________/-10 28166396 211.8 ns/op 336 B/op 7 allocs/op
53 | no/13/URN:---xxx:x_________________________________/-10 23635159 255.6 ns/op 419 B/op 5 allocs/op
54 | no/14/urn::colon:nss_______________________________/-10 23594779 258.4 ns/op 419 B/op 5 allocs/op
55 | no/15/URN:@,:x_____________________________________/-10 23742535 261.5 ns/op 419 B/op 5 allocs/op
56 | no/16/URN:URN:NSS__________________________________/-10 27432714 223.3 ns/op 371 B/op 5 allocs/op
57 | no/17/urn:UrN:NSS__________________________________/-10 26922117 224.9 ns/op 371 B/op 5 allocs/op
58 | no/18/urn:a:%______________________________________/-10 24926733 224.6 ns/op 371 B/op 5 allocs/op
59 | no/19/urn:urn:NSS__________________________________/-10 27652641 220.7 ns/op 371 B/op 5 allocs/op
60 | ```
61 |
62 | * [1]: Apple M1 Pro
63 |
64 |
65 | ## Example
66 |
67 | For more examples take a look at the [examples file](examples_test.go).
68 |
69 |
70 | ```go
71 | package main
72 |
73 | import (
74 | "fmt"
75 | "github.com/leodido/go-urn"
76 | )
77 |
78 | func main() {
79 | var uid = "URN:foo:a123,456"
80 |
81 | // Parse the input string as a RFC 2141 URN only
82 | u, e := urn.NewMachine().Parse(uid)
83 | if e != nil {
84 | fmt.Errorf(err)
85 |
86 | return
87 | }
88 |
89 | fmt.Println(u.ID)
90 | fmt.Println(u.SS)
91 |
92 | // Output:
93 | // foo
94 | // a123,456
95 | }
96 | ```
97 |
98 | ```go
99 | package main
100 |
101 | import (
102 | "fmt"
103 | "github.com/leodido/go-urn"
104 | )
105 |
106 | func main() {
107 | var uid = "URN:foo:a123,456"
108 |
109 | // Parse the input string as a RFC 2141 URN only
110 | u, ok := urn.Parse([]byte(uid))
111 | if !ok {
112 | panic("error parsing urn")
113 | }
114 |
115 | fmt.Println(u.ID)
116 | fmt.Println(u.SS)
117 |
118 | // Output:
119 | // foo
120 | // a123,456
121 | }
122 | ```
123 |
124 | ```go
125 | package main
126 |
127 | import (
128 | "fmt"
129 | "github.com/leodido/go-urn"
130 | )
131 |
132 | func main() {
133 | input := "urn:ietf:params:scim:api:messages:2.0:ListResponse"
134 |
135 | // Parsing the input string as a RFC 7643 SCIM URN
136 | u, ok := urn.Parse([]byte(input), urn.WithParsingMode(urn.RFC7643Only))
137 | if !ok {
138 | panic("error parsing urn")
139 | }
140 |
141 | fmt.Println(u.IsSCIM())
142 | scim := u.SCIM()
143 | fmt.Println(scim.Type.String())
144 | fmt.Println(scim.Name)
145 | fmt.Println(scim.Other)
146 |
147 | // Output:
148 | // true
149 | // api
150 | // messages
151 | // 2.0:ListResponse
152 | }
153 | ```
--------------------------------------------------------------------------------
/docs/urn.dot:
--------------------------------------------------------------------------------
1 | digraph urn {
2 | rankdir=LR;
3 | node [ shape = point ];
4 | ENTRY;
5 | en_5;
6 | en_44;
7 | en_48;
8 | en_83;
9 | en_95;
10 | en_1;
11 | eof_1;
12 | eof_2;
13 | eof_3;
14 | eof_4;
15 | eof_5;
16 | eof_6;
17 | eof_7;
18 | eof_8;
19 | eof_9;
20 | eof_10;
21 | eof_11;
22 | eof_12;
23 | eof_13;
24 | eof_14;
25 | eof_15;
26 | eof_16;
27 | eof_17;
28 | eof_18;
29 | eof_19;
30 | eof_20;
31 | eof_21;
32 | eof_22;
33 | eof_23;
34 | eof_24;
35 | eof_25;
36 | eof_26;
37 | eof_27;
38 | eof_28;
39 | eof_29;
40 | eof_30;
41 | eof_31;
42 | eof_32;
43 | eof_33;
44 | eof_34;
45 | eof_35;
46 | eof_36;
47 | eof_37;
48 | eof_38;
49 | eof_39;
50 | eof_40;
51 | eof_41;
52 | eof_42;
53 | eof_43;
54 | eof_44;
55 | eof_45;
56 | eof_46;
57 | eof_47;
58 | eof_48;
59 | eof_49;
60 | eof_50;
61 | eof_51;
62 | eof_52;
63 | eof_53;
64 | eof_54;
65 | eof_55;
66 | eof_56;
67 | eof_57;
68 | eof_58;
69 | eof_59;
70 | eof_60;
71 | eof_61;
72 | eof_62;
73 | eof_63;
74 | eof_64;
75 | eof_65;
76 | eof_66;
77 | eof_67;
78 | eof_68;
79 | eof_69;
80 | eof_70;
81 | eof_71;
82 | eof_72;
83 | eof_73;
84 | eof_74;
85 | eof_75;
86 | eof_76;
87 | eof_77;
88 | eof_78;
89 | eof_79;
90 | eof_80;
91 | eof_81;
92 | eof_82;
93 | eof_83;
94 | eof_84;
95 | eof_85;
96 | eof_86;
97 | eof_88;
98 | eof_89;
99 | eof_91;
100 | eof_92;
101 | eof_93;
102 | node [ shape = circle, height = 0.2 ];
103 | err_1 [ label=""];
104 | err_2 [ label=""];
105 | err_3 [ label=""];
106 | err_4 [ label=""];
107 | err_5 [ label=""];
108 | err_6 [ label=""];
109 | err_7 [ label=""];
110 | err_8 [ label=""];
111 | err_9 [ label=""];
112 | err_10 [ label=""];
113 | err_11 [ label=""];
114 | err_12 [ label=""];
115 | err_13 [ label=""];
116 | err_14 [ label=""];
117 | err_15 [ label=""];
118 | err_16 [ label=""];
119 | err_17 [ label=""];
120 | err_18 [ label=""];
121 | err_19 [ label=""];
122 | err_20 [ label=""];
123 | err_21 [ label=""];
124 | err_22 [ label=""];
125 | err_23 [ label=""];
126 | err_24 [ label=""];
127 | err_25 [ label=""];
128 | err_26 [ label=""];
129 | err_27 [ label=""];
130 | err_28 [ label=""];
131 | err_29 [ label=""];
132 | err_30 [ label=""];
133 | err_31 [ label=""];
134 | err_32 [ label=""];
135 | err_33 [ label=""];
136 | err_34 [ label=""];
137 | err_35 [ label=""];
138 | err_36 [ label=""];
139 | err_37 [ label=""];
140 | err_38 [ label=""];
141 | err_39 [ label=""];
142 | err_40 [ label=""];
143 | err_41 [ label=""];
144 | err_42 [ label=""];
145 | err_43 [ label=""];
146 | err_44 [ label=""];
147 | err_45 [ label=""];
148 | err_46 [ label=""];
149 | err_47 [ label=""];
150 | err_48 [ label=""];
151 | err_49 [ label=""];
152 | err_50 [ label=""];
153 | err_51 [ label=""];
154 | err_52 [ label=""];
155 | err_53 [ label=""];
156 | err_54 [ label=""];
157 | err_55 [ label=""];
158 | err_56 [ label=""];
159 | err_57 [ label=""];
160 | err_58 [ label=""];
161 | err_59 [ label=""];
162 | err_60 [ label=""];
163 | err_61 [ label=""];
164 | err_62 [ label=""];
165 | err_63 [ label=""];
166 | err_64 [ label=""];
167 | err_65 [ label=""];
168 | err_66 [ label=""];
169 | err_67 [ label=""];
170 | err_68 [ label=""];
171 | err_69 [ label=""];
172 | err_70 [ label=""];
173 | err_71 [ label=""];
174 | err_72 [ label=""];
175 | err_73 [ label=""];
176 | err_74 [ label=""];
177 | err_75 [ label=""];
178 | err_76 [ label=""];
179 | err_77 [ label=""];
180 | err_78 [ label=""];
181 | err_79 [ label=""];
182 | err_80 [ label=""];
183 | err_81 [ label=""];
184 | err_82 [ label=""];
185 | err_83 [ label=""];
186 | err_84 [ label=""];
187 | err_85 [ label=""];
188 | err_86 [ label=""];
189 | err_87 [ label=""];
190 | err_88 [ label=""];
191 | err_89 [ label=""];
192 | err_90 [ label=""];
193 | err_91 [ label=""];
194 | err_92 [ label=""];
195 | err_93 [ label=""];
196 | err_94 [ label=""];
197 | node [ fixedsize = true, height = 0.65, shape = doublecircle ];
198 | 87;
199 | 88;
200 | 89;
201 | 90;
202 | 91;
203 | 92;
204 | 93;
205 | 94;
206 | 95;
207 | node [ shape = circle ];
208 | 1 -> 2 [ label = "'U', 'u' / mark, throw_pre_urn_err" ];
209 | 1 -> err_1 [ label = "DEF / err_pre, err_parse" ];
210 | 2 -> 3 [ label = "'R', 'r'" ];
211 | 2 -> err_2 [ label = "DEF / err_pre, err_parse" ];
212 | 3 -> 4 [ label = "'N', 'n'" ];
213 | 3 -> err_3 [ label = "DEF / err_pre, err_parse" ];
214 | 4 -> 87 [ label = "':' / set_pre, 235:33" ];
215 | 4 -> err_4 [ label = "DEF / err_pre, err_parse" ];
216 | 5 -> 6 [ label = "'0'..'9', 'A'..'T', 'V'..'Z', 'a'..'t', 'v'..'z' / mark" ];
217 | 5 -> 41 [ label = "'U', 'u' / mark, throw_pre_urn_err" ];
218 | 5 -> err_5 [ label = "DEF / err_nid, err_pre, err_parse" ];
219 | 6 -> 7 [ label = "'-', '0'..'9', 'A'..'Z', 'a'..'z'" ];
220 | 6 -> 38 [ label = "':' / set_nid" ];
221 | 6 -> err_6 [ label = "DEF / err_nid, err_parse" ];
222 | 7 -> 8 [ label = "'-', '0'..'9', 'A'..'Z', 'a'..'z'" ];
223 | 7 -> 38 [ label = "':' / set_nid" ];
224 | 7 -> err_7 [ label = "DEF / err_nid, err_parse" ];
225 | 8 -> 9 [ label = "'-', '0'..'9', 'A'..'Z', 'a'..'z'" ];
226 | 8 -> 38 [ label = "':' / set_nid" ];
227 | 8 -> err_8 [ label = "DEF / err_nid, err_parse" ];
228 | 9 -> 10 [ label = "'-', '0'..'9', 'A'..'Z', 'a'..'z'" ];
229 | 9 -> 38 [ label = "':' / set_nid" ];
230 | 9 -> err_9 [ label = "DEF / err_nid, err_parse" ];
231 | 10 -> 11 [ label = "'-', '0'..'9', 'A'..'Z', 'a'..'z'" ];
232 | 10 -> 38 [ label = "':' / set_nid" ];
233 | 10 -> err_10 [ label = "DEF / err_nid, err_parse" ];
234 | 11 -> 12 [ label = "'-', '0'..'9', 'A'..'Z', 'a'..'z'" ];
235 | 11 -> 38 [ label = "':' / set_nid" ];
236 | 11 -> err_11 [ label = "DEF / err_nid, err_parse" ];
237 | 12 -> 13 [ label = "'-', '0'..'9', 'A'..'Z', 'a'..'z'" ];
238 | 12 -> 38 [ label = "':' / set_nid" ];
239 | 12 -> err_12 [ label = "DEF / err_nid, err_parse" ];
240 | 13 -> 14 [ label = "'-', '0'..'9', 'A'..'Z', 'a'..'z'" ];
241 | 13 -> 38 [ label = "':' / set_nid" ];
242 | 13 -> err_13 [ label = "DEF / err_nid, err_parse" ];
243 | 14 -> 15 [ label = "'-', '0'..'9', 'A'..'Z', 'a'..'z'" ];
244 | 14 -> 38 [ label = "':' / set_nid" ];
245 | 14 -> err_14 [ label = "DEF / err_nid, err_parse" ];
246 | 15 -> 16 [ label = "'-', '0'..'9', 'A'..'Z', 'a'..'z'" ];
247 | 15 -> 38 [ label = "':' / set_nid" ];
248 | 15 -> err_15 [ label = "DEF / err_nid, err_parse" ];
249 | 16 -> 17 [ label = "'-', '0'..'9', 'A'..'Z', 'a'..'z'" ];
250 | 16 -> 38 [ label = "':' / set_nid" ];
251 | 16 -> err_16 [ label = "DEF / err_nid, err_parse" ];
252 | 17 -> 18 [ label = "'-', '0'..'9', 'A'..'Z', 'a'..'z'" ];
253 | 17 -> 38 [ label = "':' / set_nid" ];
254 | 17 -> err_17 [ label = "DEF / err_nid, err_parse" ];
255 | 18 -> 19 [ label = "'-', '0'..'9', 'A'..'Z', 'a'..'z'" ];
256 | 18 -> 38 [ label = "':' / set_nid" ];
257 | 18 -> err_18 [ label = "DEF / err_nid, err_parse" ];
258 | 19 -> 20 [ label = "'-', '0'..'9', 'A'..'Z', 'a'..'z'" ];
259 | 19 -> 38 [ label = "':' / set_nid" ];
260 | 19 -> err_19 [ label = "DEF / err_nid, err_parse" ];
261 | 20 -> 21 [ label = "'-', '0'..'9', 'A'..'Z', 'a'..'z'" ];
262 | 20 -> 38 [ label = "':' / set_nid" ];
263 | 20 -> err_20 [ label = "DEF / err_nid, err_parse" ];
264 | 21 -> 22 [ label = "'-', '0'..'9', 'A'..'Z', 'a'..'z'" ];
265 | 21 -> 38 [ label = "':' / set_nid" ];
266 | 21 -> err_21 [ label = "DEF / err_nid, err_parse" ];
267 | 22 -> 23 [ label = "'-', '0'..'9', 'A'..'Z', 'a'..'z'" ];
268 | 22 -> 38 [ label = "':' / set_nid" ];
269 | 22 -> err_22 [ label = "DEF / err_nid, err_parse" ];
270 | 23 -> 24 [ label = "'-', '0'..'9', 'A'..'Z', 'a'..'z'" ];
271 | 23 -> 38 [ label = "':' / set_nid" ];
272 | 23 -> err_23 [ label = "DEF / err_nid, err_parse" ];
273 | 24 -> 25 [ label = "'-', '0'..'9', 'A'..'Z', 'a'..'z'" ];
274 | 24 -> 38 [ label = "':' / set_nid" ];
275 | 24 -> err_24 [ label = "DEF / err_nid, err_parse" ];
276 | 25 -> 26 [ label = "'-', '0'..'9', 'A'..'Z', 'a'..'z'" ];
277 | 25 -> 38 [ label = "':' / set_nid" ];
278 | 25 -> err_25 [ label = "DEF / err_nid, err_parse" ];
279 | 26 -> 27 [ label = "'-', '0'..'9', 'A'..'Z', 'a'..'z'" ];
280 | 26 -> 38 [ label = "':' / set_nid" ];
281 | 26 -> err_26 [ label = "DEF / err_nid, err_parse" ];
282 | 27 -> 28 [ label = "'-', '0'..'9', 'A'..'Z', 'a'..'z'" ];
283 | 27 -> 38 [ label = "':' / set_nid" ];
284 | 27 -> err_27 [ label = "DEF / err_nid, err_parse" ];
285 | 28 -> 29 [ label = "'-', '0'..'9', 'A'..'Z', 'a'..'z'" ];
286 | 28 -> 38 [ label = "':' / set_nid" ];
287 | 28 -> err_28 [ label = "DEF / err_nid, err_parse" ];
288 | 29 -> 30 [ label = "'-', '0'..'9', 'A'..'Z', 'a'..'z'" ];
289 | 29 -> 38 [ label = "':' / set_nid" ];
290 | 29 -> err_29 [ label = "DEF / err_nid, err_parse" ];
291 | 30 -> 31 [ label = "'-', '0'..'9', 'A'..'Z', 'a'..'z'" ];
292 | 30 -> 38 [ label = "':' / set_nid" ];
293 | 30 -> err_30 [ label = "DEF / err_nid, err_parse" ];
294 | 31 -> 32 [ label = "'-', '0'..'9', 'A'..'Z', 'a'..'z'" ];
295 | 31 -> 38 [ label = "':' / set_nid" ];
296 | 31 -> err_31 [ label = "DEF / err_nid, err_parse" ];
297 | 32 -> 33 [ label = "'-', '0'..'9', 'A'..'Z', 'a'..'z'" ];
298 | 32 -> 38 [ label = "':' / set_nid" ];
299 | 32 -> err_32 [ label = "DEF / err_nid, err_parse" ];
300 | 33 -> 34 [ label = "'-', '0'..'9', 'A'..'Z', 'a'..'z'" ];
301 | 33 -> 38 [ label = "':' / set_nid" ];
302 | 33 -> err_33 [ label = "DEF / err_nid, err_parse" ];
303 | 34 -> 35 [ label = "'-', '0'..'9', 'A'..'Z', 'a'..'z'" ];
304 | 34 -> 38 [ label = "':' / set_nid" ];
305 | 34 -> err_34 [ label = "DEF / err_nid, err_parse" ];
306 | 35 -> 36 [ label = "'-', '0'..'9', 'A'..'Z', 'a'..'z'" ];
307 | 35 -> 38 [ label = "':' / set_nid" ];
308 | 35 -> err_35 [ label = "DEF / err_nid, err_parse" ];
309 | 36 -> 37 [ label = "'-', '0'..'9', 'A'..'Z', 'a'..'z'" ];
310 | 36 -> 38 [ label = "':' / set_nid" ];
311 | 36 -> err_36 [ label = "DEF / err_nid, err_parse" ];
312 | 37 -> 38 [ label = "':' / set_nid" ];
313 | 37 -> err_37 [ label = "DEF / err_nid, err_parse" ];
314 | 38 -> 88 [ label = "'!', '$', '''..'.', '0'..';', '=', '@'..'Z', '_', 'a'..'z' / mark" ];
315 | 38 -> 39 [ label = "'%' / mark" ];
316 | 38 -> err_38 [ label = "DEF / err_nss, err_parse" ];
317 | 39 -> 40 [ label = "'0'..'9', 'a'..'z'" ];
318 | 39 -> 40 [ label = "'A'..'Z' / tolower" ];
319 | 39 -> err_39 [ label = "DEF / err_hex, err_nss, err_parse" ];
320 | 40 -> 89 [ label = "'0'..'9', 'a'..'z'" ];
321 | 40 -> 89 [ label = "'A'..'Z' / tolower" ];
322 | 40 -> err_40 [ label = "DEF / err_hex, err_nss, err_parse" ];
323 | 41 -> 7 [ label = "'-', '0'..'9', 'A'..'Q', 'S'..'Z', 'a'..'q', 's'..'z'" ];
324 | 41 -> 38 [ label = "':' / set_nid" ];
325 | 41 -> 42 [ label = "'R', 'r'" ];
326 | 41 -> err_41 [ label = "DEF / err_nid, err_pre, err_parse" ];
327 | 42 -> 8 [ label = "'-', '0'..'9', 'A'..'M', 'O'..'Z', 'a'..'m', 'o'..'z'" ];
328 | 42 -> 38 [ label = "':' / set_nid" ];
329 | 42 -> 43 [ label = "'N', 'n'" ];
330 | 42 -> err_42 [ label = "DEF / err_nid, err_pre, err_parse" ];
331 | 43 -> 9 [ label = "'-', '0'..'9', 'A'..'Z', 'a'..'z'" ];
332 | 43 -> err_43 [ label = "DEF / err_nid, err_urn, err_parse" ];
333 | 44 -> 45 [ label = "'U', 'u' / mark, throw_pre_urn_err" ];
334 | 44 -> err_44 [ label = "DEF / err_pre" ];
335 | 45 -> 46 [ label = "'R', 'r'" ];
336 | 45 -> err_45 [ label = "DEF / err_pre" ];
337 | 46 -> 47 [ label = "'N', 'n'" ];
338 | 46 -> err_46 [ label = "DEF / err_pre" ];
339 | 47 -> 90 [ label = "':' / set_pre, 215:36" ];
340 | 47 -> err_47 [ label = "DEF / err_pre" ];
341 | 48 -> 49 [ label = "'i' / mark" ];
342 | 48 -> err_48 [ label = "DEF / err_scim_nid" ];
343 | 49 -> 50 [ label = "'e'" ];
344 | 49 -> err_49 [ label = "DEF / err_scim_nid" ];
345 | 50 -> 51 [ label = "'t'" ];
346 | 50 -> err_50 [ label = "DEF / err_scim_nid" ];
347 | 51 -> 52 [ label = "'f'" ];
348 | 51 -> err_51 [ label = "DEF / err_scim_nid" ];
349 | 52 -> 53 [ label = "':'" ];
350 | 52 -> err_52 [ label = "DEF / err_scim_nid" ];
351 | 53 -> 54 [ label = "'p'" ];
352 | 53 -> err_53 [ label = "DEF / err_scim_nid" ];
353 | 54 -> 55 [ label = "'a'" ];
354 | 54 -> err_54 [ label = "DEF / err_scim_nid" ];
355 | 55 -> 56 [ label = "'r'" ];
356 | 55 -> err_55 [ label = "DEF / err_scim_nid" ];
357 | 56 -> 57 [ label = "'a'" ];
358 | 56 -> err_56 [ label = "DEF / err_scim_nid" ];
359 | 57 -> 58 [ label = "'m'" ];
360 | 57 -> err_57 [ label = "DEF / err_scim_nid" ];
361 | 58 -> 59 [ label = "'s'" ];
362 | 58 -> err_58 [ label = "DEF / err_scim_nid" ];
363 | 59 -> 60 [ label = "':'" ];
364 | 59 -> err_59 [ label = "DEF / err_scim_nid" ];
365 | 60 -> 61 [ label = "'s'" ];
366 | 60 -> err_60 [ label = "DEF / err_scim_nid" ];
367 | 61 -> 62 [ label = "'c'" ];
368 | 61 -> err_61 [ label = "DEF / err_scim_nid" ];
369 | 62 -> 63 [ label = "'i'" ];
370 | 62 -> err_62 [ label = "DEF / err_scim_nid" ];
371 | 63 -> 64 [ label = "'m'" ];
372 | 63 -> err_63 [ label = "DEF / err_scim_nid" ];
373 | 64 -> 65 [ label = "':' / set_nid, create_scim" ];
374 | 64 -> err_64 [ label = "DEF / err_scim_nid" ];
375 | 65 -> 66 [ label = "'a' / mark" ];
376 | 65 -> 73 [ label = "'p' / mark" ];
377 | 65 -> 77 [ label = "'s' / mark" ];
378 | 65 -> err_65 [ label = "DEF / err_scim_type" ];
379 | 66 -> 67 [ label = "'p'" ];
380 | 66 -> err_66 [ label = "DEF / err_scim_type" ];
381 | 67 -> 68 [ label = "'i'" ];
382 | 67 -> err_67 [ label = "DEF / err_scim_type" ];
383 | 68 -> 69 [ label = "':' / set_scim_type" ];
384 | 68 -> err_68 [ label = "DEF / err_scim_type" ];
385 | 69 -> 91 [ label = "'0'..'9', 'A'..'Z', 'a'..'z' / mark_scim_name" ];
386 | 69 -> err_69 [ label = "DEF / err_scim_name" ];
387 | 70 -> 92 [ label = "'!', '$', '''..'.', '0'..';', '=', '@'..'Z', '_', 'a'..'z' / mark_scim_other" ];
388 | 70 -> 71 [ label = "'%' / mark_scim_other" ];
389 | 70 -> err_70 [ label = "DEF / err_scim_other" ];
390 | 71 -> 72 [ label = "'0'..'9', 'a'..'z'" ];
391 | 71 -> 72 [ label = "'A'..'Z' / tolower" ];
392 | 71 -> err_71 [ label = "DEF / err_hex, err_scim_other" ];
393 | 72 -> 93 [ label = "'0'..'9', 'a'..'z'" ];
394 | 72 -> 93 [ label = "'A'..'Z' / tolower" ];
395 | 72 -> err_72 [ label = "DEF / err_hex, err_scim_other" ];
396 | 73 -> 74 [ label = "'a'" ];
397 | 73 -> err_73 [ label = "DEF / err_scim_type" ];
398 | 74 -> 75 [ label = "'r'" ];
399 | 74 -> err_74 [ label = "DEF / err_scim_type" ];
400 | 75 -> 76 [ label = "'a'" ];
401 | 75 -> err_75 [ label = "DEF / err_scim_type" ];
402 | 76 -> 68 [ label = "'m'" ];
403 | 76 -> err_76 [ label = "DEF / err_scim_type" ];
404 | 77 -> 78 [ label = "'c'" ];
405 | 77 -> err_77 [ label = "DEF / err_scim_type" ];
406 | 78 -> 79 [ label = "'h'" ];
407 | 78 -> err_78 [ label = "DEF / err_scim_type" ];
408 | 79 -> 80 [ label = "'e'" ];
409 | 79 -> err_79 [ label = "DEF / err_scim_type" ];
410 | 80 -> 81 [ label = "'m'" ];
411 | 80 -> err_80 [ label = "DEF / err_scim_type" ];
412 | 81 -> 82 [ label = "'a'" ];
413 | 81 -> err_81 [ label = "DEF / err_scim_type" ];
414 | 82 -> 68 [ label = "'s'" ];
415 | 82 -> err_82 [ label = "DEF / err_scim_type" ];
416 | 83 -> 84 [ label = "'U', 'u' / mark, throw_pre_urn_err" ];
417 | 83 -> err_83 [ label = "DEF / err_pre" ];
418 | 84 -> 85 [ label = "'R', 'r'" ];
419 | 84 -> err_84 [ label = "DEF / err_pre" ];
420 | 85 -> 86 [ label = "'N', 'n'" ];
421 | 85 -> err_85 [ label = "DEF / err_pre" ];
422 | 86 -> 94 [ label = "':' / set_pre, 229:37" ];
423 | 86 -> err_86 [ label = "DEF / err_pre" ];
424 | 87 -> err_87 [ label = "DEF / err_pre, err_parse" ];
425 | 88 -> 88 [ label = "'!', '$', '''..'.', '0'..';', '=', '@'..'Z', '_', 'a'..'z'" ];
426 | 88 -> 39 [ label = "'%'" ];
427 | 88 -> err_88 [ label = "DEF / err_nss, err_parse" ];
428 | 89 -> 88 [ label = "'!', '$', '''..'.', '0'..';', '=', '@'..'Z', '_', 'a'..'z'" ];
429 | 89 -> 39 [ label = "'%'" ];
430 | 89 -> err_89 [ label = "DEF / err_hex, err_nss, err_parse" ];
431 | 90 -> err_90 [ label = "DEF / err_pre" ];
432 | 91 -> 91 [ label = "'0'..'9', 'A'..'Z', 'a'..'z'" ];
433 | 91 -> 70 [ label = "':' / set_scim_name" ];
434 | 91 -> err_91 [ label = "DEF / err_scim_name" ];
435 | 92 -> 92 [ label = "'!', '$', '''..'.', '0'..';', '=', '@'..'Z', '_', 'a'..'z'" ];
436 | 92 -> 71 [ label = "'%'" ];
437 | 92 -> err_92 [ label = "DEF / err_scim_other" ];
438 | 93 -> 92 [ label = "'!', '$', '''..'.', '0'..';', '=', '@'..'Z', '_', 'a'..'z'" ];
439 | 93 -> 71 [ label = "'%'" ];
440 | 93 -> err_93 [ label = "DEF / err_hex, err_scim_other" ];
441 | 94 -> err_94 [ label = "DEF / err_pre" ];
442 | 95 -> 95 [ label = "0..'\\t', '\\v'..'\\f', 14..255" ];
443 | ENTRY -> 1 [ label = "IN" ];
444 | en_5 -> 5 [ label = "urn" ];
445 | en_44 -> 44 [ label = "urn_only" ];
446 | en_48 -> 48 [ label = "scim" ];
447 | en_83 -> 83 [ label = "scim_only" ];
448 | en_95 -> 95 [ label = "fail" ];
449 | en_1 -> 1 [ label = "main" ];
450 | 1 -> eof_1 [ label = "EOF / err_pre, err_parse" ];
451 | 2 -> eof_2 [ label = "EOF / err_pre, err_parse" ];
452 | 3 -> eof_3 [ label = "EOF / err_pre, err_parse" ];
453 | 4 -> eof_4 [ label = "EOF / err_pre, err_parse" ];
454 | 5 -> eof_5 [ label = "EOF / err_nid, err_pre, err_parse" ];
455 | 6 -> eof_6 [ label = "EOF / err_nid, err_parse" ];
456 | 7 -> eof_7 [ label = "EOF / err_nid, err_parse" ];
457 | 8 -> eof_8 [ label = "EOF / err_nid, err_parse" ];
458 | 9 -> eof_9 [ label = "EOF / err_nid, err_parse" ];
459 | 10 -> eof_10 [ label = "EOF / err_nid, err_parse" ];
460 | 11 -> eof_11 [ label = "EOF / err_nid, err_parse" ];
461 | 12 -> eof_12 [ label = "EOF / err_nid, err_parse" ];
462 | 13 -> eof_13 [ label = "EOF / err_nid, err_parse" ];
463 | 14 -> eof_14 [ label = "EOF / err_nid, err_parse" ];
464 | 15 -> eof_15 [ label = "EOF / err_nid, err_parse" ];
465 | 16 -> eof_16 [ label = "EOF / err_nid, err_parse" ];
466 | 17 -> eof_17 [ label = "EOF / err_nid, err_parse" ];
467 | 18 -> eof_18 [ label = "EOF / err_nid, err_parse" ];
468 | 19 -> eof_19 [ label = "EOF / err_nid, err_parse" ];
469 | 20 -> eof_20 [ label = "EOF / err_nid, err_parse" ];
470 | 21 -> eof_21 [ label = "EOF / err_nid, err_parse" ];
471 | 22 -> eof_22 [ label = "EOF / err_nid, err_parse" ];
472 | 23 -> eof_23 [ label = "EOF / err_nid, err_parse" ];
473 | 24 -> eof_24 [ label = "EOF / err_nid, err_parse" ];
474 | 25 -> eof_25 [ label = "EOF / err_nid, err_parse" ];
475 | 26 -> eof_26 [ label = "EOF / err_nid, err_parse" ];
476 | 27 -> eof_27 [ label = "EOF / err_nid, err_parse" ];
477 | 28 -> eof_28 [ label = "EOF / err_nid, err_parse" ];
478 | 29 -> eof_29 [ label = "EOF / err_nid, err_parse" ];
479 | 30 -> eof_30 [ label = "EOF / err_nid, err_parse" ];
480 | 31 -> eof_31 [ label = "EOF / err_nid, err_parse" ];
481 | 32 -> eof_32 [ label = "EOF / err_nid, err_parse" ];
482 | 33 -> eof_33 [ label = "EOF / err_nid, err_parse" ];
483 | 34 -> eof_34 [ label = "EOF / err_nid, err_parse" ];
484 | 35 -> eof_35 [ label = "EOF / err_nid, err_parse" ];
485 | 36 -> eof_36 [ label = "EOF / err_nid, err_parse" ];
486 | 37 -> eof_37 [ label = "EOF / err_nid, err_parse" ];
487 | 38 -> eof_38 [ label = "EOF / err_nss, err_parse" ];
488 | 39 -> eof_39 [ label = "EOF / err_hex, err_nss, err_parse" ];
489 | 40 -> eof_40 [ label = "EOF / err_hex, err_nss, err_parse" ];
490 | 41 -> eof_41 [ label = "EOF / err_nid, err_pre, err_parse" ];
491 | 42 -> eof_42 [ label = "EOF / err_nid, err_pre, err_parse" ];
492 | 43 -> eof_43 [ label = "EOF / err_nid, err_urn, err_parse" ];
493 | 44 -> eof_44 [ label = "EOF / err_pre" ];
494 | 45 -> eof_45 [ label = "EOF / err_pre" ];
495 | 46 -> eof_46 [ label = "EOF / err_pre" ];
496 | 47 -> eof_47 [ label = "EOF / err_pre" ];
497 | 48 -> eof_48 [ label = "EOF / err_scim_nid" ];
498 | 49 -> eof_49 [ label = "EOF / err_scim_nid" ];
499 | 50 -> eof_50 [ label = "EOF / err_scim_nid" ];
500 | 51 -> eof_51 [ label = "EOF / err_scim_nid" ];
501 | 52 -> eof_52 [ label = "EOF / err_scim_nid" ];
502 | 53 -> eof_53 [ label = "EOF / err_scim_nid" ];
503 | 54 -> eof_54 [ label = "EOF / err_scim_nid" ];
504 | 55 -> eof_55 [ label = "EOF / err_scim_nid" ];
505 | 56 -> eof_56 [ label = "EOF / err_scim_nid" ];
506 | 57 -> eof_57 [ label = "EOF / err_scim_nid" ];
507 | 58 -> eof_58 [ label = "EOF / err_scim_nid" ];
508 | 59 -> eof_59 [ label = "EOF / err_scim_nid" ];
509 | 60 -> eof_60 [ label = "EOF / err_scim_nid" ];
510 | 61 -> eof_61 [ label = "EOF / err_scim_nid" ];
511 | 62 -> eof_62 [ label = "EOF / err_scim_nid" ];
512 | 63 -> eof_63 [ label = "EOF / err_scim_nid" ];
513 | 64 -> eof_64 [ label = "EOF / err_scim_nid" ];
514 | 65 -> eof_65 [ label = "EOF / err_scim_type" ];
515 | 66 -> eof_66 [ label = "EOF / err_scim_type" ];
516 | 67 -> eof_67 [ label = "EOF / err_scim_type" ];
517 | 68 -> eof_68 [ label = "EOF / err_scim_type" ];
518 | 69 -> eof_69 [ label = "EOF / err_scim_name" ];
519 | 70 -> eof_70 [ label = "EOF / err_scim_other" ];
520 | 71 -> eof_71 [ label = "EOF / err_hex, err_scim_other" ];
521 | 72 -> eof_72 [ label = "EOF / err_hex, err_scim_other" ];
522 | 73 -> eof_73 [ label = "EOF / err_scim_type" ];
523 | 74 -> eof_74 [ label = "EOF / err_scim_type" ];
524 | 75 -> eof_75 [ label = "EOF / err_scim_type" ];
525 | 76 -> eof_76 [ label = "EOF / err_scim_type" ];
526 | 77 -> eof_77 [ label = "EOF / err_scim_type" ];
527 | 78 -> eof_78 [ label = "EOF / err_scim_type" ];
528 | 79 -> eof_79 [ label = "EOF / err_scim_type" ];
529 | 80 -> eof_80 [ label = "EOF / err_scim_type" ];
530 | 81 -> eof_81 [ label = "EOF / err_scim_type" ];
531 | 82 -> eof_82 [ label = "EOF / err_scim_type" ];
532 | 83 -> eof_83 [ label = "EOF / err_pre" ];
533 | 84 -> eof_84 [ label = "EOF / err_pre" ];
534 | 85 -> eof_85 [ label = "EOF / err_pre" ];
535 | 86 -> eof_86 [ label = "EOF / err_pre" ];
536 | 88 -> eof_88 [ label = "EOF / set_nss, base_type" ];
537 | 89 -> eof_89 [ label = "EOF / set_nss, base_type" ];
538 | 91 -> eof_91 [ label = "EOF / set_scim_name, set_nss, scim_type" ];
539 | 92 -> eof_92 [ label = "EOF / set_scim_other, set_nss, scim_type" ];
540 | 93 -> eof_93 [ label = "EOF / set_scim_other, set_nss, scim_type" ];
541 | }
542 |
--------------------------------------------------------------------------------
/docs/urn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leodido/go-urn/d725923fe33ce69c89b9e2033d069099b498224f/docs/urn.png
--------------------------------------------------------------------------------
/examples_test.go:
--------------------------------------------------------------------------------
1 | package urn_test
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/leodido/go-urn"
7 | )
8 |
9 | func ExampleParse() {
10 | var uid = "URN:foo:a123,456"
11 |
12 | if u, ok := urn.Parse([]byte(uid)); ok {
13 | fmt.Println(u.ID)
14 | fmt.Println(u.SS)
15 | fmt.Println(u.SCIM())
16 | }
17 |
18 | // Output: foo
19 | // a123,456
20 | //
21 | }
22 |
23 | func ExampleURN_MarshalJSON() {
24 | var uid = "URN:foo:a123,456"
25 |
26 | if u, ok := urn.Parse([]byte(uid)); ok {
27 | json, err := u.MarshalJSON()
28 | if err != nil {
29 | panic("invalid urn")
30 | }
31 | fmt.Println(string(json))
32 | }
33 |
34 | // Output: "URN:foo:a123,456"
35 | }
36 |
37 | func ExampleURN_Equal() {
38 | var uid1 = "URN:foo:a123,456"
39 | var uid2 = "URN:FOO:a123,456"
40 |
41 | u1, ok := urn.Parse([]byte(uid1))
42 | if !ok {
43 | panic("invalid urn")
44 | }
45 |
46 | u2, ok := urn.Parse([]byte(uid2))
47 | if !ok {
48 | panic("invalid urn")
49 | }
50 |
51 | if u1.Equal(u2) {
52 | fmt.Printf("%s equals %s", u1.String(), u2.String())
53 | }
54 |
55 | // Output: URN:foo:a123,456 equals URN:FOO:a123,456
56 | }
57 |
58 | func ExampleParse_scim() {
59 | input := "urn:ietf:params:scim:api:messages:2.0:ListResponse"
60 |
61 | u, ok := urn.Parse([]byte(input), urn.WithParsingMode(urn.RFC7643Only))
62 | if !ok {
63 | panic("invalid SCIM urn")
64 | }
65 | data, err := u.MarshalJSON()
66 | if err != nil {
67 | panic("couldn't marshal")
68 | }
69 | fmt.Println(string(data))
70 | fmt.Println(u.IsSCIM())
71 | scim := u.SCIM()
72 | fmt.Println(scim.Type.String())
73 | fmt.Println(scim.Name)
74 | fmt.Println(scim.Other)
75 |
76 | // Output:
77 | // "urn:ietf:params:scim:api:messages:2.0:ListResponse"
78 | // true
79 | // api
80 | // messages
81 | // 2.0:ListResponse
82 | }
83 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/leodido/go-urn
2 |
3 | go 1.18
4 |
5 | require github.com/stretchr/testify v1.8.4
6 |
7 | require (
8 | github.com/davecgh/go-spew v1.1.1 // indirect
9 | github.com/pmezard/go-difflib v1.0.0 // indirect
10 | gopkg.in/yaml.v3 v3.0.1 // indirect
11 | )
12 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
5 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
6 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
7 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
8 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
9 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
10 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
11 |
--------------------------------------------------------------------------------
/kind.go:
--------------------------------------------------------------------------------
1 | package urn
2 |
3 | type Kind int
4 |
5 | const (
6 | NONE Kind = iota
7 | RFC2141
8 | RFC7643
9 | RFC8141
10 | )
11 |
--------------------------------------------------------------------------------
/lexicaleq_test.go:
--------------------------------------------------------------------------------
1 | package urn
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | "github.com/stretchr/testify/require"
8 | )
9 |
10 | type equivalenceTestCase struct {
11 | eq bool
12 | lx []byte
13 | rx []byte
14 | }
15 |
16 | var equivalenceTests = []equivalenceTestCase{
17 | {
18 | true,
19 | []byte("urn:foo:a123%2C456"),
20 | []byte("URN:FOO:a123%2c456"),
21 | },
22 | {
23 | true,
24 | []byte("urn:example:a123%2Cz456"),
25 | []byte("URN:EXAMPLE:a123%2cz456"),
26 | },
27 | {
28 | true,
29 | []byte("urn:foo:AbC123%2C456"),
30 | []byte("URN:FOO:AbC123%2c456"),
31 | },
32 | {
33 | true,
34 | []byte("urn:foo:AbC123%2C456%1f"),
35 | []byte("URN:FOO:AbC123%2c456%1f"),
36 | },
37 | {
38 | true,
39 | []byte("URN:foo:a123,456"),
40 | []byte("urn:foo:a123,456"),
41 | },
42 | {
43 | true,
44 | []byte("URN:foo:a123,456"),
45 | []byte("urn:FOO:a123,456"),
46 | },
47 | {
48 | true,
49 | []byte("urn:foo:a123,456"),
50 | []byte("urn:FOO:a123,456"),
51 | },
52 | {
53 | true,
54 | []byte("urn:ciao:%2E"),
55 | []byte("urn:ciao:%2e"),
56 | },
57 | {
58 | false,
59 | []byte("urn:foo:A123,456"),
60 | []byte("URN:foo:a123,456"),
61 | },
62 | {
63 | false,
64 | []byte("urn:foo:A123,456"),
65 | []byte("urn:foo:a123,456"),
66 | },
67 | {
68 | false,
69 | []byte("urn:foo:A123,456"),
70 | []byte("urn:FOO:a123,456"),
71 | },
72 | {
73 | false,
74 | []byte("urn:example:a123%2Cz456"),
75 | []byte("urn:example:a123,z456"),
76 | },
77 | {
78 | false,
79 | []byte("urn:example:A123,z456"),
80 | []byte("urn:example:a123,Z456"),
81 | },
82 | }
83 |
84 | var equivalenceTests8141 = []equivalenceTestCase{
85 | {
86 | false,
87 | []byte("urn:example:a123,z456/bar"),
88 | []byte("urn:example:a123,z456/foo"),
89 | },
90 | {
91 | true,
92 | []byte("urn:example:a123,z456?+abc"),
93 | []byte("urn:example:a123,z456?=xyz"),
94 | },
95 | {
96 | true,
97 | []byte("urn:example:a123,z456#789"),
98 | []byte("urn:example:a123,z456?=xyz"),
99 | },
100 | }
101 |
102 | func lexicalEqual(t *testing.T, ii int, tt equivalenceTestCase, os ...Option) {
103 | t.Helper()
104 | urnlx, oklx := Parse(tt.lx, os...)
105 | urnrx, okrx := Parse(tt.rx, os...)
106 |
107 | if oklx && okrx {
108 | assert.True(t, urnlx.Equal(urnlx))
109 | assert.True(t, urnrx.Equal(urnrx))
110 |
111 | if tt.eq {
112 | assert.True(t, urnlx.Equal(urnrx), ierror(ii))
113 | assert.True(t, urnrx.Equal(urnlx), ierror(ii))
114 | } else {
115 | assert.False(t, urnlx.Equal(urnrx), ierror(ii))
116 | assert.False(t, urnrx.Equal(urnlx), ierror(ii))
117 | }
118 | } else {
119 | t.Log("Something wrong in the testing table ...")
120 | }
121 | }
122 |
123 | func TestLexicalEquivalence(t *testing.T) {
124 | for ii, tt := range equivalenceTests {
125 | lexicalEqual(t, ii, tt, WithParsingMode(RFC2141Only))
126 | }
127 |
128 | // The r-component, q-component, and f-component not taken into account for purposes of URN-equivalence
129 | // See [RFC8141#3.2](https://datatracker.ietf.org/doc/html/rfc8141#section-3.2)
130 | for ii, tt := range append(equivalenceTests, equivalenceTests8141...) {
131 | lexicalEqual(t, ii, tt, WithParsingMode(RFC8141Only))
132 | }
133 | }
134 |
135 | func TestEqualNil(t *testing.T) {
136 | u, ok := Parse([]byte("urn:hello:world"))
137 | require.NotNil(t, u)
138 | require.True(t, ok)
139 | require.False(t, u.Equal(nil))
140 | }
141 |
--------------------------------------------------------------------------------
/machine.go.rl:
--------------------------------------------------------------------------------
1 | package urn
2 |
3 | import (
4 | "fmt"
5 |
6 | scimschema "github.com/leodido/go-urn/scim/schema"
7 | )
8 |
9 | var (
10 | errPrefix = "expecting the prefix to be the \"urn\" string (whatever case) [col %d]"
11 | errIdentifier = "expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its beginning) [col %d]"
12 | errSpecificString = "expecting the specific string to be a string containing alnum, hex, or others ([()+,-.:=@;$_!*']) chars [col %d]"
13 | errNoUrnWithinID = "expecting the identifier to not contain the \"urn\" reserved string [col %d]"
14 | errHex = "expecting the percent encoded chars to be well-formed (%%alnum{2}) [col %d]"
15 | errSCIMNamespace = "expecing the SCIM namespace identifier (ietf:params:scim) [col %d]"
16 | errSCIMType = "expecting a correct SCIM type (schemas, api, param) [col %d]"
17 | errSCIMName = "expecting one or more alnum char in the SCIM name part [col %d]"
18 | errSCIMOther = "expecting a well-formed other SCIM part [col %d]"
19 | errSCIMOtherIncomplete = "expecting a not empty SCIM other part after colon [col %d]"
20 | err8141InformalID = "informal URN namespace must be in the form urn-[1-9][0-9] [col %d]"
21 | err8141SpecificString = "expecting the specific string to contain alnum, hex, or others ([~&()+,-.:=@;$_!*'] or [/?] not in first position) chars [col %d]"
22 | err8141Identifier = "expecting the indentifier to be a string with (length 2 to 32 chars) containing alnum (or dashes) not starting or ending with a dash [col %d]"
23 | err8141RComponentStart = "expecting only one r-component (starting with the ?+ sequence) [col %d]"
24 | err8141QComponentStart = "expecting only one q-component (starting with the ?= sequence) [col %d]"
25 | err8141MalformedRComp = "expecting a non-empty r-component containing alnum, hex, or others ([~&()+,-.:=@;$_!*'] or [/?] but not at its beginning) [col %d]"
26 | err8141MalformedQComp = "expecting a non-empty q-component containing alnum, hex, or others ([~&()+,-.:=@;$_!*'] or [/?] but not at its beginning) [col %d]"
27 | )
28 |
29 | %%{
30 | machine urn;
31 |
32 | # unsigned alphabet
33 | alphtype uint8;
34 |
35 | action mark {
36 | m.pb = m.p
37 | }
38 |
39 | action tolower {
40 | // List of positions in the buffer to later lowercase
41 | output.tolower = append(output.tolower, m.p - m.pb)
42 | }
43 |
44 | action set_pre {
45 | output.prefix = string(m.text())
46 | }
47 |
48 | action throw_pre_urn_err {
49 | if m.parsingMode != RFC8141Only {
50 | // Throw an error when:
51 | // - we are entering here matching the the prefix in the namespace identifier part
52 | // - looking ahead (3 chars) we find a colon
53 | if pos := m.p + 3; pos < m.pe && m.data[pos] == 58 && output.prefix != "" {
54 | m.err = fmt.Errorf(errNoUrnWithinID, pos)
55 | fhold;
56 | fgoto fail;
57 | }
58 | }
59 | }
60 |
61 | action set_nid {
62 | output.ID = string(m.text())
63 | }
64 |
65 | action set_nss {
66 | output.SS = string(m.text())
67 | // Iterate upper letters lowering them
68 | for _, i := range output.tolower {
69 | m.data[m.pb+i] = m.data[m.pb+i] + 32
70 | }
71 | output.norm = string(m.text())
72 | // Revert the buffer to the original
73 | for _, i := range output.tolower {
74 | m.data[m.pb+i] = m.data[m.pb+i] - 32
75 | }
76 | }
77 |
78 | action err_pre {
79 | m.err = fmt.Errorf(errPrefix, m.p)
80 | fhold;
81 | fgoto fail;
82 | }
83 |
84 | action err_nid {
85 | m.err = fmt.Errorf(errIdentifier, m.p)
86 | fhold;
87 | fgoto fail;
88 | }
89 |
90 | action err_nss {
91 | m.err = fmt.Errorf(errSpecificString, m.p)
92 | fhold;
93 | fgoto fail;
94 | }
95 |
96 | action err_urn {
97 | m.err = fmt.Errorf(errNoUrnWithinID, m.p)
98 | fhold;
99 | fgoto fail;
100 | }
101 |
102 | action err_hex {
103 | if m.parsingMode == RFC2141Only || m.parsingMode == RFC8141Only {
104 | m.err = fmt.Errorf(errHex, m.p)
105 | fhold;
106 | fgoto fail;
107 | }
108 | }
109 |
110 | action base_type {
111 | output.kind = RFC2141;
112 | }
113 |
114 | pre = ([uU] @err(err_pre) [rR] @err(err_pre) [nN] @err(err_pre)) >mark >throw_pre_urn_err %set_pre;
115 |
116 | nid = (alnum >mark (alnum | '-'){0,31}) $err(err_nid) %set_nid;
117 |
118 | hex = '%' (digit | lower | upper >tolower){2} $err(err_hex);
119 |
120 | sss = (alnum | [()+,\-.:=@;$_!*']);
121 |
122 | nss = (sss | hex)+ $err(err_nss);
123 |
124 | nid_not_urn = (nid - pre %err(err_urn));
125 |
126 | urn = pre ':' @err(err_pre) (nid_not_urn ':' nss >mark %set_nss) %eof(base_type);
127 |
128 | ### SCIM BEG
129 |
130 | action err_scim_nid {
131 | m.err = fmt.Errorf(errSCIMNamespace, m.p)
132 | fhold;
133 | fgoto fail;
134 | }
135 |
136 | action err_scim_type {
137 | m.err = fmt.Errorf(errSCIMType, m.p)
138 | fhold;
139 | fgoto fail;
140 | }
141 |
142 | action err_scim_name {
143 | m.err = fmt.Errorf(errSCIMName, m.p)
144 | fhold;
145 | fgoto fail;
146 | }
147 |
148 | action err_scim_other {
149 | if m.p == m.pe {
150 | m.err = fmt.Errorf(errSCIMOtherIncomplete, m.p-1)
151 | } else {
152 | m.err = fmt.Errorf(errSCIMOther, m.p)
153 | }
154 | fhold;
155 | fgoto fail;
156 | }
157 |
158 | action scim_type {
159 | output.kind = RFC7643;
160 | }
161 |
162 | action create_scim {
163 | output.scim = &SCIM{};
164 | }
165 |
166 | action set_scim_type {
167 | output.scim.Type = scimschema.TypeFromString(string(m.text()))
168 | }
169 |
170 | action mark_scim_name {
171 | output.scim.pos = m.p
172 | }
173 |
174 | action set_scim_name {
175 | output.scim.Name = string(m.data[output.scim.pos:m.p])
176 | }
177 |
178 | action mark_scim_other {
179 | output.scim.pos = m.p
180 | }
181 |
182 | action set_scim_other {
183 | output.scim.Other = string(m.data[output.scim.pos:m.p])
184 | }
185 |
186 | scim_nid = 'ietf:params:scim' >mark %set_nid %create_scim $err(err_scim_nid);
187 |
188 | scim_other = ':' (sss | hex)+ >mark_scim_other %set_scim_other $err(err_scim_other);
189 |
190 | scim_name = (alnum)+ >mark_scim_name %set_scim_name $err(err_scim_name);
191 |
192 | scim_type = ('schemas' | 'api' | 'param') >mark %set_scim_type $err(err_scim_type);
193 |
194 | scim_only := pre ':' @err(err_pre) (scim_nid ':' scim_type ':' scim_name scim_other? %set_nss) %eof(scim_type);
195 |
196 | ### SCIM END
197 |
198 | ### 8141 BEG
199 |
200 | action err_nss_8141 {
201 | m.err = fmt.Errorf(err8141SpecificString, m.p)
202 | fhold;
203 | fgoto fail;
204 | }
205 |
206 | action err_nid_8141 {
207 | m.err = fmt.Errorf(err8141Identifier, m.p)
208 | fhold;
209 | fgoto fail;
210 | }
211 |
212 | action rfc8141_type {
213 | output.kind = RFC8141;
214 | }
215 |
216 | action set_r_component {
217 | output.rComponent = string(m.text())
218 | }
219 |
220 | action set_q_component {
221 | output.qComponent = string(m.text())
222 | }
223 |
224 | action set_f_component {
225 | output.fComponent = string(m.text())
226 | }
227 |
228 | action informal_nid_match {
229 | fhold;
230 | m.err = fmt.Errorf(err8141InformalID, m.p);
231 | fgoto fail;
232 | }
233 |
234 | action mark_r_start {
235 | if output.rStart {
236 | m.err = fmt.Errorf(err8141RComponentStart, m.p)
237 | fhold;
238 | fgoto fail;
239 | }
240 | output.rStart = true
241 | }
242 |
243 | action mark_q_start {
244 | if output.qStart {
245 | m.err = fmt.Errorf(err8141QComponentStart, m.p)
246 | fhold;
247 | fgoto fail;
248 | }
249 | output.qStart = true
250 | }
251 |
252 | action err_malformed_r_component {
253 | m.err = fmt.Errorf(err8141MalformedRComp, m.p)
254 | fhold;
255 | fgoto fail;
256 | }
257 |
258 | action err_malformed_q_component {
259 | m.err = fmt.Errorf(err8141MalformedQComp, m.p)
260 | fhold;
261 | fgoto fail;
262 | }
263 |
264 | pchar = (sss | '~' | '&' | hex);
265 |
266 | component = pchar (pchar | '/' | '?')*;
267 |
268 | r_start = ('?+') %mark_r_start;
269 |
270 | r_component = r_start <: (r_start | component)+ $err(err_malformed_r_component) >mark %set_r_component;
271 |
272 | q_start = ('?=') %mark_q_start;
273 |
274 | q_component = q_start <: (q_start | component)+ $err(err_malformed_q_component) >mark %set_q_component;
275 |
276 | rq_components = (r_component :>> q_component? | q_component);
277 |
278 | fragment = (pchar | '/' | '?')*;
279 |
280 | f_component = '#' fragment >mark %set_f_component;
281 |
282 | nss_rfc8141 = (pchar >mark (pchar | '/')*) $err(err_nss_8141) %set_nss;
283 |
284 | nid_rfc8141 = (alnum >mark (alnum | '-'){0,30} alnum) $err(err_nid_8141) %set_nid;
285 |
286 | informal_id = pre ('-' [a-zA-z0] %to(informal_nid_match));
287 |
288 | nid_rfc8141_not_urn = (nid_rfc8141 - informal_id?);
289 |
290 | rfc8141_only := pre ':' @err(err_pre) nid_rfc8141_not_urn ':' nss_rfc8141 rq_components? f_component? %eof(rfc8141_type);
291 |
292 | ### 8141 END
293 |
294 | fail := (any - [\n\r])* @err{ fgoto main; };
295 |
296 | main := urn;
297 |
298 | }%%
299 |
300 | %% write data noerror noprefix;
301 |
302 | // Machine is the interface representing the FSM
303 | type Machine interface {
304 | Error() error
305 | Parse(input []byte) (*URN, error)
306 | WithParsingMode(ParsingMode)
307 | }
308 |
309 | type machine struct {
310 | data []byte
311 | cs int
312 | p, pe, eof, pb int
313 | err error
314 | startParsingAt int
315 | parsingMode ParsingMode
316 | parsingModeSet bool
317 | }
318 |
319 | // NewMachine creates a new FSM able to parse RFC 2141 strings.
320 | func NewMachine(options ...Option) Machine {
321 | m := &machine{
322 | parsingModeSet: false,
323 | }
324 |
325 | for _, o := range options {
326 | o(m)
327 | }
328 | // Set default parsing mode
329 | if !m.parsingModeSet {
330 | m.WithParsingMode(DefaultParsingMode)
331 | }
332 |
333 | %% access m.;
334 | %% variable p m.p;
335 | %% variable pe m.pe;
336 | %% variable eof m.eof;
337 | %% variable data m.data;
338 |
339 | return m
340 | }
341 |
342 | // Err returns the error that occurred on the last call to Parse.
343 | //
344 | // If the result is nil, then the line was parsed successfully.
345 | func (m *machine) Error() error {
346 | return m.err
347 | }
348 |
349 | func (m *machine) text() []byte {
350 | return m.data[m.pb:m.p]
351 | }
352 |
353 | // Parse parses the input byte array as a RFC 2141 or RFC7643 string.
354 | func (m *machine) Parse(input []byte) (*URN, error) {
355 | m.data = input
356 | m.p = 0
357 | m.pb = 0
358 | m.pe = len(input)
359 | m.eof = len(input)
360 | m.err = nil
361 | m.cs = m.startParsingAt
362 | output := &URN{
363 | tolower: []int{},
364 | }
365 |
366 | %% write exec;
367 |
368 | if m.cs < first_final || m.cs == en_fail {
369 | return nil, m.err
370 | }
371 |
372 | return output, nil
373 | }
374 |
375 | func (m *machine) WithParsingMode(x ParsingMode) {
376 | m.parsingMode = x
377 | switch m.parsingMode {
378 | case RFC2141Only:
379 | m.startParsingAt = en_main
380 | case RFC8141Only:
381 | m.startParsingAt = en_rfc8141_only
382 | case RFC7643Only:
383 | m.startParsingAt = en_scim_only
384 | }
385 | m.parsingModeSet = true
386 | }
--------------------------------------------------------------------------------
/machine_test.go:
--------------------------------------------------------------------------------
1 | package urn
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | "github.com/stretchr/testify/require"
8 | )
9 |
10 | func TestDefaultParsingMode(t *testing.T) {
11 | m := NewMachine()
12 | require.NotNil(t, m)
13 | require.IsType(t, &machine{}, m)
14 | require.Equal(t, DefaultParsingMode, m.(*machine).parsingMode)
15 | }
16 |
17 | func exec(t *testing.T, testCases []testCase, mode ParsingMode) {
18 | t.Helper()
19 | m := NewMachine(WithParsingMode(mode))
20 | for ii, tt := range testCases {
21 | urn, err := m.Parse([]byte(tt.in))
22 | ok := err == nil
23 |
24 | if ok {
25 | assert.True(t, tt.ok, herror(ii, tt))
26 | assert.Equal(t, tt.obj.prefix, urn.prefix, herror(ii, tt))
27 | assert.Equal(t, tt.obj.ID, urn.ID, herror(ii, tt))
28 | assert.Equal(t, tt.obj.SS, urn.SS, herror(ii, tt))
29 | assert.Equal(t, tt.str, urn.String(), herror(ii, tt))
30 | assert.Equal(t, tt.norm, urn.Normalize().String(), herror(ii, tt))
31 | if mode == RFC8141Only {
32 | assert.Equal(t, tt.obj.rComponent, urn.rComponent, herror(ii, tt))
33 | assert.Equal(t, tt.obj.rComponent, urn.rComponent, herror(ii, tt))
34 | assert.Equal(t, tt.obj.rComponent, urn.rComponent, herror(ii, tt))
35 | assert.Equal(t, urn.RFC(), RFC8141, herror(ii, tt))
36 | }
37 | if mode == RFC7643Only {
38 | assert.Equal(t, tt.isSCIM, urn.IsSCIM(), herror(ii, tt))
39 | }
40 | } else {
41 | assert.False(t, tt.ok, herror(ii, tt))
42 | assert.Empty(t, urn, herror(ii, tt))
43 | assert.Equal(t, tt.estr, err.Error(), herror(ii, tt))
44 | assert.Equal(t, tt.estr, m.Error().Error(), herror(ii, tt))
45 | }
46 | }
47 | }
48 |
49 | func TestParseDefaultMode(t *testing.T) {
50 | require.Equal(t, RFC2141Only, DefaultParsingMode)
51 | exec(t, urn2141OnlyTestCases, DefaultParsingMode)
52 | }
53 |
54 | func TestParse2141Only(t *testing.T) {
55 | exec(t, urn2141OnlyTestCases, RFC2141Only)
56 | }
57 |
58 | func TestParseUrnLex2141Only(t *testing.T) {
59 | exec(t, urnlexTestCases, RFC2141Only)
60 | }
61 |
62 | func TestSCIMOnly(t *testing.T) {
63 | exec(t, scimOnlyTestCases, RFC7643Only)
64 | }
65 |
66 | func TestParse8141Only(t *testing.T) {
67 | exec(t, rfc8141TestCases, RFC8141Only)
68 | }
69 |
--------------------------------------------------------------------------------
/makefile:
--------------------------------------------------------------------------------
1 | SHELL := /bin/bash
2 | RAGEL := ragel
3 | GOFMT := go fmt
4 |
5 | export GO_TEST=env GOTRACEBACK=all go test $(GO_ARGS)
6 |
7 | .PHONY: build
8 | build: machine.go
9 |
10 | .PHONY: clean
11 | clean:
12 | @rm -rf docs
13 | @rm -f machine.go
14 |
15 | .PHONY: images
16 | images: docs/urn.png
17 |
18 | .PHONY: snake2camel
19 | snake2camel:
20 | @cd ./tools/snake2camel; go build -o ../../snake2camel .
21 |
22 | .PHONY: removecomments
23 | removecomments:
24 | @cd ./tools/removecomments; go build -o ../../removecomments .
25 |
26 | machine.go: machine.go.rl
27 |
28 | machine.go: snake2camel
29 |
30 | machine.go: removecomments
31 |
32 | machine.go:
33 | $(RAGEL) -Z -G1 -e -o $@ $<
34 | @./removecomments $@
35 | @./snake2camel $@
36 | $(GOFMT) $@
37 |
38 | docs/urn.dot: machine.go.rl
39 | @mkdir -p docs
40 | $(RAGEL) -Z -e -Vp $< -o $@
41 |
42 | docs/urn.png: docs/urn.dot
43 | dot $< -Tpng -o $@
44 |
45 | .PHONY: bench
46 | bench: *_test.go machine.go
47 | go test -bench=. -benchmem -benchtime=5s ./...
48 |
49 | .PHONY: tests
50 | tests: *_test.go
51 | $(GO_TEST) ./...
52 |
--------------------------------------------------------------------------------
/options.go:
--------------------------------------------------------------------------------
1 | package urn
2 |
3 | type Option func(Machine)
4 |
5 | func WithParsingMode(mode ParsingMode) Option {
6 | return func(m Machine) {
7 | m.WithParsingMode(mode)
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/parsing_mode.go:
--------------------------------------------------------------------------------
1 | package urn
2 |
3 | type ParsingMode int
4 |
5 | const (
6 | Default ParsingMode = iota
7 | RFC2141Only
8 | RFC7643Only
9 | RFC8141Only
10 | )
11 |
12 | const DefaultParsingMode = RFC2141Only
13 |
--------------------------------------------------------------------------------
/performance_test.go:
--------------------------------------------------------------------------------
1 | package urn
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 | )
7 |
8 | var benchs = []testCase{
9 | urn2141OnlyTestCases[14],
10 | urn2141OnlyTestCases[2],
11 | urn2141OnlyTestCases[6],
12 | urn2141OnlyTestCases[10],
13 | urn2141OnlyTestCases[11],
14 | urn2141OnlyTestCases[13],
15 | urn2141OnlyTestCases[20],
16 | urn2141OnlyTestCases[23],
17 | urn2141OnlyTestCases[33],
18 | urn2141OnlyTestCases[45],
19 | urn2141OnlyTestCases[47],
20 | urn2141OnlyTestCases[48],
21 | urn2141OnlyTestCases[50],
22 | urn2141OnlyTestCases[52],
23 | urn2141OnlyTestCases[53],
24 | urn2141OnlyTestCases[57],
25 | urn2141OnlyTestCases[62],
26 | urn2141OnlyTestCases[63],
27 | urn2141OnlyTestCases[67],
28 | urn2141OnlyTestCases[60],
29 | }
30 |
31 | // This is here to avoid compiler optimizations that
32 | // could remove the actual call we are benchmarking
33 | // during benchmarks
34 | var benchParseResult *URN
35 |
36 | func BenchmarkParse(b *testing.B) {
37 | for ii, tt := range benchs {
38 | tt := tt
39 | outcome := (map[bool]string{true: "ok", false: "no"})[tt.ok]
40 | b.Run(
41 | fmt.Sprintf("%s/%02d/%s/", outcome, ii, rxpad(string(tt.in), 45)),
42 | func(b *testing.B) {
43 | for i := 0; i < b.N; i++ {
44 | benchParseResult, _ = Parse(tt.in)
45 | }
46 | },
47 | )
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/scim.go:
--------------------------------------------------------------------------------
1 | package urn
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 |
7 | scimschema "github.com/leodido/go-urn/scim/schema"
8 | )
9 |
10 | const errInvalidSCIMURN = "invalid SCIM URN: %s"
11 |
12 | type SCIM struct {
13 | Type scimschema.Type
14 | Name string
15 | Other string
16 | pos int
17 | }
18 |
19 | func (s SCIM) MarshalJSON() ([]byte, error) {
20 | return json.Marshal(s.String())
21 | }
22 |
23 | func (s *SCIM) UnmarshalJSON(bytes []byte) error {
24 | var str string
25 | if err := json.Unmarshal(bytes, &str); err != nil {
26 | return err
27 | }
28 | // Parse as SCIM
29 | value, ok := Parse([]byte(str), WithParsingMode(RFC7643Only))
30 | if !ok {
31 | return fmt.Errorf(errInvalidSCIMURN, str)
32 | }
33 | if value.RFC() != RFC7643 {
34 | return fmt.Errorf(errInvalidSCIMURN, str)
35 | }
36 | *s = *value.SCIM()
37 |
38 | return nil
39 | }
40 |
41 | func (s *SCIM) String() string {
42 | ret := fmt.Sprintf("urn:ietf:params:scim:%s:%s", s.Type.String(), s.Name)
43 | if s.Other != "" {
44 | ret += fmt.Sprintf(":%s", s.Other)
45 | }
46 |
47 | return ret
48 | }
49 |
--------------------------------------------------------------------------------
/scim/schema/type.go:
--------------------------------------------------------------------------------
1 | package scimschema
2 |
3 | type Type int
4 |
5 | const (
6 | Unsupported Type = iota
7 | Schemas
8 | API
9 | Param
10 | )
11 |
12 | func (t Type) String() string {
13 | switch t {
14 | case Schemas:
15 | return "schemas"
16 | case API:
17 | return "api"
18 | case Param:
19 | return "param"
20 | }
21 |
22 | return ""
23 | }
24 |
25 | func TypeFromString(input string) Type {
26 | switch input {
27 | case "schemas":
28 | return Schemas
29 | case "api":
30 | return API
31 | case "param":
32 | return Param
33 | }
34 |
35 | return Unsupported
36 | }
37 |
--------------------------------------------------------------------------------
/scim/schema/type_test.go:
--------------------------------------------------------------------------------
1 | package scimschema
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/require"
7 | )
8 |
9 | func TestTypeFromString(t *testing.T) {
10 | uns := TypeFromString("wrong")
11 | require.Equal(t, Unsupported, uns)
12 | require.Empty(t, uns.String())
13 |
14 | schemas := TypeFromString("schemas")
15 | require.Equal(t, Schemas, schemas)
16 | require.Equal(t, "schemas", schemas.String())
17 |
18 | api := TypeFromString("api")
19 | require.Equal(t, API, api)
20 | require.Equal(t, "api", api.String())
21 |
22 | param := TypeFromString("param")
23 | require.Equal(t, Param, param)
24 | require.Equal(t, "param", param.String())
25 | }
26 |
--------------------------------------------------------------------------------
/scim_test.go:
--------------------------------------------------------------------------------
1 | package urn
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "testing"
7 |
8 | scimschema "github.com/leodido/go-urn/scim/schema"
9 | "github.com/stretchr/testify/assert"
10 | "github.com/stretchr/testify/require"
11 | )
12 |
13 | func TestSCIMJSONMarshaling(t *testing.T) {
14 | t.Run("roundtrip", func(t *testing.T) {
15 | // Marshal
16 | exp := SCIM{Type: scimschema.Schemas, Name: "core", Other: "extension:enterprise:2.0:User"}
17 | mar, err := json.Marshal(exp)
18 | require.NoError(t, err)
19 | require.Equal(t, `"urn:ietf:params:scim:schemas:core:extension:enterprise:2.0:User"`, string(mar))
20 |
21 | // Unmarshal
22 | var act SCIM
23 | err = json.Unmarshal(mar, &act)
24 | require.NoError(t, err)
25 | exp.pos = 34
26 | require.Equal(t, exp, act)
27 | })
28 |
29 | t.Run("unmarshal", func(t *testing.T) {
30 | exp := `urn:ietf:params:scim:schemas:extension:enterprise:2.0:User`
31 | var got SCIM
32 | err := json.Unmarshal([]byte(fmt.Sprintf(`"%s"`, exp)), &got)
33 | if !assert.NoError(t, err) {
34 | return
35 | }
36 | assert.Equal(t, exp, got.String())
37 | assert.Equal(t, SCIM{
38 | Type: scimschema.Schemas,
39 | Name: "extension",
40 | Other: "enterprise:2.0:User",
41 | pos: 39,
42 | }, got)
43 | })
44 |
45 | t.Run("unmarshal without the part", func(t *testing.T) {
46 | exp := `urn:ietf:params:scim:schemas:core`
47 | var got SCIM
48 | err := json.Unmarshal([]byte(fmt.Sprintf(`"%s"`, exp)), &got)
49 | if !assert.NoError(t, err) {
50 | return
51 | }
52 | assert.Equal(t, exp, got.String())
53 | assert.Equal(t, SCIM{
54 | Type: scimschema.Schemas,
55 | Name: "core",
56 | pos: 29,
57 | }, got)
58 | })
59 |
60 | t.Run("invalid URN", func(t *testing.T) {
61 | var actual SCIM
62 | err := json.Unmarshal([]byte(`"not a URN"`), &actual)
63 | assert.EqualError(t, err, "invalid SCIM URN: not a URN")
64 | })
65 |
66 | t.Run("empty", func(t *testing.T) {
67 | var actual SCIM
68 | err := actual.UnmarshalJSON(nil)
69 | assert.EqualError(t, err, "unexpected end of JSON input")
70 | })
71 | }
72 |
--------------------------------------------------------------------------------
/tables_test.go:
--------------------------------------------------------------------------------
1 | package urn
2 |
3 | import (
4 | "fmt"
5 | "strconv"
6 | "strings"
7 | )
8 |
9 | func ierror(index int) string {
10 | return "Test case num. " + strconv.Itoa(index+1)
11 | }
12 |
13 | func herror(index int, test testCase) string {
14 | return ierror(index) + ", input \"" + string(test.in) + "\""
15 | }
16 |
17 | func rxpad(str string, lim int) string {
18 | str = str + strings.Repeat(" ", lim)
19 | return str[:lim]
20 | }
21 |
22 | type testCase struct {
23 | in []byte // the input
24 | ok bool // whether it is valid or not
25 | obj *URN // a pointer to the resulting urn.URN instance
26 | str string // string representation
27 | norm string // norm string representation
28 | estr string // error string
29 | isSCIM bool // whether it is a SCIM URN or not
30 | }
31 |
32 | var urnlexTestCases = []testCase{
33 | // Italian act
34 | {
35 | []byte("urn:lex:it:stato:legge:2003-09-21;456"),
36 | true,
37 | &URN{
38 | prefix: "urn",
39 | ID: "lex",
40 | SS: "it:stato:legge:2003-09-21;456",
41 | },
42 | "urn:lex:it:stato:legge:2003-09-21;456",
43 | "urn:lex:it:stato:legge:2003-09-21;456",
44 | "",
45 | false,
46 | },
47 | // French act
48 | {
49 | []byte("urn:lex:fr:etat:lois:2004-12-06;321"),
50 | true,
51 | &URN{
52 | prefix: "urn",
53 | ID: "lex",
54 | SS: "fr:etat:lois:2004-12-06;321",
55 | },
56 | "urn:lex:fr:etat:lois:2004-12-06;321",
57 | "urn:lex:fr:etat:lois:2004-12-06;321",
58 | "",
59 | false,
60 | },
61 | // Spanish act
62 | {
63 | []byte("urn:lex:es:estado:ley:2002-07-12;123"),
64 | true,
65 | &URN{
66 | prefix: "urn",
67 | ID: "lex",
68 | SS: "es:estado:ley:2002-07-12;123",
69 | },
70 | "urn:lex:es:estado:ley:2002-07-12;123",
71 | "urn:lex:es:estado:ley:2002-07-12;123",
72 | "",
73 | false,
74 | },
75 | // Glarus Swiss Canton decree
76 | {
77 | []byte("urn:lex:ch;glarus:regiere:erlass:2007-10-15;963"),
78 | true,
79 | &URN{
80 | prefix: "urn",
81 | ID: "lex",
82 | SS: "ch;glarus:regiere:erlass:2007-10-15;963",
83 | },
84 | "urn:lex:ch;glarus:regiere:erlass:2007-10-15;963",
85 | "urn:lex:ch;glarus:regiere:erlass:2007-10-15;963",
86 | "",
87 | false,
88 | },
89 | // EU Council Directive
90 | {
91 | []byte("urn:lex:eu:council:directive:2010-03-09;2010-19-UE"),
92 | true,
93 | &URN{
94 | prefix: "urn",
95 | ID: "lex",
96 | SS: "eu:council:directive:2010-03-09;2010-19-UE",
97 | },
98 | "urn:lex:eu:council:directive:2010-03-09;2010-19-UE",
99 | "urn:lex:eu:council:directive:2010-03-09;2010-19-UE",
100 | "",
101 | false,
102 | },
103 | {
104 | []byte("urn:lex:eu:council:directive:2010-03-09;2010-19-UE"),
105 | true,
106 | &URN{
107 | prefix: "urn",
108 | ID: "lex",
109 | SS: "eu:council:directive:2010-03-09;2010-19-UE",
110 | },
111 | "urn:lex:eu:council:directive:2010-03-09;2010-19-UE",
112 | "urn:lex:eu:council:directive:2010-03-09;2010-19-UE",
113 | "",
114 | false,
115 | },
116 | // US FSC decision
117 | {
118 | []byte("urn:lex:us:federal.supreme.court:decision:1963-03-18;372.us.335"),
119 | true,
120 | &URN{
121 | prefix: "urn",
122 | ID: "lex",
123 | SS: "us:federal.supreme.court:decision:1963-03-18;372.us.335",
124 | },
125 | "urn:lex:us:federal.supreme.court:decision:1963-03-18;372.us.335",
126 | "urn:lex:us:federal.supreme.court:decision:1963-03-18;372.us.335",
127 | "",
128 | false,
129 | },
130 | }
131 |
132 | var scimOnlyTestCases = []testCase{
133 | // ok
134 | {
135 | []byte("urn:ietf:params:scim:schemas:core:2.0:User"),
136 | true,
137 | &URN{
138 | prefix: "urn",
139 | ID: "ietf:params:scim",
140 | SS: "schemas:core:2.0:User",
141 | },
142 | "urn:ietf:params:scim:schemas:core:2.0:User",
143 | "urn:ietf:params:scim:schemas:core:2.0:User",
144 | "",
145 | true,
146 | },
147 | {
148 | []byte("urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"),
149 | true,
150 | &URN{
151 | prefix: "urn",
152 | ID: "ietf:params:scim",
153 | SS: "schemas:extension:enterprise:2.0:User",
154 | },
155 | "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User",
156 | "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User",
157 | "",
158 | true,
159 | },
160 | {
161 | []byte("urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:userName"),
162 | true,
163 | &URN{
164 | prefix: "urn",
165 | ID: "ietf:params:scim",
166 | SS: "schemas:extension:enterprise:2.0:User:userName",
167 | },
168 | "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:userName",
169 | "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:userName",
170 | "",
171 | true,
172 | },
173 | {
174 | []byte("urn:ietf:params:scim:api:messages:2.0:ListResponse"),
175 | true,
176 | &URN{
177 | prefix: "urn",
178 | ID: "ietf:params:scim",
179 | SS: "api:messages:2.0:ListResponse",
180 | },
181 | "urn:ietf:params:scim:api:messages:2.0:ListResponse",
182 | "urn:ietf:params:scim:api:messages:2.0:ListResponse",
183 | "",
184 | true,
185 | },
186 | {
187 | []byte("urn:ietf:params:scim:schemas:core"),
188 | true,
189 | &URN{
190 | prefix: "urn",
191 | ID: "ietf:params:scim",
192 | SS: "schemas:core",
193 | },
194 | "urn:ietf:params:scim:schemas:core",
195 | "urn:ietf:params:scim:schemas:core",
196 | "",
197 | true,
198 | },
199 | {
200 | []byte("urn:ietf:params:scim:param:core"),
201 | true,
202 | &URN{
203 | prefix: "urn",
204 | ID: "ietf:params:scim",
205 | SS: "param:core",
206 | },
207 | "urn:ietf:params:scim:param:core",
208 | "urn:ietf:params:scim:param:core",
209 | "",
210 | true,
211 | },
212 | {
213 | []byte("urn:ietf:params:scim:api:messages:%FF"),
214 | true,
215 | &URN{
216 | prefix: "urn",
217 | ID: "ietf:params:scim",
218 | SS: "api:messages:%FF",
219 | },
220 | "urn:ietf:params:scim:api:messages:%FF",
221 | "urn:ietf:params:scim:api:messages:%ff",
222 | "",
223 | true,
224 | },
225 |
226 | // no
227 | {
228 | []byte("arn:ietf:params:scim:schemas:core"),
229 | false,
230 | nil,
231 | "",
232 | "",
233 | fmt.Sprintf(errPrefix, 0),
234 | false,
235 | },
236 | {
237 | []byte("usn:ietf:params:scim:schemas:core"),
238 | false,
239 | nil,
240 | "",
241 | "",
242 | fmt.Sprintf(errPrefix, 1),
243 | false,
244 | },
245 | {
246 | []byte("urm:ietf:params:scim:schemas:core"),
247 | false,
248 | nil,
249 | "",
250 | "",
251 | fmt.Sprintf(errPrefix, 2),
252 | false,
253 | },
254 | {
255 | []byte("urno:ietf:params:scim:schemas:core"),
256 | false,
257 | nil,
258 | "",
259 | "",
260 | fmt.Sprintf(errPrefix, 3),
261 | false,
262 | },
263 | {
264 | []byte("urno"),
265 | false,
266 | nil,
267 | "",
268 | "",
269 | fmt.Sprintf(errPrefix, 3),
270 | false,
271 | },
272 | {
273 | []byte("urn:WRONG:schemas:core"),
274 | false,
275 | nil,
276 | "",
277 | "",
278 | fmt.Sprintf(errSCIMNamespace, 4),
279 | false,
280 | },
281 | {
282 | []byte("urn:ietf:params:scim:WRONG:core"),
283 | false,
284 | nil,
285 | "",
286 | "",
287 | fmt.Sprintf(errSCIMType, 21),
288 | false,
289 | },
290 | {
291 | []byte("urn:ietf:params:scim:schemas:$"),
292 | false,
293 | nil,
294 | "",
295 | "",
296 | fmt.Sprintf(errSCIMName, 29),
297 | false,
298 | },
299 | {
300 | []byte("urn:ietf:params:scim:schemas:core-"),
301 | false,
302 | nil,
303 | "",
304 | "",
305 | fmt.Sprintf(errSCIMName, 33),
306 | false,
307 | },
308 | {
309 | []byte("urn:ietf:params:scim:schemas:core:"),
310 | false,
311 | nil,
312 | "",
313 | "",
314 | fmt.Sprintf(errSCIMOtherIncomplete, 33),
315 | false,
316 | },
317 | {
318 | []byte("urn:ietf:params:scim:schemas:core:2.&"),
319 | false,
320 | nil,
321 | "",
322 | "",
323 | fmt.Sprintf(errSCIMOther, 36),
324 | false,
325 | },
326 | {
327 | []byte("urn:ietf:params:scim:api:messages:%"),
328 | false,
329 | nil,
330 | "",
331 | "",
332 | fmt.Sprintf(errSCIMOtherIncomplete, 34),
333 | false,
334 | },
335 | {
336 | []byte("urn:ietf:params:scim:api:messages:%F"),
337 | false,
338 | nil,
339 | "",
340 | "",
341 | fmt.Sprintf(errSCIMOtherIncomplete, 35),
342 | false,
343 | },
344 | // TODO: verify
345 | // {
346 | // []byte("urn:ietf:params:scim:api:core:"),
347 | // true,
348 | // nil,
349 | // "",
350 | // "",
351 | // fmt.Sprintf(errSCIMOtherIncomplete, 29),
352 | // false,
353 | // },
354 | {
355 | []byte("urn:"),
356 | false,
357 | nil,
358 | "",
359 | "",
360 | fmt.Sprintf(errSCIMNamespace, 4),
361 | false,
362 | },
363 | {
364 | []byte("urn::"),
365 | false,
366 | nil,
367 | "",
368 | "",
369 | fmt.Sprintf(errSCIMNamespace, 4),
370 | false,
371 | },
372 | {
373 | []byte("urn:a:"),
374 | false,
375 | nil,
376 | "",
377 | "",
378 | fmt.Sprintf(errSCIMNamespace, 4),
379 | false,
380 | },
381 | {
382 | []byte("urn:a"),
383 | false,
384 | nil,
385 | "",
386 | "",
387 | fmt.Sprintf(errSCIMNamespace, 4),
388 | false,
389 | },
390 | {
391 | []byte(`u`),
392 | false,
393 | nil,
394 | "",
395 | "",
396 | fmt.Sprintf(errPrefix, 1),
397 | false,
398 | },
399 | {
400 | []byte(`ur`),
401 | false,
402 | nil,
403 | "",
404 | "",
405 | fmt.Sprintf(errPrefix, 2),
406 | false,
407 | },
408 | {
409 | []byte(`urn`),
410 | false,
411 | nil,
412 | "",
413 | "",
414 | fmt.Sprintf(errPrefix, 3),
415 | false,
416 | },
417 | }
418 |
419 | var urn2141OnlyTestCases = []testCase{
420 | // ok
421 | {
422 | []byte("urn:simple:simple"),
423 | true,
424 | &URN{
425 | prefix: "urn",
426 | ID: "simple",
427 | SS: "simple",
428 | },
429 | "urn:simple:simple",
430 | "urn:simple:simple",
431 | "",
432 | false,
433 | },
434 | {
435 | []byte("urn:ciao:%5D"),
436 | true,
437 | &URN{
438 | prefix: "urn",
439 | ID: "ciao",
440 | SS: "%5D",
441 | },
442 | "urn:ciao:%5D",
443 | "urn:ciao:%5d",
444 | "",
445 | false,
446 | },
447 |
448 | // ok - RFC examples
449 | {
450 | []byte("URN:foo:a123,456"),
451 | true,
452 | &URN{
453 | prefix: "URN",
454 | ID: "foo",
455 | SS: "a123,456",
456 | },
457 | "URN:foo:a123,456",
458 | "urn:foo:a123,456",
459 | "",
460 | false,
461 | },
462 | {
463 | []byte("urn:foo:a123,456"),
464 | true,
465 | &URN{
466 | prefix: "urn",
467 | ID: "foo",
468 | SS: "a123,456",
469 | },
470 | "urn:foo:a123,456",
471 | "urn:foo:a123,456",
472 | "",
473 | false,
474 | },
475 | {
476 | []byte("urn:FOO:a123,456"),
477 | true,
478 | &URN{
479 | prefix: "urn",
480 | ID: "FOO",
481 | SS: "a123,456",
482 | },
483 | "urn:FOO:a123,456",
484 | "urn:foo:a123,456",
485 | "",
486 | false,
487 | },
488 | {
489 | []byte("urn:foo:A123,456"),
490 | true,
491 | &URN{
492 | prefix: "urn",
493 | ID: "foo",
494 | SS: "A123,456",
495 | },
496 | "urn:foo:A123,456",
497 | "urn:foo:A123,456",
498 | "",
499 | false,
500 | },
501 | {
502 | []byte("urn:foo:a123%2C456"),
503 | true,
504 | &URN{
505 | prefix: "urn",
506 | ID: "foo",
507 | SS: "a123%2C456",
508 | },
509 | "urn:foo:a123%2C456",
510 | "urn:foo:a123%2c456",
511 | "",
512 | false,
513 | },
514 | {
515 | []byte("URN:FOO:a123%2c456"),
516 | true,
517 | &URN{
518 | prefix: "URN",
519 | ID: "FOO",
520 | SS: "a123%2c456",
521 | },
522 | "URN:FOO:a123%2c456",
523 | "urn:foo:a123%2c456",
524 | "",
525 | false,
526 | },
527 | {
528 | []byte("URN:FOO:ABC%FFabc123%2c456"),
529 | true,
530 | &URN{
531 | prefix: "URN",
532 | ID: "FOO",
533 | SS: "ABC%FFabc123%2c456",
534 | },
535 | "URN:FOO:ABC%FFabc123%2c456",
536 | "urn:foo:ABC%ffabc123%2c456",
537 | "",
538 | false,
539 | },
540 | {
541 | []byte("URN:FOO:ABC%FFabc123%2C456%9A"),
542 | true,
543 | &URN{
544 | prefix: "URN",
545 | ID: "FOO",
546 | SS: "ABC%FFabc123%2C456%9A",
547 | },
548 | "URN:FOO:ABC%FFabc123%2C456%9A",
549 | "urn:foo:ABC%ffabc123%2c456%9a",
550 | "",
551 | false,
552 | },
553 |
554 | // ok - SCIM v2
555 | {
556 | []byte("urn:ietf:params:scim:schemas:core:2.0:User"),
557 | true,
558 | &URN{
559 | prefix: "urn",
560 | ID: "ietf",
561 | SS: "params:scim:schemas:core:2.0:User",
562 | },
563 | "urn:ietf:params:scim:schemas:core:2.0:User",
564 | "urn:ietf:params:scim:schemas:core:2.0:User",
565 | "",
566 | true,
567 | },
568 | {
569 | []byte("urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"),
570 | true,
571 | &URN{
572 | prefix: "urn",
573 | ID: "ietf",
574 | SS: "params:scim:schemas:extension:enterprise:2.0:User",
575 | },
576 | "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User",
577 | "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User",
578 | "",
579 | true,
580 | },
581 | {
582 | []byte("urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:userName"),
583 | true,
584 | &URN{
585 | prefix: "urn",
586 | ID: "ietf",
587 | SS: "params:scim:schemas:extension:enterprise:2.0:User:userName",
588 | },
589 | "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:userName",
590 | "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:userName",
591 | "",
592 | true,
593 | },
594 | {
595 | []byte("urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:meta.lastModified"),
596 | true,
597 | &URN{
598 | prefix: "urn",
599 | ID: "ietf",
600 | SS: "params:scim:schemas:extension:enterprise:2.0:User:meta.lastModified",
601 | },
602 | "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:meta.lastModified",
603 | "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:meta.lastModified",
604 | "",
605 | true,
606 | },
607 |
608 | // ok - minimum urn
609 | {
610 | []byte("urn:a:b"),
611 | true,
612 | &URN{
613 | prefix: "urn",
614 | ID: "a",
615 | SS: "b",
616 | },
617 | "urn:a:b",
618 | "urn:a:b",
619 | "",
620 | false,
621 | },
622 | {
623 | []byte("urn:a::"),
624 | true,
625 | &URN{
626 | prefix: "urn",
627 | ID: "a",
628 | SS: ":",
629 | },
630 | "urn:a::",
631 | "urn:a::",
632 | "",
633 | false,
634 | },
635 | {
636 | []byte("urn:a:-"),
637 | true,
638 | &URN{
639 | prefix: "urn",
640 | ID: "a",
641 | SS: "-",
642 | },
643 | "urn:a:-",
644 | "urn:a:-",
645 | "",
646 | false,
647 | },
648 |
649 | // ok - URN prefix is case-insensitive
650 | {
651 | []byte("URN:simple:simple"),
652 | true,
653 | &URN{
654 | prefix: "URN",
655 | ID: "simple",
656 | SS: "simple",
657 | },
658 | "URN:simple:simple",
659 | "urn:simple:simple",
660 | "",
661 | false,
662 | },
663 | {
664 | []byte("Urn:simple:simple"),
665 | true,
666 | &URN{
667 | prefix: "Urn",
668 | ID: "simple",
669 | SS: "simple",
670 | },
671 | "Urn:simple:simple",
672 | "urn:simple:simple",
673 | "",
674 | false,
675 | },
676 |
677 | // ok - ID can contain the "urn" string but it can not be exactly equal to it
678 | {
679 | []byte("urn:urna:simple"),
680 | true,
681 | &URN{
682 | prefix: "urn",
683 | ID: "urna",
684 | SS: "simple",
685 | },
686 | "urn:urna:simple",
687 | "urn:urna:simple",
688 | "",
689 | false,
690 | },
691 | {
692 | []byte("urn:burnout:nss"),
693 | true,
694 | &URN{
695 | prefix: "urn",
696 | ID: "burnout",
697 | SS: "nss",
698 | },
699 | "urn:burnout:nss",
700 | "urn:burnout:nss",
701 | "",
702 | false,
703 | },
704 | {
705 | []byte("urn:burn:nss"),
706 | true,
707 | &URN{
708 | prefix: "urn",
709 | ID: "burn",
710 | SS: "nss",
711 | },
712 | "urn:burn:nss",
713 | "urn:burn:nss",
714 | "",
715 | false,
716 | },
717 | {
718 | []byte("urn:urnurnurn:x"),
719 | true,
720 | &URN{
721 | prefix: "urn",
722 | ID: "urnurnurn",
723 | SS: "x",
724 | },
725 | "urn:urnurnurn:x",
726 | "urn:urnurnurn:x",
727 | "",
728 | false,
729 | },
730 |
731 | // ok - ID can contains maximum 32 characters
732 | {
733 | []byte("urn:abcdefghilmnopqrstuvzabcdefghilm:x"),
734 | true,
735 | &URN{
736 | prefix: "urn",
737 | ID: "abcdefghilmnopqrstuvzabcdefghilm",
738 | SS: "x",
739 | },
740 | "urn:abcdefghilmnopqrstuvzabcdefghilm:x",
741 | "urn:abcdefghilmnopqrstuvzabcdefghilm:x",
742 | "",
743 | false,
744 | },
745 |
746 | // ok - ID can be alpha numeric
747 | {
748 | []byte("URN:123:x"),
749 | true,
750 | &URN{
751 | prefix: "URN",
752 | ID: "123",
753 | SS: "x",
754 | },
755 | "URN:123:x",
756 | "urn:123:x",
757 | "",
758 | false,
759 | },
760 | {
761 | []byte("URN:1ab:x"),
762 | true,
763 | &URN{
764 | prefix: "URN",
765 | ID: "1ab",
766 | SS: "x",
767 | },
768 | "URN:1ab:x",
769 | "urn:1ab:x",
770 | "",
771 | false,
772 | },
773 | {
774 | []byte("URN:a1b:x"),
775 | true,
776 | &URN{
777 | prefix: "URN",
778 | ID: "a1b",
779 | SS: "x",
780 | },
781 | "URN:a1b:x",
782 | "urn:a1b:x",
783 | "",
784 | false,
785 | },
786 | {
787 | []byte("URN:a12:x"),
788 | true,
789 | &URN{
790 | prefix: "URN",
791 | ID: "a12",
792 | SS: "x",
793 | },
794 | "URN:a12:x",
795 | "urn:a12:x",
796 | "",
797 | false,
798 | },
799 | {
800 | []byte("URN:cd2:x"),
801 | true,
802 | &URN{
803 | prefix: "URN",
804 | ID: "cd2",
805 | SS: "x",
806 | },
807 | "URN:cd2:x",
808 | "urn:cd2:x",
809 | "",
810 | false,
811 | },
812 |
813 | // ok - ID can contain an hyphen (not in its first position, see below)
814 | {
815 | []byte("URN:abcd-:x"),
816 | true,
817 | &URN{
818 | prefix: "URN",
819 | ID: "abcd-",
820 | SS: "x",
821 | },
822 | "URN:abcd-:x",
823 | "urn:abcd-:x",
824 | "",
825 | false,
826 | },
827 | {
828 | []byte("URN:abcd-abcd:x"),
829 | true,
830 | &URN{
831 | prefix: "URN",
832 | ID: "abcd-abcd",
833 | SS: "x",
834 | },
835 | "URN:abcd-abcd:x",
836 | "urn:abcd-abcd:x",
837 | "",
838 | false,
839 | },
840 | {
841 | []byte("URN:a123-456z:x"),
842 | true,
843 | &URN{
844 | prefix: "URN",
845 | ID: "a123-456z",
846 | SS: "x",
847 | },
848 | "URN:a123-456z:x",
849 | "urn:a123-456z:x",
850 | "",
851 | false,
852 | },
853 |
854 | // ok - SS can contain the "urn" string, also be exactly equal to it
855 | {
856 | []byte("urn:urnx:urn"),
857 | true,
858 | &URN{
859 | prefix: "urn",
860 | ID: "urnx",
861 | SS: "urn",
862 | },
863 | "urn:urnx:urn",
864 | "urn:urnx:urn",
865 | "",
866 | false,
867 | },
868 | {
869 | []byte("urn:urnurnurn:urn"),
870 | true,
871 | &URN{
872 | prefix: "urn",
873 | ID: "urnurnurn",
874 | SS: "urn",
875 | },
876 | "urn:urnurnurn:urn",
877 | "urn:urnurnurn:urn",
878 | "",
879 | false,
880 | },
881 | {
882 | []byte("urn:hey:urnurnurn"),
883 | true,
884 | &URN{
885 | prefix: "urn",
886 | ID: "hey",
887 | SS: "urnurnurn",
888 | },
889 | "urn:hey:urnurnurn",
890 | "urn:hey:urnurnurn",
891 | "",
892 | false,
893 | },
894 |
895 | // ok - SS can contains and discerns multiple colons, also at the end
896 | {
897 | []byte("urn:ciao:a:b:c"),
898 | true,
899 | &URN{
900 | prefix: "urn",
901 | ID: "ciao",
902 | SS: "a:b:c",
903 | },
904 | "urn:ciao:a:b:c",
905 | "urn:ciao:a:b:c",
906 | "",
907 | false,
908 | },
909 | {
910 | []byte("urn:aaa:x:y:"),
911 | true,
912 | &URN{
913 | prefix: "urn",
914 | ID: "aaa",
915 | SS: "x:y:",
916 | },
917 | "urn:aaa:x:y:",
918 | "urn:aaa:x:y:",
919 | "",
920 | false,
921 | },
922 | {
923 | []byte("urn:aaa:x:y:"),
924 | true,
925 | &URN{
926 | prefix: "urn",
927 | ID: "aaa",
928 | SS: "x:y:",
929 | },
930 | "urn:aaa:x:y:",
931 | "urn:aaa:x:y:",
932 | "",
933 | false,
934 | },
935 |
936 | // ok - SS can contain (and also start with) some non-alphabetical (ie., OTHER) characters
937 | {
938 | []byte("urn:ciao:-"),
939 | true,
940 | &URN{
941 | prefix: "urn",
942 | ID: "ciao",
943 | SS: "-",
944 | },
945 | "urn:ciao:-",
946 | "urn:ciao:-",
947 | "",
948 | false,
949 | },
950 | {
951 | []byte("urn:ciao:("),
952 | true,
953 | &URN{
954 | prefix: "urn",
955 | ID: "ciao",
956 | SS: "(",
957 | },
958 | "urn:ciao:(",
959 | "urn:ciao:(",
960 | "",
961 | false,
962 | },
963 | {
964 | []byte("urn:ciao:)"),
965 | true,
966 | &URN{
967 | prefix: "urn",
968 | ID: "ciao",
969 | SS: ")",
970 | },
971 | "urn:ciao:)",
972 | "urn:ciao:)",
973 | "",
974 | false,
975 | },
976 | {
977 | []byte("urn:ciao:+"),
978 | true,
979 | &URN{
980 | prefix: "urn",
981 | ID: "ciao",
982 | SS: "+",
983 | },
984 | "urn:ciao:+",
985 | "urn:ciao:+",
986 | "",
987 | false,
988 | },
989 | {
990 | []byte("urn:ciao::"),
991 | true,
992 | &URN{
993 | prefix: "urn",
994 | ID: "ciao",
995 | SS: ":",
996 | },
997 | "urn:ciao::",
998 | "urn:ciao::",
999 | "",
1000 | false,
1001 | },
1002 | {
1003 | []byte("urn:colon:::::nss"),
1004 | true,
1005 | &URN{
1006 | prefix: "urn",
1007 | ID: "colon",
1008 | SS: "::::nss",
1009 | },
1010 | "urn:colon:::::nss",
1011 | "urn:colon:::::nss",
1012 | "",
1013 | false,
1014 | },
1015 | {
1016 | []byte("urn:ciao:!"),
1017 | true,
1018 | &URN{
1019 | prefix: "urn",
1020 | ID: "ciao",
1021 | SS: "!",
1022 | },
1023 | "urn:ciao:!",
1024 | "urn:ciao:!",
1025 | "",
1026 | false,
1027 | },
1028 | {
1029 | []byte("urn:ciao:!!*"),
1030 | true,
1031 | &URN{
1032 | prefix: "urn",
1033 | ID: "ciao",
1034 | SS: "!!*",
1035 | },
1036 | "urn:ciao:!!*",
1037 | "urn:ciao:!!*",
1038 | "",
1039 | false,
1040 | },
1041 | {
1042 | []byte("urn:ciao:-!:-,:x"),
1043 | true,
1044 | &URN{
1045 | prefix: "urn",
1046 | ID: "ciao",
1047 | SS: "-!:-,:x",
1048 | },
1049 | "urn:ciao:-!:-,:x",
1050 | "urn:ciao:-!:-,:x",
1051 | "",
1052 | false,
1053 | },
1054 | {
1055 | []byte("urn:ciao:=@"),
1056 | true,
1057 | &URN{
1058 | prefix: "urn",
1059 | ID: "ciao",
1060 | SS: "=@",
1061 | },
1062 | "urn:ciao:=@",
1063 | "urn:ciao:=@",
1064 | "",
1065 | false,
1066 | },
1067 | {
1068 | []byte("urn:ciao:@!=%2C(xyz)+a,b.*@g=$_'"),
1069 | true,
1070 | &URN{
1071 | prefix: "urn",
1072 | ID: "ciao",
1073 | SS: "@!=%2C(xyz)+a,b.*@g=$_'",
1074 | },
1075 | "urn:ciao:@!=%2C(xyz)+a,b.*@g=$_'",
1076 | "urn:ciao:@!=%2c(xyz)+a,b.*@g=$_'",
1077 | "",
1078 | false,
1079 | },
1080 |
1081 | // ok - SS can contain (and also start with) hexadecimal representation of octets
1082 | {
1083 | []byte("URN:hexes:%25"),
1084 | true,
1085 | &URN{
1086 | prefix: "URN",
1087 | ID: "hexes",
1088 | SS: "%25",
1089 | },
1090 | "URN:hexes:%25",
1091 | "urn:hexes:%25",
1092 | "",
1093 | false,
1094 | }, // Literal use of the "%" character in a namespace must be encoded using "%25"
1095 | {
1096 | []byte("URN:x:abc%1Dz%2F%3az"),
1097 | true,
1098 | &URN{
1099 | prefix: "URN",
1100 | ID: "x",
1101 | SS: "abc%1Dz%2F%3az",
1102 | },
1103 | "URN:x:abc%1Dz%2F%3az",
1104 | "urn:x:abc%1dz%2f%3az",
1105 | "",
1106 | false,
1107 | }, // Literal use of the "%" character in a namespace must be encoded using "%25"
1108 |
1109 | // no - ID can not start with an hyphen
1110 | {
1111 | []byte("URN:-xxx:x"),
1112 | false,
1113 | nil,
1114 | "",
1115 | "",
1116 | `expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its beginning) [col 4]`,
1117 | false,
1118 | },
1119 | {
1120 | []byte("URN:---xxx:x"),
1121 | false,
1122 | nil,
1123 | "",
1124 | "",
1125 | `expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its beginning) [col 4]`,
1126 | false,
1127 | },
1128 |
1129 | // no - ID can not start with a colon
1130 | {
1131 | []byte("urn::colon:nss"),
1132 | false,
1133 | nil,
1134 | "",
1135 | "",
1136 | `expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its beginning) [col 4]`,
1137 | false,
1138 | },
1139 | {
1140 | []byte("urn::::nss"),
1141 | false,
1142 | nil,
1143 | "",
1144 | "",
1145 | `expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its beginning) [col 4]`,
1146 | false,
1147 | },
1148 |
1149 | // no - ID can not contains more than 32 characters
1150 | {
1151 | []byte("urn:abcdefghilmnopqrstuvzabcdefghilmn:specificstring"),
1152 | false,
1153 | nil,
1154 | "",
1155 | "",
1156 | `expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its beginning) [col 36]`,
1157 | false,
1158 | },
1159 |
1160 | // no - ID can not contain special characters
1161 | {
1162 | []byte("URN:a!?:x"),
1163 | false,
1164 | nil,
1165 | "",
1166 | "",
1167 | `expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its beginning) [col 5]`,
1168 | false,
1169 | },
1170 | {
1171 | []byte("URN:@,:x"),
1172 | false,
1173 | nil,
1174 | "",
1175 | "",
1176 | `expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its beginning) [col 4]`,
1177 | false,
1178 | },
1179 | {
1180 | []byte("URN:#,:x"),
1181 | false,
1182 | nil,
1183 | "",
1184 | "",
1185 | `expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its beginning) [col 4]`,
1186 | false,
1187 | },
1188 | {
1189 | []byte("URN:bc'.@:x"),
1190 | false,
1191 | nil,
1192 | "",
1193 | "",
1194 | `expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its beginning) [col 6]`,
1195 | false,
1196 | },
1197 |
1198 | // no - ID can not be equal to "urn"
1199 | {
1200 | []byte("urn:urn:NSS"),
1201 | false,
1202 | nil,
1203 | "",
1204 | "",
1205 | `expecting the identifier to not contain the "urn" reserved string [col 7]`,
1206 | false,
1207 | },
1208 | {
1209 | []byte("urn:URN:NSS"),
1210 | false,
1211 | nil,
1212 | "",
1213 | "",
1214 | `expecting the identifier to not contain the "urn" reserved string [col 7]`,
1215 | false,
1216 | },
1217 | {
1218 | []byte("URN:URN:NSS"),
1219 | false,
1220 | nil,
1221 | "",
1222 | "",
1223 | `expecting the identifier to not contain the "urn" reserved string [col 7]`,
1224 | false,
1225 | },
1226 | {
1227 | []byte("urn:UrN:NSS"),
1228 | false,
1229 | nil,
1230 | "",
1231 | "",
1232 | `expecting the identifier to not contain the "urn" reserved string [col 7]`,
1233 | false,
1234 | },
1235 | {
1236 | []byte("urn:Urn:NSS"),
1237 | false,
1238 | nil,
1239 | "",
1240 | "",
1241 | `expecting the identifier to not contain the "urn" reserved string [col 7]`,
1242 | false,
1243 | },
1244 |
1245 | // no - ID can not contain spaces
1246 | {
1247 | []byte("urn:white space:NSS"),
1248 | false,
1249 | nil,
1250 | "",
1251 | "",
1252 | `expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its beginning) [col 9]`,
1253 | false,
1254 | },
1255 |
1256 | // no - SS can not contain spaces
1257 | {
1258 | []byte("urn:concat:no spaces"),
1259 | false,
1260 | nil,
1261 | "",
1262 | "",
1263 | `expecting the specific string to be a string containing alnum, hex, or others ([()+,-.:=@;$_!*']) chars [col 13]`,
1264 | false,
1265 | },
1266 |
1267 | // no - SS can not contain reserved characters (can accept them only if %-escaped)
1268 | {
1269 | []byte("urn:a:%"), // the presence of an "%" character in an URN MUST be followed by two characters from the character set
1270 | false,
1271 | nil,
1272 | "",
1273 | "",
1274 | fmt.Sprintf(errHex, 7),
1275 | false,
1276 | },
1277 | {
1278 | []byte("urn:a:%A"), // the presence of an "%" character in an URN MUST be followed by two characters from the character set
1279 | false,
1280 | nil,
1281 | "",
1282 | "",
1283 | fmt.Sprintf(errHex, 8),
1284 | false,
1285 | },
1286 | {
1287 | []byte("urn:a:?"),
1288 | false,
1289 | nil,
1290 | "",
1291 | "",
1292 | `expecting the specific string to be a string containing alnum, hex, or others ([()+,-.:=@;$_!*']) chars [col 6]`,
1293 | false,
1294 | },
1295 | {
1296 | []byte("urn:a:#"),
1297 | false,
1298 | nil,
1299 | "",
1300 | "",
1301 | `expecting the specific string to be a string containing alnum, hex, or others ([()+,-.:=@;$_!*']) chars [col 6]`,
1302 | false,
1303 | },
1304 | {
1305 | []byte("urn:a:/"),
1306 | false,
1307 | nil,
1308 | "",
1309 | "",
1310 | `expecting the specific string to be a string containing alnum, hex, or others ([()+,-.:=@;$_!*']) chars [col 6]`,
1311 | false,
1312 | },
1313 | {
1314 | []byte("urn:ietf:params:scim:api:messages:%"),
1315 | false,
1316 | nil,
1317 | "",
1318 | "",
1319 | fmt.Sprintf(errHex, 35),
1320 | false,
1321 | },
1322 | {
1323 | []byte("urn:ietf:params:scim:api:messages:%F"),
1324 | false,
1325 | nil,
1326 | "",
1327 | "",
1328 | fmt.Sprintf(errHex, 36),
1329 | false,
1330 | },
1331 | {
1332 | []byte("arn:ietf:params:scim:schemas:core"),
1333 | false,
1334 | nil,
1335 | "",
1336 | "",
1337 | fmt.Sprintf(errPrefix, 0),
1338 | false,
1339 | },
1340 | {
1341 | []byte("usn:ietf:params:scim:schemas:core"),
1342 | false,
1343 | nil,
1344 | "",
1345 | "",
1346 | fmt.Sprintf(errPrefix, 1),
1347 | false,
1348 | },
1349 | {
1350 | []byte("urm:ietf:params:scim:schemas:core"),
1351 | false,
1352 | nil,
1353 | "",
1354 | "",
1355 | fmt.Sprintf(errPrefix, 2),
1356 | false,
1357 | },
1358 | {
1359 | []byte("urno:ietf:params:scim:schemas:core"),
1360 | false,
1361 | nil,
1362 | "",
1363 | "",
1364 | fmt.Sprintf(errPrefix, 3),
1365 | false,
1366 | },
1367 | {
1368 | []byte("urno"),
1369 | false,
1370 | nil,
1371 | "",
1372 | "",
1373 | fmt.Sprintf(errPrefix, 3),
1374 | false,
1375 | },
1376 | {
1377 | []byte("URN:a!?:x"),
1378 | false,
1379 | nil,
1380 | "",
1381 | "",
1382 | `expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its beginning) [col 5]`,
1383 | false,
1384 | },
1385 | {
1386 | []byte("urn:Urn:NSS"),
1387 | false,
1388 | nil,
1389 | "",
1390 | "",
1391 | `expecting the identifier to not contain the "urn" reserved string [col 7]`,
1392 | false,
1393 | },
1394 | {
1395 | []byte("urn:spazio bianco:NSS"),
1396 | false,
1397 | nil,
1398 | "",
1399 | "",
1400 | `expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its beginning) [col 10]`,
1401 | false,
1402 | },
1403 | {
1404 | []byte("urn:conca:z ws"),
1405 | false,
1406 | nil,
1407 | "",
1408 | "",
1409 | fmt.Sprintf(errSpecificString, 11),
1410 | false,
1411 | },
1412 | {
1413 | []byte("urn:ietf:params:scim:schemas:core:2.&"),
1414 | false,
1415 | nil,
1416 | "",
1417 | "",
1418 | fmt.Sprintf(errSpecificString, 36),
1419 | false,
1420 | },
1421 |
1422 | // no - Incomplete URNs
1423 | {
1424 | []byte("urn:"),
1425 | false,
1426 | nil,
1427 | "",
1428 | "",
1429 | `expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its beginning) [col 4]`,
1430 | false,
1431 | },
1432 | {
1433 | []byte("urn::"),
1434 | false,
1435 | nil,
1436 | "",
1437 | "",
1438 | `expecting the identifier to be string (1..31 alnum chars, also containing dashes but not at its beginning) [col 4]`,
1439 | false,
1440 | },
1441 | {
1442 | []byte("urn:a:"),
1443 | false,
1444 | nil,
1445 | "",
1446 | "",
1447 | `expecting the specific string to be a string containing alnum, hex, or others ([()+,-.:=@;$_!*']) chars [col 6]`,
1448 | false,
1449 | },
1450 | {
1451 | []byte("urn:a"),
1452 | false,
1453 | nil,
1454 | "",
1455 | "",
1456 | fmt.Sprintf(errIdentifier, 5),
1457 | false,
1458 | },
1459 | // no - Incomplete prefix
1460 | {
1461 | []byte(`u`),
1462 | false,
1463 | nil,
1464 | "",
1465 | "",
1466 | fmt.Sprintf(errPrefix, 1),
1467 | false,
1468 | },
1469 | {
1470 | []byte(`ur`),
1471 | false,
1472 | nil,
1473 | "",
1474 | "",
1475 | fmt.Sprintf(errPrefix, 2),
1476 | false,
1477 | },
1478 | {
1479 | []byte(`urn`),
1480 | false,
1481 | nil,
1482 | "",
1483 | "",
1484 | fmt.Sprintf(errPrefix, 3),
1485 | false,
1486 | },
1487 |
1488 | // no, empty
1489 | {
1490 | []byte(""),
1491 | false,
1492 | nil,
1493 | "",
1494 | "",
1495 | fmt.Sprintf(errPrefix, 0),
1496 | false,
1497 | },
1498 | }
1499 |
1500 | var rfc8141TestCases = []testCase{
1501 | // ok
1502 | {
1503 | []byte("urn:lex:it:ministero.giustizia:decreto:1992-07-24;358~art5"), // Italian decree
1504 | true,
1505 | &URN{
1506 | prefix: "urn",
1507 | ID: "lex",
1508 | SS: "it:ministero.giustizia:decreto:1992-07-24;358~art5",
1509 | },
1510 | "urn:lex:it:ministero.giustizia:decreto:1992-07-24;358~art5",
1511 | "urn:lex:it:ministero.giustizia:decreto:1992-07-24;358~art5",
1512 | "",
1513 | false,
1514 | }, // ~ allowed in the NSS
1515 | {
1516 | []byte("urn:nid:nss/"),
1517 | true,
1518 | &URN{
1519 | prefix: "urn",
1520 | ID: "nid",
1521 | SS: "nss/",
1522 | },
1523 | "urn:nid:nss/",
1524 | "urn:nid:nss/",
1525 | "",
1526 | false,
1527 | }, // / allowed in the NSS
1528 | {
1529 | []byte("urn:nid:nss&"),
1530 | true,
1531 | &URN{
1532 | prefix: "urn",
1533 | ID: "nid",
1534 | SS: "nss&",
1535 | },
1536 | "urn:nid:nss&",
1537 | "urn:nid:nss&",
1538 | "",
1539 | false,
1540 | }, // & allowed in the NSS
1541 | {
1542 | []byte("urn:example:1/406/47452/2"),
1543 | true,
1544 | &URN{
1545 | prefix: "urn",
1546 | ID: "example",
1547 | SS: "1/406/47452/2",
1548 | },
1549 | "urn:example:1/406/47452/2",
1550 | "urn:example:1/406/47452/2",
1551 | "",
1552 | false,
1553 | }, // & allowed in the NSS
1554 | {
1555 | []byte("urn:example:foo-bar-baz-qux?+CCResolve:cc=uk"),
1556 | true,
1557 | &URN{
1558 | prefix: "urn",
1559 | ID: "example",
1560 | SS: "foo-bar-baz-qux",
1561 | rComponent: "CCResolve:cc=uk",
1562 | },
1563 | "urn:example:foo-bar-baz-qux?+CCResolve:cc=uk",
1564 | "urn:example:foo-bar-baz-qux",
1565 | "",
1566 | false,
1567 | },
1568 | {
1569 | []byte("urn:example:foo-bar-baz-qux?+&"),
1570 | true,
1571 | &URN{
1572 | prefix: "urn",
1573 | ID: "example",
1574 | SS: "foo-bar-baz-qux",
1575 | rComponent: "&",
1576 | },
1577 | "urn:example:foo-bar-baz-qux?+&",
1578 | "urn:example:foo-bar-baz-qux",
1579 | "",
1580 | false,
1581 | },
1582 | {
1583 | []byte("urn:example:foo-bar-baz-qux?+~"),
1584 | true,
1585 | &URN{
1586 | prefix: "urn",
1587 | ID: "example",
1588 | SS: "foo-bar-baz-qux",
1589 | rComponent: "~",
1590 | },
1591 | "urn:example:foo-bar-baz-qux?+~",
1592 | "urn:example:foo-bar-baz-qux",
1593 | "",
1594 | false,
1595 | },
1596 | {
1597 | []byte("urn:example:foo-bar-baz-qux?+%16CCResolve:cc=uk"),
1598 | true,
1599 | &URN{
1600 | prefix: "urn",
1601 | ID: "example",
1602 | SS: "foo-bar-baz-qux",
1603 | rComponent: "%16CCResolve:cc=uk",
1604 | },
1605 | "urn:example:foo-bar-baz-qux?+%16CCResolve:cc=uk",
1606 | "urn:example:foo-bar-baz-qux",
1607 | "",
1608 | false,
1609 | },
1610 | {
1611 | []byte("urn:example:foo-bar-baz-qut?+~&%FF()+,-.:=@;$_!/?Alnum123456"),
1612 | true,
1613 | &URN{
1614 | prefix: "urn",
1615 | ID: "example",
1616 | SS: "foo-bar-baz-qut",
1617 | rComponent: "~&%FF()+,-.:=@;$_!/?Alnum123456",
1618 | },
1619 | "urn:example:foo-bar-baz-qut?+~&%FF()+,-.:=@;$_!/?Alnum123456",
1620 | "urn:example:foo-bar-baz-qut",
1621 | "",
1622 | false,
1623 | },
1624 | {
1625 | []byte("urn:example:weather?=op=map&lat=39.56&lon=-104.85&datetime=1969-07-21T02:56:15Z"),
1626 | true,
1627 | &URN{
1628 | prefix: "urn",
1629 | ID: "example",
1630 | SS: "weather",
1631 | qComponent: "op=map&lat=39.56&lon=-104.85&datetime=1969-07-21T02:56:15Z",
1632 | },
1633 | "urn:example:weather?=op=map&lat=39.56&lon=-104.85&datetime=1969-07-21T02:56:15Z",
1634 | "urn:example:weather",
1635 | "",
1636 | false,
1637 | },
1638 | {
1639 | []byte("urn:esempio:climate?=alnum~&%FF()+,-.:=@;$_!/?123456"),
1640 | true,
1641 | &URN{
1642 | prefix: "urn",
1643 | ID: "esempio",
1644 | SS: "climate",
1645 | qComponent: "alnum~&%FF()+,-.:=@;$_!/?123456",
1646 | },
1647 | "urn:esempio:climate?=alnum~&%FF()+,-.:=@;$_!/?123456",
1648 | "urn:esempio:climate",
1649 | "",
1650 | false,
1651 | },
1652 | {
1653 | []byte("urn:esempio:climate?=&&"),
1654 | true,
1655 | &URN{
1656 | prefix: "urn",
1657 | ID: "esempio",
1658 | SS: "climate",
1659 | qComponent: "&&",
1660 | },
1661 | "urn:esempio:climate?=&&",
1662 | "urn:esempio:climate",
1663 | "",
1664 | false,
1665 | },
1666 | {
1667 | []byte("urn:esempio:climate?=%A1alnum~&%FF()+,-.:=@;$_!/?123456"),
1668 | true,
1669 | &URN{
1670 | prefix: "urn",
1671 | ID: "esempio",
1672 | SS: "climate",
1673 | qComponent: "%A1alnum~&%FF()+,-.:=@;$_!/?123456",
1674 | },
1675 | "urn:esempio:climate?=%A1alnum~&%FF()+,-.:=@;$_!/?123456",
1676 | "urn:esempio:climate",
1677 | "",
1678 | false,
1679 | },
1680 | {
1681 | []byte("urn:example:foo-bar-baz-qux#somepart"),
1682 | true,
1683 | &URN{
1684 | prefix: "urn",
1685 | ID: "example",
1686 | SS: "foo-bar-baz-qux",
1687 | fComponent: "somepart",
1688 | },
1689 | "urn:example:foo-bar-baz-qux#somepart",
1690 | "urn:example:foo-bar-baz-qux",
1691 | "",
1692 | false,
1693 | },
1694 | {
1695 | []byte("urn:example:foo-bar-baz-qux#~&"),
1696 | true,
1697 | &URN{
1698 | prefix: "urn",
1699 | ID: "example",
1700 | SS: "foo-bar-baz-qux",
1701 | fComponent: "~&",
1702 | },
1703 | "urn:example:foo-bar-baz-qux#~&",
1704 | "urn:example:foo-bar-baz-qux",
1705 | "",
1706 | false,
1707 | },
1708 | {
1709 | []byte("urn:example:foo-bar-baz-qux#alnum~&%FF()+,-.:=@;$_!/?123456"),
1710 | true,
1711 | &URN{
1712 | prefix: "urn",
1713 | ID: "example",
1714 | SS: "foo-bar-baz-qux",
1715 | fComponent: "alnum~&%FF()+,-.:=@;$_!/?123456",
1716 | },
1717 | "urn:example:foo-bar-baz-qux#alnum~&%FF()+,-.:=@;$_!/?123456",
1718 | "urn:example:foo-bar-baz-qux",
1719 | "",
1720 | false,
1721 | },
1722 | {
1723 | []byte("urn:example:foo-bar-baz-qux#%D0alnum~&%FF()+,-.:=@;$_!/?123456"),
1724 | true,
1725 | &URN{
1726 | prefix: "urn",
1727 | ID: "example",
1728 | SS: "foo-bar-baz-qux",
1729 | fComponent: "%D0alnum~&%FF()+,-.:=@;$_!/?123456",
1730 | },
1731 | "urn:example:foo-bar-baz-qux#%D0alnum~&%FF()+,-.:=@;$_!/?123456",
1732 | "urn:example:foo-bar-baz-qux",
1733 | "",
1734 | false,
1735 | },
1736 | {
1737 | []byte("urn:example:CamelCase1/406/47452/2?+Cc=it&prefix=39?=lat=41.22255&long=16.06596#frag"),
1738 | true,
1739 | &URN{
1740 | prefix: "urn",
1741 | ID: "example",
1742 | SS: "CamelCase1/406/47452/2",
1743 | rComponent: "Cc=it&prefix=39",
1744 | qComponent: "lat=41.22255&long=16.06596",
1745 | fComponent: "frag",
1746 | },
1747 | "urn:example:CamelCase1/406/47452/2?+Cc=it&prefix=39?=lat=41.22255&long=16.06596#frag",
1748 | "urn:example:CamelCase1/406/47452/2",
1749 | "",
1750 | false,
1751 | }, // r_component, q_component, f_component
1752 | {
1753 | []byte("urn:example:CamelCase1/406/47452/2?=lat=41.22255&long=16.06596#frag"),
1754 | true,
1755 | &URN{
1756 | prefix: "urn",
1757 | ID: "example",
1758 | SS: "CamelCase1/406/47452/2",
1759 | qComponent: "lat=41.22255&long=16.06596",
1760 | fComponent: "frag",
1761 | },
1762 | "urn:example:CamelCase1/406/47452/2?=lat=41.22255&long=16.06596#frag",
1763 | "urn:example:CamelCase1/406/47452/2",
1764 | "",
1765 | false,
1766 | }, // q_component, f_component
1767 | {
1768 | []byte("urn:example:CamelCase1/406/47452/2?=lat=41.22255&long=16.06596#frag?some/slash/~%D0"),
1769 | true,
1770 | &URN{
1771 | prefix: "urn",
1772 | ID: "example",
1773 | SS: "CamelCase1/406/47452/2",
1774 | qComponent: "lat=41.22255&long=16.06596",
1775 | fComponent: "frag?some/slash/~%D0",
1776 | },
1777 | "urn:example:CamelCase1/406/47452/2?=lat=41.22255&long=16.06596#frag?some/slash/~%D0",
1778 | "urn:example:CamelCase1/406/47452/2",
1779 | "",
1780 | false,
1781 | }, // q_component, f_component
1782 | {
1783 | []byte("urn:example:CamelCase1/406/47452/2?+Cc=it&prefix=39?=lat=41.22255&long=16.06596"),
1784 | true,
1785 | &URN{
1786 | prefix: "urn",
1787 | ID: "example",
1788 | SS: "CamelCase1/406/47452/2",
1789 | rComponent: "Cc=it&prefix=39",
1790 | qComponent: "lat=41.22255&long=16.06596",
1791 | },
1792 | "urn:example:CamelCase1/406/47452/2?+Cc=it&prefix=39?=lat=41.22255&long=16.06596",
1793 | "urn:example:CamelCase1/406/47452/2",
1794 | "",
1795 | false,
1796 | }, // r_component, q_component
1797 | {
1798 | []byte("urn:TESTt3st:&/987/QWERTYUIOP/0#"),
1799 | true,
1800 | &URN{
1801 | prefix: "urn",
1802 | ID: "TESTt3st",
1803 | SS: "&/987/QWERTYUIOP/0",
1804 | fComponent: "",
1805 | },
1806 | "urn:TESTt3st:&/987/QWERTYUIOP/0",
1807 | "urn:testt3st:&/987/QWERTYUIOP/0",
1808 | "",
1809 | false,
1810 | }, // empty fragment
1811 | {
1812 | []byte("urn:example:%D0%B0123,z456"),
1813 | true,
1814 | &URN{
1815 | prefix: "urn",
1816 | ID: "example",
1817 | SS: "%D0%B0123,z456",
1818 | },
1819 | "urn:example:%D0%B0123,z456",
1820 | "urn:example:%d0%b0123,z456",
1821 | "",
1822 | false,
1823 | },
1824 | {
1825 | []byte("urn:example:apple:pear:plum:cherry"),
1826 | true,
1827 | &URN{
1828 | prefix: "urn",
1829 | ID: "example",
1830 | SS: "apple:pear:plum:cherry",
1831 | },
1832 | "urn:example:apple:pear:plum:cherry",
1833 | "urn:example:apple:pear:plum:cherry",
1834 | "",
1835 | false,
1836 | },
1837 | {
1838 | []byte("urn:z------------------------------a:q"),
1839 | true,
1840 | &URN{
1841 | prefix: "urn",
1842 | ID: "z------------------------------a",
1843 | SS: "q",
1844 | },
1845 | "urn:z------------------------------a:q",
1846 | "urn:z------------------------------a:q",
1847 | "",
1848 | false,
1849 | },
1850 | {
1851 | []byte("urn:10:2"),
1852 | true,
1853 | &URN{
1854 | prefix: "urn",
1855 | ID: "10",
1856 | SS: "2",
1857 | },
1858 | "urn:10:2",
1859 | "urn:10:2",
1860 | "",
1861 | false,
1862 | },
1863 | {
1864 | []byte("urn:a1l2n3m4-56789aeiou:2"),
1865 | true,
1866 | &URN{
1867 | prefix: "urn",
1868 | ID: "a1l2n3m4-56789aeiou",
1869 | SS: "2",
1870 | },
1871 | "urn:a1l2n3m4-56789aeiou:2",
1872 | "urn:a1l2n3m4-56789aeiou:2",
1873 | "",
1874 | false,
1875 | },
1876 | {
1877 | []byte("urn:a1l2n3m4-56789aeiou:2%D0%B0"),
1878 | true,
1879 | &URN{
1880 | prefix: "urn",
1881 | ID: "a1l2n3m4-56789aeiou",
1882 | SS: "2%D0%B0",
1883 | },
1884 | "urn:a1l2n3m4-56789aeiou:2%D0%B0",
1885 | "urn:a1l2n3m4-56789aeiou:2%d0%b0",
1886 | "",
1887 | false,
1888 | },
1889 | {
1890 | []byte("urn:amp:&"),
1891 | true,
1892 | &URN{
1893 | prefix: "urn",
1894 | ID: "amp",
1895 | SS: "&",
1896 | },
1897 | "urn:amp:&",
1898 | "urn:amp:&",
1899 | "",
1900 | false,
1901 | },
1902 | {
1903 | []byte("urn:tilde:~~~"),
1904 | true,
1905 | &URN{
1906 | prefix: "urn",
1907 | ID: "tilde",
1908 | SS: "~~~",
1909 | },
1910 | "urn:tilde:~~~",
1911 | "urn:tilde:~~~",
1912 | "",
1913 | false,
1914 | },
1915 | {
1916 | []byte("urn:signs:()+,-.:=@;$_!*alnum123456789"),
1917 | true,
1918 | &URN{
1919 | prefix: "urn",
1920 | ID: "signs",
1921 | SS: "()+,-.:=@;$_!*alnum123456789",
1922 | },
1923 | "urn:signs:()+,-.:=@;$_!*alnum123456789",
1924 | "urn:signs:()+,-.:=@;$_!*alnum123456789",
1925 | "",
1926 | false,
1927 | },
1928 | {
1929 | []byte("URN:signs:()+,-.:=@;$_!*alnum123456789"),
1930 | true,
1931 | &URN{
1932 | prefix: "URN",
1933 | ID: "signs",
1934 | SS: "()+,-.:=@;$_!*alnum123456789",
1935 | },
1936 | "URN:signs:()+,-.:=@;$_!*alnum123456789",
1937 | "urn:signs:()+,-.:=@;$_!*alnum123456789",
1938 | "",
1939 | false,
1940 | },
1941 | {
1942 | []byte("urn:urn-7:informal"),
1943 | true,
1944 | &URN{
1945 | prefix: "urn",
1946 | ID: "urn-7",
1947 | SS: "informal",
1948 | },
1949 | "urn:urn-7:informal",
1950 | "urn:urn-7:informal",
1951 | "",
1952 | false,
1953 | }, // NID containing informal URN namespace
1954 | {
1955 | []byte("urn:ex:ex?+a?"),
1956 | true,
1957 | &URN{
1958 | prefix: "urn",
1959 | ID: "ex",
1960 | SS: "ex",
1961 | rComponent: "a?",
1962 | },
1963 | "urn:ex:ex?+a?",
1964 | "urn:ex:ex",
1965 | "",
1966 | false,
1967 | }, // r_component containing ? not immediately followed by + or =
1968 |
1969 | // no
1970 | {
1971 | []byte("urn:urn-0:nss"),
1972 | false,
1973 | nil,
1974 | "",
1975 | "",
1976 | fmt.Sprintf(err8141InformalID, 7),
1977 | false,
1978 | }, // NID must not start the a wrong informal URN namespace
1979 | {
1980 | []byte("urn:urn-s:nss"),
1981 | false,
1982 | nil,
1983 | "",
1984 | "",
1985 | fmt.Sprintf(err8141InformalID, 7),
1986 | false,
1987 | }, // NID must not start the "urn-" prefix followed by a string
1988 | {
1989 | []byte("urn:example:а123,z456"),
1990 | false,
1991 | nil,
1992 | "",
1993 | "",
1994 | fmt.Sprintf(err8141SpecificString, 12),
1995 | false,
1996 | }, // CYRILLIC а (U+0430)
1997 | {
1998 | []byte("URN:-leading:w"),
1999 | false,
2000 | nil,
2001 | "",
2002 | "",
2003 | fmt.Sprintf(err8141Identifier, 4),
2004 | false,
2005 | }, // leading - not allowed in the NID
2006 | {
2007 | []byte("URN:trailing-:w"),
2008 | false,
2009 | nil,
2010 | "",
2011 | "",
2012 | fmt.Sprintf(err8141Identifier, 13),
2013 | false,
2014 | }, // trailing - not allowed in the NID
2015 | {
2016 | []byte("urn:a:nss"),
2017 | false,
2018 | nil,
2019 | "",
2020 | "",
2021 | fmt.Sprintf(err8141Identifier, 5),
2022 | false,
2023 | }, // NID at least 2 characters
2024 | {
2025 | []byte("urn:1:nss"),
2026 | false,
2027 | nil,
2028 | "",
2029 | "",
2030 | fmt.Sprintf(err8141Identifier, 5),
2031 | false,
2032 | }, // NID at least 2 characters
2033 | {
2034 | []byte("urn:yz-:nss"),
2035 | false,
2036 | nil,
2037 | "",
2038 | "",
2039 | fmt.Sprintf(err8141Identifier, 7),
2040 | false,
2041 | }, // NID must not start with 2 characters followerd by a dash
2042 | {
2043 | []byte("urn:9x-:nss"),
2044 | false,
2045 | nil,
2046 | "",
2047 | "",
2048 | fmt.Sprintf(err8141Identifier, 7),
2049 | false,
2050 | }, // NID must not start with 2 characters followerd by a dash
2051 | {
2052 | []byte("urn:X-:nss"),
2053 | false,
2054 | nil,
2055 | "",
2056 | "",
2057 | fmt.Sprintf(err8141Identifier, 6),
2058 | false,
2059 | }, // NID must not start with experimental URN namespace of RFC3406
2060 | {
2061 | []byte("urn:xn--:nss"),
2062 | false,
2063 | nil,
2064 | "",
2065 | "",
2066 | fmt.Sprintf(err8141Identifier, 8),
2067 | false,
2068 | }, // NID must not start with "xn" followed by 2 dashes
2069 | {
2070 | []byte("urn:ss--:nss"),
2071 | false,
2072 | nil,
2073 | "",
2074 | "",
2075 | fmt.Sprintf(err8141Identifier, 8),
2076 | false,
2077 | }, // NID must not start with 2 chars followed by 2 dashes
2078 | {
2079 | []byte("urn:1E--:nss"),
2080 | false,
2081 | nil,
2082 | "",
2083 | "",
2084 | fmt.Sprintf(err8141Identifier, 8),
2085 | false,
2086 | }, // NID must not start with 2 chars followed by 2 dashes
2087 | {
2088 | []byte("urn:ex:ex?+a?+"),
2089 | false,
2090 | nil,
2091 | "",
2092 | "",
2093 | fmt.Sprintf(err8141RComponentStart, 14),
2094 | false,
2095 | }, // r_component containing ?+
2096 | {
2097 | []byte("urn:ex:ex?+"),
2098 | false,
2099 | nil,
2100 | "",
2101 | "",
2102 | fmt.Sprintf(err8141MalformedRComp, 11),
2103 | false,
2104 | }, // empty r_component
2105 | {
2106 | []byte("urn:ex:ex?=a?="),
2107 | false,
2108 | nil,
2109 | "",
2110 | "",
2111 | fmt.Sprintf(err8141QComponentStart, 14),
2112 | false,
2113 | }, // q_component containing ?=
2114 | {
2115 | []byte("urn:example:CamelCase1/406/47452/2?="),
2116 | false,
2117 | nil,
2118 | "",
2119 | "",
2120 | fmt.Sprintf(err8141MalformedQComp, 36),
2121 | false,
2122 | }, // empty q_component
2123 | {
2124 | []byte("urn:ex:ex?+rcomponent?+rcomponent?=qcomponent"),
2125 | false,
2126 | nil,
2127 | "",
2128 | "",
2129 | fmt.Sprintf(err8141RComponentStart, 23),
2130 | false,
2131 | }, // r_component containing ?+ followed by q_component
2132 | {
2133 | []byte("urn:ex:ex?+rcomponent?+rcomponent?="),
2134 | false,
2135 | nil,
2136 | "",
2137 | "",
2138 | fmt.Sprintf(err8141RComponentStart, 23),
2139 | false,
2140 | }, // r_component containing ?+ followed by empty q_component
2141 | {
2142 | []byte("urn:ex:ex?+rcomponent?=qcomponent?=q"),
2143 | false,
2144 | nil,
2145 | "",
2146 | "",
2147 | fmt.Sprintf(err8141QComponentStart, 35),
2148 | false,
2149 | }, // r_component followed by q_component containing ?+
2150 | {
2151 | []byte("urn:ex:ex?+?=q"),
2152 | false,
2153 | nil,
2154 | "",
2155 | "",
2156 | fmt.Sprintf(err8141MalformedRComp, 12),
2157 | false,
2158 | }, // empty r_component followed by q_component
2159 | {
2160 | []byte("urn:ex:ex?+/"),
2161 | false,
2162 | nil,
2163 | "",
2164 | "",
2165 | fmt.Sprintf(err8141MalformedRComp, 11),
2166 | false,
2167 | }, // r_component starting with /
2168 | {
2169 | []byte("urn:ex:ex?+?"),
2170 | false,
2171 | nil,
2172 | "",
2173 | "",
2174 | fmt.Sprintf(err8141MalformedRComp, 12),
2175 | false,
2176 | }, // r_component starting with /
2177 | {
2178 | []byte("urn:ex:ex?=/"),
2179 | false,
2180 | nil,
2181 | "",
2182 | "",
2183 | fmt.Sprintf(err8141MalformedQComp, 11),
2184 | false,
2185 | }, // q_component starting with /
2186 | {
2187 | []byte("urn:ex:ex?=?"),
2188 | false,
2189 | nil,
2190 | "",
2191 | "",
2192 | fmt.Sprintf(err8141MalformedQComp, 12),
2193 | false,
2194 | }, // q_component starting with /
2195 | {
2196 | []byte("urn:mm:/"),
2197 | false,
2198 | nil,
2199 | "",
2200 | "",
2201 | fmt.Sprintf(err8141SpecificString, 7),
2202 | false,
2203 | }, // / not in first position in the NSS part
2204 | {
2205 | []byte("urn:mm:?"),
2206 | false,
2207 | nil,
2208 | "",
2209 | "",
2210 | fmt.Sprintf(err8141SpecificString, 7),
2211 | false,
2212 | }, // ? not in first position in the NSS part
2213 | {
2214 | []byte("urn:123456789-1234567890-abcdefghilmn:o"),
2215 | false,
2216 | nil,
2217 | "",
2218 | "",
2219 | fmt.Sprintf(err8141Identifier, 36),
2220 | false,
2221 | }, // too long NID
2222 | {
2223 | []byte("urn:"),
2224 | false,
2225 | nil,
2226 | "",
2227 | "",
2228 | fmt.Sprintf(err8141Identifier, 4),
2229 | false,
2230 | },
2231 | {
2232 | []byte("urn::"),
2233 | false,
2234 | nil,
2235 | "",
2236 | "",
2237 | fmt.Sprintf(err8141Identifier, 4),
2238 | false,
2239 | },
2240 | {
2241 | []byte("urn:aa:"),
2242 | false,
2243 | nil,
2244 | "",
2245 | "",
2246 | fmt.Sprintf(err8141SpecificString, 7),
2247 | false,
2248 | },
2249 | {
2250 | []byte("urn:a"),
2251 | false,
2252 | nil,
2253 | "",
2254 | "",
2255 | fmt.Sprintf(err8141Identifier, 5),
2256 | false,
2257 | },
2258 | {
2259 | []byte("urn:ex:ex?=%"),
2260 | false,
2261 | nil,
2262 | "",
2263 | "",
2264 | fmt.Sprintf(errHex, 12),
2265 | false,
2266 | }, // q_component containing incomplete percent encoded chars
2267 | {
2268 | []byte("urn:ex:ex?+%"),
2269 | false,
2270 | nil,
2271 | "",
2272 | "",
2273 | fmt.Sprintf(errHex, 12),
2274 | false,
2275 | }, // r_component containing incomplete percent encoded chars
2276 | {
2277 | []byte("urn:ex:ex?=something#%"),
2278 | false,
2279 | nil,
2280 | "",
2281 | "",
2282 | fmt.Sprintf(errHex, 22),
2283 | false,
2284 | }, // fragment containing incomplete percent encoded chars
2285 | {
2286 | []byte("urn:example%:test"),
2287 | false,
2288 | nil,
2289 | "",
2290 | "",
2291 | fmt.Sprintf(err8141Identifier, 11),
2292 | false,
2293 | },
2294 | {
2295 | []byte(`urn:"`),
2296 | false,
2297 | nil,
2298 | "",
2299 | "",
2300 | fmt.Sprintf(err8141Identifier, 4),
2301 | false,
2302 | },
2303 | {
2304 | []byte(`urn:a1{}`),
2305 | false,
2306 | nil,
2307 | "",
2308 | "",
2309 | fmt.Sprintf(err8141Identifier, 6),
2310 | false,
2311 | },
2312 | {
2313 | []byte(`u`),
2314 | false,
2315 | nil,
2316 | "",
2317 | "",
2318 | fmt.Sprintf(errPrefix, 1),
2319 | false,
2320 | },
2321 | {
2322 | []byte(`ur`),
2323 | false,
2324 | nil,
2325 | "",
2326 | "",
2327 | fmt.Sprintf(errPrefix, 2),
2328 | false,
2329 | },
2330 | {
2331 | []byte(`urn`),
2332 | false,
2333 | nil,
2334 | "",
2335 | "",
2336 | fmt.Sprintf(errPrefix, 3),
2337 | false,
2338 | },
2339 | }
2340 |
--------------------------------------------------------------------------------
/tools/removecomments/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/leodido/go-urn/tools/removecomments
2 |
3 | go 1.16
4 |
--------------------------------------------------------------------------------
/tools/removecomments/go.sum:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leodido/go-urn/d725923fe33ce69c89b9e2033d069099b498224f/tools/removecomments/go.sum
--------------------------------------------------------------------------------
/tools/removecomments/removecomments.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "regexp"
7 | )
8 |
9 | // commentLineRegex matches a comment line and its newline characters. A
10 | // comment line, for the purposes of this function, begins with optional
11 | // whitespace followed by "//line" then any other text.
12 | //
13 | // optional newline character ---------------------------------+++
14 | // optional carriage return --------------------------------+++|||
15 | // end of line --------------------------------------------+||||||
16 | // wildcard ---------------------------------------------++|||||||
17 | // the literal word "line" --------------------------++++|||||||||
18 | // comment delimiters -----------------------------++|||||||||||||
19 | // one more more whitespace characters ---------+++|||||||||||||||
20 | // beginning of line --------------------------+||||||||||||||||||
21 | // allow multiline matching ---------------++++|||||||||||||||||||
22 | // |||||||||||||||||||||||
23 | var commentLineRegex = regexp.MustCompile(`(?m)^\s*//line.*$\r?\n?`)
24 |
25 | func removeComments(src []byte) []byte {
26 | return commentLineRegex.ReplaceAllLiteral(src, []byte(""))
27 | }
28 |
29 | func removeCommentsFromFile(path string) error {
30 | content, err := os.ReadFile(path)
31 | if err != nil {
32 | return err
33 | }
34 | updated := removeComments(content)
35 | return os.WriteFile(path, updated, 0)
36 | }
37 |
38 | func exitWithError(msg string) {
39 | err := fmt.Sprintf("error: %s\n", msg)
40 | fmt.Fprintln(os.Stderr, err)
41 | os.Exit(1)
42 | }
43 |
44 | func main() {
45 | if len(os.Args) != 2 {
46 | exitWithError("must be called with the file path as the only argument")
47 | }
48 | path := os.Args[1]
49 | if err := removeCommentsFromFile(path); err != nil {
50 | exitWithError(err.Error())
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/tools/removecomments/removecomments_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 | )
7 |
8 | func TestRemoveComments(t *testing.T) {
9 | txt := `//line this should be deleted
10 |
11 | This is a test file
12 |
13 | it would contain go code
14 |
15 | // and comments
16 | // and comments after spaces
17 | // and comments after tabs
18 | //line but not this comment
19 | //line or this one after spaces
20 | //line or this one after tabs
21 |
22 | and that is all
23 | //line this should be deleted`
24 |
25 | expected := `
26 | This is a test file
27 |
28 | it would contain go code
29 |
30 | // and comments
31 | // and comments after spaces
32 | // and comments after tabs
33 |
34 | and that is all
35 | `
36 |
37 | s := removeComments([]byte(txt))
38 | if string(s) != expected {
39 | fmt.Printf("expected length: %d, got %d\n", len(expected), len(s))
40 | t.Fatalf("expected:\n%s\ngot:\n%s", expected, s)
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/tools/snake2camel/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/leodido/go-urn/tools/snake2camel
2 |
3 | go 1.21.6
4 |
--------------------------------------------------------------------------------
/tools/snake2camel/go.sum:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leodido/go-urn/d725923fe33ce69c89b9e2033d069099b498224f/tools/snake2camel/go.sum
--------------------------------------------------------------------------------
/tools/snake2camel/snake2camel.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "regexp"
7 | "strings"
8 | )
9 |
10 | func exitWithError(msg string) {
11 | err := fmt.Sprintf("error: %s\n", msg)
12 | fmt.Fprintln(os.Stderr, err)
13 | os.Exit(1)
14 | }
15 |
16 | func replaceAllStringSubmatchFunc(re *regexp.Regexp, str string, repl func([]string) string) (string, int) {
17 | result := ""
18 | lastIndex := 0
19 |
20 | for _, v := range re.FindAllSubmatchIndex([]byte(str), -1) {
21 | groups := []string{}
22 | for i := 0; i < len(v); i += 2 {
23 | if v[i] == -1 || v[i+1] == -1 {
24 | groups = append(groups, "")
25 | } else {
26 | groups = append(groups, str[v[i]:v[i+1]])
27 | }
28 | }
29 |
30 | result += str[lastIndex:v[0]] + repl(groups)
31 | lastIndex = v[1]
32 | }
33 |
34 | return result + str[lastIndex:], lastIndex
35 | }
36 |
37 | func snake2camel(content []byte) []byte {
38 | re := regexp.MustCompile(`(.*)([a-z]+[0-9]*)_([a-zA-Z0-9])(.*)`)
39 | res := string(content)
40 | last := -1
41 |
42 | for last != 0 {
43 | res, last = replaceAllStringSubmatchFunc(re, res, func(groups []string) string {
44 | return groups[1] + groups[2] + strings.ToUpper(groups[3]) + groups[4]
45 | })
46 | }
47 |
48 | return []byte(res)
49 | }
50 |
51 | func snake2camelFile(path string) error {
52 | content, err := os.ReadFile(path)
53 | if err != nil {
54 | return err
55 | }
56 | updated := snake2camel(content)
57 |
58 | return os.WriteFile(path, updated, 0)
59 | }
60 |
61 | func main() {
62 | if len(os.Args) != 2 {
63 | exitWithError("must be called with the file path as the only argument")
64 | }
65 | path := os.Args[1]
66 | if err := snake2camelFile(path); err != nil {
67 | exitWithError(err.Error())
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/tools/snake2camel/snake2camel_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 | )
7 |
8 | func TestSnake2CamelNoReplace(t *testing.T) {
9 | txt := `const start int = 1`
10 |
11 | exp := `const start int = 1`
12 |
13 | got := snake2camel([]byte(txt))
14 | if string(got) != exp {
15 | fmt.Printf("expected length: %d, got %d\n", len(exp), len(got))
16 | t.Fatalf("expected:\n%s\ngot:\n%s", exp, got)
17 | }
18 | }
19 |
20 | func TestSnake2CamelOneUnderscore(t *testing.T) {
21 | txt := `const en_urn int = 5
22 | const en_scim int = 44
23 | const en_fail int = 81
24 | const en_main int = 1`
25 |
26 | exp := `const enUrn int = 5
27 | const enScim int = 44
28 | const enFail int = 81
29 | const enMain int = 1`
30 |
31 | got := snake2camel([]byte(txt))
32 | if string(got) != exp {
33 | fmt.Printf("expected length: %d, got %d\n", len(exp), len(got))
34 | t.Fatalf("expected:\n%s\ngot:\n%s", exp, got)
35 | }
36 | }
37 |
38 | func TestSnake2CamelMoreUnderscores(t *testing.T) {
39 | txt := `if (m.p) == (m.pe) {
40 | goto _test_eof
41 | }
42 | switch m.cs {
43 | case 1:
44 | goto st_case_1
45 | }`
46 |
47 | exp := `if (m.p) == (m.pe) {
48 | goto _testEof
49 | }
50 | switch m.cs {
51 | case 1:
52 | goto stCase1
53 | }`
54 |
55 | got := snake2camel([]byte(txt))
56 | if string(got) != exp {
57 | fmt.Printf("expected length: %d, got %d\n", len(exp), len(got))
58 | t.Fatalf("expected:\n%s\ngot:\n%s", exp, got)
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/urn.go:
--------------------------------------------------------------------------------
1 | package urn
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "strings"
7 | )
8 |
9 | const errInvalidURN = "invalid URN: %s"
10 |
11 | // URN represents an Uniform Resource Name.
12 | //
13 | // The general form represented is:
14 | //
15 | // urn::
16 | //
17 | // Details at https://tools.ietf.org/html/rfc2141.
18 | type URN struct {
19 | prefix string // Static prefix. Equal to "urn" when empty.
20 | ID string // Namespace identifier (NID)
21 | SS string // Namespace specific string (NSS)
22 | norm string // Normalized namespace specific string
23 | kind Kind
24 | scim *SCIM
25 | rComponent string // RFC8141
26 | qComponent string // RFC8141
27 | fComponent string // RFC8141
28 | rStart bool // RFC8141
29 | qStart bool // RFC8141
30 | tolower []int
31 | }
32 |
33 | // Normalize turns the receiving URN into its norm version.
34 | //
35 | // Which means: lowercase prefix, lowercase namespace identifier, and immutate namespace specific string chars (except tokens which are lowercased).
36 | func (u *URN) Normalize() *URN {
37 | return &URN{
38 | prefix: "urn",
39 | ID: strings.ToLower(u.ID),
40 | SS: u.norm,
41 | // rComponent: u.rComponent,
42 | // qComponent: u.qComponent,
43 | // fComponent: u.fComponent,
44 | }
45 | }
46 |
47 | // Equal checks the lexical equivalence of the current URN with another one.
48 | func (u *URN) Equal(x *URN) bool {
49 | if x == nil {
50 | return false
51 | }
52 | nu := u.Normalize()
53 | nx := x.Normalize()
54 |
55 | return nu.prefix == nx.prefix && nu.ID == nx.ID && nu.SS == nx.SS
56 | }
57 |
58 | // String reassembles the URN into a valid URN string.
59 | //
60 | // This requires both ID and SS fields to be non-empty.
61 | // Otherwise it returns an empty string.
62 | //
63 | // Default URN prefix is "urn".
64 | func (u *URN) String() string {
65 | var res string
66 | if u.ID != "" && u.SS != "" {
67 | if u.prefix == "" {
68 | res += "urn"
69 | }
70 | res += u.prefix + ":" + u.ID + ":" + u.SS
71 | if u.rComponent != "" {
72 | res += "?+" + u.rComponent
73 | }
74 | if u.qComponent != "" {
75 | res += "?=" + u.qComponent
76 | }
77 | if u.fComponent != "" {
78 | res += "#" + u.fComponent
79 | }
80 | }
81 |
82 | return res
83 | }
84 |
85 | // Parse is responsible to create an URN instance from a byte array matching the correct URN syntax (RFC 2141).
86 | func Parse(u []byte, options ...Option) (*URN, bool) {
87 | urn, err := NewMachine(options...).Parse(u)
88 | if err != nil {
89 | return nil, false
90 | }
91 |
92 | return urn, true
93 | }
94 |
95 | // MarshalJSON marshals the URN to JSON string form (e.g. `"urn:oid:1.2.3.4"`).
96 | func (u URN) MarshalJSON() ([]byte, error) {
97 | return json.Marshal(u.String())
98 | }
99 |
100 | // UnmarshalJSON unmarshals a URN from JSON string form (e.g. `"urn:oid:1.2.3.4"`).
101 | func (u *URN) UnmarshalJSON(bytes []byte) error {
102 | var str string
103 | if err := json.Unmarshal(bytes, &str); err != nil {
104 | return err
105 | }
106 | if value, ok := Parse([]byte(str)); !ok {
107 | return fmt.Errorf(errInvalidURN, str)
108 | } else {
109 | *u = *value
110 | }
111 |
112 | return nil
113 | }
114 |
115 | func (u *URN) IsSCIM() bool {
116 | return u.kind == RFC7643
117 | }
118 |
119 | func (u *URN) SCIM() *SCIM {
120 | if u.kind != RFC7643 {
121 | return nil
122 | }
123 |
124 | return u.scim
125 | }
126 |
127 | func (u *URN) RFC() Kind {
128 | return u.kind
129 | }
130 |
131 | func (u *URN) FComponent() string {
132 | return u.fComponent
133 | }
134 |
135 | func (u *URN) QComponent() string {
136 | return u.qComponent
137 | }
138 |
139 | func (u *URN) RComponent() string {
140 | return u.rComponent
141 | }
142 |
--------------------------------------------------------------------------------
/urn8141.go:
--------------------------------------------------------------------------------
1 | package urn
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | )
7 |
8 | const errInvalidURN8141 = "invalid URN per RFC 8141: %s"
9 |
10 | type URN8141 struct {
11 | *URN
12 | }
13 |
14 | func (u URN8141) MarshalJSON() ([]byte, error) {
15 | return json.Marshal(u.String())
16 | }
17 |
18 | func (u *URN8141) UnmarshalJSON(bytes []byte) error {
19 | var str string
20 | if err := json.Unmarshal(bytes, &str); err != nil {
21 | return err
22 | }
23 | if value, ok := Parse([]byte(str), WithParsingMode(RFC8141Only)); !ok {
24 | return fmt.Errorf(errInvalidURN8141, str)
25 | } else {
26 | *u = URN8141{value}
27 | }
28 |
29 | return nil
30 | }
31 |
--------------------------------------------------------------------------------
/urn8141_test.go:
--------------------------------------------------------------------------------
1 | package urn
2 |
3 | import (
4 | "encoding/json"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | "github.com/stretchr/testify/require"
9 | )
10 |
11 | func TestURN8141JSONMarshaling(t *testing.T) {
12 | t.Run("roundtrip", func(t *testing.T) {
13 | // Marshal
14 | expected := URN8141{
15 | URN: &URN{
16 | ID: "lex",
17 | SS: "it:ministero.giustizia:decreto:1992-07-24;358~art5",
18 | rComponent: "r",
19 | qComponent: "q%D0",
20 | fComponent: "frag",
21 | },
22 | }
23 | bytes, err := json.Marshal(expected)
24 | if !assert.NoError(t, err) {
25 | return
26 | }
27 | require.Equal(t, `"urn:lex:it:ministero.giustizia:decreto:1992-07-24;358~art5?+r?=q%D0#frag"`, string(bytes))
28 | // Unmarshal
29 | var got URN8141
30 | err = json.Unmarshal(bytes, &got)
31 | if !assert.NoError(t, err) {
32 | return
33 | }
34 | assert.Equal(t, expected.String(), got.String())
35 | assert.Equal(t, expected.fComponent, got.FComponent())
36 | assert.Equal(t, expected.qComponent, got.QComponent())
37 | assert.Equal(t, expected.rComponent, got.RComponent())
38 | })
39 |
40 | t.Run("invalid URN", func(t *testing.T) {
41 | var actual URN8141
42 | err := json.Unmarshal([]byte(`"not a URN"`), &actual)
43 | assert.EqualError(t, err, "invalid URN per RFC 8141: not a URN")
44 | })
45 |
46 | t.Run("empty", func(t *testing.T) {
47 | var actual URN8141
48 | err := actual.UnmarshalJSON(nil)
49 | assert.EqualError(t, err, "unexpected end of JSON input")
50 | })
51 | }
52 |
--------------------------------------------------------------------------------
/urn_test.go:
--------------------------------------------------------------------------------
1 | package urn
2 |
3 | import (
4 | "encoding/json"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestDefaultPrefixWhenString(t *testing.T) {
11 | u := &URN{
12 | ID: "pippo",
13 | SS: "pluto",
14 | }
15 |
16 | assert.Equal(t, "urn:pippo:pluto", u.String())
17 | }
18 |
19 | func TestParseSignature(t *testing.T) {
20 | urn, ok := Parse([]byte(``))
21 | assert.Nil(t, urn)
22 | assert.False(t, ok)
23 | }
24 |
25 | func TestJSONMarshaling(t *testing.T) {
26 | t.Run("roundtrip", func(t *testing.T) {
27 | // Marshal
28 | expected := URN{ID: "oid", SS: "1.2.3.4"}
29 | bytes, err := json.Marshal(expected)
30 | if !assert.NoError(t, err) {
31 | return
32 | }
33 | // Unmarshal
34 | var actual URN
35 | err = json.Unmarshal(bytes, &actual)
36 | if !assert.NoError(t, err) {
37 | return
38 | }
39 | assert.Equal(t, expected.String(), actual.String())
40 | })
41 |
42 | t.Run("invalid URN", func(t *testing.T) {
43 | var actual URN
44 | err := json.Unmarshal([]byte(`"not a URN"`), &actual)
45 | assert.EqualError(t, err, "invalid URN: not a URN")
46 | })
47 |
48 | t.Run("empty", func(t *testing.T) {
49 | var actual URN
50 | err := actual.UnmarshalJSON(nil)
51 | assert.EqualError(t, err, "unexpected end of JSON input")
52 | })
53 | }
54 |
--------------------------------------------------------------------------------