Skip to content

Commit da78de5

Browse files
committed
Release v1.23.0
1 parent 1f40d5f commit da78de5

4 files changed

Lines changed: 131 additions & 81 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,10 @@ The focus of this project is minimalism and flexibility. To that end, the featur
4646
### Setup
4747

4848
For SBT simply include:
49-
`libraryDependencies += "org.typelevel" %%% "fabric-core" % "1.22.0"`
49+
`libraryDependencies += "org.typelevel" %%% "fabric-core" % "1.23.0"`
5050

5151
For parsing support include:
52-
`libraryDependencies += "org.typelevel" %%% "fabric-io" % "1.22.0"`
52+
`libraryDependencies += "org.typelevel" %%% "fabric-io" % "1.23.0"`
5353

5454
### Create
5555

build.sbt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ val scala2 = List(scala213, scala212)
99
val scalaVersions = scala3 :: scala2
1010

1111
name := "fabric"
12-
ThisBuild / tlBaseVersion := "1.22"
12+
ThisBuild / tlBaseVersion := "1.23"
1313
ThisBuild / organization := "org.typelevel"
1414
ThisBuild / startYear := Some(2021)
1515
ThisBuild / licenses := Seq(License.MIT)

core/shared/src/main/scala/fabric/define/Definition.scala

Lines changed: 92 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -99,36 +99,48 @@ case class Definition(
9999
deprecated: Boolean = false
100100
) {
101101

102-
/** Returns true if the underlying type is optional (`DefType.Opt`). */
102+
/**
103+
* Returns true if the underlying type is optional (`DefType.Opt`).
104+
*/
103105
def isOpt: Boolean = defType.isOpt
104106

105-
/** Returns true if the underlying type is null (`DefType.Null`). */
107+
/**
108+
* Returns true if the underlying type is null (`DefType.Null`).
109+
*/
106110
def isNull: Boolean = defType.isNull
107111

108-
/** Wraps this Definition in `DefType.Opt` if not already optional. */
112+
/**
113+
* Wraps this Definition in `DefType.Opt` if not already optional.
114+
*/
109115
def opt: Definition = if (isOpt) this else Definition(DefType.Opt(this))
110116

111-
/** Returns a copy with the given description. */
117+
/**
118+
* Returns a copy with the given description.
119+
*/
112120
def describe(desc: String): Definition = copy(description = Some(desc))
113121

114-
/** Returns a copy with the given class name. */
122+
/**
123+
* Returns a copy with the given class name.
124+
*/
115125
def withClassName(cn: String): Definition = copy(className = Some(cn))
116126

117-
/** Validates that a JSON value conforms to this Definition's type structure. */
127+
/**
128+
* Validates that a JSON value conforms to this Definition's type structure.
129+
*/
118130
def validate(value: Json): Boolean = Try(FabricDefinition(value).merge(this)).toOption.contains(this)
119131

120-
/** Generates a template JSON value from this Definition's type structure using the given config. */
132+
/**
133+
* Generates a template JSON value from this Definition's type structure using the given config.
134+
*/
121135
final def template(config: TemplateConfig): Json = template(JsonPath.empty, config)
122136

123137
protected[define] def template(path: JsonPath, config: TemplateConfig): Json = defType match {
124-
case DefType.Obj(map) => obj(map.toList.map { case (key, d) =>
125-
key -> d.template(path \ key, config)
126-
}*)
138+
case DefType.Obj(map) => obj(map.toList.map { case (key, d) => key -> d.template(path \ key, config) }*)
127139
case DefType.Arr(t) => arr(
128-
t.template(path \ 0, config),
129-
t.template(path \ 1, config),
130-
t.template(path \ 2, config)
131-
)
140+
t.template(path \ 0, config),
141+
t.template(path \ 1, config),
142+
t.template(path \ 2, config)
143+
)
132144
case DefType.Opt(t) => t.template(path, config)
133145
case DefType.Str => str(config.string(path))
134146
case DefType.Int => num(config.int(path))
@@ -139,7 +151,8 @@ case class Definition(
139151
case DefType.Poly(values) => config.poly(path, values)
140152
}
141153

142-
/** Merges this Definition with another, combining their type structures. Used by [[FabricDefinition]] when inferring
154+
/**
155+
* Merges this Definition with another, combining their type structures. Used by [[FabricDefinition]] when inferring
143156
* types from multiple JSON values.
144157
*/
145158
def merge(that: Definition): Definition = {
@@ -164,8 +177,7 @@ case class Definition(
164177
case (DefType.Arr(t1), DefType.Arr(t2)) => Definition(DefType.Arr(t1.merge(t2)))
165178
case (DefType.Int, DefType.Dec) => that
166179
case (DefType.Dec, DefType.Int) => this
167-
case (DefType.Opt(inner1), DefType.Opt(inner2)) =>
168-
inner1.merge(inner2) match {
180+
case (DefType.Opt(inner1), DefType.Opt(inner2)) => inner1.merge(inner2) match {
169181
case d if d.isOpt => d
170182
case d => d.opt
171183
}
@@ -188,51 +200,54 @@ case class Definition(
188200

189201
object Definition {
190202

191-
/** Annotates fields of an Obj-typed Definition with format values from `@format` annotations. */
192-
def applyFieldFormats(d: Definition, formats: Map[String, Format]): Definition = {
203+
/**
204+
* Annotates fields of an Obj-typed Definition with format values from `@format` annotations.
205+
*/
206+
def applyFieldFormats(d: Definition, formats: Map[String, Format]): Definition =
193207
if (formats.isEmpty) d
194208
else d.defType match {
195209
case o: DefType.Obj => d.copy(defType = o.copy(map = o.map.map { case (k, v) =>
196-
formats.get(k).fold(k -> v)(fmt => k -> v.copy(format = fmt))
197-
}))
210+
formats.get(k).fold(k -> v)(fmt => k -> v.copy(format = fmt))
211+
}))
198212
case _ => d
199213
}
200-
}
201214

202-
/** Marks fields as deprecated based on `@fieldDeprecated` annotations. */
203-
def applyFieldDeprecations(d: Definition, fields: Set[String]): Definition = {
215+
/**
216+
* Marks fields as deprecated based on `@fieldDeprecated` annotations.
217+
*/
218+
def applyFieldDeprecations(d: Definition, fields: Set[String]): Definition =
204219
if (fields.isEmpty) d
205220
else d.defType match {
206221
case o: DefType.Obj => d.copy(defType = o.copy(map = o.map.map { case (k, v) =>
207-
if (fields.contains(k)) k -> v.copy(deprecated = true) else k -> v
208-
}))
222+
if (fields.contains(k)) k -> v.copy(deprecated = true) else k -> v
223+
}))
209224
case _ => d
210225
}
211-
}
212226

213-
/** Applies default values to fields. Default values are captured at compile time from case class defaults. */
214-
def applyFieldDefaults(d: Definition, defaults: Map[String, Json]): Definition = {
227+
/**
228+
* Applies default values to fields. Default values are captured at compile time from case class defaults.
229+
*/
230+
def applyFieldDefaults(d: Definition, defaults: Map[String, Json]): Definition =
215231
if (defaults.isEmpty) d
216232
else d.defType match {
217233
case o: DefType.Obj => d.copy(defType = o.copy(map = o.map.map { case (k, v) =>
218-
defaults.get(k).fold(k -> v)(dv => k -> v.copy(defaultValue = Some(dv)))
219-
}))
234+
defaults.get(k).fold(k -> v)(dv => k -> v.copy(defaultValue = Some(dv)))
235+
}))
220236
case _ => d
221237
}
222-
}
223238

224-
/** Annotates fields of an Obj-typed Definition with their generic type parameter names. Used by macro-generated RW
239+
/**
240+
* Annotates fields of an Obj-typed Definition with their generic type parameter names. Used by macro-generated RW
225241
* instances to connect fields like `value: T` back to the type parameter `"T"`.
226242
*/
227-
def applyGenericNames(d: Definition, fieldGenericNames: Map[String, String]): Definition = {
243+
def applyGenericNames(d: Definition, fieldGenericNames: Map[String, String]): Definition =
228244
if (fieldGenericNames.isEmpty) d
229245
else d.defType match {
230246
case o: DefType.Obj => d.copy(defType = o.copy(map = o.map.map { case (k, v) =>
231-
fieldGenericNames.get(k).fold(k -> v)(gn => k -> v.copy(genericName = Some(gn)))
232-
}))
247+
fieldGenericNames.get(k).fold(k -> v)(gn => k -> v.copy(genericName = Some(gn)))
248+
}))
233249
case _ => d
234250
}
235-
}
236251

237252
implicit def rw: RW[Definition] = RW.from[Definition](r = toJson, w = fromJson, d = Definition(DefType.Json))
238253

@@ -244,9 +259,9 @@ object Definition {
244259
private def toJson(d: Definition): Json = {
245260
val base = d.defType match {
246261
case DefType.Obj(map) => obj(
247-
"type" -> str("object"),
248-
"values" -> fabric.Obj(map.map { case (key, inner) => key -> toJson(inner) })
249-
)
262+
"type" -> str("object"),
263+
"values" -> fabric.Obj(map.map { case (key, inner) => key -> toJson(inner) })
264+
)
250265
case DefType.Arr(t) => obj("type" -> str("array"), "value" -> toJson(t))
251266
case DefType.Opt(t) => obj("type" -> str("optional"), "value" -> toJson(t))
252267
case DefType.Str => obj("type" -> str("string"))
@@ -256,22 +271,29 @@ object Definition {
256271
case DefType.Json => obj("type" -> str("json"))
257272
case DefType.Null => obj("type" -> str("null"))
258273
case DefType.Poly(values) => obj(
259-
"type" -> str("poly"),
260-
"values" -> fabric.Obj(values.map { case (key, inner) => key -> toJson(inner) })
261-
)
274+
"type" -> str("poly"),
275+
"values" -> fabric.Obj(values.map { case (key, inner) => key -> toJson(inner) })
276+
)
262277
}
263-
var result = withOptional(withOptional(withOptional(base, "className", d.className), "description", d.description), "genericName", d.genericName)
264-
if (d.genericTypes.nonEmpty)
265-
result = result.merge(fabric.Obj("genericTypes" -> Arr(d.genericTypes.map(gt =>
266-
obj("name" -> str(gt.name), "definition" -> toJson(gt.definition))
267-
).toVector))).asObj
268-
if (d.format != Format.Raw)
269-
result = result.merge(fabric.Obj("format" -> str(d.format.name))).asObj
278+
var result = withOptional(
279+
withOptional(withOptional(base, "className", d.className), "description", d.description),
280+
"genericName",
281+
d.genericName
282+
)
283+
if (d.genericTypes.nonEmpty) result = result
284+
.merge(
285+
fabric.Obj(
286+
"genericTypes" -> Arr(
287+
d.genericTypes.map(gt => obj("name" -> str(gt.name), "definition" -> toJson(gt.definition))).toVector
288+
)
289+
)
290+
)
291+
.asObj
292+
if (d.format != Format.Raw) result = result.merge(fabric.Obj("format" -> str(d.format.name))).asObj
270293
d.defaultValue.foreach { dv =>
271294
result = result.merge(fabric.Obj("default" -> dv)).asObj
272295
}
273-
if (d.deprecated)
274-
result = result.merge(fabric.Obj("deprecated" -> bool(true))).asObj
296+
if (d.deprecated) result = result.merge(fabric.Obj("deprecated" -> bool(true))).asObj
275297
result
276298
}
277299

@@ -280,31 +302,33 @@ object Definition {
280302
val cn = o.get("className").map(_.asString)
281303
val desc = o.get("description").map(_.asString)
282304
val dt = o.value("type").asString match {
283-
case "object" =>
284-
DefType.Obj(o.value("values").asMap.map { case (key, value) => key -> fromJson(value) })
305+
case "object" => DefType.Obj(o.value("values").asMap.map { case (key, value) => key -> fromJson(value) })
285306
case "array" => DefType.Arr(fromJson(o.value("value")))
286307
case "optional" => DefType.Opt(fromJson(o.value("value")))
287308
case "string" => DefType.Str
288309
case "numeric" => o.value("precision").asString match {
289-
case "integer" => DefType.Int
290-
case "decimal" => DefType.Dec
291-
}
310+
case "integer" => DefType.Int
311+
case "decimal" => DefType.Dec
312+
}
292313
case "boolean" => DefType.Bool
293314
case "json" => DefType.Json
294315
case "null" => DefType.Null
295316
case "enum" =>
296317
val values = o.value("values").asVector.toList
297318
DefType.Poly(VectorMap(values.map(v => v.asString -> Definition(DefType.Null))*))
298319

299-
case "poly" =>
300-
DefType.Poly(o.value("values").asMap.map { case (key, json) => key -> fromJson(json) })
320+
case "poly" => DefType.Poly(o.value("values").asMap.map { case (key, json) => key -> fromJson(json) })
301321
}
302322
val gn = o.get("genericName").map(_.asString)
303-
val gt = o.get("genericTypes").map(_.asVector.toList.map { gtJson =>
304-
val gtObj = gtJson.asObj
305-
GenericType(gtObj.value("name").asString, fromJson(gtObj.value("definition")))
306-
}).getOrElse(Nil)
307-
val fmt = o.get("format").map(f => Format.values.find(_.name == f.asString).getOrElse(Format.Raw)).getOrElse(Format.Raw)
323+
val gt = o
324+
.get("genericTypes")
325+
.map(_.asVector.toList.map { gtJson =>
326+
val gtObj = gtJson.asObj
327+
GenericType(gtObj.value("name").asString, fromJson(gtObj.value("definition")))
328+
})
329+
.getOrElse(Nil)
330+
val fmt =
331+
o.get("format").map(f => Format.values.find(_.name == f.asString).getOrElse(Format.Raw)).getOrElse(Format.Raw)
308332
val dv = o.get("default")
309333
val dep = o.get("deprecated").exists(_.asBool.value)
310334
Definition(dt, cn, desc, gt, gn, fmt, dv, dep)
@@ -332,7 +356,9 @@ object GenericType {
332356
implicit lazy val rw: RW[GenericType] = RW.from[GenericType](
333357
r = gt => obj("name" -> str(gt.name), "definition" -> gt.definition.json),
334358
w = j => GenericType(j("name").asString, j("definition").as[Definition]),
335-
d = Definition(DefType.Obj("name" -> Definition(DefType.Str), "definition" -> Definition(DefType.Json)),
336-
className = Some("fabric.define.GenericType"))
359+
d = Definition(
360+
DefType.Obj("name" -> Definition(DefType.Str), "definition" -> Definition(DefType.Json)),
361+
className = Some("fabric.define.GenericType")
362+
)
337363
)
338364
}

core/shared/src/main/scala/fabric/define/Format.scala

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -46,39 +46,63 @@ object Format {
4646

4747
val values: List[Format] = List(Raw, Date, DateTime, Time, Email, Uri, Uuid, Hostname, Ipv4, Ipv6, Binary, Password)
4848

49-
/** No specific format — the value is used as-is. */
49+
/**
50+
* No specific format — the value is used as-is.
51+
*/
5052
case object Raw extends Format { val name = "raw" }
5153

52-
/** A date string (e.g. `"2024-01-15"`). OpenAPI format: `date`. */
54+
/**
55+
* A date string (e.g. `"2024-01-15"`). OpenAPI format: `date`.
56+
*/
5357
case object Date extends Format { val name = "date" }
5458

55-
/** A date-time string (e.g. `"2024-01-15T10:30:00Z"`). OpenAPI format: `date-time`. */
59+
/**
60+
* A date-time string (e.g. `"2024-01-15T10:30:00Z"`). OpenAPI format: `date-time`.
61+
*/
5662
case object DateTime extends Format { val name = "date-time" }
5763

58-
/** A time string (e.g. `"10:30:00"`). */
64+
/**
65+
* A time string (e.g. `"10:30:00"`).
66+
*/
5967
case object Time extends Format { val name = "time" }
6068

61-
/** An email address. OpenAPI format: `email`. */
69+
/**
70+
* An email address. OpenAPI format: `email`.
71+
*/
6272
case object Email extends Format { val name = "email" }
6373

64-
/** A URI/URL. OpenAPI format: `uri`. */
74+
/**
75+
* A URI/URL. OpenAPI format: `uri`.
76+
*/
6577
case object Uri extends Format { val name = "uri" }
6678

67-
/** A UUID string (e.g. `"550e8400-e29b-41d4-a716-446655440000"`). OpenAPI format: `uuid`. */
79+
/**
80+
* A UUID string (e.g. `"550e8400-e29b-41d4-a716-446655440000"`). OpenAPI format: `uuid`.
81+
*/
6882
case object Uuid extends Format { val name = "uuid" }
6983

70-
/** A hostname. OpenAPI format: `hostname`. */
84+
/**
85+
* A hostname. OpenAPI format: `hostname`.
86+
*/
7187
case object Hostname extends Format { val name = "hostname" }
7288

73-
/** An IPv4 address. OpenAPI format: `ipv4`. */
89+
/**
90+
* An IPv4 address. OpenAPI format: `ipv4`.
91+
*/
7492
case object Ipv4 extends Format { val name = "ipv4" }
7593

76-
/** An IPv6 address. OpenAPI format: `ipv6`. */
94+
/**
95+
* An IPv6 address. OpenAPI format: `ipv6`.
96+
*/
7797
case object Ipv6 extends Format { val name = "ipv6" }
7898

79-
/** Binary data (e.g. base64-encoded). OpenAPI format: `binary`. */
99+
/**
100+
* Binary data (e.g. base64-encoded). OpenAPI format: `binary`.
101+
*/
80102
case object Binary extends Format { val name = "binary" }
81103

82-
/** A password/secret value (hints to UI to mask the field). OpenAPI format: `password`. */
104+
/**
105+
* A password/secret value (hints to UI to mask the field). OpenAPI format: `password`.
106+
*/
83107
case object Password extends Format { val name = "password" }
84108
}

0 commit comments

Comments
 (0)